Browse Source

Build huffman tree

pull/1552/head
Brian Popow 6 years ago
parent
commit
21880f74e2
  1. 16
      src/ImageSharp/Formats/WebP/BitReader/Vp8BitReader.cs
  2. 32
      src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs
  3. 12
      src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs
  4. 305
      src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
  5. 30
      src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs
  6. 20
      src/ImageSharp/Formats/WebP/Lossless/PixOrCopy.cs
  7. 37
      src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs
  8. 146
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
  9. 26
      src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs
  10. 27
      src/ImageSharp/Formats/WebP/WebPCommonUtils.cs
  11. 61
      src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
  12. 68
      src/ImageSharp/Formats/WebP/WebPLookupTables.cs

16
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];
}
}
}

32
src/ImageSharp/Formats/WebP/Lossless/HuffmanTree.cs

@ -9,23 +9,39 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
internal class HuffmanTree
{
/// <summary>
/// Gets the symbol frequency.
/// Gets or sets the symbol frequency.
/// </summary>
public int TotalCount { get; }
public int TotalCount { get; set; }
/// <summary>
/// Gets the symbol value.
/// Gets or sets the symbol value.
/// </summary>
public int Value { get; }
public int Value { get; set; }
/// <summary>
/// Gets the index for the left sub-tree.
/// Gets or sets the index for the left sub-tree.
/// </summary>
public int PoolIndexLeft { get; }
public int PoolIndexLeft { get; set; }
/// <summary>
/// Gets the index for the right sub-tree.
/// Gets or sets the index for the right sub-tree.
/// </summary>
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;
}
}
}
}

12
src/ImageSharp/Formats/WebP/Lossless/HuffmanTreeCode.cs

@ -9,18 +9,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
internal class HuffmanTreeCode
{
/// <summary>
/// Gets the number of symbols.
/// Gets or sets the number of symbols.
/// </summary>
public int NumSymbols { get; }
public int NumSymbols { get; set; }
/// <summary>
/// Gets the code lengths of the symbols.
/// Gets or sets the code lengths of the symbols.
/// </summary>
public byte[] CodeLengths { get; }
public byte[] CodeLengths { get; set; }
/// <summary>
/// Gets the symbol Codes.
/// Gets or sets the symbol Codes.
/// </summary>
public short[] Codes { get; }
public short[] Codes { get; set; }
}
}

305
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);
}
/// <summary>
/// Change the population counts in a way that the consequent
/// Huffman tree compression, especially its RLE-part, give smaller output.
/// </summary>
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);
}
}
}
}
/// <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="bitDepths">How many bits are used for the symbol.</param>
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<HuffmanTree> 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<HuffmanCode> 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;
}
/// <summary>
/// Get the actual bit values for a tree of bit depths.
/// </summary>
/// <param name="tree">The hiffman tree.</param>
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<HuffmanTree> tree, Span<HuffmanTree> 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;
}
/// <summary>
/// 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;
}
/// <summary>
/// Heuristics for selecting the stride ranges to collapse.
/// </summary>
private static bool ValuesShouldBeCollapsedToStrideAverage(uint a, uint b)
{
return Math.Abs(a - b) < 4;
}
}
}

30
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);
}
}
/// <summary>
/// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green').
/// </summary>
@ -396,6 +412,20 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
/// <summary>
/// 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.
/// </summary>
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<uint> input, int startIdx, int numberOfPixels, Span<uint> output)
{
int endIdx = startIdx + numberOfPixels;

20
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;

37
src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs

@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// </summary>
public uint NoneZeroCode { get; set; }
public double BitsEntropyRefine(Span<uint> 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);
}
/// <summary>
/// Get the entropy for the distribution 'X'.
/// </summary>
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;
}
}
}

146
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];
}
/// <summary>
/// Gets the palette code bits.
/// </summary>
public int PaletteCodeBits { get; }
/// <summary>
/// Cached value of bit cost.
/// Gets the cached value of bit cost.
/// </summary>
public double BitCost { get; }
/// <summary>
/// Cached value of literal entropy costs.
/// Gets the cached value of literal entropy costs.
/// </summary>
public double LiteralCost { get; }
/// <summary>
/// Cached value of red entropy costs.
/// Gets the cached value of red entropy costs.
/// </summary>
public double RedCost { get; }
/// <summary>
/// Cached value of blue entropy costs.
/// Gets the cached value of blue entropy costs.
/// </summary>
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<PixOrCopy>.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);
}
/// <summary>
/// Get the symbol entropy for the distribution 'population'.
/// </summary>
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);
}
/// <summary>
/// Finalize the Huffman cost based on streak numbers and length type (<3 or >=3).
/// </summary>
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<uint> population, int length)
{
double cost = 0.0d;
for (int i = 2; i < length - 2; ++i)
{
cost += (i >> 1) * population[i + 2];
}
return cost;
}
}
}

26
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];
}
/// <summary>
/// index: 0=zero streak, 1=non-zero streak.
/// </summary>
public int[] Counts { get; }
/// <summary>
/// [zero/non-zero][streak < 3 / streak >= 3].
/// </summary>
public int[][] Streaks { get; }
}
}

27
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
{
/// <summary>
/// Utility methods for lossy and lossless webp format.
/// </summary>
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];
}
}
}

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

@ -204,11 +204,16 @@ namespace SixLabors.ImageSharp.Formats.WebP
refsTmp1,
refsTmp2);
var histogramImage = new List<Vp8LHistogram>()
{
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<uint> 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<Vp8LHistogram> 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]);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="col1">First color.</param>
/// <param name="col2">Second color.</param>

68
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<int, byte>();

Loading…
Cancel
Save