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 int PredLowEffort = 11;
/// <summary>
/// Finds the best predictor for each tile, and converts the image to residuals
/// with respect to predictions. If nearLosslessQuality &lt; 100, applies
@ -42,7 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
bool nearLossless,
int nearLosslessQuality,
bool exact,
bool usedSubtractGreen)
bool usedSubtractGreen,
bool lowEffort)
{
int tilesPerRow = LosslessUtils.SubSampleSize(width, bits);
int tilesPerCol = LosslessUtils.SubSampleSize(height, bits);
@ -55,27 +58,36 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
histo[i] = new int[256];
}
// TODO: Low Effort
for (int tileY = 0; tileY < tilesPerCol; ++tileY)
if (lowEffort)
{
for (int tileX = 0; tileX < tilesPerRow; ++tileX)
for (int i = 0; i < tilesPerRow * tilesPerCol; ++i)
{
int pred = GetBestPredictorForTile(
width,
height,
tileX,
tileY,
bits,
histo,
bgraScratch,
bgra,
maxQuantization,
exact,
usedSubtractGreen,
nearLossless,
image);
image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8));
image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8);
}
}
else
{
for (int tileY = 0; tileY < tilesPerCol; ++tileY)
{
for (int tileX = 0; tileX < tilesPerRow; ++tileX)
{
int pred = GetBestPredictorForTile(
width,
height,
tileX,
tileY,
bits,
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,
exact,
usedSubtractGreen,
nearLossless);
nearLossless,
lowEffort);
}
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,
bool exact,
bool usedSubtractGreen,
bool nearLossless)
bool nearLossless,
bool lowEffort)
{
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
// when at the right edge.
Span<uint> upperRow = argbScratch;
Span<uint> currentRow = upperRow.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);
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));
src.CopyTo(currentRow);
// TODO: Near lossless conditional?
if (maxQuantization > 1)
if (lowEffort)
{
// Compute max_diffs 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);
}
PredictBatch(PredLowEffort, 0, y, width, currentRow, upperRow, argb.Slice(y * width));
}
for (int x = 0; x < width;)
else
{
int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff);
int xEnd = x + (1 << bits);
if (xEnd > width)
if (nearLossless && maxQuantization > 1)
{
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(
width,
height,
upperRow,
currentRow,
currentMaxDiffs,
mode,
x,
xEnd,
y,
maxQuantization,
exact,
usedSubtractGreen,
nearLossless,
argb.Slice((y * width) + x));
x = xEnd;
for (int x = 0; x < width;)
{
int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff);
int xEnd = x + (1 << bits);
if (xEnd > width)
{
xEnd = width;
}
GetResidual(
width,
height,
upperRow,
currentRow,
currentMaxDiffs,
mode,
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> encodedData = this.EncodedData.GetSpan();
bool lowEffort = this.method == 0;
// Analyze image (entropy, numPalettes etc).
CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero);
@ -286,7 +287,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
(crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen);
this.UsePredictorTransform = (crunchConfig.EntropyIdx == EntropyIx.Spatial) ||
(crunchConfig.EntropyIdx == EntropyIx.SpatialSubGreen);
this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform;
if (lowEffort)
{
this.UseCrossColorTransform = false;
}
else
{
this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform;
}
this.AllocateTransformBuffer(width, height);
// 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.
if (this.UsePalette)
{
this.EncodePalette();
this.EncodePalette(lowEffort);
this.MapImageFromPalette(width, height);
// 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)
{
this.ApplyPredictFilter(this.CurrentWidth, height);
this.ApplyPredictFilter(this.CurrentWidth, height, lowEffort);
}
if (this.UseCrossColorTransform)
{
this.ApplyCrossColorFilter(this.CurrentWidth, height);
this.ApplyCrossColorFilter(this.CurrentWidth, height, lowEffort);
}
this.bitWriter.PutBits(0, 1); // No more transforms.
@ -341,7 +350,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
height,
useCache,
crunchConfig,
this.CacheBits);
this.CacheBits,
lowEffort);
// If we are better than what we already have.
if (isFirstConfig || this.bitWriter.NumBytes() < bestSize)
@ -462,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
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.
Span<uint> bgra = this.EncodedData.GetSpan();
@ -487,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
// 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 bwInit = this.bitWriter;
@ -568,7 +578,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.Refs[2],
LosslessUtils.SubSampleSize(width, this.HistoBits),
LosslessUtils.SubSampleSize(height, this.HistoBits),
this.quality);
this.quality,
lowEffort);
}
// Store Huffman codes.
@ -615,7 +626,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// <summary>
/// Save the palette to the bitstream.
/// </summary>
private void EncodePalette()
private void EncodePalette(bool lowEffort)
{
Span<uint> tmpPalette = new uint[WebpConstants.MaxPaletteSize];
int paletteSize = this.PaletteSize;
@ -629,7 +640,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
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>
@ -642,7 +653,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
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.
int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality;
@ -660,16 +671,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.nearLossless,
nearLosslessStrength,
this.exact,
this.UseSubtractGreenTransform);
this.UseSubtractGreenTransform,
lowEffort);
this.bitWriter.PutBits(WebpConstants.TransformPresent, 1);
this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2);
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 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)(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;
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.
hashChain.Fill(this.memoryAllocator, bgra, quality, width, height);
hashChain.Fill(this.memoryAllocator, bgra, quality, width, height, lowEffort);
Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences(
width,

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

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary>
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 iterMax = GetMaxItersForQuality(quality);
@ -147,32 +147,36 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
pos = chain[basePosition];
int currLength;
// Heuristic: use the comparison with the above line as an initialization.
if (basePosition >= (uint)xSize)
if (!lowEffort)
{
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)
{
bestLength = currLength;
bestDistance = (uint)xSize;
bestDistance = 1;
}
iter--;
}
// Heuristic: compare to the previous pixel.
currLength = LosslessUtils.FindMatchLength(bgra.Slice(bgraStart - 1), bgra.Slice(bgraStart), bestLength, maxLen);
if (currLength > bestLength)
{
bestLength = currLength;
bestDistance = 1;
}
iter--;
if (bestLength == BackwardReferenceEncoder.MaxLength)
{
pos = minPos - 1;
// Skip the for loop if we already have the maximum.
if (bestLength == BackwardReferenceEncoder.MaxLength)
{
pos = minPos - 1;
}
}
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, 20)]
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 10)]
[WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)]
public void Encode_Lossless_WithNearLosslessFlag_Works<TPixel>(TestImageProvider<TPixel> provider, int nearLosslessQuality)
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 TestPatternOpaqueSmall = "WebP/testpattern_opaque_small.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.
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
oid sha256:f1ffed99a3cc701fff7f63cdca32c437a3e03d2a8a178380744190636decb0f8
size 12453
oid sha256:5150fccc821b2196678771d46567e01af4702c53e4031aee24e2611cecfdf48e
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