diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
index 55e729f5a2..64dbeb89cc 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
@@ -2,7 +2,7 @@ using System;
using System.Runtime.InteropServices;
using Android.Runtime;
using Android.Views;
-using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
namespace Avalonia.Android.Platform.SkiaPlatform
{
diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj
index 6119103e6d..f12f07070e 100644
--- a/src/Avalonia.Controls/Avalonia.Controls.csproj
+++ b/src/Avalonia.Controls/Avalonia.Controls.csproj
@@ -59,8 +59,6 @@
-
-
diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
index 84988e912f..4dc96a074d 100644
--- a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
+++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Avalonia.Platform;
namespace Avalonia.Controls.Platform.Surfaces
{
diff --git a/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs b/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs
deleted file mode 100644
index c9f8eabe97..0000000000
--- a/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Avalonia.Controls.Platform.Surfaces
-{
- public enum PixelFormat
- {
- Rgb565,
- Rgba8888,
- Bgra8888
- }
-}
diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
index a0ed19d2b4..812e9d48ad 100644
--- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj
+++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
@@ -69,6 +69,7 @@
+
@@ -102,7 +103,10 @@
+
+
+
diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
index 1800110e68..c9f7e0f7ac 100644
--- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
+++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
@@ -41,6 +41,20 @@ namespace Avalonia.Media.Imaging
PlatformImpl = impl;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Pixel format
+ /// Pointer to source bytes
+ /// Bitmap width
+ /// Bitmap height
+ /// Bytes per row
+ public Bitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
+ {
+ PlatformImpl = AvaloniaLocator.Current.GetService()
+ .LoadBitmap(format, data, width, height, stride);
+ }
+
///
/// Gets the width of the bitmap, in pixels.
///
diff --git a/src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs
new file mode 100644
index 0000000000..5c5b516ddd
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.Imaging
+{
+ ///
+ /// Holds a writable bitmap image.
+ ///
+ public class WritableBitmap : Bitmap
+ {
+ public WritableBitmap(int width, int height, PixelFormat? format = null)
+ : base(AvaloniaLocator.Current.GetService().CreateWritableBitmap(width, height, format))
+ {
+ }
+
+ public ILockedFramebuffer Lock() => ((IWritableBitmapImpl) PlatformImpl).Lock();
+ }
+}
diff --git a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs b/src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
similarity index 94%
rename from src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs
rename to src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
index d6402d170d..92ec2877ab 100644
--- a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs
+++ b/src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
@@ -1,6 +1,6 @@
using System;
-namespace Avalonia.Controls.Platform.Surfaces
+namespace Avalonia.Platform
{
public interface ILockedFramebuffer : IDisposable
{
diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
index cdec0a07a1..ef58d52b4f 100644
--- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
@@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
@@ -55,6 +56,15 @@ namespace Avalonia.Platform
/// An .
IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height);
+ ///
+ /// Creates a writable bitmap implementation.
+ ///
+ /// The width of the bitmap.
+ /// The height of the bitmap.
+ /// Pixel format (optional).
+ /// An .
+ IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = null);
+
///
/// Loads a bitmap implementation from a file..
///
@@ -68,5 +78,16 @@ namespace Avalonia.Platform
/// The stream to read the bitmap from.
/// An .
IBitmapImpl LoadBitmap(Stream stream);
+
+ ///
+ /// Loads a bitmap implementation from a pixels in memory..
+ ///
+ /// Pixel format
+ /// Pointer to source bytes
+ /// Bitmap width
+ /// Bitmap height
+ /// Bytes per row
+ /// An .
+ IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride);
}
}
diff --git a/src/Avalonia.Visuals/Platform/IWritableBitmapImpl.cs b/src/Avalonia.Visuals/Platform/IWritableBitmapImpl.cs
new file mode 100644
index 0000000000..b736c11dab
--- /dev/null
+++ b/src/Avalonia.Visuals/Platform/IWritableBitmapImpl.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Platform
+{
+ ///
+ /// Defines the platform-specific interface for a .
+ ///
+ public interface IWritableBitmapImpl : IBitmapImpl
+ {
+ ILockedFramebuffer Lock();
+ }
+}
diff --git a/src/Avalonia.Visuals/Platform/PixelFormat.cs b/src/Avalonia.Visuals/Platform/PixelFormat.cs
new file mode 100644
index 0000000000..526303ebb1
--- /dev/null
+++ b/src/Avalonia.Visuals/Platform/PixelFormat.cs
@@ -0,0 +1,9 @@
+namespace Avalonia.Platform
+{
+ public enum PixelFormat
+ {
+ Rgb565,
+ Rgba8888,
+ Bgra8888
+ }
+}
diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs
index e6c493320f..9cf16312e9 100644
--- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs
+++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs
@@ -91,5 +91,15 @@ namespace Avalonia.Cairo
Gtk.Application.Init();
return new Gtk.Invisible().CreatePangoContext();
}
+
+ public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
+ {
+ throw new NotSupportedException("No proper control over pixel format with Cairo, use Skia backend instead");
+ }
+
+ public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt)
+ {
+ throw new NotSupportedException("No proper support with Cairo, use Skia backend instead");
+ }
}
}
diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs
index 0c9ed44274..5ec49fb91f 100644
--- a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs
+++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
namespace Avalonia.Gtk
{
diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
index e52f0efb81..41e174bce4 100644
--- a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
+++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
namespace Avalonia.Gtk3
{
diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs
index 7d99156a1d..b564734a47 100644
--- a/src/Skia/Avalonia.Skia/BitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs
@@ -9,7 +9,7 @@ using SkiaSharp;
namespace Avalonia.Skia
{
- class BitmapImpl : IRenderTargetBitmapImpl
+ class BitmapImpl : IRenderTargetBitmapImpl, IWritableBitmapImpl
{
public SKBitmap Bitmap { get; private set; }
@@ -20,11 +20,11 @@ namespace Avalonia.Skia
PixelWidth = bm.Width;
}
- public BitmapImpl(int width, int height)
+ public BitmapImpl(int width, int height, PixelFormat? fmt = null)
{
PixelHeight = height;
PixelWidth = width;
- var colorType = SKImageInfo.PlatformColorType;
+ var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType;
var runtime = AvaloniaLocator.Current?.GetService()?.GetRuntimeInfo();
if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux)
colorType = SKColorType.Bgra8888;
@@ -38,10 +38,21 @@ namespace Avalonia.Skia
public void Save(string fileName)
{
+
#if DESKTOP
+ if(Bitmap.ColorType != SKColorType.Bgra8888)
+ {
+ using (var tmp = new BitmapImpl(Bitmap.Copy(SKColorType.Bgra8888)))
+ tmp.Save(fileName);
+ return;
+ }
+
IntPtr length;
using (var sdb = new System.Drawing.Bitmap(PixelWidth, PixelHeight, Bitmap.RowBytes,
- System.Drawing.Imaging.PixelFormat.Format32bppArgb, Bitmap.GetPixels(out length)))
+
+ System.Drawing.Imaging.PixelFormat.Format32bppArgb,
+
+ Bitmap.GetPixels(out length)))
sdb.Save(fileName);
#else
//SkiaSharp doesn't expose image encoders yet
@@ -96,5 +107,31 @@ namespace Avalonia.Skia
data.SaveTo(stream);
}
}
+
+ class BitmapFramebuffer : ILockedFramebuffer
+ {
+ private SKBitmap _bmp;
+
+ public BitmapFramebuffer(SKBitmap bmp)
+ {
+ _bmp = bmp;
+ _bmp.LockPixels();
+ }
+
+ public void Dispose()
+ {
+ _bmp.UnlockPixels();
+ _bmp = null;
+ }
+
+ public IntPtr Address => _bmp.GetPixels();
+ public int Width => _bmp.Width;
+ public int Height => _bmp.Height;
+ public int RowBytes => _bmp.RowBytes;
+ public Size Dpi { get; } = new Size(96, 96);
+ public PixelFormat Format => _bmp.ColorType.ToPixelFormat();
+ }
+
+ public ILockedFramebuffer Lock() => new BitmapFramebuffer(Bitmap);
}
}
diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
index b273d6209f..b2b5a0653f 100644
--- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
@@ -22,19 +22,6 @@ namespace Avalonia.Skia
//Nothing to do here, since we don't own framebuffer
}
-
- SKColorType TranslatePixelFormat(PixelFormat fmt)
- {
- if(fmt == PixelFormat.Rgb565)
- return SKColorType.Rgb565;
- if(fmt == PixelFormat.Bgra8888)
- return SKColorType.Bgra8888;
- if (fmt == PixelFormat.Rgba8888)
- return SKColorType.Rgba8888;
- throw new ArgumentException("Unknown pixel format: " + fmt);
- }
-
-
class PixelFormatShim : IDisposable
{
private readonly SKImageInfo _nfo;
@@ -73,8 +60,8 @@ namespace Avalonia.Skia
{
var fb = _surface.Lock();
PixelFormatShim shim = null;
- SKImageInfo framebuffer = new SKImageInfo(fb.Width, fb.Height, TranslatePixelFormat(fb.Format),
- SKAlphaType.Opaque);
+ SKImageInfo framebuffer = new SKImageInfo(fb.Width, fb.Height, fb.Format.ToSkColorType(),
+ SKAlphaType.Premul);
var surface = SKSurface.Create(framebuffer, fb.Address, fb.RowBytes) ??
(shim = new PixelFormatShim(framebuffer, fb.Address, fb.RowBytes))
.CreateSurface();
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index f0735bb0df..72f8d08d44 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -52,6 +52,16 @@ namespace Avalonia.Skia
}
}
+ public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
+ {
+ using (var tmp = new SKBitmap())
+ {
+ tmp.InstallPixels(new SKImageInfo(width, height, format.ToSkColorType(), SKAlphaType.Premul)
+ , data, stride);
+ return new BitmapImpl(tmp.Copy());
+ }
+ }
+
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new Renderer(root, renderLoop);
@@ -74,5 +84,10 @@ namespace Avalonia.Skia
throw new Exception("Skia backend currently only supports framebuffer render target");
return new FramebufferRenderTarget(fb);
}
+
+ public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = null)
+ {
+ return new BitmapImpl(width, height, format);
+ }
}
}
diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
index 0d0ab380c5..8591f9218a 100644
--- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
+++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
@@ -1,4 +1,6 @@
+using System;
using Avalonia.Media;
+using Avalonia.Platform;
using SkiaSharp;
@@ -44,6 +46,28 @@ namespace Avalonia.Skia
return new SKColor(c.R, c.G, c.B, c.A);
}
+ public static SKColorType ToSkColorType(this PixelFormat fmt)
+ {
+ if (fmt == PixelFormat.Rgb565)
+ return SKColorType.Rgb565;
+ if (fmt == PixelFormat.Bgra8888)
+ return SKColorType.Bgra8888;
+ if (fmt == PixelFormat.Rgba8888)
+ return SKColorType.Rgba8888;
+ throw new ArgumentException("Unknown pixel format: " + fmt);
+ }
+
+ public static PixelFormat ToPixelFormat(this SKColorType fmt)
+ {
+ if (fmt == SKColorType.Rgb565)
+ return PixelFormat.Rgb565;
+ if (fmt == SKColorType.Bgra8888)
+ return PixelFormat.Bgra8888;
+ if (fmt == SKColorType.Rgba8888)
+ return PixelFormat.Rgba8888;
+ throw new ArgumentException("Unknown pixel format: " + fmt);
+ }
+
public static SKShaderTileMode ToSKShaderTileMode(this Media.GradientSpreadMethod m)
{
switch (m)
diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
index a055a337eb..ba117d72e9 100644
--- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
+++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
@@ -70,6 +70,7 @@
+
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 34595fecc8..d5b0f22090 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -10,6 +10,7 @@ using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Rendering;
namespace Avalonia
@@ -119,6 +120,11 @@ namespace Avalonia.Direct2D1
return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Device.Factory, width, height);
}
+ public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = null)
+ {
+ return new WritableWicBitmapImpl(s_imagingFactory, width, height, format);
+ }
+
public IStreamGeometryImpl CreateStreamGeometry()
{
return new StreamGeometryImpl();
@@ -133,5 +139,10 @@ namespace Avalonia.Direct2D1
{
return new WicBitmapImpl(s_imagingFactory, stream);
}
+
+ public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
+ {
+ return new WicBitmapImpl(s_imagingFactory, format, data, width, height, stride);
+ }
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
index f17c516edd..1554296c0f 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
@@ -4,6 +4,9 @@
using System;
using System.IO;
using Avalonia.Platform;
+using Avalonia.Win32.Interop;
+using PixelFormat = SharpDX.WIC.PixelFormat;
+using APixelFormat = Avalonia.Platform.PixelFormat;
using SharpDX.WIC;
namespace Avalonia.Direct2D1.Media
@@ -53,17 +56,38 @@ namespace Avalonia.Direct2D1.Media
/// The WIC imaging factory to use.
/// The width of the bitmap.
/// The height of the bitmap.
- public WicBitmapImpl(ImagingFactory factory, int width, int height)
+ /// Pixel format
+ public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null)
{
+ if (!pixelFormat.HasValue)
+ pixelFormat = APixelFormat.Bgra8888;
+
_factory = factory;
+ PixelFormat = pixelFormat;
WicImpl = new Bitmap(
factory,
width,
height,
- PixelFormat.Format32bppPBGRA,
+ pixelFormat.Value.ToWic(),
BitmapCreateCacheOption.CacheOnLoad);
}
+ public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride)
+ {
+ WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
+ PixelFormat = format;
+ using (var l = WicImpl.Lock(BitmapLockFlags.Write))
+ {
+ for (var row = 0; row < height; row++)
+ {
+ UnmanagedMethods.CopyMemory(new IntPtr(l.Data.DataPointer.ToInt64() + row * l.Stride),
+ new IntPtr(data.ToInt64() + row * stride), (uint) l.Data.Pitch);
+ }
+ }
+ }
+
+ protected APixelFormat? PixelFormat { get; }
+
///
/// Gets the width of the bitmap, in pixels.
///
@@ -95,7 +119,7 @@ namespace Avalonia.Direct2D1.Media
if (_direct2D == null)
{
FormatConverter converter = new FormatConverter(_factory);
- converter.Initialize(WicImpl, PixelFormat.Format32bppPBGRA);
+ converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
_direct2D = SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
new file mode 100644
index 0000000000..06eb26b407
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+using SharpDX.WIC;
+using PixelFormat = Avalonia.Platform.PixelFormat;
+
+namespace Avalonia.Direct2D1.Media.Imaging
+{
+ class WritableWicBitmapImpl : WicBitmapImpl, IWritableBitmapImpl
+ {
+ public WritableWicBitmapImpl(ImagingFactory factory, int width, int height, PixelFormat? pixelFormat)
+ : base(factory, width, height, pixelFormat)
+ {
+ }
+
+ class LockedBitmap : ILockedFramebuffer
+ {
+ private readonly BitmapLock _lock;
+ private readonly PixelFormat _format;
+
+ public LockedBitmap(BitmapLock l, PixelFormat format)
+ {
+ _lock = l;
+ _format = format;
+ }
+
+
+ public void Dispose()
+ {
+ _lock.Dispose();
+ }
+
+ public IntPtr Address => _lock.Data.DataPointer;
+ public int Width => _lock.Size.Width;
+ public int Height => _lock.Size.Height;
+ public int RowBytes => _lock.Stride;
+ public Size Dpi { get; } = new Size(96, 96);
+ public PixelFormat Format => _format;
+
+ }
+
+ public ILockedFramebuffer Lock() => new LockedBitmap(WicImpl.Lock(BitmapLockFlags.Write), PixelFormat.Value);
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
index 87be89d10c..118b6deb97 100644
--- a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
+++ b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
@@ -88,6 +88,17 @@ namespace Avalonia.Direct2D1
return CapStyle.Triangle;
}
+ public static Guid ToWic(this Platform.PixelFormat format)
+ {
+ if (format == Platform.PixelFormat.Rgb565)
+ return SharpDX.WIC.PixelFormat.Format16bppBGR565;
+ if (format == Platform.PixelFormat.Bgra8888)
+ return SharpDX.WIC.PixelFormat.Format32bppPBGRA;
+ if (format == Platform.PixelFormat.Rgba8888)
+ return SharpDX.WIC.PixelFormat.Format32bppPRGBA;
+ throw new ArgumentException("Unknown pixel format");
+ }
+
///
/// Converts a pen to a Direct2D stroke style.
///
diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
index 8362305b9f..119715b5fc 100644
--- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
+++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
@@ -9,6 +9,7 @@ using Avalonia.Win32.Interop;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DXGI;
+using PixelFormat = SharpDX.Direct2D1.PixelFormat;
using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using Device = SharpDX.Direct2D1.Device;
using Factory = SharpDX.Direct2D1.Factory;
diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs
index ecd05f41b4..f0a6430918 100644
--- a/src/Windows/Avalonia.Win32/FramebufferManager.cs
+++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 65a9f96b71..fe04d2c011 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -909,6 +909,9 @@ namespace Avalonia.Win32.Interop
uint dwMaximumSizeLow,
string lpName);
+ [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
+ public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
+
public enum MONITOR
{
MONITOR_DEFAULTTONULL = 0x00000000,
diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs
index b00348d97d..fe4fe5c668 100644
--- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs
+++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs
@@ -1,8 +1,9 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
using Avalonia.Win32.Interop;
-using PixelFormat = Avalonia.Controls.Platform.Surfaces.PixelFormat;
+using PixelFormat = Avalonia.Platform.PixelFormat;
namespace Avalonia.Win32
{
diff --git a/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs b/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
index 1baed80ed7..f3fc90a2ab 100644
--- a/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
+++ b/src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
-using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
using CoreGraphics;
using UIKit;
diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
index e00d504124..a70130990a 100644
--- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
+++ b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
@@ -365,6 +365,16 @@ namespace Avalonia.Input.UnitTests
throw new NotImplementedException();
}
+ public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt)
+ {
+ throw new NotImplementedException();
+ }
+
class MockStreamGeometry : Avalonia.Platform.IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
diff --git a/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems b/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems
index ad3b182bdf..e26ac81dda 100644
--- a/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems
+++ b/tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems
@@ -10,6 +10,7 @@
+
diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
new file mode 100644
index 0000000000..1fd5d1eda6
--- /dev/null
+++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
@@ -0,0 +1,139 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+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_CAIRO
+namespace Avalonia.Cairo.RenderTests.Media
+#elif AVALONIA_SKIA
+namespace Avalonia.Skia.RenderTests
+#else
+namespace Avalonia.Direct2D1.RenderTests.Media
+#endif
+{
+ public class BitmapTests : TestBase
+ {
+ public BitmapTests()
+ : base(@"Media\Bitmap")
+ {
+ Directory.CreateDirectory(OutputPath);
+ }
+
+ class Framebuffer : ILockedFramebuffer, IFramebufferPlatformSurface
+ {
+ public Framebuffer(PixelFormat fmt, int width, int height)
+ {
+ Format = fmt;
+ var bpp = fmt == PixelFormat.Rgb565 ? 2 : 4;
+ Width = width;
+ Height = height;
+ RowBytes = bpp * width;
+ Address = Marshal.AllocHGlobal(Height * RowBytes);
+ }
+
+ public IntPtr Address { get; }
+
+ public Size Dpi { get; } = new Size(96, 96);
+
+ public PixelFormat Format { get; }
+
+ public int Height { get; }
+
+ public int RowBytes { get; }
+
+ public int Width { get; }
+
+ public void Dispose()
+ {
+ //no-op
+ }
+
+ public ILockedFramebuffer Lock()
+ {
+ return this;
+ }
+
+ public void Deallocate() => Marshal.FreeHGlobal(Address);
+ }
+
+
+#if AVALONIA_SKIA
+ [Theory]
+#else
+ [Theory(Skip = "Framebuffer not supported")]
+#endif
+ [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgb565)]
+ public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt)
+ {
+ var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
+ var fb = new Framebuffer(fmt, 80, 80);
+ var r = Avalonia.AvaloniaLocator.Current.GetService();
+ using (var target = r.CreateRenderTarget(new object[] { fb }))
+ using (var ctx = target.CreateDrawingContext())
+ {
+ ctx.PushOpacity(0.8);
+ ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100));
+ ctx.FillRectangle(Brushes.Crimson, new Rect(20, 0, 20, 100));
+ ctx.FillRectangle(Brushes.Gold, new Rect(40, 0, 20, 100));
+ }
+
+ var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes);
+ fb.Deallocate();
+ using (var rtb = new RenderTargetBitmap(100, 100))
+ {
+ using (var ctx = rtb.CreateDrawingContext())
+ {
+ ctx.FillRectangle(Brushes.Blue, new Rect(0, 0, 100, 100));
+ ctx.FillRectangle(Brushes.Pink, new Rect(0, 20, 100, 10));
+
+ var rc = new Rect(0, 0, 60, 60);
+ ctx.DrawImage(bmp, 1, rc, rc);
+ }
+ rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png"));
+ }
+ CompareImages(testName);
+ }
+
+#if AVALONIA_CAIRO
+ //wontfix
+#else
+ [Theory]
+#endif
+ [InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)]
+ public void WritableBitmapShouldBeUsable(PixelFormat fmt)
+ {
+ var writableBitmap = new WritableBitmap(256, 256, fmt);
+
+ var data = new int[256 * 256];
+ for (int y = 0; y < 256; y++)
+ for (int x = 0; x < 256; x++)
+ data[y * 256 + x] =(int)((uint)(x + (y << 8)) | 0xFF000000u);
+
+
+ using (var l = writableBitmap.Lock())
+ {
+ for(var r = 0; r<256; r++)
+ {
+ Marshal.Copy(data, r * 256, new IntPtr(l.Address.ToInt64() + r * l.RowBytes), 256);
+ }
+ }
+
+
+ var name = nameof(WritableBitmapShouldBeUsable) + "_" + fmt;
+
+ writableBitmap.Save(System.IO.Path.Combine(OutputPath, name + ".out.png"));
+ CompareImages(name);
+
+ }
+ }
+}
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
index ac31b3852b..303736be9c 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
@@ -45,6 +45,16 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt)
+ {
+ throw new NotImplementedException();
+ }
+
class MockStreamGeometry : IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png
new file mode 100644
index 0000000000..ef77cbe0f2
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png
new file mode 100644
index 0000000000..3cabea1742
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png b/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png
new file mode 100644
index 0000000000..19686464c5
Binary files /dev/null and b/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png b/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png
new file mode 100644
index 0000000000..f3d20008a1
Binary files /dev/null and b/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png b/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png
new file mode 100644
index 0000000000..19686464c5
Binary files /dev/null and b/tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png b/tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png
new file mode 100644
index 0000000000..ef77cbe0f2
Binary files /dev/null and b/tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png b/tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png
new file mode 100644
index 0000000000..3cabea1742
Binary files /dev/null and b/tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png differ