From ba75735848cff6287402ec4e9bd8ade95e72e8e4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 6 May 2020 16:55:09 -0300 Subject: [PATCH 01/14] decode bitmaps at a specified size. --- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 12 ++++++++++ .../Media/Imaging/BitmapDecodeOptions.cs | 11 +++++++++ .../Platform/IPlatformRenderInterface.cs | 5 ++++ src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 19 +-------------- .../Avalonia.Skia/PlatformRenderInterface.cs | 23 +++++++++++++++++++ src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs | 18 +++++++++++++++ .../Avalonia.Direct2D1/Direct2D1Platform.cs | 11 +++++++++ 7 files changed, 81 insertions(+), 18 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index 42ee35133e..408a93eae7 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -31,6 +31,18 @@ namespace Avalonia.Media.Imaging PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream)); } + public Bitmap(Stream stream, BitmapDecodeOptions decodeOptions) + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream, decodeOptions)); + } + + public Bitmap(string file, BitmapDecodeOptions decodeOptions) + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + PlatformImpl = RefCountable.Create(factory.LoadBitmap(file, decodeOptions)); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs new file mode 100644 index 0000000000..ec9d823bb4 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs @@ -0,0 +1,11 @@ +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Media.Imaging +{ + public struct BitmapDecodeOptions + { + public PixelSize DecodePixelSize { get; set; } + + public BitmapInterpolationMode InterpolationMode { get; set; } + } +} diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index bd569fe841..86662a3584 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using Avalonia.Media; +using Avalonia.Media.Imaging; namespace Avalonia.Platform { @@ -84,6 +85,10 @@ namespace Avalonia.Platform /// An . IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null); + IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions decodeOptions); + + IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions decodeOptions); + /// /// Loads a bitmap implementation from a file.. /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 9f99ed3cef..6472338734 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -123,29 +123,12 @@ namespace Avalonia.Skia Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) }) { - paint.FilterQuality = GetInterpolationMode(bitmapInterpolationMode); + paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); drawableImage.Draw(this, s, d, paint); } } - private static SKFilterQuality GetInterpolationMode(BitmapInterpolationMode interpolationMode) - { - switch (interpolationMode) - { - case BitmapInterpolationMode.LowQuality: - return SKFilterQuality.Low; - case BitmapInterpolationMode.MediumQuality: - return SKFilterQuality.Medium; - case BitmapInterpolationMode.HighQuality: - return SKFilterQuality.High; - case BitmapInterpolationMode.Default: - return SKFilterQuality.None; - default: - throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); - } - } - /// public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index f6d77e1045..b96a65186e 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.OpenGL; using Avalonia.Platform; using SkiaSharp; @@ -77,6 +78,28 @@ namespace Avalonia.Skia return new StreamGeometryImpl(); } + public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions decodeOptions) + { + using (var stream = File.OpenRead(fileName)) + { + return LoadBitmap(stream, decodeOptions); + } + } + + public unsafe IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions decodeOptions) + { + var skBitmap = SKBitmap.Decode(stream); + + skBitmap = skBitmap.Resize(new SKImageInfo(decodeOptions.DecodePixelSize.Width, decodeOptions.DecodePixelSize.Height), decodeOptions.InterpolationMode.ToSKFilterQuality()); + + fixed (byte* p = skBitmap.Bytes) + { + IntPtr ptr = (IntPtr)p; + + return LoadBitmap(PixelFormat.Bgra8888, ptr, new PixelSize(skBitmap.Width, skBitmap.Height), new Vector(96, 96), skBitmap.RowBytes); + } + } + /// public IBitmapImpl LoadBitmap(Stream stream) { diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs index 1dd2310475..c706d5c1aa 100644 --- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs +++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs @@ -1,12 +1,30 @@ using System; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia { public static class SkiaSharpExtensions { + public static SKFilterQuality ToSKFilterQuality(this BitmapInterpolationMode interpolationMode) + { + switch (interpolationMode) + { + case BitmapInterpolationMode.LowQuality: + return SKFilterQuality.Low; + case BitmapInterpolationMode.MediumQuality: + return SKFilterQuality.Medium; + case BitmapInterpolationMode.HighQuality: + return SKFilterQuality.High; + case BitmapInterpolationMode.Default: + return SKFilterQuality.None; + default: + throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); + } + } + public static SKPoint ToSKPoint(this Point p) { return new SKPoint((float)p.X, (float)p.Y); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index b8bbed24f8..534ecdd6e6 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Platform.Surfaces; using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Media; +using Avalonia.Media.Imaging; using Avalonia.Platform; using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; @@ -194,6 +195,16 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(format, data, size, dpi, stride); } + public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions decodeOptions) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions decodeOptions) + { + throw new NotImplementedException(); + } + public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width) { var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; From 3995b8abb97cc4b6ed386da8f1516f2014872d9b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 6 May 2020 17:29:41 -0300 Subject: [PATCH 02/14] use default arguments for bitmap decoder options in platform backends. --- .../Platform/IPlatformRenderInterface.cs | 10 ++--- .../Avalonia.Skia/PlatformRenderInterface.cs | 38 +++++++++---------- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 14 +------ 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index 86662a3584..a32aeb2326 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -85,23 +85,21 @@ namespace Avalonia.Platform /// An . IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null); - IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions decodeOptions); - - IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions decodeOptions); - /// /// Loads a bitmap implementation from a file.. /// /// The filename of the bitmap. + /// Decode options that specify the size of the decoded bitmap. /// An . - IBitmapImpl LoadBitmap(string fileName); + IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions = null); /// /// Loads a bitmap implementation from a file.. /// /// The stream to read the bitmap from. + /// Decode options that specify the size of the decoded bitmap. /// An . - IBitmapImpl LoadBitmap(Stream stream); + IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? decodeOptions = null); /// /// Loads a bitmap implementation from a pixels in memory. diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index b96a65186e..28a8b2b1ef 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -78,40 +78,36 @@ namespace Avalonia.Skia return new StreamGeometryImpl(); } - public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions decodeOptions) - { - using (var stream = File.OpenRead(fileName)) + /// + public unsafe IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? decodeOptions = null) + { + if (decodeOptions is null) { - return LoadBitmap(stream, decodeOptions); + return new ImmutableBitmap(stream); } - } + else + { + var options = decodeOptions.Value; - public unsafe IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions decodeOptions) - { - var skBitmap = SKBitmap.Decode(stream); + var skBitmap = SKBitmap.Decode(stream); - skBitmap = skBitmap.Resize(new SKImageInfo(decodeOptions.DecodePixelSize.Width, decodeOptions.DecodePixelSize.Height), decodeOptions.InterpolationMode.ToSKFilterQuality()); + skBitmap = skBitmap.Resize(new SKImageInfo(options.DecodePixelSize.Width, options.DecodePixelSize.Height), options.InterpolationMode.ToSKFilterQuality()); - fixed (byte* p = skBitmap.Bytes) - { - IntPtr ptr = (IntPtr)p; + fixed (byte* p = skBitmap.Bytes) + { + IntPtr ptr = (IntPtr)p; - return LoadBitmap(PixelFormat.Bgra8888, ptr, new PixelSize(skBitmap.Width, skBitmap.Height), new Vector(96, 96), skBitmap.RowBytes); + return LoadBitmap(PixelFormat.Bgra8888, ptr, new PixelSize(skBitmap.Width, skBitmap.Height), new Vector(96, 96), skBitmap.RowBytes); + } } } /// - public IBitmapImpl LoadBitmap(Stream stream) - { - return new ImmutableBitmap(stream); - } - - /// - public IBitmapImpl LoadBitmap(string fileName) + public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions) { using (var stream = File.OpenRead(fileName)) { - return LoadBitmap(stream); + return LoadBitmap(stream, decodeOptions); } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 534ecdd6e6..617bae2245 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -180,12 +180,12 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect); public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl(); - public IBitmapImpl LoadBitmap(string fileName) + public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? options = null) { return new WicBitmapImpl(fileName); } - public IBitmapImpl LoadBitmap(Stream stream) + public IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? options = null) { return new WicBitmapImpl(stream); } @@ -195,16 +195,6 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(format, data, size, dpi, stride); } - public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions decodeOptions) - { - throw new NotImplementedException(); - } - - public IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions decodeOptions) - { - throw new NotImplementedException(); - } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width) { var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; From 22d263812f6fe655926ecb8b240947f8fdd5c780 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 6 May 2020 18:35:59 -0300 Subject: [PATCH 03/14] code tidy --- src/Skia/Avalonia.Skia/PlatformRenderInterface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 28a8b2b1ef..baf4d066ce 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -103,7 +103,7 @@ namespace Avalonia.Skia } /// - public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions) + public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions = null) { using (var stream = File.OpenRead(fileName)) { From 663b91a8bf1ddef5e2e5425ba0482067328e8cc2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 9 May 2020 16:01:18 -0300 Subject: [PATCH 04/14] Implement ability to decode a bitmap at a specified width or height. --- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 24 ++++---- .../Media/Imaging/BitmapDecodeOptions.cs | 11 ---- .../Platform/IPlatformRenderInterface.cs | 25 ++++++-- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 61 +++++++++++++++++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 40 +++++------- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 15 ++++- 6 files changed, 121 insertions(+), 55 deletions(-) delete mode 100644 src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index 408a93eae7..e276b7f65d 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -11,6 +11,18 @@ namespace Avalonia.Media.Imaging /// public class Bitmap : IBitmap { + public static Bitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + return new Bitmap(factory.LoadBitmapToWidth(stream, width, interpolationMode)); + } + + public static Bitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + return new Bitmap(factory.LoadBitmapToHeight(stream, height, interpolationMode)); + } + /// /// Initializes a new instance of the class. /// @@ -31,18 +43,6 @@ namespace Avalonia.Media.Imaging PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream)); } - public Bitmap(Stream stream, BitmapDecodeOptions decodeOptions) - { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream, decodeOptions)); - } - - public Bitmap(string file, BitmapDecodeOptions decodeOptions) - { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - PlatformImpl = RefCountable.Create(factory.LoadBitmap(file, decodeOptions)); - } - /// /// Initializes a new instance of the class. /// diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs deleted file mode 100644 index ec9d823bb4..0000000000 --- a/src/Avalonia.Visuals/Media/Imaging/BitmapDecodeOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.Visuals.Media.Imaging; - -namespace Avalonia.Media.Imaging -{ - public struct BitmapDecodeOptions - { - public PixelSize DecodePixelSize { get; set; } - - public BitmapInterpolationMode InterpolationMode { get; set; } - } -} diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index a32aeb2326..381dde4891 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using Avalonia.Media; using Avalonia.Media.Imaging; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Platform { @@ -88,18 +89,30 @@ namespace Avalonia.Platform /// /// Loads a bitmap implementation from a file.. /// - /// The filename of the bitmap. - /// Decode options that specify the size of the decoded bitmap. + /// The filename of the bitmap. /// An . - IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions = null); + IBitmapImpl LoadBitmap(string fileName); /// /// Loads a bitmap implementation from a file.. /// - /// The stream to read the bitmap from. - /// Decode options that specify the size of the decoded bitmap. + /// The stream to read the bitmap from. /// An . - IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? decodeOptions = null); + IBitmapImpl LoadBitmap(Stream stream); + + /// + /// Loads a bitmap implementation from a file.. + /// + /// The stream to read the bitmap from. + /// An . + IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + + /// + /// Loads a bitmap implementation from a file.. + /// + /// The stream to read the bitmap from. + /// An . + IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); /// /// Loads a bitmap implementation from a pixels in memory. diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 9222c5ac61..887d023917 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -1,7 +1,11 @@ using System; using System.IO; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Skia.Helpers; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia @@ -36,6 +40,63 @@ namespace Avalonia.Skia } } + // NOTE, putting the stream before options in the parameters, causes an exception + // inside SKCodec.Create with optimized code. Probably a bug in .net compiler. + // Other option is to have the argument order as desired and use PreserveSig options. + [MethodImpl(MethodImplOptions.PreserveSig)] + public ImmutableBitmap(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode) + { + // create the codec + var codec = SKCodec.Create(stream); + var info = codec.Info; + + // get the scale that is nearest to what we want (eg: jpg returned 512) + var supportedScale = codec.GetScaledDimensions(horizontal ? ((float)decodeSize / info.Width) : ((float)decodeSize / info.Height)); + + // decode the bitmap at the nearest size + var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height); + var bmp = SKBitmap.Decode(codec, nearest); + + // now scale that to the size that we want + var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height); + + SKImageInfo desired; + + + if (horizontal) + { + desired = new SKImageInfo(decodeSize, (int)(realScale * decodeSize)); + } + else + { + desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize); + } + + if (bmp.Width != desired.Width || bmp.Height != desired.Height) + { + if (bmp.Height != bmp.Width) + { + + } + var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality()); + bmp.Dispose(); + bmp = scaledBmp; + } + + _image = SKImage.FromBitmap(bmp); + bmp.Dispose(); + + if (_image == null) + { + throw new ArgumentException("Unable to load bitmap from provided data"); + } + + PixelSize = new PixelSize(_image.Width, _image.Height); + + // TODO: Skia doesn't have an API for DPI. + Dpi = new Vector(96, 96); + } + /// /// Create immutable bitmap from given pixel data copy. /// diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 6c8ca7e5b2..32fee417a4 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; using System.IO; +using System.Security.Cryptography; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.OpenGL; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia @@ -78,37 +80,27 @@ namespace Avalonia.Skia return new StreamGeometryImpl(); } - /// - public unsafe IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? decodeOptions = null) - { - if (decodeOptions is null) + public IBitmapImpl LoadBitmap(string fileName) + { + using (var stream = File.OpenRead(fileName)) { - return new ImmutableBitmap(stream); + return LoadBitmap(stream); } - else - { - var options = decodeOptions.Value; - - var skBitmap = SKBitmap.Decode(stream); - - skBitmap = skBitmap.Resize(new SKImageInfo(options.DecodePixelSize.Width, options.DecodePixelSize.Height), options.InterpolationMode.ToSKFilterQuality()); + } - fixed (byte* p = skBitmap.Bytes) - { - IntPtr ptr = (IntPtr)p; + public IBitmapImpl LoadBitmap(Stream stream) + { + return new ImmutableBitmap(stream); + } - return LoadBitmap(PixelFormat.Bgra8888, ptr, new PixelSize(skBitmap.Width, skBitmap.Height), new Vector(96, 96), skBitmap.RowBytes); - } - } + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new ImmutableBitmap(stream, width, true, interpolationMode); } - /// - public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? decodeOptions = null) + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - using (var stream = File.OpenRead(fileName)) - { - return LoadBitmap(stream, decodeOptions); - } + return new ImmutableBitmap(stream, height, false, interpolationMode); } /// diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 617bae2245..fd6c2dffd5 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -9,6 +9,7 @@ using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; using TextAlignment = Avalonia.Media.TextAlignment; @@ -180,16 +181,26 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect); public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl(); - public IBitmapImpl LoadBitmap(string fileName, BitmapDecodeOptions? options = null) + public IBitmapImpl LoadBitmap(string fileName) { return new WicBitmapImpl(fileName); } - public IBitmapImpl LoadBitmap(Stream stream, BitmapDecodeOptions? options = null) + public IBitmapImpl LoadBitmap(Stream stream) { return new WicBitmapImpl(stream); } + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { return new WicBitmapImpl(format, data, size, dpi, stride); From 0889318c57ebe215f341da1fa01cdd12d39856d5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 9 May 2020 16:28:32 -0300 Subject: [PATCH 05/14] Add createscaled bitmap --- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 6 ++++++ .../Platform/IPlatformRenderInterface.cs | 2 ++ src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 14 ++++++++++++++ src/Skia/Avalonia.Skia/PlatformRenderInterface.cs | 12 ++++++++++++ .../Avalonia.Direct2D1/Direct2D1Platform.cs | 5 +++++ 5 files changed, 39 insertions(+) diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index e276b7f65d..3d567fe458 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -23,6 +23,12 @@ namespace Avalonia.Media.Imaging return new Bitmap(factory.LoadBitmapToHeight(stream, height, interpolationMode)); } + public static Bitmap CreateScaledBitmap(Bitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + return new Bitmap(factory.ResizeBitmap(src.PlatformImpl.Item, destinationSize, interpolationMode)); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index 381dde4891..e35d1154a1 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -114,6 +114,8 @@ namespace Avalonia.Platform /// An . IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + /// /// Loads a bitmap implementation from a pixels in memory. /// diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 887d023917..8323eabfbb 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -40,6 +40,20 @@ namespace Avalonia.Skia } } + public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode) + { + SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888); + SKImage output = SKImage.Create(info); + src._image.ScalePixels(output.PeekPixels(), interpolationMode.ToSKFilterQuality()); + + _image = output; + + PixelSize = new PixelSize(_image.Width, _image.Height); + + // TODO: Skia doesn't have an API for DPI. + Dpi = new Vector(96, 96); + } + // NOTE, putting the stream before options in the parameters, causes an exception // inside SKCodec.Create with optimized code. Probably a bug in .net compiler. // Other option is to have the argument order as desired and use PreserveSig options. diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 32fee417a4..f744b2f00a 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -109,6 +109,18 @@ namespace Avalonia.Skia return new ImmutableBitmap(size, dpi, stride, format, data); } + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + if (bitmapImpl is ImmutableBitmap ibmp) + { + return new ImmutableBitmap(ibmp, destinationSize, interpolationMode); + } + else + { + throw new Exception("Invalid source bitmap type."); + } + } + /// public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index fd6c2dffd5..ef5b3255c1 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -201,6 +201,11 @@ namespace Avalonia.Direct2D1 throw new NotImplementedException(); } + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { return new WicBitmapImpl(format, data, size, dpi, stride); From ac65b4e71762c7d072a83d4668f36225d911ac84 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 9 May 2020 16:40:33 -0300 Subject: [PATCH 06/14] Add documentation. --- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 27 +++++++++++++++++-- .../Platform/IPlatformRenderInterface.cs | 14 ++++++---- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 2 +- .../Avalonia.Skia/PlatformRenderInterface.cs | 15 +++++++---- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 6 +++++ 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index 3d567fe458..af69e75571 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -11,18 +11,41 @@ namespace Avalonia.Media.Imaging /// public class Bitmap : IBitmap { + /// + /// Loads a Bitmap from a stream and decodes at the desired width. Aspect ratio is maintained. + /// This is more efficient than loading and then resizing. + /// + /// The stream to read the bitmap from. This can be any supported image format. + /// The desired width of the resulting bitmap. + /// The to use should any scaling be required. + /// An instance of the class. public static Bitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); return new Bitmap(factory.LoadBitmapToWidth(stream, width, interpolationMode)); } + /// + /// Loads a Bitmap from a stream and decodes at the desired height. Aspect ratio is maintained. + /// This is more efficient than loading and then resizing. + /// + /// The stream to read the bitmap from. This can be any supported image format. + /// The desired height of the resulting bitmap. + /// The to use should any scaling be required. + /// An instance of the class. public static Bitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); return new Bitmap(factory.LoadBitmapToHeight(stream, height, interpolationMode)); } + /// + /// Creates a Bitmap from another Bitmap scaled to a specified size. + /// + /// The source bitmap. + /// The destination size. + /// The to use should any scaling be required. + /// An instance of the class. public static Bitmap CreateScaledBitmap(Bitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); @@ -57,7 +80,7 @@ namespace Avalonia.Media.Imaging { PlatformImpl = impl.Clone(); } - + /// /// Initializes a new instance of the class. /// @@ -66,7 +89,7 @@ namespace Avalonia.Media.Imaging { PlatformImpl = RefCountable.Create(impl); } - + /// public virtual void Dispose() { diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index e35d1154a1..c0ef65943a 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -91,7 +91,7 @@ namespace Avalonia.Platform /// /// The filename of the bitmap. /// An . - IBitmapImpl LoadBitmap(string fileName); + IBitmapImpl LoadBitmap(string fileName); /// /// Loads a bitmap implementation from a file.. @@ -101,16 +101,20 @@ namespace Avalonia.Platform IBitmapImpl LoadBitmap(Stream stream); /// - /// Loads a bitmap implementation from a file.. + /// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio. /// - /// The stream to read the bitmap from. + /// The stream to read the bitmap from. + /// The desired width of the resulting bitmap. + /// The to use should resizing be required. /// An . IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); /// - /// Loads a bitmap implementation from a file.. + /// Loads a bitmap implementation from a stream to a specified height maintaining aspect ratio. /// - /// The stream to read the bitmap from. + /// The stream to read the bitmap from. + /// The desired height of the resulting bitmap. + /// The to use should resizing be required. /// An . IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 8323eabfbb..4fc02b28e1 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia } public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode) - { + { SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888); SKImage output = SKImage.Create(info); src._image.ScalePixels(output.PeekPixels(), interpolationMode.ToSKFilterQuality()); diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index f744b2f00a..869ca6969b 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -80,6 +80,7 @@ namespace Avalonia.Skia return new StreamGeometryImpl(); } + /// public IBitmapImpl LoadBitmap(string fileName) { using (var stream = File.OpenRead(fileName)) @@ -88,27 +89,31 @@ namespace Avalonia.Skia } } + /// public IBitmapImpl LoadBitmap(Stream stream) { return new ImmutableBitmap(stream); } + /// + public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) + { + return new ImmutableBitmap(size, dpi, stride, format, data); + } + + /// public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { return new ImmutableBitmap(stream, width, true, interpolationMode); } + /// public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { return new ImmutableBitmap(stream, height, false, interpolationMode); } /// - public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) - { - return new ImmutableBitmap(size, dpi, stride, format, data); - } - public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { if (bitmapImpl is ImmutableBitmap ibmp) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index ef5b3255c1..fa96a5eebf 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -181,31 +181,37 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect); public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl(); + /// public IBitmapImpl LoadBitmap(string fileName) { return new WicBitmapImpl(fileName); } + /// public IBitmapImpl LoadBitmap(Stream stream) { return new WicBitmapImpl(stream); } + /// public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { throw new NotImplementedException(); } + /// public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { throw new NotImplementedException(); } + /// public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { throw new NotImplementedException(); } + /// public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { return new WicBitmapImpl(format, data, size, dpi, stride); From 5d8a03d2aa9722e7c7d3420b3d0597c30721d67d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 9 May 2020 16:51:02 -0300 Subject: [PATCH 07/14] fix tests --- .../MockPlatformRenderInterface.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 23b6a00cc8..c5d9699c01 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; using Moq; namespace Avalonia.UnitTests @@ -69,6 +70,21 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return Mock.Of(); + } + + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return Mock.Of(); + } + + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return Mock.Of(); + } + public IBitmapImpl LoadBitmap( PixelFormat format, IntPtr data, From ab9556257c90a26582b7960a2000e4baaf3bd34f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 9 May 2020 17:48:49 -0300 Subject: [PATCH 08/14] fix test interfaces --- .../Avalonia.Benchmarks/NullRenderingPlatform.cs | 16 ++++++++++++++++ .../VisualTree/MockRenderInterface.cs | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 1f983069c2..62491f62c6 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -4,6 +4,7 @@ using System.IO; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Benchmarks { @@ -65,6 +66,21 @@ namespace Avalonia.Benchmarks throw new NotImplementedException(); } + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + public IFontManagerImpl CreateFontManager() { return new MockFontManagerImpl(); diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 28304b674b..710556ec97 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -4,6 +4,7 @@ using System.IO; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Visuals.UnitTests.VisualTree { @@ -81,6 +82,21 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } + public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + class MockStreamGeometry : IStreamGeometryImpl { private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); From 4d7c89b00420041a9f5ef01b5b27e2b4894f061e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 11 May 2020 09:33:07 -0300 Subject: [PATCH 09/14] make CreateScaledBitmap an instance method --- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index af69e75571..86e2700c04 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -40,16 +40,15 @@ namespace Avalonia.Media.Imaging } /// - /// Creates a Bitmap from another Bitmap scaled to a specified size. - /// - /// The source bitmap. + /// Creates a Bitmap scaled to a specified size from the current bitmap. + /// /// The destination size. /// The to use should any scaling be required. /// An instance of the class. - public static Bitmap CreateScaledBitmap(Bitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + public Bitmap CreateScaledBitmap(PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - return new Bitmap(factory.ResizeBitmap(src.PlatformImpl.Item, destinationSize, interpolationMode)); + return new Bitmap(factory.ResizeBitmap(PlatformImpl.Item, destinationSize, interpolationMode)); } /// From 6bb873d5bb6c651981830c7dd6eff933a623d9b2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 11 May 2020 12:48:59 -0300 Subject: [PATCH 10/14] implement bitmap resize apis for direct2d --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 6 +- .../Media/Imaging/WicBitmapImpl.cs | 76 ++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index fa96a5eebf..dc52d4eefb 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -196,19 +196,19 @@ namespace Avalonia.Direct2D1 /// public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - throw new NotImplementedException(); + return new WicBitmapImpl(stream, width, true, interpolationMode); } /// public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - throw new NotImplementedException(); + return new WicBitmapImpl(stream, height, false, interpolationMode); } /// public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - throw new NotImplementedException(); + return (bitmapImpl as WicBitmapImpl).CreateScaledBitmap(destinationSize, interpolationMode); } /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index f5159b1f84..c07f8f98e6 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Security.Cryptography; using Avalonia.Win32.Interop; using SharpDX.WIC; using APixelFormat = Avalonia.Platform.PixelFormat; @@ -14,6 +15,26 @@ namespace Avalonia.Direct2D1.Media { private BitmapDecoder _decoder; + private static BitmapInterpolationMode ConvertInterpolationMode(Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) + { + switch (interpolationMode) + { + case Visuals.Media.Imaging.BitmapInterpolationMode.Default: + return BitmapInterpolationMode.Fant; + + case Visuals.Media.Imaging.BitmapInterpolationMode.LowQuality: + return BitmapInterpolationMode.NearestNeighbor; + + case Visuals.Media.Imaging.BitmapInterpolationMode.MediumQuality: + return BitmapInterpolationMode.Fant; + + default: + case Visuals.Media.Imaging.BitmapInterpolationMode.HighQuality: + return BitmapInterpolationMode.HighQualityCubic; + + } + } + /// /// Initializes a new instance of the class. /// @@ -27,6 +48,12 @@ namespace Avalonia.Direct2D1.Media } } + private WicBitmapImpl(Bitmap bmp) + { + WicImpl = bmp; + Dpi = new Vector(96, 96); + } + /// /// Initializes a new instance of the class. /// @@ -60,7 +87,7 @@ namespace Avalonia.Direct2D1.Media size.Height, pixelFormat.Value.ToWic(), BitmapCreateCacheOption.CacheOnLoad); - WicImpl.SetResolution(dpi.X, dpi.Y); + Dpi = dpi; } @@ -84,6 +111,43 @@ namespace Avalonia.Direct2D1.Media } } + public WicBitmapImpl(Stream stream, int decodeSize, bool horizontal, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) + { + _decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad); + + var bmp = new Bitmap(Direct2D1Platform.ImagingFactory, _decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); + + // now scale that to the size that we want + var realScale = horizontal ? ((double)bmp.Size.Height / bmp.Size.Width) : ((double)bmp.Size.Width / bmp.Size.Height); + + PixelSize desired; + + if (horizontal) + { + desired = new PixelSize(decodeSize, (int)(realScale * decodeSize)); + } + else + { + desired = new PixelSize((int)(realScale * decodeSize), decodeSize); + } + + if (bmp.Size.Width != desired.Width || bmp.Size.Height != desired.Height) + { + using (var scaler = new BitmapScaler(Direct2D1Platform.ImagingFactory)) + { + scaler.Initialize(bmp, desired.Width, desired.Height, ConvertInterpolationMode(interpolationMode)); + + var image = new Bitmap(Direct2D1Platform.ImagingFactory, scaler, BitmapCreateCacheOption.CacheOnDemand); + bmp.Dispose(); + bmp = image; + } + } + + WicImpl = bmp; + + Dpi = new Vector(96, 96); + } + public override Vector Dpi { get; } public override PixelSize PixelSize => WicImpl.Size.ToAvalonia(); @@ -113,6 +177,16 @@ namespace Avalonia.Direct2D1.Media return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } + public unsafe WicBitmapImpl CreateScaledBitmap(PixelSize size, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) + { + using (var scaler = new BitmapScaler(Direct2D1Platform.ImagingFactory)) + { + scaler.Initialize(WicImpl, size.Width, size.Height, ConvertInterpolationMode(interpolationMode)); + + return new WicBitmapImpl(new Bitmap(Direct2D1Platform.ImagingFactory, scaler, BitmapCreateCacheOption.CacheOnDemand)); + } + } + public override void Save(Stream stream) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) From 238ba26daa280e2d489cbcf0f1e1a2374cd25fa5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 11 May 2020 13:29:51 -0300 Subject: [PATCH 11/14] use decoder frame directly to load at scaled size. --- .../Media/Imaging/WicBitmapImpl.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index c07f8f98e6..d6887ef9ab 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -115,10 +115,10 @@ namespace Avalonia.Direct2D1.Media { _decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad); - var bmp = new Bitmap(Direct2D1Platform.ImagingFactory, _decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); + var frame = _decoder.GetFrame(0); // now scale that to the size that we want - var realScale = horizontal ? ((double)bmp.Size.Height / bmp.Size.Width) : ((double)bmp.Size.Width / bmp.Size.Height); + var realScale = horizontal ? ((double)frame.Size.Height / frame.Size.Width) : ((double)frame.Size.Width / frame.Size.Height); PixelSize desired; @@ -131,19 +131,19 @@ namespace Avalonia.Direct2D1.Media desired = new PixelSize((int)(realScale * decodeSize), decodeSize); } - if (bmp.Size.Width != desired.Width || bmp.Size.Height != desired.Height) + if (frame.Size.Width != desired.Width || frame.Size.Height != desired.Height) { using (var scaler = new BitmapScaler(Direct2D1Platform.ImagingFactory)) { - scaler.Initialize(bmp, desired.Width, desired.Height, ConvertInterpolationMode(interpolationMode)); + scaler.Initialize(frame, desired.Width, desired.Height, ConvertInterpolationMode(interpolationMode)); - var image = new Bitmap(Direct2D1Platform.ImagingFactory, scaler, BitmapCreateCacheOption.CacheOnDemand); - bmp.Dispose(); - bmp = image; + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, scaler, BitmapCreateCacheOption.CacheOnLoad); } } - - WicImpl = bmp; + else + { + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, frame, BitmapCreateCacheOption.CacheOnLoad); + } Dpi = new Vector(96, 96); } From 4dbaaf4b1f5aa7b9e8ff63617f27fdb28f84dead Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 11 May 2020 17:37:32 -0300 Subject: [PATCH 12/14] disable optimizations for method. --- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 4fc02b28e1..9410895f80 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -54,10 +54,8 @@ namespace Avalonia.Skia Dpi = new Vector(96, 96); } - // NOTE, putting the stream before options in the parameters, causes an exception - // inside SKCodec.Create with optimized code. Probably a bug in .net compiler. - // Other option is to have the argument order as desired and use PreserveSig options. - [MethodImpl(MethodImplOptions.PreserveSig)] + //NOTE: SKCodec.Create randomly crashes when optimizations are enabled. + [MethodImpl(MethodImplOptions.NoOptimization)] public ImmutableBitmap(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode) { // create the codec From b08c60bd1466b31cbe9b2b4a5ecf81b9865e53c9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 11 May 2020 18:09:46 -0300 Subject: [PATCH 13/14] wrap stream in skmanagedstream to prevent collection of underlying stream, dispose codec. --- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 74 +++++++++++------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 9410895f80..e84c7e34de 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -54,59 +54,59 @@ namespace Avalonia.Skia Dpi = new Vector(96, 96); } - //NOTE: SKCodec.Create randomly crashes when optimizations are enabled. - [MethodImpl(MethodImplOptions.NoOptimization)] public ImmutableBitmap(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode) { - // create the codec - var codec = SKCodec.Create(stream); - var info = codec.Info; + using (var skStream = new SKManagedStream(stream)) + using (var codec = SKCodec.Create(skStream)) + { + var info = codec.Info; - // get the scale that is nearest to what we want (eg: jpg returned 512) - var supportedScale = codec.GetScaledDimensions(horizontal ? ((float)decodeSize / info.Width) : ((float)decodeSize / info.Height)); + // get the scale that is nearest to what we want (eg: jpg returned 512) + var supportedScale = codec.GetScaledDimensions(horizontal ? ((float)decodeSize / info.Width) : ((float)decodeSize / info.Height)); - // decode the bitmap at the nearest size - var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height); - var bmp = SKBitmap.Decode(codec, nearest); + // decode the bitmap at the nearest size + var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height); + var bmp = SKBitmap.Decode(codec, nearest); - // now scale that to the size that we want - var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height); + // now scale that to the size that we want + var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height); - SKImageInfo desired; + SKImageInfo desired; - if (horizontal) - { - desired = new SKImageInfo(decodeSize, (int)(realScale * decodeSize)); - } - else - { - desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize); - } + if (horizontal) + { + desired = new SKImageInfo(decodeSize, (int)(realScale * decodeSize)); + } + else + { + desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize); + } - if (bmp.Width != desired.Width || bmp.Height != desired.Height) - { - if (bmp.Height != bmp.Width) + if (bmp.Width != desired.Width || bmp.Height != desired.Height) { + if (bmp.Height != bmp.Width) + { + } + var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality()); + bmp.Dispose(); + bmp = scaledBmp; } - var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality()); - bmp.Dispose(); - bmp = scaledBmp; - } - _image = SKImage.FromBitmap(bmp); - bmp.Dispose(); + _image = SKImage.FromBitmap(bmp); + bmp.Dispose(); - if (_image == null) - { - throw new ArgumentException("Unable to load bitmap from provided data"); - } + if (_image == null) + { + throw new ArgumentException("Unable to load bitmap from provided data"); + } - PixelSize = new PixelSize(_image.Width, _image.Height); + PixelSize = new PixelSize(_image.Width, _image.Height); - // TODO: Skia doesn't have an API for DPI. - Dpi = new Vector(96, 96); + // TODO: Skia doesn't have an API for DPI. + Dpi = new Vector(96, 96); + } } /// From a856b210fbb7fa84729ac2a162736799b7c0c30a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 13 May 2020 12:47:49 -0300 Subject: [PATCH 14/14] dont implement CreateScaleBitmap on Direct2d. --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 3 ++- .../Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs | 10 ---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 942a98586e..c4c0541d53 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -208,7 +208,8 @@ namespace Avalonia.Direct2D1 /// public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - return (bitmapImpl as WicBitmapImpl).CreateScaledBitmap(destinationSize, interpolationMode); + // https://github.com/sharpdx/SharpDX/issues/959 blocks implementation. + throw new NotImplementedException(); } /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index d6887ef9ab..743abddd1e 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -177,16 +177,6 @@ namespace Avalonia.Direct2D1.Media return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } - public unsafe WicBitmapImpl CreateScaledBitmap(PixelSize size, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) - { - using (var scaler = new BitmapScaler(Direct2D1Platform.ImagingFactory)) - { - scaler.Initialize(WicImpl, size.Width, size.Height, ConvertInterpolationMode(interpolationMode)); - - return new WicBitmapImpl(new Bitmap(Direct2D1Platform.ImagingFactory, scaler, BitmapCreateCacheOption.CacheOnDemand)); - } - } - public override void Save(Stream stream) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream))