diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index addc89b205..268171d467 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -70,6 +70,28 @@ namespace Avalonia.Headless return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); + } + public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride) { return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96)); diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 35ba8f2b19..f9fd125615 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -64,3 +64,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGl InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation. MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract. Total Issues: 64 +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. +Total Issues: 11 diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index b86444bc2f..b0e1a954e6 100644 --- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs @@ -1,5 +1,8 @@ using System; +using System.IO; +using System.Threading.Tasks; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media.Imaging { @@ -34,8 +37,50 @@ namespace Avalonia.Media.Imaging { } + private WriteableBitmap(IWriteableBitmapImpl impl) : base(impl) + { + + } + public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock(); + public static WriteableBitmap Decode(Stream stream) + { + var ri = AvaloniaLocator.Current.GetService(); + + return new WriteableBitmap(ri.LoadWriteableBitmap(stream)); + } + + /// + /// Loads a WriteableBitmap 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 new static WriteableBitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + var ri = AvaloniaLocator.Current.GetService(); + + return new WriteableBitmap(ri.LoadWriteableBitmapToWidth(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 new static WriteableBitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + var ri = AvaloniaLocator.Current.GetService(); + + return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode)); + } + private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat) { var ri = AvaloniaLocator.Current.GetService(); diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index f0dbfee718..de67aca5a8 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -100,6 +100,38 @@ namespace Avalonia.Platform /// An . IBitmapImpl LoadBitmap(Stream stream); + /// + /// Loads a WriteableBitmap implementation from a stream to a specified width maintaining aspect ratio. + /// + /// The stream to read the bitmap from. + /// The desired width of the resulting bitmap. + /// The to use should resizing be required. + /// An . + IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + + /// + /// Loads a WriteableBitmap implementation from a stream to a specified height maintaining aspect ratio. + /// + /// The stream to read the bitmap from. + /// The desired height of the resulting bitmap. + /// The to use should resizing be required. + /// An . + IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); + + /// + /// Loads a WriteableBitmap implementation from a file. + /// + /// The filename of the bitmap. + /// An . + IWriteableBitmapImpl LoadWriteableBitmap(string fileName); + + /// + /// Loads a WriteableBitmap implementation from a file. + /// + /// The stream to read the bitmap from. + /// An . + IWriteableBitmapImpl LoadWriteableBitmap(Stream stream); + /// /// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio. /// diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 6b7f0e11cf..7bc83ec85b 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -77,6 +77,31 @@ namespace Avalonia.Skia return new ImmutableBitmap(stream); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WriteableBitmapImpl(stream, width, true, interpolationMode); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WriteableBitmapImpl(stream, height, false, interpolationMode); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + using (var stream = File.OpenRead(fileName)) + { + return LoadWriteableBitmap(stream); + } + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + return new WriteableBitmapImpl(stream); + } + /// public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride) { diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index d48e7d10e6..63a1e8f869 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -1,8 +1,10 @@ using System; using System.IO; using System.Threading; +using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Skia.Helpers; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia @@ -15,7 +17,70 @@ namespace Avalonia.Skia private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc; private readonly SKBitmap _bitmap; private readonly object _lock = new object(); + + /// + /// Create a WriteableBitmap from given stream. + /// + /// Stream containing encoded data. + public WriteableBitmapImpl(Stream stream) + { + using (var skiaStream = new SKManagedStream(stream)) + { + _bitmap = SKBitmap.Decode(skiaStream); + + if (_bitmap == null) + { + throw new ArgumentException("Unable to load bitmap from provided data"); + } + + PixelSize = new PixelSize(_bitmap.Width, _bitmap.Height); + Dpi = SkiaPlatform.DefaultDpi; + } + } + + public WriteableBitmapImpl(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode) + { + 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)); + + // 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) + { + var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality()); + bmp.Dispose(); + bmp = scaledBmp; + } + + _bitmap = bmp; + + PixelSize = new PixelSize(bmp.Width, bmp.Height); + Dpi = SkiaPlatform.DefaultDpi; + } + } + /// /// Create new writeable bitmap. /// diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 15685c0187..f50167b39a 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -188,6 +188,28 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(stream); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WriteableWicBitmapImpl(stream, width, true, interpolationMode); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + return new WriteableWicBitmapImpl(stream, height, false, interpolationMode); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + return new WriteableWicBitmapImpl(fileName); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + return new WriteableWicBitmapImpl(stream); + } + /// public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index 3261c45f15..fef6a9aa82 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Avalonia.Platform; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -7,11 +8,27 @@ namespace Avalonia.Direct2D1.Media.Imaging { class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl { + public WriteableWicBitmapImpl(Stream stream, int decodeSize, bool horizontal, + Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode) + : base(stream, decodeSize, horizontal, interpolationMode) + { + } + public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat, AlphaFormat? alphaFormat) : base(size, dpi, pixelFormat, alphaFormat) { } + public WriteableWicBitmapImpl(Stream stream) + : base(stream) + { + } + + public WriteableWicBitmapImpl(string fileName) + : base(fileName) + { + } + class LockedBitmap : ILockedFramebuffer { private readonly WriteableWicBitmapImpl _parent; diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 268977d662..8792cb5cb1 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -61,6 +61,28 @@ namespace Avalonia.Benchmarks throw new NotImplementedException(); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride) { throw new NotImplementedException(); diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 8015172e72..74366f9e26 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -66,6 +66,28 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(string fileName) { return Mock.Of(); diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 1a6d003062..51ea1e893f 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -42,6 +42,28 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } + public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, + BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(string fileName) + { + throw new NotImplementedException(); + } + + public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(string fileName) { throw new NotImplementedException();