Browse Source

Add scale down from 8bit method

This nearly works... Scaling seems correct but getting an unexpected offset.
af/merge-core
James Jackson-South 7 years ago
parent
commit
fcbe2ba289
  1. 18
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 71
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  3. 8
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

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

@ -92,6 +92,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private readonly bool ignoreMetadata;
/// <summary>
/// Used the manage memory allocations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The stream to decode from.
/// </summary>
@ -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;
/// <summary>
/// Decodes the stream to the image.
/// </summary>
@ -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<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)
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer)
? buffer.GetSpan()
: trimmed;

71
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -21,6 +21,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
internal sealed class PngEncoderCore : IDisposable
{
/// <summary>
/// Used the manage memory allocations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
@ -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<TPixel> quantized = null;
ReadOnlySpan<byte> 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<TPixel>().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
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The row span.</param>
/// <param name="quantizedPixelsSpan">The span of quantized pixels. Can be null.</param>
/// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, ReadOnlySpan<byte> quantizedPixelsSpan, int row)
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, QuantizedFrame<TPixel> quantized, int row)
where TPixel : struct, IPixel<TPixel>
{
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
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image.</param>
/// <param name="quantizedPixelsSpan">The span of quantized pixel data. Can be null.</param>
/// <param name="quantized">The quantized pixel data. Can be null.</param>
/// <param name="stream">The stream.</param>
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, ReadOnlySpan<byte> quantizedPixelsSpan, Stream stream)
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, QuantizedFrame<TPixel> quantized, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>)pixels.GetPixelRowSpan(y), quantizedPixelsSpan, y);
IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan<TPixel>)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<byte> source, Span<byte> 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;
}
}
/// <summary>
/// Calculates the scanline length.
/// </summary>

8
src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

@ -59,6 +59,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="Span{T}"/></returns>
public Span<byte> GetPixelSpan() => this.pixels.GetSpan();
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{T}"/></returns>
public Span<byte> GetRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width);
/// <inheritdoc/>
public void Dispose()
{

Loading…
Cancel
Save