Răsfoiți Sursa

Implemented pixel format transcoding for Bitmap and WriteableBitmap

Nikita Tsukanov 2 ani în urmă
părinte
comite
abf8819947
58 a modificat fișierele cu 797 adăugiri și 83 ștergeri
  1. 76 1
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  2. 51 0
      src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
  3. 280 0
      src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
  4. 45 6
      src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
  5. 1 1
      src/Avalonia.Base/PixelRect.cs
  6. 7 0
      src/Avalonia.Base/Platform/IBitmapWithPixelReadAccessImpl.cs
  7. 2 0
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  8. 1 2
      src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
  9. 93 3
      src/Avalonia.Base/Platform/PixelFormat.cs
  10. 2 1
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  11. 1 1
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  12. 3 0
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  13. 1 1
      src/Avalonia.Native/DeferredFramebuffer.cs
  14. 8 7
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  15. 42 20
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  16. 5 0
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  17. 11 0
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  18. 2 0
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  19. 3 0
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  20. 37 1
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  21. 1 30
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
  22. 2 2
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  23. 1 0
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  24. 2 0
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  25. 115 5
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  26. 2 2
      tests/Avalonia.RenderTests/TestBase.cs
  27. 1 0
      tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
  28. 2 0
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  29. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits
  30. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr24.bits
  31. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr32.bits
  32. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr555.bits
  33. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png
  34. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr565.bits
  35. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png
  36. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgra32.bits
  37. BIN
      tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits
  38. BIN
      tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png
  39. BIN
      tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits
  40. BIN
      tests/TestFiles/PixelFormats/Lenna/Default.expected.png
  41. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray16.bits
  42. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png
  43. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray2.bits
  44. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png
  45. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits
  46. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png
  47. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray4.bits
  48. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png
  49. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray8.bits
  50. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png
  51. BIN
      tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits
  52. BIN
      tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits
  53. BIN
      tests/TestFiles/PixelFormats/Lenna/Prgba64.bits
  54. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits
  55. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgb24.bits
  56. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgb48.bits
  57. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits
  58. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgba64.bits

+ 76 - 1
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@@ -1,5 +1,7 @@
 using System;
 using System;
 using System.IO;
 using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 
