Browse Source

Merge branch 'master' into fixes/remove-legacy-renderers

pull/10078/head
Nikita Tsukanov 3 years ago
committed by GitHub
parent
commit
2293372413
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 77
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  2. 51
      src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
  3. 280
      src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
  4. 77
      src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
  5. 2
      src/Avalonia.Base/PixelRect.cs
  6. 2
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  7. 7
      src/Avalonia.Base/Platform/IReadableBitmapImpl.cs
  8. 3
      src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
  9. 71
      src/Avalonia.Base/Platform/PixelFormat.cs
  10. 3
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  11. 4
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  12. 3
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  13. 2
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  14. 3
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  15. 2
      src/Avalonia.Native/DeferredFramebuffer.cs
  16. 15
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  17. 62
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  18. 5
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  19. 11
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  20. 2
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  21. 3
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  22. 38
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  23. 31
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
  24. 4
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  25. 1
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  26. 2
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  27. 1
      tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj
  28. 132
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  29. 4
      tests/Avalonia.RenderTests/TestBase.cs
  30. 1
      tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
  31. 2
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  32. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr101010.bits
  33. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr24.bits
  34. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr32.bits
  35. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr555.bits
  36. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr555.expected.png
  37. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr565.bits
  38. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgr565.expected.png
  39. BIN
      tests/TestFiles/PixelFormats/Lenna/Bgra32.bits
  40. BIN
      tests/TestFiles/PixelFormats/Lenna/BlackWhite.bits
  41. BIN
      tests/TestFiles/PixelFormats/Lenna/BlackWhite.expected.png
  42. BIN
      tests/TestFiles/PixelFormats/Lenna/Cmyk32.bits
  43. BIN
      tests/TestFiles/PixelFormats/Lenna/Default.expected.png
  44. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray16.bits
  45. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray16.expected.png
  46. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray2.bits
  47. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray2.expected.png
  48. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray32Float.bits
  49. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray32Float.expected.png
  50. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray4.bits
  51. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray4.expected.png
  52. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray8.bits
  53. BIN
      tests/TestFiles/PixelFormats/Lenna/Gray8.expected.png
  54. BIN
      tests/TestFiles/PixelFormats/Lenna/Pbgra32.bits
  55. BIN
      tests/TestFiles/PixelFormats/Lenna/Prgba128Float.bits
  56. BIN
      tests/TestFiles/PixelFormats/Lenna/Prgba64.bits
  57. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgb128Float.bits
  58. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgb24.bits
  59. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgb48.bits
  60. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgba128Float.bits
  61. BIN
      tests/TestFiles/PixelFormats/Lenna/Rgba64.bits

77
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
/// </summary>
public class Bitmap : IBitmap
{
private bool _isTranscoded;
/// <summary>
/// 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
/// <param name="stride">The number of bytes per row.</param>
public Bitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
PlatformImpl = RefCountable.Create(GetFactory().LoadBitmap(format, alphaFormat, data, size, dpi, stride));
var factory = GetFactory();
if (factory.IsSupportedBitmapPixelFormat(format))
PlatformImpl = RefCountable.Create(factory.LoadBitmap(format, alphaFormat, data, size, dpi, stride));
else
{
var transcoded = Marshal.AllocHGlobal(size.Width * size.Height * 4);
var transcodedStride = size.Width * 4;
try
{
PixelFormatReader.Transcode(transcoded, data, size, stride, transcodedStride, format);
var transcodedAlphaFormat = format.HasAlpha ? alphaFormat : AlphaFormat.Opaque;
PlatformImpl = RefCountable.Create(factory.LoadBitmap(PixelFormat.Rgba8888, transcodedAlphaFormat,
transcoded, size, dpi, transcodedStride));
}
finally
{
Marshal.FreeHGlobal(transcoded);
}
_isTranscoded = true;
}
}
/// <inheritdoc/>
@ -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);
}
/// <inheritdoc/>
void IImage.Draw(
DrawingContext context,

51
src/Avalonia.Base/Media/Imaging/BitmapMemory.cs

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

280
src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs

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

77
src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Avalonia.Platform;
namespace Avalonia.Media.Imaging
@ -9,7 +10,9 @@ namespace Avalonia.Media.Imaging
/// </summary>
public class WriteableBitmap : Bitmap
{
// Holds a buffer with pixel format that requires transcoding
private BitmapMemory? _pixelFormatMemory = null;
/// <summary>
/// Initializes a new instance of the <see cref="WriteableBitmap"/> class.
/// </summary>
@ -19,16 +22,67 @@ namespace Avalonia.Media.Imaging
/// <param name="alphaFormat">The alpha format (optional).</param>
/// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="WriteableBitmap"/> class with existing pixel data
/// The data is copied to the bitmap
/// </summary>
/// <param name="format">The pixel format.</param>
/// <param name="alphaFormat">The alpha format.</param>
/// <param name="data">The pointer to the source bytes.</param>
/// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="stride">The number of bytes per row.</param>
public unsafe WriteableBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
: this(size, dpi, format, alphaFormat)
{
var minStride = (format.BitsPerPixel * size.Width + 7) / 8;
if (minStride > stride)
throw new ArgumentOutOfRangeException(nameof(stride));
public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock();
using (var locked = Lock())
{
for (var y = 0; y < size.Height; y++)
Unsafe.CopyBlock((locked.Address + locked.RowBytes * y).ToPointer(),
(data + y * stride).ToPointer(), (uint)minStride);
}
}
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 +121,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()

2
src/Avalonia.Base/PixelRect.cs

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

2
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

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

7
src/Avalonia.Base/Platform/IReadableBitmapImpl.cs

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

3
src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs

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

71
src/Avalonia.Base/Platform/PixelFormat.cs

@ -1,9 +1,74 @@
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 record struct PixelFormat
{
internal PixelFormatEnum FormatEnum;
public int BitsPerPixel
{
get
{
if (FormatEnum == PixelFormatEnum.BlackWhite)
return 1;
else if (FormatEnum == PixelFormatEnum.Gray2)
return 2;
else if (FormatEnum == PixelFormatEnum.Gray4)
return 4;
else if (FormatEnum == PixelFormatEnum.Gray8)
return 8;
else if (FormatEnum == PixelFormatEnum.Rgb565
|| FormatEnum == PixelFormatEnum.Gray16)
return 16;
else if (FormatEnum == PixelFormatEnum.Rgba64)
return 64;
return 32;
}
}
internal bool HasAlpha => FormatEnum == PixelFormatEnum.Rgba8888
|| FormatEnum == PixelFormatEnum.Bgra8888
|| FormatEnum == PixelFormatEnum.Rgba64;
internal PixelFormat(PixelFormatEnum format)
{
FormatEnum = format;
}
public static PixelFormat Rgb565 => PixelFormats.Rgb565;
public static PixelFormat Rgba8888 => PixelFormats.Rgba8888;
public static PixelFormat Bgra8888 => PixelFormats.Bgra8888;
public 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);
}
}

