Browse Source

Add near lossless encoding mode

pull/1552/head
Brian Popow 5 years ago
parent
commit
be36ff6911
  1. 12
      src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs
  2. 1
      src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs
  3. 129
      src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs
  4. 89
      src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
  5. 44
      src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
  6. 6
      src/ImageSharp/Formats/WebP/WebpEncoder.cs
  7. 33
      src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
  8. 3
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  9. 117
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  10. 1
      tests/ImageSharp.Tests/TestImages.cs
  11. 3
      tests/Images/Input/WebP/rgb_pattern.png

12
src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs

@ -54,5 +54,17 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// The default value is false.
/// </summary>
bool Exact { get; }
/// <summary>
/// Gets a value indicating whether near lossless mode should be used.
/// This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality.
/// </summary>
bool NearLossless { get; }
/// <summary>
/// Gets the quality of near-lossless image preprocessing. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).
/// The typical value is around 60. Note that lossy with -q 100 can at times yield better results.
/// </summary>
int NearLosslessQuality { get; }
}
}

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

@ -770,6 +770,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
m.RedToBlue = (byte)((colorCode >> 16) & 0xff);
}
// Converts near lossless quality into max number of bits shaved off.
// 100 -> 0
// 80..99 -> 1
// 60..79 -> 2

129
src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs

@ -0,0 +1,129 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
namespace SixLabors.ImageSharp.Formats.WebP.Lossless
{
/// <summary>
/// Near-lossless image preprocessing adjusts pixel values to help compressibility with a guarantee
/// of maximum deviation between original and resulting pixel values.
/// </summary>
internal static class NearLosslessEnc
{
private const int MinDimForNearLossless = 64;
public static void ApplyNearLossless(int xSize, int ySize, int quality, Span<uint> argbSrc, Span<uint> argbDst, int stride)
{
uint[] copyBuffer = new uint[xSize * 3];
int limitBits = LosslessUtils.NearLosslessBits(quality);
// For small icon images, don't attempt to apply near-lossless compression.
if ((xSize < MinDimForNearLossless && ySize < MinDimForNearLossless) || ySize < 3)
{
for (int i = 0; i < ySize; ++i)
{
argbSrc.Slice(i * stride, xSize).CopyTo(argbDst.Slice(i * xSize, xSize));
}
return;
}
NearLossless(xSize, ySize, argbSrc, stride, limitBits, copyBuffer, argbDst);
for (int i = limitBits - 1; i != 0; --i)
{
NearLossless(xSize, ySize, argbDst, xSize, i, copyBuffer, argbDst);
}
}
// Adjusts pixel values of image with given maximum error.
private static void NearLossless(int xSize, int ySize, Span<uint> argbSrc, int stride, int limitBits, Span<uint> copyBuffer, Span<uint> argbDst)
{
int x, y;
int limit = 1 << limitBits;
Span<uint> prevRow = copyBuffer;
Span<uint> currRow = copyBuffer.Slice(xSize, xSize);
Span<uint> nextRow = copyBuffer.Slice(xSize * 2, xSize);
argbSrc.Slice(0, xSize).CopyTo(currRow);
argbSrc.Slice(xSize, xSize).CopyTo(nextRow);
int srcOffset = 0;
int dstOffset = 0;
for (y = 0; y < ySize; ++y)
{
if (y == 0 || y == ySize - 1)
{
argbSrc.Slice(srcOffset, xSize).CopyTo(argbDst.Slice(dstOffset, xSize));
}
else
{
argbSrc.Slice(srcOffset + stride, xSize).CopyTo(nextRow);
argbDst[dstOffset] = argbSrc[srcOffset];
argbDst[dstOffset + xSize - 1] = argbSrc[srcOffset + xSize - 1];
for (x = 1; x < xSize - 1; ++x)
{
if (IsSmooth(prevRow, currRow, nextRow, x, limit))
{
argbDst[dstOffset + x] = currRow[x];
}
else
{
argbDst[dstOffset + x] = ClosestDiscretizedArgb(currRow[x], limitBits);
}
}
}
Span<uint> temp = prevRow;
prevRow = currRow;
currRow = nextRow;
nextRow = temp;
srcOffset += stride;
dstOffset += xSize;
}
}
// Applies FindClosestDiscretized to all channels of pixel.
private static uint ClosestDiscretizedArgb(uint a, int bits) =>
(FindClosestDiscretized(a >> 24, bits) << 24) |
(FindClosestDiscretized((a >> 16) & 0xff, bits) << 16) |
(FindClosestDiscretized((a >> 8) & 0xff, bits) << 8) |
FindClosestDiscretized(a & 0xff, bits);
private static uint FindClosestDiscretized(uint a, int bits)
{
uint mask = (1u << bits) - 1;
uint biased = a + (mask >> 1) + ((a >> bits) & 1);
if (biased > 0xff)
{
return 0xff;
}
return biased & ~mask;
}
private static bool IsSmooth(Span<uint> prevRow, Span<uint> currRow, Span<uint> nextRow, int ix, int limit)
{
// Check that all pixels in 4-connected neighborhood are smooth.
return IsNear(currRow[ix], currRow[ix - 1], limit) &&
IsNear(currRow[ix], currRow[ix + 1], limit) &&
IsNear(currRow[ix], prevRow[ix], limit) &&
IsNear(currRow[ix], nextRow[ix], limit);
}
// Checks if distance between corresponding channel values of pixels a and b is within the given limit.
private static bool IsNear(uint a, uint b, int limit)
{
for (int k = 0; k < 4; ++k)
{
int delta = (int)((a >> (k * 8)) & 0xff) - (int)((b >> (k * 8)) & 0xff);
if (delta >= limit || delta <= -limit)
{
return false;
}
}
return true;
}
}
}

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