@@ -10,6 +12,7 @@ namespace Avalonia.Media.Imaging
     /// </summary>
     /// </summary>
     public class Bitmap : IBitmap
     public class Bitmap : IBitmap
     {
     {
+        private bool _isTranscoded;
         /// <summary>
         /// <summary>
         /// Loads a Bitmap from a stream and decodes at the desired width. Aspect ratio is maintained.
         /// Loads a Bitmap from a stream and decodes at the desired width. Aspect ratio is maintained.
         /// This is more efficient than loading and then resizing.
         /// This is more efficient than loading and then resizing.
@@ -100,7 +103,28 @@ namespace Avalonia.Media.Imaging
         /// <param name="stride">The number of bytes per row.</param>
         /// <param name="stride">The number of bytes per row.</param>
         public Bitmap(PixelFormat format, AlphaFormat alphaFormat, 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(GetFactory().LoadBitmap(format, alphaFormat, data, size, dpi, stride));
+            var factory = GetFactory();
+            if (factory.IsSupportedBitmapPixelFormat(format))
+                PlatformImpl = RefCountable.Create(factory.LoadBitmap(format, alphaFormat, data, size, dpi, stride));
+            else
+            {
+                var transcoded = Marshal.AllocHGlobal(size.Width * size.Height * 4);
+                var transcodedStride = size.Width * 4;
+                try
+                {
+                    PixelFormatReader.Transcode(transcoded, data, size, stride, transcodedStride, format);
+                    var transcodedAlphaFormat = format.HasAlpha ? alphaFormat : AlphaFormat.Opaque;
+                    
+                    PlatformImpl = RefCountable.Create(factory.LoadBitmap(PixelFormat.Rgba8888, transcodedAlphaFormat,
+                        transcoded, size, dpi, transcodedStride));
+                }
+                finally
+                {
+                    Marshal.FreeHGlobal(transcoded);
+                }
+
+                _isTranscoded = true;
+            }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -145,6 +169,57 @@ namespace Avalonia.Media.Imaging
             PlatformImpl.Item.Save(stream, quality);
             PlatformImpl.Item.Save(stream, quality);
         }
         }
 
 
+        public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format;
+
+        protected internal unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride,
+            ILockedFramebuffer fb)
+        {
+            if ((sourceRect.Width <= 0 || sourceRect.Height <= 0) && (sourceRect.X != 0 || sourceRect.Y != 0))
+                throw new ArgumentOutOfRangeException(nameof(sourceRect));
+
+            if (sourceRect.X < 0 || sourceRect.Y < 0)
+                throw new ArgumentOutOfRangeException(nameof(sourceRect));
+            
+            if (sourceRect.Width <= 0)
+                sourceRect = sourceRect.WithWidth(PixelSize.Width);
+            if (sourceRect.Height <= 0)
+                sourceRect = sourceRect.WithHeight(PixelSize.Height);
+
+            if (sourceRect.Right > PixelSize.Width || sourceRect.Bottom > PixelSize.Height)
+                throw new ArgumentOutOfRangeException(nameof(sourceRect));
+            
+            int minStride = checked(((sourceRect.Width * fb.Format.BitsPerPixel) + 7) / 8);
+            if (stride < minStride)
+                throw new ArgumentOutOfRangeException(nameof(stride));
+
+            var minBufferSize = stride * sourceRect.Height;
+            if (minBufferSize > bufferSize)
+                throw new ArgumentOutOfRangeException(nameof(bufferSize));
+
+            for (var y = 0; y < sourceRect.Height; y++)
+            {
+                var srcAddress = fb.Address + fb.RowBytes * y;
+                var dstAddress = buffer + stride * y;
+                Unsafe.CopyBlock(dstAddress.ToPointer(), srcAddress.ToPointer(), (uint)minStride);
+            }
+        }
+        
+        public virtual void CopyPixels(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride)
+        {
+            if (
+                Format == null
+                || PlatformImpl.Item is not IReadableBitmapImpl readable
+                || Format != readable.Format
+            )
+                throw new NotSupportedException("CopyPixels is not supported for this bitmap type");
+            
+            if (_isTranscoded)
+                throw new NotSupportedException("CopyPixels is not supported for transcoded bitmaps");
+            
+            using (var fb = readable.Lock())
+                CopyPixelsCore(sourceRect, buffer, bufferSize, stride, fb);
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         void IImage.Draw(
         void IImage.Draw(
             DrawingContext context,
             DrawingContext context,

+ 51 - 0
src/Avalonia.Base/Media/Imaging/BitmapMemory.cs

@@ -0,0 +1,51 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Avalonia.Platform;
+
+namespace Avalonia.Media.Imaging;
+
+internal class BitmapMemory : IDisposable
+{
+    private readonly int _memorySize;
+
+    public BitmapMemory(PixelFormat format, PixelSize size)
+    {
+        Format = format;
+        Size = size;
+        RowBytes = (size.Width * format.BitsPerPixel + 7) / 8;
+        _memorySize = RowBytes * size.Height;
+        Address = Marshal.AllocHGlobal(_memorySize);
+        GC.AddMemoryPressure(_memorySize);
+    }
+
+    private void ReleaseUnmanagedResources()
+    {
+        if (Address != IntPtr.Zero)
+        {
+            GC.RemoveMemoryPressure(_memorySize);
+            Marshal.FreeHGlobal(Address);
+        }
+    }
+
+    public void Dispose()
+    {
+        ReleaseUnmanagedResources();
+        GC.SuppressFinalize(this);
+    }
+
+    ~BitmapMemory()
+    {
+        ReleaseUnmanagedResources();
+    }
+
+    public IntPtr Address { get; private set; }
+    public PixelSize Size { get; }
+    public int RowBytes { get; }
+    public PixelFormat Format { get; }
+
+
+
+    public void CopyToRgba(IntPtr buffer, int rowBytes) =>
+        PixelFormatReader.Transcode(buffer, Address, Size, RowBytes, rowBytes, Format);
+}

+ 280 - 0
src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs

@@ -0,0 +1,280 @@
+using System;
+using Avalonia.Platform;
+namespace Avalonia.Media.Imaging;
+
+internal struct Rgba8888Pixel
+{
+    public byte R;
+    public byte G;
+    public byte B;
+    public byte A;
+}
+
+static unsafe class PixelFormatReader
+{
+    public interface IPixelFormatReader
+    {
+        Rgba8888Pixel ReadNext();
+        void Reset(IntPtr address);
+    }
+    
+    private static readonly Rgba8888Pixel s_white = new Rgba8888Pixel
+    {
+        A = 255,
+        B = 255,
+        G = 255,
+        R = 255
+    };
+    
+    private static readonly Rgba8888Pixel s_black = new Rgba8888Pixel
+    {
+        A = 255,
+        B = 0,
+        G = 0,
+        R = 0
+    };
+
+    public unsafe struct BlackWhitePixelReader : IPixelFormatReader
+    {
+        private int _bit;
+        private byte* _address;
+
+        public void Reset(IntPtr address)
+        {
+            _address = (byte*)address;
+            _bit = 0;
+        }
+
+        public Rgba8888Pixel ReadNext()
+        {
+            var shift = 7 - _bit;
+            var value = (*_address >> shift) & 1;
+            _bit++;
+            if (_bit == 8)
+            {
+                _address++;
+                _bit = 0;
+            }
+            return value == 1 ? s_white : s_black;
+        }
+    }
+    
+    public unsafe struct Gray2PixelReader : IPixelFormatReader
+    {
+        private int _bit;
+        private byte* _address;
+
+        public void Reset(IntPtr address)
+        {
+            _address = (byte*)address;
+            _bit = 0;
+        }
+
+        private static Rgba8888Pixel[] Palette = new[]
+        {
+            s_black,
+            new Rgba8888Pixel
+            {
+                A = 255, B = 0x55, G = 0x55, R = 0x55
+            },
+            new Rgba8888Pixel
+            {
+                A = 255, B = 0xAA, G = 0xAA, R = 0xAA
+            },
+            s_white
+        };
+
+        public Rgba8888Pixel ReadNext()
+        {
+            var shift = 6 - _bit;
+            var value = (byte)((*_address >> shift));
+            value = (byte)((value & 3)); 
+            _bit += 2;
+            if (_bit == 8)
+            {
+                _address++;
+                _bit = 0;
+            }
+
+            return Palette[value];
+        }
+    }
+    
+    public unsafe struct Gray4PixelReader : IPixelFormatReader
+    {
+        private int _bit;
+        private byte* _address;
+
+        public void Reset(IntPtr address)
+        {
+            _address = (byte*)address;
+            _bit = 0;
+        }
+
+        public Rgba8888Pixel ReadNext()
+        {
+            var shift = 4 - _bit;
+            var value = (byte)((*_address >> shift));
+            value = (byte)((value & 0xF));
+            value = (byte)(value | (value << 4));
+            _bit += 4;
+            if (_bit == 8)
+            {
+                _address++;
+                _bit = 0;
+            }
+
+            return new Rgba8888Pixel
+            {
+                A = 255,
+                B = value,
+                G = value,
+                R = value
+            };
+        }
+    }
+    
+    public unsafe struct Gray8PixelReader : IPixelFormatReader
+    {
+        private byte* _address;
+        public void Reset(IntPtr address)
+        {
+            _address = (byte*)address;
+        }
+
+        public Rgba8888Pixel ReadNext()
+        {
+            var value = *_address;
+            _address++;
+
+            return new Rgba8888Pixel
+            {
+                A = 255,
+                B = value,
+                G = value,
+                R = value
+            };
+        }
+    }
+    
+    public unsafe struct Gray16PixelReader : IPixelFormatReader
+    {
+        private ushort* _address;
+        public Rgba8888Pixel ReadNext()
+        {
+            var value16 = *_address;
+            _address++;
+            var value8 = (byte)(value16 >> 8);
+            return new Rgba8888Pixel
+            {
+                A = 255,
+                B = value8,
+                G = value8,
+                R = value8
+            };
+        }
+
+        public void Reset(IntPtr address) => _address = (ushort*)address;
+    }
+
+    public unsafe struct Gray32FloatPixelReader : IPixelFormatReader
+    {
+        private byte* _address;
+        public Rgba8888Pixel ReadNext()
+        {
+            var f = *(float*)_address;
+            var srgb = Math.Pow(f, 1 / 2.2);
+            var value = (byte)(srgb * 255);
+
+            _address += 4;
+            return new Rgba8888Pixel
+            {
+                A = 255,
+                B = value,
+                G = value,
+                R = value
+            };
+        }
+
+        public void Reset(IntPtr address) => _address = (byte*)address;
+    }
+
+    struct Rgba64
+    {
+#pragma warning disable CS0649
+        public ushort R;
+        public ushort G;
+        public ushort B;
+        public ushort A;
+#pragma warning restore CS0649
+    }
+
+    public unsafe struct Rgba64PixelFormatReader : IPixelFormatReader
+    {
+        private Rgba64* _address;
+        public Rgba8888Pixel ReadNext()
+        {
+            var value = *_address;
+
+            _address++;
+            return new Rgba8888Pixel
+            {
+                A = (byte)(value.A >> 8),
+                B = (byte)(value.B >> 8),
+                G = (byte)(value.G >> 8),
+                R = (byte)(value.R >> 8),
+            };
+        }
+
+        public void Reset(IntPtr address) => _address = (Rgba64*)address;
+    }
+
+    public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst,
+        PixelFormat format)
+    {
+        if (format == PixelFormats.BlackWhite)
+            Transcode<BlackWhitePixelReader>(dst, src, size, strideSrc, strideDst);
+        else if (format == PixelFormats.Gray2)
+            Transcode<Gray2PixelReader>(dst, src, size, strideSrc, strideDst);
+        else if (format == PixelFormats.Gray4)
+            Transcode<Gray4PixelReader>(dst, src, size, strideSrc, strideDst);
+        else if (format == PixelFormats.Gray8)
+            Transcode<Gray8PixelReader>(dst, src, size, strideSrc, strideDst);
+        else if (format == PixelFormats.Gray16)
+            Transcode<Gray16PixelReader>(dst, src, size, strideSrc, strideDst);
+        else if (format == PixelFormats.Gray32Float)
+            Transcode<Gray32FloatPixelReader>(dst, src, size, strideSrc, strideDst);
+        else if (format == PixelFormats.Rgba64)
+            Transcode<Rgba64PixelFormatReader>(dst, src, size, strideSrc, strideDst);
+        else
+            throw new NotSupportedException($"Pixel format {format} is not supported");
+    }
+    
+    public static bool SupportsFormat(PixelFormat format)
+    {
+        return format == PixelFormats.BlackWhite
+               || format == PixelFormats.Gray2
+               || format == PixelFormats.Gray4
+               || format == PixelFormats.Gray8
+               || format == PixelFormats.Gray16
+               || format == PixelFormats.Gray32Float
+               || format == PixelFormats.Rgba64;
+    }
+    
+    public static void Transcode<TReader>(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst) where TReader : struct, IPixelFormatReader
+    {
+        var w = size.Width;
+        var h = size.Height;
+        TReader reader = default;
+        for (var y = 0; y < h; y++)
+        {
+            reader.Reset(src + strideSrc * y);
+            var dstRow = (Rgba8888Pixel*)(dst + strideDst * y);
+            for (var x = 0; x < w; x++)
+            {
+                *dstRow = reader.ReadNext();
+                dstRow++;
+            }
+        }
+    }
+}

+ 45 - 6
src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs

@@ -9,7 +9,9 @@ namespace Avalonia.Media.Imaging
     /// </summary>
     /// </summary>
     public class WriteableBitmap : Bitmap
     public class WriteableBitmap : Bitmap
     {
     {
-
+        // Holds a buffer with pixel format that requires transcoding
+        private BitmapMemory? _pixelFormatMemory = null;
+        
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="WriteableBitmap"/> class.
         /// Initializes a new instance of the <see cref="WriteableBitmap"/> class.
         /// </summary>
         /// </summary>
@@ -19,16 +21,42 @@ namespace Avalonia.Media.Imaging
         /// <param name="alphaFormat">The alpha format (optional).</param>
         /// <param name="alphaFormat">The alpha format (optional).</param>
         /// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
         /// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
         public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null) 
         public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null) 
-            : base(CreatePlatformImpl(size, dpi, format, alphaFormat))
+            : this(CreatePlatformImpl(size, dpi, format, alphaFormat))
         {
         {
         }
         }
 
 
-        private WriteableBitmap(IWriteableBitmapImpl impl) : base(impl)
+        private WriteableBitmap((IBitmapImpl impl, BitmapMemory? mem) bitmapWithMem) : this(bitmapWithMem.impl,
+            bitmapWithMem.mem)
         {
         {
             
             
         }
         }
+        
+        private WriteableBitmap(IBitmapImpl impl, BitmapMemory? pixelFormatMemory = null) : base(impl)
+        {
+            _pixelFormatMemory = pixelFormatMemory;
+        }
 
 
-        public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock();
+        public override PixelFormat? Format => _pixelFormatMemory?.Format ?? base.Format;
+        
+        public ILockedFramebuffer Lock()
+        {
+            if (_pixelFormatMemory == null)
+                return ((IWriteableBitmapImpl)PlatformImpl.Item).Lock();
+            
+            return new LockedFramebuffer(_pixelFormatMemory.Address, _pixelFormatMemory.Size,
+                _pixelFormatMemory.RowBytes,
+                Dpi, _pixelFormatMemory.Format, () =>
+                {
+                    using var inner = ((IWriteableBitmapImpl)PlatformImpl.Item).Lock();
+                    _pixelFormatMemory.CopyToRgba(inner.Address, inner.RowBytes);
+                });
+        }
+
+        public override void CopyPixels(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride)
+        {
+            using (var fb = Lock())
+                CopyPixelsCore(sourceRect, buffer, bufferSize, stride, fb);
+        }
 
 
         public static WriteableBitmap Decode(Stream stream)
         public static WriteableBitmap Decode(Stream stream)
         {
         {
@@ -67,14 +95,25 @@ namespace Avalonia.Media.Imaging
             return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode));
             return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode));
         }
         }
 
 
