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,