Browse Source

Remove per-pixel transform

pull/3028/head
James Jackson-South 4 weeks ago
parent
commit
bb93350bb5
  1. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsPixelCompatible.cs
  2. 65
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  3. 162
      src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

4
src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsPixelCompatible.cs

@ -47,7 +47,7 @@ internal static class ColorProfileConverterExtensionsPixelCompatible
throw new InvalidOperationException("Target ICC profile is missing.");
}
// Process the rows in parallel chnks, the converter itself is thread safe.
// Process the rows in parallel chunks, the converter itself is thread safe.
source.Mutate(o => o.ProcessPixelRowsAsVector4(
row =>
{
@ -64,7 +64,7 @@ internal static class ColorProfileConverterExtensionsPixelCompatible
for (int i = 0; i < rgbSpan.Length; i++)
{
Vector3 rgb = rgbSpan[i].AsVector3Unsafe();
Unsafe.As<Vector4, Vector3>(ref Unsafe.Add(ref rowRef, i)) = rgb;
Unsafe.As<Vector4, Vector3>(ref Unsafe.Add(ref rowRef, (uint)i)) = rgb;
}
},
PixelConversionModifiers.Scale));

65
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -230,12 +230,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore
this.InitializeFrame(previousFrameControl, currentFrameControl.Value, image, previousFrame, out currentFrame);
if (!this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
{
// TODO: Rework this. We need to preserve metadata
// metadata.IccProfile = null;
}
this.currentStream.Position += 4;
this.ReadScanlines(
chunk.Length - 4,
@ -243,7 +237,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore
pngMetadata,
this.ReadNextFrameDataChunk,
currentFrameControl.Value,
iccProfile,
cancellationToken);
// if current frame dispose is restore to previous, then from future frame's perspective, it never happened
@ -268,19 +261,12 @@ internal sealed class PngDecoderCore : ImageDecoderCore
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
}
if (!this.Options.TryGetIccProfileForColorConversion(metadata.IccProfile, out IccProfile? iccProfile))
{
// TODO: Rework this. We need to preserve metadata
// metadata.IccProfile = null;
}
this.ReadScanlines(
chunk.Length,
image.Frames.RootFrame,
pngMetadata,
this.ReadNextDataChunk,
currentFrameControl.Value,
iccProfile,
cancellationToken);
if (pngMetadata.AnimateRootFrame)
{
@ -764,7 +750,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="pngMetadata">The png metadata</param>
/// <param name="getData">A delegate to get more data from the inner stream for <see cref="ZlibInflateStream"/>.</param>
/// <param name="frameControl">The frame control</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private void ReadScanlines<TPixel>(
int chunkLength,
@ -772,7 +757,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore
PngMetadata pngMetadata,
Func<int> getData,
in FrameControl frameControl,
IccProfile? iccProfile,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -786,11 +770,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore
if (this.header.InterlaceMethod is PngInterlaceMode.Adam7)
{
this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, iccProfile, cancellationToken);
this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, cancellationToken);
}
else
{
this.DecodePixelData(frameControl, dataStream, image, pngMetadata, iccProfile, cancellationToken);
this.DecodePixelData(frameControl, dataStream, image, pngMetadata, cancellationToken);
}
}
@ -802,14 +786,12 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="imageFrame">The image frame to decode to.</param>
/// <param name="pngMetadata">The png metadata</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">The CancellationToken</param>
private void DecodePixelData<TPixel>(
FrameControl frameControl,
DeflateStream compressedStream,
ImageFrame<TPixel> imageFrame,
PngMetadata pngMetadata,
IccProfile? iccProfile,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -876,7 +858,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
break;
}
this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer, iccProfile);
this.ProcessDefilteredScanline(frameControl, currentRow, scanSpan, imageFrame, pngMetadata, blendRowBuffer);
this.SwapScanlineBuffers();
currentRow++;
}
@ -894,14 +876,12 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="imageFrame">The current image frame.</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private void DecodeInterlacedPixelData<TPixel>(
in FrameControl frameControl,
DeflateStream compressedStream,
ImageFrame<TPixel> imageFrame,
PngMetadata pngMetadata,
IccProfile? iccProfile,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -992,7 +972,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore
rowSpan,
pngMetadata,
blendRowBuffer,
iccProfile,
pixelOffset: Adam7.FirstColumn[pass],
increment: Adam7.ColumnIncrement[pass]);
@ -1031,15 +1010,13 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="pixels">The image</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="blendRowBuffer">A span used to temporarily hold the decoded row pixel data for alpha blending.</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
private void ProcessDefilteredScanline<TPixel>(
in FrameControl frameControl,
int currentRow,
ReadOnlySpan<byte> scanline,
ImageFrame<TPixel> pixels,
PngMetadata pngMetadata,
Span<TPixel> blendRowBuffer,
IccProfile? iccProfile)
Span<TPixel> blendRowBuffer)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> destination = pixels.PixelBuffer.DangerousGetRowSpan(currentRow);
@ -1073,8 +1050,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
in frameControl,
scanlineSpan,
rowSpan,
pngMetadata.TransparentColor,
iccProfile);
pngMetadata.TransparentColor);
break;
@ -1085,8 +1061,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
scanlineSpan,
rowSpan,
(uint)this.bytesPerPixel,
(uint)this.bytesPerSample,
iccProfile);
(uint)this.bytesPerSample);
break;
@ -1095,8 +1070,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
in frameControl,
scanlineSpan,
rowSpan,
pngMetadata.ColorTable,
iccProfile);
pngMetadata.ColorTable);
break;
@ -1109,8 +1083,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
rowSpan,
this.bytesPerPixel,
this.bytesPerSample,
pngMetadata.TransparentColor,
iccProfile);
pngMetadata.TransparentColor);
break;
@ -1122,8 +1095,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
scanlineSpan,
rowSpan,
this.bytesPerPixel,
this.bytesPerSample,
iccProfile);
this.bytesPerSample);
break;
}
@ -1134,8 +1106,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore
PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
blender.Blend<TPixel>(this.configuration, destination, destination, rowSpan, 1F);
}
// TODO: Here is where we would perform ICC color conversion if needed over the 'destination' span.
}
finally
{
@ -1152,7 +1122,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <param name="destination">The current image row.</param>
/// <param name="pngMetadata">The png metadata.</param>
/// <param name="blendRowBuffer">A span used to temporarily hold the decoded row pixel data for alpha blending.</param>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param>
/// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
private void ProcessInterlacedDefilteredScanline<TPixel>(
@ -1161,7 +1130,6 @@ internal sealed class PngDecoderCore : ImageDecoderCore
Span<TPixel> destination,
PngMetadata pngMetadata,
Span<TPixel> blendRowBuffer,
IccProfile? iccProfile,
int pixelOffset = 0,
int increment = 1)
where TPixel : unmanaged, IPixel<TPixel>
@ -1196,8 +1164,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
rowSpan,
(uint)pixelOffset,
(uint)increment,
pngMetadata.TransparentColor,
iccProfile);
pngMetadata.TransparentColor);
break;
@ -1210,8 +1177,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
(uint)pixelOffset,
(uint)increment,
(uint)this.bytesPerPixel,
(uint)this.bytesPerSample,
iccProfile);
(uint)this.bytesPerSample);
break;
@ -1222,8 +1188,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
rowSpan,
(uint)pixelOffset,
(uint)increment,
pngMetadata.ColorTable,
iccProfile);
pngMetadata.ColorTable);
break;
@ -1238,8 +1203,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
(uint)increment,
this.bytesPerPixel,
this.bytesPerSample,
pngMetadata.TransparentColor,
iccProfile);
pngMetadata.TransparentColor);
break;
@ -1253,8 +1217,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
(uint)pixelOffset,
(uint)increment,
this.bytesPerPixel,
this.bytesPerSample,
iccProfile);
this.bytesPerSample);
break;
}

