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);