public class SkiaSharpImageFactory : IImageFactory { private SKImage m_current; private Stream m_stream; public IImageFactory Load(Stream input) { ArgumentNullException.ThrowIfNull(input); m_stream = new MemoryStream(); input.CopyTo(m_stream); m_stream.Position = 0; UpdateImage(SKImage.FromEncodedData(m_stream)); m_stream.Position = 0; return this; } public IImageFactory Apply(ImageOperations operations) { ArgumentNullException.ThrowIfNull(operations); if (operations.Resize.HasValue) { Resize(operations.Resize, null); } if (operations.Rotate.HasValue) { Rotate(operations.Rotate.Value); } if (operations.Crop != null) { Crop(operations.Crop); } if (operations.Round.HasValue && operations.Round.Value > 0) { Round(operations.Round.Value); } return this; } public IImageFactory Resize(int? width, int? height) { if (!width.HasValue && !height.HasValue) throw new ArgumentException("Either width or height must be specified!"); double scale = m_current.Width / (double) m_current.Height; int resultWidth = width ?? (int) Math.Round(height.Value * scale); int resultHeight = height ?? (int) Math.Round(width.Value / scale); using SKBitmap sourceBitmap = SKBitmap.FromImage(m_current); using SKBitmap scaledBitmap = sourceBitmap.Resize(new SKImageInfo(resultHeight, resultWidth), SKFilterQuality.High); UpdateImage(SKImage.FromBitmap(scaledBitmap)); return this; } public IImageFactory Rotate(int degrees) { double radians = Math.PI * degrees / 180D; float sine = (float)Math.Abs(Math.Sin(radians)); float cosine = (float)Math.Abs(Math.Cos(radians)); int originalWidth = m_current.Width; int originalHeight = m_current.Height; int rotatedWidth = (int)(cosine * originalWidth + sine * originalHeight); int rotatedHeight = (int)(cosine * originalHeight + sine * originalWidth); using SKBitmap sourceBitmap = SKBitmap.FromImage(m_current); using SKBitmap rotated = new SKBitmap(new SKImageInfo(rotatedWidth, rotatedHeight)); using var surface = new SKCanvas(rotated); surface.Translate(rotatedWidth / 2F, rotatedHeight / 2F); surface.RotateDegrees(degrees); surface.Translate(-originalWidth / 2F, -originalHeight / 2F); surface.DrawBitmap(sourceBitmap, new SKPoint()); UpdateImage(SKImage.FromBitmap(rotated)); return this; } public IImageFactory Crop(CropRectangle rectangle) { using SKBitmap sourceBitmap = SKBitmap.FromImage(m_current); using var pixmap = new SKPixmap(sourceBitmap.Info, sourceBitmap.GetPixels()); using SKPixmap subset = pixmap.ExtractSubset(new SKRectI(rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height)); UpdateImage(SKImage.FromPixelCopy(subset)); return this; } public IImageFactory Round(int radius) { throw new System.NotImplementedException(); } public IImageFactory Save(Stream output, ImageFormat format = ImageFormat.Png) { ArgumentNullException.ThrowIfNull(output); using var data = m_current.Encode(ToSKImageFormat(format), 100); data.SaveTo(output); return this; } private SKEncodedImageFormat ToSKImageFormat(ImageFormat format) { return format switch { ImageFormat.Jpeg => SKEncodedImageFormat.Jpeg, ImageFormat.Png => SKEncodedImageFormat.Png, ImageFormat.Bmp => SKEncodedImageFormat.Bmp, ImageFormat.Gif => SKEncodedImageFormat.Gif, _ => SKEncodedImageFormat.Png }; } public void Dispose() { m_current?.Dispose(); m_stream?.Dispose(); } private void UpdateImage(SKImage updated) { m_current?.Dispose(); m_current = updated; } }