-        private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat)
+        private static (IBitmapImpl, BitmapMemory?) CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat)
         {
         {
+            if (size.Width <= 0 || size.Height <= 0)
+                throw new ArgumentException("Size should be >= (1,1)", nameof(size));
+            
             var ri = GetFactory();
             var ri = GetFactory();
 
 
             PixelFormat finalFormat = format ?? ri.DefaultPixelFormat;
             PixelFormat finalFormat = format ?? ri.DefaultPixelFormat;
             AlphaFormat finalAlphaFormat = alphaFormat ?? ri.DefaultAlphaFormat;
             AlphaFormat finalAlphaFormat = alphaFormat ?? ri.DefaultAlphaFormat;
 
 
-            return ri.CreateWriteableBitmap(size, dpi, finalFormat, finalAlphaFormat);
+            if (ri.IsSupportedBitmapPixelFormat(finalFormat))
+                return (ri.CreateWriteableBitmap(size, dpi, finalFormat, finalAlphaFormat), null);
+
+            if (!PixelFormatReader.SupportsFormat(finalFormat))
+                throw new NotSupportedException($"Pixel format {finalFormat} is not supported");
+
+            var impl = ri.CreateWriteableBitmap(size, dpi, PixelFormat.Rgba8888,
+                finalFormat.HasAlpha ? finalAlphaFormat : AlphaFormat.Opaque);
+            return (impl, new BitmapMemory(finalFormat, size));
         }
         }
 
 
         private static IPlatformRenderInterface GetFactory()
         private static IPlatformRenderInterface GetFactory()

