diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs
new file mode 100644
index 0000000000..d3c21e690f
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Huffman table group.
+ /// Includes special handling for the following cases:
+ /// - is_trivial_literal: one common literal base for RED/BLUE/ALPHA (not GREEN)
+ /// - is_trivial_code: only 1 code (no bit is read from bitstream)
+ /// - use_packed_table: few enough literal symbols, so all the bit codes
+ /// can fit into a small look-up table packed_table[]
+ /// The common literal base, if applicable, is stored in 'literal_arb'.
+ ///
+ internal class HTreeGroup
+ {
+ ///
+ /// This has a maximum of HuffmanCodesPerMetaCode (5) entrys.
+ ///
+ public List HTree { get; set; }
+
+ ///
+ /// True, if huffman trees for Red, Blue & Alpha Symbols are trivial (have a single code).
+ ///
+ public bool IsTrivialLiteral { get; set; }
+
+ ///
+ /// If is_trivial_literal is true, this is the ARGB value of the pixel, with Green channel being set to zero.
+ ///
+ public int LiteralArb { get; set; }
+
+ ///
+ /// True if is_trivial_literal with only one code.
+ ///
+ public bool IsTrivialCode { get; set; }
+
+ ///
+ /// use packed table below for short literal code
+ ///
+ public bool UsePackedTable { get; set; }
+
+ ///
+ /// Table mapping input bits to a packed values, or escape case to literal code.
+ ///
+ public HuffmanCode PackedTable { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/HuffmanCode.cs
index a5511335d5..b3133786c6 100644
--- a/src/ImageSharp/Formats/WebP/HuffmanCode.cs
+++ b/src/ImageSharp/Formats/WebP/HuffmanCode.cs
@@ -1,8 +1,11 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Diagnostics;
+
namespace SixLabors.ImageSharp.Formats.WebP
{
+ [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")]
internal class HuffmanCode
{
///
diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs
new file mode 100644
index 0000000000..a32dceff4a
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs
@@ -0,0 +1,146 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal static class HuffmanUtils
+ {
+ public static List BuildHuffmanTable(HuffmanCode[] table, int rootBits, int[] codeLengths, int codeLengthsSize)
+ {
+ Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits));
+ Guard.NotNull(codeLengths, nameof(codeLengths));
+ Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize));
+
+ // TODO: not sure yet howto store the codes properly
+ var huffmanCodes = new List();
+
+ // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length.
+ var sorted = new int[codeLengthsSize];
+ // total size root table + 2nd level table
+ int totalSize = 1 << rootBits;
+ // current code length
+ int len;
+ // symbol index in original or sorted table
+ int symbol;
+ // number of codes of each length:
+ var count = new int[WebPConstants.MaxAllowedCodeLength + 1];
+ // offsets in sorted table for each length
+ var offset = new int[WebPConstants.MaxAllowedCodeLength + 1];
+
+ // Build histogram of code lengths.
+ for (symbol = 0; symbol < codeLengthsSize; ++symbol)
+ {
+ if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength)
+ {
+ return huffmanCodes;
+ }
+
+ ++count[codeLengths[symbol]];
+ }
+
+ // Generate offsets into sorted symbol table by code length.
+ offset[1] = 0;
+ for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len)
+ {
+ if (count[len] > (1 << len))
+ {
+ return huffmanCodes;
+ }
+
+ offset[len + 1] = offset[len] + count[len];
+ }
+
+ // Sort symbols by length, by symbol order within each length.
+ for (symbol = 0; symbol < codeLengthsSize; ++symbol)
+ {
+ int symbolCodeLength = codeLengths[symbol];
+ if (codeLengths[symbol] > 0)
+ {
+ sorted[offset[symbolCodeLength]++] = symbol;
+ }
+ }
+
+ // Special case code with only one value.
+ if (offset[WebPConstants.MaxAllowedCodeLength] is 1)
+ {
+ var huffmanCode = new HuffmanCode()
+ {
+ BitsUsed = 0,
+ Value = sorted[0]
+ };
+ huffmanCodes.Add(huffmanCode);
+
+ return huffmanCodes;
+ }
+
+ int step; // step size to replicate values in current table
+ int low = -1; // low bits for current root entry
+ int mask = totalSize - 1; // mask for low bits
+ int key = 0; // reversed prefix code
+ int numNodes = 1; // number of Huffman tree nodes
+ int numOpen = 1; // number of open branches in current tree level
+ int tableBits = rootBits; // key length of current table
+ int tableSize = 1 << tableBits; // size of current table
+ symbol = 0;
+ // Fill in root table.
+ for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1)
+ {
+ numOpen <<= 1;
+ numNodes += numOpen;
+ numOpen -= count[len];
+ if (numOpen < 0)
+ {
+ return huffmanCodes;
+ }
+
+ for (; count[len] > 0; --count[len])
+ {
+ var huffmanCode = new HuffmanCode()
+ {
+ BitsUsed = len,
+ Value = sorted[symbol++]
+ };
+ huffmanCodes.Add(huffmanCode);
+ ReplicateValue(table, step, tableSize, huffmanCode);
+ key = GetNextKey(key, len);
+ }
+ }
+
+ return huffmanCodes;
+ }
+
+ ///
+ /// Stores code in table[0], table[step], table[2*step], ..., table[end].
+ /// Assumes that end is an integer multiple of step.
+ ///
+ private static void ReplicateValue(HuffmanCode[] table, int step, int end, HuffmanCode code)
+ {
+ Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step");
+
+ do
+ {
+ end -= step;
+ table[end] = code;
+ }
+ while (end > 0);
+ }
+
+ ///
+ /// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the
+ /// bit-wise reversal of the len least significant bits of key.
+ ///
+ private static int GetNextKey(int key, int len)
+ {
+ int step = 1 << (len - 1);
+ while ((key & step) != 0)
+ {
+ step >>= 1;
+ }
+
+ return step != 0 ? (key & (step - 1)) + step : key;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
index 2b90ee060c..282b643854 100644
--- a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Collections.Generic;
using System.IO;
@@ -87,6 +88,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
+ // TODO: not sure about the correct tabelSize here. Harcoded for now.
+ //int tableSize = kTableSize[colorCacheBits];
+ int tableSize = 2970;
+ var table = new HuffmanCode[numHtreeGroups * tableSize];
for (int i = 0; i < numHtreeGroupsMax; i++)
{
int size;
@@ -99,18 +104,22 @@ namespace SixLabors.ImageSharp.Formats.WebP
int alphabetSize = WebPConstants.kAlphabetSize[j];
if (j == 0 && colorCacheBits > 0)
{
- alphabetSize += 1 << colorCacheBits;
- size = this.ReadHuffmanCode(alphabetSize, codeLengths);
- /*if (size is 0)
+ if (j == 0 && colorCacheBits > 0)
+ {
+ alphabetSize += 1 << colorCacheBits;
+ }
+
+ size = this.ReadHuffmanCode(alphabetSize, codeLengths, table);
+ if (size is 0)
{
WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero");
- }*/
+ }
}
}
}
}
- private int ReadHuffmanCode(int alphabetSize, int[] codeLengths)
+ private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, HuffmanCode[] table)
{
bool simpleCode = this.bitReader.ReadBit();
if (simpleCode)
@@ -139,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
// (ii)Normal Code Length Code:
// The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths;
// the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros.
- var codeLengthCodeLengths = new int[WebPConstants.NumLengthCodes];
+ var codeLengthCodeLengths = new int[WebPConstants.NumCodeLengthCodes];
uint numCodes = this.bitReader.Read(4) + 4;
if (numCodes > WebPConstants.NumCodeLengthCodes)
{
@@ -151,7 +160,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
codeLengthCodeLengths[WebPConstants.KCodeLengthCodeOrder[i]] = (int)this.bitReader.Read(3);
}
- this.ReadHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths);
+ this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths);
}
int size = 0;
@@ -160,12 +169,12 @@ namespace SixLabors.ImageSharp.Formats.WebP
return size;
}
- private void ReadHuffmanCodeLengths(int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths)
+ private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths)
{
int maxSymbol;
int symbol = 0;
int prevCodeLen = WebPConstants.DefaultCodeLength;
- BuildHuffmanTable(WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes);
+ HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, WebPConstants.NumCodeLengthCodes);
if (this.bitReader.ReadBit())
{
int lengthNBits = 2 + (2 * (int)this.bitReader.Read(3));
@@ -216,99 +225,6 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
- private int BuildHuffmanTable(int rootBits, int[] codeLengths, int codeLengthsSize)
- {
- // sorted[code_lengths_size] is a pre-allocated array for sorting symbols by code length.
- var sorted = new int[codeLengthsSize];
- // total size root table + 2nd level table
- int totalSize = 1 << rootBits;
- // current code length
- int len;
- // symbol index in original or sorted table
- int symbol;
- // number of codes of each length:
- var count = new int[WebPConstants.MaxAllowedCodeLength + 1];
- // offsets in sorted table for each length
- var offset = new int[WebPConstants.MaxAllowedCodeLength + 1];
-
- // Build histogram of code lengths.
- for (symbol = 0; symbol < codeLengthsSize; ++symbol)
- {
- if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength)
- {
- return 0;
- }
-
- ++count[codeLengths[symbol]];
- }
-
- // Generate offsets into sorted symbol table by code length.
- offset[1] = 0;
- for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len)
- {
- if (count[len] > (1 << len))
- {
- return 0;
- }
-
- offset[len + 1] = offset[len] + count[len];
- }
-
- // Sort symbols by length, by symbol order within each length.
- for (symbol = 0; symbol < codeLengthsSize; ++symbol)
- {
- int symbolCodeLength = codeLengths[symbol];
- if (codeLengths[symbol] > 0)
- {
- sorted[offset[symbolCodeLength]++] = symbol;
- }
- }
-
- // Special case code with only one value.
- if (offset[WebPConstants.MaxAllowedCodeLength] is 1)
- {
- var huffmanCode = new HuffmanCode()
- {
- BitsUsed = 0,
- Value = sorted[0]
- };
-
- return totalSize;
- }
-
- int step; // step size to replicate values in current table
- int low = -1; // low bits for current root entry
- int mask = totalSize - 1; // mask for low bits
- int key = 0; // reversed prefix code
- int numNodes = 1; // number of Huffman tree nodes
- int numOpen = 1; // number of open branches in current tree level
- int tableBits = rootBits; // key length of current table
- int tableSize = 1 << tableBits; // size of current table
- symbol = 0;
- // Fill in root table.
- for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1)
- {
- numOpen <<= 1;
- numNodes += numOpen;
- numOpen -= count[len];
- if (numOpen < 0)
- {
- return 0;
- }
-
- for (; count[len] > 0; --count[len])
- {
- var code = new HuffmanCode()
- {
- BitsUsed = len,
- Value = sorted[symbol++]
- };
- }
- }
-
- return 0;
- }
-
private void ReadTransformations()
{
// Next bit indicates, if a transformation is present.