@ -39,6 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Span<uint> bgra,
Span<uint> bgraScratch,
Span<uint> image,
bool nearLossless,
int nearLosslessQuality,
bool exact,
bool usedSubtractGreen)
@ -71,6 +72,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
maxQuantization,
exact,
usedSubtractGreen,
nearLossless,
image);
image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8));
@ -86,7 +88,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
bgra,
maxQuantization,
exact,
usedSubtractGreen);
usedSubtractGreen,
nearLossless);
}
public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span<uint> bgra, Span<uint> image)
@ -175,6 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int maxQuantization,
bool exact,
bool usedSubtractGreen,
bool nearLossless,
Span<uint> modes)
{
const int numPredModes = 14;
@ -242,18 +246,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
// pixel to the right in all cases except at the bottom right corner of
// the image (wrapping to the leftmost pixel of the next row if it does
// not exist in the currentRow).
Span<uint> src = argb.Slice((y * width) + contextStartX, maxX + haveLeft + ((y + 1) < height ? 1 : 0));
int offset = (y * width) + contextStartX;
Span<uint> src = argb.Slice(offset, maxX + haveLeft + ((y + 1) < height ? 1 : 0));
Span<uint> dst = currentRow.Slice(contextStartX);
src.CopyTo(dst);
// TODO: Source wraps this in conditional
// WEBP_NEAR_LOSSLESS == 1
if (maxQuantization > 1 && y >= 1 && y + 1 < height)
if (nearLossless)
{
MaxDiffsForRow(contextWidth, width, argb.Slice((y * width) + contextStartX), maxDiffs.Slice(contextStartX), usedSubtractGreen);
if (maxQuantization > 1 && y >= 1 && y + 1 < height)
{
MaxDiffsForRow(contextWidth, width, argb, offset, maxDiffs.Slice(contextStartX), usedSubtractGreen);
}
}
GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, exact, usedSubtractGreen, residuals);
GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, exact, usedSubtractGreen, nearLossless, residuals);
for (int relativeX = 0; relativeX < maxX; ++relativeX)
{
UpdateHisto(histoArgb, residuals[relativeX]);
@ -316,6 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int maxQuantization,
bool exact,
bool usedSubtractGreen,
bool nearLossless,
Span<uint> output)
{
if (exact)
@ -388,18 +395,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
}
if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1)
if (nearLossless)
{
residual = LosslessUtils.SubPixels(currentRow[x], predict);
if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1)
{
residual = LosslessUtils.SubPixels(currentRow[x], predict);
}
else
{
residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen);
// Update the source image.
currentRow[x] = LosslessUtils.AddPixels(predict, residual);
// x is never 0 here so we do not need to update upperRow like below.
}
}
else
{
residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen);
// Update the source image.
currentRow[x] = LosslessUtils.AddPixels(predict, residual);
// x is never 0 here so we do not need to update upperRow like below.
residual = LosslessUtils.SubPixels(currentRow[x], predict);
}
if ((currentRow[x] & MaskAlpha) == 0)
@ -534,7 +548,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// residuals to multiples of quantization levels up to max_quantization
/// (the actual quantization level depends on smoothness near the given pixel).
/// </summary>
private static void CopyImageWithPrediction(int width, int height, int bits, Span<uint> modes, Span<uint> argbScratch, Span<uint> argb, int maxQuantization, bool exact, bool usedSubtractGreen)
private static void CopyImageWithPrediction(
int width,
int height,
int bits,
Span<uint> modes,
Span<uint> argbScratch,
Span<uint> argb,
int maxQuantization,
bool exact,
bool usedSubtractGreen,
bool nearLossless)
{
int tilesPerRow = LosslessUtils.SubSampleSize(width, bits);
@ -566,7 +590,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
lowerMaxDiffs = tmp8;
if (y + 2 < height)
{
MaxDiffsForRow(width, width, argb.Slice((y + 1) * width), lowerMaxDiffs, usedSubtractGreen);
MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen);
}
}
@ -592,6 +616,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
maxQuantization,
exact,
usedSubtractGreen,
nearLossless,
argb.Slice((y * width) + x));
x = xEnd;
@ -687,15 +712,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
}
}
private static void MaxDiffsForRow(int width, int stride, Span<uint> argb, Span<byte> maxDiffs, bool usedSubtractGreen)
private static void MaxDiffsForRow(int width, int stride, Span<uint> argb, int offset, Span<byte> maxDiffs, bool usedSubtractGreen)
{
if (width <= 2)
{
return;
}
uint current = argb[0];
uint right = argb[1];
uint current = argb[offset];
uint right = argb[offset + 1];
if (usedSubtractGreen)
{
current = AddGreenToBlueAndRed(current);
@ -704,11 +729,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
for (int x = 1; x < width - 1; ++x)
{
uint up = argb[-stride + x];
uint down = argb[stride + x];
uint up = argb[offset - stride + x];
uint down = argb[offset + stride + x];
uint left = current;
current = right;
right = argb[x + 1];
right = argb[offset + x + 1];
if (usedSubtractGreen)
{
up = AddGreenToBlueAndRed(up);
@ -874,12 +899,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
if ((byte)greenToRed == prevX.GreenToRed)
{
curDiff -= 3; // Favor keeping the areas locally similar.
// Favor keeping the areas locally similar.
curDiff -= 3;
}
if ((byte)greenToRed == prevY.GreenToRed)
{
curDiff -= 3; // Favor keeping the areas locally similar.
// Favor keeping the areas locally similar.
curDiff -= 3;
}
if (greenToRed == 0)
@ -898,22 +925,26 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo);
if ((byte)greenToBlue == prevX.GreenToBlue)
{
curDiff -= 3; // Favor keeping the areas locally similar.
// Favor keeping the areas locally similar.
curDiff -= 3;
}
if ((byte)greenToBlue == prevY.GreenToBlue)
{
curDiff -= 3; // Favor keeping the areas locally similar.
// Favor keeping the areas locally similar.
curDiff -= 3;
}
if ((byte)redToBlue == prevX.RedToBlue)
{
curDiff -= 3; // Favor keeping the areas locally similar.
// Favor keeping the areas locally similar.
curDiff -= 3;
}
if ((byte)redToBlue == prevY.RedToBlue)
{
curDiff -= 3; // Favor keeping the areas locally similar.
// Favor keeping the areas locally similar.
curDiff -= 3;
}
if (greenToBlue == 0)

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

@ -9,6 +9,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Formats.WebP.Lossless;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -60,6 +61,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// </summary>
private readonly bool exact;
/// <summary>
/// Indicating whether near lossless mode should be used.
/// </summary>
private readonly bool nearLossless;
/// <summary>
/// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).
/// </summary>
private readonly int nearLosslessQuality;
private const int ApplyPaletteGreedyMax = 4;
private const int PaletteInvSizeBits = 11;
@ -76,7 +87,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// <param name="quality">The encoding quality.</param>
/// <param name="method">Quality/speed trade-off (0=fast, 6=slower-better).</param>
/// <param name="exact">Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible RGB information for better compression.</param>
public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, bool exact)
/// <param name="nearLossless">Indicating whether near lossless mode should be used.</param>
/// <param name="nearLosslessQuality">The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).</param>
public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, bool exact, bool nearLossless, int nearLosslessQuality)
{
int pixelCount = width * height;
int initialSize = pixelCount * 2;
@ -86,6 +99,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.quality = Numerics.Clamp(quality, 0, 100);
this.method = Numerics.Clamp(method, 0, 6);
this.exact = exact;
this.nearLossless = nearLossless;
this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100);
this.bitWriter = new Vp8LBitWriter(initialSize);
this.Bgra = memoryAllocator.Allocate<uint>(pixelCount);
this.EncodedData = memoryAllocator.Allocate<uint>(pixelCount);
@ -251,7 +266,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int width = image.Width;
int height = image.Height;
ReadOnlySpan<uint> bgra = this.Bgra.GetSpan();
Span<uint> bgra = this.Bgra.GetSpan();
Span<uint> encodedData = this.EncodedData.GetSpan();
// Analyze image (entropy, numPalettes etc).
@ -278,7 +293,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.CacheBits = 0;
this.ClearRefs();
// TODO: Apply near-lossless preprocessing.
if (this.nearLossless)
{
// Apply near-lossless preprocessing.
bool useNearLossless = (this.nearLosslessQuality < 100) && !this.UsePalette && !this.UsePredictorTransform;
if (useNearLossless)
{
this.AllocateTransformBuffer(width, height);
NearLosslessEnc.ApplyNearLossless(width, height, this.nearLosslessQuality, bgra, bgra, width);
}
}
// Encode palette.
if (this.UsePalette)
@ -301,7 +325,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
if (this.UsePredictorTransform)
{
this.ApplyPredictFilter(this.CurrentWidth, height, this.UseSubtractGreenTransform);
this.ApplyPredictFilter(this.CurrentWidth, height);
}
if (this.UseCrossColorTransform)
@ -618,9 +642,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan());
}
private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen)
private void ApplyPredictFilter(int width, int height)
{
int nearLosslessStrength = 100; // TODO: for now always 100
// We disable near-lossless quantization if palette is used.
int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality;
int predBits = this.TransformBits;
int transformWidth = LosslessUtils.SubSampleSize(width, predBits);
int transformHeight = LosslessUtils.SubSampleSize(height, predBits);
@ -632,9 +657,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
this.EncodedData.GetSpan(),
this.BgraScratch.GetSpan(),
this.TransformData.GetSpan(),
this.nearLossless,
nearLosslessStrength,
this.exact,
usedSubtractGreen);
this.UseSubtractGreenTransform);
this.bitWriter.PutBits(WebpConstants.TransformPresent, 1);
this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2);
@ -1709,10 +1735,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{
// VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra
// pixel in each, plus 2 regular scanlines of bytes.
int argbScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0;
int bgraScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0;
int transformDataSize = (this.UsePredictorTransform || this.UseCrossColorTransform) ? LosslessUtils.SubSampleSize(width, this.TransformBits) * LosslessUtils.SubSampleSize(height, this.TransformBits) : 0;
this.BgraScratch = this.memoryAllocator.Allocate<uint>(argbScratchSize);
this.BgraScratch = this.memoryAllocator.Allocate<uint>(bgraScratchSize);
this.TransformData = this.memoryAllocator.Allocate<uint>(transformDataSize);
this.CurrentWidth = width;
}

