mirror of https://github.com/SixLabors/ImageSharp
13 changed files with 1136 additions and 135 deletions
@ -0,0 +1,92 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the GNU Affero General Public License, Version 3.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP.Lossless |
|||
{ |
|||
/// <summary>
|
|||
/// Data container to keep track of cost range for the three dominant entropy symbols.
|
|||
/// </summary>
|
|||
internal class DominantCostRange |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DominantCostRange"/> class.
|
|||
/// </summary>
|
|||
public DominantCostRange() |
|||
{ |
|||
this.LiteralMax = 0.0d; |
|||
this.LiteralMin = double.MaxValue; |
|||
this.RedMax = 0.0d; |
|||
this.RedMin = double.MaxValue; |
|||
this.BlueMax = 0.0d; |
|||
this.BlueMin = double.MaxValue; |
|||
} |
|||
|
|||
public double LiteralMax { get; set; } |
|||
|
|||
public double LiteralMin { get; set; } |
|||
|
|||
public double RedMax { get; set; } |
|||
|
|||
public double RedMin { get; set; } |
|||
|
|||
public double BlueMax { get; set; } |
|||
|
|||
public double BlueMin { get; set; } |
|||
|
|||
public void UpdateDominantCostRange(Vp8LHistogram h) |
|||
{ |
|||
if (this.LiteralMax < h.LiteralCost) |
|||
{ |
|||
this.LiteralMax = h.LiteralCost; |
|||
} |
|||
|
|||
if (this.LiteralMin > h.LiteralCost) |
|||
{ |
|||
this.LiteralMin = h.LiteralCost; |
|||
} |
|||
|
|||
if (this.RedMax < h.RedCost) |
|||
{ |
|||
this.RedMax = h.RedCost; |
|||
} |
|||
|
|||
if (this.RedMin > h.RedCost) |
|||
{ |
|||
this.RedMin = h.RedCost; |
|||
} |
|||
|
|||
if (this.BlueMax < h.BlueCost) |
|||
{ |
|||
this.BlueMax = h.BlueCost; |
|||
} |
|||
|
|||
if (this.BlueMin > h.BlueCost) |
|||
{ |
|||
this.BlueMin = h.BlueCost; |
|||
} |
|||
} |
|||
|
|||
public int GetHistoBinIndex(Vp8LHistogram h, int numPartitions) |
|||
{ |
|||
int binId = GetBinIdForEntropy(this.LiteralMin, this.LiteralMax, h.LiteralCost, numPartitions); |
|||
binId = (binId * numPartitions) + GetBinIdForEntropy(this.RedMin, this.RedMax, h.RedCost, numPartitions); |
|||
binId = (binId * numPartitions) + GetBinIdForEntropy(this.BlueMin, this.BlueMax, h.BlueCost, numPartitions); |
|||
|
|||
return binId; |
|||
} |
|||
|
|||
private static int GetBinIdForEntropy(double min, double max, double val, int numPartitions) |
|||
{ |
|||
double range = max - min; |
|||
if (range > 0.0d) |
|||
{ |
|||
double delta = val - min; |
|||
return (int)((numPartitions - 1e-6) * delta / range); |
|||
} |
|||
else |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,627 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the GNU Affero General Public License, Version 3.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP.Lossless |
|||
{ |
|||
internal class HistogramEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Number of partitions for the three dominant (literal, red and blue) symbol costs.
|
|||
/// </summary>
|
|||
private const int NumPartitions = 4; |
|||
|
|||
/// <summary>
|
|||
/// The size of the bin-hash corresponding to the three dominant costs.
|
|||
/// </summary>
|
|||
private const int BinSize = NumPartitions * NumPartitions * NumPartitions; |
|||
|
|||
/// <summary>
|
|||
/// Maximum number of histograms allowed in greedy combining algorithm.
|
|||
/// </summary>
|
|||
private const int MaxHistoGreedy = 100; |
|||
|
|||
private const uint NonTrivialSym = 0xffffffff; |
|||
|
|||
public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, short[] histogramSymbols) |
|||
{ |
|||
int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; |
|||
int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; |
|||
int imageHistoRawSize = histoXSize * histoYSize; |
|||
int entropyCombineNumBins = BinSize; |
|||
short[] mapTmp = new short[imageHistoRawSize]; |
|||
short[] clusterMappings = new short[imageHistoRawSize]; |
|||
int numUsed = imageHistoRawSize; |
|||
var origHisto = new List<Vp8LHistogram>(imageHistoRawSize); |
|||
for (int i = 0; i < imageHistoRawSize; i++) |
|||
{ |
|||
origHisto.Add(new Vp8LHistogram(cacheBits)); |
|||
} |
|||
|
|||
// Construct the histograms from backward references.
|
|||
HistogramBuild(xSize, histoBits, refs, origHisto); |
|||
|
|||
// Copies the histograms and computes its bit_cost. histogramSymbols is optimized.
|
|||
HistogramCopyAndAnalyze(origHisto, imageHisto, ref numUsed, histogramSymbols); |
|||
|
|||
var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); |
|||
if (entropyCombine) |
|||
{ |
|||
var binMap = mapTmp; |
|||
var numClusters = numUsed; |
|||
double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); |
|||
HistogramAnalyzeEntropyBin(imageHisto, binMap); |
|||
|
|||
// Collapse histograms with similar entropy.
|
|||
HistogramCombineEntropyBin(imageHisto, ref numUsed, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); |
|||
|
|||
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) |
|||
{ |
|||
HistogramCombineGreedy(imageHisto, ref numUsed); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Construct the histograms from backward references.
|
|||
/// </summary>
|
|||
private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List<Vp8LHistogram> histograms) |
|||
{ |
|||
int x = 0, y = 0; |
|||
int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits); |
|||
using List<PixOrCopy>.Enumerator backwardRefsEnumerator = backwardRefs.Refs.GetEnumerator(); |
|||
while (backwardRefsEnumerator.MoveNext()) |
|||
{ |
|||
PixOrCopy v = backwardRefsEnumerator.Current; |
|||
int ix = ((y >> histoBits) * histoXSize) + (x >> histoBits); |
|||
histograms[ix].AddSinglePixOrCopy(v, false); |
|||
x += v.Len; |
|||
while (x >= xSize) |
|||
{ |
|||
x -= xSize; |
|||
y++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Partition histograms to different entropy bins for three dominant (literal,
|
|||
/// red and blue) symbol costs and compute the histogram aggregate bitCost.
|
|||
/// </summary>
|
|||
private static void HistogramAnalyzeEntropyBin(List<Vp8LHistogram> histograms, short[] binMap) |
|||
{ |
|||
int histoSize = histograms.Count; |
|||
var costRange = new DominantCostRange(); |
|||
|
|||
// Analyze the dominant (literal, red and blue) entropy costs.
|
|||
for (int i = 0; i < histoSize; i++) |
|||
{ |
|||
costRange.UpdateDominantCostRange(histograms[i]); |
|||
} |
|||
|
|||
// bin-hash histograms on three of the dominant (literal, red and blue)
|
|||
// symbol costs and store the resulting bin_id for each histogram.
|
|||
for (int i = 0; i < histoSize; i++) |
|||
{ |
|||
binMap[i] = (short)costRange.GetHistoBinIndex(histograms[i], NumPartitions); |
|||
} |
|||
} |
|||
|
|||
private static void HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, ref int numUsed, short[] histogramSymbols) |
|||
{ |
|||
int numUsedOrig = numUsed; |
|||
var indicesToRemove = new List<int>(); |
|||
for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) |
|||
{ |
|||
Vp8LHistogram histo = origHistograms[i]; |
|||
histo.UpdateHistogramCost(); |
|||
|
|||
// Skip the histogram if it is completely empty, which can happen for tiles
|
|||
// with no information (when they are skipped because of LZ77).
|
|||
if (!histo.IsUsed[0] && !histo.IsUsed[1] && !histo.IsUsed[2] && !histo.IsUsed[3] && !histo.IsUsed[4]) |
|||
{ |
|||
indicesToRemove.Add(i); |
|||
} |
|||
else |
|||
{ |
|||
// TODO: HistogramCopy(histo, histograms[i]);
|
|||
histogramSymbols[i] = (short)clusterId++; |
|||
} |
|||
} |
|||
|
|||
foreach (int indice in indicesToRemove.OrderByDescending(v => v)) |
|||
{ |
|||
origHistograms.RemoveAt(indice); |
|||
histograms.RemoveAt(indice); |
|||
} |
|||
} |
|||
|
|||
private static void HistogramCombineEntropyBin(List<Vp8LHistogram> histograms, ref int numUsed, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor) |
|||
{ |
|||
for (int idx = 0; idx < histograms.Count; idx++) |
|||
{ |
|||
clusterMappings[idx] = (short)idx; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
private static void OptimizeHistogramSymbols(List<Vp8LHistogram> histograms, short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols) |
|||
{ |
|||
int clusterMax; |
|||
bool doContinue = true; |
|||
|
|||
// First, assign the lowest cluster to each pixel.
|
|||
while (doContinue) |
|||
{ |
|||
doContinue = false; |
|||
for (int i = 0; i < numClusters; i++) |
|||
{ |
|||
int k; |
|||
k = clusterMappings[i]; |
|||
while (k != clusterMappings[k]) |
|||
{ |
|||
clusterMappings[k] = clusterMappings[clusterMappings[k]]; |
|||
k = clusterMappings[k]; |
|||
} |
|||
|
|||
if (k != clusterMappings[i]) |
|||
{ |
|||
doContinue = true; |
|||
clusterMappings[i] = (short)k; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Create a mapping from a cluster id to its minimal version.
|
|||
clusterMax = 0; |
|||
clusterMappingsTmp.AsSpan().Fill(0); |
|||
|
|||
// Re-map the ids.
|
|||
for (int i = 0; i < histograms.Count; i++) |
|||
{ |
|||
int cluster; |
|||
cluster = clusterMappings[symbols[i]]; |
|||
if (cluster > 0 && clusterMappingsTmp[cluster] == 0) |
|||
{ |
|||
clusterMax++; |
|||
clusterMappingsTmp[cluster] = (short)clusterMax; |
|||
} |
|||
|
|||
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++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Perform histogram aggregation using a stochastic approach.
|
|||
/// </summary>
|
|||
/// <returns>true if a greedy approach needs to be performed afterwards, false otherwise.</returns>
|
|||
private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, ref int numUsed, int minClusterSize) |
|||
{ |
|||
var rand = new Random(); |
|||
int triesWithNoSuccess = 0; |
|||
int outerIters = numUsed; |
|||
int numTriesNoSuccess = outerIters / 2; |
|||
|
|||
// 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<HistogramPair>(); |
|||
int histoQueueMaxSize = histograms.Count * histograms.Count; |
|||
|
|||
// Fill the initial mapping.
|
|||
int[] mappings = new int[histograms.Count]; |
|||
for (int j = 0, iter = 0; iter < histograms.Count; iter++) |
|||
{ |
|||
mappings[j++] = iter; |
|||
} |
|||
|
|||
// Collapse similar histograms
|
|||
for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) |
|||
{ |
|||
double bestCost = (histoPriorityList.Count == 0) ? 0.0d : histoPriorityList[0].CostDiff; |
|||
int bestIdx1 = -1; |
|||
int bestIdx2 = 1; |
|||
int numTries = numUsed / 2; // TODO: should that be histogram.Count/2?
|
|||
uint randRange = (uint)((numUsed - 1) * numUsed); |
|||
|
|||
// Pick random samples.
|
|||
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); |
|||
double currCost; |
|||
int idx1 = (int)(tmp / (numUsed - 1)); |
|||
int idx2 = (int)(tmp % (numUsed - 1)); |
|||
if (idx2 >= idx1) |
|||
{ |
|||
idx2++; |
|||
} |
|||
|
|||
idx1 = mappings[idx1]; |
|||
idx2 = mappings[idx2]; |
|||
|
|||
// Calculate cost reduction on combination.
|
|||
currCost = HistoQueuePush(histoPriorityList, histoQueueMaxSize, histograms, idx1, idx2, bestCost); |
|||
|
|||
// Found a better pair?
|
|||
if (currCost < 0) |
|||
{ |
|||
bestCost = currCost; |
|||
|
|||
// Empty the queue if we reached full capacity.
|
|||
if (histoPriorityList.Count == histoQueueMaxSize) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (histoPriorityList.Count == 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Get the best histograms.
|
|||
bestIdx1 = histoPriorityList[0].Idx1; |
|||
bestIdx2 = histoPriorityList[0].Idx2; |
|||
|
|||
// Pop bestIdx2 from mappings.
|
|||
var mappingIndex = Array.BinarySearch(mappings, bestIdx2); |
|||
// TODO: memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *((*num_used) - (mapping_index - mappings) - 1));
|
|||
|
|||
// Merge the histograms and remove bestIdx2 from the queue.
|
|||
HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); |
|||
histograms.ElementAt(bestIdx1).BitCost = histoPriorityList[0].CostCombo; |
|||
histograms.RemoveAt(bestIdx2); |
|||
numUsed--; |
|||
|
|||
var indicesToRemove = new List<int>(); |
|||
int lastIndex = histoPriorityList.Count - 1; |
|||
for (int j = 0; j < histoPriorityList.Count;) |
|||
{ |
|||
HistogramPair p = histoPriorityList.ElementAt(j); |
|||
bool isIdx1Best = p.Idx1 == bestIdx1 || p.Idx1 == bestIdx2; |
|||
bool isIdx2Best = p.Idx2 == bestIdx1 || p.Idx2 == bestIdx2; |
|||
bool doEval = false; |
|||
|
|||
// The front pair could have been duplicated by a random pick so
|
|||
// check for it all the time nevertheless.
|
|||
if (isIdx1Best && isIdx2Best) |
|||
{ |
|||
indicesToRemove.Add(lastIndex); |
|||
numUsed--; |
|||
lastIndex--; |
|||
continue; |
|||
} |
|||
|
|||
// Any pair containing one of the two best indices should only refer to
|
|||
// best_idx1. Its cost should also be updated.
|
|||
if (isIdx1Best) |
|||
{ |
|||
p.Idx1 = bestIdx1; |
|||
doEval = true; |
|||
} |
|||
else if (isIdx2Best) |
|||
{ |
|||
p.Idx2 = bestIdx1; |
|||
doEval = true; |
|||
} |
|||
|
|||
// Make sure the index order is respected.
|
|||
if (p.Idx1 > p.Idx2) |
|||
{ |
|||
int tmp = p.Idx2; |
|||
p.Idx2 = p.Idx1; |
|||
p.Idx1 = tmp; |
|||
} |
|||
|
|||
if (doEval) |
|||
{ |
|||
// Re-evaluate the cost of an updated pair.
|
|||
HistoQueueUpdatePair(histograms[p.Idx1], histograms[p.Idx2], 0.0d, p); |
|||
if (p.CostDiff >= 0.0d) |
|||
{ |
|||
indicesToRemove.Add(lastIndex); |
|||
lastIndex--; |
|||
numUsed--; |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
HistoQueueUpdateHead(histoPriorityList, p); |
|||
j++; |
|||
} |
|||
|
|||
triesWithNoSuccess = 0; |
|||
} |
|||
|
|||
bool doGreedy = numUsed <= minClusterSize; |
|||
|
|||
return doGreedy; |
|||
} |
|||
|
|||
private static void HistogramCombineGreedy(List<Vp8LHistogram> histograms, ref int numUsed) |
|||
{ |
|||
int histoSize = histograms.Count; |
|||
|
|||
// Priority list of histogram pairs.
|
|||
var histoPriorityList = new List<HistogramPair>(); |
|||
int maxHistoQueueSize = 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); |
|||
} |
|||
} |
|||
|
|||
while (histoPriorityList.Count > 0) |
|||
{ |
|||
int idx1 = histoPriorityList[0].Idx1; |
|||
int idx2 = histoPriorityList[0].Idx2; |
|||
HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]); |
|||
histograms[idx1].BitCost = histoPriorityList[0].CostCombo; |
|||
|
|||
// Remove merged histogram.
|
|||
histograms.RemoveAt(idx2); |
|||
numUsed--; |
|||
|
|||
// Remove pairs intersecting the just combined best pair.
|
|||
for (int i = 0; i < histoPriorityList.Count;) |
|||
{ |
|||
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?
|
|||
} |
|||
else |
|||
{ |
|||
HistoQueueUpdateHead(histoPriorityList, p); |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
// Push new pairs formed with combined histogram to the queue.
|
|||
for (int i = 0; i < histograms.Count; i++) |
|||
{ |
|||
if (i == idx1) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
HistoQueuePush(histoPriorityList, maxHistoQueueSize, histograms, idx1, i, 0.0d); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// // Create a pair from indices "idx1" and "idx2" provided its cost
|
|||
/// is inferior to "threshold", a negative entropy.
|
|||
/// </summary>
|
|||
/// <returns>The cost of the pair, or 0. if it superior to threshold.</returns>
|
|||
private static double HistoQueuePush(List<HistogramPair> histoQueue, int queueMaxSize, List<Vp8LHistogram> histograms, int idx1, int idx2, double threshold) |
|||
{ |
|||
var pair = new HistogramPair(); |
|||
|
|||
// Stop here if the queue is full.
|
|||
if (histoQueue.Count == queueMaxSize) |
|||
{ |
|||
return 0.0d; |
|||
} |
|||
|
|||
if (idx1 > idx2) |
|||
{ |
|||
int tmp = idx2; |
|||
idx2 = idx1; |
|||
idx1 = tmp; |
|||
} |
|||
|
|||
pair.Idx1 = idx1; |
|||
pair.Idx2 = idx2; |
|||
Vp8LHistogram h1 = histograms[idx1]; |
|||
Vp8LHistogram h2 = histograms[idx2]; |
|||
|
|||
HistoQueueUpdatePair(h1, h2, threshold, pair); |
|||
|
|||
// Do not even consider the pair if it does not improve the entropy.
|
|||
if (pair.CostDiff >= threshold) |
|||
{ |
|||
return 0.0d; |
|||
} |
|||
|
|||
histoQueue.Add(pair); |
|||
|
|||
HistoQueueUpdateHead(histoQueue, pair); |
|||
|
|||
return pair.CostDiff; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
private static void HistoQueueUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, double threshold, HistogramPair pair) |
|||
{ |
|||
double sumCost = h1.BitCost + h2.BitCost; |
|||
pair.CostCombo = GetCombinedHistogramEntropy(h1, h2, sumCost + threshold); |
|||
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<uint> x, Span<uint> 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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Check whether a pair in the list should be updated as head or not.
|
|||
/// </summary>
|
|||
private static void HistoQueueUpdateHead(List<HistogramPair> histoQueue, HistogramPair pair) |
|||
{ |
|||
if (pair.CostDiff < histoQueue[0].CostDiff) |
|||
{ |
|||
// Replace the best pair.
|
|||
histoQueue.RemoveAt(0); |
|||
histoQueue.Insert(0, pair); |
|||
} |
|||
} |
|||
|
|||
private static double GetCombineCostFactor(int histoSize, int quality) |
|||
{ |
|||
double combineCostFactor = 0.16d; |
|||
if (quality < 90) |
|||
{ |
|||
if (histoSize > 256) |
|||
{ |
|||
combineCostFactor /= 2.0d; |
|||
} |
|||
|
|||
if (histoSize > 512) |
|||
{ |
|||
combineCostFactor /= 2.0d; |
|||
} |
|||
|
|||
if (histoSize > 1024) |
|||
{ |
|||
combineCostFactor /= 2.0d; |
|||
} |
|||
|
|||
if (quality <= 50) |
|||
{ |
|||
combineCostFactor /= 2.0d; |
|||
} |
|||
} |
|||
|
|||
return combineCostFactor; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the GNU Affero General Public License, Version 3.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.WebP.Lossless |
|||
{ |
|||
/// <summary>
|
|||
/// Pair of histograms. Negative Idx1 value means that pair is out-of-date.
|
|||
/// </summary>
|
|||
internal class HistogramPair |
|||
{ |
|||
public int Idx1 { get; set; } |
|||
|
|||
public int Idx2 { get; set; } |
|||
|
|||
public double CostDiff { get; set; } |
|||
|
|||
public double CostCombo { get; set; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue