diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
index 26b58377f9..ab7d03c7a6 100644
--- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
+++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
@@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
this.PutBitsFlushBits();
}
- this.bits |= bits << this.used;
+ this.bits |= (ulong)bits << this.used;
this.used += nBits;
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
index 048ddde997..e23b6bf8e4 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/BackwardReferenceEncoder.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using SixLabors.ImageSharp.Formats.WebP.Lossy;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
@@ -43,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
/// We want the max value to be attainable and stored in MaxLengthBits bits.
///
- private const int MaxLength = (1 << MaxLengthBits) - 1;
+ public const int MaxLength = (1 << MaxLengthBits) - 1;
///
/// Minimum number of pixels for which it is cheaper to encode a
@@ -51,6 +52,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
private const int MinLength = 4;
+ // TODO: move to Hashchain?
public static void HashChainFill(Vp8LHashChain p, Span bgra, int quality, int xSize, int ySize)
{
int size = xSize * ySize;
@@ -230,7 +232,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
/// Evaluates best possible backward references for specified quality.
- /// The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache
+ /// The input cacheBits to 'GetBackwardReferences' sets the maximum cache
/// bits to use (passing 0 implies disabling the local color cache).
/// The optimal cache bits is evaluated and set for the cacheBits parameter.
/// The return value is the pointer to the best of the two backward refs viz,
@@ -313,6 +315,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// The input bestCacheBits sets the maximum cache bits to use (passing 0 implies disabling the local color cache).
/// The local color cache is also disabled for the lower (smaller then 25) quality.
///
+ /// Best cache size.
private static int CalculateBestCacheSize(Span bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits)
{
int cacheBitsMax = (quality <= 25) ? 0 : bestCacheBits;
@@ -328,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
var histos = new Vp8LHistogram[WebPConstants.MaxColorCacheBits + 1];
for (int i = 0; i < WebPConstants.MaxColorCacheBits + 1; i++)
{
- histos[i] = new Vp8LHistogram();
+ histos[i] = new Vp8LHistogram(bestCacheBits);
colorCache[i] = new ColorCache();
colorCache[i].Init(i);
}
@@ -419,19 +422,210 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
int distArraySize = xSize * ySize;
var distArray = new short[distArraySize];
- short[] chosenPath;
+
+ BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray);
+ int chosenPathSize = TraceBackwards(distArray, distArraySize);
+ Span chosenPath = distArray.AsSpan(distArraySize - chosenPathSize);
+ BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst);
+ }
+
+ private static void BackwardReferencesHashChainDistanceOnly(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs, short[] distArray)
+ {
+ int pixCount = xSize * ySize;
+ bool useColorCache = cacheBits > 0;
+ var literalArraySize = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((cacheBits > 0) ? (1 << cacheBits) : 0);
+ var costModel = new CostModel(literalArraySize);
+ int offsetPrev = -1;
+ int lenPrev = -1;
+ double offsetCost = -1;
+ int firstOffsetIsConstant = -1; // initialized with 'impossible' value
+ int reach = 0;
+ var colorCache = new ColorCache();
+
+ if (useColorCache)
+ {
+ colorCache.Init(cacheBits);
+ }
+
+ costModel.Build(xSize, cacheBits, refs);
+ var costManager = new CostManager(distArray, pixCount, costModel);
+
+ // We loop one pixel at a time, but store all currently best points to
+ // non-processed locations from this point.
+ distArray[0] = 0;
+
+ // Add first pixel as literal.
+ AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManager.Costs, distArray);
+
+ for (int i = 1; i < pixCount; i++)
+ {
+ float prevCost = costManager.Costs[i - 1];
+ int offset = hashChain.FindOffset(i);
+ int len = hashChain.FindLength(i);
+
+ // Try adding the pixel as a literal.
+ AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManager.Costs, distArray);
+
+ // If we are dealing with a non-literal.
+ if (len >= 2)
+ {
+ if (offset != offsetPrev)
+ {
+ int code = DistanceToPlaneCode(xSize, offset);
+ offsetCost = costModel.GetDistanceCost(code);
+ firstOffsetIsConstant = 1;
+ costManager.PushInterval(prevCost + offsetCost, i, len);
+ }
+ else
+ {
+ // Instead of considering all contributions from a pixel i by calling:
+ // costManager.PushInterval(prevCost + offsetCost, i, len);
+ // we optimize these contributions in case offsetCost stays the same
+ // for consecutive pixels. This describes a set of pixels similar to a
+ // previous set (e.g. constant color regions).
+ if (firstOffsetIsConstant != 0)
+ {
+ reach = i - 1 + lenPrev - 1;
+ firstOffsetIsConstant = 0;
+ }
+
+ if (i + len - 1 > reach)
+ {
+ int offsetJ = 0;
+ int lenJ = 0;
+ int j;
+ for (j = i; j <= reach; ++j)
+ {
+ offset = hashChain.FindOffset(j + 1);
+ len = hashChain.FindLength(j + 1);
+ if (offsetJ != offset)
+ {
+ offset = hashChain.FindOffset(j);
+ len = hashChain.FindLength(j);
+ break;
+ }
+ }
+
+ // Update the cost at j - 1 and j.
+ costManager.UpdateCostAtIndex(j - 1, false);
+ costManager.UpdateCostAtIndex(j, false);
+
+ costManager.PushInterval(costManager.Costs[j - 1] + offsetCost, j, lenJ);
+ reach = j + lenJ - 1;
+ }
+ }
+ }
+
+ costManager.UpdateCostAtIndex(i, true);
+ offsetPrev = offset;
+ lenPrev = len;
+ }
+ }
+
+ private static int TraceBackwards(short[] distArray, int distArraySize)
+ {
int chosenPathSize = 0;
+ int pathPos = distArraySize;
+ int curPos = distArraySize - 1;
+ while (curPos >= 0)
+ {
+ short cur = distArray[curPos];
+ pathPos--;
+ chosenPathSize++;
+ distArray[pathPos] = cur;
+ curPos -= cur;
+ }
- // TODO: implement this
- // BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray);
- // TraceBackwards(distArray, distArraySize, chosenPath, chosenPathSize);
- // BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst);
+ return chosenPathSize;
+ }
+
+ private static void BackwardReferencesHashChainFollowChosenPath(Span bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs)
+ {
+ bool useColorCache = cacheBits > 0;
+ var colorCache = new ColorCache();
+ int i = 0;
+
+ if (useColorCache)
+ {
+ colorCache.Init(cacheBits);
+ }
+
+ backwardRefs.Refs.Clear();
+ for (int ix = 0; ix < chosenPathSize; ix++)
+ {
+ int len = chosenPath[ix];
+ if (len != 1)
+ {
+ int offset = hashChain.FindOffset(i);
+ backwardRefs.Add(PixOrCopy.CreateCopy((uint)offset, (short)len));
+
+ if (useColorCache)
+ {
+ for (int k = 0; k < len; k++)
+ {
+ colorCache.Insert(bgra[i + k]);
+ }
+ }
+
+ i += len;
+ }
+ else
+ {
+ PixOrCopy v;
+ int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1;
+ if (idx >= 0)
+ {
+ // useColorCache is true and color cache contains bgra[i]
+ // Push pixel as a color cache index.
+ v = PixOrCopy.CreateCacheIdx(idx);
+ }
+ else
+ {
+ if (useColorCache)
+ {
+ colorCache.Insert(bgra[i]);
+ }
+
+ v = PixOrCopy.CreateLiteral(bgra[i]);
+ }
+
+ backwardRefs.Add(v);
+ i++;
+ }
+ }
+ }
+
+ private static void AddSingleLiteralWithCostModel(Span bgra, ColorCache colorCache, CostModel costModel, int idx, bool useColorCache, float prevCost, float[] cost, short[] distArray)
+ {
+ double costVal = prevCost;
+ uint color = bgra[idx];
+ int ix = useColorCache ? colorCache.Contains(color) : -1;
+ if (ix >= 0)
+ {
+ double mul0 = 0.68;
+ costVal += costModel.GetCacheCost((uint)ix) * mul0;
+ }
+ else
+ {
+ double mul1 = 0.82;
+ if (useColorCache)
+ {
+ colorCache.Insert(color);
+ }
+
+ costVal += costModel.GetLiteralCost(color) * mul1;
+ }
+
+ if (cost[idx] > costVal)
+ {
+ cost[idx] = (float)costVal;
+ distArray[idx] = 1; // only one is inserted.
+ }
}
private static void BackwardReferencesLz77(int xSize, int ySize, Span bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs)
{
int iLastCheck = -1;
- int ccInit = 0;
bool useColorCache = cacheBits > 0;
int pixCount = xSize * ySize;
var colorCache = new ColorCache();
@@ -526,7 +720,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
if (bgra[i] == bgra[i + 1])
{
- // Max out the counts to MAX_LENGTH.
+ // Max out the counts to MaxLength.
counts[countsPos] = counts[countsPos + 1];
if (counts[countsPos + 1] != MaxLength)
{
@@ -540,7 +734,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
// Figure out the window offsets around a pixel. They are stored in a
- // spiraling order around the pixel as defined by VP8LDistanceToPlaneCode.
+ // spiraling order around the pixel as defined by DistanceToPlaneCode.
for (int y = 0; y <= 6; y++)
{
for (int x = -6; x <= 6; x++)
@@ -819,7 +1013,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
refs.Add(v);
}
- private static int DistanceToPlaneCode(int xSize, int dist)
+ public static int DistanceToPlaneCode(int xSize, int dist)
{
int yOffset = dist / xSize;
int xOffset = dist - (yOffset * xSize);
diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs
new file mode 100644
index 0000000000..68bd7df117
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossless/CostCacheInterval.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the GNU Affero General Public License, Version 3.
+
+using System.Diagnostics;
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossless
+{
+ ///
+ /// The GetLengthCost(costModel, k) are cached in a CostCacheInterval.
+ ///
+ [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")]
+ internal class CostCacheInterval
+ {
+ public double Cost { get; set; }
+
+ public int Start { get; set; }
+
+ public int End { get; set; } // Exclusive.
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs
new file mode 100644
index 0000000000..3f20b3dd62
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossless/CostInterval.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the GNU Affero General Public License, Version 3.
+
+using System.Diagnostics;
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossless
+{
+ [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")]
+ internal class CostInterval
+ {
+ public float Cost { get; set; }
+
+ public int Start { get; set; }
+
+ public int End { get; set; }
+
+ public int Index { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs
new file mode 100644
index 0000000000..1f22411915
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossless/CostManager.cs
@@ -0,0 +1,250 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the GNU Affero General Public License, Version 3.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossless
+{
+ ///
+ /// The CostManager is in charge of managing intervals and costs.
+ /// It caches the different CostCacheInterval, caches the different
+ /// GetLengthCost(cost_model, k) in cost_cache_ and the CostInterval's.
+ ///
+ internal class CostManager
+ {
+ public CostManager(short[] distArray, int pixCount, CostModel costModel)
+ {
+ int costCacheSize = (pixCount > BackwardReferenceEncoder.MaxLength) ? BackwardReferenceEncoder.MaxLength : pixCount;
+
+ this.Intervals = new List();
+ this.CacheIntervals = new List();
+ this.CostCache = new List();
+ this.Costs = new float[pixCount];
+ this.DistArray = distArray;
+ this.Count = 0;
+
+ // Fill in the cost cache.
+ this.CacheIntervalsSize++;
+ this.CostCache.Add(costModel.GetLengthCost(0));
+ for (int i = 1; i < costCacheSize; i++)
+ {
+ this.CostCache.Add(costModel.GetLengthCost(i));
+
+ // Get the number of bound intervals.
+ if (this.CostCache[i] != this.CostCache[i - 1])
+ {
+ this.CacheIntervalsSize++;
+ }
+ }
+
+ // Fill in the cache intervals.
+ var cur = new CostCacheInterval()
+ {
+ Start = 0,
+ End = 1,
+ Cost = this.CostCache[0]
+ };
+ this.CacheIntervals.Add(cur);
+
+ for (int i = 1; i < costCacheSize; i++)
+ {
+ double costVal = this.CostCache[i];
+ if (costVal != cur.Cost)
+ {
+ cur = new CostCacheInterval()
+ {
+ Start = i,
+ Cost = costVal
+ };
+ this.CacheIntervals.Add(cur);
+ }
+
+ cur.End = i + 1;
+ }
+
+ // Set the initial costs_ high for every pixel as we will keep the minimum.
+ for (int i = 0; i < pixCount; i++)
+ {
+ this.Costs[i] = 1e38f;
+ }
+ }
+
+ ///
+ /// Gets the number of stored intervals.
+ ///
+ public int Count { get; }
+
+ ///
+ /// Gets the costs cache. Contains the GetLengthCost(costModel, k).
+ ///
+ public List CostCache { get; }
+
+ public int CacheIntervalsSize { get; }
+
+ public float[] Costs { get; }
+
+ public short[] DistArray { get; }
+
+ public List Intervals { get; }
+
+ public List CacheIntervals { get; }
+
+ ///
+ /// Update the cost at index i by going over all the stored intervals that overlap with i.
+ ///
+ /// The index to update.
+ /// If 'doCleanIntervals' is true, intervals that end before 'i' will be popped.
+ public void UpdateCostAtIndex(int i, bool doCleanIntervals)
+ {
+ var indicesToRemove = new List();
+ using List.Enumerator intervalEnumerator = this.Intervals.GetEnumerator();
+ while (intervalEnumerator.MoveNext() && intervalEnumerator.Current.Start <= i)
+ {
+ if (intervalEnumerator.Current.End <= i)
+ {
+ if (doCleanIntervals)
+ {
+ // We have an outdated interval, remove it.
+ indicesToRemove.Add(i);
+ }
+ }
+ else
+ {
+ this.UpdateCost(i, intervalEnumerator.Current.Index, intervalEnumerator.Current.Cost);
+ }
+ }
+
+ foreach (int index in indicesToRemove.OrderByDescending(i => i))
+ {
+ this.Intervals.RemoveAt(index);
+ }
+ }
+
+ ///
+ /// Given a new cost interval defined by its start at position, its length value
+ /// and distanceCost, add its contributions to the previous intervals and costs.
+ /// If handling the interval or one of its subintervals becomes to heavy, its
+ /// contribution is added to the costs right away.
+ ///
+ public void PushInterval(double distanceCost, int position, int len)
+ {
+ // If the interval is small enough, no need to deal with the heavy
+ // interval logic, just serialize it right away. This constant is empirical.
+ int skipDistance = 10;
+
+ if (len < skipDistance)
+ {
+ for (int j = position; j < position + len; ++j)
+ {
+ int k = j - position;
+ float costTmp = (float)(distanceCost + this.CostCache[k]);
+
+ if (this.Costs[j] > costTmp)
+ {
+ this.Costs[j] = costTmp;
+ this.DistArray[j] = (short)(k + 1);
+ }
+ }
+
+ return;
+ }
+
+ for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++)
+ {
+ // Define the intersection of the ith interval with the new one.
+ int start = position + this.CacheIntervals[i].Start;
+ int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End);
+ float cost = (float)(distanceCost + this.CacheIntervals[i].Cost);
+
+ var idx = i;
+ CostCacheInterval interval = this.CacheIntervals[idx];
+ var indicesToRemove = new List();
+ for (; interval.Start < end; idx++)
+ {
+ // Make sure we have some overlap.
+ if (start >= interval.End)
+ {
+ continue;
+ }
+
+ if (cost >= interval.Cost)
+ {
+ int startNew = interval.End;
+ this.InsertInterval(cost, position, start, interval.Start);
+ start = startNew;
+ if (start >= end)
+ {
+ break;
+ }
+
+ continue;
+ }
+
+ if (start <= interval.Start)
+ {
+ if (interval.End <= end)
+ {
+ indicesToRemove.Add(idx);
+ }
+ else
+ {
+ interval.Start = end;
+ break;
+ }
+ }
+ else
+ {
+ if (end < interval.End)
+ {
+ int endOriginal = interval.End;
+ interval.End = start;
+ this.InsertInterval(interval.Cost, idx, end, endOriginal);
+ break;
+ }
+ else
+ {
+ interval.End = start;
+ }
+ }
+ }
+
+ foreach (int indice in indicesToRemove.OrderByDescending(i => i))
+ {
+ this.Intervals.RemoveAt(indice);
+ }
+
+ // Insert the remaining interval from start to end.
+ this.InsertInterval(cost, position, start, end);
+ }
+ }
+
+ private void InsertInterval(double cost, int position, int start, int end)
+ {
+ // TODO: use COST_CACHE_INTERVAL_SIZE_MAX
+
+ var interval = new CostCacheInterval()
+ {
+ Cost = cost,
+ Start = start,
+ End = end
+ };
+
+ this.CacheIntervals.Insert(position, interval);
+ }
+
+ ///
+ /// Given the cost and the position that define an interval, update the cost at
+ /// pixel 'i' if it is smaller than the previously computed value.
+ ///
+ private void UpdateCost(int i, int position, float cost)
+ {
+ int k = i - position;
+ if (this.Costs[i] > cost)
+ {
+ this.Costs[i] = cost;
+ this.DistArray[i] = (short)(k + 1);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs
new file mode 100644
index 0000000000..d0226bbbf7
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossless/CostModel.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the GNU Affero General Public License, Version 3.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossless
+{
+ internal class CostModel
+ {
+ private const int ValuesInBytes = 256;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The literal array size.
+ public CostModel(int literalArraySize)
+ {
+ this.Alpha = new double[ValuesInBytes];
+ this.Red = new double[ValuesInBytes];
+ this.Blue = new double[ValuesInBytes];
+ this.Distance = new double[WebPConstants.NumDistanceCodes];
+ this.Literal = new double[literalArraySize];
+ }
+
+ public double[] Alpha { get; }
+
+ public double[] Red { get; }
+
+ public double[] Blue { get; }
+
+ public double[] Distance { get; }
+
+ public double[] Literal { get; }
+
+ public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs)
+ {
+ var histogram = new Vp8LHistogram(cacheBits);
+ using System.Collections.Generic.List.Enumerator refsEnumerator = backwardRefs.Refs.GetEnumerator();
+
+ // The following code is similar to HistogramCreate but converts the distance to plane code.
+ while (refsEnumerator.MoveNext())
+ {
+ histogram.AddSinglePixOrCopy(refsEnumerator.Current, true, xSize);
+ }
+
+ ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal);
+ ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Red, this.Red);
+ ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Blue, this.Blue);
+ ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Alpha, this.Alpha);
+ ConvertPopulationCountTableToBitEstimates(WebPConstants.NumDistanceCodes, histogram.Distance, this.Distance);
+ }
+
+ public double GetLengthCost(int length)
+ {
+ int extraBits = 0;
+ int code = LosslessUtils.PrefixEncodeBits(length, ref extraBits);
+ return this.Literal[ValuesInBytes + code] + extraBits;
+ }
+
+ public double GetDistanceCost(int distance)
+ {
+ int extraBits = 0;
+ int code = LosslessUtils.PrefixEncodeBits(distance, ref extraBits);
+ return this.Distance[code] + extraBits;
+ }
+
+ public double GetCacheCost(uint idx)
+ {
+ int literalIdx = (int)(ValuesInBytes + WebPConstants.NumLengthCodes + idx);
+ return this.Literal[literalIdx];
+ }
+
+ public double GetLiteralCost(uint v)
+ {
+ return this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff];
+ }
+
+ private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, uint[] populationCounts, double[] output)
+ {
+ uint sum = 0;
+ int nonzeros = 0;
+ for (int i = 0; i < numSymbols; i++)
+ {
+ sum += populationCounts[i];
+ if (populationCounts[i] > 0)
+ {
+ nonzeros++;
+ }
+ }
+
+ if (nonzeros <= 1)
+ {
+ output.AsSpan(0, numSymbols).Fill(0);
+ }
+ else
+ {
+ double logsum = LosslessUtils.FastLog2(sum);
+ for (int i = 0; i < numSymbols; i++)
+ {
+ output[i] = logsum - LosslessUtils.FastLog2(populationCounts[i]);
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs
new file mode 100644
index 0000000000..eb58c62908
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramBinInfo.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the GNU Affero General Public License, Version 3.
+
+namespace SixLabors.ImageSharp.Formats.WebP.Lossless
+{
+ internal struct HistogramBinInfo
+ {
+ ///
+ /// Position of the histogram that accumulates all histograms with the same binId.
+ ///
+ public short First;
+
+ ///
+ /// Number of combine failures per binId.
+ ///
+ public short NumCombineFailures;
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs
index 330d3afb2f..6e1b6ab70b 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs
@@ -41,10 +41,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
origHisto.Add(new Vp8LHistogram(cacheBits));
}
- // Construct the histograms from backward references.
+ // Construct the histograms from the backward references.
HistogramBuild(xSize, histoBits, refs, origHisto);
- // Copies the histograms and computes its bit_cost. histogramSymbols is optimized.
+ // Copies the histograms and computes its bitCost. histogramSymbols is optimized.
HistogramCopyAndAnalyze(origHisto, imageHisto, ref numUsed, histogramSymbols);
var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100);
@@ -61,22 +61,44 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
OptimizeHistogramSymbols(imageHisto, clusterMappings, numClusters, mapTmp, histogramSymbols);
}
- if (!entropyCombine)
+ float x = quality / 100.0f;
+
+ // Cubic ramp between 1 and MaxHistoGreedy:
+ int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1)));
+ bool doGreedy = HistogramCombineStochastic(imageHisto, ref numUsed, thresholdSize);
+ if (doGreedy)
{
- float x = quality / 100.0f;
+ HistogramCombineGreedy(imageHisto);
+ }
- // Cubic ramp between 1 and MaxHistoGreedy:
- int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1)));
- bool doGreedy = HistogramCombineStochastic(imageHisto, ref numUsed, thresholdSize);
- if (doGreedy)
+ // Find the optimal map from original histograms to the final ones.
+ RemoveEmptyHistograms(imageHisto);
+ HistogramRemap(origHisto, imageHisto, histogramSymbols);
+ }
+
+ private static void RemoveEmptyHistograms(List histograms)
+ {
+ int size = 0;
+ var indicesToRemove = new List();
+ for (int i = 0; i < histograms.Count; i++)
+ {
+ if (histograms[i] == null)
{
- HistogramCombineGreedy(imageHisto, ref numUsed);
+ indicesToRemove.Add(i);
+ continue;
}
+
+ histograms[size++] = histograms[i];
+ }
+
+ foreach (int index in indicesToRemove.OrderByDescending(i => i))
+ {
+ histograms.RemoveAt(index);
}
}
///
- /// Construct the histograms from backward references.
+ /// Construct the histograms from the backward references.
///
private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List histograms)
{
@@ -137,30 +159,91 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
else
{
- // TODO: HistogramCopy(histo, histograms[i]);
+ histograms[i] = (Vp8LHistogram)histo.DeepClone();
histogramSymbols[i] = (short)clusterId++;
}
}
- foreach (int indice in indicesToRemove.OrderByDescending(v => v))
+ foreach (int index in indicesToRemove.OrderByDescending(v => v))
{
- origHistograms.RemoveAt(indice);
- histograms.RemoveAt(indice);
+ origHistograms.RemoveAt(index);
+ histograms.RemoveAt(index);
}
}
private static void HistogramCombineEntropyBin(List histograms, ref int numUsed, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor)
{
+ var binInfo = new HistogramBinInfo[BinSize];
+ for (int idx = 0; idx < numBins; idx++)
+ {
+ binInfo[idx].First = -1;
+ binInfo[idx].NumCombineFailures = 0;
+ }
+
+ // By default, a cluster matches itself.
for (int idx = 0; idx < histograms.Count; idx++)
{
clusterMappings[idx] = (short)idx;
}
+
+ var indicesToRemove = new List();
+ for (int idx = 0; idx < histograms.Count; idx++)
+ {
+ if (histograms[idx] == null)
+ {
+ continue;
+ }
+
+ int binId = binMap[idx];
+ int first = binInfo[binId].First;
+ if (first == -1)
+ {
+ binInfo[binId].First = (short)idx;
+ }
+ else
+ {
+ // Try to merge #idx into #first (both share the same binId)
+ double bitCost = histograms[idx].BitCost;
+ double bitCostThresh = -bitCost * combineCostFactor;
+ double currCostDiff = histograms[first].AddEval(histograms[idx], bitCostThresh, curCombo);
+
+ if (currCostDiff < bitCostThresh)
+ {
+ // Try to merge two histograms only if the combo is a trivial one or
+ // the two candidate histograms are already non-trivial.
+ // For some images, 'tryCombine' turns out to be false for a lot of
+ // histogram pairs. In that case, we fallback to combining
+ // histograms as usual to avoid increasing the header size.
+ bool tryCombine = (curCombo.TrivialSymbol != NonTrivialSym) || ((histograms[idx].TrivialSymbol == NonTrivialSym) && (histograms[first].TrivialSymbol == NonTrivialSym));
+ int maxCombineFailures = 32;
+ if (tryCombine || binInfo[binId].NumCombineFailures >= maxCombineFailures)
+ {
+ // Move the (better) merged histogram to its final slot.
+ Vp8LHistogram tmp = curCombo;
+ curCombo = histograms[first];
+ histograms[first] = tmp;
+
+ histograms[idx] = null;
+ indicesToRemove.Add(idx);
+ clusterMappings[clusters[idx]] = clusters[first];
+ }
+ else
+ {
+ binInfo[binId].NumCombineFailures++;
+ }
+ }
+ }
+ }
+
+ foreach (int index in indicesToRemove.OrderByDescending(i => i))
+ {
+ histograms.RemoveAt(index);
+ }
}
///
/// Given a Histogram set, the mapping of clusters 'clusterMapping' and the
- /// current assignment of the cells in 'symbols', merge the clusters and
- /// assign the smallest possible clusters values.
+ /// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values.
///
private static void OptimizeHistogramSymbols(List histograms, short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols)
{
@@ -194,10 +277,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
clusterMappingsTmp.AsSpan().Fill(0);
// Re-map the ids.
- for (int i = 0; i < histograms.Count; i++)
+ for (int i = 0; i < symbols.Length; i++)
{
- int cluster;
- cluster = clusterMappings[symbols[i]];
+ int cluster = clusterMappings[symbols[i]];
if (cluster > 0 && clusterMappingsTmp[cluster] == 0)
{
clusterMax++;
@@ -206,18 +288,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
symbols[i] = clusterMappingsTmp[cluster];
}
-
- // Make sure all cluster values are used.
- clusterMax = 0;
- for (int i = 0; i < histograms.Count; i++)
- {
- if (symbols[i] <= clusterMax)
- {
- continue;
- }
-
- clusterMax++;
- }
}
///
@@ -231,6 +301,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
int outerIters = numUsed;
int numTriesNoSuccess = outerIters / 2;
+ if (histograms.Count < minClusterSize)
+ {
+ return true;
+ }
+
// Priority queue of histogram pairs. Its size impacts the quality of the compression and the speed:
// the smaller the faster but the worse for the compression.
var histoPriorityList = new List();
@@ -269,7 +344,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
idx2 = mappings[idx2];
// Calculate cost reduction on combination.
- currCost = HistoQueuePush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost);
+ currCost = HistoPriorityListPush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost);
// Found a better pair?
if (currCost < 0)
@@ -346,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
if (doEval)
{
// Re-evaluate the cost of an updated pair.
- HistoQueueUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p);
+ HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p);
if (p.CostDiff >= 0.0d)
{
indicesToRemove.Add(lastIndex);
@@ -356,7 +431,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
- HistoQueueUpdateHead(histoPriorityList, p);
+ HistoListUpdateHead(histoPriorityList, p);
j++;
}
@@ -368,20 +443,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return doGreedy;
}
- private static void HistogramCombineGreedy(List histograms, ref int numUsed)
+ private static void HistogramCombineGreedy(List histograms)
{
int histoSize = histograms.Count;
// Priority list of histogram pairs.
var histoPriorityList = new List();
- int maxHistoQueueSize = histoSize * histoSize;
+ int maxSize = histoSize * histoSize;
for (int i = 0; i < histograms.Count; i++)
{
for (int j = i + 1; j < histograms.Count; j++)
{
- // Initialize queue.
- HistoQueuePush(histoPriorityList, maxHistoQueueSize, histograms, i, j, 0.0d);
+ HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d);
}
}
@@ -393,8 +467,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
histograms[idx1].BitCost = histoPriorityList[0].CostCombo;
// Remove merged histogram.
- histograms.RemoveAt(idx2);
- numUsed--;
+ // TODO: can the element be removed instead? histograms.RemoveAt(idx2);
+ histograms[idx2] = null;
// Remove pairs intersecting the just combined best pair.
for (int i = 0; i < histoPriorityList.Count;)
@@ -402,41 +476,83 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
HistogramPair p = histoPriorityList.ElementAt(i);
if (p.Idx1 == idx1 || p.Idx2 == idx1 || p.Idx1 == idx2 || p.Idx2 == idx2)
{
- // Remove last entry from the queue.
- p = histoPriorityList.ElementAt(histoPriorityList.Count - 1);
- histoPriorityList.RemoveAt(histoPriorityList.Count - 1); // TODO: use list instead Queue?
+ // Replace item at pos i with the last one and shrinking the list.
+ histoPriorityList[i] = histoPriorityList[histoPriorityList.Count - 1];
+ histoPriorityList.RemoveAt(histoPriorityList.Count - 1);
}
else
{
- HistoQueueUpdateHead(histoPriorityList, p);
+ HistoListUpdateHead(histoPriorityList, p);
i++;
}
}
- // Push new pairs formed with combined histogram to the queue.
+ // Push new pairs formed with combined histogram to the list.
for (int i = 0; i < histograms.Count; i++)
{
- if (i == idx1)
+ if (i == idx1 || histograms[i] == null)
{
continue;
}
- HistoQueuePush(histoPriorityList, maxHistoQueueSize, histograms, idx1, i, 0.0d);
+ HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d);
}
}
}
+ private static void HistogramRemap(List input, List output, short[] symbols)
+ {
+ int inSize = symbols.Length;
+ int outSize = output.Count;
+ if (outSize > 1)
+ {
+ for (int i = 0; i < inSize; i++)
+ {
+ int bestOut = 0;
+ double bestBits = double.MaxValue;
+ for (int k = 0; k < outSize; k++)
+ {
+ double curBits = output[k].AddThresh(input[i], bestBits);
+ if (k == 0 || curBits < bestBits)
+ {
+ bestBits = curBits;
+ bestOut = k;
+ }
+ }
+
+ symbols[i] = (short)bestOut;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < inSize; i++)
+ {
+ symbols[i] = 0;
+ }
+ }
+
+ for (int i = 0; i < inSize; i++)
+ {
+ if (input[i] == null)
+ {
+ continue;
+ }
+
+ int idx = symbols[i];
+ input[i].Add(output[idx], output[idx]);
+ }
+ }
+
///
- /// // Create a pair from indices "idx1" and "idx2" provided its cost
+ /// Create a pair from indices "idx1" and "idx2" provided its cost
/// is inferior to "threshold", a negative entropy.
///
/// The cost of the pair, or 0. if it superior to threshold.
- private static double HistoQueuePush(List histoQueue, int queueMaxSize, List histograms, int idx1, int idx2, double threshold)
+ private static double HistoPriorityListPush(List histoList, int maxSize, List histograms, int idx1, int idx2, double threshold)
{
var pair = new HistogramPair();
- // Stop here if the queue is full.
- if (histoQueue.Count == queueMaxSize)
+ if (histoList.Count == maxSize)
{
return 0.0d;
}
@@ -453,7 +569,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
Vp8LHistogram h1 = histograms[idx1];
Vp8LHistogram h2 = histograms[idx2];
- HistoQueueUpdatePair(h1, h2, threshold, pair);
+ HistoListUpdatePair(h1, h2, threshold, pair);
// Do not even consider the pair if it does not improve the entropy.
if (pair.CostDiff >= threshold)
@@ -461,140 +577,44 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return 0.0d;
}
- histoQueue.Add(pair);
+ histoList.Add(pair);
- HistoQueueUpdateHead(histoQueue, pair);
+ HistoListUpdateHead(histoList, pair);
return pair.CostDiff;
}
///
- /// Update the cost diff and combo of a pair of histograms. This needs to be
- /// called when the the histograms have been merged with a third one.
+ /// Update the cost diff and combo of a pair of histograms. This needs to be called when the the histograms have been merged with a third one.
///
- private static void HistoQueueUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, double threshold, HistogramPair pair)
+ private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, double threshold, HistogramPair pair)
{
double sumCost = h1.BitCost + h2.BitCost;
- pair.CostCombo = GetCombinedHistogramEntropy(h1, h2, sumCost + threshold);
+ h1.GetCombinedHistogramEntropy(h2, sumCost + threshold, costInitial: pair.CostCombo, out var cost);
+ pair.CostCombo = cost;
pair.CostDiff = pair.CostCombo - sumCost;
}
- private static double GetCombinedHistogramEntropy(Vp8LHistogram a, Vp8LHistogram b, double costThreshold)
- {
- double cost = 0.0d;
- int paletteCodeBits = a.PaletteCodeBits;
- bool trivialAtEnd = false;
-
- cost += GetCombinedEntropy(a.Literal, b.Literal, Vp8LHistogram.HistogramNumCodes(paletteCodeBits), a.IsUsed[0], b.IsUsed[0], false);
-
- cost += ExtraCostCombined(a.Literal.AsSpan(WebPConstants.NumLiteralCodes), b.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes);
-
- if (cost > costThreshold)
- {
- return 0;
- }
-
- if (a.TrivialSymbol != NonTrivialSym && a.TrivialSymbol == b.TrivialSymbol)
- {
- // A, R and B are all 0 or 0xff.
- uint color_a = (a.TrivialSymbol >> 24) & 0xff;
- uint color_r = (a.TrivialSymbol >> 16) & 0xff;
- uint color_b = (a.TrivialSymbol >> 0) & 0xff;
- if ((color_a == 0 || color_a == 0xff) &&
- (color_r == 0 || color_r == 0xff) &&
- (color_b == 0 || color_b == 0xff))
- {
- trivialAtEnd = true;
- }
- }
-
- cost += GetCombinedEntropy(a.Red, b.Red, WebPConstants.NumLiteralCodes, a.IsUsed[1], b.IsUsed[1], trivialAtEnd);
-
- return cost;
- }
-
- private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd)
- {
- var stats = new Vp8LStreaks();
- if (trivialAtEnd)
- {
- // This configuration is due to palettization that transforms an indexed
- // pixel into 0xff000000 | (pixel << 8) in BundleColorMap.
- // BitsEntropyRefine is 0 for histograms with only one non-zero value.
- // Only FinalHuffmanCost needs to be evaluated.
-
- // Deal with the non-zero value at index 0 or length-1.
- stats.Streaks[1][0] = 1;
-
- // Deal with the following/previous zero streak.
- stats.Counts[0] = 1;
- stats.Streaks[0][1] = length - 1;
-
- return stats.FinalHuffmanCost();
- }
-
- var bitEntropy = new Vp8LBitEntropy();
- if (isXUsed)
- {
- if (isYUsed)
- {
- bitEntropy.GetCombinedEntropyUnrefined(x, y, length, stats);
- }
- else
- {
- bitEntropy.GetEntropyUnrefined(x, length, stats);
- }
- }
- else
- {
- if (isYUsed)
- {
- bitEntropy.GetEntropyUnrefined(y, length, stats);
- }
- else
- {
- stats.Counts[0] = 1;
- stats.Streaks[0][length > 3 ? 1 : 0] = length;
- bitEntropy.Init();
- }
- }
-
- return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost();
- }
-
- private static double ExtraCostCombined(Span x, Span y, int length)
- {
- double cost = 0.0d;
- for (int i = 2; i < length - 2; i++)
- {
- int xy = (int)(x[i + 2] + y[i + 2]);
- cost += (i >> 1) * xy;
- }
-
- return cost;
- }
-
- private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output)
- {
- // TODO: VP8LHistogramAdd(a, b, out);
- output.TrivialSymbol = (a.TrivialSymbol == b.TrivialSymbol)
- ? a.TrivialSymbol
- : NonTrivialSym;
- }
-
///
/// Check whether a pair in the list should be updated as head or not.
///
- private static void HistoQueueUpdateHead(List histoQueue, HistogramPair pair)
+ private static void HistoListUpdateHead(List histoList, HistogramPair pair)
{
- if (pair.CostDiff < histoQueue[0].CostDiff)
+ if (pair.CostDiff < histoList[0].CostDiff)
{
// Replace the best pair.
- histoQueue.RemoveAt(0);
- histoQueue.Insert(0, pair);
+ var oldIdx = histoList.IndexOf(pair);
+ histoList[oldIdx] = histoList[0];
+ histoList[0] = pair;
}
}
+ private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output)
+ {
+ a.Add(b, output);
+ output.TrivialSymbol = (a.TrivialSymbol == b.TrivialSymbol) ? a.TrivialSymbol : NonTrivialSym;
+ }
+
private static double GetCombineCostFactor(int histoSize, int quality)
{
double combineCostFactor = 0.16d;
diff --git a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs
index 8e314c561b..edd8a2ba70 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/HistogramPair.cs
@@ -1,11 +1,14 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
+using System.Diagnostics;
+
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
///
/// Pair of histograms. Negative Idx1 value means that pair is out-of-date.
///
+ [DebuggerDisplay("Idx1: {Idx1}, Idx2: {Idx2}, CostDiff: {CostDiff}, CostCombo: {CostCombo}")]
internal class HistogramPair
{
public int Idx1 { get; set; }
diff --git a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
index 885d99a135..47f5ec1286 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/HuffmanUtils.cs
@@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
///
/// The huffman tree.
- /// The historgram.
+ /// The histogram.
/// The size of the histogram.
/// The tree depth limit.
/// How many bits are used for the symbol.
@@ -269,13 +269,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
- public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokens)
+ public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokensArray)
{
int depthSize = tree.NumSymbols;
int prevValue = 8; // 8 is the initial value for rle.
int i = 0;
- int tokenIdx = 0;
- Span tokenSpan = tokens.AsSpan();
+ int tokenPos = 0;
while (i < depthSize)
{
int value = tree.CodeLengths[i];
@@ -289,19 +288,18 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
runs = k - i;
if (value == 0)
{
- tokenIdx += CodeRepeatedZeros(runs, tokens);
+ tokenPos += CodeRepeatedZeros(runs, tokensArray.AsSpan(tokenPos));
}
else
{
- tokenIdx += CodeRepeatedValues(runs, tokens, value, prevValue);
+ tokenPos += CodeRepeatedValues(runs, tokensArray.AsSpan(tokenPos), value, prevValue);
prevValue = value;
}
- tokenSpan.Slice(tokenIdx);
i += runs;
}
- return tokenIdx;
+ return tokenPos;
}
public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize)
@@ -458,8 +456,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
if (repetitions < 3)
{
- int i;
- for (i = 0; i < repetitions; ++i)
+ for (int i = 0; i < repetitions; i++)
{
tokens[pos].Code = 0; // 0-value
tokens[pos].ExtraBits = 0;
diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
index 467bba031c..ea11fcfe44 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
@@ -325,7 +325,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Update the source image.
currentRow[x] = LosslessUtils.AddPixels(predict, residual);
- // x is never 0 here so we do not need to update upper_row like below.
+ // x is never 0 here so we do not need to update upperRow like below.
}
if ((currentRow[x] & MaskAlpha) == 0)
@@ -344,7 +344,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// in that row as its top-right context pixel. Hence if we change the
// leftmost pixel of current_row, the corresponding change must be
// applied
- // to upper_row as well where top-right context is being read from.
+ // to upperRow as well where top-right context is being read from.
if (x == 0 && y != 0)
{
upperRow[width] = currentRow[0];
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs
index 8e5864146f..0433f3eed2 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LBitEntropy.cs
@@ -79,9 +79,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return (0.99 * this.Sum) + (0.01 * this.Entropy);
}
- // No matter what the entropy says, we cannot be better than min_limit
+ // No matter what the entropy says, we cannot be better than minLimit
// with Huffman coding. I am mixing a bit of entropy into the
- // min_limit since it produces much better (~0.5 %) compression results
+ // minLimit since it produces much better (~0.5 %) compression results
// perhaps because of better entropy clustering.
if (this.NoneZeros == 3)
{
@@ -195,9 +195,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats)
{
- // Gather info for the bit entropy.
int streak = i - iPrev;
+ // Gather info for the bit entropy.
+ if (valPrev != 0)
+ {
+ this.Sum += (uint)(valPrev * streak);
+ this.NoneZeros += streak;
+ this.NoneZeroCode = (uint)iPrev;
+ this.Entropy -= LosslessUtils.FastSLog2(valPrev) * streak;
+ if (this.MaxVal < valPrev)
+ {
+ this.MaxVal = valPrev;
+ }
+ }
+
// Gather info for the Huffman cost.
stats.Counts[valPrev != 0 ? 1 : 0] += streak > 3 ? 1 : 0;
stats.Streaks[valPrev != 0 ? 1 : 0][streak > 3 ? 1 : 0] += streak;
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
index e8d383917c..60b795c151 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
@@ -7,11 +7,21 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LHashChain
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The size off the chain.
+ public Vp8LHashChain(int size)
+ {
+ this.OffsetLength = new uint[size];
+ this.OffsetLength.AsSpan().Fill(0xcdcdcdcd);
+ this.Size = size;
+ }
+
///
/// The 20 most significant bits contain the offset at which the best match is found.
/// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20).
- /// The lower 12 bits contain the length of the match. The 12 bit limit is
- /// defined in MaxFindCopyLength with MAX_LENGTH=4096.
+ /// The lower 12 bits contain the length of the match.
///
public uint[] OffsetLength { get; }
@@ -21,13 +31,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
public int Size { get; }
- public Vp8LHashChain(int size)
- {
- this.OffsetLength = new uint[size];
- this.OffsetLength.AsSpan().Fill(0xcdcdcdcd);
- this.Size = size;
- }
-
public int FindLength(int basePosition)
{
return (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1));
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
index fe47e45486..d189105747 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs
@@ -6,41 +6,55 @@ using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
- internal class Vp8LHistogram
+ internal class Vp8LHistogram : IDeepCloneable
{
private const uint NonTrivialSym = 0xffffffff;
///
/// Initializes a new instance of the class.
///
- /// The backward references to initialize the histogram with.
- /// The palette code bits.
- public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits)
- : this()
+ public Vp8LHistogram()
{
- if (paletteCodeBits >= 0)
- {
- this.PaletteCodeBits = paletteCodeBits;
- }
+ }
- this.StoreRefs(refs);
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The histogram to create an instance from.
+ private Vp8LHistogram(Vp8LHistogram other)
+ : this(other.PaletteCodeBits)
+ {
+ other.Red.AsSpan().CopyTo(this.Red);
+ other.Blue.AsSpan().CopyTo(this.Blue);
+ other.Alpha.AsSpan().CopyTo(this.Alpha);
+ other.Literal.AsSpan().CopyTo(this.Literal);
+ other.IsUsed.AsSpan().CopyTo(this.IsUsed);
+ this.LiteralCost = other.LiteralCost;
+ this.RedCost = other.RedCost;
+ this.BlueCost = other.BlueCost;
+ this.BitCost = other.BitCost;
+ this.TrivialSymbol = other.TrivialSymbol;
+ this.PaletteCodeBits = other.PaletteCodeBits;
}
///
/// Initializes a new instance of the class.
///
+ /// The backward references to initialize the histogram with.
/// The palette code bits.
- public Vp8LHistogram(int paletteCodeBits)
- : this()
+ public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits)
+ : this(paletteCodeBits)
{
- this.PaletteCodeBits = paletteCodeBits;
+ this.StoreRefs(refs);
}
///
/// Initializes a new instance of the class.
///
- public Vp8LHistogram()
+ /// The palette code bits.
+ public Vp8LHistogram(int paletteCodeBits)
{
+ this.PaletteCodeBits = paletteCodeBits;
this.Red = new uint[WebPConstants.NumLiteralCodes + 1];
this.Blue = new uint[WebPConstants.NumLiteralCodes + 1];
this.Alpha = new uint[WebPConstants.NumLiteralCodes + 1];
@@ -53,10 +67,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
this.IsUsed = new bool[5];
}
+ ///
+ public IDeepCloneable DeepClone() => new Vp8LHistogram(this);
+
///
- /// Gets the palette code bits.
+ /// Gets or sets the palette code bits.
///
- public int PaletteCodeBits { get; }
+ public int PaletteCodeBits { get; set; }
///
/// Gets or sets the cached value of bit cost.
@@ -110,7 +127,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
///
/// The token to add.
/// Indicates whether to use the distance modifier.
- public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier)
+ /// xSize is only used when useDistanceModifier is true.
+ public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier, int xSize = 0)
{
if (v.IsLiteral())
{
@@ -135,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
else
{
- // TODO: VP8LPrefixEncodeBits(distance_modifier(distance_modifier_arg0, PixOrCopyDistance(v)), &code, &extra_bits);
+ code = LosslessUtils.PrefixEncodeBits(BackwardReferenceEncoder.DistanceToPlaneCode(xSize, (int)v.Distance()), ref extraBits);
}
this.Distance[code]++;
@@ -170,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
uint notUsed = 0;
double alphaCost = PopulationCost(this.Alpha, WebPConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3]);
double distanceCost = PopulationCost(this.Distance, WebPConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4]) + ExtraCost(this.Distance, WebPConstants.NumDistanceCodes);
- int numCodes = HistogramNumCodes(this.PaletteCodeBits);
+ int numCodes = this.NumCodes();
this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0]) + ExtraCost(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes);
this.RedCost = PopulationCost(this.Red, WebPConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1]);
this.BlueCost = PopulationCost(this.Blue, WebPConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2]);
@@ -181,10 +199,295 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
else
{
- this.TrivialSymbol = ((uint)alphaSym << 24) | (redSym << 16) | (blueSym << 0);
+ this.TrivialSymbol = (alphaSym << 24) | (redSym << 16) | (blueSym << 0);
}
}
+ ///
+ /// Performs output = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing
+ /// to the threshold value 'costThreshold'. The score returned is
+ /// Score = C(a+b) - C(a) - C(b), where C(a) + C(b) is known and fixed.
+ /// Since the previous score passed is 'costThreshold', we only need to compare
+ /// the partial cost against 'costThreshold + C(a) + C(b)' to possibly bail-out early.
+ ///
+ public double AddEval(Vp8LHistogram b, double costThreshold, Vp8LHistogram output)
+ {
+ double sumCost = this.BitCost + b.BitCost;
+ costThreshold += sumCost;
+ if (this.GetCombinedHistogramEntropy(b, costThreshold, costInitial: 0, out var cost))
+ {
+ this.Add(b, output);
+ output.BitCost = cost;
+ output.PaletteCodeBits = this.PaletteCodeBits;
+ }
+
+ return cost - sumCost;
+ }
+
+ public double AddThresh(Vp8LHistogram b, double costThreshold)
+ {
+ double costInitial = -this.BitCost;
+ this.GetCombinedHistogramEntropy(b, costThreshold, costInitial, out var cost);
+ return cost;
+ }
+
+ public void Add(Vp8LHistogram b, Vp8LHistogram output)
+ {
+ int literalSize = this.NumCodes();
+
+ this.AddLiteral(b, output, literalSize);
+ this.AddRed(b, output, WebPConstants.NumLiteralCodes);
+ this.AddBlue(b, output, WebPConstants.NumLiteralCodes);
+ this.AddAlpha(b, output, WebPConstants.NumLiteralCodes);
+ this.AddDistance(b, output, WebPConstants.NumDistanceCodes);
+
+ for (int i = 0; i < 5; i++)
+ {
+ output.IsUsed[i] = this.IsUsed[i] | b.IsUsed[i];
+ }
+
+ output.TrivialSymbol = (this.TrivialSymbol == b.TrivialSymbol)
+ ? this.TrivialSymbol
+ : NonTrivialSym;
+ }
+
+ public bool GetCombinedHistogramEntropy(Vp8LHistogram b, double costThreshold, double costInitial, out double cost)
+ {
+ bool trivialAtEnd = false;
+ cost = costInitial;
+
+ cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed[0], b.IsUsed[0], false);
+
+ cost += ExtraCostCombined(this.Literal.AsSpan(WebPConstants.NumLiteralCodes), b.Literal.AsSpan(WebPConstants.NumLiteralCodes), WebPConstants.NumLengthCodes);
+
+ if (cost > costThreshold)
+ {
+ return false;
+ }
+
+ if (this.TrivialSymbol != NonTrivialSym && this.TrivialSymbol == b.TrivialSymbol)
+ {
+ // A, R and B are all 0 or 0xff.
+ uint colorA = (this.TrivialSymbol >> 24) & 0xff;
+ uint colorR = (this.TrivialSymbol >> 16) & 0xff;
+ uint colorB = (this.TrivialSymbol >> 0) & 0xff;
+ if ((colorA == 0 || colorA == 0xff) &&
+ (colorR == 0 || colorR == 0xff) &&
+ (colorB == 0 || colorB == 0xff))
+ {
+ trivialAtEnd = true;
+ }
+ }
+
+ cost += GetCombinedEntropy(this.Red, b.Red, WebPConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd);
+ if (cost > costThreshold)
+ {
+ return false;
+ }
+
+ cost += GetCombinedEntropy(this.Blue, b.Blue, WebPConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd);
+ if (cost > costThreshold)
+ {
+ return false;
+ }
+
+ cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebPConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd);
+ if (cost > costThreshold)
+ {
+ return false;
+ }
+
+ cost += GetCombinedEntropy(this.Distance, b.Distance, WebPConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false);
+ if (cost > costThreshold)
+ {
+ return false;
+ }
+
+ cost += ExtraCostCombined(this.Distance, b.Distance, WebPConstants.NumDistanceCodes);
+ if (cost > costThreshold)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize)
+ {
+ if (this.IsUsed[0])
+ {
+ if (b.IsUsed[0])
+ {
+ AddVector(this.Literal, b.Literal, output.Literal, literalSize);
+ }
+ else
+ {
+ this.Literal.AsSpan(0, literalSize).CopyTo(output.Literal);
+ }
+ }
+ else if (b.IsUsed[0])
+ {
+ b.Literal.AsSpan(0, literalSize).CopyTo(output.Literal);
+ }
+ else
+ {
+ output.Literal.AsSpan(0, literalSize).Fill(0);
+ }
+ }
+
+ private void AddRed(Vp8LHistogram b, Vp8LHistogram output, int size)
+ {
+ if (this.IsUsed[1])
+ {
+ if (b.IsUsed[1])
+ {
+ AddVector(this.Red, b.Red, output.Red, size);
+ }
+ else
+ {
+ this.Red.AsSpan(0, size).CopyTo(output.Red);
+ }
+ }
+ else if (b.IsUsed[1])
+ {
+ b.Red.AsSpan(0, size).CopyTo(output.Red);
+ }
+ else
+ {
+ output.Red.AsSpan(0, size).Fill(0);
+ }
+ }
+
+ private void AddBlue(Vp8LHistogram b, Vp8LHistogram output, int size)
+ {
+ if (this.IsUsed[2])
+ {
+ if (b.IsUsed[2])
+ {
+ AddVector(this.Blue, b.Blue, output.Blue, size);
+ }
+ else
+ {
+ this.Blue.AsSpan(0, size).CopyTo(output.Blue);
+ }
+ }
+ else if (b.IsUsed[2])
+ {
+ b.Blue.AsSpan(0, size).CopyTo(output.Blue);
+ }
+ else
+ {
+ output.Blue.AsSpan(0, size).Fill(0);
+ }
+ }
+
+ private void AddAlpha(Vp8LHistogram b, Vp8LHistogram output, int size)
+ {
+ if (this.IsUsed[3])
+ {
+ if (b.IsUsed[3])
+ {
+ AddVector(this.Alpha, b.Alpha, output.Alpha, size);
+ }
+ else
+ {
+ this.Alpha.AsSpan(0, size).CopyTo(output.Alpha);
+ }
+ }
+ else if (b.IsUsed[3])
+ {
+ b.Alpha.AsSpan(0, size).CopyTo(output.Alpha);
+ }
+ else
+ {
+ output.Alpha.AsSpan(0, size).Fill(0);
+ }
+ }
+
+ private void AddDistance(Vp8LHistogram b, Vp8LHistogram output, int size)
+ {
+ if (this.IsUsed[4])
+ {
+ if (b.IsUsed[4])
+ {
+ AddVector(this.Distance, b.Distance, output.Distance, size);
+ }
+ else
+ {
+ this.Distance.AsSpan(0, size).CopyTo(output.Distance);
+ }
+ }
+ else if (b.IsUsed[4])
+ {
+ b.Distance.AsSpan(0, size).CopyTo(output.Distance);
+ }
+ else
+ {
+ output.Distance.AsSpan(0, size).Fill(0);
+ }
+ }
+
+ private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd)
+ {
+ var stats = new Vp8LStreaks();
+ if (trivialAtEnd)
+ {
+ // This configuration is due to palettization that transforms an indexed
+ // pixel into 0xff000000 | (pixel << 8) in BundleColorMap.
+ // BitsEntropyRefine is 0 for histograms with only one non-zero value.
+ // Only FinalHuffmanCost needs to be evaluated.
+
+ // Deal with the non-zero value at index 0 or length-1.
+ stats.Streaks[1][0] = 1;
+
+ // Deal with the following/previous zero streak.
+ stats.Counts[0] = 1;
+ stats.Streaks[0][1] = length - 1;
+
+ return stats.FinalHuffmanCost();
+ }
+
+ var bitEntropy = new Vp8LBitEntropy();
+ if (isXUsed)
+ {
+ if (isYUsed)
+ {
+ bitEntropy.GetCombinedEntropyUnrefined(x, y, length, stats);
+ }
+ else
+ {
+ bitEntropy.GetEntropyUnrefined(x, length, stats);
+ }
+ }
+ else
+ {
+ if (isYUsed)
+ {
+ bitEntropy.GetEntropyUnrefined(y, length, stats);
+ }
+ else
+ {
+ stats.Counts[0] = 1;
+ stats.Streaks[0][length > 3 ? 1 : 0] = length;
+ bitEntropy.Init();
+ }
+ }
+
+ return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost();
+ }
+
+ private static double ExtraCostCombined(Span x, Span y, int length)
+ {
+ double cost = 0.0d;
+ for (int i = 2; i < length - 2; i++)
+ {
+ int xy = (int)(x[i + 2] + y[i + 2]);
+ cost += (i >> 1) * xy;
+ }
+
+ return cost;
+ }
+
///
/// Get the symbol entropy for the distribution 'population'.
///
@@ -194,13 +497,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
var stats = new Vp8LStreaks();
bitEntropy.BitsEntropyUnrefined(population, length, stats);
+ trivialSym = (bitEntropy.NoneZeros == 1) ? bitEntropy.NoneZeroCode : NonTrivialSym;
+
// The histogram is used if there is at least one non-zero streak.
isUsed = stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0;
return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost();
}
- private static double ExtraCost(Span population, int length)
+ private static double ExtraCost(Span population, int length)
{
double cost = 0.0d;
for (int i = 2; i < length - 2; ++i)
@@ -211,9 +516,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return cost;
}
- public static int HistogramNumCodes(int paletteCodeBits)
+ private static void AddVector(uint[] a, uint[] b, uint[] output, int size)
{
- return WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes + ((paletteCodeBits > 0) ? (1 << paletteCodeBits) : 0);
+ for (int i = 0; i < size; i++)
+ {
+ output[i] = a[i] + b[i];
+ }
}
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs
index 728e4e893d..bec2cc099d 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LStreaks.cs
@@ -5,6 +5,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
internal class Vp8LStreaks
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public Vp8LStreaks()
{
this.Counts = new int[2];
diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
index ee47e33d81..d7fc395b13 100644
--- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
+++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs
@@ -5,8 +5,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
+
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.WebP.BitWriter;
using SixLabors.ImageSharp.Formats.WebP.Lossless;
using SixLabors.ImageSharp.Memory;
@@ -211,11 +211,15 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, int cacheBits, int histogramBits, int initBytePosition)
{
- int lz77sTypesToTrySize = 1; // TODO: harcoded for now.
+ int lz77sTypesToTrySize = 1; // TODO: hardcoded for now.
int[] lz77sTypesToTry = { 3 };
int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits);
- short[] histogramSymbols = new short[histogramImageXySize];
+ var histogramSymbols = new short[histogramImageXySize];
var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes];
+ for (int i = 0; i < huffTree.Length; i++)
+ {
+ huffTree[i] = new HuffmanTree();
+ }
if (useCache)
{
@@ -256,6 +260,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
var histogramImageSize = histogramImage.Count;
var bitArraySize = 5 * histogramImageSize;
var huffmanCodes = new HuffmanTreeCode[bitArraySize];
+ for (int i = 0; i < huffmanCodes.Length; i++)
+ {
+ huffmanCodes[i] = new HuffmanTreeCode();
+ }
GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes);
@@ -306,6 +314,11 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
var tokens = new HuffmanTreeToken[maxTokens];
+ for (int i = 0; i < tokens.Length; i++)
+ {
+ tokens[i] = new HuffmanTreeToken();
+ }
+
for (int i = 0; i < 5 * histogramImageSize; i++)
{
HuffmanTreeCode codes = huffmanCodes[i];
@@ -347,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
///
/// Applies the substract green transformation to the pixel data of the image.
///
- /// The VP8 Encoder.
+ /// The VP8L Encoder.
/// The width of the image.
/// The height of the image.
private void ApplySubtractGreen(Vp8LEncoder enc, int width, int height)
@@ -517,32 +530,29 @@ namespace SixLabors.ImageSharp.Formats.WebP
private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree)
{
- int numTokens;
int i;
- byte[] codeLengthBitdepth = new byte[WebPConstants.CodeLengthCodes];
- short[] codeLengthBitdepthSymbols = new short[WebPConstants.CodeLengthCodes];
+ var codeLengthBitDepth = new byte[WebPConstants.CodeLengthCodes];
+ var codeLengthBitDepthSymbols = new short[WebPConstants.CodeLengthCodes];
var huffmanCode = new HuffmanTreeCode();
huffmanCode.NumSymbols = WebPConstants.CodeLengthCodes;
- huffmanCode.CodeLengths = codeLengthBitdepth;
- huffmanCode.Codes = codeLengthBitdepthSymbols;
+ huffmanCode.CodeLengths = codeLengthBitDepth;
+ huffmanCode.Codes = codeLengthBitDepthSymbols;
this.bitWriter.PutBits(0, 1);
- numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens);
- uint[] histogram = new uint[WebPConstants.CodeLengthCodes + 1];
- bool[] bufRle = new bool[WebPConstants.CodeLengthCodes + 1];
+ var numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens);
+ var histogram = new uint[WebPConstants.CodeLengthCodes + 1];
+ var bufRle = new bool[WebPConstants.CodeLengthCodes + 1];
for (i = 0; i < numTokens; i++)
{
histogram[tokens[i].Code]++;
}
HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode);
- this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitdepth);
+ this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitDepth);
ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode);
int trailingZeroBits = 0;
int trimmedLength = numTokens;
- bool writeTrimmedLength;
- int length;
i = numTokens;
while (i-- > 0)
{
@@ -550,7 +560,7 @@ namespace SixLabors.ImageSharp.Formats.WebP
if (ix == 0 || ix == 17 || ix == 18)
{
trimmedLength--; // discount trailing zeros.
- trailingZeroBits += codeLengthBitdepth[ix];
+ trailingZeroBits += codeLengthBitDepth[ix];
if (ix == 17)
{
trailingZeroBits += 3;
@@ -566,8 +576,8 @@ namespace SixLabors.ImageSharp.Formats.WebP
}
}
- writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12;
- length = writeTrimmedLength ? trimmedLength : numTokens;
+ var writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12;
+ var length = writeTrimmedLength ? trimmedLength : numTokens;
this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1);
if (writeTrimmedLength)
{