From 7b692fd15af032d91d79848a31e238aff574d623 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Sep 2018 21:17:08 +0100 Subject: [PATCH] Optimize x-bit scanline packing. --- .../Formats/Png/IPngEncoderOptions.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 8 ++--- src/ImageSharp/Formats/Png/PngEncoder.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 33 ++++++++++++------- .../Quantization/QuantizedFrame{TPixel}.cs | 4 ++- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 77bc9f7a0..7e5a9fa6b 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 c52f97494..0acf0f4bf 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -396,18 +396,18 @@ namespace SixLabors.ImageSharp.Formats.Png } 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 resultOffset = 0; 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++; } } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index f47a6518f..96e97a305 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 bc7f1906f..48d611f58 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -4,6 +4,8 @@ using System; using System.Buffers.Binary; using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Png.Filters; @@ -162,8 +164,10 @@ namespace SixLabors.ImageSharp.Formats.Png this.pngBitDepth = options.BitDepth; this.pngColorType = options.ColorType; - // Palette compresses better with none and spec recommends it. - this.pngFilterMethod = options.ColorType.Equals(PngColorType.Palette) ? PngFilterMethod.None : 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; @@ -212,9 +216,11 @@ namespace SixLabors.ImageSharp.Formats.Png // Create quantized frame returning the palette and set the bit depth. quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame); 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 - 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) { bits = 4; @@ -228,6 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Png } else { + // TODO: Get the correct bit depth for grayscale images. this.bitDepth = (byte)(this.use16Bit ? 16 : 8); } @@ -416,13 +423,13 @@ namespace SixLabors.ImageSharp.Formats.Png { case PngColorType.Palette: - int stride = this.rawScanline.Length(); 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()); } @@ -841,12 +848,16 @@ 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) { - if (bits >= 8) - { - return; - } + 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); @@ -856,13 +867,13 @@ namespace SixLabors.ImageSharp.Formats.Png for (int i = 0; i < source.Length; i++) { - int value = source[i] & mask; + int value = Unsafe.Add(ref sourceRef, i) & mask; v |= value << shift; if (shift == 0) { shift = shift0; - result[resultOffset] = (byte)v; + Unsafe.Add(ref resultRef, resultOffset) = (byte)v; resultOffset++; v = 0; } @@ -874,7 +885,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (shift != shift0) { - result[resultOffset] = (byte)v; + Unsafe.Add(ref resultRef, resultOffset) = (byte)v; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index 61ea342a8..38862ef44 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,6 +57,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Gets the pixels of this . /// /// The + [MethodImpl(InliningOptions.ShortMethod)] public Span GetPixelSpan() => this.pixels.GetSpan(); /// @@ -65,6 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The row. /// The + [MethodImpl(InliningOptions.ShortMethod)] public Span GetRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); ///