Browse Source

Add low effort path for method == 0

pull/1552/head
Brian Popow 5 years ago
parent
commit
1c3110699e
  1. 137
      src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
  2. 46
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  3. 42
      src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs
  4. 1
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  5. 1
      tests/ImageSharp.Tests/TestImages.cs
  6. 4
      tests/Images/Input/WebP/rgb_pattern.png
  7. 3
      tests/Images/Input/WebP/rgb_pattern_63x63.png

137
src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs

@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private const float SpatialPredictorBias = 15.0f; private const float SpatialPredictorBias = 15.0f;
private const int PredLowEffort = 11;
/// <summary> /// <summary>
/// Finds the best predictor for each tile, and converts the image to residuals /// Finds the best predictor for each tile, and converts the image to residuals
/// with respect to predictions. If nearLosslessQuality &lt; 100, applies /// with respect to predictions. If nearLosslessQuality &lt; 100, applies
@ -42,7 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
bool nearLossless, bool nearLossless,
int nearLosslessQuality, int nearLosslessQuality,
bool exact, bool exact,
bool usedSubtractGreen) bool usedSubtractGreen,
bool lowEffort)
{ {
int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); int tilesPerRow = LosslessUtils.SubSampleSize(width, bits);
int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); int tilesPerCol = LosslessUtils.SubSampleSize(height, bits);
@ -55,27 +58,36 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
histo[i] = new int[256]; histo[i] = new int[256];
} }
// TODO: Low Effort if (lowEffort)
for (int tileY = 0; tileY < tilesPerCol; ++tileY)
{ {
for (int tileX = 0; tileX < tilesPerRow; ++tileX) for (int i = 0; i < tilesPerRow * tilesPerCol; ++i)
{ {
int pred = GetBestPredictorForTile( image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8);
width, }
height, }
tileX, else
tileY, {
bits, for (int tileY = 0; tileY < tilesPerCol; ++tileY)
histo, {
bgraScratch, for (int tileX = 0; tileX < tilesPerRow; ++tileX)
bgra, {
maxQuantization, int pred = GetBestPredictorForTile(
exact, width,
usedSubtractGreen, height,
nearLossless, tileX,
image); tileY,
bits,
image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); histo,
bgraScratch,
bgra,
maxQuantization,
exact,
usedSubtractGreen,
nearLossless,
image);
image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8));
}
} }
} }
@ -89,7 +101,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
maxQuantization, maxQuantization,
exact, exact,
usedSubtractGreen, usedSubtractGreen,
nearLossless); nearLossless,
lowEffort);
} }
public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span<uint> bgra, Span<uint> image) public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span<uint> bgra, Span<uint> image)
@ -558,18 +571,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int maxQuantization, int maxQuantization,
bool exact, bool exact,
bool usedSubtractGreen, bool usedSubtractGreen,
bool nearLossless) bool nearLossless,
bool lowEffort)
{ {
int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); int tilesPerRow = LosslessUtils.SubSampleSize(width, bits);
// The width of upper_row and current_row is one pixel larger than image width // The width of upperRow and currentRow is one pixel larger than image width
// to allow the top right pixel to point to the leftmost pixel of the next row // to allow the top right pixel to point to the leftmost pixel of the next row
// when at the right edge. // when at the right edge.
Span<uint> upperRow = argbScratch; Span<uint> upperRow = argbScratch;
Span<uint> currentRow = upperRow.Slice(width + 1); Span<uint> currentRow = upperRow.Slice(width + 1);
Span<byte> currentMaxDiffs = MemoryMarshal.Cast<uint, byte>(currentRow.Slice(width + 1)); Span<byte> currentMaxDiffs = MemoryMarshal.Cast<uint, byte>(currentRow.Slice(width + 1));
// TODO: This should be wrapped in a condition?
Span<byte> lowerMaxDiffs = currentMaxDiffs.Slice(width); Span<byte> lowerMaxDiffs = currentMaxDiffs.Slice(width);
for (int y = 0; y < height; ++y) for (int y = 0; y < height; ++y)
{ {
@ -579,47 +592,53 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Span<uint> src = argb.Slice(y * width, width + ((y + 1) < height ? 1 : 0)); Span<uint> src = argb.Slice(y * width, width + ((y + 1) < height ? 1 : 0));
src.CopyTo(currentRow); src.CopyTo(currentRow);
// TODO: Near lossless conditional? if (lowEffort)
if (maxQuantization > 1)
{ {
// Compute max_diffs for the lower row now, because that needs the PredictBatch(PredLowEffort, 0, y, width, currentRow, upperRow, argb.Slice(y * width));
// contents of bgra for the current row, which we will overwrite with
// residuals before proceeding with the next row.
Span<byte> tmp8 = currentMaxDiffs;
currentMaxDiffs = lowerMaxDiffs;
lowerMaxDiffs = tmp8;
if (y + 2 < height)
{
MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen);
}
} }
else
for (int x = 0; x < width;)
{ {
int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff); if (nearLossless && maxQuantization > 1)
int xEnd = x + (1 << bits);
if (xEnd > width)
{ {
xEnd = width; // Compute maxDiffs for the lower row now, because that needs the
// contents of bgra for the current row, which we will overwrite with
// residuals before proceeding with the next row.
Span<byte> tmp8 = currentMaxDiffs;
currentMaxDiffs = lowerMaxDiffs;
lowerMaxDiffs = tmp8;
if (y + 2 < height)
{
MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen);
}
} }
GetResidual( for (int x = 0; x < width;)
width, {
height, int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff);
upperRow, int xEnd = x + (1 << bits);
currentRow, if (xEnd > width)
currentMaxDiffs, {
mode, xEnd = width;
x, }
xEnd,
y, GetResidual(
maxQuantization, width,
exact, height,
usedSubtractGreen, upperRow,
nearLossless, currentRow,
argb.Slice((y * width) + x)); currentMaxDiffs,
mode,
x = xEnd; x,
xEnd,
y,
maxQuantization,
exact,
usedSubtractGreen,
nearLossless,
argb.Slice((y * width) + x));
x = xEnd;
}
} }
} }
} }

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

