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; 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 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 Dictionary _FileGroups; private readonly bool _ForceResizeLastWriteTimeToCreationTime; private readonly IPropertyConfiguration _PropertyConfiguration; private readonly JsonSerializerOptions _WriteIndentedJsonSerializerOptions; public C_Resize(IPropertyConfiguration propertyConfiguration, bool forceResizeLastWriteTimeToCreationTime, bool overrideForResizeImages, bool propertiesChangedForResize, string[] validResolutions, ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension) { _FileGroups = new(); _Original = "Original"; _TempResolutionWidth = 3; _TempResolutionHeight = 4; _OutputResolutionWidthIndex = 0; _ImageCodecInfo = imageCodecInfo; _OutputResolutionHeightIndex = 1; _ASCIIEncoding = new ASCIIEncoding(); _ValidResolutions = validResolutions; _OutputResolutionOrientationIndex = 2; _EncoderParameters = encoderParameters; _FileNameExtension = filenameExtension; AngleBracketCollection = new List(); _PropertyConfiguration = propertyConfiguration; _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) ?? throw new Exception(); _ConstructorInfo = constructorInfo; } public override string ToString() { string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); return result; } public void Update(string cResultsFullGroupDirectory) { _FileGroups.Clear(); Dictionary keyValuePairs = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(_PropertyConfiguration, cResultsFullGroupDirectory, new string[] { _PropertyConfiguration.ResultSingleton }); foreach (KeyValuePair keyValuePair in keyValuePairs) _FileGroups.Add(keyValuePair.Key, keyValuePair.Value); } 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)); } #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 void SaveResizedSubfile3(MappingFromItem mappingFromItem, int[] resize, byte[] bytes) { 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; } CopyPropertyItems(bytes, propertyItems, bitmap); bitmap.Save(mappingFromItem.ResizedFileHolder.FullName, _ImageCodecInfo, _EncoderParameters); bitmap.Dispose(); } private void SaveResizedSubfile5(MappingFromItem mappingFromItem, int[] resize, byte[] bytes) { 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); CopyPropertyItems(bytes, propertyItems, bitmap); bitmap.Save(mappingFromItem.ResizedFileHolder.FullName, _ImageCodecInfo, _EncoderParameters); } bitmap.Dispose(); } #pragma warning restore CA1416 private void SaveResizedSubfile(Shared.Models.Property property, MappingFromItem mappingFromItem, int[] resize) { string dateTimeFormat = Shared.Models.Stateless.Methods.IProperty.DateTimeFormat(); DateTime dateTime = property.DateTimeOriginal is not null ? property.DateTimeOriginal.Value : 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) SaveResizedSubfile3(mappingFromItem, resize, bytes); else if (resize.Length == 5) SaveResizedSubfile5(mappingFromItem, resize, bytes); else throw new Exception(); } public void SaveResizedSubfile(Configuration configuration, string outputResolution, string cResultsFullGroupDirectory, List> subFileTuples, Item item, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize) { if (mappingFromItem.ResizedFileHolder is null) throw new NullReferenceException(nameof(mappingFromItem.ResizedFileHolder)); if (!outputResolutionToResize.ContainsKey(_Original)) throw new Exception(); if (!outputResolutionToResize.ContainsKey(outputResolution)) throw new Exception(); 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 = new string[] { 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.Any() && dateTimes.Max() > fileInfo.CreationTime.AddDays(1)) check = true; if (check) { if (fileInfo.Exists) File.Delete(fileInfo.FullName); 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 { if (!check && dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime) check = true; if (check) { SaveResizedSubfile(property, mappingFromItem, resize); 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 Dictionary GetImageResizes(Shared.Models.Property property, List> metadataCollection) { 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 = Metadata.Models.Stateless.Methods.IMetadata.GetOrientation(metadataCollection); checkWidth = property.Width.Value; checkHeight = property.Height.Value; if (!_ValidResolutions.Contains(_Original)) results.Add(_Original, new int[] { checkWidth, checkHeight, orientation }); 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, 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("<>", _PropertyConfiguration.ResultContent), Path.GetFileName(item.ImageFileHolder.FullName))); return result; } public Dictionary GetResizeKeyValuePairs(Configuration configuration, string cResultsFullGroupDirectory, List> subFileTuples, List parseExceptions, List> metadataCollection, Shared.Models.Property property, MappingFromItem mappingFromItem) { Dictionary? results; string json; string[] changesFrom = new string[] { nameof(A_Property), nameof(B_Metadata) }; List dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList(); (_, int directoryIndex) = Shared.Models.Stateless.Methods.IPath.GetDirectoryNameAndIndex(_PropertyConfiguration.ResultAllInOneSubdirectoryLength, mappingFromItem.ImageFileHolder.Name); FileInfo fileInfo = new(Path.Combine(_FileGroups[_PropertyConfiguration.ResultSingleton][directoryIndex], $"{mappingFromItem.ImageFileHolder.NameWithoutExtension}{mappingFromItem.ImageFileHolder.ExtensionLowered}.json")); 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.Any() && 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(property, metadataCollection); 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) 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; } }