diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index 3cb554a641..afb91b43a1 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System; +using SixLabors.ImageSharp.Formats.WebP.Lossless; namespace SixLabors.ImageSharp.Formats.WebP.BitWriter { @@ -70,6 +71,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter } } + public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex) + { + int depth = code.CodeLengths[codeIndex]; + int symbol = code.Codes[codeIndex]; + this.PutBits((uint)symbol, depth); + } + + public void WriteHuffmanCodeWithExtraBits(HuffmanTreeCode code, int codeIndex, int bits, int nBits) + { + int depth = code.CodeLengths[codeIndex]; + int symbol = code.Codes[codeIndex]; + this.PutBits((uint)((bits << depth) | symbol), depth + nBits); + } + /// /// Internal function for PutBits flushing 32 bits from the written state. /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs index 49e9edd2f9..d9532c91b5 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs @@ -419,7 +419,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless short[] chosenPath; int chosenPathSize = 0; - // TODO: + // TODO: implement this // BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); // TraceBackwards(distArray, distArraySize, chosenPath, chosenPathSize); // BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); @@ -524,7 +524,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (bgra[i] == bgra[i + 1]) { // Max out the counts to MAX_LENGTH. - counts[countsPos] = counts[countsPos + 1]; // TODO: + (counts[1] != MaxLength); + counts[countsPos] = counts[countsPos + 1]; + if (counts[countsPos + 1] != MaxLength) + { + counts[countsPos]++; + } } else { diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs index ae96c85792..daf807335e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs @@ -1,13 +1,35 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Represents the Huffman tree. /// - internal class HuffmanTree + [DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] + internal class HuffmanTree : IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + public HuffmanTree() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The HuffmanTree to create an instance from. + private HuffmanTree(HuffmanTree other) + { + this.TotalCount = other.TotalCount; + this.Value = other.Value; + this.PoolIndexLeft = other.PoolIndexLeft; + this.PoolIndexRight = other.PoolIndexRight; + } + /// /// Gets or sets the symbol frequency. /// @@ -43,5 +65,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (t1.Value < t2.Value) ? -1 : 1; } } + + public IDeepCloneable DeepClone() => new HuffmanTree(this); } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs index a140a2c21b..3708838c2e 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs @@ -1,21 +1,24 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Holds the tree header in coded form. /// + [DebuggerDisplay("Code = {Code}, ExtraBits = {ExtraBits}")] internal class HuffmanTreeToken { /// - /// Gets the code. Value (0..15) or escape code (16, 17, 18). + /// Gets or sets the code. Value (0..15) or escape code (16, 17, 18). /// - public byte Code { get; } + public byte Code { get; set; } /// - /// Gets extra bits for escape codes. + /// Gets or sets the extra bits for escape codes. /// - public byte ExtraBits { get; } + public byte ExtraBits { get; set; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs index 067c68eccb..885d99a135 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs @@ -28,8 +28,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode) { int numSymbols = huffCode.NumSymbols; + bufRle.AsSpan().Fill(false); OptimizeHuffmanForRle(numSymbols, bufRle, histogram); - GenerateOptimalTree(huffTree, histogram, numSymbols, huffCode.CodeLengths); + GenerateOptimalTree(huffTree, histogram, numSymbols, treeDepthLimit, huffCode.CodeLengths); // Create the actual bit codes for the bit lengths. ConvertBitDepthsToSymbols(huffCode); @@ -42,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] counts) { // 1) Let's make the Huffman code more compatible with rle encoding. - for (; length >= 0; --length) + for (; length >= 0; length--) { if (length == 0) { @@ -58,19 +59,17 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // 2) Let's mark all population counts that already can be encoded with an rle code. // Let's not spoil any of the existing good rle codes. - // Mark any seq of 0's that is longer as 5 as a good_for_rle. - // Mark any seq of non-0's that is longer as 7 as a good_for_rle. + // Mark any seq of 0's that is longer as 5 as a goodForRle. + // Mark any seq of non-0's that is longer as 7 as a goodForRle. uint symbol = counts[0]; int stride = 0; - for (int i = 0; i < length + 1; ++i) + for (int i = 0; i < length + 1; i++) { if (i == length || counts[i] != symbol) { - if ((symbol == 0 && stride >= 5) || - (symbol != 0 && stride >= 7)) + if ((symbol == 0 && stride >= 5) || (symbol != 0 && stride >= 7)) { - int k; - for (k = 0; k < stride; ++k) + for (int k = 0; k < stride; k++) { goodForRle[i - k - 1] = true; } @@ -84,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } else { - ++stride; + stride++; } } @@ -94,9 +93,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless uint sum = 0; for (int i = 0; i < length + 1; i++) { - if (i == length || goodForRle[i] || - (i != 0 && goodForRle[i - 1]) || - !ValuesShouldBeCollapsedToStrideAverage(counts[i], limit)) + var valuesShouldBeCollapsedToStrideAverage = ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit); + if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !valuesShouldBeCollapsedToStrideAverage) { if (stride >= 4 || (stride >= 3 && sum == 0)) { @@ -115,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless count = 0; } - for (k = 0; k < stride; ++k) + for (k = 0; k < stride; k++) { // We don't want to change value at counts[i], // that is already belonging to the next stride. Thus - 1. @@ -127,8 +125,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless sum = 0; if (i < length - 3) { - // All interesting strides have a count of at least 4, - // at least when non-zeros. + // All interesting strides have a count of at least 4, at least when non-zeros. limit = (counts[i] + counts[i + 1] + counts[i + 2] + counts[i + 3] + 2) / 4; } @@ -142,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } - ++stride; + stride++; if (i != length) { sum += counts[i]; @@ -156,19 +153,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Create an optimal Huffman tree. - /// - /// The catch here is that the tree cannot be arbitrarily deep - /// - /// This algorithm is not of excellent performance for very long data blocks, - /// especially when population counts are longer than 2**tree_limit, but - /// we are not planning to use this with extremely long blocks. /// /// /// The huffman tree. /// The historgram. /// The size of the histogram. + /// The tree depth limit. /// How many bits are used for the symbol. - public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, byte[] bitDepths) + public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) { uint countMin; int treeSizeOrig = 0; @@ -211,7 +203,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } // Build the Huffman tree. - Array.Sort(tree, HuffmanTree.Compare); + HuffmanTree[] treeCopy = tree.AsSpan().Slice(0, treeSize).ToArray(); + Array.Sort(treeCopy, HuffmanTree.Compare); + treeCopy.AsSpan().CopyTo(tree); if (treeSize > 1) { @@ -220,8 +214,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless while (treeSize > 1) { // Finish when we have only one root. - treePool[treePoolSize++] = tree[treeSize - 1]; - treePool[treePoolSize++] = tree[treeSize - 2]; + treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 1].DeepClone(); + treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 2].DeepClone(); int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; treeSize -= 2; @@ -235,9 +229,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + var endIdx = k + 1; + var num = treeSize - k; + var startIdx = endIdx + num - 1; + for (int i = startIdx; i >= endIdx; i--) + { + tree[i] = (HuffmanTree)tree[i - 1].DeepClone(); + } + tree[k].TotalCount = count; tree[k].Value = -1; - tree[k].PoolIndexLeft = treePoolSize - 1; tree[k].PoolIndexRight = treePoolSize - 2; treeSize = treeSize + 1; @@ -250,9 +251,59 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Trivial case: only one element. bitDepths[tree[0].Value] = 1; } + + // Test if this Huffman tree satisfies our 'treeDepthLimit' criteria. + int maxDepth = bitDepths[0]; + for (int j = 1; j < histogramSize; j++) + { + if (maxDepth < bitDepths[j]) + { + maxDepth = bitDepths[j]; + } + } + + if (maxDepth <= treeDepthLimit) + { + break; + } } } + public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokens) + { + int depthSize = tree.NumSymbols; + int prevValue = 8; // 8 is the initial value for rle. + int i = 0; + int tokenIdx = 0; + Span tokenSpan = tokens.AsSpan(); + while (i < depthSize) + { + int value = tree.CodeLengths[i]; + int k = i + 1; + int runs; + while (k < depthSize && tree.CodeLengths[k] == value) + { + k++; + } + + runs = k - i; + if (value == 0) + { + tokenIdx += CodeRepeatedZeros(runs, tokens); + } + else + { + tokenIdx += CodeRepeatedValues(runs, tokens, value, prevValue); + prevValue = value; + } + + tokenSpan.Slice(tokenIdx); + i += runs; + } + + return tokenIdx; + } + public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); @@ -400,6 +451,94 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return totalSize; } + private static int CodeRepeatedZeros(int repetitions, Span tokens) + { + int pos = 0; + while (repetitions >= 1) + { + if (repetitions < 3) + { + int i; + for (i = 0; i < repetitions; ++i) + { + tokens[pos].Code = 0; // 0-value + tokens[pos].ExtraBits = 0; + pos++; + } + + break; + } + else if (repetitions < 11) + { + tokens[pos].Code = 17; + tokens[pos].ExtraBits = (byte)(repetitions - 3); + pos++; + break; + } + else if (repetitions < 139) + { + tokens[pos].Code = 18; + tokens[pos].ExtraBits = (byte)(repetitions - 11); + pos++; + break; + } + else + { + tokens[pos].Code = 18; + tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s + pos++; + repetitions -= 138; + } + } + + return pos; + } + + private static int CodeRepeatedValues(int repetitions, Span tokens, int value, int prevValue) + { + int pos = 0; + + if (value != prevValue) + { + tokens[pos].Code = (byte)value; + tokens[pos].ExtraBits = 0; + pos++; + repetitions--; + } + + while (repetitions >= 1) + { + if (repetitions < 3) + { + int i; + for (i = 0; i < repetitions; ++i) + { + tokens[pos].Code = (byte)value; + tokens[pos].ExtraBits = 0; + pos++; + } + + break; + } + else if (repetitions < 7) + { + tokens[pos].Code = 16; + tokens[pos].ExtraBits = (byte)(repetitions - 3); + pos++; + break; + } + else + { + tokens[pos].Code = 16; + tokens[pos].ExtraBits = 3; + pos++; + repetitions -= 6; + } + } + + return pos; + } + /// /// Get the actual bit values for a tree of bit depths. /// @@ -518,7 +657,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// /// Heuristics for selecting the stride ranges to collapse. /// - private static bool ValuesShouldBeCollapsedToStrideAverage(uint a, uint b) + private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) { return Math.Abs(a - b) < 4; } diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index 0197c66dbf..c3cf4cb2da 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -40,6 +40,22 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless } } + public static int PrefixEncode(int distance, ref int extraBits, ref int extraBitsValue) + { + if (distance < PrefixLookupIdxMax) + { + (int code, int extraBits) prefixCode = WebPLookupTables.PrefixEncodeCode[distance]; + extraBits = prefixCode.extraBits; + extraBitsValue = WebPLookupTables.PrefixEncodeExtraBitsValue[distance]; + + return prefixCode.code; + } + else + { + return PrefixEncodeNoLUT(distance, ref extraBits, ref extraBitsValue); + } + } + /// /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). /// @@ -426,6 +442,16 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return code; } + private static int PrefixEncodeNoLUT(int distance, ref int extraBits, ref int extraBitsValue) + { + int highestBit = WebPCommonUtils.BitsLog2Floor((uint)--distance); + int secondHighestBit = (distance >> (highestBit - 1)) & 1; + extraBits = highestBit - 1; + extraBitsValue = distance & ((1 << extraBits) - 1); + int code = (2 * highestBit) + secondHighestBit; + return code; + } + private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs index 1a6734a984..ec326905a7 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs @@ -27,11 +27,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public Vp8LHistogram() { - this.Red = new uint[WebPConstants.NumLiteralCodes]; - this.Blue = new uint[WebPConstants.NumLiteralCodes]; - this.Alpha = new uint[WebPConstants.NumLiteralCodes]; - this.Distance = new uint[WebPConstants.NumLiteralCodes]; - this.Literal = new uint[WebPConstants.NumLiteralCodes]; // TODO: is this enough? + this.Red = new uint[WebPConstants.NumLiteralCodes + 1]; + this.Blue = new uint[WebPConstants.NumLiteralCodes + 1]; + this.Alpha = new uint[WebPConstants.NumLiteralCodes + 1]; + this.Distance = new uint[WebPConstants.NumDistanceCodes]; + + var literalSize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0); + this.Literal = new uint[literalSize]; // 5 for literal, red, blue, alpha, distance. this.IsUsed = new bool[5]; diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index 8953842504..39f01e30d6 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -185,10 +185,19 @@ namespace SixLabors.ImageSharp.Formats.WebP private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) { - var huffmanCodes = new HuffmanTreeCode[5]; int cacheBits = 0; - HuffmanTreeToken[] tokens; + var histogramSymbols = new short[1]; // Only one tree, one symbol. + var huffmanCodes = new HuffmanTreeCode[5]; + for (int i = 0; i < huffmanCodes.Length; i++) + { + huffmanCodes[i] = new HuffmanTreeCode(); + } + var huffTree = new HuffmanTree[3UL * WebPConstants.CodeLengthCodes]; + for (int i = 0; i < huffTree.Length; i++) + { + huffTree[i] = new HuffmanTree(); + } // Calculate backward references from ARGB image. BackwardReferenceEncoder.HashChainFill(hashChain, bgra, quality, width, height); @@ -219,30 +228,206 @@ namespace SixLabors.ImageSharp.Formats.WebP this.bitWriter.PutBits(0, 1); // Find maximum number of symbols for the huffman tree-set. - /*for (i = 0; i < 5; ++i) + int maxTokens = 0; + for (int i = 0; i < 5; i++) { - HuffmanTreeCode * const codes = &huffman_codes[i]; - if (max_tokens < codes->num_symbols) + HuffmanTreeCode codes = huffmanCodes[i]; + if (maxTokens < codes.NumSymbols) { - max_tokens = codes->num_symbols; + maxTokens = codes.NumSymbols; } - }*/ + } + + var tokens = new HuffmanTreeToken[maxTokens]; + for(int i = 0; i < tokens.Length; i++) + { + tokens[i] = new HuffmanTreeToken(); + } // Store Huffman codes. - /* - for (i = 0; i < 5; ++i) + for (int i = 0; i < 5; i++) { - HuffmanTreeCode * const codes = &huffman_codes[i]; - StoreHuffmanCode(bw, huff_tree, tokens, codes); + HuffmanTreeCode codes = huffmanCodes[i]; + this.StoreHuffmanCode(huffTree, tokens, codes); ClearHuffmanTreeIfOnlyOneSymbol(codes); } // Store actual literals. - StoreImageToBitMask(bw, width, 0, refs, histogram_symbols, huffman_codes); - */ + this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes); } - private void StoreImageToBitMask(int width, int histoBits, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) + private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) + { + int count = 0; + int[] symbols = { 0, 0 }; + int maxBits = 8; + int maxSymbol = 1 << maxBits; + + // Check whether it's a small tree. + for (int i = 0; i < huffmanCode.NumSymbols && count < 3; ++i) + { + if (huffmanCode.CodeLengths[i] != 0) + { + if (count < 2) + { + symbols[count] = i; + } + + count++; + } + } + + if (count == 0) + { + // emit minimal tree for empty cases + // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 + this.bitWriter.PutBits(0x01, 4); + } + else if (count <= 2 && symbols[0] < maxSymbol && symbols[1] < maxSymbol) + { + this.bitWriter.PutBits(1, 1); // Small tree marker to encode 1 or 2 symbols. + this.bitWriter.PutBits((uint)(count - 1), 1); + if (symbols[0] <= 1) + { + this.bitWriter.PutBits(0, 1); // Code bit for small (1 bit) symbol value. + this.bitWriter.PutBits((uint)symbols[0], 1); + } + else + { + this.bitWriter.PutBits(1, 1); + this.bitWriter.PutBits((uint)symbols[0], 8); + } + + if (count == 2) + { + this.bitWriter.PutBits((uint)symbols[1], 8); + } + } + else + { + this.StoreFullHuffmanCode(huffTree, tokens, huffmanCode); + } + } + + private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) + { + int numTokens; + int i; + byte[] codeLengthBitdepth = new byte[WebPConstants.CodeLengthCodes]; + short[] codeLengthBitdepthSymbols = new short[WebPConstants.CodeLengthCodes]; + var huffmanCode = new HuffmanTreeCode(); + huffmanCode.NumSymbols = WebPConstants.CodeLengthCodes; + huffmanCode.CodeLengths = codeLengthBitdepth; + huffmanCode.Codes = codeLengthBitdepthSymbols; + + this.bitWriter.PutBits(0, 1); + numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); + uint[] histogram = new uint[WebPConstants.CodeLengthCodes + 1]; + bool[] bufRle = new bool[WebPConstants.CodeLengthCodes + 1]; + for (i = 0; i < numTokens; i++) + { + histogram[tokens[i].Code]++; + } + + HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode); + this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitdepth); + ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode); + + int trailingZeroBits = 0; + int trimmedLength = numTokens; + bool writeTrimmedLength; + int length; + i = numTokens; + while (i-- > 0) + { + int ix = tokens[i].Code; + if (ix == 0 || ix == 17 || ix == 18) + { + trimmedLength--; // discount trailing zeros. + trailingZeroBits += codeLengthBitdepth[ix]; + if (ix == 17) + { + trailingZeroBits += 3; + } + else if (ix == 18) + { + trailingZeroBits += 7; + } + } + else + { + break; + } + } + + writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; + length = writeTrimmedLength ? trimmedLength : numTokens; + this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); + if (writeTrimmedLength) + { + if (trimmedLength == 2) + { + this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmed_length=2 + } + else + { + int nbits = WebPCommonUtils.BitsLog2Floor((uint)trimmedLength - 2); + int nbitpairs = (nbits / 2) + 1; + this.bitWriter.PutBits((uint)nbitpairs - 1, 3); + this.bitWriter.PutBits((uint)trimmedLength - 2, nbitpairs * 2); + } + } + + this.StoreHuffmanTreeToBitMask(tokens, length, huffmanCode); + } + + private void StoreHuffmanTreeToBitMask(HuffmanTreeToken[] tokens, int numTokens, HuffmanTreeCode huffmanCode) + { + for (int i = 0; i < numTokens; i++) + { + int ix = tokens[i].Code; + int extraBits = tokens[i].ExtraBits; + this.bitWriter.PutBits((uint)huffmanCode.Codes[ix], huffmanCode.CodeLengths[ix]); + switch (ix) + { + case 16: + this.bitWriter.PutBits((uint)extraBits, 2); + break; + case 17: + this.bitWriter.PutBits((uint)extraBits, 3); + break; + case 18: + this.bitWriter.PutBits((uint)extraBits, 7); + break; + } + } + } + + private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitdepth) + { + // RFC 1951 will calm you down if you are worried about this funny sequence. + // This sequence is tuned from that, but more weighted for lower symbol count, + // and more spiking histograms. + byte[] storageOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + // Throw away trailing zeros: + int codesToStore = WebPConstants.CodeLengthCodes; + for (; codesToStore > 4; codesToStore--) + { + if (codeLengthBitdepth[storageOrder[codesToStore - 1]] != 0) + { + break; + } + } + + this.bitWriter.PutBits((uint)codesToStore - 4, 4); + for (int i = 0; i < codesToStore; i++) + { + this.bitWriter.PutBits(codeLengthBitdepth[storageOrder[i]], 3); + } + } + + private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, short[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) { int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; int tileMask = (histoBits == 0) ? 0 : -(1 << histoBits); @@ -254,7 +439,56 @@ namespace SixLabors.ImageSharp.Formats.WebP int tileY = y & tileMask; int histogramIx = histogramSymbols[0]; Span codes = huffmanCodes.AsSpan(5 * histogramIx); + using List.Enumerator c = backwardRefs.Refs.GetEnumerator(); + while (c.MoveNext()) + { + PixOrCopy v = c.Current; + if ((tileX != (x & tileMask)) || (tileY != (y & tileMask))) + { + tileX = x & tileMask; + tileY = y & tileMask; + histogramIx = histogramSymbols[((y >> histoBits) * histoXSize) + (x >> histoBits)]; + codes = huffmanCodes.AsSpan(5 * histogramIx); + } + if (v.IsLiteral()) + { + byte[] order = { 1, 2, 0, 3 }; + for (int k = 0; k < 4; k++) + { + int code = (int)v.Literal(order[k]); + this.bitWriter.WriteHuffmanCode(codes[k], code); + } + } + else if (v.IsCacheIdx()) + { + int code = (int)v.CacheIdx(); + int literalIx = 256 + WebPConstants.NumLengthCodes + code; + this.bitWriter.WriteHuffmanCode(codes[0], literalIx); + } + else + { + int bits = 0; + int nBits = 0; + int distance = (int)v.Distance(); + int code = LosslessUtils.PrefixEncode(v.Len, ref nBits, ref bits); + this.bitWriter.WriteHuffmanCodeWithExtraBits(codes[0], 256 + code, bits, nBits); + + // Don't write the distance with the extra bits code since + // the distance can be up to 18 bits of extra bits, and the prefix + // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. + code = LosslessUtils.PrefixEncode(distance, ref nBits, ref bits); + this.bitWriter.WriteHuffmanCode(codes[4], code); + this.bitWriter.PutBits((uint)bits, nBits); + } + + x += v.Length(); + while (x >= width) + { + x -= width; + y++; + } + } } /// @@ -438,7 +672,6 @@ namespace SixLabors.ImageSharp.Formats.WebP return false; } - // TODO: figure out how the palette needs to be sorted. uint[] paletteArray = palette.Slice(0, enc.PaletteSize).ToArray(); Array.Sort(paletteArray); paletteArray.CopyTo(palette); @@ -464,7 +697,7 @@ namespace SixLabors.ImageSharp.Formats.WebP var colors = new HashSet(); for (int y = 0; y < image.Height; y++) { - System.Span rowSpan = image.GetPixelRowSpan(y); + Span rowSpan = image.GetPixelRowSpan(y); for (int x = 0; x < rowSpan.Length; x++) { colors.Add(rowSpan[x]); @@ -488,6 +721,28 @@ namespace SixLabors.ImageSharp.Formats.WebP return colors.Count; } + private static void ClearHuffmanTreeIfOnlyOneSymbol(HuffmanTreeCode huffmanCode) + { + int count = 0; + for (int k = 0; k < huffmanCode.NumSymbols; k++) + { + if (huffmanCode.CodeLengths[k] != 0) + { + count++; + if (count > 1) + { + return; + } + } + } + + for (int k = 0; k < huffmanCode.NumSymbols; k++) + { + huffmanCode.CodeLengths[k] = 0; + huffmanCode.Codes[k] = 0; + } + } + /// /// The palette has been sorted by alpha. This function checks if the other components of the palette /// have a monotonic development with regards to position in the palette. @@ -549,7 +804,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } } - // swap color(palette[bestIdx], palette[i]); + // Swap color(palette[bestIdx], palette[i]); uint best = palette[bestIdx]; palette[bestIdx] = palette[i]; palette[i] = best; @@ -592,6 +847,11 @@ namespace SixLabors.ImageSharp.Formats.WebP // Create Huffman trees. bool[] bufRle = new bool[maxNumSymbols]; var huffTree = new HuffmanTree[3 * maxNumSymbols]; + for (int i = 0; i < huffTree.Length; i++) + { + huffTree[i] = new HuffmanTree(); + } + for (int i = 0; i < histogramImage.Count; i++) { int codesStartIdx = 5 * i; diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index ae66e90e40..43ded2c512 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -656,72 +656,109 @@ namespace SixLabors.ImageSharp.Formats.WebP } }; - public static (int code, int extraBits)[] PrefixEncodeCode = new (int code, int extraBits)[] - { - (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), - (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), - (7, 2), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), - (8, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), - (9, 3), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), - (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), - (10, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), - (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), - (11, 4), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + public static readonly (int code, int extraBits)[] PrefixEncodeCode = new (int code, int extraBits)[] + { + (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), + (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), + (7, 2), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), + (8, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), + (9, 3), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), + (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), + (10, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), + (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), + (11, 4), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + }; + + public static readonly byte[] PrefixEncodeExtraBitsValue = + { + 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, + 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126 }; static WebPLookupTables()