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