+ 1 - 1
src/Avalonia.Base/PixelRect.cs

@@ -351,7 +351,7 @@ namespace Avalonia
         /// <returns>The new <see cref="PixelRect"/>.</returns>
         /// <returns>The new <see cref="PixelRect"/>.</returns>
         public PixelRect WithHeight(int height)
         public PixelRect WithHeight(int height)
         {
         {
-            return new PixelRect(X, Y, Width, Height);
+            return new PixelRect(X, Y, Width, height);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 7 - 0
src/Avalonia.Base/Platform/IBitmapWithPixelReadAccessImpl.cs

@@ -0,0 +1,7 @@
+namespace Avalonia.Platform;
+
+public interface IReadableBitmapImpl
+{
+    PixelFormat? Format { get; }
+    ILockedFramebuffer Lock();
+}

+ 2 - 0
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@@ -197,6 +197,8 @@ namespace Avalonia.Platform
         /// Default <see cref="PixelFormat"/> used on this platform.
         /// Default <see cref="PixelFormat"/> used on this platform.
         /// </summary>
         /// </summary>
         public PixelFormat DefaultPixelFormat { get; }
         public PixelFormat DefaultPixelFormat { get; }
+
+        bool IsSupportedBitmapPixelFormat(PixelFormat format);
     }
     }
 
 
     [Unstable]
     [Unstable]

+ 1 - 2
src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs

@@ -6,8 +6,7 @@ namespace Avalonia.Platform
     /// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.WriteableBitmap"/>.
     /// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.WriteableBitmap"/>.
     /// </summary>
     /// </summary>
     [Unstable]
     [Unstable]
-    public interface IWriteableBitmapImpl : IBitmapImpl
+    public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapImpl
     {
     {
-        ILockedFramebuffer Lock();
     }
     }
 }
 }

+ 93 - 3
src/Avalonia.Base/Platform/PixelFormat.cs

