Skip to content

Commit 576a69f

Browse files
Merge pull request #1926 from br3aker/dp/huff-table-improvements
Jpeg HuffmanTable improvements
2 parents ce34e84 + 544c557 commit 576a69f

File tree

3 files changed

+61
-108
lines changed

3 files changed

+61
-108
lines changed

src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -167,18 +167,6 @@ private void ParseBaselineDataInterleaved()
167167
int mcusPerLine = this.frame.McusPerLine;
168168
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
169169

170-
// Pre-derive the huffman table to avoid in-loop checks.
171-
for (int i = 0; i < this.componentsCount; i++)
172-
{
173-
int order = this.frame.ComponentOrder[i];
174-
JpegComponent component = this.components[order];
175-
176-
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
177-
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
178-
dcHuffmanTable.Configure();
179-
acHuffmanTable.Configure();
180-
}
181-
182170
for (int j = 0; j < mcusPerColumn; j++)
183171
{
184172
this.cancellationToken.ThrowIfCancellationRequested();
@@ -248,8 +236,6 @@ private void ParseBaselineDataNonInterleaved()
248236

249237
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
250238
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
251-
dcHuffmanTable.Configure();
252-
acHuffmanTable.Configure();
253239

254240
for (int j = 0; j < h; j++)
255241
{
@@ -347,15 +333,6 @@ private void ParseProgressiveDataInterleaved()
347333
int mcusPerLine = this.frame.McusPerLine;
348334
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
349335

350-
// Pre-derive the huffman table to avoid in-loop checks.
351-
for (int k = 0; k < this.componentsCount; k++)
352-
{
353-
int order = this.frame.ComponentOrder[k];
354-
JpegComponent component = this.components[order];
355-
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
356-
dcHuffmanTable.Configure();
357-
}
358-
359336
for (int j = 0; j < mcusPerColumn; j++)
360337
{
361338
for (int i = 0; i < mcusPerLine; i++)
@@ -416,7 +393,6 @@ private void ParseProgressiveDataNonInterleaved()
416393
if (this.SpectralStart == 0)
417394
{
418395
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
419-
dcHuffmanTable.Configure();
420396

421397
for (int j = 0; j < h; j++)
422398
{
@@ -444,7 +420,6 @@ ref Unsafe.Add(ref blockRef, i),
444420
else
445421
{
446422
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
447-
acHuffmanTable.Configure();
448423

449424
for (int j = 0; j < h; j++)
450425
{
@@ -752,11 +727,12 @@ private bool HandleRestart()
752727
/// <param name="index">Table index.</param>
753728
/// <param name="codeLengths">Code lengths.</param>
754729
/// <param name="values">Code values.</param>
730+
/// <param name="workspace">The provided spare workspace memory, can be dirty.</param>
755731
[MethodImpl(InliningOptions.ShortMethod)]
756-
public void BuildHuffmanTable(int type, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
732+
public void BuildHuffmanTable(int type, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values, Span<uint> workspace)
757733
{
758734
HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
759-
tables[index] = new HuffmanTable(codeLengths, values);
735+
tables[index] = new HuffmanTable(codeLengths, values, workspace);
760736
}
761737
}
762738
}

src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

Lines changed: 28 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
1313
[StructLayout(LayoutKind.Sequential)]
1414
internal unsafe struct HuffmanTable
1515
{
16-
private bool isConfigured;
17-
1816
/// <summary>
19-
/// Derived from the DHT marker. Sizes[k] = # of symbols with codes of length k bits; Sizes[0] is unused.
17+
/// Memory workspace buffer size used in <see cref="HuffmanTable"/> ctor.
2018
/// </summary>
21-
public fixed byte Sizes[17];
19+
public const int WorkspaceByteSize = 256 * sizeof(uint);
2220

2321
/// <summary>
2422
/// Derived from the DHT marker. Contains the symbols, in order of incremental code length.
@@ -58,51 +56,35 @@ internal unsafe struct HuffmanTable
5856
/// <summary>
5957
/// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
6058
/// </summary>
61-
/// <param name="codeLengths">The code lengths</param>
62-
/// <param name="values">The huffman values</param>
63-
public HuffmanTable(ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
59+
/// <param name="codeLengths">The code lengths.</param>
60+
/// <param name="values">The huffman values.</param>
61+
/// <param name="workspace">The provided spare workspace memory, can be dirty.</param>
62+
public HuffmanTable(ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values, Span<uint> workspace)
6463
{
65-
this.isConfigured = false;
66-
Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length);
6764
Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length);
68-
}
69-
70-
/// <summary>
71-
/// Expands the HuffmanTable into its readable form.
72-
/// </summary>
73-
public void Configure()
74-
{
75-
if (this.isConfigured)
76-
{
77-
return;
78-
}
7965

80-
Span<char> huffSize = stackalloc char[257];
81-
Span<uint> huffCode = stackalloc uint[257];
82-
83-
// Figure C.1: make table of Huffman code length for each symbol
66+
// Generate codes
67+
uint code = 0;
68+
int si = 1;
8469
int p = 0;
85-
for (int j = 1; j <= 16; j++)
70+
for (int i = 1; i <= 16; i++)
8671
{
87-
int i = this.Sizes[j];
88-
while (i-- != 0)
72+
int count = codeLengths[i];
73+
for (int j = 0; j < count; j++)
8974
{
90-
huffSize[p++] = (char)j;
75+
workspace[p++] = code;
76+
code++;
9177
}
92-
}
9378

94-
huffSize[p] = (char)0;
95-
96-
// Figure C.2: generate the codes themselves
97-
uint code = 0;
98-
int si = huffSize[0];
99-
p = 0;
100-
while (huffSize[p] != 0)
101-
{
102-
while (huffSize[p] == si)
79+
// 'code' is now 1 more than the last code used for codelength 'si'
80+
// in the valid worst possible case 'code' would have the least
81+
// significant bit set to 1, e.g. 1111(0) +1 => 1111(1)
82+
// but it must still fit in 'si' bits since no huffman code can be equal to all 1s
83+
// if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield
84+
// a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0)
85+
if (code >= (1 << si))
10386
{
104-
huffCode[p++] = code;
105-
code++;
87+
JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table.");
10688
}
10789

10890
code <<= 1;
@@ -113,11 +95,11 @@ public void Configure()
11395
p = 0;
11496
for (int j = 1; j <= 16; j++)
11597
{
116-
if (this.Sizes[j] != 0)
98+
if (codeLengths[j] != 0)
11799
{
118-
this.ValOffset[j] = p - (int)huffCode[p];
119-
p += this.Sizes[j];
120-
this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l
100+
this.ValOffset[j] = p - (int)workspace[p];
101+
p += codeLengths[j];
102+
this.MaxCode[j] = workspace[p - 1]; // Maximum code of length l
121103
this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify
122104
this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1;
123105
}
@@ -142,11 +124,11 @@ public void Configure()
142124
for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++)
143125
{
144126
int jShift = JpegConstants.Huffman.LookupBits - length;
145-
for (int i = 1; i <= this.Sizes[length]; i++, p++)
127+
for (int i = 1; i <= codeLengths[length]; i++, p++)
146128
{
147129
// length = current code's length, p = its index in huffCode[] & Values[].
148130
// Generate left-justified code followed by all possible bit sequences
149-
int lookBits = (int)(huffCode[p] << jShift);
131+
int lookBits = (int)(workspace[p] << jShift);
150132
for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--)
151133
{
152134
this.LookaheadSize[lookBits] = (byte)length;
@@ -155,8 +137,6 @@ public void Configure()
155137
}
156138
}
157139
}
158-
159-
this.isConfigured = true;
160140
}
161141
}
162142
}

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,12 +1095,18 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
10951095
/// <param name="remaining">The remaining bytes in the segment block.</param>
10961096
private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining)
10971097
{
1098-
int length = remaining;
1098+
const int codeLengthsByteSize = 17;
1099+
const int codeValuesMaxByteSize = 256;
1100+
const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize;
10991101

1100-
using (IMemoryOwner<byte> huffmanData = this.Configuration.MemoryAllocator.Allocate<byte>(256, AllocationOptions.Clean))
1102+
int length = remaining;
1103+
using (IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(totalBufferSize))
11011104
{
1102-
Span<byte> huffmanDataSpan = huffmanData.GetSpan();
1103-
ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanDataSpan);
1105+
Span<byte> bufferSpan = buffer.GetSpan();
1106+
Span<byte> huffmanLegthsSpan = bufferSpan.Slice(0, codeLengthsByteSize);
1107+
Span<byte> huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize);
1108+
Span<uint> tableWorkspace = MemoryMarshal.Cast<byte, uint>(bufferSpan.Slice(codeLengthsByteSize + codeValuesMaxByteSize));
1109+
11041110
for (int i = 2; i < remaining;)
11051111
{
11061112
byte huffmanTableSpec = (byte)stream.ReadByte();
@@ -1110,49 +1116,40 @@ private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int rem
11101116
// Types 0..1 DC..AC
11111117
if (tableType > 1)
11121118
{
1113-
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}");
1119+
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}.");
11141120
}
11151121

11161122
// Max tables of each type
11171123
if (tableIndex > 3)
11181124
{
1119-
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}");
1125+
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}.");
11201126
}
11211127

