diff --git a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs
index b482a861db..40f0ae5f7d 100644
--- a/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs
+++ b/src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs
@@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitReader
range = split + 1;
}
- int shift = 7 ^ this.BitsLog2Floor(range);
+ int shift = 7 ^ WebPCommonUtils.BitsLog2Floor(range);
range <<= shift;
this.bits -= shift;
@@ -229,19 +229,5 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitReader
x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8);
return x;
}
-
- // Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n).
- [MethodImpl(InliningOptions.ShortMethod)]
- private int BitsLog2Floor(uint n)
- {
- int logValue = 0;
- while (n >= 256)
- {
- logValue += 8;
- n >>= 8;
- }
-
- return logValue + WebPLookupTables.LogTable8bit[n];
- }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs
index 3fba3327dd..ae96c85792 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs
@@ -9,23 +9,39 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
internal class HuffmanTree
{
///
- /// Gets the symbol frequency.
+ /// Gets or sets the symbol frequency.
///
- public int TotalCount { get; }
+ public int TotalCount { get; set; }
///
- /// Gets the symbol value.
+ /// Gets or sets the symbol value.
///
- public int Value { get; }
+ public int Value { get; set; }
///
- /// Gets the index for the left sub-tree.
+ /// Gets or sets the index for the left sub-tree.
///
- public int PoolIndexLeft { get; }
+ public int PoolIndexLeft { get; set; }
///
- /// Gets the index for the right sub-tree.
+ /// Gets or sets the index for the right sub-tree.
///
- public int PoolIndexRight { get; }
+ public int PoolIndexRight { get; set; }
+
+ public static int Compare(HuffmanTree t1, HuffmanTree t2)
+ {
+ if (t1.TotalCount > t2.TotalCount)
+ {
+ return -1;
+ }
+ else if (t1.TotalCount < t2.TotalCount)
+ {
+ return 1;
+ }
+ else
+ {
+ return (t1.Value < t2.Value) ? -1 : 1;
+ }
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs
index fe582b8f29..459a53de84 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs
@@ -9,18 +9,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
internal class HuffmanTreeCode
{
///
- /// Gets the number of symbols.
+ /// Gets or sets the number of symbols.
///
- public int NumSymbols { get; }
+ public int NumSymbols { get; set; }
///
- /// Gets the code lengths of the symbols.
+ /// Gets or sets the code lengths of the symbols.
///
- public byte[] CodeLengths { get; }
+ public byte[] CodeLengths { get; set; }
///
- /// Gets the symbol Codes.
+ /// Gets or sets the symbol Codes.
///
- public short[] Codes { get; }
+ public short[] Codes { get; set; }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
index fb1de6bef9..067c68eccb 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
@@ -18,6 +18,241 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits;
+ // Pre-reversed 4-bit values.
+ private static byte[] reversedBits =
+ {
+ 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
+ 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf
+ };
+
+ public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode)
+ {
+ int numSymbols = huffCode.NumSymbols;
+ OptimizeHuffmanForRle(numSymbols, bufRle, histogram);
+ GenerateOptimalTree(huffTree, histogram, numSymbols, huffCode.CodeLengths);
+
+ // Create the actual bit codes for the bit lengths.
+ ConvertBitDepthsToSymbols(huffCode);
+ }
+
+ ///
+ /// Change the population counts in a way that the consequent
+ /// Huffman tree compression, especially its RLE-part, give smaller output.
+ ///
+ 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)
+ {
+ if (length == 0)
+ {
+ return; // All zeros.
+ }
+
+ if (counts[length - 1] != 0)
+ {
+ // Now counts[0..length - 1] does not have trailing zeros.
+ break;
+ }
+ }
+
+ // 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.
+ uint symbol = counts[0];
+ int stride = 0;
+ for (int i = 0; i < length + 1; ++i)
+ {
+ if (i == length || counts[i] != symbol)
+ {
+ if ((symbol == 0 && stride >= 5) ||
+ (symbol != 0 && stride >= 7))
+ {
+ int k;
+ for (k = 0; k < stride; ++k)
+ {
+ goodForRle[i - k - 1] = true;
+ }
+ }
+
+ stride = 1;
+ if (i != length)
+ {
+ symbol = counts[i];
+ }
+ }
+ else
+ {
+ ++stride;
+ }
+ }
+
+ // 3) Let's replace those population counts that lead to more rle codes.
+ stride = 0;
+ uint limit = counts[0];
+ 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))
+ {
+ if (stride >= 4 || (stride >= 3 && sum == 0))
+ {
+ uint k;
+
+ // The stride must end, collapse what we have, if we have enough (4).
+ uint count = (uint)((sum + (stride / 2)) / stride);
+ if (count < 1)
+ {
+ count = 1;
+ }
+
+ if (sum == 0)
+ {
+ // Don't make an all zeros stride to be upgraded to ones.
+ count = 0;
+ }
+
+ 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.
+ counts[i - k - 1] = count;
+ }
+ }
+
+ stride = 0;
+ sum = 0;
+ if (i < length - 3)
+ {
+ // 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;
+ }
+ else if (i < length)
+ {
+ limit = counts[i];
+ }
+ else
+ {
+ limit = 0;
+ }
+ }
+
+ ++stride;
+ if (i != length)
+ {
+ sum += counts[i];
+ if (stride >= 4)
+ {
+ limit = (uint)((sum + (stride / 2)) / stride);
+ }
+ }
+ }
+ }
+
+ ///
+ /// 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.
+ /// How many bits are used for the symbol.
+ public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, byte[] bitDepths)
+ {
+ uint countMin;
+ int treeSizeOrig = 0;
+
+ for (int i = 0; i < histogramSize; i++)
+ {
+ if (histogram[i] != 0)
+ {
+ treeSizeOrig++;
+ }
+ }
+
+ if (treeSizeOrig == 0)
+ {
+ return;
+ }
+
+ Span treePool = tree.AsSpan(treeSizeOrig);
+
+ // For block sizes with less than 64k symbols we never need to do a
+ // second iteration of this loop.
+ for (countMin = 1; ; countMin *= 2)
+ {
+ int treeSize = treeSizeOrig;
+
+ // We need to pack the Huffman tree in treeDepthLimit bits.
+ // So, we try by faking histogram entries to be at least 'countMin'.
+ int idx = 0;
+ for (int j = 0; j < histogramSize; j++)
+ {
+ if (histogram[j] != 0)
+ {
+ uint count = (histogram[j] < countMin) ? countMin : histogram[j];
+ tree[idx].TotalCount = (int)count;
+ tree[idx].Value = j;
+ tree[idx].PoolIndexLeft = -1;
+ tree[idx].PoolIndexRight = -1;
+ idx++;
+ }
+ }
+
+ // Build the Huffman tree.
+ Array.Sort(tree, HuffmanTree.Compare);
+
+ if (treeSize > 1)
+ {
+ // Normal case.
+ int treePoolSize = 0;
+ while (treeSize > 1)
+ {
+ // Finish when we have only one root.
+ treePool[treePoolSize++] = tree[treeSize - 1];
+ treePool[treePoolSize++] = tree[treeSize - 2];
+ int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount;
+ treeSize -= 2;
+
+ // Search for the insertion point.
+ int k;
+ for (k = 0; k < treeSize; k++)
+ {
+ if (tree[k].TotalCount <= count)
+ {
+ break;
+ }
+ }
+
+ tree[k].TotalCount = count;
+ tree[k].Value = -1;
+
+ tree[k].PoolIndexLeft = treePoolSize - 1;
+ tree[k].PoolIndexRight = treePoolSize - 2;
+ treeSize = treeSize + 1;
+ }
+
+ SetBitDepths(tree, treePool, bitDepths, 0);
+ }
+ else if (treeSize == 1)
+ {
+ // Trivial case: only one element.
+ bitDepths[tree[0].Value] = 1;
+ }
+ }
+ }
+
public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize)
{
Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits));
@@ -165,6 +400,68 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return totalSize;
}
+ ///
+ /// Get the actual bit values for a tree of bit depths.
+ ///
+ /// The hiffman tree.
+ private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree)
+ {
+ // 0 bit-depth means that the symbol does not exist.
+ uint[] nextCode = new uint[WebPConstants.MaxAllowedCodeLength + 1];
+ int[] depthCount = new int[WebPConstants.MaxAllowedCodeLength + 1];
+
+ int len = tree.NumSymbols;
+ for (int i = 0; i < len; i++)
+ {
+ int codeLength = tree.CodeLengths[i];
+ depthCount[codeLength]++;
+ }
+
+ depthCount[0] = 0; // ignore unused symbol.
+ nextCode[0] = 0;
+
+ uint code = 0;
+ for (int i = 1; i <= WebPConstants.MaxAllowedCodeLength; i++)
+ {
+ code = (uint)((code + depthCount[i - 1]) << 1);
+ nextCode[i] = code;
+ }
+
+ for (int i = 0; i < len; i++)
+ {
+ int codeLength = tree.CodeLengths[i];
+ tree.Codes[i] = (short)ReverseBits(codeLength, nextCode[codeLength]++);
+ }
+ }
+
+ private static void SetBitDepths(Span tree, Span pool, byte[] bitDepths, int level)
+ {
+ if (tree[0].PoolIndexLeft >= 0)
+ {
+ SetBitDepths(pool.Slice(tree[0].PoolIndexLeft), pool, bitDepths, level + 1);
+ SetBitDepths(pool.Slice(tree[0].PoolIndexRight), pool, bitDepths, level + 1);
+ }
+ else
+ {
+ bitDepths[tree[0].Value] = (byte)level;
+ }
+ }
+
+ private static uint ReverseBits(int numBits, uint bits)
+ {
+ uint retval = 0;
+ int i = 0;
+ while (i < numBits)
+ {
+ i += 4;
+ retval |= (uint)(reversedBits[bits & 0xf] << (WebPConstants.MaxAllowedCodeLength + 1 - i));
+ bits >>= 4;
+ }
+
+ retval >>= WebPConstants.MaxAllowedCodeLength + 1 - numBits;
+ return retval;
+ }
+
///
/// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols,
/// len is the code length of the next processed symbol.
@@ -217,5 +514,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return step != 0 ? (key & (step - 1)) + step : key;
}
+
+ ///
+ /// Heuristics for selecting the stride ranges to collapse.
+ ///
+ private static bool ValuesShouldBeCollapsedToStrideAverage(uint a, uint 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 350fc06035..0197c66dbf 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs
@@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
private const uint Predictor0 = WebPConstants.ArgbBlack;
+ private const int PrefixLookupIdxMax = 512;
+
private const int LogLookupIdxMax = 256;
private const int ApproxLogMax = 4096;
@@ -24,6 +26,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
private const double Log2Reciprocal = 1.44269504088896338700465094007086;
+ public static int PrefixEncodeBits(int distance, ref int extraBits)
+ {
+ if (distance < PrefixLookupIdxMax)
+ {
+ (int code, int extraBits) prefixCode = WebPLookupTables.PrefixEncodeCode[distance];
+ extraBits = prefixCode.extraBits;
+ return prefixCode.code;
+ }
+ else
+ {
+ return PrefixEncodeBitsNoLut(distance, ref extraBits);
+ }
+ }
+
///
/// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green').
///
@@ -396,6 +412,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
+ ///
+ /// Splitting of distance and length codes into prefixes and
+ /// extra bits. The prefixes are encoded with an entropy code
+ /// while the extra bits are stored just as normal bits.
+ ///
+ private static int PrefixEncodeBitsNoLut(int distance, ref int extraBits)
+ {
+ int highestBit = WebPCommonUtils.BitsLog2Floor((uint)--distance);
+ int secondHighestBit = (distance >> (highestBit - 1)) & 1;
+ extraBits = highestBit - 1;
+ var 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/PixOrCopy.cs b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs
index fe85e95a94..356db2e92f 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs
@@ -47,6 +47,26 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return retval;
}
+ public uint Literal(int component)
+ {
+ return (this.BgraOrDistance >> (component * 8)) & 0xff;
+ }
+
+ public uint CacheIdx()
+ {
+ return this.BgraOrDistance;
+ }
+
+ public short Length()
+ {
+ return this.Len;
+ }
+
+ public uint Distance()
+ {
+ return this.BgraOrDistance;
+ }
+
public bool IsLiteral()
{
return this.Mode == PixOrCopyMode.Literal;
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs
index a9ea62f840..c16ff52974 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs
@@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
public uint NoneZeroCode { get; set; }
- public double BitsEntropyRefine(Span array, int n)
+ public double BitsEntropyRefine()
{
double mix;
if (this.NoneZeros < 5)
@@ -112,5 +112,40 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
this.Entropy += LosslessUtils.FastSLog2(this.Sum);
}
+
+ ///
+ /// Get the entropy for the distribution 'X'.
+ ///
+ public void BitsEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats)
+ {
+ int i;
+ int iPrev = 0;
+ uint xPrev = x[0];
+ for (i = 1; i < length; ++i)
+ {
+ uint xi = x[i];
+ if (xi != xPrev)
+ {
+ this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats);
+ }
+ }
+
+ this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats);
+
+ this.Entropy += LosslessUtils.FastSLog2(this.Sum);
+ }
+
+ private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats)
+ {
+ // Gather info for the bit entropy.
+ int streak = i - iPrev;
+
+ // Gather info for the Huffman cost.
+ stats.Counts[valPrev != 0 ? 1 : 0] += streak > 3 ? 1 : 0;
+ stats.Streaks[valPrev != 0 ? 1 : 0][streak > 3 ? 1 : 0] += streak;
+
+ valPrev = val;
+ iPrev = i;
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
index 98b791bb0d..1a6734a984 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
@@ -1,19 +1,28 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
+using System;
+using System.Collections.Generic;
+
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LHistogram
{
public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits)
+ : this()
{
if (paletteCodeBits >= 0)
{
this.PaletteCodeBits = paletteCodeBits;
}
- //HistogramClear();
- // TODO: VP8LHistogramStoreRefs(refs);
+ this.StoreRefs(refs);
+ }
+
+ public Vp8LHistogram(int paletteCodeBits)
+ : this()
+ {
+ this.PaletteCodeBits = paletteCodeBits;
}
public Vp8LHistogram()
@@ -23,27 +32,33 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
this.Alpha = new uint[WebPConstants.NumLiteralCodes];
this.Distance = new uint[WebPConstants.NumLiteralCodes];
this.Literal = new uint[WebPConstants.NumLiteralCodes]; // TODO: is this enough?
+
+ // 5 for literal, red, blue, alpha, distance.
+ this.IsUsed = new bool[5];
}
+ ///
+ /// Gets the palette code bits.
+ ///
public int PaletteCodeBits { get; }
///
- /// Cached value of bit cost.
+ /// Gets the cached value of bit cost.
///
public double BitCost { get; }
///
- /// Cached value of literal entropy costs.
+ /// Gets the cached value of literal entropy costs.
///
public double LiteralCost { get; }
///
- /// Cached value of red entropy costs.
+ /// Gets the cached value of red entropy costs.
///
public double RedCost { get; }
///
- /// Cached value of blue entropy costs.
+ /// Gets the cached value of blue entropy costs.
///
public double BlueCost { get; }
@@ -57,10 +72,125 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
public uint[] Distance { get; }
+ public bool[] IsUsed { get; }
+
+ public void StoreRefs(Vp8LBackwardRefs refs)
+ {
+ using List.Enumerator c = refs.Refs.GetEnumerator();
+ while (c.MoveNext())
+ {
+ this.AddSinglePixOrCopy(c.Current, false);
+ }
+ }
+
+ public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier)
+ {
+ if (v.IsLiteral())
+ {
+ this.Alpha[v.Literal(3)]++;
+ this.Red[v.Literal(2)]++;
+ this.Literal[v.Literal(1)]++;
+ this.Blue[v.Literal(0)]++;
+ }
+ else if (v.IsCacheIdx())
+ {
+ int literalIx = (int)(WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + v.CacheIdx());
+ this.Literal[literalIx]++;
+ }
+ else
+ {
+ int extraBits = 0;
+ int code = LosslessUtils.PrefixEncodeBits(v.Length(), ref extraBits);
+ this.Literal[WebPConstants.NumLiteralCodes + code]++;
+ if (!useDistanceModifier)
+ {
+ code = LosslessUtils.PrefixEncodeBits((int)v.Distance(), ref extraBits);
+ }
+ else
+ {
+ // TODO: VP8LPrefixEncodeBits(distance_modifier(distance_modifier_arg0, PixOrCopyDistance(v)), &code, &extra_bits);
+ }
+
+ this.Distance[code]++;
+ }
+ }
+
+ public int NumCodes()
+ {
+ return WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((this.PaletteCodeBits > 0) ? (1 << this.PaletteCodeBits) : 0);
+ }
+
public double EstimateBits()
{
- // TODO: implement this.
- return 0.0;
+ return
+ PopulationCost(this.Literal, this.NumCodes(), ref this.IsUsed[0])
+ + PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref this.IsUsed[1])
+ + PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref this.IsUsed[2])
+ + PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref this.IsUsed[3])
+ + PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref this.IsUsed[4])
+ + ExtraCost(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes)
+ + ExtraCost(this.Distance, WebPConstants.NumDistanceCodes);
+ }
+
+ ///
+ /// Get the symbol entropy for the distribution 'population'.
+ ///
+ private static double PopulationCost(uint[] population, int length, ref bool isUsed)
+ {
+ var bitEntropy = new Vp8LBitEntropy();
+ var stats = new Vp8LStreaks();
+ bitEntropy.BitsEntropyUnrefined(population, length, stats);
+
+ // The histogram is used if there is at least one non-zero streak.
+ isUsed = stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0;
+
+ return bitEntropy.BitsEntropyRefine() + FinalHuffmanCost(stats);
+ }
+
+ ///
+ /// Finalize the Huffman cost based on streak numbers and length type (<3 or >=3).
+ ///
+ private static double FinalHuffmanCost(Vp8LStreaks stats)
+ {
+ // The constants in this function are experimental and got rounded from
+ // their original values in 1/8 when switched to 1/1024.
+ double retval = InitialHuffmanCost();
+
+ // Second coefficient: Many zeros in the histogram are covered efficiently
+ // by a run-length encode. Originally 2/8.
+ retval += (stats.Counts[0] * 1.5625) + (0.234375 * stats.Streaks[0][1]);
+
+ // Second coefficient: Constant values are encoded less efficiently, but still
+ // RLE'ed. Originally 6/8.
+ retval += (stats.Counts[1] * 2.578125) + 0.703125 * stats.Streaks[1][1];
+
+ // 0s are usually encoded more efficiently than non-0s.
+ // Originally 15/8.
+ retval += 1.796875 * stats.Streaks[0][0];
+
+ // Originally 26/8.
+ retval += 3.28125 * stats.Streaks[1][0];
+
+ return retval;
+ }
+
+ private static double InitialHuffmanCost()
+ {
+ // Small bias because Huffman code length is typically not stored in full length.
+ int huffmanCodeOfHuffmanCodeSize = WebPConstants.CodeLengthCodes * 3;
+ double smallBias = 9.1;
+ return huffmanCodeOfHuffmanCodeSize - smallBias;
+ }
+
+ private static double ExtraCost(Span population, int length)
+ {
+ double cost = 0.0d;
+ for (int i = 2; i < length - 2; ++i)
+ {
+ cost += (i >> 1) * population[i + 2];
+ }
+
+ return cost;
}
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs
new file mode 100644
index 0000000000..41947ae682
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the GNU Affero General Public License, Version 3.
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossless
+{
+ internal class Vp8LStreaks
+ {
+ public Vp8LStreaks()
+ {
+ this.Counts = new int[2];
+ this.Streaks = new int[2][];
+ this.Streaks[0] = new int[2];
+ this.Streaks[1] = new int[2];
+ }
+
+ ///
+ /// index: 0=zero streak, 1=non-zero streak.
+ ///
+ public int[] Counts { get; }
+
+ ///
+ /// [zero/non-zero][streak < 3 / streak >= 3].
+ ///
+ public int[][] Streaks { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs
new file mode 100644
index 0000000000..52027192b0
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPCommonUtils.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the GNU Affero General Public License, Version 3.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Utility methods for lossy and lossless webp format.
+ ///
+ public static class WebPCommonUtils
+ {
+ // Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n).
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static int BitsLog2Floor(uint n)
+ {
+ int logValue = 0;
+ while (n >= 256)
+ {
+ logValue += 8;
+ n >>= 8;
+ }
+
+ return logValue + WebPLookupTables.LogTable8bit[n];
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
index 7f985a29e2..8953842504 100644
--- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
+++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
@@ -204,11 +204,16 @@ namespace SixLabors.ImageSharp.Formats.WebP
refsTmp1,
refsTmp2);
+ var histogramImage = new List()
+ {
+ new Vp8LHistogram(cacheBits)
+ };
+
// Build histogram image and symbols from backward references.
- //VP8LHistogramStoreRefs(refs, histogram_image->histograms[0]);
+ histogramImage[0].StoreRefs(refs);
// Create Huffman bit lengths and codes for each histogram image.
- //GetHuffBitLengthsAndCodes(histogram_image, huffman_codes)
+ GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes);
// No color cache, no Huffman image.
this.bitWriter.PutBits(0, 1);
@@ -342,7 +347,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
var bitEntropy = new Vp8LBitEntropy();
Span curHisto = histo.Slice(j * 256, 256);
bitEntropy.BitsEntropyUnrefined(curHisto, 256);
- entropyComp[j] = bitEntropy.BitsEntropyRefine(curHisto, 256);
+ entropyComp[j] = bitEntropy.BitsEntropyRefine();
}
entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] +
@@ -552,9 +557,55 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
+ private static void GetHuffBitLengthsAndCodes(List histogramImage, HuffmanTreeCode[] huffmanCodes)
+ {
+ long totalLengthSize = 0;
+ int maxNumSymbols = 0;
+
+ // Iterate over all histograms and get the aggregate number of codes used.
+ for (int i = 0; i < histogramImage.Count; i++)
+ {
+ Vp8LHistogram histo = histogramImage[i];
+ int startIdx = 5 * i;
+ for (int k = 0; k < 5; k++)
+ {
+ int numSymbols =
+ (k == 0) ? histo.NumCodes() :
+ (k == 4) ? WebPConstants.NumDistanceCodes : 256;
+ huffmanCodes[startIdx + k].NumSymbols = numSymbols;
+ totalLengthSize += numSymbols;
+ }
+ }
+
+ var end = 5 * histogramImage.Count;
+ for (int i = 0; i < end; i++)
+ {
+ int bitLength = huffmanCodes[i].NumSymbols;
+ huffmanCodes[i].Codes = new short[bitLength];
+ huffmanCodes[i].CodeLengths = new byte[bitLength];
+ if (maxNumSymbols < bitLength)
+ {
+ maxNumSymbols = bitLength;
+ }
+ }
+
+ // Create Huffman trees.
+ bool[] bufRle = new bool[maxNumSymbols];
+ var huffTree = new HuffmanTree[3 * maxNumSymbols];
+ for (int i = 0; i < histogramImage.Count; i++)
+ {
+ int codesStartIdx = 5 * i;
+ Vp8LHistogram histo = histogramImage[i];
+ HuffmanUtils.CreateHuffmanTree(histo.Literal, 15, bufRle, huffTree, huffmanCodes[codesStartIdx]);
+ HuffmanUtils.CreateHuffmanTree(histo.Red, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 1]);
+ HuffmanUtils.CreateHuffmanTree(histo.Blue, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 2]);
+ HuffmanUtils.CreateHuffmanTree(histo.Alpha, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 3]);
+ HuffmanUtils.CreateHuffmanTree(histo.Distance, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 4]);
+ }
+ }
+
///
- /// Computes a value that is related to the entropy created by the
- /// palette entry diff.
+ /// Computes a value that is related to the entropy created by the palette entry diff.
///
/// First color.
/// Second color.
diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
index 8b1466c235..ae66e90e40 100644
--- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
+++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
@@ -656,6 +656,74 @@ 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),
+ };
+
static WebPLookupTables()
{
Abs0 = new Dictionary();