Implemented pixel format transcoding for Bitmap and WriteableBitmap
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,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 +21,42 @@ 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;
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Avalonia.Platform;
|
||||
|
||||
public interface IReadableBitmapImpl
|
||||
{
|
||||
PixelFormat? Format { get; }
|
||||
ILockedFramebuffer Lock();
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PixelFormat>
|
||||
{
|
||||
internal PixelFormatEnum FormatEnum;
|
||||
|
||||
public int BitsPerPixel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (FormatEnum == PixelFormatEnum.BlackWhite)
|
||||
return 1;
|
||||
else if (FormatEnum == PixelFormatEnum.Gray2)
|
||||
return 2;
|
||||
else if (FormatEnum == PixelFormatEnum.Gray4)
|
||||
return 4;
|
||||
else if (FormatEnum == PixelFormatEnum.Gray8)
|
||||
return 8;
|
||||
else if (FormatEnum == PixelFormatEnum.Rgb565
|
||||
|| FormatEnum == PixelFormatEnum.Gray16)
|
||||
return 16;
|
||||
else if (FormatEnum == PixelFormatEnum.Rgba64)
|
||||
return 64;
|
||||
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasAlpha => FormatEnum == PixelFormatEnum.Rgba8888
|
||||
|| FormatEnum == PixelFormatEnum.Bgra8888
|
||||
|| FormatEnum == PixelFormatEnum.Rgba64;
|
||||
|
||||
internal PixelFormat(PixelFormatEnum format)
|
||||
{
|
||||
FormatEnum = format;
|
||||
}
|
||||
|
||||
public static PixelFormat Rgb565 => PixelFormats.Rgb565;
|
||||
public static PixelFormat Rgba8888 => PixelFormats.Rgba8888;
|
||||
public static PixelFormat Bgra8888 => PixelFormats.Bgra8888;
|
||||
|
||||
public bool Equals(PixelFormat other)
|
||||
{
|
||||
return FormatEnum == other.FormatEnum;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is PixelFormat other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (int)FormatEnum;
|
||||
}
|
||||
|
||||
public static bool operator ==(PixelFormat left, PixelFormat right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(PixelFormat left, PixelFormat right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public override string ToString() => FormatEnum.ToString();
|
||||
}
|
||||
|
||||
public static class PixelFormats
|
||||
{
|
||||
public static PixelFormat Rgb565 { get; } = new PixelFormat(PixelFormatEnum.Rgb565);
|
||||
public static PixelFormat Rgba8888 { get; } = new PixelFormat(PixelFormatEnum.Rgba8888);
|
||||
public static PixelFormat Rgba64 { get; } = new PixelFormat(PixelFormatEnum.Rgba64);
|
||||
public static PixelFormat Bgra8888 { get; } = new PixelFormat(PixelFormatEnum.Bgra8888);
|
||||
public static PixelFormat BlackWhite { get; } = new PixelFormat(PixelFormatEnum.BlackWhite);
|
||||
public static PixelFormat Gray2 { get; } = new PixelFormat(PixelFormatEnum.Gray2);
|
||||
public static PixelFormat Gray4 { get; } = new PixelFormat(PixelFormatEnum.Gray4);
|
||||
public static PixelFormat Gray8 { get; } = new PixelFormat(PixelFormatEnum.Gray8);
|
||||
public static PixelFormat Gray16 { get; } = new PixelFormat(PixelFormatEnum.Gray16);
|
||||
public static PixelFormat Gray32Float { get; } = new PixelFormat(PixelFormatEnum.Gray32Float);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Avalonia.Native
|
||||
},
|
||||
Width = Size.Width,
|
||||
Height = Size.Height,
|
||||
PixelFormat = (AvnPixelFormat)Format,
|
||||
PixelFormat = (AvnPixelFormat)Format.FormatEnum,
|
||||
Stride = RowBytes
|
||||
};
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -154,6 +154,8 @@ namespace Avalonia.Skia
|
||||
}
|
||||
}
|
||||
|
||||
public PixelFormat? Format => _bitmap.ColorType.ToAvalonia();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILockedFramebuffer Lock() => new BitmapFramebuffer(this, _bitmap);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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,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,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<byte, RawHeader>(bitsData.Slice(0, Unsafe.SizeOf<RawHeader>()))[0];
|
||||
var data = bitsData.Slice(Unsafe.SizeOf<RawHeader>());
|
||||
|
||||
var size = new PixelSize(header.Width, header.Height);
|
||||
var stride = header.Stride;
|
||||
|
||||
string expectedName = Path.Combine(relativeFilesDir, formatName);
|
||||
if (!File.Exists(Path.Combine(OutputPath, expectedName + ".expected.png")))
|
||||
expectedName = Path.Combine(relativeFilesDir, "Default");
|
||||
|
||||
foreach (var writable in new[] { false, true })
|
||||
{
|
||||
var testName = nameof(BitmapsShouldSupportTranscoders_Lenna) + "_" + formatName +
|
||||
(writable ? "_Writeable" : "_Normal");
|
||||
|
||||
var path = System.IO.Path.Combine(OutputPath, testName + ".out.png");
|
||||
fixed (byte* pData = data)
|
||||
{
|
||||
Bitmap? b = null;
|
||||
try
|
||||
{
|
||||
if (writable)
|
||||
{
|
||||
var bmp = new WriteableBitmap(size, new Vector(96, 96), new PixelFormat(format),
|
||||
alphaFormat);
|
||||
|
||||
using (var l = bmp.Lock())
|
||||
{
|
||||
var minStride = (l.Size.Width * l.Format.BitsPerPixel + 7) / 8;
|
||||
for (var y = 0; y < size.Height; y++)
|
||||
{
|
||||
Unsafe.CopyBlock((l.Address + y * l.RowBytes).ToPointer(), pData + y * stride,
|
||||
(uint)minStride);
|
||||
}
|
||||
}
|
||||
|
||||
b = bmp;
|
||||
var copyTo = new byte[data.Length];
|
||||
fixed (byte* pCopyTo = copyTo)
|
||||
b.CopyPixels(default, new IntPtr(pCopyTo), copyTo.Length, stride);
|
||||
Assert.Equal(data.ToArray(), copyTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
b = new Bitmap(new PixelFormat(format), alphaFormat, new IntPtr(pData),
|
||||
size, new Vector(96, 96), stride);
|
||||
}
|
||||
|
||||
b.Save(path);
|
||||
CompareImagesNoRenderer(testName, expectedName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
b?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public unsafe void CopyPixelsShouldWorkForNonTranscodedBitmaps()
|
||||
{
|
||||
var stride = 32 * 4;
|
||||
var data = new byte[32 * stride];
|
||||
new Random().NextBytes(data);
|
||||
for (var c = 0; c < data.Length; c++)
|
||||
if (data[c] == 0)
|
||||
data[c] = 1;
|
||||
|
||||
Bitmap bmp;
|
||||
fixed (byte* pData = data)
|
||||
bmp = new Bitmap(PixelFormat.Bgra8888, AlphaFormat.Unpremul, new IntPtr(pData), new PixelSize(32, 32),
|
||||
new Vector(96, 96), 32 * 4);
|
||||
|
||||
var copyTo = new byte[data.Length];
|
||||
fixed (byte* pCopyTo = copyTo)
|
||||
bmp.CopyPixels(default, new IntPtr(pCopyTo), data.Length, stride);
|
||||
Assert.Equal(data, copyTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Rgba32>(expectedPath))
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 25 KiB |