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. ///