Browse Source

Remove allocation when upscaling.

pull/613/head
James Jackson-South 8 years ago
parent
commit
1d8c2398ca
  1. 296
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 5
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

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

@ -341,25 +341,36 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
/// <summary> /// <summary>
/// Converts a byte array to a new array where each value in the original array is represented by the specified number of bits. /// Reads the least significant bits from the byte pair with the others set to 0.
/// </summary>
/// <param name="buffer">The source buffer</param>
/// <param name="offset">THe offset</param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ReadByteLittleEndian(ReadOnlySpan<byte> buffer, int offset)
{
return (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF));
}
/// <summary>
/// Attempts to convert a byte array to a new array where each value in the original array is represented by the
/// specified number of bits.
/// </summary> /// </summary>
/// <param name="source">The bytes to convert from. Cannot be empty.</param> /// <param name="source">The bytes to convert from. Cannot be empty.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bits">The number of bits per value.</param> /// <param name="bits">The number of bits per value.</param>
/// <param name="buffer">The new array.</param>
/// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns> /// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns>
/// <exception cref="System.ArgumentException"><paramref name="bits"/> is less than or equals than zero.</exception> private bool TryScaleUpTo8BitArray(ReadOnlySpan<byte> source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer)
private static ReadOnlySpan<byte> ToArrayByBitsLength(ReadOnlySpan<byte> source, int bytesPerScanline, int bits)
{ {
Guard.MustBeGreaterThan(source.Length, 0, nameof(source));
Guard.MustBeGreaterThan(bits, 0, nameof(bits));
if (bits >= 8) if (bits >= 8)
{ {
return source; buffer = null;
return false;
} }
// TODO: We should be pooling this. buffer = this.MemoryAllocator.AllocateCleanManagedByteBuffer(bytesPerScanline * 8 / bits);
byte[] result = new byte[bytesPerScanline * 8 / bits]; byte[] result = buffer.Array;
int mask = 0xFF >> (8 - bits); int mask = 0xFF >> (8 - bits);
int resultOffset = 0; int resultOffset = 0;
@ -369,26 +380,13 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int shift = 0; shift < 8; shift += bits) for (int shift = 0; shift < 8; shift += bits)
{ {
int colorIndex = (b >> (8 - bits - shift)) & mask; int colorIndex = (b >> (8 - bits - shift)) & mask;
result[resultOffset] = (byte)colorIndex; result[resultOffset] = (byte)colorIndex;
resultOffset++; resultOffset++;
} }
} }
return result; return true;
}
/// <summary>
/// Reads the least significant bits from the byte pair with the others set to 0.
/// </summary>
/// <param name="buffer">The source buffer</param>
/// <param name="offset">THe offset</param>
/// <returns>The <see cref="int"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ReadByteLittleEndian(ReadOnlySpan<byte> buffer, int offset)
{
return (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF));
} }
/// <summary> /// <summary>
@ -669,11 +667,16 @@ namespace SixLabors.ImageSharp.Formats.Png
private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, ImageFrame<TPixel> pixels) private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, ImageFrame<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var color = default(TPixel); TPixel pixel = default;
Span<TPixel> rowSpan = pixels.GetPixelRowSpan(this.currentRow); Span<TPixel> rowSpan = pixels.GetPixelRowSpan(this.currentRow);
// Trim the first marker byte from the buffer // Trim the first marker byte from the buffer
ReadOnlySpan<byte> scanlineBuffer = defilteredScanline.Slice(1, defilteredScanline.Length - 1); ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType) switch (this.pngColorType)
{ {
@ -681,9 +684,6 @@ namespace SixLabors.ImageSharp.Formats.Png
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
ReadOnlySpan<byte> scanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
if (!this.hasTrans) if (!this.hasTrans)
{ {
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
@ -691,12 +691,12 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgb48 rgb48 = default; Rgb48 rgb48 = default;
for (int x = 0, o = 0; x < this.header.Width; x++, o += 2) for (int x = 0, o = 0; x < this.header.Width; x++, o += 2)
{ {
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgb48.R = luminance; rgb48.R = luminance;
rgb48.G = luminance; rgb48.G = luminance;
rgb48.B = luminance; rgb48.B = luminance;
color.PackFromRgb48(rgb48); pixel.PackFromRgb48(rgb48);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -705,12 +705,12 @@ namespace SixLabors.ImageSharp.Formats.Png
var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue);
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
byte luminance = (byte)(scanline[x] * factor); byte luminance = (byte)(scanlineSpan[x] * factor);
rgba32.R = luminance; rgba32.R = luminance;
rgba32.G = luminance; rgba32.G = luminance;
rgba32.B = luminance; rgba32.B = luminance;
color.PackFromRgba32(rgba32); pixel.PackFromRgba32(rgba32);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
} }
@ -721,14 +721,14 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
for (int x = 0, o = 0; x < this.header.Width; x++, o += 2) for (int x = 0, o = 0; x < this.header.Width; x++, o += 2)
{ {
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgba64.R = luminance; rgba64.R = luminance;
rgba64.G = luminance; rgba64.G = luminance;
rgba64.B = luminance; rgba64.B = luminance;
rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue; rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue;
color.PackFromRgba64(rgba64); pixel.PackFromRgba64(rgba64);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -736,14 +736,14 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba32 rgba32 = default; Rgba32 rgba32 = default;
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
byte luminance = (byte)(scanline[x] * factor); byte luminance = (byte)(scanlineSpan[x] * factor);
rgba32.R = luminance; rgba32.R = luminance;
rgba32.G = luminance; rgba32.G = luminance;
rgba32.B = luminance; rgba32.B = luminance;
rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue; rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue;
color.PackFromRgba32(rgba32); pixel.PackFromRgba32(rgba32);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
} }
@ -757,15 +757,15 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
for (int x = 0, o = 0; x < this.header.Width; x++, o += 4) for (int x = 0, o = 0; x < this.header.Width; x++, o += 4)
{ {
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 2, 2)); ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
rgba64.R = luminance; rgba64.R = luminance;
rgba64.G = luminance; rgba64.G = luminance;
rgba64.B = luminance; rgba64.B = luminance;
rgba64.A = alpha; rgba64.A = alpha;
color.PackFromRgba64(rgba64); pixel.PackFromRgba64(rgba64);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -774,16 +774,16 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
int offset = x * this.bytesPerPixel; int offset = x * this.bytesPerPixel;
byte luminance = scanlineBuffer[offset]; byte luminance = scanlineSpan[offset];
byte alpha = scanlineBuffer[offset + this.bytesPerSample]; byte alpha = scanlineSpan[offset + this.bytesPerSample];
rgba32.R = luminance; rgba32.R = luminance;
rgba32.G = luminance; rgba32.G = luminance;
rgba32.B = luminance; rgba32.B = luminance;
rgba32.A = alpha; rgba32.A = alpha;
color.PackFromRgba32(rgba32); pixel.PackFromRgba32(rgba32);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
@ -791,7 +791,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Palette: case PngColorType.Palette:
this.ProcessScanlineFromPalette(scanlineBuffer, rowSpan); this.ProcessScanlineFromPalette(scanlineSpan, rowSpan);
break; break;
@ -804,16 +804,16 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgb48 rgb48 = default; Rgb48 rgb48 = default;
for (int x = 0, o = 0; x < this.header.Width; x++, o += 6) for (int x = 0, o = 0; x < this.header.Width; x++, o += 6)
{ {
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 2, 2)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 4, 2)); rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
color.PackFromRgb48(rgb48); pixel.PackFromRgb48(rgb48);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
{ {
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(scanlineBuffer, rowSpan, this.header.Width); PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(scanlineSpan, rowSpan, this.header.Width);
} }
} }
else else
@ -824,20 +824,20 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
for (int x = 0, o = 0; x < this.header.Width; x++, o += 6) for (int x = 0, o = 0; x < this.header.Width; x++, o += 6)
{ {
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 2, 2)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 4, 2)); rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
rgba64.Rgb = rgb48; rgba64.Rgb = rgb48;
rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue; rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
color.PackFromRgba64(rgba64); pixel.PackFromRgba64(rgba64);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
{ {
ReadOnlySpan<Rgb24> rgb24Span = MemoryMarshal.Cast<byte, Rgb24>(scanlineBuffer); ReadOnlySpan<Rgb24> rgb24Span = MemoryMarshal.Cast<byte, Rgb24>(scanlineSpan);
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
ref readonly Rgb24 rgb24 = ref rgb24Span[x]; ref readonly Rgb24 rgb24 = ref rgb24Span[x];
@ -845,8 +845,8 @@ namespace SixLabors.ImageSharp.Formats.Png
rgba32.Rgb = rgb24; rgba32.Rgb = rgb24;
rgba32.A = rgb24.Equals(this.rgb24Trans) ? byte.MinValue : byte.MaxValue; rgba32.A = rgb24.Equals(this.rgb24Trans) ? byte.MinValue : byte.MaxValue;
color.PackFromRgba32(rgba32); pixel.PackFromRgba32(rgba32);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
} }
@ -860,21 +860,23 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
for (int x = 0, o = 0; x < this.header.Width; x++, o += 8) for (int x = 0, o = 0; x < this.header.Width; x++, o += 8)
{ {
rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 2, 2)); rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 4, 2)); rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 6, 2)); rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 6, 2));
color.PackFromRgba64(rgba64); pixel.PackFromRgba64(rgba64);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
{ {
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(scanlineBuffer, rowSpan, this.header.Width); PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(scanlineSpan, rowSpan, this.header.Width);
} }
break; break;
} }
buffer?.Dispose();
} }
/// <summary> /// <summary>
@ -888,10 +890,15 @@ namespace SixLabors.ImageSharp.Formats.Png
private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, Span<TPixel> rowSpan, int pixelOffset = 0, int increment = 1) private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, Span<TPixel> rowSpan, int pixelOffset = 0, int increment = 1)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var color = default(TPixel); TPixel pixel = default;
// Trim the first marker byte from the buffer // Trim the first marker byte from the buffer
ReadOnlySpan<byte> scanlineBuffer = defilteredScanline.Slice(1, defilteredScanline.Length - 1); ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType) switch (this.pngColorType)
{ {
@ -899,9 +906,6 @@ namespace SixLabors.ImageSharp.Formats.Png
int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
ReadOnlySpan<byte> scanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth);
if (!this.hasTrans) if (!this.hasTrans)
{ {
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
@ -909,13 +913,13 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgb48 rgb48 = default; Rgb48 rgb48 = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2)
{ {
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgb48.R = luminance; rgb48.R = luminance;
rgb48.G = luminance; rgb48.G = luminance;
rgb48.B = luminance; rgb48.B = luminance;
color.PackFromRgb48(rgb48); pixel.PackFromRgb48(rgb48);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -924,13 +928,13 @@ namespace SixLabors.ImageSharp.Formats.Png
var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{ {
byte luminance = (byte)(scanline[o] * factor); byte luminance = (byte)(scanlineSpan[o] * factor);
rgba32.R = luminance; rgba32.R = luminance;
rgba32.G = luminance; rgba32.G = luminance;
rgba32.B = luminance; rgba32.B = luminance;
color.PackFromRgba32(rgba32); pixel.PackFromRgba32(rgba32);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
} }
@ -941,14 +945,14 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2)
{ {
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgba64.R = luminance; rgba64.R = luminance;
rgba64.G = luminance; rgba64.G = luminance;
rgba64.B = luminance; rgba64.B = luminance;
rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue; rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue;
color.PackFromRgba64(rgba64); pixel.PackFromRgba64(rgba64);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -956,14 +960,14 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba32 rgba32 = default; Rgba32 rgba32 = default;
for (int x = pixelOffset; x < this.header.Width; x += increment) for (int x = pixelOffset; x < this.header.Width; x += increment)
{ {
byte luminance = (byte)(scanline[x] * factor); byte luminance = (byte)(scanlineSpan[x] * factor);
rgba32.R = luminance; rgba32.R = luminance;
rgba32.G = luminance; rgba32.G = luminance;
rgba32.B = luminance; rgba32.B = luminance;
rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue; rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue;
color.PackFromRgba32(rgba32); pixel.PackFromRgba32(rgba32);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
} }
@ -977,15 +981,15 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4)
{ {
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 2, 2)); ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
rgba64.R = luminance; rgba64.R = luminance;
rgba64.G = luminance; rgba64.G = luminance;
rgba64.B = luminance; rgba64.B = luminance;
rgba64.A = alpha; rgba64.A = alpha;
color.PackFromRgba64(rgba64); pixel.PackFromRgba64(rgba64);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -994,15 +998,15 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int x = pixelOffset; x < this.header.Width; x += increment) for (int x = pixelOffset; x < this.header.Width; x += increment)
{ {
int offset = x * this.bytesPerPixel; int offset = x * this.bytesPerPixel;
byte luminance = scanlineBuffer[offset]; byte luminance = scanlineSpan[offset];
byte alpha = scanlineBuffer[offset + this.bytesPerSample]; byte alpha = scanlineSpan[offset + this.bytesPerSample];
rgba32.R = luminance; rgba32.R = luminance;
rgba32.G = luminance; rgba32.G = luminance;
rgba32.B = luminance; rgba32.B = luminance;
rgba32.A = alpha; rgba32.A = alpha;
color.PackFromRgba32(rgba32); pixel.PackFromRgba32(rgba32);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
@ -1010,8 +1014,7 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.Palette: case PngColorType.Palette:
ReadOnlySpan<byte> newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); Span<Rgb24> palettePixels = MemoryMarshal.Cast<byte, Rgb24>(this.palette);
Span<Rgb24> pal = MemoryMarshal.Cast<byte, Rgb24>(this.palette);
if (this.paletteAlpha?.Length > 0) if (this.paletteAlpha?.Length > 0)
{ {
@ -1020,12 +1023,12 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba32 rgba = default; Rgba32 rgba = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{ {
int index = newScanline[o]; int index = scanlineSpan[o];
rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue;
rgba.Rgb = pal[index]; rgba.Rgb = palettePixels[index];
color.PackFromRgba32(rgba); pixel.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -1033,11 +1036,11 @@ namespace SixLabors.ImageSharp.Formats.Png
var rgba = new Rgba32(0, 0, 0, byte.MaxValue); var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++)
{ {
int index = newScanline[o]; int index = scanlineSpan[o];
rgba.Rgb = pal[index]; rgba.Rgb = palettePixels[index];
color.PackFromRgba32(rgba); pixel.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
@ -1053,15 +1056,15 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6)
{ {
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 2, 2)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 4, 2)); rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
rgba64.Rgb = rgb48; rgba64.Rgb = rgb48;
rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue; rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
color.PackFromRgba64(rgba64); pixel.PackFromRgba64(rgba64);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -1069,11 +1072,11 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgb48 rgb48 = default; Rgb48 rgb48 = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6)
{ {
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 2, 2)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 4, 2)); rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
color.PackFromRgb48(rgb48); pixel.PackFromRgb48(rgb48);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
} }
@ -1084,13 +1087,13 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba32 rgba = default; Rgba32 rgba = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{ {
rgba.R = scanlineBuffer[o]; rgba.R = scanlineSpan[o];
rgba.G = scanlineBuffer[o + this.bytesPerSample]; rgba.G = scanlineSpan[o + this.bytesPerSample];
rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)];
rgba.A = this.rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; rgba.A = this.rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue;
color.PackFromRgba32(rgba); pixel.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -1098,12 +1101,12 @@ namespace SixLabors.ImageSharp.Formats.Png
var rgba = new Rgba32(0, 0, 0, byte.MaxValue); var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{ {
rgba.R = scanlineBuffer[o]; rgba.R = scanlineSpan[o];
rgba.G = scanlineBuffer[o + this.bytesPerSample]; rgba.G = scanlineSpan[o + this.bytesPerSample];
rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)];
color.PackFromRgba32(rgba); pixel.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
} }
@ -1117,12 +1120,12 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba64 rgba64 = default; Rgba64 rgba64 = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 8) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 8)
{ {
rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o, 2)); rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 2, 2)); rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 4, 2)); rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2));
rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineBuffer.Slice(o + 6, 2)); rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 6, 2));
color.PackFromRgba64(rgba64); pixel.PackFromRgba64(rgba64);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
else else
@ -1130,18 +1133,20 @@ namespace SixLabors.ImageSharp.Formats.Png
Rgba32 rgba = default; Rgba32 rgba = default;
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel)
{ {
rgba.R = scanlineBuffer[o]; rgba.R = scanlineSpan[o];
rgba.G = scanlineBuffer[o + this.bytesPerSample]; rgba.G = scanlineSpan[o + this.bytesPerSample];
rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)];
rgba.A = scanlineBuffer[o + (3 * this.bytesPerSample)]; rgba.A = scanlineSpan[o + (3 * this.bytesPerSample)];
color.PackFromRgba32(rgba); pixel.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = pixel;
} }
} }
break; break;
} }
buffer?.Dispose();
} }
/// <summary> /// <summary>
@ -1193,13 +1198,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Processes a scanline that uses a palette /// Processes a scanline that uses a palette
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam> /// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam>
/// <param name="defilteredScanline">The scanline</param> /// <param name="scanline">The defiltered scanline</param>
/// <param name="row">Thecurrent output image row</param> /// <param name="row">Thecurrent output image row</param>
private void ProcessScanlineFromPalette<TPixel>(ReadOnlySpan<byte> defilteredScanline, Span<TPixel> row) private void ProcessScanlineFromPalette<TPixel>(ReadOnlySpan<byte> scanline, Span<TPixel> row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
ReadOnlySpan<byte> newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); ReadOnlySpan<Rgb24> palettePixels = MemoryMarshal.Cast<byte, Rgb24>(this.palette);
ReadOnlySpan<Rgb24> pal = MemoryMarshal.Cast<byte, Rgb24>(this.palette);
var color = default(TPixel); var color = default(TPixel);
if (this.paletteAlpha?.Length > 0) if (this.paletteAlpha?.Length > 0)
@ -1210,9 +1214,9 @@ namespace SixLabors.ImageSharp.Formats.Png
// channel and we should try to read it. // channel and we should try to read it.
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
int index = newScanline[x]; int index = scanline[x];
rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue;
rgba.Rgb = pal[index]; rgba.Rgb = palettePixels[index];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
row[x] = color; row[x] = color;
@ -1220,13 +1224,13 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
else else
{ {
// TODO: We should have PackFromRgb24.
var rgba = new Rgba32(0, 0, 0, byte.MaxValue); var rgba = new Rgba32(0, 0, 0, byte.MaxValue);
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
{ {
int index = newScanline[x]; int index = scanline[x];
rgba.Rgb = pal[index]; rgba.Rgb = palettePixels[index];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
row[x] = color; row[x] = color;

5
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -44,14 +44,14 @@ namespace SixLabors.ImageSharp.Tests
// IDAT // IDAT
0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00,
0x00, 0x04, 0x00, 0x01, 0x00, 0x04, 0x00, 0x01,
// IDAT CRC // IDAT CRC
0x5C, 0xCD, 0xFF, 0x69, 0x5C, 0xCD, 0xFF, 0x69,
// IEND // IEND
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
0x4E, 0x44, 0x4E, 0x44,
// IEND CRC // IEND CRC
0xAE, 0x42, 0x60, 0x82 0xAE, 0x42, 0x60, 0x82
}; };
@ -76,7 +76,6 @@ namespace SixLabors.ImageSharp.Tests
TestImages.Png.VimImage2, TestImages.Png.VimImage2,
}; };
public static readonly string[] TestImages48Bpp = public static readonly string[] TestImages48Bpp =
{ {
TestImages.Png.Rgb48Bpp, TestImages.Png.Rgb48Bpp,

Loading…
Cancel
Save