downloadmanager/ImageProcessing.cs
Robert Morrison 9a1838becc
EVIL COMMIT!
That one evil commit that means you've actually started development like
a real developer.
But before that you just wrote things
2023-05-12 22:58:58 +01:00

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}";
}
}