diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
index 77bc9f7a05..7e5a9fa6b8 100644
--- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
@@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Gets the filter method.
///
- PngFilterMethod FilterMethod { get; }
+ PngFilterMethod? FilterMethod { get; }
///
/// Gets the compression level 1-9.
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index ef2f226556..1bfba68ed6 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -92,6 +92,11 @@ namespace SixLabors.ImageSharp.Formats.Png
///
private readonly bool ignoreMetadata;
+ ///
+ /// Used the manage memory allocations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
///
/// The stream to decode from.
///
@@ -200,12 +205,11 @@ namespace SixLabors.ImageSharp.Formats.Png
public PngDecoderCore(Configuration configuration, IPngDecoderOptions options)
{
this.configuration = configuration ?? Configuration.Default;
+ this.memoryAllocator = this.configuration.MemoryAllocator;
this.textEncoding = options.TextEncoding ?? PngConstants.DefaultEncoding;
this.ignoreMetadata = options.IgnoreMetadata;
}
- private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator;
-
///
/// Decodes the stream to the image.
///
@@ -391,19 +395,19 @@ namespace SixLabors.ImageSharp.Formats.Png
return false;
}
- buffer = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
- byte[] result = buffer.Array;
+ buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
+ ref byte sourceRef = ref MemoryMarshal.GetReference(source);
+ ref byte resultRef = ref buffer.Array[0];
int mask = 0xFF >> (8 - bits);
int resultOffset = 0;
- for (int i = 0; i < bytesPerScanline - 1; i++)
+ for (int i = 0; i < bytesPerScanline; i++)
{
- byte b = source[i];
+ byte b = Unsafe.Add(ref sourceRef, i);
for (int shift = 0; shift < 8; shift += bits)
{
int colorIndex = (b >> (8 - bits - shift)) & mask;
- result[resultOffset] = (byte)colorIndex;
-
+ Unsafe.Add(ref resultRef, resultOffset) = (byte)colorIndex;
resultOffset++;
}
}
@@ -470,7 +474,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8;
}
- this.previousScanline = this.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
+ this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
}
@@ -612,7 +616,7 @@ namespace SixLabors.ImageSharp.Formats.Png
throw new ImageFormatException("Unknown filter type.");
}
- this.ProcessDefilteredScanline(this.scanline.Array, image);
+ this.ProcessDefilteredScanline(scanlineSpan, image);
this.SwapBuffers();
this.currentRow++;
@@ -718,14 +722,13 @@ namespace SixLabors.ImageSharp.Formats.Png
private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels)
where TPixel : struct, IPixel
{
- TPixel pixel = default;
Span rowSpan = pixels.GetPixelRowSpan(this.currentRow);
// Trim the first marker byte from the buffer
ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
- ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer)
+ ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer)
? buffer.GetSpan()
: trimmed;
@@ -733,196 +736,60 @@ namespace SixLabors.ImageSharp.Formats.Png
{
case PngColorType.Grayscale:
- int factor = 255 / (ImageMaths.GetColorCountForBitDepth(this.header.BitDepth) - 1);
-
- if (!this.hasTrans)
- {
- if (this.header.BitDepth == 16)
- {
- Rgb48 rgb48 = default;
- for (int x = 0, o = 0; x < this.header.Width; x++, o += 2)
- {
- ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgb48.R = luminance;
- rgb48.G = luminance;
- rgb48.B = luminance;
- pixel.PackFromRgb48(rgb48);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method.
- var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue);
- for (int x = 0; x < this.header.Width; x++)
- {
- byte luminance = (byte)(scanlineSpan[x] * factor);
- rgba32.R = luminance;
- rgba32.G = luminance;
- rgba32.B = luminance;
- pixel.PackFromRgba32(rgba32);
- rowSpan[x] = pixel;
- }
- }
- }
- else
- {
- if (this.header.BitDepth == 16)
- {
- Rgba64 rgba64 = default;
- for (int x = 0, o = 0; x < this.header.Width; x++, o += 2)
- {
- ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgba64.R = luminance;
- rgba64.G = luminance;
- rgba64.B = luminance;
- rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue;
-
- pixel.PackFromRgba64(rgba64);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- Rgba32 rgba32 = default;
- for (int x = 0; x < this.header.Width; x++)
- {
- byte luminance = (byte)(scanlineSpan[x] * factor);
- rgba32.R = luminance;
- rgba32.G = luminance;
- rgba32.B = luminance;
- rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue;
-
- pixel.PackFromRgba32(rgba32);
- rowSpan[x] = pixel;
- }
- }
- }
+ PngScanlineProcessor.ProcessGrayscaleScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ this.hasTrans,
+ this.luminance16Trans,
+ this.luminanceTrans);
break;
case PngColorType.GrayscaleWithAlpha:
- if (this.header.BitDepth == 16)
- {
- Rgba64 rgba64 = default;
- for (int x = 0, o = 0; x < this.header.Width; x++, o += 4)
- {
- ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
- rgba64.R = luminance;
- rgba64.G = luminance;
- rgba64.B = luminance;
- rgba64.A = alpha;
-
- pixel.PackFromRgba64(rgba64);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- Rgba32 rgba32 = default;
- for (int x = 0; x < this.header.Width; x++)
- {
- int offset = x * this.bytesPerPixel;
- byte luminance = scanlineSpan[offset];
- byte alpha = scanlineSpan[offset + this.bytesPerSample];
-
- rgba32.R = luminance;
- rgba32.G = luminance;
- rgba32.B = luminance;
- rgba32.A = alpha;
-
- pixel.PackFromRgba32(rgba32);
- rowSpan[x] = pixel;
- }
- }
+ PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ this.bytesPerPixel,
+ this.bytesPerSample);
break;
case PngColorType.Palette:
- this.ProcessScanlineFromPalette(scanlineSpan, rowSpan);
+ PngScanlineProcessor.ProcessPaletteScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ this.palette,
+ this.paletteAlpha);
break;
case PngColorType.Rgb:
- if (!this.hasTrans)
- {
- if (this.header.BitDepth == 16)
- {
- Rgb48 rgb48 = default;
- for (int x = 0, o = 0; x < this.header.Width; x++, o += 6)
- {
- rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
- rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
- pixel.PackFromRgb48(rgb48);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- PixelOperations.Instance.PackFromRgb24Bytes(scanlineSpan, rowSpan, this.header.Width);
- }
- }
- else
- {
- if (this.header.BitDepth == 16)
- {
- Rgb48 rgb48 = default;
- Rgba64 rgba64 = default;
- for (int x = 0, o = 0; x < this.header.Width; x++, o += 6)
- {
- rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
- rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
-
- rgba64.Rgb = rgb48;
- rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
-
- pixel.PackFromRgba64(rgba64);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan);
- for (int x = 0; x < this.header.Width; x++)
- {
- ref readonly Rgb24 rgb24 = ref rgb24Span[x];
- Rgba32 rgba32 = default;
- rgba32.Rgb = rgb24;
- rgba32.A = rgb24.Equals(this.rgb24Trans) ? byte.MinValue : byte.MaxValue;
-
- pixel.PackFromRgba32(rgba32);
- rowSpan[x] = pixel;
- }
- }
- }
+ PngScanlineProcessor.ProcessRgbScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ this.bytesPerPixel,
+ this.bytesPerSample,
+ this.hasTrans,
+ this.rgb48Trans,
+ this.rgb24Trans);
break;
case PngColorType.RgbWithAlpha:
- if (this.header.BitDepth == 16)
- {
- Rgba64 rgba64 = default;
- for (int x = 0, o = 0; x < this.header.Width; x++, o += 8)
- {
- rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
- rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
- rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 6, 2));
- pixel.PackFromRgba64(rgba64);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- PixelOperations.Instance.PackFromRgba32Bytes(scanlineSpan, rowSpan, this.header.Width);
- }
+ PngScanlineProcessor.ProcessRgbaScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ this.bytesPerPixel,
+ this.bytesPerSample);
break;
}
@@ -941,8 +808,6 @@ namespace SixLabors.ImageSharp.Formats.Png
private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, int pixelOffset = 0, int increment = 1)
where TPixel : struct, IPixel
{
- TPixel pixel = default;
-
// Trim the first marker byte from the buffer
ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
@@ -955,244 +820,70 @@ namespace SixLabors.ImageSharp.Formats.Png
{
case PngColorType.Grayscale:
- int factor = 255 / (ImageMaths.GetColorCountForBitDepth(this.header.BitDepth) - 1);
-
- if (!this.hasTrans)
- {
- if (this.header.BitDepth == 16)
- {
- Rgb48 rgb48 = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2)
- {
- ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgb48.R = luminance;
- rgb48.G = luminance;
- rgb48.B = luminance;
-
- pixel.PackFromRgb48(rgb48);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method.
- var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue);
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
- {
- byte luminance = (byte)(scanlineSpan[o] * factor);
- rgba32.R = luminance;
- rgba32.G = luminance;
- rgba32.B = luminance;
-
- pixel.PackFromRgba32(rgba32);
- rowSpan[x] = pixel;
- }
- }
- }
- else
- {
- if (this.header.BitDepth == 16)
- {
- Rgba64 rgba64 = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2)
- {
- ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgba64.R = luminance;
- rgba64.G = luminance;
- rgba64.B = luminance;
- rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue;
-
- pixel.PackFromRgba64(rgba64);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- Rgba32 rgba32 = default;
- for (int x = pixelOffset; x < this.header.Width; x += increment)
- {
- byte luminance = (byte)(scanlineSpan[x] * factor);
- rgba32.R = luminance;
- rgba32.G = luminance;
- rgba32.B = luminance;
- rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue;
-
- pixel.PackFromRgba32(rgba32);
- rowSpan[x] = pixel;
- }
- }
- }
+ PngScanlineProcessor.ProcessInterlacedGrayscaleScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ pixelOffset,
+ increment,
+ this.hasTrans,
+ this.luminance16Trans,
+ this.luminanceTrans);
break;
case PngColorType.GrayscaleWithAlpha:
- if (this.header.BitDepth == 16)
- {
- Rgba64 rgba64 = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4)
- {
- ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
- rgba64.R = luminance;
- rgba64.G = luminance;
- rgba64.B = luminance;
- rgba64.A = alpha;
-
- pixel.PackFromRgba64(rgba64);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- Rgba32 rgba32 = default;
- for (int x = pixelOffset; x < this.header.Width; x += increment)
- {
- int offset = x * this.bytesPerPixel;
- byte luminance = scanlineSpan[offset];
- byte alpha = scanlineSpan[offset + this.bytesPerSample];
- rgba32.R = luminance;
- rgba32.G = luminance;
- rgba32.B = luminance;
- rgba32.A = alpha;
-
- pixel.PackFromRgba32(rgba32);
- rowSpan[x] = pixel;
- }
- }
+ PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ pixelOffset,
+ increment,
+ this.bytesPerPixel,
+ this.bytesPerSample);
break;
case PngColorType.Palette:
- Span palettePixels = MemoryMarshal.Cast(this.palette);
-
- if (this.paletteAlpha?.Length > 0)
- {
- // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
- // channel and we should try to read it.
- Rgba32 rgba = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
- {
- int index = scanlineSpan[o];
- rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue;
- rgba.Rgb = palettePixels[index];
-
- pixel.PackFromRgba32(rgba);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
- {
- int index = scanlineSpan[o];
- rgba.Rgb = palettePixels[index];
-
- pixel.PackFromRgba32(rgba);
- rowSpan[x] = pixel;
- }
- }
+ PngScanlineProcessor.ProcessInterlacedPaletteScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ pixelOffset,
+ increment,
+ this.palette,
+ this.paletteAlpha);
break;
case PngColorType.Rgb:
- if (this.header.BitDepth == 16)
- {
- if (this.hasTrans)
- {
- Rgb48 rgb48 = default;
- Rgba64 rgba64 = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6)
- {
- rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
- rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
-
- rgba64.Rgb = rgb48;
- rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
-
- pixel.PackFromRgba64(rgba64);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- Rgb48 rgb48 = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6)
- {
- rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
- rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
- pixel.PackFromRgb48(rgb48);
- rowSpan[x] = pixel;
- }
- }
- }
- else
- {
- if (this.hasTrans)
- {
- Rgba32 rgba = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
- {
- rgba.R = scanlineSpan[o];
- rgba.G = scanlineSpan[o + this.bytesPerSample];
- rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)];
- rgba.A = this.rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue;
-
- pixel.PackFromRgba32(rgba);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
- {
- rgba.R = scanlineSpan[o];
- rgba.G = scanlineSpan[o + this.bytesPerSample];
- rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)];
-
- pixel.PackFromRgba32(rgba);
- rowSpan[x] = pixel;
- }
- }
- }
+ PngScanlineProcessor.ProcessInterlacedRgbScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ pixelOffset,
+ increment,
+ this.bytesPerPixel,
+ this.bytesPerSample,
+ this.hasTrans,
+ this.rgb48Trans,
+ this.rgb24Trans);
break;
case PngColorType.RgbWithAlpha:
- if (this.header.BitDepth == 16)
- {
- Rgba64 rgba64 = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 8)
- {
- rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
- rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
- rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 6, 2));
- pixel.PackFromRgba64(rgba64);
- rowSpan[x] = pixel;
- }
- }
- else
- {
- Rgba32 rgba = default;
- for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
- {
- rgba.R = scanlineSpan[o];
- rgba.G = scanlineSpan[o + this.bytesPerSample];
- rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)];
- rgba.A = scanlineSpan[o + (3 * this.bytesPerSample)];
-
- pixel.PackFromRgba32(rgba);
- rowSpan[x] = pixel;
- }
- }
+ PngScanlineProcessor.ProcessInterlacedRgbaScanline(
+ this.header,
+ scanlineSpan,
+ rowSpan,
+ pixelOffset,
+ increment,
+ this.bytesPerPixel,
+ this.bytesPerSample);
break;
}
@@ -1245,50 +936,6 @@ namespace SixLabors.ImageSharp.Formats.Png
}
}
- ///
- /// Processes a scanline that uses a palette
- ///
- /// The type of pixel we are expanding to
- /// The defiltered scanline
- /// The current output image row
- private void ProcessScanlineFromPalette(ReadOnlySpan scanline, Span row)
- where TPixel : struct, IPixel
- {
- ReadOnlySpan palettePixels = MemoryMarshal.Cast(this.palette);
- var color = default(TPixel);
-
- if (this.paletteAlpha?.Length > 0)
- {
- Rgba32 rgba = default;
-
- // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
- // channel and we should try to read it.
- for (int x = 0; x < this.header.Width; x++)
- {
- int index = scanline[x];
- rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue;
- rgba.Rgb = palettePixels[index];
-
- color.PackFromRgba32(rgba);
- row[x] = color;
- }
- }
- else
- {
- // TODO: We should have PackFromRgb24.
- var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
- for (int x = 0; x < this.header.Width; x++)
- {
- int index = scanline[x];
-
- rgba.Rgb = palettePixels[index];
-
- color.PackFromRgba32(rgba);
- row[x] = color;
- }
- }
- }
-
///
/// Reads a header chunk from the data.
///
diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs
index f47a6518f0..96e97a305f 100644
--- a/src/ImageSharp/Formats/Png/PngEncoder.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoder.cs
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Gets or sets the filter method.
///
- public PngFilterMethod FilterMethod { get; set; } = PngFilterMethod.Paeth;
+ public PngFilterMethod? FilterMethod { get; set; }
///
/// Gets or sets the compression level 1-9.
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index e4d2fc510d..603162fbe8 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -3,7 +3,11 @@
using System;
using System.Buffers.Binary;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
@@ -21,6 +25,21 @@ namespace SixLabors.ImageSharp.Formats.Png
///
internal sealed class PngEncoderCore : IDisposable
{
+ ///
+ /// The dictionary of available color types.
+ ///
+ private static readonly Dictionary ColorTypes = new Dictionary()
+ {
+ [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 },
+ [PngColorType.Rgb] = new byte[] { 8, 16 },
+ [PngColorType.Palette] = new byte[] { 1, 2, 4, 8 },
+ [PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 },
+ [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 }
+ };
+
+ ///
+ /// Used the manage memory allocations.
+ ///
private readonly MemoryAllocator memoryAllocator;
///
@@ -158,7 +177,11 @@ namespace SixLabors.ImageSharp.Formats.Png
this.memoryAllocator = memoryAllocator;
this.pngBitDepth = options.BitDepth;
this.pngColorType = options.ColorType;
- this.pngFilterMethod = options.FilterMethod;
+
+ // Specification recommends default filter method None for paletted images and Paeth for others.
+ this.pngFilterMethod = options.FilterMethod ?? (options.ColorType.Equals(PngColorType.Palette)
+ ? PngFilterMethod.None
+ : PngFilterMethod.Paeth);
this.compressionLevel = options.CompressionLevel;
this.gamma = options.Gamma;
this.quantizer = options.Quantizer;
@@ -189,28 +212,37 @@ namespace SixLabors.ImageSharp.Formats.Png
this.pngBitDepth = this.pngBitDepth ?? pngMetaData.BitDepth;
this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
+ // Ensure we are not allowing impossible combinations.
+ if (!ColorTypes.ContainsKey(this.pngColorType.Value))
+ {
+ throw new NotSupportedException("Color type is not supported or not valid.");
+ }
+
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame quantized = null;
- ReadOnlySpan quantizedPixelsSpan = default;
if (this.pngColorType == PngColorType.Palette)
{
- byte bits;
+ byte bits = (byte)this.pngBitDepth;
+ if (!ColorTypes[this.pngColorType.Value].Contains(bits))
+ {
+ throw new NotSupportedException("Bit depth is not supported or not valid.");
+ }
// Use the metadata to determine what quantization depth to use if no quantizer has been set.
if (this.quantizer == null)
{
- bits = (byte)Math.Min(8u, (short)this.pngBitDepth);
- int colorSize = ImageMaths.GetColorCountForBitDepth(bits);
- this.quantizer = new WuQuantizer(colorSize);
+ this.quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits));
}
// Create quantized frame returning the palette and set the bit depth.
quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame);
- quantizedPixelsSpan = quantized.GetPixelSpan();
- bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
+ byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
+ bits = Math.Max(bits, quantizedBits);
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
+ // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not
+ // be within the acceptable range.
if (bits == 3)
{
bits = 4;
@@ -224,7 +256,11 @@ namespace SixLabors.ImageSharp.Formats.Png
}
else
{
- this.bitDepth = (byte)(this.use16Bit ? 16 : 8);
+ this.bitDepth = (byte)this.pngBitDepth;
+ if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth))
+ {
+ throw new NotSupportedException("Bit depth is not supported or not valid.");
+ }
}
this.bytesPerPixel = this.CalculateBytesPerPixel();
@@ -249,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePhysicalChunk(stream, metaData);
this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, metaData);
- this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
+ this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
this.WriteEndChunk(stream);
stream.Flush();
@@ -280,32 +316,55 @@ namespace SixLabors.ImageSharp.Formats.Png
const float RX = .2126F;
const float GX = .7152F;
const float BX = .0722F;
+
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
Span rawScanlineSpan = this.rawScanline.GetSpan();
+ ref byte rawScanlineSpanRef = ref MemoryMarshal.GetReference(rawScanlineSpan);
if (this.pngColorType.Equals(PngColorType.Grayscale))
{
- // TODO: Realistically we should support 1, 2, 4, 8, and 16 bit grayscale images.
- // we currently do the other types via palette. Maybe RC as I don't understand how the data is packed yet
- // for 1, 2, and 4 bit grayscale images.
+ // TODO: Research and add support for grayscale plus tRNS
if (this.use16Bit)
{
// 16 bit grayscale
Rgb48 rgb = default;
for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2)
{
- rowSpan[x].ToRgb48(ref rgb);
+ Unsafe.Add(ref rowSpanRef, x).ToRgb48(ref rgb);
ushort luminance = (ushort)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B));
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance);
}
}
else
{
- // 8 bit grayscale
- Rgb24 rgb = default;
- for (int x = 0; x < rowSpan.Length; x++)
+ if (this.bitDepth == 8)
{
- rowSpan[x].ToRgb24(ref rgb);
- rawScanlineSpan[x] = (byte)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B));
+ // 8 bit grayscale
+ Rgb24 rgb = default;
+ for (int x = 0; x < rowSpan.Length; x++)
+ {
+ Unsafe.Add(ref rowSpanRef, x).ToRgb24(ref rgb);
+ Unsafe.Add(ref rawScanlineSpanRef, x) = (byte)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B));
+ }
+ }
+ else
+ {
+ // 1, 2, and 4 bit grayscale
+ using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer(rowSpan.Length, AllocationOptions.Clean))
+ {
+ int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(this.bitDepth) - 1);
+ Span tempSpan = temp.GetSpan();
+ ref byte tempSpanRef = ref MemoryMarshal.GetReference(tempSpan);
+
+ Rgb24 rgb = default;
+ for (int x = 0; x < rowSpan.Length; x++)
+ {
+ Unsafe.Add(ref rowSpanRef, x).ToRgb24(ref rgb);
+ float luminance = ((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B)) / scaleFactor;
+ Unsafe.Add(ref tempSpanRef, x) = (byte)luminance;
+ this.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth);
+ }
+ }
}
}
}
@@ -317,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba = default;
for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 4)
{
- rowSpan[x].ToRgba64(ref rgba);
+ Unsafe.Add(ref rowSpanRef, x).ToRgba64(ref rgba);
ushort luminance = (ushort)((RX * rgba.R) + (GX * rgba.G) + (BX * rgba.B));
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.A);
@@ -329,9 +388,9 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba32 rgba = default;
for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2)
{
- rowSpan[x].ToRgba32(ref rgba);
- rawScanlineSpan[o] = (byte)((RX * rgba.R) + (GX * rgba.G) + (BX * rgba.B));
- rawScanlineSpan[o + 1] = rgba.A;
+ Unsafe.Add(ref rowSpanRef, x).ToRgba32(ref rgba);
+ Unsafe.Add(ref rawScanlineSpanRef, o) = (byte)((RX * rgba.R) + (GX * rgba.G) + (BX * rgba.B));
+ Unsafe.Add(ref rawScanlineSpanRef, o + 1) = rgba.A;
}
}
}
@@ -367,9 +426,10 @@ namespace SixLabors.ImageSharp.Formats.Png
{
// 16 bit Rgba
Rgba64 rgba = default;
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8)
{
- rowSpan[x].ToRgba64(ref rgba);
+ Unsafe.Add(ref rowSpanRef, x).ToRgba64(ref rgba);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgba.R);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.G);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgba.B);
@@ -383,9 +443,10 @@ namespace SixLabors.ImageSharp.Formats.Png
{
// 16 bit Rgb
Rgb48 rgb = default;
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6)
{
- rowSpan[x].ToRgb48(ref rgb);
+ Unsafe.Add(ref rowSpanRef, x).ToRgb48(ref rgb);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgb.R);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgb.G);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgb.B);
@@ -402,18 +463,25 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// The pixel format.
/// The row span.
- /// The span of quantized pixels. Can be null.
+ /// The quantized pixels. Can be null.
/// The row.
/// The
- private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, ReadOnlySpan quantizedPixelsSpan, int row)
+ private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row)
where TPixel : struct, IPixel
{
switch (this.pngColorType)
{
case PngColorType.Palette:
- int stride = this.rawScanline.Length();
- quantizedPixelsSpan.Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan());
+ if (this.bitDepth < 8)
+ {
+ this.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.rawScanline.GetSpan(), this.bitDepth);
+ }
+ else
+ {
+ int stride = this.rawScanline.Length();
+ quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan());
+ }
break;
case PngColorType.Grayscale:
@@ -569,8 +637,8 @@ namespace SixLabors.ImageSharp.Formats.Png
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength))
{
- Span colorTableSpan = colorTable.GetSpan();
- Span alphaTableSpan = alphaTable.GetSpan();
+ ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan());
+ ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan());
Span quantizedSpan = quantized.GetPixelSpan();
for (int i = 0; i < paletteLength; i++)
@@ -582,9 +650,9 @@ namespace SixLabors.ImageSharp.Formats.Png
byte alpha = rgba.A;
- colorTableSpan[offset] = rgba.R;
- colorTableSpan[offset + 1] = rgba.G;
- colorTableSpan[offset + 2] = rgba.B;
+ Unsafe.Add(ref colorTableRef, offset) = rgba.R;
+ Unsafe.Add(ref colorTableRef, offset + 1) = rgba.G;
+ Unsafe.Add(ref colorTableRef, offset + 2) = rgba.B;
if (alpha > this.threshold)
{
@@ -592,7 +660,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
anyAlpha = anyAlpha || alpha < byte.MaxValue;
- alphaTableSpan[i] = alpha;
+ Unsafe.Add(ref alphaTableRef, i) = alpha;
}
}
@@ -696,9 +764,9 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// The pixel format.
/// The image.
- /// The span of quantized pixel data. Can be null.
+ /// The quantized pixel data. Can be null.
/// The stream.
- private void WriteDataChunks(ImageFrame pixels, ReadOnlySpan quantizedPixelsSpan, Stream stream)
+ private void WriteDataChunks(ImageFrame pixels, QuantizedFrame quantized, Stream stream)
where TPixel : struct, IPixel
{
this.bytesPerScanline = this.CalculateScanlineLength(this.width);
@@ -750,7 +818,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
for (int y = 0; y < this.height; y++)
{
- IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)pixels.GetPixelRowSpan(y), quantizedPixelsSpan, y);
+ IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)pixels.GetPixelRowSpan(y), quantized, y);
deflateStream.Write(r.Array, 0, resultLength);
IManagedByteBuffer temp = this.rawScanline;
@@ -830,6 +898,47 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(this.buffer, 0, 4); // write the crc
}
+ ///
+ /// Packs the given 8 bit array into and array of depths.
+ ///
+ /// The source span in 8 bits.
+ /// The resultant span in .
+ /// The bit depth.
+ private void ScaleDownFrom8BitArray(ReadOnlySpan source, Span result, int bits)
+ {
+ ref byte sourceRef = ref MemoryMarshal.GetReference(source);
+ ref byte resultRef = ref MemoryMarshal.GetReference(result);
+
+ byte mask = (byte)(0xFF >> (8 - bits));
+ byte shift0 = (byte)(8 - bits);
+ int shift = 8 - bits;
+ int v = 0;
+ int resultOffset = 0;
+
+ for (int i = 0; i < source.Length; i++)
+ {
+ int value = Unsafe.Add(ref sourceRef, i) & mask;
+ v |= value << shift;
+
+ if (shift == 0)
+ {
+ shift = shift0;
+ Unsafe.Add(ref resultRef, resultOffset) = (byte)v;
+ resultOffset++;
+ v = 0;
+ }
+ else
+ {
+ shift -= bits;
+ }
+ }
+
+ if (shift != shift0)
+ {
+ Unsafe.Add(ref resultRef, resultOffset) = (byte)v;
+ }
+ }
+
///
/// Calculates the scanline length.
///
diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
new file mode 100644
index 0000000000..6c81ba76c2
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
@@ -0,0 +1,600 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Png
+{
+ ///
+ /// Provides methods to allow the decoding of raw scanlines to image rows of different pixel formats.
+ ///
+ internal static class PngScanlineProcessor
+ {
+ public static void ProcessGrayscaleScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ bool hasTrans,
+ ushort luminance16Trans,
+ byte luminanceTrans)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+ int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(header.BitDepth) - 1);
+
+ if (!hasTrans)
+ {
+ if (header.BitDepth == 16)
+ {
+ Rgb48 rgb48 = default;
+ for (int x = 0, o = 0; x < header.Width; x++, o += 2)
+ {
+ ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
+ rgb48.R = luminance;
+ rgb48.G = luminance;
+ rgb48.B = luminance;
+
+ pixel.PackFromRgb48(rgb48);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method.
+ var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue);
+ for (int x = 0; x < header.Width; x++)
+ {
+ byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor);
+ rgba32.R = luminance;
+ rgba32.G = luminance;
+ rgba32.B = luminance;
+
+ pixel.PackFromRgba32(rgba32);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+
+ return;
+ }
+
+ if (header.BitDepth == 16)
+ {
+ Rgba64 rgba64 = default;
+ for (int x = 0, o = 0; x < header.Width; x++, o += 2)
+ {
+ ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
+ rgba64.R = luminance;
+ rgba64.G = luminance;
+ rgba64.B = luminance;
+ rgba64.A = luminance.Equals(luminance16Trans) ? ushort.MinValue : ushort.MaxValue;
+
+ pixel.PackFromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ Rgba32 rgba32 = default;
+ for (int x = 0; x < header.Width; x++)
+ {
+ byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor);
+ rgba32.R = luminance;
+ rgba32.G = luminance;
+ rgba32.B = luminance;
+ rgba32.A = luminance.Equals(luminanceTrans) ? byte.MinValue : byte.MaxValue;
+
+ pixel.PackFromRgba32(rgba32);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+
+ public static void ProcessInterlacedGrayscaleScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ int pixelOffset,
+ int increment,
+ bool hasTrans,
+ ushort luminance16Trans,
+ byte luminanceTrans)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+ int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(header.BitDepth) - 1);
+
+ if (!hasTrans)
+ {
+ if (header.BitDepth == 16)
+ {
+ Rgb48 rgb48 = default;
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2)
+ {
+ ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
+ rgb48.R = luminance;
+ rgb48.G = luminance;
+ rgb48.B = luminance;
+
+ pixel.PackFromRgb48(rgb48);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method.
+ var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue);
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++)
+ {
+ byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor);
+ rgba32.R = luminance;
+ rgba32.G = luminance;
+ rgba32.B = luminance;
+
+ pixel.PackFromRgba32(rgba32);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+
+ return;
+ }
+
+ if (header.BitDepth == 16)
+ {
+ Rgba64 rgba64 = default;
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2)
+ {
+ ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
+ rgba64.R = luminance;
+ rgba64.G = luminance;
+ rgba64.B = luminance;
+ rgba64.A = luminance.Equals(luminance16Trans) ? ushort.MinValue : ushort.MaxValue;
+
+ pixel.PackFromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ Rgba32 rgba32 = default;
+ for (int x = pixelOffset; x < header.Width; x += increment)
+ {
+ byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor);
+ rgba32.R = luminance;
+ rgba32.G = luminance;
+ rgba32.B = luminance;
+ rgba32.A = luminance.Equals(luminanceTrans) ? byte.MinValue : byte.MaxValue;
+
+ pixel.PackFromRgba32(rgba32);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+
+ public static void ProcessGrayscaleWithAlphaScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ int bytesPerPixel,
+ int bytesPerSample)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+
+ if (header.BitDepth == 16)
+ {
+ Rgba64 rgba64 = default;
+ for (int x = 0, o = 0; x < header.Width; x++, o += 4)
+ {
+ ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
+ ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
+ rgba64.R = luminance;
+ rgba64.G = luminance;
+ rgba64.B = luminance;
+ rgba64.A = alpha;
+
+ pixel.PackFromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ Rgba32 rgba32 = default;
+ int bps = bytesPerSample;
+ for (int x = 0; x < header.Width; x++)
+ {
+ int offset = x * bytesPerPixel;
+ byte luminance = Unsafe.Add(ref scanlineSpanRef, offset);
+ byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bps);
+
+ rgba32.R = luminance;
+ rgba32.G = luminance;
+ rgba32.B = luminance;
+ rgba32.A = alpha;
+
+ pixel.PackFromRgba32(rgba32);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+
+ public static void ProcessInterlacedGrayscaleWithAlphaScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ int pixelOffset,
+ int increment,
+ int bytesPerPixel,
+ int bytesPerSample)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+
+ if (header.BitDepth == 16)
+ {
+ Rgba64 rgba64 = default;
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 4)
+ {
+ ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
+ ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
+ rgba64.R = luminance;
+ rgba64.G = luminance;
+ rgba64.B = luminance;
+ rgba64.A = alpha;
+
+ pixel.PackFromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ Rgba32 rgba32 = default;
+ for (int x = pixelOffset; x < header.Width; x += increment)
+ {
+ int offset = x * bytesPerPixel;
+ byte luminance = Unsafe.Add(ref scanlineSpanRef, offset);
+ byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample);
+ rgba32.R = luminance;
+ rgba32.G = luminance;
+ rgba32.B = luminance;
+ rgba32.A = alpha;
+
+ pixel.PackFromRgba32(rgba32);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+
+ public static void ProcessPaletteScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ ReadOnlySpan palette,
+ byte[] paletteAlpha)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+ ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette);
+ ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels);
+
+ if (paletteAlpha?.Length > 0)
+ {
+ // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
+ // channel and we should try to read it.
+ Rgba32 rgba = default;
+ ref byte paletteAlphaRef = ref paletteAlpha[0];
+
+ for (int x = 0; x < header.Width; x++)
+ {
+ int index = Unsafe.Add(ref scanlineSpanRef, x);
+ rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index);
+ rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue;
+
+ pixel.PackFromRgba32(rgba);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ // TODO: We should have PackFromRgb24.
+ var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
+ for (int x = 0; x < header.Width; x++)
+ {
+ int index = Unsafe.Add(ref scanlineSpanRef, x);
+ rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index);
+
+ pixel.PackFromRgba32(rgba);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+
+ public static void ProcessInterlacedPaletteScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ int pixelOffset,
+ int increment,
+ ReadOnlySpan palette,
+ byte[] paletteAlpha)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+ ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette);
+ ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels);
+
+ if (paletteAlpha?.Length > 0)
+ {
+ // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
+ // channel and we should try to read it.
+ Rgba32 rgba = default;
+ ref byte paletteAlphaRef = ref paletteAlpha[0];
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++)
+ {
+ int index = Unsafe.Add(ref scanlineSpanRef, o);
+ rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue;
+ rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index);
+
+ pixel.PackFromRgba32(rgba);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++)
+ {
+ int index = Unsafe.Add(ref scanlineSpanRef, o);
+ rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index);
+
+ pixel.PackFromRgba32(rgba);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+
+ public static void ProcessRgbScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ int bytesPerPixel,
+ int bytesPerSample,
+ bool hasTrans,
+ Rgb48 rgb48Trans,
+ Rgb24 rgb24Trans)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+
+ if (!hasTrans)
+ {
+ if (header.BitDepth == 16)
+ {
+ Rgb48 rgb48 = default;
+ for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel)
+ {
+ rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
+ rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
+ rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
+
+ pixel.PackFromRgb48(rgb48);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ PixelOperations.Instance.PackFromRgb24Bytes(scanlineSpan, rowSpan, header.Width);
+ }
+
+ return;
+ }
+
+ if (header.BitDepth == 16)
+ {
+ Rgb48 rgb48 = default;
+ Rgba64 rgba64 = default;
+ for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel)
+ {
+ rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
+ rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
+ rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
+
+ rgba64.Rgb = rgb48;
+ rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
+
+ pixel.PackFromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan);
+ ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span);
+ for (int x = 0; x < header.Width; x++)
+ {
+ ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x);
+ Rgba32 rgba32 = default;
+ rgba32.Rgb = rgb24;
+ rgba32.A = rgb24.Equals(rgb24Trans) ? byte.MinValue : byte.MaxValue;
+
+ pixel.PackFromRgba32(rgba32);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+
+ public static void ProcessInterlacedRgbScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ int pixelOffset,
+ int increment,
+ int bytesPerPixel,
+ int bytesPerSample,
+ bool hasTrans,
+ Rgb48 rgb48Trans,
+ Rgb24 rgb24Trans)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+
+ if (header.BitDepth == 16)
+ {
+ if (hasTrans)
+ {
+ Rgb48 rgb48 = default;
+ Rgba64 rgba64 = default;
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel)
+ {
+ rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
+ rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
+ rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
+
+ rgba64.Rgb = rgb48;
+ rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
+
+ pixel.PackFromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ Rgb48 rgb48 = default;
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel)
+ {
+ rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
+ rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
+ rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
+
+ pixel.PackFromRgb48(rgb48);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+
+ return;
+ }
+
+ if (hasTrans)
+ {
+ Rgba32 rgba = default;
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel)
+ {
+ rgba.R = Unsafe.Add(ref scanlineSpanRef, o);
+ rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample);
+ rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample));
+ rgba.A = rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue;
+
+ pixel.PackFromRgba32(rgba);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel)
+ {
+ rgba.R = Unsafe.Add(ref scanlineSpanRef, o);
+ rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample);
+ rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample));
+
+ pixel.PackFromRgba32(rgba);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+
+ public static void ProcessRgbaScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ int bytesPerPixel,
+ int bytesPerSample)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+
+ if (header.BitDepth == 16)
+ {
+ Rgba64 rgba64 = default;
+ for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel)
+ {
+ rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
+ rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
+ rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
+ rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample));
+
+ pixel.PackFromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ PixelOperations.Instance.PackFromRgba32Bytes(scanlineSpan, rowSpan, header.Width);
+ }
+ }
+
+ public static void ProcessInterlacedRgbaScanline(
+ in PngHeader header,
+ ReadOnlySpan scanlineSpan,
+ Span rowSpan,
+ int pixelOffset,
+ int increment,
+ int bytesPerPixel,
+ int bytesPerSample)
+ where TPixel : struct, IPixel
+ {
+ TPixel pixel = default;
+ ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
+
+ if (header.BitDepth == 16)
+ {
+ Rgba64 rgba64 = default;
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel)
+ {
+ rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
+ rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
+ rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
+ rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample));
+
+ pixel.PackFromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ else
+ {
+ Rgba32 rgba = default;
+ for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel)
+ {
+ rgba.R = Unsafe.Add(ref scanlineSpanRef, o);
+ rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample);
+ rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample));
+ rgba.A = Unsafe.Add(ref scanlineSpanRef, o + (3 * bytesPerSample));
+
+ pixel.PackFromRgba32(rgba);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
index 2e3bb2c419..38862ef446 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
@@ -3,7 +3,7 @@
using System;
using System.Buffers;
-
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@@ -57,8 +57,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Gets the pixels of this .
///
/// The
+ [MethodImpl(InliningOptions.ShortMethod)]
public Span GetPixelSpan() => this.pixels.GetSpan();
+ ///
+ /// Gets the representation of the pixels as a of contiguous memory
+ /// at row beginning from the the first pixel on that row.
+ ///
+ /// The row.
+ /// The
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public Span GetRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width);
+
///
public void Dispose()
{
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index 0508ac8c26..5d328db361 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@@ -19,10 +18,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public class PngEncoderTests
{
- // This is bull. Failing online for no good reason.
- // The images are an exact match. Maybe the submodule isn't updating?
- private const float ToleranceThresholdForPaletteEncoder = 1.3F / 100;
-
public static readonly TheoryData PngBitDepthFiles =
new TheoryData
{
@@ -137,19 +132,32 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
[Theory]
- [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb)]
- [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha)]
- [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha)]
- public void WorksWithBitDepth16(TestImageProvider provider, PngColorType pngColorType)
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
+ [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)]
+ public void WorksWithAllBitDepths(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : struct, IPixel
{
TestPngEncoderCore(
provider,
pngColorType,
PngFilterMethod.Adaptive,
- PngBitDepth.Bit16,
+ pngBitDepth,
appendPngColorType: true,
- appendPixelType: true);
+ appendPixelType: true,
+ appendPngBitDepth: true);
}
[Theory]
@@ -166,87 +174,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
appendPaletteSize: true);
}
- private static bool HasAlpha(PngColorType pngColorType) =>
- pngColorType == PngColorType.GrayscaleWithAlpha || pngColorType == PngColorType.RgbWithAlpha;
-
- private static void TestPngEncoderCore(
- TestImageProvider provider,
- PngColorType pngColorType,
- PngFilterMethod pngFilterMethod,
- PngBitDepth bitDepth,
- int compressionLevel = 6,
- int paletteSize = 255,
- bool appendPngColorType = false,
- bool appendPngFilterMethod = false,
- bool appendPixelType = false,
- bool appendCompressionLevel = false,
- bool appendPaletteSize = false)
- where TPixel : struct, IPixel
- {
- using (Image image = provider.GetImage())
- {
- if (!HasAlpha(pngColorType))
- {
- image.Mutate(c => c.MakeOpaque());
- }
-
- var encoder = new PngEncoder
- {
- ColorType = pngColorType,
- FilterMethod = pngFilterMethod,
- CompressionLevel = compressionLevel,
- BitDepth = bitDepth,
- Quantizer = new WuQuantizer(paletteSize)
- };
-
- string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
- string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty;
- string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty;
- string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty;
- string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}";
- //string referenceInfo = $"{pngColorTypeInfo}";
-
- // Does DebugSave & load reference CompareToReferenceInput():
- string actualOutputFile = ((ITestImageProvider)provider).Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType);
-
- if (TestEnvironment.IsMono)
- {
- // There are bugs in mono's System.Drawing implementation, reference decoders are not always reliable!
- return;
- }
-
- IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
- string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType, true);
-
- bool referenceOutputFileExists = File.Exists(referenceOutputFile);
-
- using (var actualImage = Image.Load(actualOutputFile, referenceDecoder))
- {
- // TODO: Do we still need the reference output files?
- Image referenceImage = referenceOutputFileExists
- ? Image.Load(referenceOutputFile, referenceDecoder)
- : image;
-
- float paletteToleranceHack = 80f / paletteSize;
- paletteToleranceHack *= paletteToleranceHack;
- ImageComparer comparer = pngColorType == PngColorType.Palette
- ? ImageComparer.Tolerant(ToleranceThresholdForPaletteEncoder * paletteToleranceHack)
- : ImageComparer.Exact;
- try
- {
- comparer.VerifySimilarity(referenceImage, actualImage);
- }
- finally
- {
- if (referenceOutputFileExists)
- {
- referenceImage.Dispose();
- }
- }
- }
- }
- }
-
[Theory]
[WithBlankImages(1, 1, PixelTypes.Rgba32)]
public void WritesFileMarker(TestImageProvider provider)
@@ -321,5 +248,54 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
}
+
+ private static void TestPngEncoderCore(
+ TestImageProvider provider,
+ PngColorType pngColorType,
+ PngFilterMethod pngFilterMethod,
+ PngBitDepth bitDepth,
+ int compressionLevel = 6,
+ int paletteSize = 255,
+ bool appendPngColorType = false,
+ bool appendPngFilterMethod = false,
+ bool appendPixelType = false,
+ bool appendCompressionLevel = false,
+ bool appendPaletteSize = false,
+ bool appendPngBitDepth = false)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ {
+ var encoder = new PngEncoder
+ {
+ ColorType = pngColorType,
+ FilterMethod = pngFilterMethod,
+ CompressionLevel = compressionLevel,
+ BitDepth = bitDepth,
+ Quantizer = new WuQuantizer(paletteSize)
+ };
+
+ string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
+ string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty;
+ string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty;
+ string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty;
+ string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty;
+ string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}";
+
+ string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType);
+
+ // Compare to the Magick reference decoder.
+ IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
+
+ // We compare using both our decoder and the reference decoder as pixel transformation
+ // occurrs within the encoder itself leaving the input image unaffected.
+ // This means we are benefiting from testing our decoder also.
+ using (var imageSharpImage = Image.Load(actualOutputFile, new PngDecoder()))
+ using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder))
+ {
+ ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage);
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
index 8cfc2472f5..7e942691e9 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
@@ -29,13 +29,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
{
if (magickImage.Depth == 8)
{
- byte[] data = pixels.ToByteArray("RGBA");
-
+ byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
+
PixelOperations.Instance.PackFromRgba32Bytes(data, resultPixels, resultPixels.Length);
}
else if (magickImage.Depth == 16)
{
- ushort[] data = pixels.ToShortArray("RGBA");
+ ushort[] data = pixels.ToShortArray(PixelMapping.RGBA);
Span bytes = MemoryMarshal.Cast(data.AsSpan());
PixelOperations.Instance.PackFromRgba64Bytes(bytes, resultPixels, resultPixels.Length);
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs
index db651886f2..b60439b488 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs
@@ -14,10 +14,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
public class MagickReferenceCodecTests
{
- public MagickReferenceCodecTests(ITestOutputHelper output)
- {
- this.Output = output;
- }
+ public MagickReferenceCodecTests(ITestOutputHelper output) => this.Output = output;
private ITestOutputHelper Output { get; }
@@ -61,6 +58,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
[WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48Bpp)]
[WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppInterlaced)]
[WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppTrans)]
+ [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Gray16Bit)]
public void MagickDecode_16BitDepthImage_IsApproximatelyEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage)
where TPixel : struct, IPixel
{
@@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
// 1020 == 4 * 255 (Equivalent to manhattan distance of 1+1+1+1=4 in Rgba32 space)
var comparer = ImageComparer.TolerantPercentage(1, 1020);
-
+
using (var mImage = Image.Load(path, magickDecoder))
using (var sdImage = Image.Load(path, sdDecoder))
{