Browse Source

Optimize x-bit scanline packing.

pull/712/head
James Jackson-South 8 years ago
parent
commit
7b692fd15a
  1. 2
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  2. 8
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  3. 2
      src/ImageSharp/Formats/Png/PngEncoder.cs
  4. 33
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  5. 4
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

2
src/ImageSharp/Formats/Png/IPngEncoderOptions.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Gets the filter method. /// Gets the filter method.
/// </summary> /// </summary>
PngFilterMethod FilterMethod { get; } PngFilterMethod? FilterMethod { get; }
/// <summary> /// <summary>
/// Gets the compression level 1-9. /// Gets the compression level 1-9.

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

@ -396,18 +396,18 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean); buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
byte[] result = buffer.Array; ref byte sourceRef = ref MemoryMarshal.GetReference(source);
ref byte resultRef = ref buffer.Array[0];
int mask = 0xFF >> (8 - bits); int mask = 0xFF >> (8 - bits);
int resultOffset = 0; int resultOffset = 0;
for (int i = 0; i < bytesPerScanline; 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) 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; Unsafe.Add(ref resultRef, resultOffset) = (byte)colorIndex;
resultOffset++; resultOffset++;
} }
} }

2
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Gets or sets the filter method. /// Gets or sets the filter method.
/// </summary> /// </summary>
public PngFilterMethod FilterMethod { get; set; } = PngFilterMethod.Paeth; public PngFilterMethod? FilterMethod { get; set; }
/// <summary> /// <summary>
/// Gets or sets the compression level 1-9. /// Gets or sets the compression level 1-9.

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

@ -4,6 +4,8 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Filters;
@ -162,8 +164,10 @@ namespace SixLabors.ImageSharp.Formats.Png
this.pngBitDepth = options.BitDepth; this.pngBitDepth = options.BitDepth;
this.pngColorType = options.ColorType; this.pngColorType = options.ColorType;
// Palette compresses better with none and spec recommends it. // Specification recommends default filter method None for paletted images and Paeth for others.
this.pngFilterMethod = options.ColorType.Equals(PngColorType.Palette) ? PngFilterMethod.None : options.FilterMethod; this.pngFilterMethod = options.FilterMethod ?? (options.ColorType.Equals(PngColorType.Palette)
? PngFilterMethod.None
: PngFilterMethod.Paeth);
this.compressionLevel = options.CompressionLevel; this.compressionLevel = options.CompressionLevel;
this.gamma = options.Gamma; this.gamma = options.Gamma;
this.quantizer = options.Quantizer; this.quantizer = options.Quantizer;
@ -212,9 +216,11 @@ namespace SixLabors.ImageSharp.Formats.Png
// Create quantized frame returning the palette and set the bit depth. // Create quantized frame returning the palette and set the bit depth.
quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame); quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame);
byte quantizedBits = (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 // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
bits = Math.Max(bits, quantizedBits); // 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) if (bits == 3)
{ {
bits = 4; bits = 4;
@ -228,6 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
else else
{ {
// TODO: Get the correct bit depth for grayscale images.
this.bitDepth = (byte)(this.use16Bit ? 16 : 8); this.bitDepth = (byte)(this.use16Bit ? 16 : 8);
} }
@ -416,13 +423,13 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
case PngColorType.Palette: case PngColorType.Palette:
int stride = this.rawScanline.Length();
if (this.bitDepth < 8) if (this.bitDepth < 8)
{ {
this.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.rawScanline.GetSpan(), this.bitDepth); this.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.rawScanline.GetSpan(), this.bitDepth);
} }
else else
{ {
int stride = this.rawScanline.Length();
quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan()); quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan());
} }
@ -841,12 +848,16 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(this.buffer, 0, 4); // write the crc stream.Write(this.buffer, 0, 4); // write the crc
} }
/// <summary>
/// Packs the given 8 bit array into and array of <paramref name="bits"/> depths.
/// </summary>
/// <param name="source">The source span in 8 bits.</param>
/// <param name="result">The resultant span in <paramref name="bits"/>.</param>
/// <param name="bits">The bit depth.</param>
private void ScaleDownFrom8BitArray(ReadOnlySpan<byte> source, Span<byte> result, int bits) private void ScaleDownFrom8BitArray(ReadOnlySpan<byte> source, Span<byte> result, int bits)
{ {
if (bits >= 8) ref byte sourceRef = ref MemoryMarshal.GetReference(source);
{ ref byte resultRef = ref MemoryMarshal.GetReference(result);
return;
}
byte mask = (byte)(0xFF >> (8 - bits)); byte mask = (byte)(0xFF >> (8 - bits));
byte shift0 = (byte)(8 - bits); byte shift0 = (byte)(8 - bits);
@ -856,13 +867,13 @@ namespace SixLabors.ImageSharp.Formats.Png
for (int i = 0; i < source.Length; i++) for (int i = 0; i < source.Length; i++)
{ {
int value = source[i] & mask; int value = Unsafe.Add(ref sourceRef, i) & mask;
v |= value << shift; v |= value << shift;
if (shift == 0) if (shift == 0)
{ {
shift = shift0; shift = shift0;
result[resultOffset] = (byte)v; Unsafe.Add(ref resultRef, resultOffset) = (byte)v;
resultOffset++; resultOffset++;
v = 0; v = 0;
} }
@ -874,7 +885,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (shift != shift0) if (shift != shift0)
{ {
result[resultOffset] = (byte)v; Unsafe.Add(ref resultRef, resultOffset) = (byte)v;
} }
} }

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

@ -3,7 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -57,6 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Gets the pixels of this <see cref="QuantizedFrame{TPixel}"/>. /// Gets the pixels of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary> /// </summary>
/// <returns>The <see cref="Span{T}"/></returns> /// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<byte> GetPixelSpan() => this.pixels.GetSpan(); public Span<byte> GetPixelSpan() => this.pixels.GetSpan();
/// <summary> /// <summary>
@ -65,6 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="rowIndex">The row.</param> /// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{T}"/></returns> /// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<byte> GetRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); public Span<byte> GetRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width);
/// <inheritdoc/> /// <inheritdoc/>

Loading…
Cancel
Save