@@ -1,9 +1,99 @@
-namespace Avalonia.Platform
+using System;
+
+namespace Avalonia.Platform
 {
 {
-    public enum PixelFormat
+    internal enum PixelFormatEnum
     {
     {
         Rgb565,
         Rgb565,
         Rgba8888,
         Rgba8888,
-        Bgra8888
+        Bgra8888,
+        BlackWhite,
+        Gray2,
+        Gray4,
+        Gray8,
+        Gray16,
+        Gray32Float,
+        Rgba64
+    }
+
+    public struct PixelFormat : IEquatable<PixelFormat>
+    {
+        internal PixelFormatEnum FormatEnum;
+
+        public int BitsPerPixel
+        {
+            get
+            {
+                if (FormatEnum == PixelFormatEnum.BlackWhite)
+                    return 1;
+                else if (FormatEnum == PixelFormatEnum.Gray2)
+                    return 2;
+                else if (FormatEnum == PixelFormatEnum.Gray4)
+                    return 4;
+                else if (FormatEnum == PixelFormatEnum.Gray8)
+                    return 8;
+                else if (FormatEnum == PixelFormatEnum.Rgb565 
+                         || FormatEnum == PixelFormatEnum.Gray16)
+                    return 16;
+                else if (FormatEnum == PixelFormatEnum.Rgba64)
+                    return 64;
+
+                return 32;
+            }
+        }
+
+        internal bool HasAlpha => FormatEnum == PixelFormatEnum.Rgba8888 
+                                  || FormatEnum == PixelFormatEnum.Bgra8888
+                                  || FormatEnum == PixelFormatEnum.Rgba64;
+
+        internal PixelFormat(PixelFormatEnum format)
+        {
+            FormatEnum = format;
+        }
+
+        public static PixelFormat Rgb565 => PixelFormats.Rgb565;
+        public static PixelFormat Rgba8888 => PixelFormats.Rgba8888;
+        public static PixelFormat Bgra8888 => PixelFormats.Bgra8888;
+
+        public bool Equals(PixelFormat other)
+        {
+            return FormatEnum == other.FormatEnum;
+        }
+
+        public override bool Equals(object? obj)
+        {
+            return obj is PixelFormat other && Equals(other);
+        }
+
+        public override int GetHashCode()
+        {
+            return (int)FormatEnum;
+        }
+
+        public static bool operator ==(PixelFormat left, PixelFormat right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(PixelFormat left, PixelFormat right)
+        {
+            return !left.Equals(right);
+        }
+
+        public override string ToString() => FormatEnum.ToString();
+    }
+
+    public static class PixelFormats
+    {
+        public static PixelFormat Rgb565 { get; } = new PixelFormat(PixelFormatEnum.Rgb565);
+        public static PixelFormat Rgba8888 { get; } = new PixelFormat(PixelFormatEnum.Rgba8888);
+        public static PixelFormat Rgba64 { get; } = new PixelFormat(PixelFormatEnum.Rgba64);
+        public static PixelFormat Bgra8888 { get; } = new PixelFormat(PixelFormatEnum.Bgra8888);
+        public static PixelFormat BlackWhite { get; } = new PixelFormat(PixelFormatEnum.BlackWhite);
+        public static PixelFormat Gray2 { get; } = new PixelFormat(PixelFormatEnum.Gray2);
+        public static PixelFormat Gray4 { get; } = new PixelFormat(PixelFormatEnum.Gray4);
+        public static PixelFormat Gray8 { get; } = new PixelFormat(PixelFormatEnum.Gray8);
+        public static PixelFormat Gray16 { get; } = new PixelFormat(PixelFormatEnum.Gray16);
+        public static PixelFormat Gray32Float { get; } = new PixelFormat(PixelFormatEnum.Gray32Float);
     }
     }
 }
 }

+ 2 - 1
src/Avalonia.Controls/Remote/RemoteWidget.cs

@@ -2,6 +2,7 @@
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Media.Imaging;
 using Avalonia.Media.Imaging;
+using Avalonia.Platform;
 using Avalonia.Remote.Protocol;
 using Avalonia.Remote.Protocol;
 using Avalonia.Remote.Protocol.Viewport;
 using Avalonia.Remote.Protocol.Viewport;
 using Avalonia.Threading;
 using Avalonia.Threading;
@@ -72,7 +73,7 @@ namespace Avalonia.Controls.Remote
         {
         {
             if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
             if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
             {
             {
-                var fmt = (PixelFormat) _lastFrame.Format;
+                var fmt = new PixelFormat((PixelFormatEnum) _lastFrame.Format);
                 if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width ||
                 if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width ||
                     _bitmap.PixelSize.Height != _lastFrame.Height)
                     _bitmap.PixelSize.Height != _lastFrame.Height)
                 {
                 {

+ 1 - 1
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@@ -282,7 +282,7 @@ namespace Avalonia.Controls.Remote.Server
             {
             {
                 if (width > 0 && height > 0)
                 if (width > 0 && height > 0)
                 {
                 {
-                    _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, (PixelFormat)fmt,
+                    _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, new((PixelFormatEnum)fmt),
                         null);
                         null);
                     Paint?.Invoke(new Rect(0, 0, width, height));
                     Paint?.Invoke(new Rect(0, 0, width, height));
                 }
                 }

+ 3 - 0
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -30,6 +30,7 @@ namespace Avalonia.Headless
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
 
 
         public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
         public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+        public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
 
 
         public IGeometryImpl CreateEllipseGeometry(Rect rect) => new HeadlessGeometryStub(rect);
         public IGeometryImpl CreateEllipseGeometry(Rect rect) => new HeadlessGeometryStub(rect);
 
 
@@ -353,6 +354,8 @@ namespace Avalonia.Headless
 
 
             }
             }
 
 
+            public PixelFormat? Format { get; }
+
             public ILockedFramebuffer Lock()
             public ILockedFramebuffer Lock()
             {
             {
                 Version++;
                 Version++;

+ 1 - 1
src/Avalonia.Native/DeferredFramebuffer.cs

@@ -63,7 +63,7 @@ namespace Avalonia.Native
                     },
                     },
                     Width = Size.Width,
                     Width = Size.Width,
                     Height = Size.Height,
                     Height = Size.Height,
-                    PixelFormat = (AvnPixelFormat)Format,
+                    PixelFormat = (AvnPixelFormat)Format.FormatEnum,
                     Stride = RowBytes
                     Stride = RowBytes
                 };
                 };
 
 

+ 8 - 7
src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs

@@ -93,9 +93,8 @@ namespace Avalonia.LinuxFramebuffer
 
 
         void SetBpp(PixelFormat format)
         void SetBpp(PixelFormat format)
         {
         {
-            switch (format)
+            if (format == PixelFormat.Rgba8888)
             {
             {
-            case PixelFormat.Rgba8888:
                 _varInfo.bits_per_pixel = 32;
                 _varInfo.bits_per_pixel = 32;
                 _varInfo.grayscale = 0;
                 _varInfo.grayscale = 0;
                 _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
                 _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
@@ -105,8 +104,9 @@ namespace Avalonia.LinuxFramebuffer
                 _varInfo.green.offset = 8;
                 _varInfo.green.offset = 8;
                 _varInfo.blue.offset = 16;
                 _varInfo.blue.offset = 16;
                 _varInfo.transp.offset = 24;
                 _varInfo.transp.offset = 24;
-                 break;
-            case PixelFormat.Bgra8888:
+            }
+            else if (format == PixelFormat.Bgra8888)
+            {
                 _varInfo.bits_per_pixel = 32;
                 _varInfo.bits_per_pixel = 32;
                 _varInfo.grayscale = 0;
                 _varInfo.grayscale = 0;
                 _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
                 _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
@@ -116,8 +116,9 @@ namespace Avalonia.LinuxFramebuffer
                 _varInfo.green.offset = 8;
                 _varInfo.green.offset = 8;
                 _varInfo.red.offset = 16;
                 _varInfo.red.offset = 16;
                 _varInfo.transp.offset = 24;
                 _varInfo.transp.offset = 24;
-                 break;
-            case PixelFormat.Rgb565:
+            }
+            else if (format == PixelFormat.Rgb565)
+            {
                 _varInfo.bits_per_pixel = 16;
                 _varInfo.bits_per_pixel = 16;
                 _varInfo.grayscale = 0;
                 _varInfo.grayscale = 0;
                 _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield();
                 _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield();
@@ -126,8 +127,8 @@ namespace Avalonia.LinuxFramebuffer
                 _varInfo.green.length = 6;
                 _varInfo.green.length = 6;
                 _varInfo.blue.offset = 11;
                 _varInfo.blue.offset = 11;
                 _varInfo.blue.length = 5;
                 _varInfo.blue.length = 5;
-                 break;
             }
             }
+            else throw new NotSupportedException($"Pixel format {format} is not supported");
         }
         }
 
 
         public string Id { get; private set; }
         public string Id { get; private set; }

+ 42 - 20
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@@ -10,9 +10,10 @@ namespace Avalonia.Skia
     /// <summary>
     /// <summary>
     /// Immutable Skia bitmap.
     /// Immutable Skia bitmap.
     /// </summary>
     /// </summary>
-    internal class ImmutableBitmap : IDrawableBitmapImpl
+    internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl
     {
     {
         private readonly SKImage _image;
         private readonly SKImage _image;
+        private readonly SKBitmap? _bitmap;
 
 
         /// <summary>
         /// <summary>
         /// Create immutable bitmap from given stream.
         /// Create immutable bitmap from given stream.
@@ -23,12 +24,13 @@ namespace Avalonia.Skia
             using (var skiaStream = new SKManagedStream(stream))
             using (var skiaStream = new SKManagedStream(stream))
             {
             {
                 using (var data = SKData.Create(skiaStream))
                 using (var data = SKData.Create(skiaStream))
-                    _image = SKImage.FromEncodedData(data);
-
-                if (_image == null)
-                {
+                    _bitmap = SKBitmap.Decode(data);
+                
+                if (_bitmap == null)
                     throw new ArgumentException("Unable to load bitmap from provided data");
                     throw new ArgumentException("Unable to load bitmap from provided data");
-                }
+
+                _bitmap.SetImmutable();
+                _image = SKImage.FromBitmap(_bitmap);
 
 
                 PixelSize = new PixelSize(_image.Width, _image.Height);
                 PixelSize = new PixelSize(_image.Width, _image.Height);
 
 
@@ -47,10 +49,10 @@ namespace Avalonia.Skia
         public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode)
         public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode)
         {
         {
             SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888);
             SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888);
-            SKImage output = SKImage.Create(info);
-            src._image.ScalePixels(output.PeekPixels(), interpolationMode.ToSKFilterQuality());
-
-            _image = output;
+            _bitmap = new SKBitmap(info);
+            src._image.ScalePixels(_bitmap.PeekPixels(), interpolationMode.ToSKFilterQuality());
+            _bitmap.SetImmutable();
+            _image = SKImage.FromBitmap(_bitmap);
 
 
             PixelSize = new PixelSize(_image.Width, _image.Height);
             PixelSize = new PixelSize(_image.Width, _image.Height);
 
 
@@ -71,8 +73,11 @@ namespace Avalonia.Skia
 
 
                 // decode the bitmap at the nearest size
                 // decode the bitmap at the nearest size
                 var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height);
                 var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height);
-                var bmp = SKBitmap.Decode(codec, nearest);
+                _bitmap = SKBitmap.Decode(codec, nearest);
 
 
+                if (_bitmap == null)
+                    throw new ArgumentException("Unable to load bitmap from provided data");
+                
                 // now scale that to the size that we want
                 // now scale that to the size that we want
                 var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height);
                 var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height);
 
 
