diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
index ce38fc5abc..6577532891 100644
--- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Utilities;
@@ -10,6 +12,7 @@ namespace Avalonia.Media.Imaging
///
public class Bitmap : IBitmap
{
+ private bool _isTranscoded;
///
/// 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.
@@ -100,7 +103,28 @@ namespace Avalonia.Media.Imaging
/// The number of bytes per row.
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;
+ }
}
///
@@ -145,6 +169,57 @@ namespace Avalonia.Media.Imaging
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);
+ }
+
///
void IImage.Draw(
DrawingContext context,
diff --git a/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs b/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
new file mode 100644
index 0000000000..68ae2e37a5
--- /dev/null
+++ b/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);
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs b/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
new file mode 100644
index 0000000000..fc7c174ed6
--- /dev/null
+++ b/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(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray2)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray4)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray8)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray16)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Gray32Float)
+ Transcode(dst, src, size, strideSrc, strideDst);
+ else if (format == PixelFormats.Rgba64)
+ Transcode(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(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++;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
index 1aac8efac7..a3dd88b075 100644
--- a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
@@ -9,7 +9,9 @@ namespace Avalonia.Media.Imaging
///
public class WriteableBitmap : Bitmap
{
-
+ // Holds a buffer with pixel format that requires transcoding
+ private BitmapMemory? _pixelFormatMemory = null;
+
///
/// Initializes a new instance of the class.
///
@@ -19,16 +21,42 @@ namespace Avalonia.Media.Imaging
/// The alpha format (optional).
/// An .
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)
{
@@ -67,14 +95,25 @@ namespace Avalonia.Media.Imaging
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();
PixelFormat finalFormat = format ?? ri.DefaultPixelFormat;
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()
diff --git a/src/Avalonia.Base/PixelRect.cs b/src/Avalonia.Base/PixelRect.cs
index 469f33e7fd..ef207a3dae 100644
--- a/src/Avalonia.Base/PixelRect.cs
+++ b/src/Avalonia.Base/PixelRect.cs
@@ -351,7 +351,7 @@ namespace Avalonia
/// The new .
public PixelRect WithHeight(int height)
{
- return new PixelRect(X, Y, Width, Height);
+ return new PixelRect(X, Y, Width, height);
}
///
diff --git a/src/Avalonia.Base/Platform/IBitmapWithPixelReadAccessImpl.cs b/src/Avalonia.Base/Platform/IBitmapWithPixelReadAccessImpl.cs
new file mode 100644
index 0000000000..acf1801e0a
--- /dev/null
+++ b/src/Avalonia.Base/Platform/IBitmapWithPixelReadAccessImpl.cs
@@ -0,0 +1,7 @@
+namespace Avalonia.Platform;
+
+public interface IReadableBitmapImpl
+{
+ PixelFormat? Format { get; }
+ ILockedFramebuffer Lock();
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
index 41e792d58e..cfc7fac3ea 100644
--- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
@@ -197,6 +197,8 @@ namespace Avalonia.Platform
/// Default used on this platform.
///
public PixelFormat DefaultPixelFormat { get; }
+
+ bool IsSupportedBitmapPixelFormat(PixelFormat format);
}
[Unstable]
diff --git a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
index fa1e1862b7..3284d34a0a 100644
--- a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
+++ b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
@@ -6,8 +6,7 @@ namespace Avalonia.Platform
/// Defines the platform-specific interface for a .
///
[Unstable]
- public interface IWriteableBitmapImpl : IBitmapImpl
+ public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapImpl
{
- ILockedFramebuffer Lock();
}
}
diff --git a/src/Avalonia.Base/Platform/PixelFormat.cs b/src/Avalonia.Base/Platform/PixelFormat.cs
index 526303ebb1..9c57abe343 100644
--- a/src/Avalonia.Base/Platform/PixelFormat.cs
+++ b/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,
Rgba8888,
- Bgra8888
+ Bgra8888,
+ BlackWhite,
+ Gray2,
+ Gray4,
+ Gray8,
+ Gray16,
+ Gray32Float,
+ Rgba64
+ }
+
+ public struct PixelFormat : IEquatable
+ {
+ 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);
}
}
diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index 27578ddc78..5cefb0d89f 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -2,6 +2,7 @@
using System.Runtime.InteropServices;
using Avalonia.Media;
using Avalonia.Media.Imaging;
+using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading;
@@ -72,7 +73,7 @@ namespace Avalonia.Controls.Remote
{
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 ||
_bitmap.PixelSize.Height != _lastFrame.Height)
{
diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
index bc11c35fde..525f695fc0 100644
--- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
@@ -282,7 +282,7 @@ namespace Avalonia.Controls.Remote.Server
{
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);
Paint?.Invoke(new Rect(0, 0, width, height));
}
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index 514d3b3e07..225e846390 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -30,6 +30,7 @@ namespace Avalonia.Headless
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new HeadlessGeometryStub(rect);
@@ -353,6 +354,8 @@ namespace Avalonia.Headless
}
+ public PixelFormat? Format { get; }
+
public ILockedFramebuffer Lock()
{
Version++;
diff --git a/src/Avalonia.Native/DeferredFramebuffer.cs b/src/Avalonia.Native/DeferredFramebuffer.cs
index 4e0c037154..c390459286 100644
--- a/src/Avalonia.Native/DeferredFramebuffer.cs
+++ b/src/Avalonia.Native/DeferredFramebuffer.cs
@@ -63,7 +63,7 @@ namespace Avalonia.Native
},
Width = Size.Width,
Height = Size.Height,
- PixelFormat = (AvnPixelFormat)Format,
+ PixelFormat = (AvnPixelFormat)Format.FormatEnum,
Stride = RowBytes
};
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
index f3f9a12ac8..56c6bdb8c4 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
@@ -93,9 +93,8 @@ namespace Avalonia.LinuxFramebuffer
void SetBpp(PixelFormat format)
{
- switch (format)
+ if (format == PixelFormat.Rgba8888)
{
- case PixelFormat.Rgba8888:
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
@@ -105,8 +104,9 @@ namespace Avalonia.LinuxFramebuffer
_varInfo.green.offset = 8;
_varInfo.blue.offset = 16;
_varInfo.transp.offset = 24;
- break;
- case PixelFormat.Bgra8888:
+ }
+ else if (format == PixelFormat.Bgra8888)
+ {
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
@@ -116,8 +116,9 @@ namespace Avalonia.LinuxFramebuffer
_varInfo.green.offset = 8;
_varInfo.red.offset = 16;
_varInfo.transp.offset = 24;
- break;
- case PixelFormat.Rgb565:
+ }
+ else if (format == PixelFormat.Rgb565)
+ {
_varInfo.bits_per_pixel = 16;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield();
@@ -126,8 +127,8 @@ namespace Avalonia.LinuxFramebuffer
_varInfo.green.length = 6;
_varInfo.blue.offset = 11;
_varInfo.blue.length = 5;
- break;
}
+ else throw new NotSupportedException($"Pixel format {format} is not supported");
}
public string Id { get; private set; }
diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
index 802736119f..4ab873fd8d 100644
--- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
+++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
@@ -10,9 +10,10 @@ namespace Avalonia.Skia
///
/// Immutable Skia bitmap.
///
- internal class ImmutableBitmap : IDrawableBitmapImpl
+ internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl
{
private readonly SKImage _image;
+ private readonly SKBitmap? _bitmap;
///
/// Create immutable bitmap from given stream.
@@ -23,12 +24,13 @@ namespace Avalonia.Skia
using (var skiaStream = new SKManagedStream(stream))
{
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");
- }
+
+ _bitmap.SetImmutable();
+ _image = SKImage.FromBitmap(_bitmap);
PixelSize = new PixelSize(_image.Width, _image.Height);
@@ -47,10 +49,10 @@ namespace Avalonia.Skia
public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode)
{
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);
@@ -71,8 +73,11 @@ namespace Avalonia.Skia
// decode the bitmap at the nearest size
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
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);
}
- 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)
{
@@ -121,9 +127,15 @@ namespace Avalonia.Skia
/// Data pixels.
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)
{
@@ -143,6 +155,7 @@ namespace Avalonia.Skia
public void Dispose()
{
_image.Dispose();
+ _bitmap?.Dispose();
}
///
@@ -162,5 +175,14 @@ namespace Avalonia.Skia
{
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);
+ }
}
}
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index b4297a7c33..d12db39ad6 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -41,6 +41,11 @@ namespace Avalonia.Skia
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 CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
index d584216f17..20dde27e9a 100644
--- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
+++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
@@ -127,6 +127,17 @@ namespace Avalonia.Skia
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)
{
if (fmt == SKColorType.Rgb565)
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index 9864a14a9c..56e627f2d8 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -154,6 +154,8 @@ namespace Avalonia.Skia
}
}
+ public PixelFormat? Format => _bitmap.ColorType.ToAvalonia();
+
///
public ILockedFramebuffer Lock() => new BitmapFramebuffer(this, _bitmap);
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index fbf8097ece..eb3f9911df 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -339,5 +339,8 @@ namespace Avalonia.Direct2D1
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Bgra8888;
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) =>
+ format == PixelFormats.Bgra8888
+ || format == PixelFormats.Rgba8888;
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
index 051790ef03..72a48aca0c 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
@@ -1,11 +1,14 @@
using System;
using System.IO;
+using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Win32.Interop;
using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
using AlphaFormat = Avalonia.Platform.AlphaFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using Avalonia.Metadata;
+using Avalonia.Platform;
+using PixelFormat = SharpDX.WIC.PixelFormat;
namespace Avalonia.Direct2D1.Media
{
@@ -13,7 +16,7 @@ namespace Avalonia.Direct2D1.Media
/// A WIC implementation of a .
///
[Unstable]
- public class WicBitmapImpl : BitmapImpl
+ public class WicBitmapImpl : BitmapImpl, IReadableBitmapImpl
{
private readonly BitmapDecoder _decoder;
@@ -197,5 +200,38 @@ namespace Avalonia.Direct2D1.Media
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);
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
index 2e40bdd9d1..5f4c033cf7 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
+++ b/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;
}
}
diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs
index 7f4b1c976d..8feecab4dd 100644
--- a/src/Windows/Avalonia.Win32/FramebufferManager.cs
+++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Win32
internal class FramebufferManager : IFramebufferPlatformSurface, IDisposable
{
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 object _lock;
@@ -50,7 +50,7 @@ namespace Avalonia.Win32
return fb = new LockedFramebuffer(
framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes,
- GetCurrentDpi(), _format, _onDisposeAction);
+ GetCurrentDpi(), s_format, _onDisposeAction);
}
finally
{
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
index 76c7fe97fc..481b98a0b2 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
@@ -91,6 +91,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat { get; }
public PixelFormat DefaultPixelFormat { get; }
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
public IFontManagerImpl CreateFontManager()
{
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 37b79855db..e5cbae4ae7 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -139,6 +139,8 @@ namespace Avalonia.Benchmarks
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
+
public void Dispose()
{
diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
index 2d83f5ce0f..5b85f98b70 100644
--- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs
+++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
@@ -9,6 +10,8 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Xunit;
+using Path = System.IO.Path;
+#pragma warning disable CS0649
#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
@@ -60,13 +63,14 @@ namespace Avalonia.Direct2D1.RenderTests.Media
[Theory]
- [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888),
+ [InlineData(PixelFormatEnum.Rgba8888), InlineData(PixelFormatEnum.Bgra8888),
#if AVALONIA_SKIA
- InlineData(PixelFormat.Rgb565)
+ InlineData(PixelFormatEnum.Rgb565)
#endif
]
- public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt)
+ internal void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormatEnum fmte)
{
+ var fmt = new PixelFormat(fmte);
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
var fb = new Framebuffer(fmt, new PixelSize(80, 80));
var r = Avalonia.AvaloniaLocator.Current.GetRequiredService();
@@ -100,9 +104,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media
}
[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 data = new int[256 * 256];
@@ -126,5 +131,110 @@ namespace Avalonia.Direct2D1.RenderTests.Media
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(bitsData.Slice(0, Unsafe.SizeOf()))[0];
+ var data = bitsData.Slice(Unsafe.SizeOf());
+
+ 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);
+ }
}
}
diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs
index edde62f041..adcaecb054 100644
--- a/tests/Avalonia.RenderTests/TestBase.cs
+++ b/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");
using (var expected = Image.Load(expectedPath))
diff --git a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
index 5e481f21c1..ba45bbbc2e 100644
--- a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
+++ b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
@@ -2,6 +2,7 @@
net6.0
AVALONIA_SKIA;AVALONIA_SKIA_SKIP_FAIL
+ true
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index 93073faefb..fe84659038 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -182,6 +182,8 @@ namespace Avalonia.UnitTests
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
+ public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
+
public void Dispose()
{
}
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits
new file mode 100644
index 0000000000..7b74651405
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr24.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr24.bits
new file mode 100644
index 0000000000..3c6b96c687
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr24.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr32.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr32.bits
new file mode 100644
index 0000000000..db58088c15
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr32.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr555.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr555.bits
new file mode 100644
index 0000000000..144529b050
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr555.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png b/tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png
new file mode 100644
index 0000000000..e580fe77ee
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr565.bits b/tests/TestFiles/PixelFormats/Lenna/Bgr565.bits
new file mode 100644
index 0000000000..ba29ff03b8
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr565.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png b/tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png
new file mode 100644
index 0000000000..c5fdc072cb
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Bgra32.bits b/tests/TestFiles/PixelFormats/Lenna/Bgra32.bits
new file mode 100644
index 0000000000..db58088c15
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Bgra32.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits b/tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits
new file mode 100644
index 0000000000..583dc86cfe
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png b/tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png
new file mode 100644
index 0000000000..0a352d448c
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits b/tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits
new file mode 100644
index 0000000000..a10913bb6b
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Default.expected.png b/tests/TestFiles/PixelFormats/Lenna/Default.expected.png
new file mode 100644
index 0000000000..df9a62ee1d
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Default.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray16.bits b/tests/TestFiles/PixelFormats/Lenna/Gray16.bits
new file mode 100644
index 0000000000..6cfab5c376
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray16.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png
new file mode 100644
index 0000000000..50048304da
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray2.bits b/tests/TestFiles/PixelFormats/Lenna/Gray2.bits
new file mode 100644
index 0000000000..adb818a1df
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray2.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png
new file mode 100644
index 0000000000..b7f3d892f4
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits b/tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits
new file mode 100644
index 0000000000..34ba7a94b5
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png
new file mode 100644
index 0000000000..e997d30d79
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray4.bits b/tests/TestFiles/PixelFormats/Lenna/Gray4.bits
new file mode 100644
index 0000000000..b24466315b
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray4.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png
new file mode 100644
index 0000000000..d8d4fb0afa
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray8.bits b/tests/TestFiles/PixelFormats/Lenna/Gray8.bits
new file mode 100644
index 0000000000..46708a66fa
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray8.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png b/tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png
new file mode 100644
index 0000000000..ab406499bb
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits b/tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits
new file mode 100644
index 0000000000..db58088c15
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits b/tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits
new file mode 100644
index 0000000000..1a000186f1
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Prgba64.bits b/tests/TestFiles/PixelFormats/Lenna/Prgba64.bits
new file mode 100644
index 0000000000..1012f290fc
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Prgba64.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits b/tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits
new file mode 100644
index 0000000000..1a000186f1
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgb24.bits b/tests/TestFiles/PixelFormats/Lenna/Rgb24.bits
new file mode 100644
index 0000000000..6ade0dbad2
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgb24.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgb48.bits b/tests/TestFiles/PixelFormats/Lenna/Rgb48.bits
new file mode 100644
index 0000000000..91843b0891
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgb48.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits b/tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits
new file mode 100644
index 0000000000..1a000186f1
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits differ
diff --git a/tests/TestFiles/PixelFormats/Lenna/Rgba64.bits b/tests/TestFiles/PixelFormats/Lenna/Rgba64.bits
new file mode 100644
index 0000000000..1012f290fc
Binary files /dev/null and b/tests/TestFiles/PixelFormats/Lenna/Rgba64.bits differ