Browse Source

Support passing alpha format to created bitmaps.

Dariusz Komosinski 5 years ago
parent
commit
972587130b

+ 3 - 0
samples/RenderDemo/MainWindow.xaml

@@ -44,6 +44,9 @@
       <TabItem Header="RenderTargetBitmap">
         <pages:RenderTargetBitmapPage/>
       </TabItem>
+      <TabItem Header="WriteableBitmap">
+        <pages:WriteableBitmapPage/>
+      </TabItem>
       <TabItem Header="GlyphRun">
         <pages:GlyphRunPage/>
       </TabItem>

+ 92 - 0
samples/RenderDemo/Pages/WriteableBitmapPage.cs

@@ -0,0 +1,92 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Media.Immutable;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace RenderDemo.Pages
+{
+    public class WriteableBitmapPage : Control
+    {
+        private WriteableBitmap _unpremulBitmap;
+        private WriteableBitmap _premulBitmap;
+        private readonly Stopwatch _st = Stopwatch.StartNew();
+
+        protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            _unpremulBitmap = new WriteableBitmap(new PixelSize(256, 256), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Unpremul);
+            _premulBitmap = new WriteableBitmap(new PixelSize(256, 256), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul);
+
+            base.OnAttachedToLogicalTree(e);
+        }
+
+        protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            base.OnDetachedFromLogicalTree(e);
+
+            _unpremulBitmap?.Dispose();
+            _unpremulBitmap = null;
+
+            _premulBitmap?.Dispose();
+            _unpremulBitmap = null;
+        }
+
+        public override void Render(DrawingContext context)
+        {
+            void FillPixels(WriteableBitmap bitmap, byte fillAlpha, bool premul)
+            {
+                using (var fb = bitmap.Lock())
+                {
+                    var data = new int[fb.Size.Width * fb.Size.Height];
+
+                    for (int y = 0; y < fb.Size.Height; y++)
+                    {
+                        for (int x = 0; x < fb.Size.Width; x++)
+                        {
+                            var color = new Color(fillAlpha, 0, 255, 0);
+
+                            if (premul)
+                            {
+                                byte r = (byte) (color.R * color.A / 255);
+                                byte g = (byte) (color.G * color.A / 255);
+                                byte b = (byte) (color.B * color.A / 255);
+
+                                color = new Color(fillAlpha, r, g, b);
+                            }
+
+                            data[y * fb.Size.Width + x] = (int) color.ToUint32();
+                        }
+                    }
+
+                    Marshal.Copy(data, 0, fb.Address, fb.Size.Width * fb.Size.Height);
+                }
+            }
+
+            base.Render(context);
+
+            byte alpha = (byte)((_st.ElapsedMilliseconds / 10) % 256);
+
+            FillPixels(_unpremulBitmap, alpha, false);
+            FillPixels(_premulBitmap, alpha, true);
+
+            context.FillRectangle(Brushes.Red, new Rect(0, 0, 256 * 3, 256));
+
+            context.DrawImage(_unpremulBitmap,
+                new Rect(0, 0, 256, 256),
+                new Rect(0, 0, 256, 256));
+
+            context.DrawImage(_premulBitmap,
+                new Rect(0, 0, 256, 256),
+                new Rect(256, 0, 256, 256));
+
+            context.FillRectangle(new ImmutableSolidColorBrush(Colors.Lime, alpha / 255d), new Rect(512, 0, 256, 256));
+
+            Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
+        }
+    }
+}

+ 3 - 2
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@@ -99,14 +99,15 @@ namespace Avalonia.Media.Imaging
         /// Initializes a new instance of the <see cref="Bitmap"/> class.
         /// </summary>
         /// <param name="format">The pixel format.</param>
+        /// <param name="alphaFormat">The alpha format.</param>
         /// <param name="data">The pointer to the source bytes.</param>
         /// <param name="size">The size of the bitmap in device pixels.</param>
         /// <param name="dpi">The DPI of the bitmap.</param>
         /// <param name="stride">The number of bytes per row.</param>
-        public Bitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
+        public Bitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
         {
             PlatformImpl = RefCountable.Create(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
-                .LoadBitmap(format, data, size, dpi, stride));
+                .LoadBitmap(format, alphaFormat, data, size, dpi, stride));
         }
 
         /// <inheritdoc/>