1122-
stream.Read(huffmanDataSpan, 0, 16);
1128+
stream.Read(huffmanLegthsSpan, 1, 16);
11231129

1124-
using (IMemoryOwner<byte> codeLengths = this.Configuration.MemoryAllocator.Allocate<byte>(17, AllocationOptions.Clean))
1130+
int codeLengthSum = 0;
1131+
for (int j = 1; j < 17; j++)
11251132
{
1126-
Span<byte> codeLengthsSpan = codeLengths.GetSpan();
1127-
ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengthsSpan);
1128-
int codeLengthSum = 0;
1129-
1130-
for (int j = 1; j < 17; j++)
1131-
{
1132-
codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1);
1133-
}
1133+
codeLengthSum += huffmanLegthsSpan[j];
1134+
}
11341135

1135-
length -= 17;
1136+
length -= 17;
11361137

1137-
if (codeLengthSum > 256 || codeLengthSum > length)
1138-
{
1139-
JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length.");
1140-
}
1138+
if (codeLengthSum > 256 || codeLengthSum > length)
1139+
{
1140+
JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length.");
1141+
}
11411142

1142-
using (IMemoryOwner<byte> huffmanValues = this.Configuration.MemoryAllocator.Allocate<byte>(256, AllocationOptions.Clean))
1143-
{
1144-
Span<byte> huffmanValuesSpan = huffmanValues.GetSpan();
1145-
stream.Read(huffmanValuesSpan, 0, codeLengthSum);
1143+
stream.Read(huffmanValuesSpan, 0, codeLengthSum);
11461144

1147-
i += 17 + codeLengthSum;
1145+
i += 17 + codeLengthSum;
11481146

1149-
this.scanDecoder.BuildHuffmanTable(
1150-
tableType,
1151-
tableIndex,
1152-
codeLengthsSpan,
1153-
huffmanValuesSpan);
1154-
}
1155-
}
1147+
this.scanDecoder.BuildHuffmanTable(
1148+
tableType,
1149+
tableIndex,
1150+
huffmanLegthsSpan,
1151+
huffmanValuesSpan.Slice(0, codeLengthSum),
1152+
tableWorkspace);
11561153
}
11571154
}
11581155
}

0 commit comments

Comments
 (0)