diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs
index 6c8449772b..2cf2cd3111 100644
--- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs
+++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs
@@ -39,6 +39,14 @@ namespace SixLabors.ImageSharp.Formats.Webp
///
int EntropyPasses { get; }
+ ///
+ /// Gets the amplitude of the spatial noise shaping. Spatial noise shaping (or sns for short) refers to a general collection of built-in algorithms
+ /// used to decide which area of the picture should use relatively less bits, and where else to better transfer these bits.
+ /// The possible range goes from 0 (algorithm is off) to 100 (the maximal effect).
+ /// Defaults to 50.
+ ///
+ int SpatialNoiseShaping { get; }
+
///
/// Gets the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).
/// A value of 0 will turn off any filtering. Higher value will increase the strength of the filtering process applied after decoding the picture.
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
index ee3f1a8a5b..9da7217b8a 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
@@ -47,6 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
///
private readonly int filterStrength;
+ ///
+ /// The spatial noise shaping. 0=off, 100=maximum.
+ ///
+ private readonly int spatialNoiseShaping;
+
///
/// A bit writer for writing lossy webp streams.
///
@@ -94,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// Quality/speed trade-off (0=fast, 6=slower-better).
/// Number of entropy-analysis passes (in [1..10]).
/// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).
- public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses, int filterStrength)
+ public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses, int filterStrength, int spatialNoiseShaping)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
@@ -104,6 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.method = Numerics.Clamp(method, 0, 6);
this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10);
this.filterStrength = Numerics.Clamp(filterStrength, 0, 100);
+ this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100);
this.rdOptLevel = (method >= 6) ? Vp8RdLevel.RdOptTrellisAll
: (method >= 5) ? Vp8RdLevel.RdOptTrellis
: (method >= 3) ? Vp8RdLevel.RdOptBasic
@@ -353,7 +359,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
int targetSize = 0; // TODO: target size is hardcoded.
float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded.
- bool doSearch = false; // TODO: doSearch hardcoded for now.
+ bool doSearch = targetSize > 0 || targetPsnr > 0;
bool fastProbe = (this.method == 0 || this.method == 3) && !doSearch;
int numPassLeft = this.entropyPasses;
Vp8RdLevel rdOpt = (this.method >= 3 || doSearch) ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone;
@@ -663,8 +669,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
int nb = this.SegmentHeader.NumSegments;
Vp8SegmentInfo[] dqm = this.SegmentInfos;
- int snsStrength = 50; // TODO: Spatial Noise Shaping, hardcoded for now.
- double amp = WebpConstants.SnsToDq * snsStrength / 100.0d / 128.0d;
+ double amp = WebpConstants.SnsToDq * this.spatialNoiseShaping / 100.0d / 128.0d;
double cBase = QualityToCompression(quality / 100.0d);
for (int i = 0; i < nb; ++i)
{
@@ -685,25 +690,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.DqUvAc = (this.uvAlpha - WebpConstants.QuantEncMidAlpha) * (WebpConstants.QuantEncMaxDqUv - WebpConstants.QuantEncMinDqUv) / (WebpConstants.QuantEncMaxAlpha - WebpConstants.QuantEncMinAlpha);
// We rescale by the user-defined strength of adaptation.
- this.DqUvAc = this.DqUvAc * snsStrength / 100;
+ this.DqUvAc = this.DqUvAc * this.spatialNoiseShaping / 100;
// and make it safe.
this.DqUvAc = Numerics.Clamp(this.DqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv);
// We also boost the dc-uv-quant a little, based on sns-strength, since
- // U/V channels are quite more reactive to high quants (flat DC-blocks
- // tend to appear, and are unpleasant).
- this.DqUvDc = -4 * snsStrength / 100;
- this.DqUvDc = Numerics.Clamp(this.DqUvDc, -15, 15); // 4bit-signed max allowed
+ // U/V channels are quite more reactive to high quants (flat DC-blocks tend to appear, and are unpleasant).
+ this.DqUvDc = -4 * this.spatialNoiseShaping / 100;
+ this.DqUvDc = Numerics.Clamp(this.DqUvDc, -15, 15); // 4bit-signed max allowed.
this.DqY1Dc = 0;
this.DqY2Dc = 0;
this.DqY2Ac = 0;
- // Initialize segments' filtering
+ // Initialize segments' filtering.
this.SetupFilterStrength();
- this.SetupMatrices(dqm, snsStrength);
+ this.SetupMatrices(dqm);
}
private void SetupFilterStrength()
@@ -784,9 +788,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
proba.NbSkip = 0;
}
- private void SetupMatrices(Vp8SegmentInfo[] dqm, int snsStrength)
+ private void SetupMatrices(Vp8SegmentInfo[] dqm)
{
- int tlambdaScale = (this.method >= 4) ? snsStrength : 0;
+ int tlambdaScale = (this.method >= 4) ? this.spatialNoiseShaping : 0;
for (int i = 0; i < dqm.Length; ++i)
{
Vp8SegmentInfo m = dqm[i];
diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs
index eb7148386f..e9b9fbbe69 100644
--- a/src/ImageSharp/Formats/WebP/WebpEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/WebpEncoder.cs
@@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Formats.Webp
///
public int EntropyPasses { get; set; }
+ ///
+ public int SpatialNoiseShaping { get; set; } = 50;
+
///
public int FilterStrength { get; set; } = 60;
diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
index b515bd48ba..ae7bce72fa 100644
--- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
+++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
@@ -47,6 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
///
private readonly int entropyPasses;
+ ///
+ /// Spatial Noise Shaping. 0=off, 100=maximum.
+ ///
+ private readonly int spatialNoiseShaping;
+
///
/// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).
///
@@ -86,6 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.quality = options.Quality;
this.method = options.Method;
this.entropyPasses = options.EntropyPasses;
+ this.spatialNoiseShaping = options.SpatialNoiseShaping;
this.filterStrength = options.FilterStrength;
this.exact = options.Exact;
this.nearLossless = options.NearLossless;
@@ -117,7 +123,8 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.quality,
this.method,
this.entropyPasses,
- this.filterStrength);
+ this.filterStrength,
+ this.spatialNoiseShaping);
enc.Encode(image, stream);
}
else
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
index 8831e6054b..8374342e85 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
@@ -188,6 +188,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75));
}
+ [Theory]
+ [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)]
+ [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)]
+ [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)]
+ [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)]
+ [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)]
+ public void Encode_Lossy_WithDifferentSpatialNoiseShapingStrength_Works(TestImageProvider provider, int snsStrength)
+ where TPixel : unmanaged, IPixel
+ {
+ var encoder = new WebpEncoder()
+ {
+ Lossy = true,
+ SpatialNoiseShaping = snsStrength
+ };
+
+ using Image image = provider.GetImage();
+ string testOutputDetails = string.Concat("lossy", "_sns", snsStrength);
+ image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75));
+ }
+
[Theory]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)]
[WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)]