From 25382afa8a736b4c2e3756f2d3261dffbe504b82 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 May 2020 14:23:06 +0200 Subject: [PATCH] Implement AnalyzeEntropy --- .../Formats/WebP/BitWriter/Vp8LBitWriter.cs | 2 +- src/ImageSharp/Formats/WebP/EntropyIx.cs | 23 ++ src/ImageSharp/Formats/WebP/HistoIx.cs | 36 ++ .../Formats/WebP/IWebPEncoderOptions.cs | 5 + .../Formats/WebP/Lossless/LosslessUtils.cs | 91 ++++++ .../Formats/WebP/Lossless/Vp8LBitEntropy.cs | 116 +++++++ .../Formats/WebP/Lossless/Vp8LEncoder.cs | 45 ++- src/ImageSharp/Formats/WebP/WebPConstants.cs | 15 + src/ImageSharp/Formats/WebP/WebPEncoder.cs | 3 + .../Formats/WebP/WebPEncoderCore.cs | 308 ++++++++++++++++-- .../Formats/WebP/WebPLookupTables.cs | 203 ++++++++++++ 11 files changed, 813 insertions(+), 34 deletions(-) create mode 100644 src/ImageSharp/Formats/WebP/EntropyIx.cs create mode 100644 src/ImageSharp/Formats/WebP/HistoIx.cs create mode 100644 src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs index fecb681d16..3cb554a641 100644 --- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter if (this.cur + WriterBytes > this.end) { var extraSize = (this.end - this.cur) + MinExtraSize; - if (!BitWriterResize(extraSize)) + if (!this.BitWriterResize(extraSize)) { this.error = true; return; diff --git a/src/ImageSharp/Formats/WebP/EntropyIx.cs b/src/ImageSharp/Formats/WebP/EntropyIx.cs new file mode 100644 index 0000000000..f39c1981aa --- /dev/null +++ b/src/ImageSharp/Formats/WebP/EntropyIx.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + /// + /// These five modes are evaluated and their respective entropy is computed. + /// + internal enum EntropyIx + { + Direct = 0, + + Spatial = 1, + + SubGreen = 2, + + SpatialSubGreen = 3, + + Palette = 4, + + NumEntropyIx = 5 + } +} diff --git a/src/ImageSharp/Formats/WebP/HistoIx.cs b/src/ImageSharp/Formats/WebP/HistoIx.cs new file mode 100644 index 0000000000..916a2c074c --- /dev/null +++ b/src/ImageSharp/Formats/WebP/HistoIx.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +namespace SixLabors.ImageSharp.Formats.WebP +{ + internal enum HistoIx + { + HistoAlpha = 0, + + HistoAlphaPred, + + HistoGreen, + + HistoGreenPred, + + HistoRed, + + HistoRedPred, + + HistoBlue, + + HistoBluePred, + + HistoRedSubGreen, + + HistoRedPredSubGreen, + + HistoBlueSubGreen, + + HistoBluePredSubGreen, + + HistoPalette, + + HistoTotal, // Must be last. + } +} diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index f87dd954f1..3ecef1b456 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -22,6 +22,11 @@ namespace SixLabors.ImageSharp.Formats.WebP /// float Quality { get; } + /// + /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). + /// + int Method { get; } + /// /// Gets a value indicating whether the alpha plane should be compressed with WebP lossless format. /// diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs index d0cbd1e0ad..350fc06035 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs @@ -16,6 +16,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { private const uint Predictor0 = WebPConstants.ArgbBlack; + private const int LogLookupIdxMax = 256; + + private const int ApproxLogMax = 4096; + + private const int ApproxLogWithCorrectionMax = 65536; + + private const double Log2Reciprocal = 1.44269504088896338700465094007086; + /// /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). /// @@ -305,6 +313,89 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); } + /// + /// Fast calculation of log2(v) for integer input. + /// + public static float FastLog2(uint v) + { + return (v < LogLookupIdxMax) ? WebPLookupTables.Log2Table[v] : FastLog2Slow(v); + } + + /// + /// Fast calculation of v * log2(v) for integer input. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static float FastSLog2(uint v) + { + return (v < LogLookupIdxMax) ? WebPLookupTables.SLog2Table[v] : FastSLog2Slow(v); + } + + private static float FastSLog2Slow(uint v) + { + Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + if (v < ApproxLogWithCorrectionMax) + { + int logCnt = 0; + uint y = 1; + int correction = 0; + float vF = (float)v; + uint origV = v; + do + { + ++logCnt; + v = v >> 1; + y = y << 1; + } + while (v >= LogLookupIdxMax); + + // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 + // Xf = floor(Xf) * (1 + (v % y) / v) + // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) + // The correction factor: log(1 + d) ~ d; for very small d values, so + // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v + // LOG_2_RECIPROCAL ~ 23/16 + correction = (int)((23 * (origV & (y - 1))) >> 4); + return (vF * (WebPLookupTables.Log2Table[v] + logCnt)) + correction; + } + else + { + return (float)(Log2Reciprocal * v * Math.Log(v)); + } + } + + private static float FastLog2Slow(uint v) + { + Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + if (v < ApproxLogWithCorrectionMax) + { + int logCnt = 0; + uint y = 1; + uint origV = v; + do + { + ++logCnt; + v = v >> 1; + y = y << 1; + } + while (v >= LogLookupIdxMax); + + double log2 = WebPLookupTables.Log2Table[v] + logCnt; + if (origV >= ApproxLogMax) + { + // Since the division is still expensive, add this correction factor only + // for large values of 'v'. + int correction = (int)(23 * (origV & (y - 1))) >> 4; + log2 += (double)correction / origV; + } + + return (float)log2; + } + else + { + return (float)(Log2Reciprocal * Math.Log(v)); + } + } + private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output) { int endIdx = startIdx + numberOfPixels; diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs new file mode 100644 index 0000000000..a9ea62f840 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; + +namespace SixLabors.ImageSharp.Formats.WebP.Lossless +{ + /// + /// Holds bit entropy results and entropy-related functions. + /// + internal class Vp8LBitEntropy + { + /// + /// Not a trivial literal symbol. + /// + private const uint NonTrivialSym = 0xffffffff; + + /// + /// Initializes a new instance of the class. + /// + public Vp8LBitEntropy() + { + this.Entropy = 0.0d; + this.Sum = 0; + this.NoneZeros = 0; + this.MaxVal = 0; + this.NoneZeroCode = NonTrivialSym; + } + + /// + /// Gets or sets the entropy. + /// + public double Entropy { get; set; } + + /// + /// Gets or sets the sum of the population. + /// + public uint Sum { get; set; } + + /// + /// Gets or sets the number of non-zero elements in the population. + /// + public int NoneZeros { get; set; } + + /// + /// Gets or sets the maximum value in the population. + /// + public uint MaxVal { get; set; } + + /// + /// Gets or sets the index of the last non-zero in the population. + /// + public uint NoneZeroCode { get; set; } + + public double BitsEntropyRefine(Span array, int n) + { + double mix; + if (this.NoneZeros < 5) + { + if (this.NoneZeros <= 1) + { + return 0; + } + + // Two symbols, they will be 0 and 1 in a Huffman code. + // Let's mix in a bit of entropy to favor good clustering when + // distributions of these are combined. + if (this.NoneZeros == 2) + { + return (0.99 * this.Sum) + (0.01 * this.Entropy); + } + + // No matter what the entropy says, we cannot be better than min_limit + // with Huffman coding. I am mixing a bit of entropy into the + // min_limit since it produces much better (~0.5 %) compression results + // perhaps because of better entropy clustering. + if (this.NoneZeros == 3) + { + mix = 0.95; + } + else + { + mix = 0.7; // nonzeros == 4. + } + } + else + { + mix = 0.627; + } + + double minLimit = (2 * this.Sum) - this.MaxVal; + minLimit = (mix * minLimit) + ((1.0 - mix) * this.Entropy); + return (this.Entropy < minLimit) ? minLimit : this.Entropy; + } + + public void BitsEntropyUnrefined(Span array, int n) + { + for (int i = 0; i < n; i++) + { + if (array[i] != 0) + { + this.Sum += array[i]; + this.NoneZeroCode = (uint)i; + this.NoneZeros++; + this.Entropy -= LosslessUtils.FastSLog2(array[i]); + if (this.MaxVal < array[i]) + { + this.MaxVal = array[i]; + } + } + } + + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } + } +} diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index efa0acc091..9e35cc1cc8 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -1,13 +1,37 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System; +using System.Buffers; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.WebP.Lossless { /// /// Encoder for lossless webp images. /// - internal class Vp8LEncoder + internal class Vp8LEncoder : IDisposable { + public Vp8LEncoder(MemoryAllocator memoryAllocator) + { + this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); + } + + /// + /// Gets or sets the huffman image bits. + /// + public int HistoBits { get; set; } + + /// + /// Gets or sets the bits used for the transformation. + /// + public int TransformBits { get; set; } + + /// + /// Gets or sets the cache bits. + /// + public bool CacheBits { get; } + /// /// Gets a value indicating whether to use the cross color transform. /// @@ -24,13 +48,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless public bool UsePredictorTransform { get; } /// - /// Gets a value indicating whether to use color indexing transform. + /// Gets or sets a value indicating whether to use color indexing transform. /// - public bool UsePalette { get; } + public bool UsePalette { get; set; } /// - /// Gets the palette size. + /// Gets or sets the palette size. /// - public int PaletteSize { get; } + public int PaletteSize { get; set; } + + /// + /// Gets the palette. + /// + public IMemoryOwner Palette { get; } + + /// + public void Dispose() + { + this.Palette.Dispose(); + } } } diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs index c696d19b15..8437a091b6 100644 --- a/src/ImageSharp/Formats/WebP/WebPConstants.cs +++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs @@ -72,6 +72,21 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public const int Vp8LVersion = 0; + /// + /// Maximum number of histogram images (sub-blocks). + /// + public const int MaxHuffImageSize = 2600; + + /// + /// Minimum number of Huffman bits. + /// + public const int MinHuffmanBits = 2; + + /// + /// Maximum number of Huffman bits. + /// + public const int MaxHuffmanBits = 9; + /// /// The maximum number of colors for a paletted images. /// diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index 062756d0d3..3e03724f30 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -18,6 +18,9 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public float Quality { get; set; } + /// + public int Method { get; set; } + /// public bool AlphaCompression { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index c019846617..f9d2d7b89c 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Formats.WebP.Lossless; @@ -68,12 +69,17 @@ namespace SixLabors.ImageSharp.Formats.WebP // Write the non-trivial Alpha flag and lossless version. bool hasAlpha = false; // TODO: for the start, this will be always false. - this.WriteRealAlphaAndVersion(hasAlpha); + this.WriteAlphaAndVersion(hasAlpha); // Encode the main image stream. this.EncodeStream(image); } + /// + /// Writes the image size to the stream. + /// + /// The input image width. + /// The input image height. private void WriteImageSize(int inputImgWidth, int inputImgHeight) { Guard.MustBeLessThan(inputImgWidth, WebPConstants.MaxDimension, nameof(inputImgWidth)); @@ -86,32 +92,207 @@ namespace SixLabors.ImageSharp.Formats.WebP this.bitWriter.PutBits(height, WebPConstants.Vp8LImageSizeBits); } - private void WriteRealAlphaAndVersion(bool hasAlpha) + private void WriteAlphaAndVersion(bool hasAlpha) { this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); this.bitWriter.PutBits(WebPConstants.Vp8LVersion, WebPConstants.Vp8LVersionBits); } + /// + /// Encodes the image stream using lossless webp format. + /// + /// The pixel type. + /// The image to encode. private void EncodeStream(Image image) where TPixel : unmanaged, IPixel { - var encoder = new Vp8LEncoder(); + var encoder = new Vp8LEncoder(this.memoryAllocator); // Analyze image (entropy, num_palettes etc). - this.EncoderAnalyze(image); + this.EncoderAnalyze(image, encoder); } /// /// Analyzes the image and decides what transforms should be used. /// - private void EncoderAnalyze(Image image) + private void EncoderAnalyze(Image image, Vp8LEncoder enc) where TPixel : unmanaged, IPixel { - // TODO: low effort is always false for now. - bool lowEffort = false; - // Check if we only deal with a small number of colors and should use a palette. - var usePalette = this.AnalyzeAndCreatePalette(image, lowEffort); + var usePalette = this.AnalyzeAndCreatePalette(image, enc); + + // Empirical bit sizes. + int method = 4; // TODO: method hardcoded to 4 for now. + enc.HistoBits = GetHistoBits(method, usePalette, image.Width, image.Height); + enc.TransformBits = GetTransformBits(method, enc.HistoBits); + + // Try out multiple LZ77 on images with few colors. + var nlz77s = (enc.PaletteSize > 0 && enc.PaletteSize <= 16) ? 2 : 1; + this.AnalyzeEntropy(image, usePalette, enc.PaletteSize, enc.TransformBits, out bool redAndBlueAlwaysZero); + } + + /// + /// Analyzes the entropy of the input image to determine which transforms to use during encoding the image. + /// + /// The pixel type of the image. + /// The image to analyze. + /// Indicates whether a palette should be used. + /// The palette size. + /// The transformation bits. + /// Indicates if red and blue are always zero. + /// The entropy mode to use. + private EntropyIx AnalyzeEntropy(Image image, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + + if (usePalette && paletteSize <= 16) + { + // In the case of small palettes, we pack 2, 4 or 8 pixels together. In + // practice, small palettes are better than any other transform. + redAndBlueAlwaysZero = true; + return EntropyIx.Palette; + } + + using System.Buffers.IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); + Span histo = histoBuffer.Memory.Span; + Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel. + Span prevRow = null; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + Span currentRow = image.GetPixelRowSpan(y); + Bgra32 pix = ToBgra32(currentRow[0]); + uint pixDiff = LosslessUtils.SubPixels(pix.PackedValue, pixPrev.PackedValue); + pixPrev = pix; + if ((pixDiff == 0) || (prevRow != null && pix == ToBgra32(prevRow[x]))) + { + continue; + } + + AddSingle( + pix.PackedValue, + histo.Slice((int)HistoIx.HistoAlpha * 256), + histo.Slice((int)HistoIx.HistoRed * 256), + histo.Slice((int)HistoIx.HistoGreen * 256), + histo.Slice((int)HistoIx.HistoBlue * 256)); + AddSingle( + pixDiff, + histo.Slice((int)HistoIx.HistoAlpha * 256), + histo.Slice((int)HistoIx.HistoRed * 256), + histo.Slice((int)HistoIx.HistoGreen * 256), + histo.Slice((int)HistoIx.HistoBlue * 256)); + AddSingleSubGreen( + pix.PackedValue, + histo.Slice((int)HistoIx.HistoRedSubGreen * 256), + histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); + AddSingleSubGreen( + pixDiff, + histo.Slice((int)HistoIx.HistoRedSubGreen * 256), + histo.Slice((int)HistoIx.HistoBlueSubGreen * 256)); + + // Approximate the palette by the entropy of the multiplicative hash. + uint hash = HashPix(pix.PackedValue); + histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; + + prevRow = currentRow; + } + } + + var entropyComp = new double[(int)HistoIx.HistoTotal]; + var entropy = new double[(int)EntropyIx.NumEntropyIx]; + int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; + + // Let's add one zero to the predicted histograms. The zeros are removed + // too efficiently by the pix_diff == 0 comparison, at least one of the + // zeros is likely to exist. + histo[(int)HistoIx.HistoRedPredSubGreen * 256]++; + histo[(int)HistoIx.HistoBluePredSubGreen * 256]++; + histo[(int)HistoIx.HistoRedPred * 256]++; + histo[(int)HistoIx.HistoGreenPred * 256]++; + histo[(int)HistoIx.HistoBluePred * 256]++; + histo[(int)HistoIx.HistoAlphaPred * 256]++; + + for (int j = 0; j < (int)HistoIx.HistoTotal; ++j) + { + var bitEntropy = new Vp8LBitEntropy(); + bitEntropy.BitsEntropyUnrefined(histo, 256); + entropyComp[j] = bitEntropy.BitsEntropyRefine(histo.Slice(j * 256), 256); + } + + entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] + + entropyComp[(int)HistoIx.HistoRed] + + entropyComp[(int)HistoIx.HistoGreen] + + entropyComp[(int)HistoIx.HistoBlue]; + entropy[(int)EntropyIx.Spatial] = entropyComp[(int)HistoIx.HistoAlphaPred] + + entropyComp[(int)HistoIx.HistoRedPred] + + entropyComp[(int)HistoIx.HistoGreenPred] + + entropyComp[(int)HistoIx.HistoBluePred]; + entropy[(int)EntropyIx.SubGreen] = entropyComp[(int)HistoIx.HistoAlpha] + + entropyComp[(int)HistoIx.HistoRedSubGreen] + + entropyComp[(int)HistoIx.HistoGreen] + + entropyComp[(int)HistoIx.HistoBlueSubGreen]; + entropy[(int)EntropyIx.SpatialSubGreen] = entropyComp[(int)HistoIx.HistoAlphaPred] + + entropyComp[(int)HistoIx.HistoRedPredSubGreen] + + entropyComp[(int)HistoIx.HistoGreenPred] + + entropyComp[(int)HistoIx.HistoBluePredSubGreen]; + entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette]; + + // When including transforms, there is an overhead in bits from + // storing them. This overhead is small but matters for small images. + // For spatial, there are 14 transformations. + entropy[(int)EntropyIx.Spatial] += LosslessUtils.SubSampleSize(width, transformBits) * + LosslessUtils.SubSampleSize(height, transformBits) * + LosslessUtils.FastLog2(14); + + // For color transforms: 24 as only 3 channels are considered in a + // ColorTransformElement. + entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) * + LosslessUtils.SubSampleSize(height, transformBits) * + LosslessUtils.FastLog2(24); + + // For palettes, add the cost of storing the palette. + // We empirically estimate the cost of a compressed entry as 8 bits. + // The palette is differential-coded when compressed hence a much + // lower cost than sizeof(uint32_t)*8. + entropy[(int)EntropyIx.Palette] += paletteSize * 8; + + EntropyIx minEntropyIx = EntropyIx.Direct; + for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; ++k) + { + if (entropy[(int)minEntropyIx] > entropy[k]) + { + minEntropyIx = (EntropyIx)k; + } + } + + redAndBlueAlwaysZero = true; + + // Let's check if the histogram of the chosen entropy mode has + // non-zero red and blue values. If all are zero, we can later skip + // the cross color optimization. + var histoPairs = new byte[][] + { + new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }, + new byte[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred }, + new byte[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen }, + new byte[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen }, + new byte[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue } + }; + Span redHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][0]); + Span blueHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][1]); + for (int i = 1; i < 256; i++) + { + if ((redHisto[i] | blueHisto[i]) != 0) + { + redAndBlueAlwaysZero = false; + break; + } + } + + return minEntropyIx; } /// @@ -119,32 +300,39 @@ namespace SixLabors.ImageSharp.Formats.WebP /// creates a palette and returns true, else returns false. /// /// true, if a palette should be used. - private bool AnalyzeAndCreatePalette(Image image, bool lowEffort) + private bool AnalyzeAndCreatePalette(Image image, Vp8LEncoder enc) where TPixel : unmanaged, IPixel { - int numColors = this.GetColorPalette(image, out uint[] palette); - - if (numColors > WebPConstants.MaxPaletteSize) + Span palette = enc.Palette.Memory.Span; + enc.PaletteSize = this.GetColorPalette(image, palette); + if (enc.PaletteSize > WebPConstants.MaxPaletteSize) { return false; } // TODO: figure out how the palette needs to be sorted. - Array.Sort(palette); + uint[] paletteArray = palette.Slice(0, enc.PaletteSize).ToArray(); + Array.Sort(paletteArray); + paletteArray.CopyTo(palette); - if (!lowEffort && PaletteHasNonMonotonousDeltas(palette, numColors)) + if (PaletteHasNonMonotonousDeltas(palette, enc.PaletteSize)) { - GreedyMinimizeDeltas(palette, numColors); + GreedyMinimizeDeltas(palette, enc.PaletteSize); } return true; } - private int GetColorPalette(Image image, out uint[] palette) + /// + /// Gets the color palette. + /// + /// The pixel type of the image. + /// The image to get the palette from. + /// The span to store the palette into. + /// The number of palette entries. + private int GetColorPalette(Image image, Span palette) where TPixel : unmanaged, IPixel { - Rgba32 color = default; - palette = null; var colors = new HashSet(); for (int y = 0; y < image.Height; y++) { @@ -161,13 +349,11 @@ namespace SixLabors.ImageSharp.Formats.WebP } // Fill the colors into the palette. - palette = new uint[colors.Count]; using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); int idx = 0; while (colorEnumerator.MoveNext()) { - colorEnumerator.Current.ToRgba32(ref color); - var bgra = new Bgra32(color.R, color.G, color.B, color.A); + Bgra32 bgra = ToBgra32(colorEnumerator.Current); palette[idx++] = bgra.PackedValue; } @@ -183,7 +369,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// The palette. /// Number of colors in the palette. /// True, if the palette has no monotonous deltas. - private static bool PaletteHasNonMonotonousDeltas(uint[] palette, int numColors) + private static bool PaletteHasNonMonotonousDeltas(Span palette, int numColors) { uint predict = 0x000000; byte signFound = 0x00; @@ -218,7 +404,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// The palette. /// The number of colors in the palette. - private static void GreedyMinimizeDeltas(uint[] palette, int numColors) + private static void GreedyMinimizeDeltas(Span palette, int numColors) { uint predict = 0x00000000; for (int i = 0; i < numColors; ++i) @@ -246,17 +432,15 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Computes a value that is related to the entropy created by the /// palette entry diff. - /// - /// Note that the last & 0xff is a no-operation in the next statement, but - /// removed by most compilers and is here only for regularity of the code. /// /// First color. /// Second color. /// The color distance. + [MethodImpl(InliningOptions.ShortMethod)] private static uint PaletteColorDistance(uint col1, uint col2) { uint diff = LosslessUtils.SubPixels(col1, col2); - int moreWeightForRGBThanForAlpha = 9; + uint moreWeightForRGBThanForAlpha = 9; uint score = PaletteComponentDistance((diff >> 0) & 0xff); score += PaletteComponentDistance((diff >> 8) & 0xff); score += PaletteComponentDistance((diff >> 16) & 0xff); @@ -266,6 +450,74 @@ namespace SixLabors.ImageSharp.Formats.WebP return score; } + /// + /// Calculates the huffman image bits. + /// + private static int GetHistoBits(int method, bool usePalette, int width, int height) + { + // Make tile size a function of encoding method (Range: 0 to 6). + int histoBits = (usePalette ? 9 : 7) - method; + while (true) + { + int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); + if (huffImageSize <= WebPConstants.MaxHuffImageSize) + { + break; + } + + histoBits++; + } + + return (histoBits < WebPConstants.MinHuffmanBits) ? WebPConstants.MinHuffmanBits : + (histoBits > WebPConstants.MaxHuffmanBits) ? WebPConstants.MaxHuffmanBits : histoBits; + } + + /// + /// Calculates the bits used for the transformation. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetTransformBits(int method, int histoBits) + { + int maxTransformBits = (method < 4) ? 6 : (method > 4) ? 4 : 5; + int res = (histoBits > maxTransformBits) ? maxTransformBits : histoBits; + return res; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Bgra32 ToBgra32(TPixel color) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + var bgra = new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); + return bgra; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddSingle(uint p, Span a, Span r, Span g, Span b) + { + a[(int)(p >> 24) & 0xff]++; + r[(int)(p >> 16) & 0xff]++; + g[(int)(p >> 8) & 0xff]++; + b[(int)(p >> 0) & 0xff]++; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddSingleSubGreen(uint p, Span r, Span b) + { + int green = (int)p >> 8; // The upper bits are masked away later. + r[(int)((p >> 16) - green) & 0xff]++; + b[(int)((p >> 0) - green) & 0xff]++; + } + + private static uint HashPix(uint pix) + { + // Note that masking with 0xffffffffu is for preventing an + // 'unsigned int overflow' warning. Doesn't impact the compiled code. + return (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; + } + + [MethodImpl(InliningOptions.ShortMethod)] private static uint PaletteComponentDistance(uint v) { return (v <= 128) ? v : (256 - v); diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs index 362bc7889b..fc1ec3258f 100644 --- a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs +++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs @@ -17,6 +17,209 @@ namespace SixLabors.ImageSharp.Formats.WebP public static readonly byte[,][] ModesProba = new byte[10, 10][]; + /// + /// Lookup table for small values of log2(int). + /// + public static readonly float[] Log2Table = + { + 0.0000000000000000f, 0.0000000000000000f, + 1.0000000000000000f, 1.5849625007211560f, + 2.0000000000000000f, 2.3219280948873621f, + 2.5849625007211560f, 2.8073549220576041f, + 3.0000000000000000f, 3.1699250014423121f, + 3.3219280948873621f, 3.4594316186372973f, + 3.5849625007211560f, 3.7004397181410921f, + 3.8073549220576041f, 3.9068905956085187f, + 4.0000000000000000f, 4.0874628412503390f, + 4.1699250014423121f, 4.2479275134435852f, + 4.3219280948873626f, 4.3923174227787606f, + 4.4594316186372973f, 4.5235619560570130f, + 4.5849625007211560f, 4.6438561897747243f, + 4.7004397181410917f, 4.7548875021634682f, + 4.8073549220576037f, 4.8579809951275718f, + 4.9068905956085187f, 4.9541963103868749f, + 5.0000000000000000f, 5.0443941193584533f, + 5.0874628412503390f, 5.1292830169449663f, + 5.1699250014423121f, 5.2094533656289501f, + 5.2479275134435852f, 5.2854022188622487f, + 5.3219280948873626f, 5.3575520046180837f, + 5.3923174227787606f, 5.4262647547020979f, + 5.4594316186372973f, 5.4918530963296747f, + 5.5235619560570130f, 5.5545888516776376f, + 5.5849625007211560f, 5.6147098441152083f, + 5.6438561897747243f, 5.6724253419714951f, + 5.7004397181410917f, 5.7279204545631987f, + 5.7548875021634682f, 5.7813597135246599f, + 5.8073549220576037f, 5.8328900141647412f, + 5.8579809951275718f, 5.8826430493618415f, + 5.9068905956085187f, 5.9307373375628866f, + 5.9541963103868749f, 5.9772799234999167f, + 6.0000000000000000f, 6.0223678130284543f, + 6.0443941193584533f, 6.0660891904577720f, + 6.0874628412503390f, 6.1085244567781691f, + 6.1292830169449663f, 6.1497471195046822f, + 6.1699250014423121f, 6.1898245588800175f, + 6.2094533656289501f, 6.2288186904958804f, + 6.2479275134435852f, 6.2667865406949010f, + 6.2854022188622487f, 6.3037807481771030f, + 6.3219280948873626f, 6.3398500028846243f, + 6.3575520046180837f, 6.3750394313469245f, + 6.3923174227787606f, 6.4093909361377017f, + 6.4262647547020979f, 6.4429434958487279f, + 6.4594316186372973f, 6.4757334309663976f, + 6.4918530963296747f, 6.5077946401986963f, + 6.5235619560570130f, 6.5391588111080309f, + 6.5545888516776376f, 6.5698556083309478f, + 6.5849625007211560f, 6.5999128421871278f, + 6.6147098441152083f, 6.6293566200796094f, + 6.6438561897747243f, 6.6582114827517946f, + 6.6724253419714951f, 6.6865005271832185f, + 6.7004397181410917f, 6.7142455176661224f, + 6.7279204545631987f, 6.7414669864011464f, + 6.7548875021634682f, 6.7681843247769259f, + 6.7813597135246599f, 6.7944158663501061f, + 6.8073549220576037f, 6.8201789624151878f, + 6.8328900141647412f, 6.8454900509443747f, + 6.8579809951275718f, 6.8703647195834047f, + 6.8826430493618415f, 6.8948177633079437f, + 6.9068905956085187f, 6.9188632372745946f, + 6.9307373375628866f, 6.9425145053392398f, + 6.9541963103868749f, 6.9657842846620869f, + 6.9772799234999167f, 6.9886846867721654f, + 7.0000000000000000f, 7.0112272554232539f, + 7.0223678130284543f, 7.0334230015374501f, + 7.0443941193584533f, 7.0552824355011898f, + 7.0660891904577720f, 7.0768155970508308f, + 7.0874628412503390f, 7.0980320829605263f, + 7.1085244567781691f, 7.1189410727235076f, + 7.1292830169449663f, 7.1395513523987936f, + 7.1497471195046822f, 7.1598713367783890f, + 7.1699250014423121f, 7.1799090900149344f, + 7.1898245588800175f, 7.1996723448363644f, + 7.2094533656289501f, 7.2191685204621611f, + 7.2288186904958804f, 7.2384047393250785f, + 7.2479275134435852f, 7.2573878426926521f, + 7.2667865406949010f, 7.2761244052742375f, + 7.2854022188622487f, 7.2946207488916270f, + 7.3037807481771030f, 7.3128829552843557f, + 7.3219280948873626f, 7.3309168781146167f, + 7.3398500028846243f, 7.3487281542310771f, + 7.3575520046180837f, 7.3663222142458160f, + 7.3750394313469245f, 7.3837042924740519f, + 7.3923174227787606f, 7.4008794362821843f, + 7.4093909361377017f, 7.4178525148858982f, + 7.4262647547020979f, 7.4346282276367245f, + 7.4429434958487279f, 7.4512111118323289f, + 7.4594316186372973f, 7.4676055500829976f, + 7.4757334309663976f, 7.4838157772642563f, + 7.4918530963296747f, 7.4998458870832056f, + 7.5077946401986963f, 7.5156998382840427f, + 7.5235619560570130f, 7.5313814605163118f, + 7.5391588111080309f, 7.5468944598876364f, + 7.5545888516776376f, 7.5622424242210728f, + 7.5698556083309478f, 7.5774288280357486f, + 7.5849625007211560f, 7.5924570372680806f, + 7.5999128421871278f, 7.6073303137496104f, + 7.6147098441152083f, 7.6220518194563764f, + 7.6293566200796094f, 7.6366246205436487f, + 7.6438561897747243f, 7.6510516911789281f, + 7.6582114827517946f, 7.6653359171851764f, + 7.6724253419714951f, 7.6794800995054464f, + 7.6865005271832185f, 7.6934869574993252f, + 7.7004397181410917f, 7.7073591320808825f, + 7.7142455176661224f, 7.7210991887071855f, + 7.7279204545631987f, 7.7347096202258383f, + 7.7414669864011464f, 7.7481928495894605f, + 7.7548875021634682f, 7.7615512324444795f, + 7.7681843247769259f, 7.7747870596011736f, + 7.7813597135246599f, 7.7879025593914317f, + 7.7944158663501061f, 7.8008998999203047f, + 7.8073549220576037f, 7.8137811912170374f, + 7.8201789624151878f, 7.8265484872909150f, + 7.8328900141647412f, 7.8392037880969436f, + 7.8454900509443747f, 7.8517490414160571f, + 7.8579809951275718f, 7.8641861446542797f, + 7.8703647195834047f, 7.8765169465649993f, + 7.8826430493618415f, 7.8887432488982591f, + 7.8948177633079437f, 7.9008668079807486f, + 7.9068905956085187f, 7.9128893362299619f, + 7.9188632372745946f, 7.9248125036057812f, + 7.9307373375628866f, 7.9366379390025709f, + 7.9425145053392398f, 7.9483672315846778f, + 7.9541963103868749f, 7.9600019320680805f, + 7.9657842846620869f, 7.9715435539507719f, + 7.9772799234999167f, 7.9829935746943103f, + 7.9886846867721654f, 7.9943534368588577f + }; + + public static readonly float[] SLog2Table = + { + 0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f, + 8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f, + 24.00000000f, 28.52932501f, 33.21928095f, 38.05374781f, + 43.01955001f, 48.10571634f, 53.30296891f, 58.60335893f, + 64.00000000f, 69.48686830f, 75.05865003f, 80.71062276f, + 86.43856190f, 92.23866588f, 98.10749561f, 104.04192499f, + 110.03910002f, 116.09640474f, 122.21143267f, 128.38196256f, + 134.60593782f, 140.88144886f, 147.20671787f, 153.58008562f, + 160.00000000f, 166.46500594f, 172.97373660f, 179.52490559f, + 186.11730005f, 192.74977453f, 199.42124551f, 206.13068654f, + 212.87712380f, 219.65963219f, 226.47733176f, 233.32938445f, + 240.21499122f, 247.13338933f, 254.08384998f, 261.06567603f, + 268.07820003f, 275.12078236f, 282.19280949f, 289.29369244f, + 296.42286534f, 303.57978409f, 310.76392512f, 317.97478424f, + 325.21187564f, 332.47473081f, 339.76289772f, 347.07593991f, + 354.41343574f, 361.77497759f, 369.16017124f, 376.56863518f, + 384.00000000f, 391.45390785f, 398.93001188f, 406.42797576f, + 413.94747321f, 421.48818752f, 429.04981119f, 436.63204548f, + 444.23460010f, 451.85719280f, 459.49954906f, 467.16140179f, + 474.84249102f, 482.54256363f, 490.26137307f, 497.99867911f, + 505.75424759f, 513.52785023f, 521.31926438f, 529.12827280f, + 536.95466351f, 544.79822957f, 552.65876890f, 560.53608414f, + 568.42998244f, 576.34027536f, 584.26677867f, 592.20931226f, + 600.16769996f, 608.14176943f, 616.13135206f, 624.13628279f, + 632.15640007f, 640.19154569f, 648.24156472f, 656.30630539f, + 664.38561898f, 672.47935976f, 680.58738488f, 688.70955430f, + 696.84573069f, 704.99577935f, 713.15956818f, 721.33696754f, + 729.52785023f, 737.73209140f, 745.94956849f, 754.18016116f, + 762.42375127f, 770.68022275f, 778.94946161f, 787.23135586f, + 795.52579543f, 803.83267219f, 812.15187982f, 820.48331383f, + 828.82687147f, 837.18245171f, 845.54995518f, 853.92928416f, + 862.32034249f, 870.72303558f, 879.13727036f, 887.56295522f, + 896.00000000f, 904.44831595f, 912.90781569f, 921.37841320f, + 929.86002376f, 938.35256392f, 946.85595152f, 955.37010560f, + 963.89494641f, 972.43039537f, 980.97637504f, 989.53280911f, + 998.09962237f, 1006.67674069f, 1015.26409097f, 1023.86160116f, + 1032.46920021f, 1041.08681805f, 1049.71438560f, 1058.35183469f, + 1066.99909811f, 1075.65610955f, 1084.32280357f, 1092.99911564f, + 1101.68498204f, 1110.38033993f, 1119.08512727f, 1127.79928282f, + 1136.52274614f, 1145.25545758f, 1153.99735821f, 1162.74838989f, + 1171.50849518f, 1180.27761738f, 1189.05570047f, 1197.84268914f, + 1206.63852876f, 1215.44316535f, 1224.25654560f, 1233.07861684f, + 1241.90932703f, 1250.74862473f, 1259.59645914f, 1268.45278005f, + 1277.31753781f, 1286.19068338f, 1295.07216828f, 1303.96194457f, + 1312.85996488f, 1321.76618236f, 1330.68055071f, 1339.60302413f, + 1348.53355734f, 1357.47210556f, 1366.41862452f, 1375.37307041f, + 1384.33539991f, 1393.30557020f, 1402.28353887f, 1411.26926400f, + 1420.26270412f, 1429.26381818f, 1438.27256558f, 1447.28890615f, + 1456.31280014f, 1465.34420819f, 1474.38309138f, 1483.42941118f, + 1492.48312945f, 1501.54420843f, 1510.61261078f, 1519.68829949f, + 1528.77123795f, 1537.86138993f, 1546.95871952f, 1556.06319119f, + 1565.17476976f, 1574.29342040f, 1583.41910860f, 1592.55180020f, + 1601.69146137f, 1610.83805860f, 1619.99155871f, 1629.15192882f, + 1638.31913637f, 1647.49314911f, 1656.67393509f, 1665.86146266f, + 1675.05570047f, 1684.25661744f, 1693.46418280f, 1702.67836605f, + 1711.89913698f, 1721.12646563f, 1730.36032233f, 1739.60067768f, + 1748.84750254f, 1758.10076802f, 1767.36044551f, 1776.62650662f, + 1785.89892323f, 1795.17766747f, 1804.46271172f, 1813.75402857f, + 1823.05159087f, 1832.35537170f, 1841.66534438f, 1850.98148244f, + 1860.30375965f, 1869.63214999f, 1878.96662767f, 1888.30716711f, + 1897.65374295f, 1907.00633003f, 1916.36490342f, 1925.72943838f, + 1935.09991037f, 1944.47629506f, 1953.85856831f, 1963.24670620f, + 1972.64068498f, 1982.04048108f, 1991.44607117f, 2000.85743204f, + 2010.27454072f, 2019.69737440f, 2029.12591044f, 2038.56012640f + }; + public static readonly int[] CodeToPlane = { 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a,