diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
index fbe9370edcf..215586eef90 100644
--- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
@@ -1,7 +1,7 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Utilities;
@@ -108,19 +108,23 @@ public Bitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSiz
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
+ using (var transcoded = new BitmapMemory(PixelFormat.Rgba8888, Platform.AlphaFormat.Unpremul, size))
{
- PixelFormatReader.Transcode(transcoded, data, size, stride, transcodedStride, format);
- var transcodedAlphaFormat = format.HasAlpha ? alphaFormat : AlphaFormat.Opaque;
-
+ var transcodedAlphaFormat = format.HasAlpha ? alphaFormat : Platform.AlphaFormat.Opaque;
+
+ PixelFormatTranscoder.Transcode(
+ data,
+ size,
+ stride,
+ format,
+ alphaFormat,
+ transcoded.Address,
+ transcoded.RowBytes,
+ transcoded.Format,
+ transcodedAlphaFormat);
+
PlatformImpl = RefCountable.Create(factory.LoadBitmap(PixelFormat.Rgba8888, transcodedAlphaFormat,
- transcoded, size, dpi, transcodedStride));
- }
- finally
- {
- Marshal.FreeHGlobal(transcoded);
+ transcoded.Address, size, dpi, transcoded.RowBytes));
}
_isTranscoded = true;
@@ -173,6 +177,8 @@ public void Save(Stream stream, int? quality = null)
public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format;
+ public virtual AlphaFormat? AlphaFormat => (PlatformImpl.Item as IReadableBitmapWithAlphaImpl)?.AlphaFormat;
+
private protected unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride,
ILockedFramebuffer fb)
{
@@ -222,6 +228,44 @@ public virtual void CopyPixels(PixelRect sourceRect, IntPtr buffer, int bufferSi
CopyPixelsCore(sourceRect, buffer, bufferSize, stride, fb);
}
+ ///
+ /// Copies pixels to the target buffer and transcodes the pixel and alpha format if needed.
+ ///
+ /// The target buffer.
+ /// The alpha format.
+ ///
+ public void CopyPixels(ILockedFramebuffer buffer, AlphaFormat alphaFormat)
+ {
+ if (PlatformImpl.Item is not IReadableBitmapWithAlphaImpl readable || readable.Format == null || readable.AlphaFormat == null)
+ {
+ throw new NotSupportedException("CopyPixels is not supported for this bitmap type");
+ }
+
+ if (readable.Format != Format || readable.AlphaFormat != alphaFormat)
+ {
+ using (var fb = readable.Lock())
+ {
+ PixelFormatTranscoder.Transcode(
+ fb.Address,
+ fb.Size,
+ fb.RowBytes,
+ fb.Format,
+ readable.AlphaFormat.Value,
+ buffer.Address,
+ buffer.RowBytes,
+ buffer.Format,
+ alphaFormat);
+ }
+ }
+ else
+ {
+ using (var fb = readable.Lock())
+ {
+ CopyPixelsCore(new PixelRect(fb.Size), buffer.Address, buffer.RowBytes * buffer.Size.Height, fb.RowBytes, fb);
+ }
+ }
+ }
+
///
void IImage.Draw(
DrawingContext context,
diff --git a/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs b/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
index 68ae2e37a5a..1032081b6e8 100644
--- a/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
+++ b/src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
@@ -9,9 +9,10 @@ internal class BitmapMemory : IDisposable
{
private readonly int _memorySize;
- public BitmapMemory(PixelFormat format, PixelSize size)
+ public BitmapMemory(PixelFormat format, AlphaFormat alphaFormat, PixelSize size)
{
Format = format;
+ AlphaFormat = alphaFormat;
Size = size;
RowBytes = (size.Width * format.BitsPerPixel + 7) / 8;
_memorySize = RowBytes * size.Height;
@@ -44,8 +45,19 @@ public void Dispose()
public int RowBytes { get; }
public PixelFormat Format { get; }
+ public AlphaFormat AlphaFormat { get; }
-
- public void CopyToRgba(IntPtr buffer, int rowBytes) =>
- PixelFormatReader.Transcode(buffer, Address, Size, RowBytes, rowBytes, Format);
-}
\ No newline at end of file
+ public void CopyToRgba(AlphaFormat alphaFormat, IntPtr buffer, int stride)
+ {
+ PixelFormatTranscoder.Transcode(
+ Address,
+ Size,
+ RowBytes,
+ Format,
+ AlphaFormat,
+ buffer,
+ stride,
+ PixelFormat.Rgba8888,
+ alphaFormat);
+ }
+}
diff --git a/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs b/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
index c90c4cb5ac4..df2614d8ffb 100644
--- a/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
+++ b/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
@@ -2,22 +2,46 @@
using Avalonia.Platform;
namespace Avalonia.Media.Imaging;
-internal struct Rgba8888Pixel
+internal record struct Rgba64Pixel
{
+ public Rgba64Pixel(ushort r, ushort g, ushort b, ushort a)
+ {
+ R = r;
+ G = g;
+ B = b;
+ A = a;
+ }
+
+ public ushort R;
+ public ushort G;
+ public ushort B;
+ public ushort A;
+}
+
+internal record struct Rgba8888Pixel
+{
+ public Rgba8888Pixel(byte r, byte g, byte b, byte a)
+ {
+ R = r;
+ G = g;
+ B = b;
+ A = a;
+ }
+
public byte R;
public byte G;
public byte B;
public byte A;
}
-static unsafe class PixelFormatReader
+internal interface IPixelFormatReader
+{
+ Rgba8888Pixel ReadNext();
+ void Reset(IntPtr address);
+}
+
+internal static unsafe class PixelFormatReader
{
- public interface IPixelFormatReader
- {
- Rgba8888Pixel ReadNext();
- void Reset(IntPtr address);
- }
-
private static readonly Rgba8888Pixel s_white = new Rgba8888Pixel
{
A = 255,
@@ -25,7 +49,7 @@ public interface IPixelFormatReader
G = 255,
R = 255
};
-
+
private static readonly Rgba8888Pixel s_black = new Rgba8888Pixel
{
A = 255,
@@ -34,7 +58,7 @@ public interface IPixelFormatReader
R = 0
};
- public unsafe struct BlackWhitePixelReader : IPixelFormatReader
+ public unsafe struct BlackWhitePixelFormatReader : IPixelFormatReader
{
private int _bit;
private byte* _address;
@@ -58,8 +82,8 @@ public Rgba8888Pixel ReadNext()
return value == 1 ? s_white : s_black;
}
}
-
- public unsafe struct Gray2PixelReader : IPixelFormatReader
+
+ public unsafe struct Gray2PixelFormatReader : IPixelFormatReader
{
private int _bit;
private byte* _address;
@@ -88,7 +112,7 @@ public Rgba8888Pixel ReadNext()
{
var shift = 6 - _bit;
var value = (byte)((*_address >> shift));
- value = (byte)((value & 3));
+ value = (byte)((value & 3));
_bit += 2;
if (_bit == 8)
{
@@ -99,8 +123,8 @@ public Rgba8888Pixel ReadNext()
return Palette[value];
}
}
-
- public unsafe struct Gray4PixelReader : IPixelFormatReader
+
+ public unsafe struct Gray4PixelFormatReader : IPixelFormatReader
{
private int _bit;
private byte* _address;
@@ -133,8 +157,8 @@ public Rgba8888Pixel ReadNext()
};
}
}
-
- public unsafe struct Gray8PixelReader : IPixelFormatReader
+
+ public unsafe struct Gray8PixelFormatReader : IPixelFormatReader
{
private byte* _address;
public void Reset(IntPtr address)
@@ -156,8 +180,8 @@ public Rgba8888Pixel ReadNext()
};
}
}
-
- public unsafe struct Gray16PixelReader : IPixelFormatReader
+
+ public unsafe struct Gray16PixelFormatReader : IPixelFormatReader
{
private ushort* _address;
public Rgba8888Pixel ReadNext()
@@ -177,7 +201,7 @@ public Rgba8888Pixel ReadNext()
public void Reset(IntPtr address) => _address = (ushort*)address;
}
- public unsafe struct Gray32FloatPixelReader : IPixelFormatReader
+ public unsafe struct Gray32FloatPixelFormatReader : IPixelFormatReader
{
private byte* _address;
public Rgba8888Pixel ReadNext()
@@ -199,19 +223,10 @@ public Rgba8888Pixel ReadNext()
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;
+ private Rgba64Pixel* _address;
public Rgba8888Pixel ReadNext()
{
var value = *_address;
@@ -226,9 +241,9 @@ public Rgba8888Pixel ReadNext()
};
}
- public void Reset(IntPtr address) => _address = (Rgba64*)address;
+ public void Reset(IntPtr address) => _address = (Rgba64Pixel*)address;
}
-
+
public unsafe struct Rgb24PixelFormatReader : IPixelFormatReader
{
private byte* _address;
@@ -247,7 +262,7 @@ public Rgba8888Pixel ReadNext()
public void Reset(IntPtr address) => _address = (byte*)address;
}
-
+
public unsafe struct Bgr24PixelFormatReader : IPixelFormatReader
{
private byte* _address;
@@ -267,58 +282,105 @@ public Rgba8888Pixel ReadNext()
public void Reset(IntPtr address) => _address = (byte*)address;
}
- public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst,
- PixelFormat format)
+ public unsafe struct Bgr555PixelFormatReader : IPixelFormatReader
{
- 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.Rgb24)
- Transcode(dst, src, size, strideSrc, strideDst);
- else if (format == PixelFormats.Bgr24)
- 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");
+ private byte* _address;
+ public Rgba8888Pixel ReadNext()
+ {
+ var addr = (ushort*)_address;
+
+ _address += 2;
+
+ return UnPack(*addr);
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+
+ private static Rgba8888Pixel UnPack(ushort value)
+ {
+ var r = (byte)Math.Round(((value >> 10) & 0x1F) / 31F * 255);
+ var g = (byte)Math.Round(((value >> 5) & 0x1F) / 31F * 255);
+ var b = (byte)Math.Round(((value >> 0) & 0x1F) / 31F * 255);
+
+ return new Rgba8888Pixel(r, g, b, 255);
+ }
}
-
- public static bool SupportsFormat(PixelFormat format)
+
+ public unsafe struct Bgr565PixelFormatReader : IPixelFormatReader
{
- return format == PixelFormats.BlackWhite
- || format == PixelFormats.Gray2
- || format == PixelFormats.Gray4
- || format == PixelFormats.Gray8
- || format == PixelFormats.Gray16
- || format == PixelFormats.Gray32Float
- || format == PixelFormats.Rgba64
- || format == PixelFormats.Bgr24
- || format == PixelFormats.Rgb24;
+ private byte* _address;
+ public Rgba8888Pixel ReadNext()
+ {
+ var addr = (ushort*)_address;
+
+ _address += 2;
+
+ return UnPack(*addr);
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+
+ private static Rgba8888Pixel UnPack(ushort value)
+ {
+ var r = (byte)Math.Round(((value >> 11) & 0x1F) / 31F * 255);
+ var g = (byte)Math.Round(((value >> 5) & 0x3F) / 63F * 255);
+ var b = (byte)Math.Round(((value >> 0) & 0x1F) / 31F * 255);
+
+ return new Rgba8888Pixel(r, g, b, 255);
+ }
}
-
- public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst) where TReader : struct, IPixelFormatReader
+
+ public unsafe struct Rgba8888PixelFormatReader : IPixelFormatReader
{
- var w = size.Width;
- var h = size.Height;
- TReader reader = default;
- for (var y = 0; y < h; y++)
+ private Rgba8888Pixel* _address;
+ public Rgba8888Pixel ReadNext()
{
- reader.Reset(src + strideSrc * y);
- var dstRow = (Rgba8888Pixel*)(dst + strideDst * y);
- for (var x = 0; x < w; x++)
- {
- *dstRow = reader.ReadNext();
- dstRow++;
- }
+ var value = *_address;
+
+ _address++;
+
+ return value;
+ }
+
+ public void Reset(IntPtr address) => _address = (Rgba8888Pixel*)address;
+ }
+
+ public unsafe struct Bgra8888PixelFormatReader : IPixelFormatReader
+ {
+ private byte* _address;
+ public Rgba8888Pixel ReadNext()
+ {
+ var addr = _address;
+
+ _address += 4;
+
+ return new Rgba8888Pixel(addr[2], addr[1], addr[0], addr[3]);
}
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
}
-}
\ No newline at end of file
+
+ public static bool SupportsFormat(PixelFormat format)
+ {
+ switch (format.FormatEnum)
+ {
+ case PixelFormatEnum.Rgb565:
+ case PixelFormatEnum.Rgba8888:
+ case PixelFormatEnum.Bgra8888:
+ case PixelFormatEnum.BlackWhite:
+ case PixelFormatEnum.Gray2:
+ case PixelFormatEnum.Gray4:
+ case PixelFormatEnum.Gray8:
+ case PixelFormatEnum.Gray16:
+ case PixelFormatEnum.Gray32Float:
+ case PixelFormatEnum.Rgba64:
+ case PixelFormatEnum.Rgb24:
+ case PixelFormatEnum.Bgr24:
+ case PixelFormatEnum.Bgr555:
+ case PixelFormatEnum.Bgr565:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs b/src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs
new file mode 100644
index 00000000000..b91c574fe06
--- /dev/null
+++ b/src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs
@@ -0,0 +1,154 @@
+using System;
+using Avalonia.Platform;
+namespace Avalonia.Media.Imaging;
+
+internal static unsafe class PixelFormatTranscoder
+{
+ public static void Transcode(
+ IntPtr source,
+ PixelSize srcSize,
+ int sourceStride,
+ PixelFormat srcFormat,
+ AlphaFormat srcAlphaFormat,
+ IntPtr dest,
+ int destStride,
+ PixelFormat destFormat,
+ AlphaFormat destAlphaFormat)
+ {
+ var reader = GetReader(srcFormat);
+ var writer = GetWriter(destFormat);
+
+ var w = srcSize.Width;
+ var h = srcSize.Height;
+
+ for (var y = 0; y < h; y++)
+ {
+ reader.Reset(source + sourceStride * y);
+
+ writer.Reset(dest + destStride * y);
+
+ for (var x = 0; x < w; x++)
+ {
+ writer.WriteNext(GetConvertedPixel(reader.ReadNext(), srcAlphaFormat, destAlphaFormat));
+ }
+ }
+ }
+
+ private static Rgba8888Pixel GetConvertedPixel(Rgba8888Pixel pixel, AlphaFormat sourceAlpha, AlphaFormat destAlpha)
+ {
+ if (sourceAlpha != destAlpha)
+ {
+ if (sourceAlpha == AlphaFormat.Premul && destAlpha != AlphaFormat.Premul)
+ {
+ return ConvertFromPremultiplied(pixel);
+ }
+
+ if (sourceAlpha != AlphaFormat.Premul && destAlpha == AlphaFormat.Premul)
+ {
+ return ConvertToPremultiplied(pixel);
+ }
+ }
+
+ return pixel;
+ }
+
+ private static Rgba8888Pixel ConvertToPremultiplied(Rgba8888Pixel pixel)
+ {
+ var factor = pixel.A / 255F;
+
+ return new Rgba8888Pixel
+ {
+ R = (byte)(pixel.R * factor),
+ G = (byte)(pixel.G * factor),
+ B = (byte)(pixel.B * factor),
+ A = pixel.A
+ };
+ }
+
+ private static Rgba8888Pixel ConvertFromPremultiplied(Rgba8888Pixel pixel)
+ {
+ var factor = 1F / (pixel.A / 255F);
+
+ return new Rgba8888Pixel
+ {
+ R = (byte)(pixel.R * factor),
+ G = (byte)(pixel.G * factor),
+ B = (byte)(pixel.B * factor),
+ A = pixel.A
+ };
+ }
+
+ private static IPixelFormatReader GetReader(PixelFormat format)
+ {
+ switch (format.FormatEnum)
+ {
+ case PixelFormatEnum.Rgb565:
+ return new PixelFormatReader.Bgr565PixelFormatReader();
+ case PixelFormatEnum.Rgba8888:
+ return new PixelFormatReader.Rgba8888PixelFormatReader();
+ case PixelFormatEnum.Bgra8888:
+ return new PixelFormatReader.Bgra8888PixelFormatReader();
+ case PixelFormatEnum.BlackWhite:
+ return new PixelFormatReader.BlackWhitePixelFormatReader();
+ case PixelFormatEnum.Gray2:
+ return new PixelFormatReader.Gray2PixelFormatReader();
+ case PixelFormatEnum.Gray4:
+ return new PixelFormatReader.Gray4PixelFormatReader();
+ case PixelFormatEnum.Gray8:
+ return new PixelFormatReader.Gray8PixelFormatReader();
+ case PixelFormatEnum.Gray16:
+ return new PixelFormatReader.Gray16PixelFormatReader();
+ case PixelFormatEnum.Gray32Float:
+ return new PixelFormatReader.Gray32FloatPixelFormatReader();
+ case PixelFormatEnum.Rgba64:
+ return new PixelFormatReader.Rgba64PixelFormatReader();
+ case PixelFormatEnum.Rgb24:
+ return new PixelFormatReader.Rgb24PixelFormatReader();
+ case PixelFormatEnum.Bgr24:
+ return new PixelFormatReader.Bgr24PixelFormatReader();
+ case PixelFormatEnum.Bgr555:
+ return new PixelFormatReader.Bgr555PixelFormatReader();
+ case PixelFormatEnum.Bgr565:
+ return new PixelFormatReader.Bgr565PixelFormatReader();
+ default:
+ throw new NotSupportedException($"Pixel format {format} is not supported");
+ }
+ }
+
+ private static IPixelFormatWriter GetWriter(PixelFormat format)
+ {
+ switch (format.FormatEnum)
+ {
+ case PixelFormatEnum.Rgb565:
+ return new PixelFormatWriter.Bgr565PixelFormatWriter();
+ case PixelFormatEnum.Rgba8888:
+ return new PixelFormatWriter.Rgba8888PixelFormatWriter();
+ case PixelFormatEnum.Bgra8888:
+ return new PixelFormatWriter.Bgra8888PixelFormatWriter();
+ case PixelFormatEnum.BlackWhite:
+ return new PixelFormatWriter.BlackWhitePixelFormatWriter();
+ case PixelFormatEnum.Gray2:
+ return new PixelFormatWriter.Gray2PixelFormatWriter();
+ case PixelFormatEnum.Gray4:
+ return new PixelFormatWriter.Gray4PixelFormatWriter();
+ case PixelFormatEnum.Gray8:
+ return new PixelFormatWriter.Gray8PixelFormatWriter();
+ case PixelFormatEnum.Gray16:
+ return new PixelFormatWriter.Gray16PixelFormatWriter();
+ case PixelFormatEnum.Gray32Float:
+ return new PixelFormatWriter.Gray32FloatPixelFormatWriter();
+ case PixelFormatEnum.Rgba64:
+ return new PixelFormatWriter.Rgba64PixelFormatWriter();
+ case PixelFormatEnum.Rgb24:
+ return new PixelFormatWriter.Rgb24PixelFormatWriter();
+ case PixelFormatEnum.Bgr24:
+ return new PixelFormatWriter.Bgr24PixelFormatWriter();
+ case PixelFormatEnum.Bgr555:
+ return new PixelFormatWriter.Bgr555PixelFormatWriter();
+ case PixelFormatEnum.Bgr565:
+ return new PixelFormatWriter.Bgr565PixelFormatWriter();
+ default:
+ throw new NotSupportedException($"Pixel format {format} is not supported");
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs b/src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs
new file mode 100644
index 00000000000..3c0d5b61f21
--- /dev/null
+++ b/src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs
@@ -0,0 +1,324 @@
+using System;
+namespace Avalonia.Media.Imaging;
+
+internal interface IPixelFormatWriter
+{
+ void WriteNext(Rgba8888Pixel pixel);
+ void Reset(IntPtr address);
+}
+
+internal static class PixelFormatWriter
+{
+ public unsafe struct Rgb24PixelFormatWriter : IPixelFormatWriter
+ {
+ private byte* _address;
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ addr[0] = pixel.R;
+ addr[1] = pixel.G;
+ addr[2] = pixel.B;
+
+ _address += 3;
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ public unsafe struct Rgba64PixelFormatWriter : IPixelFormatWriter
+ {
+ private Rgba64Pixel* _address;
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ *addr = new Rgba64Pixel((ushort)(pixel.R << 8), (ushort)(pixel.G << 8), (ushort)(pixel.B << 8), (ushort)(pixel.A << 8));
+
+ _address++;
+ }
+
+ public void Reset(IntPtr address) => _address = (Rgba64Pixel*)address;
+ }
+
+ public unsafe struct Rgba8888PixelFormatWriter : IPixelFormatWriter
+ {
+ private Rgba8888Pixel* _address;
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ *addr = pixel;
+
+ _address++;
+ }
+
+ public void Reset(IntPtr address) => _address = (Rgba8888Pixel*)address;
+ }
+
+ public unsafe struct Bgra8888PixelFormatWriter : IPixelFormatWriter
+ {
+ private byte* _address;
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ addr[0] = pixel.B;
+ addr[1] = pixel.G;
+ addr[2] = pixel.R;
+ addr[3] = pixel.A;
+
+ _address += 4;
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ public unsafe struct Bgr24PixelFormatWriter : IPixelFormatWriter
+ {
+ private byte* _address;
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ addr[2] = pixel.R;
+ addr[1] = pixel.G;
+ addr[0] = pixel.B;
+
+ _address += 3;
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ public unsafe struct Bgra32PixelFormatWriter : IPixelFormatWriter
+ {
+ private byte* _address;
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ addr[3] = pixel.A;
+ addr[2] = pixel.R;
+ addr[1] = pixel.G;
+ addr[0] = pixel.B;
+
+ _address += 4;
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ public unsafe struct Bgr565PixelFormatWriter : IPixelFormatWriter
+ {
+ private ushort* _address;
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ *addr = Pack(pixel);
+
+ _address++;
+ }
+
+ public void Reset(IntPtr address) => _address = (ushort*)address;
+
+ private static ushort Pack(Rgba8888Pixel pixel)
+ {
+ return (ushort)((((int)Math.Round(pixel.R / 255F * 31F) & 0x1F) << 11)
+ | (((int)Math.Round(pixel.G / 255F * 63F) & 0x3F) << 5)
+ | ((int)Math.Round(pixel.B / 255F * 31F) & 0x1F));
+ }
+ }
+
+ public unsafe struct Bgr555PixelFormatWriter : IPixelFormatWriter
+ {
+ private ushort* _address;
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ *addr = Pack(pixel);
+
+ _address++;
+ }
+
+ public void Reset(IntPtr address) => _address = (ushort*)address;
+
+ private static ushort Pack(Rgba8888Pixel pixel)
+ {
+ return (ushort)(
+ (((int)Math.Round(pixel.R / 255F * 31F) & 0x1F) << 10)
+ | (((int)Math.Round(pixel.G / 255F * 31F) & 0x1F) << 5)
+ | (((int)Math.Round(pixel.B / 255F * 31F) & 0x1F) << 0));
+ }
+ }
+
+ public unsafe struct Gray32FloatPixelFormatWriter : IPixelFormatWriter
+ {
+ private float* _address;
+
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ *addr = Pack(pixel);
+
+ _address++;
+ }
+
+ private static float Pack(Rgba8888Pixel pixel)
+ {
+ return (float)Math.Pow(pixel.R / 255F, 2.2);
+ }
+
+ public void Reset(IntPtr address) => _address = (float*)address;
+ }
+
+ public unsafe struct BlackWhitePixelFormatWriter : IPixelFormatWriter
+ {
+ private int _bit;
+ private byte* _address;
+
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ var grayscale = Math.Round(0.299F * pixel.R + 0.587F * pixel.G + 0.114F * pixel.B);
+
+ var value = grayscale > 0x7F ? 1 : 0;
+
+ var shift = 7 - _bit;
+ var mask = 1 << shift;
+
+ *addr = (byte)((*addr & ~mask) | value << shift);
+
+ _bit++;
+
+ if (_bit == 8)
+ {
+ _address++;
+
+ _bit = 0;
+ }
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ public unsafe struct Gray2PixelFormatWriter : IPixelFormatWriter
+ {
+ private int _bit;
+ private byte* _address;
+
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+ var value = 0;
+
+ var grayscale = (byte)Math.Round(0.299F * pixel.R + 0.587F * pixel.G + 0.114F * pixel.B);
+
+ if (grayscale > 0 && grayscale <= 0x55)
+ {
+ //01
+ value = 1;
+ }
+
+ if (grayscale > 0x55 && grayscale <= 0xAA)
+ {
+ //10
+
+ value = 2;
+ }
+
+ if (grayscale > 0xAA)
+ {
+ //11
+ value = 3;
+ }
+
+ var shift = 6 - _bit;
+ var mask = 3 << shift;
+
+ *addr = (byte)((*addr & ~mask) | value << shift);
+
+ _bit += 2;
+
+ if (_bit == 8)
+ {
+ _address++;
+ _bit = 0;
+ }
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ public unsafe struct Gray4PixelFormatWriter : IPixelFormatWriter
+ {
+ private int _bit;
+ private byte* _address;
+
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ var grayscale = (byte)Math.Round(0.299F * pixel.R + 0.587F * pixel.G + 0.114F * pixel.B);
+
+ var value = (byte)(grayscale / 255F * 0xF);
+
+ var shift = 4 - _bit;
+ var mask = 0xF << shift;
+
+ *addr = (byte)((*addr & ~mask) | value << shift);
+
+ _bit += 4;
+
+ if (_bit == 8)
+ {
+ _address++;
+ _bit = 0;
+ }
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ public unsafe struct Gray8PixelFormatWriter : IPixelFormatWriter
+ {
+ private byte* _address;
+
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ var grayscale = (byte)Math.Round(0.299F * pixel.R + 0.587F * pixel.G + 0.114F * pixel.B);
+
+ *addr = grayscale;
+
+ _address++;
+ }
+
+ public void Reset(IntPtr address) => _address = (byte*)address;
+ }
+
+ public unsafe struct Gray16PixelFormatWriter : IPixelFormatWriter
+ {
+ private ushort* _address;
+
+ public void WriteNext(Rgba8888Pixel pixel)
+ {
+ var addr = _address;
+
+ var grayscale = (ushort)Math.Round((0.299F * pixel.R + 0.587F * pixel.G + 0.114F * pixel.B) * 0x0101);
+
+ *addr = grayscale;
+
+ _address++;
+ }
+
+ public void Reset(IntPtr address) => _address = (ushort*)address;
+ }
+}
+
+
diff --git a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
index 868f4439c41..5ec00f9c7e4 100644
--- a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
@@ -74,7 +74,7 @@ public ILockedFramebuffer Lock()
Dpi, _pixelFormatMemory.Format, () =>
{
using var inner = ((IWriteableBitmapImpl)PlatformImpl.Item).Lock();
- _pixelFormatMemory.CopyToRgba(inner.Address, inner.RowBytes);
+ _pixelFormatMemory.CopyToRgba(Platform.AlphaFormat.Unpremul, inner.Address, inner.RowBytes);
});
}
@@ -137,9 +137,10 @@ private static (IBitmapImpl, BitmapMemory?) CreatePlatformImpl(PixelSize size, i
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));
+ finalAlphaFormat = finalFormat.HasAlpha ? finalAlphaFormat : Platform.AlphaFormat.Opaque;
+
+ var impl = ri.CreateWriteableBitmap(size, dpi, PixelFormat.Rgba8888, finalAlphaFormat);
+ return (impl, new BitmapMemory(finalFormat, finalAlphaFormat, size));
}
private static IPlatformRenderInterface GetFactory()
diff --git a/src/Avalonia.Base/Platform/ILockedFramebuffer.cs b/src/Avalonia.Base/Platform/ILockedFramebuffer.cs
index 2f631142d2f..f963b77cd92 100644
--- a/src/Avalonia.Base/Platform/ILockedFramebuffer.cs
+++ b/src/Avalonia.Base/Platform/ILockedFramebuffer.cs
@@ -28,5 +28,7 @@ public interface ILockedFramebuffer : IDisposable
/// Pixel format
///
PixelFormat Format { get; }
+
+ //TODO12: Add AlphaFormat
}
}
diff --git a/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs b/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs
index acf1801e0a9..d5a0c765ccf 100644
--- a/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs
+++ b/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs
@@ -1,7 +1,16 @@
+using Avalonia.Metadata;
+
namespace Avalonia.Platform;
public interface IReadableBitmapImpl
{
PixelFormat? Format { get; }
ILockedFramebuffer Lock();
-}
\ No newline at end of file
+}
+
+//TODO12: Remove me once we can change IReadableBitmapImpl
+[Unstable]
+public interface IReadableBitmapWithAlphaImpl : IReadableBitmapImpl
+{
+ AlphaFormat? AlphaFormat { get; }
+}
diff --git a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
index 3284d34a0ac..685491a326e 100644
--- a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
+++ b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Platform
/// Defines the platform-specific interface for a .
///
[Unstable]
- public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapImpl
+ public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapWithAlphaImpl
{
}
}
diff --git a/src/Avalonia.Base/Platform/PixelFormat.cs b/src/Avalonia.Base/Platform/PixelFormat.cs
index 95f49bdb25b..d03b4e04b8d 100644
--- a/src/Avalonia.Base/Platform/PixelFormat.cs
+++ b/src/Avalonia.Base/Platform/PixelFormat.cs
@@ -15,7 +15,9 @@ internal enum PixelFormatEnum
Gray32Float,
Rgba64,
Rgb24,
- Bgr24
+ Bgr24,
+ Bgr555,
+ Bgr565
}
public record struct PixelFormat
@@ -34,8 +36,10 @@ public int BitsPerPixel
return 4;
else if (FormatEnum == PixelFormatEnum.Gray8)
return 8;
- else if (FormatEnum == PixelFormatEnum.Rgb565
- || FormatEnum == PixelFormatEnum.Gray16)
+ else if (FormatEnum == PixelFormatEnum.Rgb565 ||
+ FormatEnum == PixelFormatEnum.Bgr555 ||
+ FormatEnum == PixelFormatEnum.Bgr565 ||
+ FormatEnum == PixelFormatEnum.Gray16)
return 16;
else if (FormatEnum is PixelFormatEnum.Bgr24 or PixelFormatEnum.Rgb24)
return 24;
@@ -76,5 +80,7 @@ public static class PixelFormats
public static PixelFormat Gray32Float { get; } = new PixelFormat(PixelFormatEnum.Gray32Float);
public static PixelFormat Rgb24 { get; } = new PixelFormat(PixelFormatEnum.Rgb24);
public static PixelFormat Bgr24 { get; } = new PixelFormat(PixelFormatEnum.Bgr24);
+ public static PixelFormat Bgr555 { get; } = new PixelFormat(PixelFormatEnum.Bgr555);
+ public static PixelFormat Bgr565 { get; } = new PixelFormat(PixelFormatEnum.Bgr565);
}
}
diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index 63940294a97..5d603143de2 100644
--- a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -406,7 +406,10 @@ public void Blit(IDrawingContextImpl context)
public Vector Dpi { get; }
public PixelSize PixelSize { get; }
+ public PixelFormat? Format { get; }
+ public AlphaFormat? AlphaFormat { get; }
public int Version { get; set; }
+
public void Save(string fileName, int? quality = null)
{
@@ -417,7 +420,6 @@ public void Save(Stream stream, int? quality = null)
}
- public PixelFormat? Format { get; }
public ILockedFramebuffer Lock()
{
diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
index 0627407509c..6051985cea2 100644
--- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
+++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Skia
///
/// Immutable Skia bitmap.
///
- internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl
+ internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapWithAlphaImpl
{
private readonly SKImage _image;
private readonly SKBitmap? _bitmap;
@@ -177,6 +177,9 @@ public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect,
}
public PixelFormat? Format => _bitmap?.ColorType.ToAvalonia();
+
+ public AlphaFormat? AlphaFormat => _bitmap?.AlphaType.ToAlphaFormat();
+
public ILockedFramebuffer Lock()
{
if (_bitmap is null)
diff --git a/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs b/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs
index 454e137389f..a6a8fd1def4 100644
--- a/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs
@@ -14,7 +14,7 @@ internal class RenderTargetBitmapImpl : WriteableBitmapImpl,
public RenderTargetBitmapImpl(PixelSize size, Vector dpi) : base(size, dpi,
SKImageInfo.PlatformColorType == SKColorType.Rgba8888 ? PixelFormats.Rgba8888 : PixelFormat.Bgra8888,
- AlphaFormat.Premul)
+ Platform.AlphaFormat.Premul)
{
_renderTarget = new FramebufferRenderTarget(this);
}
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index 581470fcfe3..865bcae99c2 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -148,6 +148,8 @@ public void Save(string fileName, int? quality = null)
public PixelFormat? Format => _bitmap.ColorType.ToAvalonia();
+ public AlphaFormat? AlphaFormat => _bitmap.AlphaType.ToAlphaFormat();
+
///
public ILockedFramebuffer Lock() => new BitmapFramebuffer(this, _bitmap);
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
index 2fa1e5bd7a9..5a8f30a235a 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Direct2D1.Media
///
/// A WIC implementation of a .
///
- internal class WicBitmapImpl : BitmapImpl, IReadableBitmapImpl
+ internal class WicBitmapImpl : BitmapImpl, IReadableBitmapWithAlphaImpl
{
private readonly BitmapDecoder _decoder;
@@ -87,10 +87,11 @@ public WicBitmapImpl(PixelSize size, Vector dpi, APixelFormat? pixelFormat = nul
if (!alphaFormat.HasValue)
{
- alphaFormat = AlphaFormat.Premul;
+ alphaFormat = Platform.AlphaFormat.Premul;
}
PixelFormat = pixelFormat;
+ AlphaFormat = alphaFormat;
WicImpl = new Bitmap(
Direct2D1Platform.ImagingFactory,
size.Width,
@@ -106,6 +107,7 @@ public WicBitmapImpl(APixelFormat format, AlphaFormat alphaFormat, IntPtr data,
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, size.Width, size.Height, format.ToWic(alphaFormat), BitmapCreateCacheOption.CacheOnDemand);
WicImpl.SetResolution(dpi.X, dpi.Y);
PixelFormat = format;
+ AlphaFormat = alphaFormat;
Dpi = dpi;
using (var l = WicImpl.Lock(BitmapLockFlags.Write))
@@ -161,7 +163,9 @@ public WicBitmapImpl(Stream stream, int decodeSize, bool horizontal, Avalonia.Me
public override PixelSize PixelSize => WicImpl.Size.ToAvalonia();
- protected APixelFormat? PixelFormat { get; }
+ public APixelFormat? PixelFormat { get; }
+
+ public AlphaFormat? AlphaFormat { get; }
public override void Dispose()
{
diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
index 4cdce8df26a..3cb85a56452 100644
--- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
+++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
@@ -3,6 +3,7 @@
net6.0
Library
true
+ true
diff --git a/tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatWriterTests.cs b/tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatWriterTests.cs
new file mode 100644
index 00000000000..daf057ff331
--- /dev/null
+++ b/tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatWriterTests.cs
@@ -0,0 +1,355 @@
+using System;
+using Avalonia.Media.Imaging;
+using Xunit;
+
+namespace Avalonia.Base.UnitTests.Media.Imaging
+{
+ public class PixelFormatWriterTests
+ {
+ 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
+ };
+
+ [Fact]
+ public void Should_Write_Bgr555()
+ {
+ var bitmapMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Bgr555),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Bgr555PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Bgr555PixelFormatReader();
+
+ pixelWriter.Reset(bitmapMemory.Address);
+ pixelReader.Reset(bitmapMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255 });
+ Assert.Equal(new Rgba8888Pixel { R = 255, A = 255 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { G = 255 });
+ Assert.Equal(new Rgba8888Pixel { G = 255, A = 255 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { B = 255 });
+ Assert.Equal(new Rgba8888Pixel { B = 255, A = 255 }, pixelReader.ReadNext());
+ }
+
+ [Fact]
+ public void Should_Write_Bgra8888()
+ {
+ var sourceMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Bgra8888),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(3, 1));
+
+ var sourceWriter = new PixelFormatWriter.Bgra8888PixelFormatWriter();
+ var sourceReader = new PixelFormatReader.Bgra8888PixelFormatReader();
+
+ sourceWriter.Reset(sourceMemory.Address);
+ sourceReader.Reset(sourceMemory.Address);
+
+ sourceWriter.WriteNext(new Rgba8888Pixel { R = 255 });
+ Assert.Equal(new Rgba8888Pixel { R = 255 }, sourceReader.ReadNext());
+
+ sourceWriter.WriteNext(new Rgba8888Pixel { G = 255 });
+ Assert.Equal(new Rgba8888Pixel { G = 255 }, sourceReader.ReadNext());
+
+ sourceWriter.WriteNext(new Rgba8888Pixel { B = 255 });
+ Assert.Equal(new Rgba8888Pixel { B = 255 }, sourceReader.ReadNext());
+ }
+
+ [Fact]
+ public void Should_Write_Rgba8888()
+ {
+ var sourceMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Rgba8888),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Rgba8888PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Rgba8888PixelFormatReader();
+
+ pixelWriter.Reset(sourceMemory.Address);
+ pixelReader.Reset(sourceMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255, G = 125, B = 125, A = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 255, G = 125, B = 125, A = 125 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 125, G = 255, B = 125, A = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 125, G = 255, B = 125, A = 125 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 125, G = 125, B = 255, A = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 125, G = 125, B = 255, A = 125 }, pixelReader.ReadNext());
+ }
+
+ [Fact]
+ public void Should_Write_Rgb24()
+ {
+ var sourceMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Rgb24),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Rgb24PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Rgb24PixelFormatReader();
+
+ pixelWriter.Reset(sourceMemory.Address);
+ pixelReader.Reset(sourceMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255, G = 125, B = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 255, G = 125, B = 125, A = 255 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 125, G = 255, B = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 125, G = 255, B = 125, A = 255 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 125, G = 125, B = 255 });
+ Assert.Equal(new Rgba8888Pixel { R = 125, G = 125, B = 255, A = 255 }, pixelReader.ReadNext());
+ }
+
+
+ [Fact]
+ public void Should_Write_Rgba64()
+ {
+ var sourceMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Rgba64),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Rgba64PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Rgba64PixelFormatReader();
+
+ pixelWriter.Reset(sourceMemory.Address);
+ pixelReader.Reset(sourceMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255, G = 125, B = 125, A = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 255, G = 125, B = 125, A = 125 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 125, G = 255, B = 125, A = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 125, G = 255, B = 125, A = 125 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 125, G = 125, B = 255, A = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 125, G = 125, B = 255, A = 125 }, pixelReader.ReadNext());
+ }
+
+ [Fact]
+ public void Should_Write_Bgr565()
+ {
+ var bitmapMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Bgr565),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Bgr565PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Bgr565PixelFormatReader();
+
+ pixelWriter.Reset(bitmapMemory.Address);
+ pixelReader.Reset(bitmapMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255 });
+ Assert.Equal(new Rgba8888Pixel { R = 255, A = 255 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { G = 255 });
+ Assert.Equal(new Rgba8888Pixel { G = 255, A = 255 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { B = 255 });
+ Assert.Equal(new Rgba8888Pixel { B = 255, A = 255 }, pixelReader.ReadNext());
+ }
+
+ [Fact]
+ public void Should_Write_Gray32Float()
+ {
+ var bitmapMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Gray32Float),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Gray32FloatPixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Gray32FloatPixelFormatReader();
+
+ pixelWriter.Reset(bitmapMemory.Address);
+ pixelReader.Reset(bitmapMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255 });
+ Assert.Equal(new Rgba8888Pixel { R = 255, G = 255, B = 255, A = 255 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 125 });
+ Assert.Equal(new Rgba8888Pixel { R = 125, G = 125, B = 125, A = 255 }, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel());
+ Assert.Equal(new Rgba8888Pixel { A = 255 }, pixelReader.ReadNext());
+ }
+
+ [Fact]
+ public void Should_Write_BlackWhite()
+ {
+ var bitmapMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.BlackWhite),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.BlackWhitePixelFormatWriter();
+ var pixelReader = new PixelFormatReader.BlackWhitePixelFormatReader();
+
+ pixelWriter.Reset(bitmapMemory.Address);
+ pixelReader.Reset(bitmapMemory.Address);
+
+ pixelWriter.WriteNext(s_white);
+ Assert.Equal(s_white, pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(s_black);
+ Assert.Equal(s_black, pixelReader.ReadNext());
+ }
+
+ [Fact]
+ public void Should_Write_Gray2()
+ {
+ var 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
+ };
+
+ var bitmapMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Gray2),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Gray2PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Gray2PixelFormatReader();
+
+ pixelWriter.Reset(bitmapMemory.Address);
+ pixelReader.Reset(bitmapMemory.Address);
+
+ pixelWriter.WriteNext(palette[0]);
+ Assert.Equal(palette[0], pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(palette[1]);
+ Assert.Equal(palette[1], pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(palette[2]);
+ Assert.Equal(palette[2], pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(palette[3]);
+ Assert.Equal(palette[3], pixelReader.ReadNext());
+ }
+
+ [Fact]
+ public void Should_Write_Gray4()
+ {
+ var bitmapMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Gray4),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Gray4PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Gray4PixelFormatReader();
+
+ pixelWriter.Reset(bitmapMemory.Address);
+ pixelReader.Reset(bitmapMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255 });
+ Assert.Equal(GetGray4(new Rgba8888Pixel { R = 255 }), pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 17 });
+ Assert.Equal(GetGray4(new Rgba8888Pixel { R = 17 }), pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel());
+ Assert.Equal(new Rgba8888Pixel { A = 255 }, pixelReader.ReadNext());
+ }
+
+ private static Rgba8888Pixel GetGray4(Rgba8888Pixel pixel)
+ {
+ var grayscale = (byte)Math.Round(0.299F * pixel.R + 0.587F * pixel.G + 0.114F * pixel.B);
+
+ var value = (byte)(grayscale / 255F * 0xF);
+
+ value = (byte)(value | (value << 4));
+
+ return new Rgba8888Pixel(value, value, value, 255);
+ }
+
+ [Fact]
+ public void Should_Write_Gray8()
+ {
+ var bitmapMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Gray8),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Gray8PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Gray8PixelFormatReader();
+
+ pixelWriter.Reset(bitmapMemory.Address);
+ pixelReader.Reset(bitmapMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255 });
+ Assert.Equal(GetGray8(new Rgba8888Pixel { R = 255 }), pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 120 });
+ Assert.Equal(GetGray8(new Rgba8888Pixel { R = 120 }), pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel());
+ Assert.Equal(GetGray8(new Rgba8888Pixel { A = 255 }), pixelReader.ReadNext());
+ }
+
+ private static Rgba8888Pixel GetGray8(Rgba8888Pixel pixel)
+ {
+ var value = (byte)Math.Round(0.299F * pixel.R + 0.587F * pixel.G + 0.114F * pixel.B);
+
+ return new Rgba8888Pixel(value, value, value, 255);
+ }
+
+ [Fact]
+ public void Should_Write_Gray16()
+ {
+ var bitmapMemory = new BitmapMemory(
+ new Platform.PixelFormat(Platform.PixelFormatEnum.Gray16),
+ Platform.AlphaFormat.Unpremul,
+ new PixelSize(10, 10));
+
+ var pixelWriter = new PixelFormatWriter.Gray16PixelFormatWriter();
+ var pixelReader = new PixelFormatReader.Gray16PixelFormatReader();
+
+ pixelWriter.Reset(bitmapMemory.Address);
+ pixelReader.Reset(bitmapMemory.Address);
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 255 });
+ Assert.Equal(GetGray16(new Rgba8888Pixel { R = 255 }), pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel { R = 120 });
+ Assert.Equal(GetGray16(new Rgba8888Pixel { R = 120 }), pixelReader.ReadNext());
+
+ pixelWriter.WriteNext(new Rgba8888Pixel());
+ Assert.Equal(GetGray16(new Rgba8888Pixel { A = 255 }), pixelReader.ReadNext());
+ }
+
+ private static Rgba8888Pixel GetGray16(Rgba8888Pixel pixel)
+ {
+ var grayscale = (ushort)Math.Round((0.299F * pixel.R + 0.587F * pixel.G + 0.114F * pixel.B) * 0x0101);
+
+ var value = (byte)(grayscale >> 8);
+
+ return new Rgba8888Pixel(value, value, value, 255);
+ }
+ }
+}