diff --git a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs
index 75b8464588..ed937201c9 100644
--- a/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs
+++ b/src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs
@@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitReader
if (bytesToRead > 0)
{
- WebPThrowHelper.ThrowImageFormatException("image file has insufficient data");
+ WebPThrowHelper.ThrowImageFormatException("webp image file has insufficient data");
}
}
}
diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
index 73f808b630..5a931259ff 100644
--- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
+++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
@@ -85,6 +85,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
}
}
+ public void Reset(Vp8LBitWriter bwInit)
+ {
+ this.bits = bwInit.bits;
+ this.used = bwInit.used;
+ this.cur = bwInit.cur;
+ }
+
public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex)
{
int depth = code.CodeLengths[codeIndex];
diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs
index 1497ca2447..cdf259765d 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
@@ -309,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// true if a greedy approach needs to be performed afterwards, false otherwise.
private static bool HistogramCombineStochastic(List histograms, int minClusterSize)
{
- var rand = new Random();
+ uint seed = 1;
int triesWithNoSuccess = 0;
var numUsed = histograms.Count(h => h != null);
int outerIters = numUsed;
@@ -350,7 +351,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
for (int j = 0; numUsed >= 2 && j < numTries; j++)
{
// Choose two different histograms at random and try to combine them.
- uint tmp = (uint)(rand.Next() % randRange);
+ uint tmp = MyRand(ref seed) % randRange;
int idx1 = (int)(tmp / (numUsed - 1));
int idx2 = (int)(tmp % (numUsed - 1));
if (idx2 >= idx1)
@@ -385,10 +386,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
bestIdx1 = histoPriorityList[0].Idx1;
bestIdx2 = histoPriorityList[0].Idx2;
- // TODO: Review this again, not sure why this is needed in the reference implementation.
- // Pop bestIdx2 from mappings.
- // var mappingIndex = Array.BinarySearch(mappings, bestIdx2);
- // memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *((*num_used) - (mapping_index - mappings) - 1));
+ var mappingIndex = Array.IndexOf(mappings, bestIdx2);
+ Span src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1);
+ Span dst = mappings.AsSpan(mappingIndex);
+ src.CopyTo(dst);
// Merge the histograms and remove bestIdx2 from the queue.
HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]);
@@ -680,5 +681,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return combineCostFactor;
}
+
+ // Implement a Lehmer random number generator with a multiplicative constant of 48271 and a modulo constant of 2^31 - 1.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static uint MyRand(ref uint seed)
+ {
+ seed = (uint)(((ulong)seed * 48271u) % 2147483647u);
+ return seed;
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
index 8ead78f864..c94d674385 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
@@ -252,7 +252,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
int width = image.Width;
int height = image.Height;
- int bytePosition = this.bitWriter.NumBytes();
// Convert image pixels to bgra array.
Span bgra = this.Bgra.GetSpan();
@@ -269,12 +268,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Analyze image (entropy, numPalettes etc).
CrunchConfig[] crunchConfigs = this.EncoderAnalyze(image, out bool redAndBlueAlwaysZero);
- // TODO : Do we want to do this multi-threaded, this will probably require a second class:
- // one which co-ordinates the threading and comparison and another which does the actual encoding
+ int bestSize = 0;
+ Vp8LBitWriter bitWriterInit = this.bitWriter;
+ Vp8LBitWriter bitWriterBest = this.bitWriter.Clone();
+ bool isFirstConfig = true;
foreach (CrunchConfig crunchConfig in crunchConfigs)
{
bool useCache = true;
- this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial;
+ this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette ||
+ crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial;
this.UseSubtractGreenTransform = (crunchConfig.EntropyIdx == EntropyIx.SubGreen) ||
(crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen);
this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) ||
@@ -329,9 +331,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
useCache,
crunchConfig,
this.CacheBits,
- this.HistoBits,
- bytePosition);
+ this.HistoBits);
+
+ // If we are better than what we already have.
+ if (isFirstConfig || this.bitWriter.NumBytes() < bestSize)
+ {
+ bestSize = this.bitWriter.NumBytes();
+ this.BitWriterSwap(ref this.bitWriter, ref bitWriterBest);
+ }
+
+ // Reset the bit writer for the following iteration if any.
+ if (crunchConfigs.Length > 1)
+ {
+ this.bitWriter.Reset(bitWriterInit);
+ }
+
+ isFirstConfig = false;
}
+
+ this.BitWriterSwap(ref bitWriterBest, ref this.bitWriter);
}
///
@@ -364,8 +382,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Go brute force on all transforms.
foreach (EntropyIx entropyIx in Enum.GetValues(typeof(EntropyIx)).Cast())
{
- // We can only apply kPalette or kPaletteAndSpatial if we can indeed use
- // a palette.
+ // We can only apply kPalette or kPaletteAndSpatial if we can indeed use a palette.
if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette)
{
crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIx });
@@ -405,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return crunchConfigs.ToArray();
}
- private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition)
+ private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits)
{
int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits);
var histogramSymbols = new ushort[histogramImageXySize];
@@ -432,7 +449,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
BackwardReferenceEncoder.HashChainFill(hashChain, bgra, this.quality, width, height);
Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter;
-
+ Vp8LBitWriter bwInit = this.bitWriter;
foreach (CrunchSubConfig subConfig in config.SubConfigs)
{
Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(
@@ -451,8 +468,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0];
// TODO : Loop based on cache/no cache
-
- // TODO: this.bitWriter.Reset();
+ this.bitWriter.Reset(bwInit);
var tmpHisto = new Vp8LHistogram(cacheBits);
var histogramImage = new List(histogramImageXySize);
for (int i = 0; i < histogramImageXySize; i++)
@@ -1583,6 +1599,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void BitWriterSwap(ref Vp8LBitWriter src, ref Vp8LBitWriter dst)
+ {
+ Vp8LBitWriter tmp = src;
+ src = dst;
+ dst = tmp;
+ }
+
///
/// Calculates the bits used for the transformation.
///