Browse Source

Add Spatial noise shaping option for the lossy encoder

pull/1552/head
Brian Popow 5 years ago
parent
commit
8c3b021a98
  1. 8
      src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs
  2. 30
      src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
  3. 3
      src/ImageSharp/Formats/WebP/WebpEncoder.cs
  4. 9
      src/ImageSharp/Formats/WebP/WebpEncoderCore.cs
  5. 20
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

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

@ -39,6 +39,14 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary>
int EntropyPasses { get; }
/// <summary>
/// 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.
/// </summary>
int SpatialNoiseShaping { get; }
/// <summary>
/// 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.

30
src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs

@ -47,6 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// </summary>
private readonly int filterStrength;
/// <summary>
/// The spatial noise shaping. 0=off, 100=maximum.
/// </summary>
private readonly int spatialNoiseShaping;
/// <summary>
/// A bit writer for writing lossy webp streams.
/// </summary>
@ -94,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
/// <param name="method">Quality/speed trade-off (0=fast, 6=slower-better).</param>
/// <param name="entropyPasses">Number of entropy-analysis passes (in [1..10]).</param>
/// <param name="filterStrength">The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).</param>
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];

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

@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <inheritdoc/>
public int EntropyPasses { get; set; }
/// <inheritdoc/>
public int SpatialNoiseShaping { get; set; } = 50;
/// <inheritdoc/>
public int FilterStrength { get; set; } = 60;

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

@ -47,6 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// </summary>
private readonly int entropyPasses;
/// <summary>
/// Spatial Noise Shaping. 0=off, 100=maximum.
/// </summary>
private readonly int spatialNoiseShaping;
/// <summary>
/// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering).
/// </summary>
@ -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

20
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<TPixel>(TestImageProvider<TPixel> provider, int snsStrength)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
{
Lossy = true,
SpatialNoiseShaping = snsStrength
};
using Image<TPixel> 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)]

Loading…
Cancel
Save