Browse Source

StoreHuffmanCode

pull/1552/head
Brian Popow 6 years ago
parent
commit
0ff8e48a61
  1. 15
      src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
  2. 8
      src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
  3. 26
      src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs
  4. 11
      src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeToken.cs
  5. 197
      src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
  6. 26
      src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs
  7. 12
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
  8. 294
      src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
  9. 169
      src/ImageSharp/Formats/WebP/WebPLookupTables.cs

15
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);
}
/// <summary>
/// Internal function for PutBits flushing 32 bits from the written state.
/// </summary>

8
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
{

26
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
{
/// <summary>
/// Represents the Huffman tree.
/// </summary>
internal class HuffmanTree
[DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")]
internal class HuffmanTree : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanTree"/> class.
/// </summary>
public HuffmanTree()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanTree"/> class.
/// </summary>
/// <param name="other">The HuffmanTree to create an instance from.</param>
private HuffmanTree(HuffmanTree other)
{
this.TotalCount = other.TotalCount;
this.Value = other.Value;
this.PoolIndexLeft = other.PoolIndexLeft;
this.PoolIndexRight = other.PoolIndexRight;
}
/// <summary>
/// Gets or sets the symbol frequency.
/// </summary>
@ -43,5 +65,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return (t1.Value < t2.Value) ? -1 : 1;
}
}
public IDeepCloneable DeepClone() => new HuffmanTree(this);
}
}

11
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
{
/// <summary>
/// Holds the tree header in coded form.
/// </summary>
[DebuggerDisplay("Code = {Code}, ExtraBits = {ExtraBits}")]
internal class HuffmanTreeToken
{
/// <summary>
/// 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).
/// </summary>
public byte Code { get; }
public byte Code { get; set; }
/// <summary>
/// Gets extra bits for escape codes.
/// Gets or sets the extra bits for escape codes.
/// </summary>
public byte ExtraBits { get; }
public byte ExtraBits { get; set; }
}
}

197
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
/// <summary>
/// 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.
/// </summary>
/// <see cref="http://en.wikipedia.org/wiki/Huffman_coding"/>
/// <param name="tree">The huffman tree.</param>
/// <param name="histogram">The historgram.</param>
/// <param name="histogramSize">The size of the histogram.</param>
/// <param name="treeDepthLimit">The tree depth limit.</param>
/// <param name="bitDepths">How many bits are used for the symbol.</param>
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<HuffmanTreeToken> 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<HuffmanCode> 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<HuffmanTreeToken> 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<HuffmanTreeToken> 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;
}
/// <summary>
/// Get the actual bit values for a tree of bit depths.
/// </summary>
@ -518,7 +657,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// <summary>
/// Heuristics for selecting the stride ranges to collapse.
/// </summary>
private static bool ValuesShouldBeCollapsedToStrideAverage(uint a, uint b)
private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b)
{
return Math.Abs(a - b) < 4;
}

26
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);
}
}
/// <summary>
/// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green').
/// </summary>
@ -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<uint> input, int startIdx, int numberOfPixels, Span<uint> output)
{
int endIdx = startIdx + numberOfPixels;

12
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];

294
src/ImageSharp/Formats/WebP/WebPEncoderCore.cs

@ -185,10 +185,19 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void EncodeImageNoHuffman(Span<uint> 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<HuffmanTreeCode> codes = huffmanCodes.AsSpan(5 * histogramIx);
using List<PixOrCopy>.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++;
}
}
}
/// <summary>
@ -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<TPixel>();
for (int y = 0; y < image.Height; y++)
{
System.Span<TPixel> rowSpan = image.GetPixelRowSpan(y);
Span<TPixel> 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;
}
}
/// <summary>
/// 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;

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

Loading…
Cancel
Save