+ 3 - 2
src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs

@@ -13,9 +13,10 @@ namespace Avalonia.Media.Imaging
         /// <param name="size">The size of the bitmap in device pixels.</param>
         /// <param name="dpi">The DPI of the bitmap.</param>
         /// <param name="format">The pixel format (optional).</param>
+        /// <param name="alphaFormat">The alpha format (optional).</param>
         /// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
-        public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null) 
-            : base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWriteableBitmap(size, dpi, format))
+        public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null) 
+            : base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWriteableBitmap(size, dpi, format, alphaFormat))
         {
         }
         

+ 9 - 0
src/Avalonia.Visuals/Platform/AlphaFormat.cs

@@ -0,0 +1,9 @@
+namespace Avalonia.Platform
+{
+    public enum AlphaFormat
+    {
+        Premul,
+        Unpremul,
+        Opaque
+    }
+}

+ 4 - 3
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@@ -2,7 +2,6 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using Avalonia.Media;
-using Avalonia.Media.Imaging;
 using Avalonia.Visuals.Media.Imaging;
 
 namespace Avalonia.Platform
@@ -83,8 +82,9 @@ namespace Avalonia.Platform
         /// <param name="size">The size of the bitmap in device pixels.</param>
         /// <param name="dpi">The DPI of the bitmap.</param>
         /// <param name="format">Pixel format (optional).</param>
+        /// <param name="alphaFormat">Alpha format (optional).</param>
         /// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
-        IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null);
+        IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null);
 
         /// <summary>
         /// Loads a bitmap implementation from a file..
@@ -124,12 +124,13 @@ namespace Avalonia.Platform
         /// Loads a bitmap implementation from a pixels in memory.
         /// </summary>
         /// <param name="format">The pixel format.</param>
+        /// <param name="alphaFormat">The alpha format.</param>
         /// <param name="data">The pointer to source bytes.</param>
         /// <param name="size">The size of the bitmap in device pixels.</param>
         /// <param name="dpi">The DPI of the bitmap.</param>
         /// <param name="stride">The number of bytes per row.</param>
         /// <returns>An <see cref="IBitmapImpl"/>.</returns>
-        IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride);
+        IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride);
 
         /// <summary>
         /// Creates a platform implementation of a glyph run.

+ 3 - 6
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@@ -85,10 +85,6 @@ namespace Avalonia.Skia
 
                 if (bmp.Width != desired.Width || bmp.Height != desired.Height)
                 {
-                    if (bmp.Height != bmp.Width)
-                    {
-
-                    }
                     var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality());
                     bmp.Dispose();
                     bmp = scaledBmp;
@@ -116,10 +112,11 @@ namespace Avalonia.Skia
         /// <param name="dpi">DPI of the bitmap.</param>
         /// <param name="stride">Stride of data pixels.</param>
         /// <param name="format">Format of data pixels.</param>
+        /// <param name="alphaFormat">Alpha format of data pixels.</param>
         /// <param name="data">Data pixels.</param>
-        public ImmutableBitmap(PixelSize size, Vector dpi, int stride, PixelFormat format, IntPtr data)
+        public ImmutableBitmap(PixelSize size, Vector dpi, int stride, PixelFormat format, AlphaFormat alphaFormat, IntPtr data)
         {
-            var imageInfo = new SKImageInfo(size.Width, size.Height, format.ToSkColorType(), SKAlphaType.Premul);
+            var imageInfo = new SKImageInfo(size.Width, size.Height, format.ToSkColorType(), alphaFormat.ToSkAlphaType());
 
             _image = SKImage.FromPixelCopy(imageInfo, data, stride);
 

+ 4 - 6
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -2,11 +2,9 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.IO; 
-using System.Security.Cryptography;
 using System.Linq;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Media;
-using Avalonia.Media.Imaging;
 using Avalonia.OpenGL;
 using Avalonia.OpenGL.Imaging;
 using Avalonia.Platform;
@@ -76,9 +74,9 @@ namespace Avalonia.Skia
         }
 
         /// <inheritdoc />