6
src/ImageSharp/Formats/WebP/WebpEncoder.cs

@ -35,6 +35,12 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <inheritdoc/>
public bool Exact { get; set; }
/// <inheritdoc/>
public bool NearLossless { get; set; }
/// <inheritdoc/>
public int NearLosslessQuality { get; set; } = 100;
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>

33
src/ImageSharp/Formats/WebP/WebpEncoderCore.cs

@ -58,6 +58,16 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary>
private readonly bool exact;
/// <summary>
/// Indicating whether near lossless mode should be used.
/// </summary>
private readonly bool nearLossless;
/// <summary>
/// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).
/// </summary>
private readonly int nearLosslessQuality;
/// <summary>
/// The global configuration.
/// </summary>
@ -78,6 +88,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.entropyPasses = options.EntropyPasses;
this.filterStrength = options.FilterStrength;
this.exact = options.Exact;
this.nearLossless = options.NearLossless;
this.nearLosslessQuality = options.NearLosslessQuality;
}
/// <summary>
@ -97,12 +109,29 @@ namespace SixLabors.ImageSharp.Formats.Webp
if (this.lossy)
{
using var enc = new Vp8Encoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.entropyPasses, this.filterStrength);
using var enc = new Vp8Encoder(
this.memoryAllocator,
this.configuration,
image.Width,
image.Height,
this.quality,
this.method,
this.entropyPasses,
this.filterStrength);
enc.Encode(image, stream);
}
else
{
using var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.exact);
using var enc = new Vp8LEncoder(
this.memoryAllocator,
this.configuration,
image.Width,
image.Height,
this.quality,
this.method,
this.exact,
this.nearLossless,
this.nearLosslessQuality);
enc.Encode(image, stream);
}
}