162
src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

@ -1,15 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png;
@ -25,8 +20,7 @@ internal static class PngScanlineProcessor
in FrameControl frameControl,
ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan,
Color? transparentColor,
IccProfile? iccProfile)
Color? transparentColor)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedGrayscaleScanline(
bitDepth,
@ -35,8 +29,7 @@ internal static class PngScanlineProcessor
rowSpan,
0,
1,
transparentColor,
iccProfile);
transparentColor);
public static void ProcessInterlacedGrayscaleScanline<TPixel>(
int bitDepth,
@ -45,11 +38,9 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan,
uint pixelOffset,
uint increment,
Color? transparentColor,
IccProfile? iccProfile)
Color? transparentColor)
where TPixel : unmanaged, IPixel<TPixel>
{
// FIXME-icc
uint offset = pixelOffset + frameControl.XOffset;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -107,8 +98,7 @@ internal static class PngScanlineProcessor
ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan,
uint bytesPerPixel,
uint bytesPerSample,
IccProfile? iccProfile)
uint bytesPerSample)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedGrayscaleWithAlphaScanline(
bitDepth,
@ -118,8 +108,7 @@ internal static class PngScanlineProcessor
0,
1,
bytesPerPixel,
bytesPerSample,
iccProfile);
bytesPerSample);
public static void ProcessInterlacedGrayscaleWithAlphaScanline<TPixel>(
int bitDepth,
@ -129,11 +118,9 @@ internal static class PngScanlineProcessor
uint pixelOffset,
uint increment,
uint bytesPerPixel,
uint bytesPerSample,
IccProfile? iccProfile)
uint bytesPerSample)
where TPixel : unmanaged, IPixel<TPixel>
{
// FIXME-icc
uint offset = pixelOffset + frameControl.XOffset;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -166,8 +153,7 @@ internal static class PngScanlineProcessor
in FrameControl frameControl,
ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan,
ReadOnlyMemory<Color>? palette,
IccProfile? iccProfile)
ReadOnlyMemory<Color>? palette)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedPaletteScanline(
frameControl,
@ -175,8 +161,7 @@ internal static class PngScanlineProcessor
rowSpan,
0,
1,
palette,
iccProfile);
palette);
public static void ProcessInterlacedPaletteScanline<TPixel>(
in FrameControl frameControl,
@ -184,11 +169,9 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan,
uint pixelOffset,
uint increment,
ReadOnlyMemory<Color>? palette,
IccProfile? iccProfile)
ReadOnlyMemory<Color>? palette)
where TPixel : unmanaged, IPixel<TPixel>
{
// FIXME-icc
if (palette is null)
{
PngThrowHelper.ThrowMissingPalette();
@ -215,8 +198,7 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan,
int bytesPerPixel,
int bytesPerSample,
Color? transparentColor,
IccProfile? iccProfile)
Color? transparentColor)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedRgbScanline(
configuration,
@ -228,8 +210,7 @@ internal static class PngScanlineProcessor
1,
bytesPerPixel,
bytesPerSample,
transparentColor,
iccProfile);
transparentColor);
public static void ProcessInterlacedRgbScanline<TPixel>(
Configuration configuration,
@ -241,11 +222,9 @@ internal static class PngScanlineProcessor
uint increment,
int bytesPerPixel,
int bytesPerSample,
Color? transparentColor,
IccProfile? iccProfile)
Color? transparentColor)
where TPixel : unmanaged, IPixel<TPixel>
{
// FIXME-icc
uint offset = pixelOffset + frameControl.XOffset;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -323,8 +302,7 @@ internal static class PngScanlineProcessor
ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan,
int bytesPerPixel,
int bytesPerSample,
IccProfile? iccProfile)
int bytesPerSample)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedRgbaScanline(
configuration,
@ -335,8 +313,7 @@ internal static class PngScanlineProcessor
0,
1,
bytesPerPixel,
bytesPerSample,
iccProfile);
bytesPerSample);
public static void ProcessInterlacedRgbaScanline<TPixel>(
Configuration configuration,
@ -347,104 +324,43 @@ internal static class PngScanlineProcessor
uint pixelOffset,
uint increment,
int bytesPerPixel,
int bytesPerSample,
IccProfile? iccProfile)
int bytesPerSample)
where TPixel : unmanaged, IPixel<TPixel>
{
uint offset = pixelOffset + frameControl.XOffset;
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
if (iccProfile != null)
if (bitDepth == 16)
{
ColorConversionOptions options = new()
{
SourceIccProfile = iccProfile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
MemoryAllocator = configuration.MemoryAllocator,
};
ColorProfileConverter converter = new(options);
using IMemoryOwner<Rgb> rgbBuffer = configuration.MemoryAllocator.Allocate<Rgb>((int)(frameControl.XMax - offset));
Span<Rgb> rgbPacked = rgbBuffer.Memory.Span;
ref Rgb rgbPackedRef = ref MemoryMarshal.GetReference(rgbPacked);
using IMemoryOwner<float> alphaBuffer = configuration.MemoryAllocator.Allocate<float>((int)(frameControl.XMax - offset));
Span<float> alphaPacked = alphaBuffer.Memory.Span;
ref float alphaPackedRef = ref MemoryMarshal.GetReference(alphaPacked);
if (bitDepth == 16)
{
int o = 0;
for (int i = 0; i < rgbPacked.Length; o += bytesPerPixel, i++)
{
ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample));
Unsafe.Add(ref rgbPackedRef, i) = new Rgb(r / (float)ushort.MaxValue, g / (float)ushort.MaxValue, b / (float)ushort.MaxValue);
Unsafe.Add(ref alphaPackedRef, i) = a / (float)ushort.MaxValue;
}
}
else
{
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
int o = 0;
for (int i = 0; i < rgbPacked.Length; o += bytesPerPixel, i++)
{
byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o);
byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample)));
Unsafe.Add(ref rgbPackedRef, i) = new Rgb(r / (float)byte.MaxValue, g / (float)byte.MaxValue, b / (float)byte.MaxValue);
Unsafe.Add(ref alphaPackedRef, i) = a / (float)byte.MaxValue;
}
}
converter.Convert<Rgb, Rgb>(rgbPacked, rgbPacked);
int idx = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, idx++)
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
Rgb rgb = Unsafe.Add(ref rgbPackedRef, idx);
Vector4 rgba = rgb.ToScaledVector4(Unsafe.Add(ref alphaPackedRef, idx));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromScaledVector4(rgba);
ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(new Rgba64(r, g, b, a));
}
}
else if (pixelOffset == 0 && increment == 1)
{
PixelOperations<TPixel>.Instance.FromRgba32Bytes(
configuration,
scanlineSpan[..(int)(frameControl.Width * bytesPerPixel)],
rowSpan.Slice((int)frameControl.XOffset, (int)frameControl.Width),
(int)frameControl.Width);
}
else
{
if (bitDepth == 16)
{
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(new Rgba64(r, g, b, a));
}
}
else if (pixelOffset == 0 && increment == 1)
{
PixelOperations<TPixel>.Instance.FromRgba32Bytes(
configuration,
scanlineSpan[..(int)(frameControl.Width * bytesPerPixel)],
rowSpan.Slice((int)frameControl.XOffset, (int)frameControl.Width),
(int)frameControl.Width);
}
else
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o);
byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample)));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(new Rgba32(r, g, b, a));
}
byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o);
byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample)));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(new Rgba32(r, g, b, a));
}
}
}

Loading…
Cancel
Save