Browse Source

Read all pixels then write all pixels to avoid virtual call overhead (#14111)

* Read all pixels then write all pixels to avoid virual call overhead

* Use unmanaged buffer

* Make UnmanagedBlob IDisposable
pull/16303/head
Benedikt Stebner 2 years ago
committed by GitHub
parent
commit
3657128cd2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 73
      src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
  2. 142
      src/Avalonia.Base/Media/Imaging/PixelFormatTranscoder.cs
  3. 130
      src/Avalonia.Base/Media/Imaging/PixelFormatWriter.cs
  4. 2
      src/Avalonia.Base/Platform/Internal/UnmanagedBlob.cs
  5. 51
      tests/Avalonia.Base.UnitTests/Media/Imaging/PixelFormatTranscoderTests.cs

73
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<T>(Span<Rgba8888Pixel> 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<Rgba8888Pixel> pixels, IntPtr source, PixelSize size, int stride, PixelFormat format)
{
switch (format.FormatEnum)
{
case PixelFormatEnum.Rgb565:
Read<Bgr565PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Rgba8888:
Read<Rgba8888PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Bgra8888:
Read<Bgra8888PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.BlackWhite:
Read<BlackWhitePixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Gray2:
Read<Gray2PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Gray4:
Read<Gray4PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Gray8:
Read<Gray8PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Gray16:
Read<Gray16PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Gray32Float:
Read<Gray32FloatPixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Rgba64:
Read<Rgba64PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Rgb24:
Read<Rgb24PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Bgr24:
Read<Bgr24PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Bgr555:
Read<Bgr555PixelFormatReader>(pixels, source, size, stride);
break;
case PixelFormatEnum.Bgr565:
Read<Bgr565PixelFormatReader>(pixels, source, size, stride);
break;
default:
throw new NotSupportedException($"Pixel format {format} is not supported");
}
}
}

142
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<Rgba8888Pixel>();
using var blob = new UnmanagedBlob(bufferSize);
var pixels = new Span<Rgba8888Pixel>((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);
}
}

130
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<T>(
ReadOnlySpan<Rgba8888Pixel> 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<Rgba8888Pixel> pixels,
IntPtr dest,
PixelSize size,
int stride,
PixelFormat format,
AlphaFormat alphaFormat,
AlphaFormat srcAlphaFormat)
{
switch (format.FormatEnum)
{
case PixelFormatEnum.Rgb565:
Write<Bgr565PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Rgba8888:
Write<Rgba8888PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Bgra8888:
Write<Bgra8888PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.BlackWhite:
Write<BlackWhitePixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Gray2:
Write<Gray2PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Gray4:
Write<Gray4PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Gray8:
Write<Gray8PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Gray16:
Write<Gray16PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Gray32Float:
Write<Gray32FloatPixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Rgba64:
Write<Rgba64PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Rgb24:
Write<Rgb24PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Bgr24:
Write<Bgr24PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Bgr555:
Write<Bgr555PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
case PixelFormatEnum.Bgr565:
Write<Bgr565PixelFormatWriter>(pixels, dest, size, stride, alphaFormat, srcAlphaFormat);
break;
default:
throw new NotSupportedException($"Pixel format {format} is not supported");
}
}
}

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

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