From 4fefcf1461c94c65865cd15365810b65b18bd8d3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 5 Jul 2021 14:47:14 +0200 Subject: [PATCH] Add filter strength as encoder parameter --- .../Formats/WebP/IWebpEncoderOptions.cs | 9 +++++++++ .../Formats/WebP/Lossy/Vp8Encoder.cs | 18 ++++++++++------- src/ImageSharp/Formats/WebP/WebpEncoder.cs | 3 +++ .../Formats/WebP/WebpEncoderCore.cs | 8 +++++++- .../Formats/WebP/WebpEncoderTests.cs | 20 +++++++++++++++++++ 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs index 2dbd54478b..dad67f8041 100644 --- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs @@ -39,6 +39,15 @@ namespace SixLabors.ImageSharp.Formats.Webp /// int EntropyPasses { 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. + /// The higher the value the smoother the picture will appear. + /// Typical values are usually in the range of 20 to 50. + /// Defaults to 60. + /// + int FilterStrength { get; } + /// /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible /// RGB information for better compression. diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs index 83f44e1ee1..ee3f1a8a5b 100644 --- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs @@ -42,6 +42,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private readonly int entropyPasses; + /// + /// Specify the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). A value of 0 will turn off any filtering. + /// + private readonly int filterStrength; + /// /// A bit writer for writing lossy webp streams. /// @@ -78,11 +83,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private const int QMax = 100; - // TODO: filterStrength is hardcoded, should be configurable. - private const int FilterStrength = 60; - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The memory allocator. /// The global configuration. @@ -91,7 +93,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). /// Number of entropy-analysis passes (in [1..10]). - public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses) + /// 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) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; @@ -100,6 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.quality = Numerics.Clamp(quality, 0, 100); this.method = Numerics.Clamp(method, 0, 6); this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); + this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); this.rdOptLevel = (method >= 6) ? Vp8RdLevel.RdOptTrellisAll : (method >= 5) ? Vp8RdLevel.RdOptTrellis : (method >= 3) ? Vp8RdLevel.RdOptBasic @@ -476,7 +480,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void AdjustFilterStrength() { - if (FilterStrength > 0) + if (this.filterStrength > 0) { int maxLevel = 0; for (int s = 0; s < WebpConstants.NumMbSegments; s++) @@ -708,7 +712,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int filterType = 1; // TODO: filterType is hardcoded // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. - int level0 = 5 * FilterStrength; + int level0 = 5 * this.filterStrength; for (int i = 0; i < WebpConstants.NumMbSegments; ++i) { Vp8SegmentInfo m = this.SegmentInfos[i]; diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index 225938d2b6..ae0fb51228 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 FilterStrength { get; set; } = 60; + /// public bool Exact { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs index 8d7da5a175..09a319de84 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; + /// + /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). + /// + private readonly int filterStrength; + /// /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible /// RGB information for better compression. @@ -71,6 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.quality = options.Quality; this.method = options.Method; this.entropyPasses = options.EntropyPasses; + this.filterStrength = options.FilterStrength; this.exact = options.Exact; } @@ -91,7 +97,7 @@ 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); + 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 diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 8de790b982..4d9ff2cfbf 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -98,6 +98,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } + [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_WithDifferentFilterStrength_Works(TestImageProvider provider, int filterStrength) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + Lossy = true, + FilterStrength = filterStrength + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossy", "_f", filterStrength); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); + } + [Theory] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)]