Browse Source

Use separate buffer for encoded image data and input bgra data

pull/1552/head
Brian Popow 5 years ago
parent
commit
1fe943a9fa
  1. 62
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs

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

@ -80,6 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.method = Numerics.Clamp(method, 0, 6); this.method = Numerics.Clamp(method, 0, 6);
this.bitWriter = new Vp8LBitWriter(initialSize); this.bitWriter = new Vp8LBitWriter(initialSize);
this.Bgra = memoryAllocator.Allocate<uint>(pixelCount); this.Bgra = memoryAllocator.Allocate<uint>(pixelCount);
this.EncodedData = memoryAllocator.Allocate<uint>(pixelCount);
this.Palette = memoryAllocator.Allocate<uint>(WebpConstants.MaxPaletteSize); this.Palette = memoryAllocator.Allocate<uint>(WebpConstants.MaxPaletteSize);
this.Refs = new Vp8LBackwardRefs[3]; this.Refs = new Vp8LBackwardRefs[3];
this.HashChain = new Vp8LHashChain(pixelCount); this.HashChain = new Vp8LHashChain(pixelCount);
@ -96,10 +97,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
/// <summary> /// <summary>
/// Gets the memory for the encoded output image data. /// Gets the memory for the image data as packed bgra values.
/// </summary> /// </summary>
public IMemoryOwner<uint> Bgra { get; } public IMemoryOwner<uint> Bgra { get; }
/// <summary>
/// Gets the memory for the encoded output image data.
/// </summary>
public IMemoryOwner<uint> EncodedData { get; }
/// <summary> /// <summary>
/// Gets or sets the scratch memory for bgra rows used for predictions. /// Gets or sets the scratch memory for bgra rows used for predictions.
/// </summary> /// </summary>
@ -237,7 +243,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// Convert image pixels to bgra array. // Convert image pixels to bgra array.
this.ConvertPixelsToBgra(image, width, height); this.ConvertPixelsToBgra(image, width, height);
Span<uint> bgra = this.Bgra.GetSpan(); ReadOnlySpan<uint> bgra = this.Bgra.GetSpan();
Span<uint> encodedData = this.EncodedData.GetSpan();
// Analyze image (entropy, numPalettes etc). // Analyze image (entropy, numPalettes etc).
CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero); CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero);
@ -248,6 +255,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
bool isFirstConfig = true; bool isFirstConfig = true;
foreach (CrunchConfig crunchConfig in crunchConfigs) foreach (CrunchConfig crunchConfig in crunchConfigs)
{ {
bgra.CopyTo(encodedData);
bool useCache = true; bool useCache = true;
this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette || this.UsePalette = crunchConfig.EntropyIdx == EntropyIx.Palette ||
crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial; crunchConfig.EntropyIdx == EntropyIx.PaletteAndSpatial;
@ -297,15 +305,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// Encode and write the transformed image. // Encode and write the transformed image.
this.EncodeImage( this.EncodeImage(
bgra,
this.HashChain,
this.Refs,
this.CurrentWidth, this.CurrentWidth,
height, height,
useCache, useCache,
crunchConfig, crunchConfig,
this.CacheBits, this.CacheBits);
this.HistoBits);
// If we are better than what we already have. // If we are better than what we already have.
if (isFirstConfig || this.bitWriter.NumBytes() < bestSize) if (isFirstConfig || this.bitWriter.NumBytes() < bestSize)
@ -421,9 +425,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return crunchConfigs.ToArray(); return crunchConfigs.ToArray();
} }
private void EncodeImage(ReadOnlySpan<uint> bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits) private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits)
{ {
int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); // bgra data with transformations applied.
Span<uint> bgra = this.EncodedData.GetSpan();
int histogramImageXySize = LosslessUtils.SubSampleSize(width, this.HistoBits) * LosslessUtils.SubSampleSize(height, this.HistoBits);
ushort[] histogramSymbols = new ushort[histogramImageXySize]; ushort[] histogramSymbols = new ushort[histogramImageXySize];
var huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes]; var huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes];
for (int i = 0; i < huffTree.Length; i++) for (int i = 0; i < huffTree.Length; i++)
@ -444,7 +450,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// Calculate backward references from BGRA image. // Calculate backward references from BGRA image.
hashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height); this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height);
Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter;
Vp8LBitWriter bwInit = this.bitWriter; Vp8LBitWriter bwInit = this.bitWriter;
@ -458,13 +464,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.quality, this.quality,
subConfig.Lz77, subConfig.Lz77,
ref cacheBits, ref cacheBits,
hashChain, this.HashChain,
refsArray[0], this.Refs[0],
refsArray[1]); // TODO : Pass do not cache this.Refs[1]);
// 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 = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0];
this.bitWriter.Reset(bwInit); this.bitWriter.Reset(bwInit);
var tmpHisto = new Vp8LHistogram(cacheBits); var tmpHisto = new Vp8LHistogram(cacheBits);
@ -475,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// Build histogram image and symbols from backward references. // Build histogram image and symbols from backward references.
HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, histogramBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, this.HistoBits, cacheBits, histogramImage, tmpHisto, histogramSymbols);
// Create Huffman bit lengths and codes for each histogram image. // Create Huffman bit lengths and codes for each histogram image.
int histogramImageSize = histogramImage.Count; int histogramImageSize = histogramImage.Count;
@ -517,8 +523,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
} }
this.bitWriter.PutBits((uint)(histogramBits - 2), 3); this.bitWriter.PutBits((uint)(this.HistoBits - 2), 3);
this.EncodeImageNoHuffman(histogramBgra, hashChain, refsTmp, refsArray[2], LosslessUtils.SubSampleSize(width, histogramBits), LosslessUtils.SubSampleSize(height, histogramBits), this.quality); this.EncodeImageNoHuffman(
histogramBgra,
this.HashChain,
refsTmp,
this.Refs[2],
LosslessUtils.SubSampleSize(width, this.HistoBits),
LosslessUtils.SubSampleSize(height, this.HistoBits),
this.quality);
} }
// Store Huffman codes. // Store Huffman codes.
@ -547,12 +560,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// Store actual literals. // Store actual literals.
this.StoreImageToBitMask(width, histogramBits, refsBest, histogramSymbols, huffmanCodes); this.StoreImageToBitMask(width, this.HistoBits, refsBest, histogramSymbols, huffmanCodes);
// Keep track of the smallest image so far. // Keep track of the smallest image so far.
if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes())) if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes()))
{ {
// TODO: This was done in the reference by swapping references, this will be slower // TODO: This was done in the reference by swapping references, this will be slower.
bitWriterBest = this.bitWriter.Clone(); bitWriterBest = this.bitWriter.Clone();
} }
@ -589,7 +602,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{ {
this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1);
this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2);
LosslessUtils.SubtractGreenFromBlueAndRed(this.Bgra.GetSpan()); LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan());
} }
private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen) private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen)
@ -600,7 +613,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int transformWidth = LosslessUtils.SubSampleSize(width, predBits); int transformWidth = LosslessUtils.SubSampleSize(width, predBits);
int transformHeight = LosslessUtils.SubSampleSize(height, predBits); int transformHeight = LosslessUtils.SubSampleSize(height, predBits);
PredictorEncoder.ResidualImage(width, height, predBits, this.Bgra.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); PredictorEncoder.ResidualImage(width, height, predBits, this.EncodedData.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen);
this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1);
this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2);
@ -615,7 +628,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits);
int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits);
PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.Bgra.GetSpan(), this.TransformData.GetSpan()); PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan());
this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1);
this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2);
@ -1161,9 +1174,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private void MapImageFromPalette(int width, int height) private void MapImageFromPalette(int width, int height)
{ {
Span<uint> src = this.Bgra.GetSpan(); Span<uint> src = this.EncodedData.GetSpan();
int srcStride = this.CurrentWidth; int srcStride = this.CurrentWidth;
Span<uint> dst = this.Bgra.GetSpan(); // Applying the palette will be done in place. Span<uint> dst = this.EncodedData.GetSpan(); // Applying the palette will be done in place.
Span<uint> palette = this.Palette.GetSpan(); Span<uint> palette = this.Palette.GetSpan();
int paletteSize = this.PaletteSize; int paletteSize = this.PaletteSize;
int xBits; int xBits;
@ -1698,6 +1711,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
public void Dispose() public void Dispose()
{ {
this.Bgra.Dispose(); this.Bgra.Dispose();
this.EncodedData.Dispose();
this.BgraScratch.Dispose(); this.BgraScratch.Dispose();
this.Palette.Dispose(); this.Palette.Dispose();
this.TransformData.Dispose(); this.TransformData.Dispose();

Loading…
Cancel
Save