Browse Source

Select bitwriter which produces the least amount of bytes for the image

pull/1552/head
Brian Popow 6 years ago
parent
commit
86c0381729
  1. 2
      src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs
  2. 7
      src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
  3. 21
      src/ImageSharp/Formats/WebP/Lossless/HistogramEncoder.cs
  4. 48
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs

2
src/ImageSharp/Formats/WebP/BitReader/BitReaderBase.cs

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitReader
if (bytesToRead > 0)
{
WebPThrowHelper.ThrowImageFormatException("image file has insufficient data");
WebPThrowHelper.ThrowImageFormatException("webp image file has insufficient data");
}
}
}

7
src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs

@ -85,6 +85,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.BitWriter
}
}
public void Reset(Vp8LBitWriter bwInit)
{
this.bits = bwInit.bits;
this.used = bwInit.used;
this.cur = bwInit.cur;
}
public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex)
{
int depth = code.CodeLengths[codeIndex];

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

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
@ -309,7 +310,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
/// <returns>true if a greedy approach needs to be performed afterwards, false otherwise.</returns>
private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, int minClusterSize)
{
var rand = new Random();
uint seed = 1;
int triesWithNoSuccess = 0;
var numUsed = histograms.Count(h => h != null);
int outerIters = numUsed;
@ -350,7 +351,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
for (int j = 0; numUsed >= 2 && j < numTries; j++)
{
// Choose two different histograms at random and try to combine them.
uint tmp = (uint)(rand.Next() % randRange);
uint tmp = MyRand(ref seed) % randRange;
int idx1 = (int)(tmp / (numUsed - 1));
int idx2 = (int)(tmp % (numUsed - 1));
if (idx2 >= idx1)
@ -385,10 +386,10 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
bestIdx1 = histoPriorityList[0].Idx1;
bestIdx2 = histoPriorityList[0].Idx2;
// TODO: Review this again, not sure why this is needed in the reference implementation.
// Pop bestIdx2 from mappings.
// var mappingIndex = Array.BinarySearch(mappings, bestIdx2);
// memmove(mapping_index, mapping_index + 1, sizeof(*mapping_index) *((*num_used) - (mapping_index - mappings) - 1));
var mappingIndex = Array.IndexOf(mappings, bestIdx2);
Span<int> src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1);
Span<int> dst = mappings.AsSpan(mappingIndex);
src.CopyTo(dst);
// Merge the histograms and remove bestIdx2 from the queue.
HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]);
@ -680,5 +681,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return combineCostFactor;
}
// Implement a Lehmer random number generator with a multiplicative constant of 48271 and a modulo constant of 2^31 - 1.
[MethodImpl(InliningOptions.ShortMethod)]
private static uint MyRand(ref uint seed)
{
seed = (uint)(((ulong)seed * 48271u) % 2147483647u);
return seed;
}
}
}

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

@ -252,7 +252,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
int width = image.Width;
int height = image.Height;
int bytePosition = this.bitWriter.NumBytes();
// Convert image pixels to bgra array.
Span<uint> bgra = this.Bgra.GetSpan();
@ -269,12 +268,15 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Analyze image (entropy, numPalettes etc).
CrunchConfig[] crunchConfigs = this.EncoderAnalyze(image, out bool redAndBlueAlwaysZero);
// TODO : Do we want to do this multi-threaded, this will probably require a second class:
// one which co-ordinates the threading and comparison and another which does the actual encoding
int bestSize = 0;
Vp8LBitWriter bitWriterInit = this.bitWriter;
Vp8LBitWriter bitWriterBest = this.bitWriter.Clone();
bool isFirstConfig = true;
foreach (CrunchConfig crunchConfig in crunchConfigs)
{
bool useCache = true;
this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial;
this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette ||
crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial;
this.UseSubtractGreenTransform = (crunchConfig.EntropyIdx == EntropyIx.SubGreen) ||
(crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen);
this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) ||
@ -329,9 +331,25 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
useCache,
crunchConfig,
this.CacheBits,
this.HistoBits,
bytePosition);
this.HistoBits);
// If we are better than what we already have.
if (isFirstConfig || this.bitWriter.NumBytes() < bestSize)
{
bestSize = this.bitWriter.NumBytes();
this.BitWriterSwap(ref this.bitWriter, ref bitWriterBest);
}
// Reset the bit writer for the following iteration if any.
if (crunchConfigs.Length > 1)
{
this.bitWriter.Reset(bitWriterInit);
}
isFirstConfig = false;
}
this.BitWriterSwap(ref bitWriterBest, ref this.bitWriter);
}
/// <summary>
@ -364,8 +382,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
// Go brute force on all transforms.
foreach (EntropyIx entropyIx in Enum.GetValues(typeof(EntropyIx)).Cast<EntropyIx>())
{
// We can only apply kPalette or kPaletteAndSpatial if we can indeed use
// a palette.
// We can only apply kPalette or kPaletteAndSpatial if we can indeed use a palette.
if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette)
{
crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIx });
@ -405,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
return crunchConfigs.ToArray();
}
private void EncodeImage(Span<uint> bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition)
private void EncodeImage(Span<uint> bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits)
{
int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits);
var histogramSymbols = new ushort[histogramImageXySize];
@ -432,7 +449,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
BackwardReferenceEncoder.HashChainFill(hashChain, bgra, this.quality, width, height);
Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter;
Vp8LBitWriter bwInit = this.bitWriter;
foreach (CrunchSubConfig subConfig in config.SubConfigs)
{
Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences(
@ -451,8 +468,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
Vp8LBackwardRefs refsTmp = refsArray[refsBest.Equals(refsArray[0]) ? 1 : 0];
// TODO : Loop based on cache/no cache
// TODO: this.bitWriter.Reset();
this.bitWriter.Reset(bwInit);
var tmpHisto = new Vp8LHistogram(cacheBits);
var histogramImage = new List<Vp8LHistogram>(histogramImageXySize);
for (int i = 0; i < histogramImageXySize; i++)
@ -1583,6 +1599,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private void BitWriterSwap(ref Vp8LBitWriter src, ref Vp8LBitWriter dst)
{
Vp8LBitWriter tmp = src;
src = dst;
dst = tmp;
}
/// <summary>
/// Calculates the bits used for the transformation.
/// </summary>

Loading…
Cancel
Save