@@ -88,15 +93,16 @@ namespace Avalonia.Skia
                     desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize);
                     desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize);
                 }
                 }
 
 
-                if (bmp.Width != desired.Width || bmp.Height != desired.Height)
+                if (_bitmap.Width != desired.Width || _bitmap.Height != desired.Height)
                 {
                 {
-                    var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality());
-                    bmp.Dispose();
-                    bmp = scaledBmp;
+                    var scaledBmp = _bitmap.Resize(desired, interpolationMode.ToSKFilterQuality());
+                    _bitmap.Dispose();
+                    _bitmap = scaledBmp;
                 }
                 }
+                
+                _bitmap!.SetImmutable();
 
 
-                _image = SKImage.FromBitmap(bmp);
-                bmp.Dispose();
+                _image = SKImage.FromBitmap(_bitmap);
 
 
                 if (_image == null)
                 if (_image == null)
                 {
                 {
@@ -121,9 +127,15 @@ namespace Avalonia.Skia
         /// <param name="data">Data pixels.</param>
         /// <param name="data">Data pixels.</param>
         public ImmutableBitmap(PixelSize size, Vector dpi, int stride, PixelFormat format, AlphaFormat alphaFormat, 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(), alphaFormat.ToSkAlphaType());
-
-            _image = SKImage.FromPixelCopy(imageInfo, data, stride);
+            using (var tmp = new SKBitmap())
+            {
+                tmp.InstallPixels(
+                    new SKImageInfo(size.Width, size.Height, format.ToSkColorType(), alphaFormat.ToSkAlphaType()),
+                    data);
+                _bitmap = tmp.Copy();
+            }
+            _bitmap!.SetImmutable();
+            _image = SKImage.FromBitmap(_bitmap);
 
 
             if (_image == null)
             if (_image == null)
             {
             {
@@ -143,6 +155,7 @@ namespace Avalonia.Skia
         public void Dispose()
         public void Dispose()
         {
         {
             _image.Dispose();
             _image.Dispose();
+            _bitmap?.Dispose();
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -162,5 +175,14 @@ namespace Avalonia.Skia
         {
         {
             context.Canvas.DrawImage(_image, sourceRect, destRect, paint);
             context.Canvas.DrawImage(_image, sourceRect, destRect, paint);
         }
         }
+
+        public PixelFormat? Format => _bitmap?.ColorType.ToAvalonia();
+        public ILockedFramebuffer Lock()
+        {
+            if (_bitmap == null)
+                throw new NotSupportedException();
+            return new LockedFramebuffer(_bitmap.GetPixels(), PixelSize, _bitmap.RowBytes, Dpi,
+                _bitmap.ColorType.ToAvalonia().Value, null);
+        }
     }
     }
 }
 }

+ 5 - 0
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -41,6 +41,11 @@ namespace Avalonia.Skia
 
 
         public PixelFormat DefaultPixelFormat { get; }
         public PixelFormat DefaultPixelFormat { get; }
 
 
+        public bool IsSupportedBitmapPixelFormat(PixelFormat format) =>
+            format == PixelFormats.Rgb565
+            || format == PixelFormats.Bgra8888
+            || format == PixelFormats.Rgba8888;
+
         public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
         public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
 
 
         public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
         public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);

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

@@ -127,6 +127,17 @@ namespace Avalonia.Skia
             throw new ArgumentException("Unknown pixel format: " + fmt);
             throw new ArgumentException("Unknown pixel format: " + fmt);
         }
         }
 
 
+        public static PixelFormat? ToAvalonia(this SKColorType colorType)
+        {
+            if (colorType == SKColorType.Rgb565)
+                return PixelFormats.Rgb565;
+            if (colorType == SKColorType.Bgra8888)
+                return PixelFormats.Bgra8888;
+            if (colorType == SKColorType.Rgba8888)
+                return PixelFormats.Rgba8888;
+            return null;
+        }
+
         public static PixelFormat ToPixelFormat(this SKColorType fmt)
         public static PixelFormat ToPixelFormat(this SKColorType fmt)
         {
         {
             if (fmt == SKColorType.Rgb565)
             if (fmt == SKColorType.Rgb565)

+ 2 - 0
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@@ -154,6 +154,8 @@ namespace Avalonia.Skia
             }
             }
         }
         }
 
 
+        public PixelFormat? Format => _bitmap.ColorType.ToAvalonia();
+
         /// <inheritdoc />
         /// <inheritdoc />
         public ILockedFramebuffer Lock() => new BitmapFramebuffer(this, _bitmap);
         public ILockedFramebuffer Lock() => new BitmapFramebuffer(this, _bitmap);
 
 

+ 3 - 0
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -339,5 +339,8 @@ namespace Avalonia.Direct2D1
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
 
 
         public PixelFormat DefaultPixelFormat => PixelFormat.Bgra8888;
         public PixelFormat DefaultPixelFormat => PixelFormat.Bgra8888;
+        public bool IsSupportedBitmapPixelFormat(PixelFormat format) =>
+            format == PixelFormats.Bgra8888 
+            || format == PixelFormats.Rgba8888;
     }
     }
 }
 }

+ 37 - 1
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@@ -1,11 +1,14 @@
 using System;
 using System;
 using System.IO;
 using System.IO;
+using Avalonia.Direct2D1.Media.Imaging;
 using Avalonia.Win32.Interop;
 using Avalonia.Win32.Interop;
 using SharpDX.WIC;
 using SharpDX.WIC;
 using APixelFormat = Avalonia.Platform.PixelFormat;
 using APixelFormat = Avalonia.Platform.PixelFormat;
 using AlphaFormat = Avalonia.Platform.AlphaFormat;
 using AlphaFormat = Avalonia.Platform.AlphaFormat;
 using D2DBitmap = SharpDX.Direct2D1.Bitmap;
 using D2DBitmap = SharpDX.Direct2D1.Bitmap;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
+using Avalonia.Platform;
+using PixelFormat = SharpDX.WIC.PixelFormat;
 
 
 namespace Avalonia.Direct2D1.Media
 namespace Avalonia.Direct2D1.Media
 {
 {
@@ -13,7 +16,7 @@ namespace Avalonia.Direct2D1.Media
     /// A WIC implementation of a <see cref="Avalonia.Media.Imaging.Bitmap"/>.
     /// A WIC implementation of a <see cref="Avalonia.Media.Imaging.Bitmap"/>.
     /// </summary>
     /// </summary>
     [Unstable]
     [Unstable]
-    public class WicBitmapImpl : BitmapImpl
+    public class WicBitmapImpl : BitmapImpl, IReadableBitmapImpl
     {
     {
         private readonly BitmapDecoder _decoder;
         private readonly BitmapDecoder _decoder;
 
 
@@ -197,5 +200,38 @@ namespace Avalonia.Direct2D1.Media
                 encoder.Commit();
                 encoder.Commit();
             }
             }
         }
         }
