From 6cf7ab9b5babbf30c9dc85d6e2988211f78c2970 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 6 Jan 2022 15:19:27 +0300 Subject: [PATCH 01/11] Removed Configure() method --- .../Components/Decoder/HuffmanScanDecoder.cs | 25 ---------------- .../Jpeg/Components/Decoder/HuffmanTable.cs | 30 +++---------------- 2 files changed, 4 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index ce5e5110b6..20c5d4a9b7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -167,18 +167,6 @@ private void ParseBaselineDataInterleaved() int mcusPerLine = this.frame.McusPerLine; ref HuffmanScanBuffer buffer = ref this.scanBuffer; - // Pre-derive the huffman table to avoid in-loop checks. - for (int i = 0; i < this.componentsCount; i++) - { - int order = this.frame.ComponentOrder[i]; - JpegComponent component = this.components[order]; - - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - dcHuffmanTable.Configure(); - acHuffmanTable.Configure(); - } - for (int j = 0; j < mcusPerColumn; j++) { this.cancellationToken.ThrowIfCancellationRequested(); @@ -248,8 +236,6 @@ private void ParseBaselineDataNonInterleaved() ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - dcHuffmanTable.Configure(); - acHuffmanTable.Configure(); for (int j = 0; j < h; j++) { @@ -347,15 +333,6 @@ private void ParseProgressiveDataInterleaved() int mcusPerLine = this.frame.McusPerLine; ref HuffmanScanBuffer buffer = ref this.scanBuffer; - // Pre-derive the huffman table to avoid in-loop checks. - for (int k = 0; k < this.componentsCount; k++) - { - int order = this.frame.ComponentOrder[k]; - JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - dcHuffmanTable.Configure(); - } - for (int j = 0; j < mcusPerColumn; j++) { for (int i = 0; i < mcusPerLine; i++) @@ -416,7 +393,6 @@ private void ParseProgressiveDataNonInterleaved() if (this.SpectralStart == 0) { ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - dcHuffmanTable.Configure(); for (int j = 0; j < h; j++) { @@ -444,7 +420,6 @@ private void ParseProgressiveDataNonInterleaved() else { ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - acHuffmanTable.Configure(); for (int j = 0; j < h; j++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index f18c636278..2b2983461d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -13,13 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder [StructLayout(LayoutKind.Sequential)] internal unsafe struct HuffmanTable { - private bool isConfigured; - - /// - /// Derived from the DHT marker. Sizes[k] = # of symbols with codes of length k bits; Sizes[0] is unused. - /// - public fixed byte Sizes[17]; - /// /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. /// @@ -62,20 +55,7 @@ internal unsafe struct HuffmanTable /// The huffman values public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) { - this.isConfigured = false; - Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length); Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - } - - /// - /// Expands the HuffmanTable into its readable form. - /// - public void Configure() - { - if (this.isConfigured) - { - return; - } Span huffSize = stackalloc char[257]; Span huffCode = stackalloc uint[257]; @@ -84,7 +64,7 @@ public void Configure() int p = 0; for (int j = 1; j <= 16; j++) { - int i = this.Sizes[j]; + int i = codeLengths[j]; while (i-- != 0) { huffSize[p++] = (char)j; @@ -113,10 +93,10 @@ public void Configure() p = 0; for (int j = 1; j <= 16; j++) { - if (this.Sizes[j] != 0) + if (codeLengths[j] != 0) { this.ValOffset[j] = p - (int)huffCode[p]; - p += this.Sizes[j]; + p += codeLengths[j]; this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; @@ -142,7 +122,7 @@ public void Configure() for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) { int jShift = JpegConstants.Huffman.LookupBits - length; - for (int i = 1; i <= this.Sizes[length]; i++, p++) + for (int i = 1; i <= codeLengths[length]; i++, p++) { // length = current code's length, p = its index in huffCode[] & Values[]. // Generate left-justified code followed by all possible bit sequences @@ -155,8 +135,6 @@ public void Configure() } } } - - this.isConfigured = true; } } } From 2334bcf5eb3c28b992678dbe7de111825cbcf1c7 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 6 Jan 2022 17:38:38 +0300 Subject: [PATCH 02/11] Testing commit, lot of out of bounds guards --- .../Jpeg/Components/Decoder/HuffmanTable.cs | 128 ++++++++++-------- 1 file changed, 69 insertions(+), 59 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 2b2983461d..7c6b2700db 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -57,81 +57,91 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - Span huffSize = stackalloc char[257]; - Span huffCode = stackalloc uint[257]; + // TODO: testing code + Span huffSize = new char[257]; + Span huffCode = new uint[257]; - // Figure C.1: make table of Huffman code length for each symbol - int p = 0; - for (int j = 1; j <= 16; j++) + fixed (void* maxCodePtr = this.MaxCode, valOffsetPtr = this.ValOffset, lookaheadSizePtr = this.LookaheadSize, lookaheadValuePtr = this.LookaheadValue) { - int i = codeLengths[j]; - while (i-- != 0) + // TODO: testing spans + var maxCodeSpan = new Span(maxCodePtr, 18); + var valOffsetSpan = new Span(valOffsetPtr, 19); + var lookaheadSizeSpan = new Span(lookaheadSizePtr, JpegConstants.Huffman.LookupSize); + var lookaheadValueSpan = new Span(lookaheadValuePtr, JpegConstants.Huffman.LookupSize); + + // Figure C.1: make table of Huffman code length for each symbol + int p = 0; + for (int j = 1; j <= 16; j++) { - huffSize[p++] = (char)j; + int i = codeLengths[j]; + while (i-- != 0) + { + huffSize[p++] = (char)j; + } } - } - huffSize[p] = (char)0; + huffSize[p] = (char)0; - // Figure C.2: generate the codes themselves - uint code = 0; - int si = huffSize[0]; - p = 0; - while (huffSize[p] != 0) - { - while (huffSize[p] == si) + // Figure C.2: generate the codes themselves + uint code = 0; + int si = huffSize[0]; + p = 0; + while (huffSize[p] != 0) { - huffCode[p++] = code; - code++; - } - - code <<= 1; - si++; - } + while (huffSize[p] == si) + { + huffCode[p++] = code; + code++; + } - // Figure F.15: generate decoding tables for bit-sequential decoding - p = 0; - for (int j = 1; j <= 16; j++) - { - if (codeLengths[j] != 0) - { - this.ValOffset[j] = p - (int)huffCode[p]; - p += codeLengths[j]; - this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l - this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify - this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; + code <<= 1; + si++; } - else + + // Figure F.15: generate decoding tables for bit-sequential decoding + p = 0; + for (int j = 1; j <= 16; j++) { - this.MaxCode[j] = 0; + if (codeLengths[j] != 0) + { + valOffsetSpan[j] = p - (int)huffCode[p]; + p += codeLengths[j]; + maxCodeSpan[j] = huffCode[p - 1]; // Maximum code of length l + maxCodeSpan[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify + maxCodeSpan[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; + } + else + { + maxCodeSpan[j] = 0; + } } - } - this.ValOffset[18] = 0; - this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates + valOffsetSpan[18] = 0; + maxCodeSpan[17] = ulong.MaxValue; // Ensures huff decode terminates - // Compute lookahead tables to speed up decoding. - // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; - // then we iterate through the Huffman codes that are short enough and - // fill in all the entries that correspond to bit sequences starting - // with that code. - ref byte lookupSizeRef = ref this.LookaheadSize[0]; - Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); + // Compute lookahead tables to speed up decoding. + // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; + // then we iterate through the Huffman codes that are short enough and + // fill in all the entries that correspond to bit sequences starting + // with that code. + ref byte lookupSizeRef = ref lookaheadSizeSpan[0]; + Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); - p = 0; - for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) - { - int jShift = JpegConstants.Huffman.LookupBits - length; - for (int i = 1; i <= codeLengths[length]; i++, p++) + p = 0; + for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) { - // length = current code's length, p = its index in huffCode[] & Values[]. - // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(huffCode[p] << jShift); - for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) + int jShift = JpegConstants.Huffman.LookupBits - length; + for (int i = 1; i <= codeLengths[length]; i++, p++) { - this.LookaheadSize[lookBits] = (byte)length; - this.LookaheadValue[lookBits] = this.Values[p]; - lookBits++; + // length = current code's length, p = its index in huffCode[] & Values[]. + // Generate left-justified code followed by all possible bit sequences + int lookBits = (int)(huffCode[p] << jShift); + for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) + { + lookaheadSizeSpan[lookBits] = (byte)length; + lookaheadValueSpan[lookBits] = this.Values[p]; + lookBits++; + } } } } From 1694a5bf84f00856833b40c1660090834af3bf51 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 7 Jan 2022 19:43:04 +0300 Subject: [PATCH 03/11] Added sanity check for generated huffman codes --- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 11 +++++++++++ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 7c6b2700db..5928606432 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -94,6 +94,17 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) code++; } + // 'code' is now 1 more than the last code used for codelength 'si' + // in the valid worst possible case 'code' would have the least + // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) + // but it must still fit in 'si' bits since no huffman code can be equal to all 1s + // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield + // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) + if (code >= (1 << si)) + { + JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); + } + code <<= 1; si++; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 73af42afd4..08d1aa1468 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1110,13 +1110,13 @@ private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int rem // Types 0..1 DC..AC if (tableType > 1) { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}"); + JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}."); } // Max tables of each type if (tableIndex > 3) { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}"); + JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); } stream.Read(huffmanDataSpan, 0, 16); From 8b01f139d431a0d00477d2ec1aff51f3efb22618 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 7 Jan 2022 20:01:02 +0300 Subject: [PATCH 04/11] Removed debug checks --- .../Jpeg/Components/Decoder/HuffmanTable.cs | 147 ++++++++---------- 1 file changed, 69 insertions(+), 78 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 5928606432..26df55738a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -58,101 +58,92 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); // TODO: testing code - Span huffSize = new char[257]; - Span huffCode = new uint[257]; + Span huffSize = stackalloc char[257]; + Span huffCode = stackalloc uint[257]; - fixed (void* maxCodePtr = this.MaxCode, valOffsetPtr = this.ValOffset, lookaheadSizePtr = this.LookaheadSize, lookaheadValuePtr = this.LookaheadValue) + // Figure C.1: make table of Huffman code length for each symbol + int p = 0; + for (int j = 1; j <= 16; j++) { - // TODO: testing spans - var maxCodeSpan = new Span(maxCodePtr, 18); - var valOffsetSpan = new Span(valOffsetPtr, 19); - var lookaheadSizeSpan = new Span(lookaheadSizePtr, JpegConstants.Huffman.LookupSize); - var lookaheadValueSpan = new Span(lookaheadValuePtr, JpegConstants.Huffman.LookupSize); - - // Figure C.1: make table of Huffman code length for each symbol - int p = 0; - for (int j = 1; j <= 16; j++) + int i = codeLengths[j]; + while (i-- != 0) { - int i = codeLengths[j]; - while (i-- != 0) - { - huffSize[p++] = (char)j; - } + huffSize[p++] = (char)j; } + } - huffSize[p] = (char)0; + huffSize[p] = (char)0; - // Figure C.2: generate the codes themselves - uint code = 0; - int si = huffSize[0]; - p = 0; - while (huffSize[p] != 0) + // Figure C.2: generate the codes themselves + uint code = 0; + int si = huffSize[0]; + p = 0; + while (huffSize[p] != 0) + { + while (huffSize[p] == si) { - while (huffSize[p] == si) - { - huffCode[p++] = code; - code++; - } - - // 'code' is now 1 more than the last code used for codelength 'si' - // in the valid worst possible case 'code' would have the least - // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) - // but it must still fit in 'si' bits since no huffman code can be equal to all 1s - // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield - // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) - if (code >= (1 << si)) - { - JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); - } + huffCode[p++] = code; + code++; + } - code <<= 1; - si++; + // 'code' is now 1 more than the last code used for codelength 'si' + // in the valid worst possible case 'code' would have the least + // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) + // but it must still fit in 'si' bits since no huffman code can be equal to all 1s + // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield + // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) + if (code >= (1 << si)) + { + JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); } - // Figure F.15: generate decoding tables for bit-sequential decoding - p = 0; - for (int j = 1; j <= 16; j++) + code <<= 1; + si++; + } + + // Figure F.15: generate decoding tables for bit-sequential decoding + p = 0; + for (int j = 1; j <= 16; j++) + { + if (codeLengths[j] != 0) { - if (codeLengths[j] != 0) - { - valOffsetSpan[j] = p - (int)huffCode[p]; - p += codeLengths[j]; - maxCodeSpan[j] = huffCode[p - 1]; // Maximum code of length l - maxCodeSpan[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify - maxCodeSpan[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; - } - else - { - maxCodeSpan[j] = 0; - } + this.ValOffset[j] = p - (int)huffCode[p]; + p += codeLengths[j]; + this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l + this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify + this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; + } + else + { + this.MaxCode[j] = 0; } + } - valOffsetSpan[18] = 0; - maxCodeSpan[17] = ulong.MaxValue; // Ensures huff decode terminates + this.ValOffset[18] = 0; + this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates - // Compute lookahead tables to speed up decoding. - // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; - // then we iterate through the Huffman codes that are short enough and - // fill in all the entries that correspond to bit sequences starting - // with that code. - ref byte lookupSizeRef = ref lookaheadSizeSpan[0]; - Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); + // Compute lookahead tables to speed up decoding. + // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; + // then we iterate through the Huffman codes that are short enough and + // fill in all the entries that correspond to bit sequences starting + // with that code. + ref byte lookupSizeRef = ref this.LookaheadSize[0]; + Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); - p = 0; - for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) + p = 0; + for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) + { + int jShift = JpegConstants.Huffman.LookupBits - length; + for (int i = 1; i <= codeLengths[length]; i++, p++) { - int jShift = JpegConstants.Huffman.LookupBits - length; - for (int i = 1; i <= codeLengths[length]; i++, p++) + // length = current code's length, p = its index in huffCode[] & Values[]. + // Generate left-justified code followed by all possible bit sequences + int lookBits = (int)(huffCode[p] << jShift); + for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) { - // length = current code's length, p = its index in huffCode[] & Values[]. - // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(huffCode[p] << jShift); - for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) - { - lookaheadSizeSpan[lookBits] = (byte)length; - lookaheadValueSpan[lookBits] = this.Values[p]; - lookBits++; - } + this.LookaheadSize[lookBits] = (byte)length; + this.LookaheadValue[lookBits] = this.Values[p]; + lookBits++; } } } From 3354ae68d7046cadf3d9e07110f2725aa87475c8 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 7 Jan 2022 20:01:49 +0300 Subject: [PATCH 05/11] Reduced stackallock pressure by changing char to byte --- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 26df55738a..c20ea1e584 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -58,7 +58,7 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); // TODO: testing code - Span huffSize = stackalloc char[257]; + Span huffSize = stackalloc byte[257]; Span huffCode = stackalloc uint[257]; // Figure C.1: make table of Huffman code length for each symbol @@ -68,11 +68,11 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) int i = codeLengths[j]; while (i-- != 0) { - huffSize[p++] = (char)j; + huffSize[p++] = (byte)j; } } - huffSize[p] = (char)0; + huffSize[p] = 0; // Figure C.2: generate the codes themselves uint code = 0; From 499ddf8defe7c1de77e635360554ad42a09301fb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 8 Jan 2022 09:50:22 +0300 Subject: [PATCH 06/11] More readable and maybe even faster code --- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index c20ea1e584..9ef29962e4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -57,7 +57,6 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - // TODO: testing code Span huffSize = stackalloc byte[257]; Span huffCode = stackalloc uint[257]; @@ -65,11 +64,9 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) int p = 0; for (int j = 1; j <= 16; j++) { - int i = codeLengths[j]; - while (i-- != 0) - { - huffSize[p++] = (byte)j; - } + int count = codeLengths[j]; + huffSize.Slice(p, count).Fill((byte)j); + p += count; } huffSize[p] = 0; From 51127b6b5624bbe82814448472e641b46f263c1f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 8 Jan 2022 11:32:23 +0300 Subject: [PATCH 07/11] Removed 257 bytes of stack memory pressure --- .../Jpeg/Components/Decoder/HuffmanTable.cs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 9ef29962e4..c69949de48 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -57,27 +57,16 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - Span huffSize = stackalloc byte[257]; Span huffCode = stackalloc uint[257]; - // Figure C.1: make table of Huffman code length for each symbol - int p = 0; - for (int j = 1; j <= 16; j++) - { - int count = codeLengths[j]; - huffSize.Slice(p, count).Fill((byte)j); - p += count; - } - - huffSize[p] = 0; - - // Figure C.2: generate the codes themselves + // Generate codes uint code = 0; - int si = huffSize[0]; - p = 0; - while (huffSize[p] != 0) + int si = 1; + int p = 0; + for (int i = 1; i <= 16; i++) { - while (huffSize[p] == si) + int count = codeLengths[i]; + for (int j = 0; j < count; j++) { huffCode[p++] = code; code++; From e7ee0fdac4c3ab94bc345e17356879487fdcf195 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 8 Jan 2022 11:49:47 +0300 Subject: [PATCH 08/11] [WIP] outer scope memory propagation --- .../Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 4 ++-- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 9 +++++---- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 20c5d4a9b7..c06f3f2a04 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -728,10 +728,10 @@ private bool HandleRestart() /// Code lengths. /// Code values. [MethodImpl(InliningOptions.ShortMethod)] - public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values) + public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables; - tables[index] = new HuffmanTable(codeLengths, values); + tables[index] = new HuffmanTable(codeLengths, values, workspace); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index c69949de48..7e69c4d94f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -51,13 +51,14 @@ internal unsafe struct HuffmanTable /// /// Initializes a new instance of the struct. /// - /// The code lengths - /// The huffman values - public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) + /// The code lengths. + /// The huffman values. + /// The spare workspace memory, must be provided by the caller. + public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - Span huffCode = stackalloc uint[257]; + Span huffCode = workspace; // Generate codes uint code = 0; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 08d1aa1468..adb0ad4b08 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1150,7 +1150,8 @@ private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int rem tableType, tableIndex, codeLengthsSpan, - huffmanValuesSpan); + huffmanValuesSpan, + stackalloc uint[257]); } } } From 46226bfe79aeb8372cf367c5628313e1bbf1fe8c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 8 Jan 2022 22:41:01 +0300 Subject: [PATCH 09/11] Reduced memory pressure, removed unnecessary buffer --- .../Formats/Jpeg/JpegDecoderCore.cs | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index adb0ad4b08..4c6772a6f6 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1095,12 +1095,15 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, /// The remaining bytes in the segment block. private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining) { - int length = remaining; + const int codeLengthsByteSize = 16; + const int codeValuesMaxByteSize = 256; + const int tableWorkspaceByteSize = 257 * sizeof(uint); + const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + tableWorkspaceByteSize; - using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) + int length = remaining; + using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(17)) { Span huffmanDataSpan = huffmanData.GetSpan(); - ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanDataSpan); for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)stream.ReadByte(); @@ -1119,40 +1122,34 @@ private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int rem JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); } - stream.Read(huffmanDataSpan, 0, 16); + stream.Read(huffmanDataSpan, 1, 16); - using (IMemoryOwner codeLengths = this.Configuration.MemoryAllocator.Allocate(17, AllocationOptions.Clean)) + int codeLengthSum = 0; + for (int j = 1; j < 17; j++) { - Span codeLengthsSpan = codeLengths.GetSpan(); - ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengthsSpan); - int codeLengthSum = 0; - - for (int j = 1; j < 17; j++) - { - codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1); - } + codeLengthSum += huffmanDataSpan[j]; + } - length -= 17; + length -= 17; - if (codeLengthSum > 256 || codeLengthSum > length) - { - JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); - } + if (codeLengthSum > 256 || codeLengthSum > length) + { + JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); + } - using (IMemoryOwner huffmanValues = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) - { - Span huffmanValuesSpan = huffmanValues.GetSpan(); - stream.Read(huffmanValuesSpan, 0, codeLengthSum); + using (IMemoryOwner huffmanValues = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) + { + Span huffmanValuesSpan = huffmanValues.GetSpan(); + stream.Read(huffmanValuesSpan, 0, codeLengthSum); - i += 17 + codeLengthSum; + i += 17 + codeLengthSum; - this.scanDecoder.BuildHuffmanTable( - tableType, - tableIndex, - codeLengthsSpan, - huffmanValuesSpan, - stackalloc uint[257]); - } + this.scanDecoder.BuildHuffmanTable( + tableType, + tableIndex, + huffmanDataSpan, + huffmanValuesSpan, + stackalloc uint[257]); } } } From 5e1eb1ad5d8b3e00eae35f7106a59e2938ea1b34 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 9 Jan 2022 03:47:34 +0300 Subject: [PATCH 10/11] Removed stack pressure, pooling single buffer for entire huffman tables parsing --- .../Components/Decoder/HuffmanScanDecoder.cs | 1 + .../Jpeg/Components/Decoder/HuffmanTable.cs | 12 +++---- .../Formats/Jpeg/JpegDecoderCore.cs | 36 +++++++++---------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index c06f3f2a04..2ae3ae86bc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -727,6 +727,7 @@ private bool HandleRestart() /// Table index. /// Code lengths. /// Code values. + /// The provided spare workspace memory, can be dirty. [MethodImpl(InliningOptions.ShortMethod)] public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 7e69c4d94f..90a966d53f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -53,13 +53,11 @@ internal unsafe struct HuffmanTable /// /// The code lengths. /// The huffman values. - /// The spare workspace memory, must be provided by the caller. + /// The provided spare workspace memory, can be dirty. public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - Span huffCode = workspace; - // Generate codes uint code = 0; int si = 1; @@ -69,7 +67,7 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, S int count = codeLengths[i]; for (int j = 0; j < count; j++) { - huffCode[p++] = code; + workspace[p++] = code; code++; } @@ -94,9 +92,9 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, S { if (codeLengths[j] != 0) { - this.ValOffset[j] = p - (int)huffCode[p]; + this.ValOffset[j] = p - (int)workspace[p]; p += codeLengths[j]; - this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l + this.MaxCode[j] = workspace[p - 1]; // Maximum code of length l this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; } @@ -125,7 +123,7 @@ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, S { // length = current code's length, p = its index in huffCode[] & Values[]. // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(huffCode[p] << jShift); + int lookBits = (int)(workspace[p] << jShift); for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) { this.LookaheadSize[lookBits] = (byte)length; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4c6772a6f6..a2cccc37f5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1095,15 +1095,19 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, /// The remaining bytes in the segment block. private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining) { - const int codeLengthsByteSize = 16; + const int codeLengthsByteSize = 17; const int codeValuesMaxByteSize = 256; - const int tableWorkspaceByteSize = 257 * sizeof(uint); + const int tableWorkspaceByteSize = 256 * sizeof(uint); const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + tableWorkspaceByteSize; int length = remaining; - using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(17)) + using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(totalBufferSize)) { - Span huffmanDataSpan = huffmanData.GetSpan(); + Span bufferSpan = buffer.GetSpan(); + Span huffmanLegthsSpan = buffer.Slice(0, codeLengthsByteSize); + Span huffmanValuesSpan = buffer.Slice(codeLengthsByteSize, codeValuesMaxByteSize); + Span tableWorkspace = MemoryMarshal.Cast(buffer.Slice(codeLengthsByteSize + codeValuesMaxByteSize)); + for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)stream.ReadByte(); @@ -1122,12 +1126,12 @@ private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int rem JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); } - stream.Read(huffmanDataSpan, 1, 16); + stream.Read(huffmanLegthsSpan, 1, 16); int codeLengthSum = 0; for (int j = 1; j < 17; j++) { - codeLengthSum += huffmanDataSpan[j]; + codeLengthSum += huffmanLegthsSpan[j]; } length -= 17; @@ -1137,20 +1141,16 @@ private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int rem JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); } - using (IMemoryOwner huffmanValues = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) - { - Span huffmanValuesSpan = huffmanValues.GetSpan(); - stream.Read(huffmanValuesSpan, 0, codeLengthSum); + stream.Read(huffmanValuesSpan, 0, codeLengthSum); - i += 17 + codeLengthSum; + i += 17 + codeLengthSum; - this.scanDecoder.BuildHuffmanTable( - tableType, - tableIndex, - huffmanDataSpan, - huffmanValuesSpan, - stackalloc uint[257]); - } + this.scanDecoder.BuildHuffmanTable( + tableType, + tableIndex, + huffmanLegthsSpan, + huffmanValuesSpan.Slice(0, codeLengthSum), + tableWorkspace); } } } From 7bb1a507c20074f9777c30a682a817240925ef11 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 9 Jan 2022 05:39:00 +0300 Subject: [PATCH 11/11] Docs, faster span slice method --- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 5 +++++ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 90a966d53f..bee5e0229b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder [StructLayout(LayoutKind.Sequential)] internal unsafe struct HuffmanTable { + /// + /// Memory workspace buffer size used in ctor. + /// + public const int WorkspaceByteSize = 256 * sizeof(uint); + /// /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index a2cccc37f5..a21f1a71d7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1097,16 +1097,15 @@ private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int rem { const int codeLengthsByteSize = 17; const int codeValuesMaxByteSize = 256; - const int tableWorkspaceByteSize = 256 * sizeof(uint); - const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + tableWorkspaceByteSize; + const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize; int length = remaining; using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(totalBufferSize)) { Span bufferSpan = buffer.GetSpan(); - Span huffmanLegthsSpan = buffer.Slice(0, codeLengthsByteSize); - Span huffmanValuesSpan = buffer.Slice(codeLengthsByteSize, codeValuesMaxByteSize); - Span tableWorkspace = MemoryMarshal.Cast(buffer.Slice(codeLengthsByteSize + codeValuesMaxByteSize)); + Span huffmanLegthsSpan = bufferSpan.Slice(0, codeLengthsByteSize); + Span huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize); + Span tableWorkspace = MemoryMarshal.Cast(bufferSpan.Slice(codeLengthsByteSize + codeValuesMaxByteSize)); for (int i = 2; i < remaining;) {