That one evil commit that means you've actually started development like a real developer. But before that you just wrote things
117 lines
3.3 KiB
C#
117 lines
3.3 KiB
C#
using Serilog;
|
|
using SkiaSharp;
|
|
|
|
/// <summary>
|
|
/// Helper class to get data from image files
|
|
/// </summary>
|
|
public class ImageProcessing
|
|
{
|
|
/// <summary> Memoization dictionary for the GCD function </summary>
|
|
private static Dictionary<(int, int), int> _gcdMemo = new();
|
|
|
|
/// <summary>
|
|
/// Uses the recursive euclidean method to calcuate the GCD of two integers
|
|
/// </summary>
|
|
/// <param name="a">the first integer</param>
|
|
/// <param name="b">the second integer</param>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// Thrown when either of the inputs is negative
|
|
/// </exception>
|
|
/// <returns>
|
|
/// The GCD of the inputs
|
|
///</returns>
|
|
private int GCD(int a, int b)
|
|
{
|
|
|
|
Log.Debug("calculating GCD for a:{a} b:{b}", a, b);
|
|
if (a < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("a", "This function can only accept positive integers");
|
|
}
|
|
if (b < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("b", "This function can only accept positive integers");
|
|
}
|
|
|
|
|
|
/* We can only run this calculation when a is larger than b */
|
|
if (a < b)
|
|
{
|
|
return GCD(b, a);
|
|
}
|
|
|
|
/* Check if we have already calculated these inputs */
|
|
if (_gcdMemo.TryGetValue((a, b), out int gcd))
|
|
{
|
|
Log.Debug("Memo hit, returning {gcd}", gcd);
|
|
return gcd;
|
|
}
|
|
|
|
/* Otherwise we calculate the GCD and return it */
|
|
/* Note that the recursive nature of this function means we may hit a match more often */
|
|
Log.Debug("No memo hit, running calculation");
|
|
gcd = b == 0 ? a : GCD(b, a % b);
|
|
|
|
_gcdMemo.Add((a, b), gcd);
|
|
|
|
return gcd;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Where possible calculate the aspect ratio of the image at "<paramref name="path"/>
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path to an image file
|
|
/// </param>
|
|
/// <returns>
|
|
/// Either the string representation of the images aspect ratio
|
|
/// or <see langword="null"/>
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// This function may return null if
|
|
/// <list type="bullet">
|
|
/// <item><paramref name="path"/> does not point to an image</item>
|
|
/// <item>The image cannot be handled by SKIA</item>
|
|
/// <item>The image has no size</item>
|
|
/// <item>You don't have permission to read the image</item>
|
|
/// </list>
|
|
/// </remarks>
|
|
public string? GetAspectRatioString(string path)
|
|
{
|
|
/* This function uses some clever abstraction to allow callers to only worry about passing a path */
|
|
return (GetAspectRatioString(LoadImage(path)));
|
|
}
|
|
|
|
private SKBitmap? LoadImage(string path)
|
|
{
|
|
try
|
|
{
|
|
using (var stream = File.OpenRead(path))
|
|
{
|
|
var bitmap = SKBitmap.Decode(stream);
|
|
return bitmap;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error(e, "Error decoding image {path}", path);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private string? GetAspectRatioString(SKBitmap? bitmap)
|
|
{
|
|
if (bitmap == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int gcd = GCD(bitmap.Width, bitmap.Height);
|
|
int aspectW = bitmap.Width / gcd;
|
|
int aspectH = bitmap.Height / gcd;
|
|
|
|
return $"{aspectW}:{aspectH}";
|
|
}
|
|
|
|
}
|