diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index ef2f22655..c52f97494 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,12 +395,12 @@ namespace SixLabors.ImageSharp.Formats.Png
return false;
}
- buffer = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
+ buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
byte[] result = buffer.Array;
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];
for (int shift = 0; shift < 8; shift += bits)
@@ -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++;
@@ -725,7 +729,7 @@ namespace SixLabors.ImageSharp.Formats.Png
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;
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index e4d2fc510..5948f350e 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -21,6 +21,9 @@ namespace SixLabors.ImageSharp.Formats.Png
///
internal sealed class PngEncoderCore : IDisposable
{
+ ///
+ /// Used the manage memory allocations.
+ ///
private readonly MemoryAllocator memoryAllocator;
///
@@ -158,7 +161,9 @@ namespace SixLabors.ImageSharp.Formats.Png
this.memoryAllocator = memoryAllocator;
this.pngBitDepth = options.BitDepth;
this.pngColorType = options.ColorType;
- this.pngFilterMethod = options.FilterMethod;
+
+ // Palette compresses better with none and spec recommends it.
+ this.pngFilterMethod = options.ColorType.Equals(PngColorType.Palette) ? PngFilterMethod.None : options.FilterMethod;
this.compressionLevel = options.CompressionLevel;
this.gamma = options.Gamma;
this.quantizer = options.Quantizer;
@@ -192,10 +197,9 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame quantized = null;
- ReadOnlySpan quantizedPixelsSpan = default;
if (this.pngColorType == PngColorType.Palette)
{
- byte bits;
+ byte bits = (byte)Math.Min(8u, (short)this.pngBitDepth);
// Use the metadata to determine what quantization depth to use if no quantizer has been set.
if (this.quantizer == null)
@@ -207,10 +211,10 @@ namespace SixLabors.ImageSharp.Formats.Png
// 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);
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
+ bits = Math.Max(bits, quantizedBits);
if (bits == 3)
{
bits = 4;
@@ -249,7 +253,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();
@@ -402,10 +406,10 @@ 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)
@@ -413,7 +417,14 @@ namespace SixLabors.ImageSharp.Formats.Png
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
+ {
+ quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan());
+ }
break;
case PngColorType.Grayscale:
@@ -696,9 +707,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 +761,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 +841,42 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(this.buffer, 0, 4); // write the crc
}
+ private void ScaleDownFrom8BitArray(ReadOnlySpan source, Span result, int bits)
+ {
+ if (bits >= 8)
+ {
+ return;
+ }
+
+ byte mask = (byte)(0xFF >> (8 - bits));
+ byte shift0 = (byte)(8 - bits);
+ int shift = 8 - bits;
+ int v = 0;
+
+ for (int i = 0, j = 0; j < source.Length; j++)
+ {
+ int value = source[j] & mask;
+ v |= value << shift;
+
+ if (shift == 0)
+ {
+ shift = shift0;
+ result[i] = (byte)v;
+ i++;
+ v = 0;
+ }
+ else
+ {
+ shift -= bits;
+ }
+ }
+
+ if (shift != shift0)
+ {
+ result[0] = (byte)v;
+ }
+ }
+
///
/// Calculates the scanline length.
///
diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
index 2e3bb2c41..61ea342a8 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
@@ -59,6 +59,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The
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
+ public Span GetRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width);
+
///
public void Dispose()
{