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.Stateless; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Stateless; 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 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 JsonSerializerOptions _WriteIndentedJsonSerializerOptions; public C_Resize(bool forceResizeLastWriteTimeToCreationTime, bool overrideForResizeImages, bool propertiesChangedForResize, string[] validResolutions, ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) { _TempResolutionWidth = 3; _TempResolutionHeight = 4; _OutputResolutionWidthIndex = 0; _ImageCodecInfo = imageCodecInfo; _OutputResolutionHeightIndex = 1; _ASCIIEncoding = new ASCIIEncoding(); _ValidResolutions = validResolutions; _OutputResolutionOrientationIndex = 2; _EncoderParameters = encoderParameters; _FileNameExtension = filenameExtension; AngleBracketCollection = new List(); _OverrideForResizeImages = overrideForResizeImages; _PropertiesChangedForResize = propertiesChangedForResize; _ForceResizeLastWriteTimeToCreationTime = forceResizeLastWriteTimeToCreationTime; _WriteIndentedJsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true }; ConstructorInfo? constructorInfo = typeof(PropertyItem).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, Array.Empty(), null); if (constructorInfo is null) throw new Exception(); _ConstructorInfo = constructorInfo; } public override string ToString() { string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); return result; } public void SetAngleBracketCollection(Property.Models.Configuration configuration, string cResultsFullGroupDirectory, string sourceDirectory) { AngleBracketCollection.Clear(); AngleBracketCollection.AddRange(IResult.GetDirectoryInfoCollection(configuration, sourceDirectory, cResultsFullGroupDirectory, contentDescription: "Resized image", singletonDescription: "Resize dimensions for each resolution", collectionDescription: string.Empty, converted: true)); } #pragma warning disable CA1416 public static (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) GetGifLowQuality() { (ImageCodecInfo, EncoderParameters, string) result; System.Drawing.Imaging.ImageFormat imageFormat = System.Drawing.Imaging.ImageFormat.Gif; ImageCodecInfo imageCodecInfo = (from l in ImageCodecInfo.GetImageEncoders() 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..]); return result; } public static (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) GetPngLowQuality() { (ImageCodecInfo, EncoderParameters, string) result; System.Drawing.Imaging.ImageFormat imageFormat = System.Drawing.Imaging.ImageFormat.Png; ImageCodecInfo imageCodecInfo = (from l in ImageCodecInfo.GetImageEncoders() 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..]); return result; } public static (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) GetTuple(string outputExtension, int outputQuality) { (ImageCodecInfo, EncoderParameters, string) result; System.Drawing.Imaging.ImageFormat imageFormat = outputExtension switch { ".gif" => System.Drawing.Imaging.ImageFormat.Gif, ".jfif" => System.Drawing.Imaging.ImageFormat.Jpeg, ".jpe" => System.Drawing.Imaging.ImageFormat.Jpeg, ".jpeg" => System.Drawing.Imaging.ImageFormat.Jpeg, ".jpg" => System.Drawing.Imaging.ImageFormat.Jpeg, ".png" => System.Drawing.Imaging.ImageFormat.Png, ".tif" => System.Drawing.Imaging.ImageFormat.Tiff, ".tiff" => System.Drawing.Imaging.ImageFormat.Tiff, _ => throw new Exception(), }; ImageCodecInfo imageCodecInfo = (from l in ImageCodecInfo.GetImageEncoders() where l.FormatID == imageFormat.Guid select l).First(); EncoderParameters encoderParameters = new(1); // encoderParameters.Param[0] = New EncoderParameter(System.Drawing.Imaging.Encoder.Quality, CType(75L, Int32)) 'Default // encoderParameters.Param[0] = New EncoderParameter(System.Drawing.Imaging.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..]); return result; } public static byte[] GetBitmapData(Bitmap bitmap) { byte[] results; 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); return results; } private void CopyPropertyItems(byte[] bytes, PropertyItem[] propertyItems, Bitmap bitmap) { bool hasId = false; int id = (int)IExif.Tags.DateTimeDigitized; int imageWidth = (int)IExif.Tags.ImageWidth; int imageLength = (int)IExif.Tags.ImageLength; int orientation = (int)IExif.Tags.Orientation; foreach (PropertyItem propertyItem in propertyItems) { if (propertyItem.Id == id) hasId = true; else if (propertyItem.Id == imageWidth) continue; else if (propertyItem.Id == imageLength) 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); } } private byte[] SaveResizedSubfile3(MappingFromItem mappingFromItem, int[] resize, bool returnAndDoNotWrite, byte[] bytes) { byte[] results; Bitmap bitmap; int outputResolutionWidth = resize[_OutputResolutionWidthIndex]; using Bitmap temp = new(mappingFromItem.ImageFileHolder.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; } if (returnAndDoNotWrite) results = GetBitmapData(bitmap); else { results = Array.Empty(); CopyPropertyItems(bytes, propertyItems, bitmap); bitmap.Save(mappingFromItem.ResizedFileHolder.FullName, _ImageCodecInfo, _EncoderParameters); } bitmap.Dispose(); return results; } private byte[] SaveResizedSubfile5(MappingFromItem mappingFromItem, int[] resize, bool returnAndDoNotWrite, byte[] bytes) { byte[] results; Bitmap bitmap; using Bitmap temp = new(mappingFromItem.ImageFileHolder.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); if (returnAndDoNotWrite) results = GetBitmapData(bitmap); else { results = Array.Empty(); CopyPropertyItems(bytes, propertyItems, bitmap); bitmap.Save(mappingFromItem.ResizedFileHolder.FullName, _ImageCodecInfo, _EncoderParameters); } } bitmap.Dispose(); return results; } #pragma warning restore CA1416 private byte[] SaveResizedSubfile(Shared.Models.Property property, MappingFromItem mappingFromItem, int[] resize, bool returnAndDoNotWrite) { byte[] results; // string subFile, Shared.Models.Property property, Shared.Models.FileHolder? fileHolder string dateTimeFormat = Shared.Models.Stateless.Methods.IProperty.DateTimeFormat(); DateTime dateTime = Shared.Models.Stateless.Methods.IProperty.GetMinimumDateTime(property); string dateTimeValue = dateTime.ToString(dateTimeFormat); byte[] bytes = _ASCIIEncoding.GetBytes(dateTimeValue); if (_ASCIIEncoding.GetString(bytes, 0, bytes.Length) != dateTimeValue) throw new Exception(); if (resize.Length == 3) results = SaveResizedSubfile3(mappingFromItem, resize, returnAndDoNotWrite, bytes); else if (resize.Length == 5) results = SaveResizedSubfile5(mappingFromItem, resize, returnAndDoNotWrite, bytes); else throw new Exception(); return results; } public byte[] GetResizedBytes(string outputResolution, string cResultsFullGroupDirectory, List> subFileTuples, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary imageResizes) { byte[] results; if (!imageResizes.ContainsKey(outputResolution)) throw new Exception(); int[] resize = imageResizes[outputResolution]; int outputResolutionWidth = resize[_OutputResolutionWidthIndex]; int outputResolutionHeight = resize[_OutputResolutionHeightIndex]; int outputResolutionOrientation = resize[_OutputResolutionOrientationIndex]; results = SaveResizedSubfile(property, mappingFromItem, resize, returnAndDoNotWrite: true); subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); return results; } public void SaveResizedSubfile(Property.Models.Configuration configuration, string outputResolution, string cResultsFullGroupDirectory, List> subFileTuples, Item item, Shared.Models.Property property, MappingFromItem mappingFromItem, string original, Dictionary imageResizes) { if (mappingFromItem.ResizedFileHolder is null) throw new NullReferenceException(nameof(mappingFromItem.ResizedFileHolder)); if (!imageResizes.ContainsKey(outputResolution)) throw new Exception(); FileInfo fileInfo = new(mappingFromItem.ResizedFileHolder.FullName); if (!fileInfo.FullName.Contains(configuration.ResultAllInOne) && !fileInfo.Exists) { if (fileInfo.Directory?.Parent is null) throw new Exception(); string parentCheck = Path.Combine(fileInfo.Directory.Parent.FullName, fileInfo.Name); if (File.Exists(parentCheck)) { File.Move(parentCheck, fileInfo.FullName); item.SetResizedFileHolder(_FileNameExtension, Shared.Models.Stateless.Methods.IFileHolder.Refresh(mappingFromItem.ResizedFileHolder)); fileInfo.Refresh(); } } int[] resize = imageResizes[outputResolution]; int outputResolutionWidth = resize[_OutputResolutionWidthIndex]; int outputResolutionHeight = resize[_OutputResolutionHeightIndex]; int outputResolutionOrientation = resize[_OutputResolutionOrientationIndex]; int[] originalCollection = imageResizes[original]; if (outputResolutionWidth == originalCollection[_OutputResolutionWidthIndex] && outputResolutionHeight == originalCollection[_OutputResolutionHeightIndex] && outputResolutionOrientation == originalCollection[_OutputResolutionOrientationIndex]) { if (!fileInfo.Exists) { File.Copy(mappingFromItem.ImageFileHolder.FullName, fileInfo.FullName); item.SetResizedFileHolder(_FileNameExtension, Shared.Models.Stateless.Methods.IFileHolder.Refresh(mappingFromItem.ResizedFileHolder)); subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); } } else { bool check = false; string[] changesFrom = new string[] { nameof(Property.Models.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; else if (dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime) check = true; if (check) { _ = SaveResizedSubfile(property, mappingFromItem, resize, returnAndDoNotWrite: false); item.SetResizedFileHolder(_FileNameExtension, Shared.Models.Stateless.Methods.IFileHolder.Refresh(mappingFromItem.ResizedFileHolder)); subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); } } } private static int[] GetCollection(string outputResolution) { List results = new(); string[] segments = outputResolution.Split('x'); results.Add(int.Parse(segments[0])); results.Add(int.Parse(segments[1])); return results.ToArray(); } private static int GetOrientation(List> metadataCollection) { int result; const string orientation = nameof(IExif.Tags.Orientation); List orientations = (from l in metadataCollection where l.Key.Contains(orientation) select l.Value).ToList(); if (!orientations.Any()) result = 0; else if (!int.TryParse(orientations[0], out result)) result = 0; return result; } private Dictionary GetImageResizes(Shared.Models.Property property, List> metadataCollection, string original) { Dictionary results = new(); int[] desired; int checkWidth; int orientation; int checkHeight; int desiredWidth; int desiredHeight; if (property is null || property.Width is null || property.Height is null) throw new Exception(); if (!string.IsNullOrEmpty(property.Orientation) && int.TryParse(property.Orientation, out int propertyOrientation)) orientation = propertyOrientation; else orientation = GetOrientation(metadataCollection); checkWidth = property.Width.Value; checkHeight = property.Height.Value; results.Add(original, new int[] { checkWidth, checkHeight, orientation }); foreach (string validResolution in _ValidResolutions) { desired = GetCollection(validResolution); desiredWidth = desired[0]; desiredHeight = desired[1]; if (checkWidth <= desiredWidth && checkHeight <= desiredHeight) results.Add(validResolution, new int[] { checkWidth, checkHeight, orientation }); else { if (desiredWidth != desiredHeight) { if (checkWidth * desiredHeight > desiredWidth * checkHeight) results.Add(validResolution, new int[] { desiredWidth, Convert.ToInt32(desiredWidth * checkHeight / (double)checkWidth), orientation }); else results.Add(validResolution, new int[] { Convert.ToInt32(desiredHeight * checkWidth / (double)checkHeight), desiredHeight, orientation }); } else { if (checkWidth * desiredHeight <= desiredWidth * checkHeight) results.Add(validResolution, new int[] { desiredWidth, desiredHeight, orientation, desiredWidth, Convert.ToInt32(desiredWidth * checkHeight / (double)checkWidth) }); else results.Add(validResolution, new int[] { desiredWidth, desiredHeight, orientation, Convert.ToInt32(desiredHeight * checkWidth / (double)checkHeight), desiredHeight }); } } } return results; } public FileHolder GetResizedFileHolder(Item item) { FileHolder result = new(Path.Combine(AngleBracketCollection[0].Replace("<>", "()"), Path.GetFileName(item.ImageFileHolder.FullName))); return result; } public Dictionary GetResizeKeyValuePairs(Property.Models.Configuration configuration, string cResultsFullGroupDirectory, List> subFileTuples, List parseExceptions, string original, List> metadataCollection, Shared.Models.Property property, MappingFromItem mappingFromItem) { Dictionary results; string json; string[] changesFrom = new string[] { nameof(Property.Models.A_Property), nameof(B_Metadata) }; List dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList(); string cResizeSingletonFile = Path.Combine(cResultsFullGroupDirectory, "{}", configuration.ResultAllInOne, $"{mappingFromItem.Id}{mappingFromItem.ImageFileHolder.ExtensionLowered}.json"); FileInfo fileInfo = new(cResizeSingletonFile); if (!fileInfo.FullName.Contains(configuration.ResultAllInOne) && !fileInfo.Exists) { if (fileInfo.Directory?.Parent is null) throw new Exception(); string parentCheck = Path.Combine(fileInfo.Directory.Parent.FullName, fileInfo.Name); if (File.Exists(parentCheck)) { File.Move(parentCheck, fileInfo.FullName); fileInfo.Refresh(); } } 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 = new(); else if (!fileInfo.Exists) results = new(); else if (!fileInfo.FullName.EndsWith(".json") && !fileInfo.FullName.EndsWith(".old")) throw new ArgumentException("must be a *.json file"); else if (dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime) results = new(); else { json = File.ReadAllText(fileInfo.FullName); try { Dictionary? keyValuePairs; keyValuePairs = JsonSerializer.Deserialize>(json); if (keyValuePairs is null) throw new Exception(); results = keyValuePairs; if ((from l in results where l.Value[0] == l.Value[1] select true).Any()) { results = GetImageResizes(property, metadataCollection, original); if (!(from l in results where l.Value[0] == l.Value[1] select true).Any()) throw new Exception("Was square!"); } subFileTuples.Add(new Tuple(nameof(C_Resize), fileInfo.LastWriteTime)); } catch (Exception) { results = new(); parseExceptions.Add(nameof(C_Resize)); } } if (results is null || !results.Any()) { results = GetImageResizes(property, metadataCollection, original); json = JsonSerializer.Serialize(results, _WriteIndentedJsonSerializerOptions); bool updateDateWhenMatches = dateTimes.Any() && 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 && (!fileInfo.Exists || fileInfo.LastWriteTime == fileInfo.CreationTime)) 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; } }