diff --git a/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs b/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs index 97c51c910a..bf055f892d 100644 --- a/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs +++ b/src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs @@ -223,7 +223,6 @@ internal static unsafe class PixelFormatReader public void Reset(IntPtr address) => _address = (byte*)address; } - public unsafe struct Rgba64PixelFormatReader : IPixelFormatReader { private Rgba64Pixel* _address; @@ -382,5 +381,75 @@ internal static unsafe class PixelFormatReader default: return false; } - } + } + + private static void Read(Span pixels, IntPtr source, PixelSize size, int stride) where T : struct, IPixelFormatReader + { + var reader = new T(); + + var w = size.Width; + var h = size.Height; + var count = 0; + + for (var y = 0; y < h; y++) + { + reader.Reset(source + stride * y); + + for (var x = 0; x < w; x++) + { + pixels[count++] = reader.ReadNext(); + } + } + } + + public static void Read(Span pixels, IntPtr source, PixelSize size, int stride, PixelFormat format) + { + switch (format.FormatEnum) + { + case PixelFormatEnum.Rgb565: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Rgba8888: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Bgra8888: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.BlackWhite: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Gray2: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Gray4: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Gray8: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Gray16: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Gray32Float: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Rgba64: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Rgb24: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Bgr24: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Bgr555: + Read(pixels, source, size, stride); + break; + case PixelFormatEnum.Bgr565: + Read(pixels, source, size, stride); + break; + default: + throw new NotSupportedException($"Pixel format {format} is not supported"); + } + } } diff --git a/src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs b/src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs index b91c574fe0..6659197e1a 100644 --- a/src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs +++ b/src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs @@ -1,5 +1,7 @@ using System; +using System.Runtime.InteropServices; using Avalonia.Platform; +using Avalonia.Platform.Internal; namespace Avalonia.Media.Imaging; internal static unsafe class PixelFormatTranscoder @@ -15,140 +17,14 @@ internal static unsafe class PixelFormatTranscoder PixelFormat destFormat, AlphaFormat destAlphaFormat) { - var reader = GetReader(srcFormat); - var writer = GetWriter(destFormat); + var pixelCount = srcSize.Width * srcSize.Height; + var bufferSize = pixelCount * Marshal.SizeOf(); + using var blob = new UnmanagedBlob(bufferSize); + + var pixels = new Span((void*)blob.Address, pixelCount); - var w = srcSize.Width; - var h = srcSize.Height; + PixelFormatReader.Read(pixels, source, srcSize, sourceStride, srcFormat); - 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"); - } + PixelFormatWriter.Write(pixels, dest, srcSize, destStride, destFormat, destAlphaFormat, srcAlphaFormat); } } diff --git a/src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs b/src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs index 3c0d5b61f2..9cc399e432 100644 --- a/src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs +++ b/src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Platform; namespace Avalonia.Media.Imaging; internal interface IPixelFormatWriter @@ -7,7 +8,7 @@ internal interface IPixelFormatWriter void Reset(IntPtr address); } -internal static class PixelFormatWriter +internal static unsafe class PixelFormatWriter { public unsafe struct Rgb24PixelFormatWriter : IPixelFormatWriter { @@ -319,6 +320,133 @@ internal static class PixelFormatWriter public void Reset(IntPtr address) => _address = (ushort*)address; } + + private static void Write( + ReadOnlySpan pixels, + IntPtr dest, + PixelSize size, + int stride, + AlphaFormat alphaFormat, + AlphaFormat srcAlphaFormat) where T : struct, IPixelFormatWriter + { + var writer = new T(); + + var w = size.Width; + var h = size.Height; + var count = 0; + + for (var y = 0; y < h; y++) + { + writer.Reset(dest + stride * y); + + for (var x = 0; x < w; x++) + { + writer.WriteNext(GetConvertedPixel(pixels[count++], srcAlphaFormat, alphaFormat)); + } + } + } + + 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 + }; + } + + public static void Write( + ReadOnlySpan pixels, + IntPtr dest, + PixelSize size, + int stride, + PixelFormat format, + AlphaFormat alphaFormat, + AlphaFormat srcAlphaFormat) + { + switch (format.FormatEnum) + { + case PixelFormatEnum.Rgb565: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Rgba8888: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Bgra8888: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.BlackWhite: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Gray2: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Gray4: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Gray8: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Gray16: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Gray32Float: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Rgba64: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Rgb24: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Bgr24: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Bgr555: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + case PixelFormatEnum.Bgr565: + Write(pixels, dest, size, stride, alphaFormat, srcAlphaFormat); + break; + default: + throw new NotSupportedException($"Pixel format {format} is not supported"); + } + } } diff --git a/src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs b/src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs index fc299dbcec..92ec820b2c 100644 --- a/src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs +++ b/src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs @@ -6,7 +6,7 @@ using System.Threading; namespace Avalonia.Platform.Internal; -internal class UnmanagedBlob +internal class UnmanagedBlob : IDisposable { private IntPtr _address; private readonly object _lock = new object(); diff --git a/tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatTranscoderTests.cs b/tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatTranscoderTests.cs new file mode 100644 index 0000000000..cca342412c --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatTranscoderTests.cs @@ -0,0 +1,51 @@ +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Xunit; + +namespace Avalonia.Base.UnitTests.Media.Imaging +{ + public class PixelFormatTranscoderTests + { + [Fact] + public void Should_Transcode() + { + var sourceMemory = CreateBitmapMemory(); + + var destMemory = new BitmapMemory(PixelFormat.Bgra8888, AlphaFormat.Opaque, sourceMemory.Size); + + PixelFormatTranscoder.Transcode( + sourceMemory.Address, + sourceMemory.Size, + sourceMemory.RowBytes, + sourceMemory.Format, + sourceMemory.AlphaFormat, + destMemory.Address, + destMemory.RowBytes, + destMemory.Format, + destMemory.AlphaFormat); + + var reader = new PixelFormatReader.Bgra8888PixelFormatReader(); + + reader.Reset(destMemory.Address); + + Assert.Equal(new Rgba8888Pixel(255, 0, 0, 0), reader.ReadNext()); + Assert.Equal(new Rgba8888Pixel(0, 255, 0, 0), reader.ReadNext()); + Assert.Equal(new Rgba8888Pixel(0, 0, 255, 0), reader.ReadNext()); + } + + private BitmapMemory CreateBitmapMemory() + { + var bitmapMemory = new BitmapMemory(PixelFormat.Rgba8888, AlphaFormat.Opaque, new PixelSize(3, 1)); + + var sourceWriter = new PixelFormatWriter.Rgba8888PixelFormatWriter(); + + sourceWriter.Reset(bitmapMemory.Address); + + sourceWriter.WriteNext(new Rgba8888Pixel { R = 255 }); + sourceWriter.WriteNext(new Rgba8888Pixel { G = 255 }); + sourceWriter.WriteNext(new Rgba8888Pixel { B = 255 }); + + return bitmapMemory; + } + } +}