3
src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs

@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Threading;
namespace Avalonia.Rendering.Composition;
@ -55,6 +56,6 @@ public class CompositionDrawingSurface : CompositionSurface
~CompositionDrawingSurface()
{
Dispose();
Dispatcher.UIThread.Post(Dispose);
}
}

4
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -613,7 +613,7 @@ namespace Avalonia.Controls.Presenters
{
height = _verticalSnapPoint;
}
else if(_verticalSnapPoints != null)
else if(_verticalSnapPoints != null && _verticalSnapPoints.Count > 0)
{
double yOffset = Offset.Y;
switch (VerticalSnapPointsAlignment)
@ -645,7 +645,7 @@ namespace Avalonia.Controls.Presenters
{
width = _horizontalSnapPoint;
}
else if(_horizontalSnapPoints != null)
else if(_horizontalSnapPoints != null && _horizontalSnapPoints.Count > 0)
{
double xOffset = Offset.X;
switch (VerticalSnapPointsAlignment)

3
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)
{

2
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -292,7 +292,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));
}

3
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++;

2
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
};

15
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; }

62
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -10,9 +10,10 @@ namespace Avalonia.Skia
/// <summary>
/// Immutable Skia bitmap.
/// </summary>
internal class ImmutableBitmap : IDrawableBitmapImpl
internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl
{
private readonly SKImage _image;
private readonly SKBitmap? _bitmap;
/// <summary>
/// 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
/// <param name="data">Data pixels.</param>
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();
}
/// <inheritdoc />
@ -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);
}
}
}

5
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);

11
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)

2
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

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

3
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;
}
}

38
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 <see cref="Avalonia.Media.Imaging.Bitmap"/>.
/// </summary>
[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);
}
}

31
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;
}
}

4
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
{

1
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()
{

2
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()
{

1
tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.RenderTests\**\*.cs" />

132
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<IPlatformRenderInterface>();
@ -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,122 @@ 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<byte, RawHeader>(bitsData.Slice(0, Unsafe.SizeOf<RawHeader>()))[0];
var data = bitsData.Slice(Unsafe.SizeOf<RawHeader>());
var size = new PixelSize(header.Width, header.Height);
var stride = header.Stride;
string expectedName = Path.Combine(relativeFilesDir, formatName);
if (!File.Exists(Path.Combine(OutputPath, expectedName + ".expected.png")))
expectedName = Path.Combine(relativeFilesDir, "Default");
var names = new[]
{
"_Writeable",
"_WriteableInitialized",
"_Normal"
};
foreach (var step in new[] { 0,1,2 })
{
var testName = nameof(BitmapsShouldSupportTranscoders_Lenna) + "_" + formatName + names[step];
var path = System.IO.Path.Combine(OutputPath, testName + ".out.png");
fixed (byte* pData = data)
{
Bitmap? b = null;
try
{
if (step == 0)
{
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;
}
else if (step == 1)
b = new WriteableBitmap(new PixelFormat(format), alphaFormat, new IntPtr(pData),
size, new Vector(96, 96), stride);
else
b = new Bitmap(new PixelFormat(format), alphaFormat, new IntPtr(pData),
size, new Vector(96, 96), stride);
if (step < 2)
{
var copyTo = new byte[data.Length];
fixed (byte* pCopyTo = copyTo)
b.CopyPixels(default, new IntPtr(pCopyTo), copyTo.Length, stride);
Assert.Equal(data.ToArray(), copyTo);
}
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);
}
}
}

4
tests/Avalonia.RenderTests/TestBase.cs

@ -165,9 +165,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<Rgba32>(expectedPath))

1
tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj

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

2
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()
{
}

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

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

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.
Loading…
Cancel
Save