Browse Source

Implement AnalyzeEntropy

pull/1552/head
Brian Popow 6 years ago
parent
commit
25382afa8a
  1. 2
      src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
  2. 23
      src/ImageSharp/Formats/WebP/EntropyIx.cs
  3. 36
      src/ImageSharp/Formats/WebP/HistoIx.cs
  4. 5
      src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs
  5. 91
      src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs
  6. 116
      src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs
  7. 45
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  8. 15
      src/ImageSharp/Formats/WebP/WebPConstants.cs
  9. 3
      src/ImageSharp/Formats/WebP/WebPEncoder.cs
  10. 308
      src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
  11. 203
      src/ImageSharp/Formats/WebP/WebPLookupTables.cs

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

23
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
{
/// <summary>
/// These five modes are evaluated and their respective entropy is computed.
/// </summary>
internal enum EntropyIx
{
Direct = 0,
Spatial = 1,
SubGreen = 2,
SpatialSubGreen = 3,
Palette = 4,
NumEntropyIx = 5
}
}

36
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.
}
}

5
src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs

@ -22,6 +22,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
float Quality { get; }
/// <summary>
/// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better).
/// </summary>
int Method { get; }
/// <summary>
/// Gets a value indicating whether the alpha plane should be compressed with WebP lossless format.
/// </summary>

91
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;
/// <summary>
/// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green').
/// </summary>
@ -305,6 +313,89 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu);
}
/// <summary>
/// Fast calculation of log2(v) for integer input.
/// </summary>
public static float FastLog2(uint v)
{
return (v < LogLookupIdxMax) ? WebPLookupTables.Log2Table[v] : FastLog2Slow(v);
}
/// <summary>
/// Fast calculation of v * log2(v) for integer input.
/// </summary>
[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<uint> input, int startIdx, int numberOfPixels, Span<uint> output)
{
int endIdx = startIdx + numberOfPixels;

116
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
{
/// <summary>
/// Holds bit entropy results and entropy-related functions.
/// </summary>
internal class Vp8LBitEntropy
{
/// <summary>
/// Not a trivial literal symbol.
/// </summary>
private const uint NonTrivialSym = 0xffffffff;
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LBitEntropy"/> class.
/// </summary>
public Vp8LBitEntropy()
{
this.Entropy = 0.0d;
this.Sum = 0;
this.NoneZeros = 0;
this.MaxVal = 0;
this.NoneZeroCode = NonTrivialSym;
}
/// <summary>
/// Gets or sets the entropy.
/// </summary>
public double Entropy { get; set; }
/// <summary>
/// Gets or sets the sum of the population.
/// </summary>
public uint Sum { get; set; }
/// <summary>
/// Gets or sets the number of non-zero elements in the population.
/// </summary>
public int NoneZeros { get; set; }
/// <summary>
/// Gets or sets the maximum value in the population.
/// </summary>
public uint MaxVal { get; set; }
/// <summary>
/// Gets or sets the index of the last non-zero in the population.
/// </summary>
public uint NoneZeroCode { get; set; }
public double BitsEntropyRefine(Span<uint> 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<uint> 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);
}
}
}

45
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
{
/// <summary>
/// Encoder for lossless webp images.
/// </summary>
internal class Vp8LEncoder
internal class Vp8LEncoder : IDisposable
{
public Vp8LEncoder(MemoryAllocator memoryAllocator)
{
this.Palette = memoryAllocator.Allocate<uint>(WebPConstants.MaxPaletteSize);
}
/// <summary>
/// Gets or sets the huffman image bits.
/// </summary>
public int HistoBits { get; set; }
/// <summary>
/// Gets or sets the bits used for the transformation.
/// </summary>
public int TransformBits { get; set; }
/// <summary>
/// Gets or sets the cache bits.
/// </summary>
public bool CacheBits { get; }
/// <summary>
/// Gets a value indicating whether to use the cross color transform.
/// </summary>
@ -24,13 +48,24 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
public bool UsePredictorTransform { get; }
/// <summary>
/// Gets a value indicating whether to use color indexing transform.
/// Gets or sets a value indicating whether to use color indexing transform.
/// </summary>
public bool UsePalette { get; }
public bool UsePalette { get; set; }
/// <summary>
/// Gets the palette size.
/// Gets or sets the palette size.
/// </summary>
public int PaletteSize { get; }
public int PaletteSize { get; set; }
/// <summary>
/// Gets the palette.
/// </summary>
public IMemoryOwner<uint> Palette { get; }
/// <inheritdoc/>
public void Dispose()
{
this.Palette.Dispose();
}
}
}

15
src/ImageSharp/Formats/WebP/WebPConstants.cs

@ -72,6 +72,21 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
public const int Vp8LVersion = 0;
/// <summary>
/// Maximum number of histogram images (sub-blocks).
/// </summary>
public const int MaxHuffImageSize = 2600;
/// <summary>
/// Minimum number of Huffman bits.
/// </summary>
public const int MinHuffmanBits = 2;
/// <summary>
/// Maximum number of Huffman bits.
/// </summary>
public const int MaxHuffmanBits = 9;
/// <summary>
/// The maximum number of colors for a paletted images.
/// </summary>

