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