diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
index ce5e5110b6..2ae3ae86bc 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++)
{
@@ -752,11 +727,12 @@ 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)
+ 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 f18c636278..bee5e0229b 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
@@ -13,12 +13,10 @@ 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.
+ /// Memory workspace buffer size used in ctor.
///
- public fixed byte Sizes[17];
+ public const int WorkspaceByteSize = 256 * sizeof(uint);
///
/// Derived from the DHT marker. Contains the symbols, in order of incremental code length.
@@ -58,51 +56,35 @@ 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 provided spare workspace memory, can be dirty.
+ public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace)
{
- 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];
-
- // Figure C.1: make table of Huffman code length for each symbol
+ // Generate codes
+ uint code = 0;
+ int si = 1;
int p = 0;
- for (int j = 1; j <= 16; j++)
+ for (int i = 1; i <= 16; i++)
{
- int i = this.Sizes[j];
- while (i-- != 0)
+ int count = codeLengths[i];
+ for (int j = 0; j < count; j++)
{
- huffSize[p++] = (char)j;
+ workspace[p++] = code;
+ code++;
}
- }
- 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)
+ // '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))
{
- huffCode[p++] = code;
- code++;
+ JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table.");
}
code <<= 1;
@@ -113,11 +95,11 @@ 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];
- this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l
+ this.ValOffset[j] = p - (int)workspace[p];
+ p += codeLengths[j];
+ 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;
}
@@ -142,11 +124,11 @@ 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
- 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;
@@ -155,8 +137,6 @@ public void Configure()
}
}
}
-
- this.isConfigured = true;
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 73af42afd4..a21f1a71d7 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -1095,12 +1095,18 @@ 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 = 17;
+ const int codeValuesMaxByteSize = 256;
+ const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize;
- using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean))
+ int length = remaining;
+ using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(totalBufferSize))
{
- Span huffmanDataSpan = huffmanData.GetSpan();
- ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanDataSpan);
+ Span bufferSpan = buffer.GetSpan();
+ 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;)
{
byte huffmanTableSpec = (byte)stream.ReadByte();
@@ -1110,49 +1116,40 @@ 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);
+ stream.Read(huffmanLegthsSpan, 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 += huffmanLegthsSpan[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);
+ stream.Read(huffmanValuesSpan, 0, codeLengthSum);
- i += 17 + codeLengthSum;
+ i += 17 + codeLengthSum;
- this.scanDecoder.BuildHuffmanTable(
- tableType,
- tableIndex,
- codeLengthsSpan,
- huffmanValuesSpan);
- }
- }
+ this.scanDecoder.BuildHuffmanTable(
+ tableType,
+ tableIndex,
+ huffmanLegthsSpan,
+ huffmanValuesSpan.Slice(0, codeLengthSum),
+ tableWorkspace);
}
}
}