using System.Drawing; using System.Drawing.Imaging; using System.Globalization; 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.Shared.Models.Stateless; namespace View_by_Distance.Resize.Models; /// // Dictionary /// public class C_Resize { public List AngleBracketCollection { get; } private readonly Serilog.ILogger? _Log; 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) { _TempResolutionWidth = 3; _TempResolutionHeight = 4; _OutputResolutionWidthIndex = 0; _ImageCodecInfo = imageCodecInfo; _OutputResolutionHeightIndex = 1; _ASCIIEncoding = new ASCIIEncoding(); _ValidResolutions = validResolutions; _OutputResolutionOrientationIndex = 2; _EncoderParameters = encoderParameters; _Log = Serilog.Log.ForContext(); 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; } #pragma warning disable CA1416 public static (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters) GetTuple(string outputExtension, int outputQuality) { (ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters) result; System.Drawing.Imaging.ImageFormat imageFormat = outputExtension switch { ".gif" => System.Drawing.Imaging.ImageFormat.Gif, ".jpg" => System.Drawing.Imaging.ImageFormat.Jpeg, ".png" => System.Drawing.Imaging.ImageFormat.Png, ".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); result = new(imageCodecInfo, encoderParameters); 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(string subFile, int[] resize, byte[] bytes, FileHolder? fileHolder) { byte[] results; Bitmap bitmap; int outputResolutionWidth = resize[_OutputResolutionWidthIndex]; using Bitmap temp = new(subFile, 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 (fileHolder is null) results = GetBitmapData(bitmap); else { results = Array.Empty(); CopyPropertyItems(bytes, propertyItems, bitmap); bitmap.Save(fileHolder.FullName, _ImageCodecInfo, _EncoderParameters); } bitmap.Dispose(); return results; } private byte[] SaveResizedSubfile5(string subFile, int[] resize, byte[] bytes, FileHolder? fileHolder) { byte[] results; Bitmap bitmap; using Bitmap temp = new(subFile, 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 (fileHolder is null) results = GetBitmapData(bitmap); else { results = Array.Empty(); CopyPropertyItems(bytes, propertyItems, bitmap); bitmap.Save(fileHolder.FullName, _ImageCodecInfo, _EncoderParameters); } } bitmap.Dispose(); return results; } #pragma warning restore CA1416 private byte[] SaveResizedSubfile(string subFile, A_Property property, int[] resize, FileHolder? fileHolder) { byte[] results; string dateTimeFormat = Property.Models.Stateless.A_Property.DateTimeFormat(); DateTime dateTime = Property.Models.Stateless.A_Property.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(subFile, resize, bytes, fileHolder); else if (resize.Length == 5) results = SaveResizedSubfile5(subFile, resize, bytes, fileHolder); else throw new Exception(); if (fileHolder is not null && false) { #pragma warning disable CA1416 using Image image = Image.FromFile(fileHolder.FullName); if (image.PropertyIdList.Contains((int)IExif.Tags.DateTimeDigitized)) { string value; DateTime checkDateTime; PropertyItem? propertyItem = image.GetPropertyItem((int)IExif.Tags.DateTimeDigitized); if (propertyItem?.Value is not null) { value = _ASCIIEncoding.GetString(propertyItem.Value, 0, propertyItem.Len - 1); if (value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out checkDateTime)) _ = checkDateTime; } } #pragma warning restore CA1416 } return results; } public byte[] GetResizedBytes(string outputResolution, string cResultsFullGroupDirectory, List> subFileTuples, Item item, A_Property property, Dictionary imageResizes) { byte[] results; if (item.ImageFileHolder is null) throw new NullReferenceException(nameof(item.ImageFileHolder)); 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(item.ImageFileHolder.FullName, property, resize, fileHolder: null); subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); return results; } public void SaveResizedSubfile(string outputResolution, string cResultsFullGroupDirectory, List> subFileTuples, Item item, string original, A_Property property, Dictionary imageResizes) { if (item.ImageFileHolder is null) throw new NullReferenceException(nameof(item.ImageFileHolder)); if (item.ResizedFileHolder is null) throw new NullReferenceException(nameof(item.ResizedFileHolder)); FileHolder fileHolder = item.ResizedFileHolder; if (!imageResizes.ContainsKey(outputResolution)) throw new Exception(); if (!fileHolder.Exists) { FileInfo fileInfo = new(fileHolder.FullName); 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(FileHolder.Refresh(fileHolder)); } } 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 (!fileHolder.Exists) { File.Copy(item.ImageFileHolder.FullName, fileHolder.FullName); subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); } } else { bool check = false; 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 (!fileHolder.Exists) check = true; else if (dateTimes.Any() && dateTimes.Max() > fileHolder.LastWriteTime) check = true; if (check) { _ = SaveResizedSubfile(item.ImageFileHolder.FullName, property, resize, fileHolder); subFileTuples.Add(new Tuple(nameof(C_Resize), DateTime.Now)); } } } private int[] GetCollection(string outputResolution) { if (_Log is null) { } 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(A_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 Dictionary GetResizeKeyValuePairs(string cResultsFullGroupDirectory, List> subFileTuples, List parseExceptions, string original, List> metadataCollection, A_Property property, Item item) { Dictionary results; if (item.Property?.Id is null) throw new NullReferenceException(nameof(item.Property.Id)); if (item.ImageFileHolder is null) throw new NullReferenceException(nameof(item.ImageFileHolder)); 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(); string usingRelativePath = Path.Combine(AngleBracketCollection[0].Replace("<>", "{}"), string.Concat(item.ImageFileHolder.NameWithoutExtension, ".json")); string cResizeSingletonFile = Path.Combine(cResultsFullGroupDirectory, "{}", Property.Models.Stateless.IResult.AllInOne, $"{item.Property.Id.Value}{item.ImageFileHolder.ExtensionLowered}.json"); FileInfo fileInfo = new(cResizeSingletonFile); if (!fileInfo.Exists) { if (File.Exists(usingRelativePath)) { File.Move(usingRelativePath, fileInfo.FullName); fileInfo.Refresh(); } if (!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 (Property.Models.Stateless.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; } }