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