-        public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
+        public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
         {
-            return new ImmutableBitmap(size, dpi, stride, format, data);
+            return new ImmutableBitmap(size, dpi, stride, format, alphaFormat, data);
         }
 
         /// <inheritdoc />
@@ -152,9 +150,9 @@ namespace Avalonia.Skia
         }
 
         /// <inheritdoc />
-        public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
+        public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null)
         {
-            return new WriteableBitmapImpl(size, dpi, format);
+            return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
         }
 
         private static readonly SKFont s_font = new SKFont

+ 22 - 0
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@@ -105,6 +105,28 @@ namespace Avalonia.Skia
             throw new ArgumentException("Unknown pixel format: " + fmt);
         }
 
+        public static SKAlphaType ToSkAlphaType(this AlphaFormat fmt)
+        {
+            return fmt switch
+            {
+                AlphaFormat.Premul => SKAlphaType.Premul,
+                AlphaFormat.Unpremul => SKAlphaType.Unpremul,
+                AlphaFormat.Opaque => SKAlphaType.Opaque,
+                _ => throw new ArgumentException($"Unknown alpha format: {fmt}")
+            };
+        }
+
+        public static AlphaFormat ToAlphaFormat(this SKAlphaType fmt)
+        {
+            return fmt switch
+            {
+                SKAlphaType.Premul => AlphaFormat.Premul,
+                SKAlphaType.Unpremul => AlphaFormat.Unpremul,
+                SKAlphaType.Opaque => AlphaFormat.Opaque,
+                _ => throw new ArgumentException($"Unknown alpha format: {fmt}")
+            };
+        }
+
         public static SKShaderTileMode ToSKShaderTileMode(this Media.GradientSpreadMethod m)
         {
             switch (m)

+ 6 - 3
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@@ -22,12 +22,15 @@ namespace Avalonia.Skia
         /// <param name="size">The size of the bitmap in device pixels.</param>
         /// <param name="dpi">The DPI of the bitmap.</param>
         /// <param name="format">The pixel format.</param>
-        public WriteableBitmapImpl(PixelSize size, Vector dpi, PixelFormat? format = null)
+        /// <param name="alphaFormat">The alpha format.</param>
+        public WriteableBitmapImpl(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null)
         {
             PixelSize = size;
             Dpi = dpi;
 
             var colorType = PixelFormatHelper.ResolveColorType(format);
+
+            SKAlphaType alphaType = alphaFormat?.ToSkAlphaType() ?? SKAlphaType.Premul;
             
             var runtimePlatform = AvaloniaLocator.Current?.GetService<IRuntimePlatform>();
             
@@ -35,14 +38,14 @@ namespace Avalonia.Skia
             {
                 _bitmap = new SKBitmap();
 
-                var nfo = new SKImageInfo(size.Width, size.Height, colorType, SKAlphaType.Premul);
+                var nfo = new SKImageInfo(size.Width, size.Height, colorType, alphaType);
                 var blob = runtimePlatform.AllocBlob(nfo.BytesSize);
 
                 _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, s_releaseDelegate, blob);
             }
             else
             {
-                _bitmap = new SKBitmap(size.Width, size.Height, colorType, SKAlphaType.Premul);
+                _bitmap = new SKBitmap(size.Width, size.Height, colorType, alphaType);
             }
 
             _bitmap.Erase(SKColor.Empty);

+ 4 - 9
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -115,11 +115,6 @@ namespace Avalonia.Direct2D1
             SharpDX.Configuration.EnableReleaseOnFinalizer = true;
         }
 
-        public IBitmapImpl CreateBitmap(PixelSize size, Vector dpi)
-        {
-            return new WicBitmapImpl(size, dpi);
-        }
-
         public IFormattedTextImpl CreateFormattedText(
             string text,
             Typeface typeface,
@@ -171,9 +166,9 @@ namespace Avalonia.Direct2D1
             return new WicRenderTargetBitmapImpl(size, dpi);
         }
 
-        public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
+        public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null)
         {
-            return new WriteableWicBitmapImpl(size, dpi, format);
+            return new WriteableWicBitmapImpl(size, dpi, format, alphaFormat);
         }
 
         public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
