diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs new file mode 100644 index 0000000000..995fd550c9 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs @@ -0,0 +1,136 @@ +namespace ImageSharp.Formats.Jpeg.Port.Components +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Represents a HUffman Table + /// + internal sealed class HuffmanTable + { + private short[] huffcode = new short[257]; + private short[] huffsize = new short[257]; + private short[] valOffset = new short[18]; + private long[] maxcode = new long[18]; + + private byte[] huffval; + private byte[] bits; + + /// + /// Initializes a new instance of the class. + /// + /// The code lengths + /// The huffman values + public HuffmanTable(byte[] lengths, byte[] values) + { + this.huffval = new byte[values.Length]; + Buffer.BlockCopy(values, 0, this.huffval, 0, values.Length); + this.bits = new byte[lengths.Length]; + Buffer.BlockCopy(lengths, 0, this.bits, 0, lengths.Length); + + this.GenerateSizeTable(); + this.GenerateCodeTable(); + this.GenerateDecoderTables(); + } + + /// + /// Gets the Huffman value code at the given index + /// + /// The index + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short GetHuffVal(int i) + { + return this.huffval[i]; + } + + /// + /// Gets the max code at the given index + /// + /// The index + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetMaxCode(int i) + { + return this.maxcode[i]; + } + + /// + /// Gets the index to the locatation of the huffman value + /// + /// The index + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetValPtr(int i) + { + return this.valOffset[i]; + } + + /// + /// Figure C.1: make table of Huffman code length for each symbol + /// + private void GenerateSizeTable() + { + short index = 0; + for (short l = 1; l <= 16; l++) + { + byte i = this.bits[l]; + for (short j = 0; j < i; j++) + { + this.huffsize[index] = l; + index++; + } + } + + this.huffsize[index] = 0; + } + + /// + /// Figure C.2: generate the codes themselves + /// + private void GenerateCodeTable() + { + short k = 0; + short si = this.huffsize[0]; + short code = 0; + for (short i = 0; i < this.huffsize.Length; i++) + { + while (this.huffsize[k] == si) + { + this.huffcode[k] = code; + code++; + k++; + } + + code <<= 1; + si++; + } + } + + /// + /// Figure F.15: generate decoding tables for bit-sequential decoding + /// + private void GenerateDecoderTables() + { + short bitcount = 0; + for (int i = 1; i <= 16; i++) + { + if (this.bits[i] != 0) + { + // valoffset[l] = huffval[] index of 1st symbol of code length i, + // minus the minimum code of length i + this.valOffset[i] = (short)(bitcount - this.huffcode[bitcount]); + bitcount += this.bits[i]; + this.maxcode[i] = this.huffcode[bitcount - 1]; // maximum code of length i + } + else + { + this.maxcode[i] = -1; // -1 if no codes of this length + } + } + + this.valOffset[17] = 0; + this.maxcode[17] = 0xFFFFFL; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs index a8644d6451..8aeafd7db8 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs @@ -13,14 +13,14 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// internal class HuffmanTables { - private readonly HuffmanBranch[][] tables = new HuffmanBranch[4][]; + private readonly HuffmanTable[] tables = new HuffmanTable[4]; /// /// Gets or sets the table at the given index. /// /// The index /// The - public HuffmanBranch[] this[int index] + public HuffmanTable this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index 5a31fd89f4..1d588301fe 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -574,6 +574,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components #if DEBUG Debug.WriteLine($"DecodeScan - Unexpected marker {(this.bitsData << 8) | nextByte:X} at {stream.Position}"); #endif + // We've encountered an unexpected marker. Reverse the stream and exit. this.unexpectedMarkerReached = true; stream.Position -= 2; @@ -587,27 +588,31 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private short DecodeHuffman(HuffmanBranch[] tree, Stream stream) + private short DecodeHuffman(HuffmanTable tree, Stream stream) { - // TODO: This is our bottleneck. We should use a faster algorithm with a LUT. - HuffmanBranch[] node = tree; - while (true) + // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81 + int i = 1; + short code = (short)this.ReadBit(stream); + if (this.endOfStreamReached || this.unexpectedMarkerReached) { - int index = this.ReadBit(stream); - if (this.endOfStreamReached || this.unexpectedMarkerReached) - { - return -1; - } + return -1; + } - HuffmanBranch branch = node[index]; + while (code > tree.GetMaxCode(i)) + { + code <<= 1; + code |= (short)this.ReadBit(stream); - if (branch.Value > -1) + if (this.endOfStreamReached || this.unexpectedMarkerReached) { - return branch.Value; + return -1; } - node = branch.Children; + i++; } + + int j = tree.GetValPtr(i); + return tree.GetHuffVal((j + code) & 0xFF); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -682,6 +687,12 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } k += r; + + if (k > 63) + { + break; + } + byte z = QuantizationTables.DctZigZag[k]; short re = (short)this.ReceiveAndExtend(s, stream); component.BlockData[offset + z] = re; diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 6a1d6311c5..7bd71048c0 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Formats.Jpeg.Port { using System; using System.Collections.Generic; + using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; @@ -233,8 +234,6 @@ namespace ImageSharp.Formats.Jpeg.Port case JpegConstants.Markers.APP15: case JpegConstants.Markers.COM: - - // TODO: Read data block this.InputStream.Skip(remaining); break; @@ -676,28 +675,28 @@ namespace ImageSharp.Formats.Jpeg.Port throw new ImageFormatException($"DHT has wrong length: {remaining}"); } - using (var huffmanData = Buffer.CreateClean(16)) + using (var huffmanData = Buffer.CreateClean(256)) { for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); this.InputStream.Read(huffmanData.Array, 0, 16); - using (var codeLengths = Buffer.CreateClean(16)) + using (var codeLengths = Buffer.CreateClean(17)) { int codeLengthSum = 0; - for (int j = 0; j < 16; j++) + for (int j = 1; j < 17; j++) { - codeLengthSum += codeLengths[j] = huffmanData[j]; + codeLengthSum += codeLengths[j] = huffmanData[j - 1]; } - using (var huffmanValues = Buffer.CreateClean(codeLengthSum)) + using (var huffmanValues = Buffer.CreateClean(256)) { this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); i += 17 + codeLengthSum; - + Debug.WriteLine(huffmanTableSpec >> 4 == 0 ? "this.dcHuffmanTables" : "this.acHuffmanTables"); this.BuildHuffmanTable( huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, huffmanTableSpec & 15, @@ -812,53 +811,7 @@ namespace ImageSharp.Formats.Jpeg.Port /// The values private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, byte[] values) { - int length = 16; - while (length > 0 && codeLengths[length - 1] == 0) - { - length--; - } - - // TODO: Check the branch children capacity here. Seems to max at 2 - var code = new List { new HuffmanBranch(-1) }; - HuffmanBranch p = code[0]; - int k = 0; - - for (int i = 0; i < length; i++) - { - HuffmanBranch q; - for (int j = 0; j < codeLengths[i]; j++) - { - p = code.Pop(); - p.Children[p.Index] = new HuffmanBranch(values[k]); - while (p.Index > 0) - { - p = code.Pop(); - } - - p.Index++; - code.Add(p); - while (code.Count <= i) - { - q = new HuffmanBranch(-1); - code.Add(q); - p.Children[p.Index] = new HuffmanBranch(q.Children); - p = q; - } - - k++; - } - - if (i + 1 < length) - { - // p here points to last code - q = new HuffmanBranch(-1); - code.Add(q); - p.Children[p.Index] = new HuffmanBranch(q.Children); - p = q; - } - } - - tables[index] = code[0].Children; + tables[index] = new HuffmanTable(codeLengths, values); } ///