diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 35ba8f2b19..ab5744e36b 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -63,4 +63,5 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract. 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.IDrawingContextImpl Avalonia.Platform.IWriteableBitmapImpl.CreateDrawingContext(Avalonia.Rendering.IVisualBrushRenderer)' is present in the implementation but not in the contract. +Total Issues: 65 diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index b86444bc2f..fa29cf013a 100644 --- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs @@ -36,6 +36,14 @@ namespace Avalonia.Media.Imaging public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock(); + /// + /// Creates a to render into the . + /// + public DrawingContext CreateDrawingContext() + { + return new DrawingContext(((IWriteableBitmapImpl)PlatformImpl.Item).CreateDrawingContext(null)); + } + 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/IWriteableBitmapImpl.cs b/src/Avalonia.Visuals/Platform/IWriteableBitmapImpl.cs index c4e2e4915f..8b20b2888f 100644 --- a/src/Avalonia.Visuals/Platform/IWriteableBitmapImpl.cs +++ b/src/Avalonia.Visuals/Platform/IWriteableBitmapImpl.cs @@ -1,10 +1,13 @@ -namespace Avalonia.Platform +using Avalonia.Rendering; + +namespace Avalonia.Platform { /// /// Defines the platform-specific interface for a . /// public interface IWriteableBitmapImpl : IBitmapImpl { + IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer); ILockedFramebuffer Lock(); } } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index d48e7d10e6..ae4dac3203 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -1,7 +1,9 @@ using System; using System.IO; +using System.Reactive.Disposables; using System.Threading; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.Skia.Helpers; using SkiaSharp; @@ -88,6 +90,28 @@ namespace Avalonia.Skia } } + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + Monitor.Enter(_lock); + + var pixmap = _bitmap.PeekPixels(); + var surface = SKSurface.Create(pixmap); + var createInfo = new DrawingContextImpl.CreateInfo + { + Canvas = surface.Canvas, + Dpi = Dpi, + VisualBrushRenderer = visualBrushRenderer + }; + + return new DrawingContextImpl(createInfo, Disposable.Create(() => + { + _bitmap.NotifyPixelsChanged(); + surface.Dispose(); + pixmap.Dispose(); + Monitor.Exit(_lock); + })); + } + /// public ILockedFramebuffer Lock() => new BitmapFramebuffer(this, _bitmap); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index 3261c45f15..9f0dfc05a3 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,5 +1,7 @@ using System; using Avalonia.Platform; +using Avalonia.Rendering; +using SharpDX.Direct2D1; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -12,6 +14,27 @@ namespace Avalonia.Direct2D1.Media.Imaging { } + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + if (WicImpl.PixelFormat != SharpDX.WIC.PixelFormat.Format32bppPBGRA && + WicImpl.PixelFormat != SharpDX.WIC.PixelFormat.Format32bppPRGBA) + throw new NotSupportedException("Direct2D only supports drawing to bitmaps with premultiplied alpha."); + + var renderTarget = new WicRenderTarget( + Direct2D1Platform.Direct2D1Factory, + WicImpl, + new RenderTargetProperties + { + DpiX = (float)Dpi.X, + DpiY = (float)Dpi.Y, + }); + + return new DrawingContextImpl(visualBrushRenderer, null, renderTarget, finishedCallback: () => + { + Version++; + }); + } + class LockedBitmap : ILockedFramebuffer { private readonly WriteableWicBitmapImpl _parent; diff --git a/tests/Avalonia.RenderTests/Media/WriteableBitmapTests.cs b/tests/Avalonia.RenderTests/Media/WriteableBitmapTests.cs new file mode 100644 index 0000000000..0de21e438c --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/WriteableBitmapTests.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Controls.Shapes; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Media +#endif +{ + public class WriteableBitmapTests : TestBase + { + public WriteableBitmapTests() + : base(@"Media\WriteableBitmap") + { + Directory.CreateDirectory(OutputPath); + } + + [Fact] + public void WriteableBitmap_DrawingContext() + { + using var target = new WriteableBitmap( + new PixelSize(100, 100), + new Vector(96, 96), + PixelFormat.Bgra8888, + AlphaFormat.Premul); + + using (var context = target.CreateDrawingContext()) + { + var geometry = new PolylineGeometry( + new[] { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) }, + true); + context.FillRectangle(Brushes.White, new Rect(0, 0, 100, 100)); + context.PushPostTransform(Matrix.CreateScale(10, 12)); + context.DrawGeometry(Brushes.Violet, null, geometry); + } + + var testName = nameof(WriteableBitmap_DrawingContext); + target.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png")); + CompareImagesNoRenderer(testName); + } + + [Fact] + public void WriteableBitmap_DrawingContext_Two_Draws() + { + using var target = new WriteableBitmap( + new PixelSize(100, 100), + new Vector(96, 96), + PixelFormat.Bgra8888, + AlphaFormat.Premul); + + using (var context = target.CreateDrawingContext()) + { + var geometry = new PolylineGeometry( + new[] { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) }, + true); + context.FillRectangle(Brushes.White, new Rect(0, 0, 100, 100)); + context.PushPostTransform(Matrix.CreateScale(10, 12)); + context.DrawGeometry(Brushes.Violet, null, geometry); + } + + using (var context = target.CreateDrawingContext()) + { + var geometry = new EllipseGeometry(new Rect(40, 40, 20, 20)); + context.DrawGeometry(Brushes.Red, null, geometry); + } + + var testName = nameof(WriteableBitmap_DrawingContext_Two_Draws); + target.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png")); + CompareImagesNoRenderer(testName); + } + } +} diff --git a/tests/TestFiles/Direct2D1/Media/WriteableBitmap/WriteableBitmap_DrawingContext.expected.png b/tests/TestFiles/Direct2D1/Media/WriteableBitmap/WriteableBitmap_DrawingContext.expected.png new file mode 100644 index 0000000000..1961767ffd Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/WriteableBitmap/WriteableBitmap_DrawingContext.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Media/WriteableBitmap/WriteableBitmap_DrawingContext_Two_Draws.expected.png b/tests/TestFiles/Direct2D1/Media/WriteableBitmap/WriteableBitmap_DrawingContext_Two_Draws.expected.png new file mode 100644 index 0000000000..aaa37605d9 Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/WriteableBitmap/WriteableBitmap_DrawingContext_Two_Draws.expected.png differ diff --git a/tests/TestFiles/Skia/Media/WriteableBitmap/WriteableBitmap_DrawingContext.expected.png b/tests/TestFiles/Skia/Media/WriteableBitmap/WriteableBitmap_DrawingContext.expected.png new file mode 100644 index 0000000000..ffa3d26e35 Binary files /dev/null and b/tests/TestFiles/Skia/Media/WriteableBitmap/WriteableBitmap_DrawingContext.expected.png differ diff --git a/tests/TestFiles/Skia/Media/WriteableBitmap/WriteableBitmap_DrawingContext_Two_Draws.expected.png b/tests/TestFiles/Skia/Media/WriteableBitmap/WriteableBitmap_DrawingContext_Two_Draws.expected.png new file mode 100644 index 0000000000..fc67d7291a Binary files /dev/null and b/tests/TestFiles/Skia/Media/WriteableBitmap/WriteableBitmap_DrawingContext_Two_Draws.expected.png differ