From 25ab5df08a498f0229ff7c950dc50596ed707b1f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 9 May 2022 02:16:01 +0300 Subject: [PATCH] Implemented huffman table --- .../Components/Encoder/HuffmanScanEncoder.cs | 25 +++++++-- .../Jpeg/Components/Encoder/HuffmanSpec.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 21 +++++++- .../Formats/Jpeg/JpegEncoderCore.cs | 52 ++++++++----------- 4 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 9803bcf2a..6b69e4320 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -60,10 +60,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private const int OutputBufferLengthMultiplier = 2; /// - /// Compiled huffman tree to encode given values. + /// The DC Huffman tables. /// - /// Yields codewords by index consisting of [run length | bitsize]. - private HuffmanLut[] huffmanTables; + private readonly HuffmanLut[] dcHuffmanTables; + + /// + /// The AC Huffman tables. + /// + private readonly HuffmanLut[] acHuffmanTables; /// /// Emitted bits 'micro buffer' before being transferred to the . @@ -115,6 +119,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; this.target = outputStream; + + this.dcHuffmanTables = new HuffmanLut[4]; + this.acHuffmanTables = new HuffmanLut[4]; } /// @@ -128,6 +135,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } + public void BuildHuffmanTable(JpegHuffmanTableConfig table) + { + HuffmanLut[] tables = table.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; + tables[table.DestinationIndex] = new HuffmanLut(table.TableSpec); + } + public void EncodeInterleavedScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -159,8 +172,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { JpegComponent component = frame.Components[k]; - ref HuffmanLut dcHuffmanTable = ref HuffmanLut.DcHuffmanLut[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref HuffmanLut.AcHuffmanLut[component.AcTableId]; + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -202,6 +215,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { } + private HuffmanLut[] huffmanTables; + /// /// Encodes the image with no subsampling. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index 51364e3c7..e0b988b43 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The Huffman encoding specifications. /// - internal readonly struct HuffmanSpec + public readonly struct HuffmanSpec { #pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 209be9d5b..d92fc457a 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -22,6 +23,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public JpegFrameConfig JpegFrameConfig { get; set; } + public JpegScanConfig JpegScanConfig { get; set; } + /// /// Encodes the image to the specified stream from the . /// @@ -31,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.JpegFrameConfig); + var encoder = new JpegEncoderCore(this, this.JpegFrameConfig, this.JpegScanConfig); encoder.Encode(image, stream); } @@ -46,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.JpegFrameConfig); + var encoder = new JpegEncoderCore(this, this.JpegFrameConfig, this.JpegScanConfig); return encoder.EncodeAsync(image, stream, cancellationToken); } } @@ -115,4 +118,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public int acTableSelector { get; set; } } + + public class JpegHuffmanTableConfig + { + public int Class { get; set; } + + public int DestinationIndex { get; set; } + + public HuffmanSpec TableSpec { get; set; } + } + + public class JpegScanConfig + { + public JpegHuffmanTableConfig[] HuffmanTables { get; set; } + } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 6d720dfd1..414f08248 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -46,6 +46,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private JpegFrameConfig frameConfig; + private JpegScanConfig scanConfig; + + private HuffmanScanEncoder scanEncoder; + /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -55,12 +59,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Initializes a new instance of the class. /// /// The options. - public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig) + public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig, JpegScanConfig scanConfig) { this.quality = options.Quality; this.frameConfig = frameConfig; this.colorType = frameConfig.ColorType; + + this.scanConfig = scanConfig; } /// @@ -83,6 +89,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg cancellationToken.ThrowIfCancellationRequested(); + this.scanEncoder = new HuffmanScanEncoder(3, stream); + this.outputStream = stream; ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); @@ -116,14 +124,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig); // Write the Huffman tables. - this.WriteDefineHuffmanTables(this.frameConfig.Components.Length); + this.WriteDefineHuffmanTables(this.scanConfig.HuffmanTables); // Write the scan header. this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - new HuffmanScanEncoder(3, stream).EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken); + this.scanEncoder.EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); @@ -273,40 +281,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Writes the Define Huffman Table marker and tables. /// /// The number of components to write. - private void WriteDefineHuffmanTables(int componentCount) + private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tables) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - // Table identifiers. - ReadOnlySpan headers = new byte[] - { - 0x00, - 0x10, - 0x01, - 0x11 - }; - int markerlen = 2; - HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs; - if (componentCount == 1) + for (int i = 0; i < tables.Length; i++) { - // Drop the Chrominance tables. - specs = new[] { HuffmanSpec.TheHuffmanSpecs[0], HuffmanSpec.TheHuffmanSpecs[1] }; - } - - for (int i = 0; i < specs.Length; i++) - { - ref HuffmanSpec s = ref specs[i]; - markerlen += 1 + 16 + s.Values.Length; + markerlen += 1 + 16 + tables[i].TableSpec.Values.Length; } this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); - for (int i = 0; i < specs.Length; i++) + for (int i = 0; i < tables.Length; i++) { - this.outputStream.WriteByte(headers[i]); - this.outputStream.Write(specs[i].Count); - this.outputStream.Write(specs[i].Values); + JpegHuffmanTableConfig table = tables[i]; + + int header = (table.Class << 4) | table.DestinationIndex; + this.outputStream.WriteByte((byte)header); + this.outputStream.Write(table.TableSpec.Count); + this.outputStream.Write(table.TableSpec.Values); + + this.scanEncoder.BuildHuffmanTable(table); } }