diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs
index dad67f804..6c8449772 100644
--- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs
+++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs
@@ -54,5 +54,17 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// The default value is false.
///
bool Exact { get; }
+
+ ///
+ /// 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.
+ ///
+ bool NearLossless { get; }
+
+ ///
+ /// 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.
+ ///
+ int NearLosslessQuality { get; }
}
}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs
index d6ba6e481..4b3cce9af 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/LosslessUtils.cs
+++ b/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
diff --git a/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/WebP/Lossless/NearLosslessEnc.cs
new file mode 100644
index 000000000..4c035a647
--- /dev/null
+++ b/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
+{
+ ///
+ /// Near-lossless image preprocessing adjusts pixel values to help compressibility with a guarantee
+ /// of maximum deviation between original and resulting pixel values.
+ ///
+ internal static class NearLosslessEnc
+ {
+ private const int MinDimForNearLossless = 64;
+
+ public static void ApplyNearLossless(int xSize, int ySize, int quality, Span argbSrc, Span 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 argbSrc, int stride, int limitBits, Span copyBuffer, Span argbDst)
+ {
+ int x, y;
+ int limit = 1 << limitBits;
+ Span prevRow = copyBuffer;
+ Span currRow = copyBuffer.Slice(xSize, xSize);
+ Span 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 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 prevRow, Span currRow, Span 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
index 9f666ff6a..e060bbc10 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs
@@ -39,6 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Span bgra,
Span bgraScratch,
Span 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 bgra, Span image)
@@ -175,6 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int maxQuantization,
bool exact,
bool usedSubtractGreen,
+ bool nearLossless,
Span 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 src = argb.Slice((y * width) + contextStartX, maxX + haveLeft + ((y + 1) < height ? 1 : 0));
+ int offset = (y * width) + contextStartX;
+ Span src = argb.Slice(offset, maxX + haveLeft + ((y + 1) < height ? 1 : 0));
Span 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 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).
///
- private static void CopyImageWithPrediction(int width, int height, int bits, Span modes, Span argbScratch, Span argb, int maxQuantization, bool exact, bool usedSubtractGreen)
+ private static void CopyImageWithPrediction(
+ int width,
+ int height,
+ int bits,
+ Span modes,
+ Span argbScratch,
+ Span 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 argb, Span maxDiffs, bool usedSubtractGreen)
+ private static void MaxDiffsForRow(int width, int stride, Span argb, int offset, Span 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)
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
index 5b86cd4c2..678eb696a 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
+++ b/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
///
private readonly bool exact;
+ ///
+ /// Indicating whether near lossless mode should be used.
+ ///
+ private readonly bool nearLossless;
+
+ ///
+ /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).
+ ///
+ private readonly int nearLosslessQuality;
+
private const int ApplyPaletteGreedyMax = 4;
private const int PaletteInvSizeBits = 11;
@@ -76,7 +87,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// The encoding quality.
/// Quality/speed trade-off (0=fast, 6=slower-better).
/// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible RGB information for better compression.
- public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, bool exact)
+ /// Indicating whether near lossless mode should be used.
+ /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).
+ 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(pixelCount);
this.EncodedData = memoryAllocator.Allocate(pixelCount);
@@ -251,7 +266,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
int width = image.Width;
int height = image.Height;
- ReadOnlySpan bgra = this.Bgra.GetSpan();
+ Span bgra = this.Bgra.GetSpan();
Span 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(argbScratchSize);
+ this.BgraScratch = this.memoryAllocator.Allocate(bgraScratchSize);
this.TransformData = this.memoryAllocator.Allocate(transformDataSize);
this.CurrentWidth = width;
}
diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs
index ae0fb5122..eb7148386 100644
--- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs
@@ -35,6 +35,12 @@ namespace SixLabors.ImageSharp.Formats.Webp
///
public bool Exact { get; set; }
+ ///
+ public bool NearLossless { get; set; }
+
+ ///
+ public int NearLosslessQuality { get; set; } = 100;
+
///
public void Encode(Image image, Stream stream)
where TPixel : unmanaged, IPixel
diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
index 09a319de8..b515bd48b 100644
--- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
@@ -58,6 +58,16 @@ namespace SixLabors.ImageSharp.Formats.Webp
///
private readonly bool exact;
+ ///
+ /// Indicating whether near lossless mode should be used.
+ ///
+ private readonly bool nearLossless;
+
+ ///
+ /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default).
+ ///
+ private readonly int nearLosslessQuality;
+
///
/// The global configuration.
///
@@ -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;
}
///
@@ -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);
}
}
diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
index c0843a51b..bf13a9097 100644
--- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
+++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
@@ -16,6 +16,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats
{
+ [Collection("RunSerial")]
public class GeneralFormatTests
{
///
@@ -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);
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
index 4d9ff2cfb..6173ebfc3 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
+++ b/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(TestImageProvider provider, int nearLosslessQuality)
+ where TPixel : unmanaged, IPixel
+ {
+ var encoder = new WebpEncoder()
+ {
+ Lossy = false,
+ NearLossless = true,
+ NearLosslessQuality = nearLosslessQuality
+ };
+
+ using Image 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(TestImageProvider provider, int method)
+ where TPixel : unmanaged, IPixel
+ {
+ var encoder = new WebpEncoder()
+ {
+ Lossy = false,
+ Method = method,
+ Exact = true
+ };
+
+ using Image 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(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image 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(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(TestImageProvider provider, int method)
- where TPixel : unmanaged, IPixel
- {
- var encoder = new WebpEncoder()
- {
- Lossy = false,
- Method = method,
- Exact = true
- };
-
- using Image 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(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
- using Image 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(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%
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 5c86ffcd3..524ea9849 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/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";
diff --git a/tests/Images/Input/WebP/rgb_pattern.png b/tests/Images/Input/WebP/rgb_pattern.png
new file mode 100644
index 000000000..d3c59cf88
--- /dev/null
+++ b/tests/Images/Input/WebP/rgb_pattern.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f1ffed99a3cc701fff7f63cdca32c437a3e03d2a8a178380744190636decb0f8
+size 12453