@ -268,6 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Span<uint> bgra = this.Bgra.GetSpan(); Span<uint> bgra = this.Bgra.GetSpan();
Span<uint> encodedData = this.EncodedData.GetSpan(); Span<uint> encodedData = this.EncodedData.GetSpan();
bool lowEffort = this.method == 0;
// 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);
@ -286,7 +287,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
(crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen);
this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) || this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) ||
(crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen); (crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen);
this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform; if (lowEffort)
{
this.UseCrossColorTransform = false;
}
else
{
this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform;
}
this.AllocateTransformBuffer(width, height); this.AllocateTransformBuffer(width, height);
// Reset any parameter in the encoder that is set in the previous iteration. // Reset any parameter in the encoder that is set in the previous iteration.
@ -307,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// Encode palette. // Encode palette.
if (this.UsePalette) if (this.UsePalette)
{ {
this.EncodePalette(); this.EncodePalette(lowEffort);
this.MapImageFromPalette(width, height); this.MapImageFromPalette(width, height);
// If using a color cache, do not have it bigger than the number of colors. // If using a color cache, do not have it bigger than the number of colors.
@ -325,12 +334,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
if (this.UsePredictorTransform) if (this.UsePredictorTransform)
{ {
this.ApplyPredictFilter(this.CurrentWidth, height); this.ApplyPredictFilter(this.CurrentWidth, height, lowEffort);
} }
if (this.UseCrossColorTransform) if (this.UseCrossColorTransform)
{ {
this.ApplyCrossColorFilter(this.CurrentWidth, height); this.ApplyCrossColorFilter(this.CurrentWidth, height, lowEffort);
} }
this.bitWriter.PutBits(0, 1); // No more transforms. this.bitWriter.PutBits(0, 1); // No more transforms.
@ -341,7 +350,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
height, height,
useCache, useCache,
crunchConfig, crunchConfig,
this.CacheBits); this.CacheBits,
lowEffort);
// 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)
@ -462,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
return crunchConfigs.ToArray(); return crunchConfigs.ToArray();
} }
private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits) private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits, bool lowEffort)
{ {
// bgra data with transformations applied. // bgra data with transformations applied.
Span<uint> bgra = this.EncodedData.GetSpan(); Span<uint> bgra = this.EncodedData.GetSpan();
@ -487,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// Calculate backward references from BGRA image. // Calculate backward references from BGRA image.
this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height); this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height, lowEffort);
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;
@ -568,7 +578,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.Refs[2], this.Refs[2],
LosslessUtils.SubSampleSize(width, this.HistoBits), LosslessUtils.SubSampleSize(width, this.HistoBits),
LosslessUtils.SubSampleSize(height, this.HistoBits), LosslessUtils.SubSampleSize(height, this.HistoBits),
this.quality); this.quality,
lowEffort);
} }
// Store Huffman codes. // Store Huffman codes.
@ -615,7 +626,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// <summary> /// <summary>
/// Save the palette to the bitstream. /// Save the palette to the bitstream.
/// </summary> /// </summary>
private void EncodePalette() private void EncodePalette(bool lowEffort)
{ {
Span<uint> tmpPalette = new uint[WebpConstants.MaxPaletteSize]; Span<uint> tmpPalette = new uint[WebpConstants.MaxPaletteSize];
int paletteSize = this.PaletteSize; int paletteSize = this.PaletteSize;
@ -629,7 +640,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
tmpPalette[0] = palette[0]; tmpPalette[0] = palette[0];
this.EncodeImageNoHuffman(tmpPalette, this.HashChain, this.Refs[0], this.Refs[1], width: paletteSize, height: 1, quality: 20); this.EncodeImageNoHuffman(tmpPalette, this.HashChain, this.Refs[0], this.Refs[1], width: paletteSize, height: 1, quality: 20, lowEffort);
} }
/// <summary> /// <summary>
@ -642,7 +653,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan()); LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan());
} }
private void ApplyPredictFilter(int width, int height) private void ApplyPredictFilter(int width, int height, bool lowEffort)
{ {
// We disable near-lossless quantization if palette is used. // We disable near-lossless quantization if palette is used.
int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality; int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality;
@ -660,16 +671,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.nearLossless, this.nearLossless,
nearLosslessStrength, nearLosslessStrength,
this.exact, this.exact,
this.UseSubtractGreenTransform); this.UseSubtractGreenTransform,
lowEffort);
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);
this.bitWriter.PutBits((uint)(predBits - 2), 3); this.bitWriter.PutBits((uint)(predBits - 2), 3);
this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality); this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort);
} }
private void ApplyCrossColorFilter(int width, int height) private void ApplyCrossColorFilter(int width, int height, bool lowEffort)
{ {
int colorTransformBits = this.TransformBits; int colorTransformBits = this.TransformBits;
int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits);
@ -681,10 +693,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2);
this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3);
this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality); this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort);
} }
private void EncodeImageNoHuffman(Span<uint> bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality) private void EncodeImageNoHuffman(Span<uint> bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality, bool lowEffort)
{ {
int cacheBits = 0; int cacheBits = 0;
ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol. ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol.
@ -702,7 +714,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
} }
// Calculate backward references from the image pixels. // Calculate backward references from the image pixels.
hashChain.Fill(this.memoryAllocator, bgra, quality, width, height); hashChain.Fill(this.memoryAllocator, bgra, quality, width, height, lowEffort);
Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences(
width, width,

42
src/ImageSharp/Formats/WebP/Lossless/Vp8LHashChain.cs

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary> /// </summary>
public int Size { get; } public int Size { get; }
public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan<uint> bgra, int quality, int xSize, int ySize) public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan<uint> bgra, int quality, int xSize, int ySize, bool lowEffort)
{ {
int size = xSize * ySize; int size = xSize * ySize;
int iterMax = GetMaxItersForQuality(quality); int iterMax = GetMaxItersForQuality(quality);
@ -147,32 +147,36 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
pos = chain[basePosition]; pos = chain[basePosition];
int currLength; int currLength;
// Heuristic: use the comparison with the above line as an initialization. if (!lowEffort)
if (basePosition >= (uint)xSize)
{ {
currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - xSize), bgra.Slice(bgraStart), bestLength, maxLen); // Heuristic: use the comparison with the above line as an initialization.
if (basePosition >= (uint)xSize)
{
currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - xSize), bgra.Slice(bgraStart), bestLength, maxLen);
if (currLength > bestLength)
{
bestLength = currLength;
bestDistance = (uint)xSize;
}
iter--;
}
// Heuristic: compare to the previous pixel.
currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen);
if (currLength > bestLength) if (currLength > bestLength)
{ {
bestLength = currLength; bestLength = currLength;
bestDistance = (uint)xSize; bestDistance = 1;
} }
iter--; iter--;
}
// Heuristic: compare to the previous pixel. // Skip the for loop if we already have the maximum.
currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen); if (bestLength == BackwardReferenceEncoder.MaxLength)
if (currLength > bestLength) {
{ pos = minPos - 1;
bestLength = currLength; }
bestDistance = 1;
}
iter--;
if (bestLength == BackwardReferenceEncoder.MaxLength)
{
pos = minPos - 1;
} }
uint bestBgra = bgra.Slice(bgraStart)[bestLength]; uint bestBgra = bgra.Slice(bgraStart)[bestLength];

