Browse Source

Introduce PixelFormatWriter (#12807)

Introduce PixelFormatTranscoder
Introduce Bitmap.CopyPixels that transcodes pixel and alpha format
pull/12940/head
Benedikt Stebner 2 years ago
committed by GitHub
parent
commit
fac9e55f69
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 68
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  2. 22
      src/Avalonia.Base/Media/Imaging/BitmapMemory.cs
  3. 222
      src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
  4. 154
      src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs
  5. 324
      src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs
  6. 9
      src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
  7. 2
      src/Avalonia.Base/Platform/ILockedFramebuffer.cs
  8. 11
      src/Avalonia.Base/Platform/IReadableBitmapImpl.cs
  9. 2
      src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs
  10. 12
      src/Avalonia.Base/Platform/PixelFormat.cs
  11. 4
      src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  12. 5
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  13. 2
      src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs
  14. 2
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  15. 10
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  16. 1
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  17. 355
      tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatWriterTests.cs

68
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 @@ namespace Avalonia.Media.Imaging
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 @@ namespace Avalonia.Media.Imaging
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 @@ namespace Avalonia.Media.Imaging
CopyPixelsCore(sourceRect, buffer, bufferSize, stride, fb);
}
/// <summary>
/// Copies pixels to the target buffer and transcodes the pixel and alpha format if needed.
/// </summary>
/// <param name="buffer">The target buffer.</param>
/// <param name="alphaFormat">The alpha format.</param>
/// <exception cref="NotSupportedException"></exception>
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);
}
}
}
/// <inheritdoc/>
void IImage.Draw(
DrawingContext context,

22
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 @@ internal class BitmapMemory : IDisposable
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);
}
public void CopyToRgba(AlphaFormat alphaFormat, IntPtr buffer, int stride)
{
PixelFormatTranscoder.Transcode(
Address,
Size,
RowBytes,
Format,
AlphaFormat,
buffer,
stride,
PixelFormat.Rgba8888,
alphaFormat);
}
}

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

@ -2,22 +2,46 @@ using System;
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 @@ static unsafe class PixelFormatReader
G = 255,
R = 255
};
private static readonly Rgba8888Pixel s_black = new Rgba8888Pixel
{
A = 255,
@ -34,7 +58,7 @@ static unsafe class PixelFormatReader
R = 0
};
public unsafe struct BlackWhitePixelReader : IPixelFormatReader
public unsafe struct BlackWhitePixelFormatReader : IPixelFormatReader
{
private int _bit;
private byte* _address;
@ -58,8 +82,8 @@ static unsafe class PixelFormatReader
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 @@ static unsafe class PixelFormatReader
{
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 @@ static unsafe class PixelFormatReader
return Palette[value];
}
}
public unsafe struct Gray4PixelReader : IPixelFormatReader
public unsafe struct Gray4PixelFormatReader : IPixelFormatReader
{
private int _bit;
private byte* _address;
@ -133,8 +157,8 @@ static unsafe class PixelFormatReader
};
}
}
public unsafe struct Gray8PixelReader : IPixelFormatReader
public unsafe struct Gray8PixelFormatReader : IPixelFormatReader
{
private byte* _address;
public void Reset(IntPtr address)
@ -156,8 +180,8 @@ static unsafe class PixelFormatReader
};
}
}
public unsafe struct Gray16PixelReader : IPixelFormatReader
public unsafe struct Gray16PixelFormatReader : IPixelFormatReader
{
private ushort* _address;
public Rgba8888Pixel ReadNext()
@ -177,7 +201,7 @@ static unsafe class PixelFormatReader
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 @@ static unsafe class PixelFormatReader
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 @@ static unsafe class PixelFormatReader
};
}
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 @@ static unsafe class PixelFormatReader
public void Reset(IntPtr address) => _address = (byte*)address;
}
public unsafe struct Bgr24PixelFormatReader : IPixelFormatReader
{
private byte* _address;
@ -267,58 +282,105 @@ static unsafe class PixelFormatReader
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<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.Rgb24)
Transcode<Rgb24PixelFormatReader>(dst, src, size, strideSrc, strideDst);
else if (format == PixelFormats.Bgr24)
Transcode<Bgr24PixelFormatReader>(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");
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<TReader>(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;
}
}
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;
}
}
}

154
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");
}
}
}

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

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

@ -74,7 +74,7 @@ namespace Avalonia.Media.Imaging
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 @@ namespace Avalonia.Media.Imaging
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()

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

@ -28,5 +28,7 @@ namespace Avalonia.Platform
/// Pixel format
/// </summary>
PixelFormat Format { get; }
//TODO12: Add AlphaFormat
}
}

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

@ -1,7 +1,16 @@
using Avalonia.Metadata;
namespace Avalonia.Platform;
public interface IReadableBitmapImpl
{
PixelFormat? Format { get; }
ILockedFramebuffer Lock();
}
}
//TODO12: Remove me once we can change IReadableBitmapImpl
[Unstable]
public interface IReadableBitmapWithAlphaImpl : IReadableBitmapImpl
{
AlphaFormat? AlphaFormat { get; }
}

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

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

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

@ -15,7 +15,9 @@ namespace Avalonia.Platform
Gray32Float,
Rgba64,
Rgb24,
Bgr24
Bgr24,
Bgr555,
Bgr565
}
public record struct PixelFormat
@ -34,8 +36,10 @@ namespace Avalonia.Platform
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 @@ namespace Avalonia.Platform
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);
}
}

4
src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -406,7 +406,10 @@ namespace Avalonia.Headless
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 @@ namespace Avalonia.Headless
}
public PixelFormat? Format { get; }
public ILockedFramebuffer Lock()
{

5
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -10,7 +10,7 @@ namespace Avalonia.Skia
/// <summary>
/// Immutable Skia bitmap.
/// </summary>
internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl
internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapWithAlphaImpl
{
private readonly SKImage _image;
private readonly SKBitmap? _bitmap;
@ -177,6 +177,9 @@ namespace Avalonia.Skia
}
public PixelFormat? Format => _bitmap?.ColorType.ToAvalonia();
public AlphaFormat? AlphaFormat => _bitmap?.AlphaType.ToAlphaFormat();
public ILockedFramebuffer Lock()
{
if (_bitmap is null)

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

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

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

10
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -13,7 +13,7 @@ namespace Avalonia.Direct2D1.Media
/// <summary>
/// A WIC implementation of a <see cref="Avalonia.Media.Imaging.Bitmap"/>.
/// </summary>
internal class WicBitmapImpl : BitmapImpl, IReadableBitmapImpl
internal class WicBitmapImpl : BitmapImpl, IReadableBitmapWithAlphaImpl
{
private readonly BitmapDecoder _decoder;
@ -87,10 +87,11 @@ namespace Avalonia.Direct2D1.Media
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 @@ namespace Avalonia.Direct2D1.Media
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 @@ namespace Avalonia.Direct2D1.Media
public override PixelSize PixelSize => WicImpl.Size.ToAvalonia();
protected APixelFormat? PixelFormat { get; }
public APixelFormat? PixelFormat { get; }
public AlphaFormat? AlphaFormat { get; }
public override void Dispose()
{

1
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@ -3,6 +3,7 @@
<TargetFramework>net6.0</TargetFramework>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\UnitTests.NetFX.props" />

355
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);
}
}
}
Loading…
Cancel
Save