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