@@ -213,9 +208,9 @@ namespace Avalonia.Direct2D1
         }
 
         /// <inheritdoc />
-        public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
+        public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
         {
-            return new WicBitmapImpl(format, data, size, dpi, stride);
+            return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride);
         }
 
         public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width)

+ 11 - 6
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@@ -1,9 +1,9 @@
 using System;
 using System.IO;
-using System.Security.Cryptography;
 using Avalonia.Win32.Interop;
 using SharpDX.WIC;
 using APixelFormat = Avalonia.Platform.PixelFormat;
+using AlphaFormat = Avalonia.Platform.AlphaFormat;
 using D2DBitmap = SharpDX.Direct2D1.Bitmap;
 
 namespace Avalonia.Direct2D1.Media
@@ -73,29 +73,34 @@ namespace Avalonia.Direct2D1.Media
         /// <param name="size">The size of the bitmap in device pixels.</param>
         /// <param name="dpi">The DPI of the bitmap.</param>
         /// <param name="pixelFormat">Pixel format</param>
-        public WicBitmapImpl(PixelSize size, Vector dpi, APixelFormat? pixelFormat = null)
+        /// <param name="alphaFormat">Alpha format.</param>
+        public WicBitmapImpl(PixelSize size, Vector dpi, APixelFormat? pixelFormat = null, AlphaFormat? alphaFormat = null)
         {
             if (!pixelFormat.HasValue)
             {
                 pixelFormat = APixelFormat.Bgra8888;
             }
 
+            if (!alphaFormat.HasValue)
+            {
+                alphaFormat = AlphaFormat.Premul;
+            }
+
             PixelFormat = pixelFormat;
             WicImpl = new Bitmap(
                 Direct2D1Platform.ImagingFactory,
                 size.Width,
                 size.Height,
-                pixelFormat.Value.ToWic(),
+                pixelFormat.Value.ToWic(alphaFormat.Value),
                 BitmapCreateCacheOption.CacheOnLoad);
 
             Dpi = dpi;
         }
 
-        public WicBitmapImpl(APixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
+        public WicBitmapImpl(APixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
         {
-            WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, size.Width, size.Height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
+            WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, size.Width, size.Height, format.ToWic(alphaFormat), BitmapCreateCacheOption.CacheOnDemand);
             WicImpl.SetResolution(dpi.X, dpi.Y);
-
             PixelFormat = format;
             Dpi = dpi;
 

+ 2 - 2
src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs

@@ -7,8 +7,8 @@ namespace Avalonia.Direct2D1.Media.Imaging
 {
     class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl
     {
-        public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat) 
-            : base(size, dpi, pixelFormat)
+        public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat, AlphaFormat? alphaFormat) 
+            : base(size, dpi, pixelFormat, alphaFormat)
         {
         }
 

+ 6 - 3
src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Linq;
+using Avalonia.Platform;
 using SharpDX;
 using SharpDX.Direct2D1;
 using SharpDX.Mathematics.Interop;
@@ -89,14 +90,16 @@ namespace Avalonia.Direct2D1
                 return CapStyle.Triangle;
         }
 
-        public static Guid ToWic(this Platform.PixelFormat format)
+        public static Guid ToWic(this Platform.PixelFormat format, Platform.AlphaFormat alphaFormat)
         {
+            bool isPremul = alphaFormat == AlphaFormat.Premul;
+
             if (format == Platform.PixelFormat.Rgb565)
                 return SharpDX.WIC.PixelFormat.Format16bppBGR565;
             if (format == Platform.PixelFormat.Bgra8888)
-                return SharpDX.WIC.PixelFormat.Format32bppPBGRA;
+                return isPremul ? SharpDX.WIC.PixelFormat.Format32bppPBGRA : SharpDX.WIC.PixelFormat.Format32bppBGRA;
             if (format == Platform.PixelFormat.Rgba8888)
-                return SharpDX.WIC.PixelFormat.Format32bppPRGBA;
+                return isPremul ? SharpDX.WIC.PixelFormat.Format32bppPRGBA : SharpDX.WIC.PixelFormat.Format32bppRGBA;
             throw new ArgumentException("Unknown pixel format");
         }