3
src/ImageSharp/Formats/WebP/WebPEncoder.cs

@ -18,6 +18,9 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <inheritdoc/>
public float Quality { get; set; }
/// <inheritdoc/>
public int Method { get; set; }
/// <inheritdoc/>
public bool AlphaCompression { get; set; }

308
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);
}
/// <summary>
/// Writes the image size to the stream.
/// </summary>
/// <param name="inputImgWidth">The input image width.</param>
/// <param name="inputImgHeight">The input image height.</param>
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);
}
/// <summary>
/// Encodes the image stream using lossless webp format.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="image">The image to encode.</param>
private void EncodeStream<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new Vp8LEncoder();
var encoder = new Vp8LEncoder(this.memoryAllocator);
// Analyze image (entropy, num_palettes etc).
this.EncoderAnalyze(image);
this.EncoderAnalyze(image, encoder);
}
/// <summary>
/// Analyzes the image and decides what transforms should be used.
/// </summary>
private void EncoderAnalyze<TPixel>(Image<TPixel> image)
private void EncoderAnalyze<TPixel>(Image<TPixel> image, Vp8LEncoder enc)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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);
}
/// <summary>
/// Analyzes the entropy of the input image to determine which transforms to use during encoding the image.
/// </summary>
/// <typeparam name="TPixel">The pixel type of the image.</typeparam>
/// <param name="image">The image to analyze.</param>
/// <param name="usePalette">Indicates whether a palette should be used.</param>
/// <param name="paletteSize">The palette size.</param>
/// <param name="transformBits">The transformation bits.</param>
/// <param name="redAndBlueAlwaysZero">Indicates if red and blue are always zero.</param>
/// <returns>The entropy mode to use.</returns>
private EntropyIx AnalyzeEntropy<TPixel>(Image<TPixel> image, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero)
where TPixel : unmanaged, IPixel<TPixel>
{
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<uint> histoBuffer = this.memoryAllocator.Allocate<uint>((int)HistoIx.HistoTotal * 256);
Span<uint> histo = histoBuffer.Memory.Span;
Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel.
Span<TPixel> prevRow = null;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
Span<TPixel> 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<uint> redHisto = histo.Slice(256 * histoPairs[(int)minEntropyIx][0]);
Span<uint> 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;
}
/// <summary>
@ -119,32 +300,39 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// creates a palette and returns true, else returns false.
/// </summary>
/// <returns>true, if a palette should be used.</returns>
private bool AnalyzeAndCreatePalette<TPixel>(Image<TPixel> image, bool lowEffort)
private bool AnalyzeAndCreatePalette<TPixel>(Image<TPixel> image, Vp8LEncoder enc)
where TPixel : unmanaged, IPixel<TPixel>
{
int numColors = this.GetColorPalette(image, out uint[] palette);
if (numColors > WebPConstants.MaxPaletteSize)
Span<uint> 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<TPixel>(Image<TPixel> image, out uint[] palette)
/// <summary>
/// Gets the color palette.
/// </summary>
/// <typeparam name="TPixel">The pixel type of the image.</typeparam>
/// <param name="image">The image to get the palette from.</param>
/// <param name="palette">The span to store the palette into.</param>
/// <returns>The number of palette entries.</returns>
private int GetColorPalette<TPixel>(Image<TPixel> image, Span<uint> palette)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 color = default;
palette = null;
var colors = new HashSet<TPixel>();
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<TPixel>.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
/// <param name="palette">The palette.</param>
/// <param name="numColors">Number of colors in the palette.</param>
/// <returns>True, if the palette has no monotonous deltas.</returns>
private static bool PaletteHasNonMonotonousDeltas(uint[] palette, int numColors)
private static bool PaletteHasNonMonotonousDeltas(Span<uint> palette, int numColors)
{
uint predict = 0x000000;
byte signFound = 0x00;
@ -218,7 +404,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// </summary>
/// <param name="palette">The palette.</param>
/// <param name="numColors">The number of colors in the palette.</param>
private static void GreedyMinimizeDeltas(uint[] palette, int numColors)
private static void GreedyMinimizeDeltas(Span<uint> palette, int numColors)
{
uint predict = 0x00000000;
for (int i = 0; i < numColors; ++i)
@ -246,17 +432,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// <summary>
/// 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.
/// </summary>
/// <param name="col1">First color.</param>
/// <param name="col2">Second color.</param>
/// <returns>The color distance.</returns>
[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;
}
/// <summary>
/// Calculates the huffman image bits.
/// </summary>
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;
}
/// <summary>
/// Calculates the bits used for the transformation.
/// </summary>
[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>(TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
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<uint> a, Span<uint> r, Span<uint> g, Span<uint> 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<uint> r, Span<uint> 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);

203
src/ImageSharp/Formats/WebP/WebPLookupTables.cs

@ -17,6 +17,209 @@ namespace SixLabors.ImageSharp.Formats.WebP
public static readonly byte[,][] ModesProba = new byte[10, 10][];
/// <summary>
/// Lookup table for small values of log2(int).
/// </summary>
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,

Loading…
Cancel
Save