From 6a1dbaea6587a7800180cb1986916b1a591b2155 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 19:23:15 +0100 Subject: [PATCH 01/16] initial implementation for skia. --- .../Media/Imaging/WriteableBitmap.cs | 45 +++++++++++++ .../Platform/IPlatformRenderInterface.cs | 32 +++++++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 25 +++++++ src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 65 +++++++++++++++++++ 4 files changed, 167 insertions(+) diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index b86444bc2f..d10c743894 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 static Bitmap 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 static Bitmap 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 fb4a4427b7..c141f2024f 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 6d0be9f64d..2ed4948d41 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..c06bd0d057 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 immutable bitmap 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 = new Vector(96, 96); + } + } + + 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 = new Vector(96, 96); + } + } + /// /// Create new writeable bitmap. /// From a4ebe5dbb51d5a1370724c41a6913e1eeafcdb8e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 20:32:48 +0100 Subject: [PATCH 02/16] add direct2d implementation. --- .../HeadlessPlatformRenderInterface.cs | 22 +++++++++++++++++++ .../Avalonia.Direct2D1/Direct2D1Platform.cs | 22 +++++++++++++++++++ .../Media/Imaging/WriteableWicBitmapImpl.cs | 17 ++++++++++++++ .../MockPlatformRenderInterface.cs | 22 +++++++++++++++++++ 4 files changed, 83 insertions(+) diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 62cac378d7..7b68a0250f 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/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 6ae27870e8..e8757bbb34 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.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index e73a76357a..b7f9cc34ca 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(); From f9173594aeb884f19af3ffc80694950c0dede021 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 20:39:11 +0100 Subject: [PATCH 03/16] add api diff. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 2 +- src/Avalonia.Visuals/ApiCompatBaseline.txt | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 3a6810eed9..613858056c 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -7,4 +7,4 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 7 +Total Issues: 8 diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 805d1955ea..a39a8600eb 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -6,4 +6,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avaloni InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract. -Total Issues: 7 +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 From 79d96ca891fdc38be519255a5985f71354c65c67 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 20:45:35 +0100 Subject: [PATCH 04/16] implement missing stubs. --- .../NullRenderingPlatform.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 1570205456..eb5c031fd1 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(); From 57cbf67879dc8db19f440846679cce249efddf99 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 19 Apr 2021 20:46:46 +0100 Subject: [PATCH 05/16] more missing stubs. --- .../VisualTree/MockRenderInterface.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 6d0683e699..6ce7f16d40 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(); From 4340c69412211ce5675ec88a284dba36e407935f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 10 May 2021 21:57:13 +0100 Subject: [PATCH 06/16] fix comment --- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index c06bd0d057..85f9d8db91 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -19,7 +19,7 @@ namespace Avalonia.Skia private readonly object _lock = new object(); /// - /// Create immutable bitmap from given stream. + /// Create a WriteableBitmap from given stream. /// /// Stream containing encoded data. public WriteableBitmapImpl(Stream stream) From fec55f3f2792589b71e21e9a56f66fe899e6619b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 11 May 2021 22:24:41 +0100 Subject: [PATCH 07/16] fix nits. --- src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs | 4 ++-- src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs | 6 +++--- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index d10c743894..b723002ce0 100644 --- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs @@ -59,7 +59,7 @@ namespace Avalonia.Media.Imaging /// 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) + public new static WriteableBitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { var ri = AvaloniaLocator.Current.GetService(); @@ -74,7 +74,7 @@ namespace Avalonia.Media.Imaging /// 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) + public new static WriteableBitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { var ri = AvaloniaLocator.Current.GetService(); diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index 41f726ac96..de67aca5a8 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -106,7 +106,7 @@ namespace Avalonia.Platform /// The stream to read the bitmap from. /// The desired width of the resulting bitmap. /// The to use should resizing be required. - /// An . + /// An . IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); /// @@ -119,14 +119,14 @@ namespace Avalonia.Platform IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality); /// - /// Loads a WriteableBitmap implementation from a file.. + /// Loads a WriteableBitmap implementation from a file. /// /// The filename of the bitmap. /// An . IWriteableBitmapImpl LoadWriteableBitmap(string fileName); /// - /// Loads a WriteableBitmap implementation from a file.. + /// Loads a WriteableBitmap implementation from a file. /// /// The stream to read the bitmap from. /// An . diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 85f9d8db91..63a1e8f869 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -34,7 +34,7 @@ namespace Avalonia.Skia } PixelSize = new PixelSize(_bitmap.Width, _bitmap.Height); - Dpi = new Vector(96, 96); + Dpi = SkiaPlatform.DefaultDpi; } } @@ -77,7 +77,7 @@ namespace Avalonia.Skia _bitmap = bmp; PixelSize = new PixelSize(bmp.Width, bmp.Height); - Dpi = new Vector(96, 96); + Dpi = SkiaPlatform.DefaultDpi; } } From 4db1f41f72ccec0f076015513795ca7e9089987d Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 14 May 2021 22:58:51 +0200 Subject: [PATCH 08/16] Change Viewbox.Stretch to a styled property --- src/Avalonia.Controls/ApiCompatBaseline.txt | 3 ++- src/Avalonia.Controls/Viewbox.cs | 11 ++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 7199c15d21..a79b3b4d7b 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -4,10 +4,11 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract. +MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 11 +Total Issues: 12 diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index 15ca070de2..624c61bb82 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -11,9 +11,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly AvaloniaProperty StretchProperty = - AvaloniaProperty.RegisterDirect(nameof(Stretch), - v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform); + public static readonly StyledProperty StretchProperty = + AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); /// /// Defines the property. @@ -21,8 +20,6 @@ namespace Avalonia.Controls public static readonly StyledProperty StretchDirectionProperty = AvaloniaProperty.Register(nameof(StretchDirection), StretchDirection.Both); - private Stretch _stretch = Stretch.Uniform; - static Viewbox() { ClipToBoundsProperty.OverrideDefaultValue(true); @@ -35,8 +32,8 @@ namespace Avalonia.Controls /// public Stretch Stretch { - get => _stretch; - set => SetAndRaise(StretchProperty, ref _stretch, value); + get => GetValue(StretchProperty); + set => SetValue(StretchProperty, value); } /// From 4d4ca6806ce9b90ae84cb4c3f311be275ed39667 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 15 May 2021 15:53:42 +0200 Subject: [PATCH 09/16] Avoid allocating lists in Classes. --- src/Avalonia.Styling/Controls/Classes.cs | 39 ++++++++++++++++-------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Styling/Controls/Classes.cs b/src/Avalonia.Styling/Controls/Classes.cs index 4e2783d4ec..50605661fa 100644 --- a/src/Avalonia.Styling/Controls/Classes.cs +++ b/src/Avalonia.Styling/Controls/Classes.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using Avalonia.Collections; +#nullable enable + namespace Avalonia.Controls { /// @@ -90,7 +92,7 @@ namespace Avalonia.Controls } /// - /// Remvoes all non-pseudoclasses from the collection. + /// Removes all non-pseudoclasses from the collection. /// public override void Clear() { @@ -135,7 +137,7 @@ namespace Avalonia.Controls /// public override void InsertRange(int index, IEnumerable names) { - var c = new List(); + List? toInsert = null; foreach (var name in names) { @@ -143,11 +145,16 @@ namespace Avalonia.Controls if (!Contains(name)) { - c.Add(name); + toInsert ??= new List(); + + toInsert.Add(name); } } - base.InsertRange(index, c); + if (toInsert != null) + { + base.InsertRange(index, toInsert); + } } /// @@ -176,19 +183,21 @@ namespace Avalonia.Controls /// public override void RemoveAll(IEnumerable names) { - var c = new List(); + List? toRemove = null; foreach (var name in names) { ThrowIfPseudoclass(name, "removed"); - if (Contains(name)) - { - c.Add(name); - } + toRemove ??= new List(); + + toRemove.Add(name); } - base.RemoveAll(c); + if (toRemove != null) + { + base.RemoveAll(toRemove); + } } /// @@ -223,7 +232,7 @@ namespace Avalonia.Controls /// The new contents of the collection. public void Replace(IList source) { - var toRemove = new List(); + List? toRemove = null; foreach (var name in source) { @@ -234,11 +243,17 @@ namespace Avalonia.Controls { if (!name.StartsWith(":")) { + toRemove ??= new List(); + toRemove.Add(name); } } - base.RemoveAll(toRemove); + if (toRemove != null) + { + base.RemoveAll(toRemove); + } + base.AddRange(source); } From 08382e5bf554381a327e215b191d901ab4b16663 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 15 May 2021 20:52:59 -0400 Subject: [PATCH 10/16] Fix SolidColorBrushAnimator NRE --- .../Animation/Animators/SolidColorBrushAnimator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index cec96fecf8..a56cc1de8c 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -12,6 +12,11 @@ namespace Avalonia.Animation.Animators { public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue) { + if (oldValue is null || newValue is null) + { + return oldValue; + } + return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); } @@ -26,6 +31,11 @@ namespace Avalonia.Animation.Animators { public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue) { + if (oldValue is null || newValue is null) + { + return oldValue; + } + return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); } } From 34505c4c8b41c9f98553e4b2f500a8462be5811f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 15 May 2021 20:53:28 -0400 Subject: [PATCH 11/16] Reuse Animator.Interpolate in transitions code --- .../Transitions/DoubleTransition.cs | 10 ++++----- .../Transitions/FloatTransition.cs | 7 +++++-- .../Transitions/IntegerTransition.cs | 7 +++++-- .../Transitions/CornerRadiusTransition.cs | 21 +++++-------------- .../Animation/Transitions/PointTransition.cs | 10 ++++----- .../Animation/Transitions/SizeTransition.cs | 10 ++++----- .../Transitions/ThicknessTransition.cs | 10 ++++----- .../Animation/Transitions/VectorTransition.cs | 10 ++++----- 8 files changed, 40 insertions(+), 45 deletions(-) diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs index 8cae1e1f81..d5bb1aac20 100644 --- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs +++ b/src/Avalonia.Animation/Transitions/DoubleTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class DoubleTransition : Transition { + private static readonly DoubleAnimator s_animator = new DoubleAnimator(); + /// public override IObservable DoTransition(IObservable progress, double oldValue, double newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Animation/Transitions/FloatTransition.cs b/src/Avalonia.Animation/Transitions/FloatTransition.cs index 427563e559..37b644fa96 100644 --- a/src/Avalonia.Animation/Transitions/FloatTransition.cs +++ b/src/Avalonia.Animation/Transitions/FloatTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,12 +10,13 @@ namespace Avalonia.Animation /// public class FloatTransition : Transition { + private static readonly FloatAnimator s_animator = new FloatAnimator(); + /// public override IObservable DoTransition(IObservable progress, float oldValue, float newValue) { - var delta = newValue - oldValue; return progress - .Select(p => (float)Easing.Ease(p) * delta + oldValue); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Animation/Transitions/IntegerTransition.cs b/src/Avalonia.Animation/Transitions/IntegerTransition.cs index 7a85bd75dc..223b2ba531 100644 --- a/src/Avalonia.Animation/Transitions/IntegerTransition.cs +++ b/src/Avalonia.Animation/Transitions/IntegerTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,12 +10,13 @@ namespace Avalonia.Animation /// public class IntegerTransition : Transition { + private static readonly Int32Animator s_animator = new Int32Animator(); + /// public override IObservable DoTransition(IObservable progress, int oldValue, int newValue) { - var delta = newValue - oldValue; return progress - .Select(p => (int)(Easing.Ease(p) * delta + oldValue)); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs index 0b0f04ca94..9ffdf53694 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,26 +10,13 @@ namespace Avalonia.Animation /// public class CornerRadiusTransition : Transition { + private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator(); + /// public override IObservable DoTransition(IObservable progress, CornerRadius oldValue, CornerRadius newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - - var deltaTL = newValue.TopLeft - oldValue.TopLeft; - var deltaTR = newValue.TopRight - oldValue.TopRight; - var deltaBR = newValue.BottomRight - oldValue.BottomRight; - var deltaBL = newValue.BottomLeft - oldValue.BottomLeft; - - var nTL = f * deltaTL + oldValue.TopLeft; - var nTR = f * deltaTR + oldValue.TopRight; - var nBR = f * deltaBR + oldValue.BottomRight; - var nBL = f * deltaBL + oldValue.BottomLeft; - - return new CornerRadius(nTL, nTR, nBR, nBL); - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs index 29db5fc868..fbe24c6d55 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class PointTransition : Transition { + private static readonly PointAnimator s_animator = new PointAnimator(); + /// public override IObservable DoTransition(IObservable progress, Point oldValue, Point newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs index b40e789915..464f83bec7 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class SizeTransition : Transition { + private static readonly SizeAnimator s_animator = new SizeAnimator(); + /// public override IObservable DoTransition(IObservable progress, Size oldValue, Size newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs index 28d4ea067f..9fb3380780 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class ThicknessTransition : Transition { + private static readonly ThicknessAnimator s_animator = new ThicknessAnimator(); + /// public override IObservable DoTransition(IObservable progress, Thickness oldValue, Thickness newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs index c073e8e192..5038117faa 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs @@ -1,6 +1,8 @@ using System; using System.Reactive.Linq; +using Avalonia.Animation.Animators; + namespace Avalonia.Animation { /// @@ -8,15 +10,13 @@ namespace Avalonia.Animation /// public class VectorTransition : Transition { + private static readonly VectorAnimator s_animator = new VectorAnimator(); + /// public override IObservable DoTransition(IObservable progress, Vector oldValue, Vector newValue) { return progress - .Select(p => - { - var f = Easing.Ease(p); - return ((newValue - oldValue) * f) + oldValue; - }); + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); } } } From f29d050e73c0144909757c085bf6bcd5a156f7ae Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 15 May 2021 20:53:45 -0400 Subject: [PATCH 12/16] Add BoxShadows transition --- .../Transitions/BoxShadowsTransition.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs diff --git a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs new file mode 100644 index 0000000000..008613fb40 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs @@ -0,0 +1,23 @@ +using System; +using System.Reactive.Linq; + +using Avalonia.Animation.Animators; +using Avalonia.Media; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// + public class BoxShadowsTransition : Transition + { + private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator(); + + /// + public override IObservable DoTransition(IObservable progress, BoxShadows oldValue, BoxShadows newValue) + { + return progress + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); + } + } +} From f5a01afc6de7ac794d9dfb1a16792cf7f6296ee0 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 15 May 2021 22:05:01 -0400 Subject: [PATCH 13/16] Update RenderDemo pages --- samples/RenderDemo/Pages/AnimationsPage.xaml | 7 +- samples/RenderDemo/Pages/TransitionsPage.xaml | 65 +++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 12fb31ea59..21c7d68b5d 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -1,7 +1,8 @@ + x:Class="RenderDemo.Pages.AnimationsPage" + MaxWidth="600"> @@ -167,8 +168,8 @@ - - Hover to activate Transform Keyframe Animations. + + Hover to activate Keyframe Animations.