1
tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

@ -86,6 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 40)] [WithFile(RgbTestPattern, PixelTypes.Rgba32, 40)]
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 20)] [WithFile(RgbTestPattern, PixelTypes.Rgba32, 20)]
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 10)] [WithFile(RgbTestPattern, PixelTypes.Rgba32, 10)]
[WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)]
public void Encode_Lossless_WithNearLosslessFlag_Works<TPixel>(TestImageProvider<TPixel> provider, int nearLosslessQuality) public void Encode_Lossless_WithNearLosslessFlag_Works<TPixel>(TestImageProvider<TPixel> provider, int nearLosslessQuality)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {

1
tests/ImageSharp.Tests/TestImages.cs

@ -509,6 +509,7 @@ namespace SixLabors.ImageSharp.Tests
public const string TestPatternOpaque = "WebP/testpattern_opaque.png"; public const string TestPatternOpaque = "WebP/testpattern_opaque.png";
public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png"; public const string TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.png";
public const string RgbTestPattern = "WebP/rgb_pattern.png"; public const string RgbTestPattern = "WebP/rgb_pattern.png";
public const string RgbTestPattern63x63 = "WebP/rgb_pattern_63x63.png";
// Test image for encoding image with a palette. // Test image for encoding image with a palette.
public const string Flag = "WebP/flag_of_germany.png"; public const string Flag = "WebP/flag_of_germany.png";

4
tests/Images/Input/WebP/rgb_pattern.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f1ffed99a3cc701fff7f63cdca32c437a3e03d2a8a178380744190636decb0f8 oid sha256:5150fccc821b2196678771d46567e01af4702c53e4031aee24e2611cecfdf48e
size 12453 size 12841

3
tests/Images/Input/WebP/rgb_pattern_63x63.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3a7826312b4dabc2d8a89bf84e501ddb0bcc09932c54d2dedb0c96909da94da8
size 12071
Loading…
Cancel
Save