508 lines
24 KiB
C#

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;
/// <summary>
// Dictionary<string, int[]>
/// </summary>
public class C_Resize
{
public List<string> 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<C_Resize>();
AngleBracketCollection = new List<string>();
_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<Type>(), 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<byte>();
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<byte>();
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, List<Tuple<string, DateTime>> subFileTuples, Item item, A_Property property, Dictionary<string, int[]> 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<string, DateTime>(nameof(C_Resize), DateTime.Now));
return results;
}
public void SaveResizedSubfile(string outputResolution, List<Tuple<string, DateTime>> subFileTuples, Item item, string original, A_Property property, Dictionary<string, int[]> 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<string, DateTime>(nameof(C_Resize), DateTime.Now));
}
}
else
{
bool check = false;
string[] changesFrom = new string[] { nameof(A_Property), nameof(B_Metadata), nameof(C_Resize) };
List<DateTime> 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<string, DateTime>(nameof(C_Resize), DateTime.Now));
}
}
}
private int[] GetCollection(string outputResolution)
{
if (_Log is null)
{ }
List<int> 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<KeyValuePair<string, string>> metadataCollection)
{
int result;
const string orientation = nameof(IExif.Tags.Orientation);
List<string> 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<string, int[]> GetImageResizes(A_Property property, List<KeyValuePair<string, string>> metadataCollection, string original)
{
Dictionary<string, int[]> 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<string, int[]> GetResizeKeyValuePairs(List<Tuple<string, DateTime>> subFileTuples, List<string> parseExceptions, string original, List<KeyValuePair<string, string>> metadataCollection, A_Property property, Item item)
{
Dictionary<string, int[]> results;
if (item.ImageFileHolder is null)
throw new NullReferenceException(nameof(item.ImageFileHolder));
string json;
string[] changesFrom = new string[] { nameof(A_Property), nameof(B_Metadata) };
List<DateTime> dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList();
FileInfo fileInfo = new(Path.Combine(AngleBracketCollection[0].Replace("<>", "{}"), string.Concat(item.ImageFileHolder.NameWithoutExtension, ".json")));
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<string, int[]>? keyValuePairs;
keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, int[]>>(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<string, DateTime>(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<string, DateTime>(nameof(C_Resize), DateTime.Now));
else
{
File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime);
fileInfo.Refresh();
subFileTuples.Add(new Tuple<string, DateTime>(nameof(C_Resize), fileInfo.CreationTime));
}
}
}
return results;
}
}