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()