Browse Source

Some bug fixes:

- Distance was not copied in histogram DeepClone
- Do not remove entries from the histogram: instead set them to null like the original code
- Fix invalid upper bound in for loop of HistogramRemap
pull/1552/head
Brian Popow 6 years ago
parent
commit
91ff2ada2e
  1. 62
      src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs
  2. 18
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  3. 1
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs

62
src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs

@ -26,6 +26,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
private const uint NonTrivialSym = 0xffffffff; private const uint NonTrivialSym = 0xffffffff;
private const short InvalidHistogramSymbol = Int16.MaxValue;
public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, short[] histogramSymbols) 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 histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1;
@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
HistogramBuild(xSize, histoBits, refs, origHisto); HistogramBuild(xSize, histoBits, refs, origHisto);
// Copies the histograms and computes its bitCost. histogramSymbols is optimized. // Copies the histograms and computes its bitCost. histogramSymbols is optimized.
HistogramCopyAndAnalyze(origHisto, imageHisto, ref numUsed, histogramSymbols); HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols);
var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100); var entropyCombine = (numUsed > entropyCombineNumBins * 2) && (quality < 100);
if (entropyCombine) if (entropyCombine)
@ -56,9 +58,9 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
HistogramAnalyzeEntropyBin(imageHisto, binMap); HistogramAnalyzeEntropyBin(imageHisto, binMap);
// Collapse histograms with similar entropy. // Collapse histograms with similar entropy.
HistogramCombineEntropyBin(imageHisto, ref numUsed, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); HistogramCombineEntropyBin(imageHisto, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor);
OptimizeHistogramSymbols(imageHisto, clusterMappings, numClusters, mapTmp, histogramSymbols); OptimizeHistogramSymbols(clusterMappings, numClusters, mapTmp, histogramSymbols);
} }
float x = quality / 100.0f; float x = quality / 100.0f;
@ -131,6 +133,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Analyze the dominant (literal, red and blue) entropy costs. // Analyze the dominant (literal, red and blue) entropy costs.
for (int i = 0; i < histoSize; i++) for (int i = 0; i < histoSize; i++)
{ {
if (histograms[i] == null)
{
continue;
}
costRange.UpdateDominantCostRange(histograms[i]); costRange.UpdateDominantCostRange(histograms[i]);
} }
@ -138,40 +145,38 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// symbol costs and store the resulting bin_id for each histogram. // symbol costs and store the resulting bin_id for each histogram.
for (int i = 0; i < histoSize; i++) for (int i = 0; i < histoSize; i++)
{ {
if (histograms[i] == null)
{
continue;
}
binMap[i] = (short)costRange.GetHistoBinIndex(histograms[i], NumPartitions); binMap[i] = (short)costRange.GetHistoBinIndex(histograms[i], NumPartitions);
} }
} }
private static void HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, ref int numUsed, short[] histogramSymbols) private static void HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, short[] histogramSymbols)
{ {
int numUsedOrig = numUsed;
var indicesToRemove = new List<int>();
for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) for (int clusterId = 0, i = 0; i < origHistograms.Count; i++)
{ {
Vp8LHistogram histo = origHistograms[i]; Vp8LHistogram origHistogram = origHistograms[i];
histo.UpdateHistogramCost(); origHistogram.UpdateHistogramCost();
// Skip the histogram if it is completely empty, which can happen for tiles // Skip the histogram if it is completely empty, which can happen for tiles with no information (when they are skipped because of LZ77).
// with no information (when they are skipped because of LZ77). if (!origHistogram.IsUsed[0] && !origHistogram.IsUsed[1] && !origHistogram.IsUsed[2] && !origHistogram.IsUsed[3] && !origHistogram.IsUsed[4])
if (!histo.IsUsed[0] && !histo.IsUsed[1] && !histo.IsUsed[2] && !histo.IsUsed[3] && !histo.IsUsed[4])
{ {
indicesToRemove.Add(i); origHistograms[i] = null;
histograms[i] = null;
histogramSymbols[i] = InvalidHistogramSymbol;
} }
else else
{ {
histograms[i] = (Vp8LHistogram)histo.DeepClone(); histograms[i] = (Vp8LHistogram)origHistogram.DeepClone();
histogramSymbols[i] = (short)clusterId++; histogramSymbols[i] = (short)clusterId++;
} }
} }
foreach (int index in indicesToRemove.OrderByDescending(v => v))
{
origHistograms.RemoveAt(index);
histograms.RemoveAt(index);
}
} }
private static void HistogramCombineEntropyBin(List<Vp8LHistogram> histograms, ref int numUsed, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor) private static void HistogramCombineEntropyBin(List<Vp8LHistogram> histograms, short[] clusters, short[] clusterMappings, Vp8LHistogram curCombo, short[] binMap, int numBins, double combineCostFactor)
{ {
var binInfo = new HistogramBinInfo[BinSize]; var binInfo = new HistogramBinInfo[BinSize];
for (int idx = 0; idx < numBins; idx++) for (int idx = 0; idx < numBins; idx++)
@ -245,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// Given a Histogram set, the mapping of clusters 'clusterMapping' and the /// 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.
/// </summary> /// </summary>
private static void OptimizeHistogramSymbols(List<Vp8LHistogram> histograms, short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols) private static void OptimizeHistogramSymbols(short[] clusterMappings, int numClusters, short[] clusterMappingsTmp, short[] symbols)
{ {
bool doContinue = true; bool doContinue = true;
@ -278,6 +283,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Re-map the ids. // Re-map the ids.
for (int i = 0; i < symbols.Length; i++) for (int i = 0; i < symbols.Length; i++)
{ {
if (symbols[i] == InvalidHistogramSymbol)
{
continue;
}
int cluster = clusterMappings[symbols[i]]; int cluster = clusterMappings[symbols[i]];
if (cluster > 0 && clusterMappingsTmp[cluster] == 0) if (cluster > 0 && clusterMappingsTmp[cluster] == 0)
{ {
@ -466,7 +476,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
histograms[idx1].BitCost = histoPriorityList[0].CostCombo; histograms[idx1].BitCost = histoPriorityList[0].CostCombo;
// Remove merged histogram. // Remove merged histogram.
// TODO: can the element be removed instead? histograms.RemoveAt(idx2);
histograms[idx2] = null; histograms[idx2] = null;
// Remove pairs intersecting the just combined best pair. // Remove pairs intersecting the just combined best pair.
@ -501,12 +510,19 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram> output, short[] symbols) private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram> output, short[] symbols)
{ {
int inSize = symbols.Length; int inSize = input.Count;
int outSize = output.Count; int outSize = output.Count;
if (outSize > 1) if (outSize > 1)
{ {
for (int i = 0; i < inSize; i++) for (int i = 0; i < inSize; i++)
{ {
if (input[i] == null)
{
// Arbitrarily set to the previous value if unused to help future LZ77.
symbols[i] = symbols[i - 1];
continue;
}
int bestOut = 0; int bestOut = 0;
double bestBits = double.MaxValue; double bestBits = double.MaxValue;
for (int k = 0; k < outSize; k++) for (int k = 0; k < outSize; k++)

18
src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs

@ -9,6 +9,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.WebP.BitWriter; using SixLabors.ImageSharp.Formats.WebP.BitWriter;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -417,12 +418,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
foreach (CrunchSubConfig subConfig in config.SubConfigs) foreach (CrunchSubConfig subConfig in config.SubConfigs)
{ {
Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, subConfig.Lz77, ref cacheBits, hashChain, refsArray[0], refsArray[1]); // TODO : Pass do not cache Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(width, height, bgra, quality, subConfig.Lz77, ref cacheBits, hashChain, refsArray[0], refsArray[1]); // TODO : Pass do not cache
// Keep the best references aside and use the other element from the first // Keep the best references aside and use the other element from the first
// two as a temporary for later usage. // two as a temporary for later usage.
Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0]; Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0];
// TODO : Loop based on cache/no cache // TODO : Loop based on cache/no cache
// TODO: this.bitWriter.Reset(); // TODO: this.bitWriter.Reset();
var tmpHisto = new Vp8LHistogram(cacheBits); var tmpHisto = new Vp8LHistogram(cacheBits);
var histogramImage = new List<Vp8LHistogram>(histogramImageXySize); var histogramImage = new List<Vp8LHistogram>(histogramImageXySize);
@ -474,7 +476,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
} }
} }
histogramImageSize = maxIndex;
this.bitWriter.PutBits((uint)(histogramBits - 2), 3); this.bitWriter.PutBits((uint)(histogramBits - 2), 3);
this.EncodeImageNoHuffman(histogramArgb, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality); this.EncodeImageNoHuffman(histogramArgb, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), quality);
} }
@ -482,7 +483,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Store Huffman codes. // Store Huffman codes.
// Find maximum number of symbols for the huffman tree-set. // Find maximum number of symbols for the huffman tree-set.
int maxTokens = 0; int maxTokens = 0;
for (int i = 0; i < 5 * histogramImageSize; i++) for (int i = 0; i < 5 * histogramImage.Count; i++)
{ {
HuffmanTreeCode codes = huffmanCodes[i]; HuffmanTreeCode codes = huffmanCodes[i];
if (maxTokens < codes.NumSymbols) if (maxTokens < codes.NumSymbols)
@ -497,7 +498,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
tokens[i] = new HuffmanTreeToken(); tokens[i] = new HuffmanTreeToken();
} }
for (int i = 0; i < 5 * histogramImageSize; i++) for (int i = 0; i < 5 * histogramImage.Count; i++)
{ {
HuffmanTreeCode codes = huffmanCodes[i]; HuffmanTreeCode codes = huffmanCodes[i];
this.StoreHuffmanCode(huffTree, tokens, codes); this.StoreHuffmanCode(huffTree, tokens, codes);
@ -912,7 +913,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return EntropyIx.Palette; return EntropyIx.Palette;
} }
using System.Buffers.IMemoryOwner<uint> histoBuffer = this.memoryAllocator.Allocate<uint>((int)HistoIx.HistoTotal * 256); using IMemoryOwner<uint> histoBuffer = this.memoryAllocator.Allocate<uint>((int)HistoIx.HistoTotal * 256);
Span<uint> histo = histoBuffer.Memory.Span; Span<uint> histo = histoBuffer.Memory.Span;
Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel. Bgra32 pixPrev = ToBgra32(image.GetPixelRowSpan(0)[0]); // Skip the first pixel.
Span<TPixel> prevRow = null; Span<TPixel> prevRow = null;
@ -955,7 +956,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++;
} }
var histo0 = histo[0];
prevRow = currentRow; prevRow = currentRow;
} }
@ -1149,7 +1149,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// </summary> /// </summary>
private void ApplyPalette(Span<uint> src, int srcStride, Span<uint> dst, int dstStride, Span<uint> palette, int paletteSize, int width, int height, int xBits) private void ApplyPalette(Span<uint> src, int srcStride, Span<uint> dst, int dstStride, Span<uint> palette, int paletteSize, int width, int height, int xBits)
{ {
using System.Buffers.IMemoryOwner<byte> tmpRowBuffer = this.memoryAllocator.Allocate<byte>(width); using IMemoryOwner<byte> tmpRowBuffer = this.memoryAllocator.Allocate<byte>(width);
Span<byte> tmpRow = tmpRowBuffer.GetSpan(); Span<byte> tmpRow = tmpRowBuffer.GetSpan();
if (paletteSize < ApplyPaletteGreedyMax) if (paletteSize < ApplyPaletteGreedyMax)
@ -1209,8 +1209,8 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
} }
else else
{ {
uint[] idxMap = new uint[paletteSize]; var idxMap = new uint[paletteSize];
uint[] paletteSorted = new uint[paletteSize]; var paletteSorted = new uint[paletteSize];
PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap);
ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize);
} }

1
src/ImageSharp/Formats/WebP/Lossless/Vp8LHistogram.cs

@ -28,6 +28,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
other.Blue.AsSpan().CopyTo(this.Blue); other.Blue.AsSpan().CopyTo(this.Blue);
other.Alpha.AsSpan().CopyTo(this.Alpha); other.Alpha.AsSpan().CopyTo(this.Alpha);
other.Literal.AsSpan().CopyTo(this.Literal); other.Literal.AsSpan().CopyTo(this.Literal);
other.Distance.AsSpan().CopyTo(this.Distance);
other.IsUsed.AsSpan().CopyTo(this.IsUsed); other.IsUsed.AsSpan().CopyTo(this.IsUsed);
this.LiteralCost = other.LiteralCost; this.LiteralCost = other.LiteralCost;
this.RedCost = other.RedCost; this.RedCost = other.RedCost;

Loading…
Cancel
Save