+        
+        class LockedBitmap : ILockedFramebuffer
+        {
+            private readonly WicBitmapImpl _parent;
+            private readonly BitmapLock _lock;
+            private readonly APixelFormat _format;
+
+            public LockedBitmap(WicBitmapImpl parent, BitmapLock l, APixelFormat format)
+            {
+                _parent = parent;
+                _lock = l;
+                _format = format;
+            }
+
+
+            public void Dispose()
+            {
+                _lock.Dispose();
+                _parent.Version++;
+            }
+
+            public IntPtr Address => _lock.Data.DataPointer;
+            public PixelSize Size => _lock.Size.ToAvalonia();
+            public int RowBytes => _lock.Stride;
+            public Vector Dpi => _parent.Dpi;
+            public APixelFormat Format => _format;
+
+        }
+
+        APixelFormat? IReadableBitmapImpl.Format => PixelFormat;
+
+        public ILockedFramebuffer Lock() =>
+            new LockedBitmap(this, WicImpl.Lock(BitmapLockFlags.Write), PixelFormat.Value);
     }
     }
 }
 }

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

@@ -29,35 +29,6 @@ namespace Avalonia.Direct2D1.Media.Imaging
         {
         {
         }
         }
 
 
-        class LockedBitmap : ILockedFramebuffer
-        {
-            private readonly WriteableWicBitmapImpl _parent;
-            private readonly BitmapLock _lock;
-            private readonly PixelFormat _format;
-
-            public LockedBitmap(WriteableWicBitmapImpl parent, BitmapLock l, PixelFormat format)
-            {
-                _parent = parent;
-                _lock = l;
-                _format = format;
-            }
-
-
-            public void Dispose()
-            {
-                _lock.Dispose();
-                _parent.Version++;
-            }
-
-            public IntPtr Address => _lock.Data.DataPointer;
-            public PixelSize Size => _lock.Size.ToAvalonia();
-            public int RowBytes => _lock.Stride;
-            public Vector Dpi => _parent.Dpi;
-            public PixelFormat Format => _format;
-
-        }
-
-        public ILockedFramebuffer Lock() =>
-            new LockedBitmap(this, WicImpl.Lock(BitmapLockFlags.Write), PixelFormat.Value);
+        public PixelFormat? Format => PixelFormat;
     }
     }
 }
 }

+ 2 - 2
src/Windows/Avalonia.Win32/FramebufferManager.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Win32
     internal class FramebufferManager : IFramebufferPlatformSurface, IDisposable
     internal class FramebufferManager : IFramebufferPlatformSurface, IDisposable
     {
     {
         private const int _bytesPerPixel = 4;
         private const int _bytesPerPixel = 4;
-        private const PixelFormat _format = PixelFormat.Bgra8888;
+        private static readonly PixelFormat s_format = PixelFormat.Bgra8888;
 
 
         private readonly IntPtr _hwnd;
         private readonly IntPtr _hwnd;
         private readonly object _lock;
         private readonly object _lock;
@@ -50,7 +50,7 @@ namespace Avalonia.Win32
 
 
                 return fb = new LockedFramebuffer(
                 return fb = new LockedFramebuffer(
                     framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes,
                     framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes,
-                    GetCurrentDpi(), _format, _onDisposeAction);
+                    GetCurrentDpi(), s_format, _onDisposeAction);
             }
             }
             finally
             finally
             {
             {

+ 1 - 0
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@@ -91,6 +91,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
         public bool SupportsIndividualRoundRects { get; set; }
         public bool SupportsIndividualRoundRects { get; set; }
         public AlphaFormat DefaultAlphaFormat { get; }
         public AlphaFormat DefaultAlphaFormat { get; }
         public PixelFormat DefaultPixelFormat { get; }
         public PixelFormat DefaultPixelFormat { get; }
+        public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
 
 
         public IFontManagerImpl CreateFontManager()
         public IFontManagerImpl CreateFontManager()
         {
         {

+ 2 - 0
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@@ -139,6 +139,8 @@ namespace Avalonia.Benchmarks
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
 
 
         public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
         public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+        public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
+
         public void Dispose()
         public void Dispose()
         {
         {
             
             

+ 115 - 5
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.IO;
 using System.IO;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Controls.Platform.Surfaces;
@@ -9,6 +10,8 @@ using Avalonia.Media;
 using Avalonia.Media.Imaging;
 using Avalonia.Media.Imaging;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Xunit;
 using Xunit;
+using Path = System.IO.Path;
+#pragma warning disable CS0649
 
 
 #if AVALONIA_SKIA
 #if AVALONIA_SKIA
 namespace Avalonia.Skia.RenderTests
 namespace Avalonia.Skia.RenderTests
@@ -60,13 +63,14 @@ namespace Avalonia.Direct2D1.RenderTests.Media
 
 
         
         
         [Theory]
         [Theory]
-        [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888),
+        [InlineData(PixelFormatEnum.Rgba8888), InlineData(PixelFormatEnum.Bgra8888),
 #if AVALONIA_SKIA
 #if AVALONIA_SKIA
-             InlineData(PixelFormat.Rgb565)
+             InlineData(PixelFormatEnum.Rgb565)
 #endif
 #endif
             ]
             ]
-        public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt)
+        internal void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormatEnum fmte)
         {
         {
+            var fmt = new PixelFormat(fmte);
             var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
             var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
             var fb = new Framebuffer(fmt, new PixelSize(80, 80));
             var fb = new Framebuffer(fmt, new PixelSize(80, 80));
             var r = Avalonia.AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
             var r = Avalonia.AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
@@ -100,9 +104,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media
         }
         }
 
 
         [Theory]
         [Theory]
-        [InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)]
-        public void WriteableBitmapShouldBeUsable(PixelFormat fmt)
+        [InlineData(PixelFormatEnum.Bgra8888), InlineData(PixelFormatEnum.Rgba8888)]
+        internal void WriteableBitmapShouldBeUsable(PixelFormatEnum fmte)
         {
         {
+            var fmt = new PixelFormat(fmte);
             var writeableBitmap = new WriteableBitmap(new PixelSize(256, 256), new Vector(96, 96), fmt);
             var writeableBitmap = new WriteableBitmap(new PixelSize(256, 256), new Vector(96, 96), fmt);
 
 
             var data = new int[256 * 256];
             var data = new int[256 * 256];
@@ -126,5 +131,110 @@ namespace Avalonia.Direct2D1.RenderTests.Media
             CompareImagesNoRenderer(name);
             CompareImagesNoRenderer(name);
 
 
         }
         }
+        
+        struct RawHeader
+        {
+            public int Width, Height, Stride;
+        }
+
+        [Theory,
+         InlineData(PixelFormatEnum.BlackWhite),
+         InlineData(PixelFormatEnum.Gray2),
+         InlineData(PixelFormatEnum.Gray4),
+         InlineData(PixelFormatEnum.Gray8),
+         InlineData(PixelFormatEnum.Gray16),
+         InlineData(PixelFormatEnum.Gray32Float),
+         InlineData(PixelFormatEnum.Rgba64),
+         InlineData(PixelFormatEnum.Rgba64, AlphaFormat.Premul),
+        ]
+        internal unsafe void BitmapsShouldSupportTranscoders_Lenna(PixelFormatEnum format, AlphaFormat alphaFormat = AlphaFormat.Unpremul)
+        {
+            var relativeFilesDir = "../../../PixelFormats/Lenna";
+            var filesDir = Path.Combine(OutputPath, relativeFilesDir);
+            
+            var formatName = format.ToString();
+            if (alphaFormat == AlphaFormat.Premul)
+                formatName = "P" + formatName.ToLowerInvariant();
+
+            var bitsData = File.ReadAllBytes(Path.Combine(filesDir, formatName + ".bits")).AsSpan();
+            var header = MemoryMarshal.Cast<byte, RawHeader>(bitsData.Slice(0, Unsafe.SizeOf<RawHeader>()))[0];
+            var data = bitsData.Slice(Unsafe.SizeOf<RawHeader>());
+
+            var size = new PixelSize(header.Width, header.Height);
+            var stride = header.Stride;
+            
+            string expectedName = Path.Combine(relativeFilesDir, formatName);
+            if (!File.Exists(Path.Combine(OutputPath, expectedName + ".expected.png")))
+                expectedName = Path.Combine(relativeFilesDir, "Default");
+
+            foreach (var writable in new[] { false, true })
+            {
+                var testName = nameof(BitmapsShouldSupportTranscoders_Lenna) + "_" + formatName +
+                               (writable ? "_Writeable" : "_Normal");
+
+                var path = System.IO.Path.Combine(OutputPath, testName + ".out.png");
+                fixed (byte* pData = data)
+                {
+                    Bitmap? b = null;
+                    try
+                    {
+                        if (writable)
+                        {
+                            var bmp = new WriteableBitmap(size, new Vector(96, 96), new PixelFormat(format),
+                                alphaFormat);
+
+                            using (var l = bmp.Lock())
+                            {
+                                var minStride = (l.Size.Width * l.Format.BitsPerPixel + 7) / 8;
+                                for (var y = 0; y < size.Height; y++)
+                                {
+                                    Unsafe.CopyBlock((l.Address + y * l.RowBytes).ToPointer(), pData + y * stride,
+                                        (uint)minStride);
+                                }
+                            }
+
+                            b = bmp;
+                            var copyTo = new byte[data.Length];
+                            fixed (byte* pCopyTo = copyTo)
+                                b.CopyPixels(default, new IntPtr(pCopyTo), copyTo.Length, stride);
+                            Assert.Equal(data.ToArray(), copyTo);
+                        }
+                        else
+                        {
+                            b = new Bitmap(new PixelFormat(format), alphaFormat, new IntPtr(pData),
+                                size, new Vector(96, 96), stride);
+                        }
+
+                        b.Save(path);
+                        CompareImagesNoRenderer(testName, expectedName);
+                    }
+                    finally
+                    {
+                        b?.Dispose();
+                    }
+                }
+            }
+        }
+
+        [Fact]
+        public unsafe void CopyPixelsShouldWorkForNonTranscodedBitmaps()
+        {
+            var stride = 32 * 4;
+            var data = new byte[32 * stride];
+            new Random().NextBytes(data);
+            for (var c = 0; c < data.Length; c++)
+                if (data[c] == 0)
+                    data[c] = 1;
+
+            Bitmap bmp;
+            fixed (byte* pData = data)
+                bmp = new Bitmap(PixelFormat.Bgra8888, AlphaFormat.Unpremul, new IntPtr(pData), new PixelSize(32, 32),
+                    new Vector(96, 96), 32 * 4);
+
+            var copyTo = new byte[data.Length];
+            fixed (byte* pCopyTo = copyTo)
+                bmp.CopyPixels(default, new IntPtr(pCopyTo), data.Length, stride);
+            Assert.Equal(data, copyTo);
+        }
     }
     }
 }
 }

+ 2 - 2
tests/Avalonia.RenderTests/TestBase.cs

@@ -190,9 +190,9 @@ namespace Avalonia.Direct2D1.RenderTests
             }
             }
         }
         }
 
 
-        protected void CompareImagesNoRenderer([CallerMemberName] string testName = "")
+        protected void CompareImagesNoRenderer([CallerMemberName] string testName = "", string expectedName = null)
         {
         {
-            var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
+            var expectedPath = Path.Combine(OutputPath, (expectedName ?? testName) + ".expected.png");
             var actualPath = Path.Combine(OutputPath, testName + ".out.png");
             var actualPath = Path.Combine(OutputPath, testName + ".out.png");
 
 
             using (var expected = Image.Load<Rgba32>(expectedPath))
             using (var expected = Image.Load<Rgba32>(expectedPath))

+ 1 - 0
tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj

@@ -2,6 +2,7 @@
   <PropertyGroup>
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>
     <TargetFramework>net6.0</TargetFramework>
     <DefineConstants>AVALONIA_SKIA;AVALONIA_SKIA_SKIP_FAIL</DefineConstants>
     <DefineConstants>AVALONIA_SKIA;AVALONIA_SKIA_SKIP_FAIL</DefineConstants>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <Compile Include="..\Avalonia.RenderTests\**\*.cs" />
     <Compile Include="..\Avalonia.RenderTests\**\*.cs" />

+ 2 - 0
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@@ -182,6 +182,8 @@ namespace Avalonia.UnitTests
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
         public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
 
 
         public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
         public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+        public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
+
         public void Dispose()
         public void Dispose()
         {
         {
         }
         }

BIN
tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Bgr24.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Bgr32.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Bgr555.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Bgr565.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Bgra32.bits


BIN
tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits


BIN
tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Default.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Gray16.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Gray2.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Gray4.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Gray8.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png


BIN
tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Prgba64.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Rgb24.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Rgb48.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits


BIN
tests/TestFiles/PixelFormats/Lenna/Rgba64.bits