3
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -16,6 +16,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats
{
[Collection("RunSerial")]
public class GeneralFormatTests
{
/// <summary>
@ -152,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Formats
using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff")))
{
image.SaveAsTga(output);
image.SaveAsTiff(output);
}
}
}

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

@ -80,6 +80,75 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder);
}
[Theory]
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 85)]
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 60)]
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 40)]
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 20)]
[WithFile(RgbTestPattern, PixelTypes.Rgba32, 10)]
public void Encode_Lossless_WithNearLosslessFlag_Works<TPixel>(TestImageProvider<TPixel> provider, int nearLosslessQuality)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
{
Lossy = false,
NearLossless = true,
NearLosslessQuality = nearLosslessQuality
};
using Image<TPixel> image = provider.GetImage();
string testOutputDetails = string.Concat("nearlossless", "_q", nearLosslessQuality);
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(nearLosslessQuality));
}
[Theory]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)]
[WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)]
public void Encode_Lossless_WithExactFlag_Works<TPixel>(TestImageProvider<TPixel> provider, int method)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
{
Lossy = false,
Method = method,
Exact = true
};
using Image<TPixel> image = provider.GetImage();
string testOutputDetails = string.Concat("lossless", "_m", method);
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder);
}
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
[WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]
public void Encode_Lossless_WorksWithTestPattern<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new WebpEncoder() { Lossy = false };
image.VerifyEncoder(provider, "webp", string.Empty, encoder);
}
[Fact]
public void Encode_Lossless_OneByOnePixel_Works()
{
// Just make sure, encoding 1 pixel by 1 pixel does not throw an exception.
using var image = new Image<Rgba32>(1, 1);
var encoder = new WebpEncoder() { Lossy = false };
using (var memStream = new MemoryStream())
{
image.SaveAsWebp(memStream, encoder);
}
}
[Theory]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 75)]
@ -148,30 +217,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality));
}
[Theory]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)]
[WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)]
public void Encode_Lossless_WithExactFlag_Works<TPixel>(TestImageProvider<TPixel> provider, int method)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
{
Lossy = false,
Method = method,
Exact = true
};
using Image<TPixel> image = provider.GetImage();
string testOutputDetails = string.Concat("lossless", "_m", method);
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder);
}
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
[WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]
@ -184,30 +229,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f));
}
[Theory]
[WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
[WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)]
public void Encode_Lossless_WorksWithTestPattern<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new WebpEncoder() { Lossy = false };
image.VerifyEncoder(provider, "webp", string.Empty, encoder);
}
[Fact]
public void Encode_Lossless_OneByOnePixel_Works()
{
// Just make sure, encoding 1 pixel by 1 pixel does not throw an exception.
using var image = new Image<Rgba32>(1, 1);
var encoder = new WebpEncoder() { Lossy = false };
using (var memStream = new MemoryStream())
{
image.SaveAsWebp(memStream, encoder);
}
}
private static ImageComparer GetComparer(int quality)
{
float tolerance = 0.01f; // ~1.0%

1
tests/ImageSharp.Tests/TestImages.cs

@ -508,6 +508,7 @@ namespace SixLabors.ImageSharp.Tests
// Test pattern images for testing the encoder.
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";
// Test image for encoding image with a palette.
public const string Flag = "WebP/flag_of_germany.png";

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

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