using System.Collections.ObjectModel; using System.Drawing; using System.Drawing.Imaging; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using View_by_Distance.Metadata.Models; using View_by_Distance.Property.Models; using View_by_Distance.Property.Models.Stateless; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Properties; namespace View_by_Distance.Resize.Models; /// // Dictionary /// public class C_Resize { public List AngleBracketCollection { get; } protected readonly string _FileNameExtension; public string FileNameExtension => _FileNameExtension; private readonly string _Original; private readonly int _TempResolutionWidth; private readonly int _TempResolutionHeight; private readonly string[] _ValidResolutions; private readonly ASCIIEncoding _ASCIIEncoding; private readonly bool _OverrideForResizeImages; private readonly ImageCodecInfo _ImageCodecInfo; private readonly int _OutputResolutionWidthIndex; private readonly bool _PropertiesChangedForResize; private readonly ConstructorInfo _ConstructorInfo; private readonly int _OutputResolutionHeightIndex; private readonly EncoderParameters _EncoderParameters; private readonly int _OutputResolutionOrientationIndex; private readonly bool _ForceResizeLastWriteTimeToCreationTime; private readonly IPropertyConfiguration _PropertyConfiguration; private readonly JsonSerializerOptions _WriteIndentedJsonSerializerOptions; private readonly ReadOnlyDictionary>[] _ResultSingletonFileGroups; public C_Resize(IPropertyConfiguration propertyConfiguration, bool forceResizeLastWriteTimeToCreationTime, bool overrideForResizeImages, bool propertiesChangedForResize, string[] validResolutions, ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) { _Original = "Original"; _TempResolutionWidth = 3; _TempResolutionHeight = 4; _OutputResolutionWidthIndex = 0; _ImageCodecInfo = imageCodecInfo; _OutputResolutionHeightIndex = 1; _ASCIIEncoding = new ASCIIEncoding(); _ValidResolutions = validResolutions; _OutputResolutionOrientationIndex = 2; _EncoderParameters = encoderParameters; _FileNameExtension = filenameExtension; AngleBracketCollection = []; _PropertyConfiguration = propertyConfiguration; _OverrideForResizeImages = overrideForResizeImages; _PropertiesChangedForResize = propertiesChangedForResize; _ForceResizeLastWriteTimeToCreationTime = forceResizeLastWriteTimeToCreationTime; _ResultSingletonFileGroups = [new(new Dictionary>())]; _WriteIndentedJsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true }; ConstructorInfo? constructorInfo = typeof(PropertyItem).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, [], null) ?? throw new Exception(); _ConstructorInfo = constructorInfo; } public static byte[] GetBitmapData(Bitmap bitmap) { byte[] results; #pragma warning disable CA1416 Rectangle rectangle = new(0, 0, bitmap.Width, bitmap.Height); BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, bitmap.PixelFormat); IntPtr intPtr = bitmapData.Scan0; int length = bitmapData.Stride * bitmap.Height; results = new byte[length]; Marshal.Copy(intPtr, results, 0, length); bitmap.UnlockBits(bitmapData); #pragma warning restore CA1416 return results; } public static (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) GetPngLowQuality() { (ImageCodecInfo, EncoderParameters, string) result; #pragma warning disable CA1416 ImageFormat imageFormat = ImageFormat.Png; ImageCodecInfo[] imageCodecInfoCollection = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo imageCodecInfo = (from l in imageCodecInfoCollection where l.FormatID == imageFormat.Guid select l).First(); EncoderParameters encoderParameters = new(1); encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75L); if (string.IsNullOrEmpty(imageCodecInfo.FilenameExtension)) throw new NullReferenceException(nameof(imageCodecInfo.FilenameExtension)); result = new(imageCodecInfo, encoderParameters, imageCodecInfo.FilenameExtension.Split(';')[0].ToLower()[1..]); #pragma warning restore CA1416 return result; } public static (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) GetJpegLowQuality() { (ImageCodecInfo, EncoderParameters, string) result; #pragma warning disable CA1416 ImageFormat imageFormat = ImageFormat.Jpeg; ImageCodecInfo[] imageCodecInfoCollection = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo imageCodecInfo = (from l in imageCodecInfoCollection where l.FormatID == imageFormat.Guid select l).First(); EncoderParameters encoderParameters = new(1); encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75L); if (string.IsNullOrEmpty(imageCodecInfo.FilenameExtension)) throw new NullReferenceException(nameof(imageCodecInfo.FilenameExtension)); result = new(imageCodecInfo, encoderParameters, imageCodecInfo.FilenameExtension.Split(';')[0].ToLower()[1..]); #pragma warning restore CA1416 return result; } public static (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) GetTuple(string outputExtension, int outputQuality) { (ImageCodecInfo, EncoderParameters, string) result; #pragma warning disable CA1416 ImageFormat imageFormat = outputExtension switch { ".gif" => ImageFormat.Gif, ".jfif" => ImageFormat.Jpeg, ".jpe" => ImageFormat.Jpeg, ".jpeg" => ImageFormat.Jpeg, ".jpg" => ImageFormat.Jpeg, ".png" => ImageFormat.Png, ".tif" => ImageFormat.Tiff, ".tiff" => ImageFormat.Tiff, _ => throw new Exception(), }; ImageCodecInfo[] imageCodecInfoCollection = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo imageCodecInfo = (from l in imageCodecInfoCollection where l.FormatID == imageFormat.Guid select l).First(); EncoderParameters encoderParameters = new(1); // encoderParameters.Param[0] = New EncoderParameter(Encoder.Quality, CType(75L, Int32)) 'Default // encoderParameters.Param[0] = New EncoderParameter(Encoder.Quality, CType(95L, Int32)) 'Paint encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, outputQuality); if (string.IsNullOrEmpty(imageCodecInfo.FilenameExtension)) throw new NullReferenceException(nameof(imageCodecInfo.FilenameExtension)); result = new(imageCodecInfo, encoderParameters, imageCodecInfo.FilenameExtension.Split(';')[0].ToLower()[1..]); #pragma warning restore CA1416 return result; } public FileHolder GetResizedFileHolder(string cResultsFullGroupDirectory, Item item, bool outputResolutionHasNumber, int id) => GetResizedFileHolder(cResultsFullGroupDirectory, item.FilePath, outputResolutionHasNumber, $"{id}{item.FilePath.ExtensionLowered}"); private FileHolder GetResizedFileHolder(string cResultsFullGroupDirectory, FilePath filePath, bool outputResolutionHasNumber, string fileName) { FileHolder result; if (outputResolutionHasNumber) result = Shared.Models.Stateless.Methods.IFileHolder.Get(Path.Combine(AngleBracketCollection[0].Replace("<>", _PropertyConfiguration.ResultContent), fileName)); else { CombinedEnumAndIndex cei = Shared.Models.Stateless.Methods.IPath.GetCombinedEnumAndIndex(_PropertyConfiguration, filePath); result = Shared.Models.Stateless.Methods.IFileHolder.Get(Path.Combine(cResultsFullGroupDirectory, _PropertyConfiguration.ResultContent, cei.Combined, fileName)); } return result; } public override string ToString() { string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); return result; } public void Update(string cResultsFullGroupDirectory) { ReadOnlyDictionary>> keyValuePairs = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(_PropertyConfiguration, cResultsFullGroupDirectory, [_PropertyConfiguration.ResultSingleton]); foreach (KeyValuePair>> keyValuePair in keyValuePairs) { if (keyValuePair.Key == _PropertyConfiguration.ResultSingleton) _ResultSingletonFileGroups[0] = keyValuePair.Value; else throw new Exception(); } } public void SetAngleBracketCollection(string cResultsFullGroupDirectory, string sourceDirectory) { AngleBracketCollection.Clear(); AngleBracketCollection.AddRange(IResult.GetDirectoryInfoCollection(_PropertyConfiguration, sourceDirectory, cResultsFullGroupDirectory, contentDescription: "Resized image", singletonDescription: "Resize dimensions for each resolution", collectionDescription: string.Empty, converted: true)); } public FileHolder GetResizedFileHolder(string cResultsFullGroupDirectory, Item item, bool outputResolutionHasNumber) => GetResizedFileHolder(cResultsFullGroupDirectory, item.FilePath, outputResolutionHasNumber, item.FilePath.Name); public Dictionary GetResizeKeyValuePairs(Configuration configuration, string cResultsFullGroupDirectory, FilePath filePath, List> subFileTuples, List parseExceptions, ExifDirectory exifDirectory, MappingFromItem mappingFromItem) { Dictionary? results; string json; string[] changesFrom = [nameof(A_Property), nameof(B_Metadata)]; List dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList(); CombinedEnumAndIndex cei = Shared.Models.Stateless.Methods.IPath.GetCombinedEnumAndIndex(_PropertyConfiguration, filePath); string fileName = $"{mappingFromItem.FilePath.NameWithoutExtension}{mappingFromItem.FilePath.ExtensionLowered}.json"; string directory = _ResultSingletonFileGroups[0][cei.Enum][cei.Index]; FileInfo fileInfo = new(Path.Combine(directory, fileName)); MoveIf(fileName, cei, directory, fileInfo); if (_ForceResizeLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete"))) { File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName); fileInfo.Refresh(); } if (_ForceResizeLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime) { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); } if (_PropertiesChangedForResize) results = null; else if (!fileInfo.Exists) results = null; else if (!fileInfo.FullName.EndsWith(".json") && !fileInfo.FullName.EndsWith(".old")) throw new ArgumentException("must be a *.json file"); else if (dateTimes.Count != 0 && dateTimes.Max() > fileInfo.LastWriteTime) results = null; else { json = File.ReadAllText(fileInfo.FullName); try { results = JsonSerializer.Deserialize>(json); if (results is null) throw new Exception(); subFileTuples.Add(new Tuple(nameof(C_Resize), fileInfo.LastWriteTime)); } catch (Exception) { results = null; parseExceptions.Add(nameof(C_Resize)); } } if (results is null) { results = GetImageResizes(exifDirectory); json = JsonSerializer.Serialize(results, _WriteIndentedJsonSerializerOptions); bool updateDateWhenMatches = dateTimes.Count != 0 && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime; DateTime? dateTime = !updateDateWhenMatches ? null : dateTimes.Max(); if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: dateTime)) { if (!_ForceResizeLastWriteTimeToCreationTime) subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); else { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); subFileTuples.Add(new Tuple(nameof(C_Resize), fileInfo.CreationTime)); } } } return results; } private static void MoveIf(string fileName, CombinedEnumAndIndex cei, string directory, FileInfo fileInfo) { string[] segments = directory.Split(cei.Combined); string? checkDirectory = segments.Length == 1 ? Path.Combine(segments[0], $"{cei.Combined[2..]}") : segments.Length == 2 ? $"{segments[0]}{cei.Combined[2..]}{segments[1]}" : null; if (checkDirectory is not null && Directory.Exists(checkDirectory)) { string checkFile = Path.Combine(checkDirectory, fileName); if (File.Exists(checkFile)) { File.Move(checkFile, fileInfo.FullName); fileInfo.Refresh(); } } } private Dictionary GetImageResizes(ExifDirectory exifDirectory) { Dictionary results = []; int[] desired; int checkWidth; int checkHeight; int desiredWidth; int desiredHeight; int? orientation = Shared.Models.Stateless.Methods.IMetaBase.GetOrientation(exifDirectory.ExifBaseDirectories); if (exifDirectory is null || orientation is null || exifDirectory.Width is null || exifDirectory.Height is null) throw new NotSupportedException(); checkWidth = exifDirectory.Width.Value; checkHeight = exifDirectory.Height.Value; if (!_ValidResolutions.Contains(_Original)) results.Add(_Original, [checkWidth, checkHeight, orientation.Value]); foreach (string validResolution in _ValidResolutions) { if (validResolution == _Original) { desiredWidth = checkWidth; desiredHeight = checkHeight; } else { desired = GetCollection(validResolution); desiredWidth = desired[0]; desiredHeight = desired[1]; } if (checkWidth <= desiredWidth && checkHeight <= desiredHeight) results.Add(validResolution, [checkWidth, checkHeight, orientation.Value]); else { if (desiredWidth != desiredHeight) { if (checkWidth * desiredHeight > desiredWidth * checkHeight) results.Add(validResolution, [desiredWidth, Convert.ToInt32(desiredWidth * checkHeight / (double)checkWidth), orientation.Value]); else results.Add(validResolution, [Convert.ToInt32(desiredHeight * checkWidth / (double)checkHeight), desiredHeight, orientation.Value]); } else { if (checkWidth * desiredHeight <= desiredWidth * checkHeight) results.Add(validResolution, [desiredWidth, desiredHeight, orientation.Value, desiredWidth, Convert.ToInt32(desiredWidth * checkHeight / (double)checkWidth)]); else results.Add(validResolution, [desiredWidth, desiredHeight, orientation.Value, Convert.ToInt32(desiredHeight * checkWidth / (double)checkHeight), desiredHeight]); } } } return results; } private static int[] GetCollection(string outputResolution) { List results = []; string[] segments = outputResolution.Split('x'); results.Add(int.Parse(segments[0])); results.Add(int.Parse(segments[1])); return results.ToArray(); } public void SaveResizedSubfile(Configuration configuration, string outputResolution, string cResultsFullGroupDirectory, List> subFileTuples, Item item, ExifDirectory exifDirectory, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize) { if (mappingFromItem.ResizedFileHolder is null) throw new NullReferenceException(nameof(mappingFromItem.ResizedFileHolder)); #pragma warning disable CA1854 if (!outputResolutionToResize.ContainsKey(_Original)) throw new Exception(); if (!outputResolutionToResize.ContainsKey(outputResolution)) throw new Exception(); #pragma warning restore CA1854 FileInfo fileInfo = new(mappingFromItem.ResizedFileHolder.FullName); bool check = false; int[] resize = outputResolutionToResize[outputResolution]; int outputResolutionWidth = resize[_OutputResolutionWidthIndex]; int outputResolutionHeight = resize[_OutputResolutionHeightIndex]; int outputResolutionOrientation = resize[_OutputResolutionOrientationIndex]; int[] originalCollection = outputResolutionToResize[_Original]; string[] changesFrom = [nameof(A_Property), nameof(B_Metadata), nameof(C_Resize)]; List dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList(); if (_OverrideForResizeImages) check = true; else if (!fileInfo.Exists) check = true; if (outputResolutionWidth == originalCollection[_OutputResolutionWidthIndex] && outputResolutionHeight == originalCollection[_OutputResolutionHeightIndex] && outputResolutionOrientation == originalCollection[_OutputResolutionOrientationIndex]) { if (!check && dateTimes.Count != 0 && dateTimes.Max() > fileInfo.CreationTime.AddDays(1)) check = true; if (check) { // if (fileInfo.Exists) // File.Delete(fileInfo.FullName); // File.Copy(mappingFromItem.FilePath.FullName, fileInfo.FullName); // item.SetResizedFileHolder(_FileNameExtension, Shared.Models.Stateless.Methods.IFileHolder.Refresh(mappingFromItem.ResizedFileHolder)); // subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); } } else { if (!check && dateTimes.Count != 0 && dateTimes.Max() > fileInfo.LastWriteTime) check = true; if (check) { SaveResizedSubfile(exifDirectory, mappingFromItem, resize); item.SetResizedFileHolder(_FileNameExtension, Shared.Models.Stateless.Methods.IFileHolder.Refresh(mappingFromItem.ResizedFileHolder)); subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); } } } private void SaveResizedSubfile(ExifDirectory exifDirectory, MappingFromItem mappingFromItem, int[] resize) { string dateTimeFormat = IProperty.DateTimeFormat; DateTime? dateTime = Shared.Models.Stateless.Methods.IDate.GetDateTimeOriginal(exifDirectory); dateTime ??= Shared.Models.Stateless.Methods.IDate.GetMinimum(exifDirectory); string dateTimeValue = dateTime.Value.ToString(dateTimeFormat); byte[] bytes = _ASCIIEncoding.GetBytes(dateTimeValue); if (_ASCIIEncoding.GetString(bytes, 0, bytes.Length) != dateTimeValue) throw new Exception(); if (resize.Length == 3) SaveResizedSubfile3(mappingFromItem, resize, bytes); else if (resize.Length == 5) SaveResizedSubfile5(mappingFromItem, resize, bytes); else throw new Exception(); } private void SaveResizedSubfile3(MappingFromItem mappingFromItem, int[] resize, byte[] bytes) { Bitmap bitmap; #pragma warning disable CA1416 int outputResolutionWidth = resize[_OutputResolutionWidthIndex]; using Bitmap temp = new(mappingFromItem.FilePath.FullName, useIcm: false); int outputResolutionHeight = resize[_OutputResolutionHeightIndex]; PropertyItem[] propertyItems = temp.PropertyItems; int outputResolutionOrientation = resize[_OutputResolutionOrientationIndex]; bitmap = new(temp, outputResolutionWidth, outputResolutionHeight); switch (outputResolutionOrientation) // exif 274 { case 0: break; case 1: break; // 1 = Horizontal (normal) case 2: bitmap.RotateFlip(RotateFlipType.RotateNoneFlipX); break; // 2 = Mirror horizontal case 3: bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); break; // 3 = Rotate 180 case 4: bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); break; // 4 = Mirror vertical case 5: bitmap.RotateFlip(RotateFlipType.Rotate270FlipX); break; // 5 = Mirror horizontal and rotate 270 CW case 6: bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone); break; // 6 = Rotate 90 CW case 7: bitmap.RotateFlip(RotateFlipType.Rotate90FlipX); break; // 7 = Mirror horizontal and rotate 90 CW case 8: bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone); break; // 8 = Rotate 270 CW default: break; } CopyPropertyItems(bytes, propertyItems, bitmap); bitmap.Save(mappingFromItem.ResizedFileHolder.FullName, _ImageCodecInfo, _EncoderParameters); bitmap.Dispose(); #pragma warning restore CA1416 } private void CopyPropertyItems(byte[] bytes, PropertyItem[] propertyItems, Bitmap bitmap) { bool hasId = false; int id = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagDateTimeDigitized; int imageWidth = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagImageWidth; int imageHeight = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagImageHeight; int orientation = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagOrientation; #pragma warning disable CA1416 foreach (PropertyItem propertyItem in propertyItems) { if (propertyItem.Id == id) hasId = true; else if (propertyItem.Id == imageWidth) continue; else if (propertyItem.Id == imageHeight) continue; else if (propertyItem.Id == orientation) continue; bitmap.SetPropertyItem(propertyItem); } if (!hasId) { PropertyItem propertyItem = (PropertyItem)_ConstructorInfo.Invoke(null); propertyItem.Id = id; propertyItem.Len = bytes.Length; propertyItem.Type = 2; propertyItem.Value = bytes; bitmap.SetPropertyItem(propertyItem); } #pragma warning restore CA1416 } private void SaveResizedSubfile5(MappingFromItem mappingFromItem, int[] resize, byte[] bytes) { Bitmap bitmap; #pragma warning disable CA1416 using Bitmap temp = new(mappingFromItem.FilePath.FullName, useIcm: false); PropertyItem[] propertyItems = temp.PropertyItems; int tempResolutionWidth = resize[_TempResolutionWidth]; int tempResolutionHeight = resize[_TempResolutionHeight]; int outputResolutionWidth = resize[_OutputResolutionWidthIndex]; int outputResolutionHeight = resize[_OutputResolutionHeightIndex]; int outputResolutionOrientation = resize[_OutputResolutionOrientationIndex]; bitmap = new(temp, tempResolutionWidth, tempResolutionHeight); switch (outputResolutionOrientation) // exif 274 { case 0: break; case 1: break; // 1 = Horizontal (normal) case 2: bitmap.RotateFlip(RotateFlipType.RotateNoneFlipX); break; // 2 = Mirror horizontal case 3: bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); break; // 3 = Rotate 180 case 4: bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); break; // 4 = Mirror vertical case 5: bitmap.RotateFlip(RotateFlipType.Rotate270FlipX); break; // 5 = Mirror horizontal and rotate 270 CW case 6: bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone); break; // 6 = Rotate 90 CW case 7: bitmap.RotateFlip(RotateFlipType.Rotate90FlipX); break; // 7 = Mirror horizontal and rotate 90 CW case 8: bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone); break; // 8 = Rotate 270 CW default: break; } Bitmap preRotated; Rectangle rectangle; if (tempResolutionHeight < tempResolutionWidth) rectangle = new Rectangle((int)((tempResolutionWidth - outputResolutionWidth) * .5), 0, outputResolutionWidth, outputResolutionHeight); else rectangle = new Rectangle(0, (int)((tempResolutionHeight - outputResolutionHeight) * .5), outputResolutionWidth, outputResolutionHeight); using (preRotated = new Bitmap(outputResolutionWidth, outputResolutionHeight)) { using (Graphics graphics = Graphics.FromImage(preRotated)) graphics.DrawImage(bitmap, new Rectangle(0, 0, outputResolutionWidth, outputResolutionHeight), rectangle, GraphicsUnit.Pixel); CopyPropertyItems(bytes, propertyItems, bitmap); bitmap.Save(mappingFromItem.ResizedFileHolder.FullName, _ImageCodecInfo, _EncoderParameters); } bitmap.Dispose(); #pragma warning restore CA1416 } }