From 217c3e6dc75c2163d64bb516168a3414d218da7d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 4 Jul 2021 18:50:04 +0200 Subject: [PATCH] Add exact flag as a encoder parameter --- .../Formats/WebP/IWebpEncoderOptions.cs | 7 ++++++ .../Formats/WebP/Lossless/PredictorEncoder.cs | 2 +- .../Formats/WebP/Lossless/Vp8LEncoder.cs | 22 +++++++++++++--- src/ImageSharp/Formats/WebP/WebpEncoder.cs | 3 +++ .../Formats/WebP/WebpEncoderCore.cs | 9 ++++++- .../Formats/WebP/WebpEncoderTests.cs | 25 ++++++++++++++++++- 6 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs index 2d8a7fdeb..2dbd54478 100644 --- a/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebpEncoderOptions.cs @@ -38,5 +38,12 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Gets the number of entropy-analysis passes (in [1..10]). /// int EntropyPasses { get; } + + /// + /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. + /// The default value is false. + /// + bool Exact { get; } } } diff --git a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs index 97d96e713..9f666ff6a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/PredictorEncoder.cs @@ -641,7 +641,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless switch (mode) { case 0: - LosslessUtils.PredictorSub0(current, numPixels, output); + LosslessUtils.PredictorSub0(current + xStart, numPixels, output); break; case 1: LosslessUtils.PredictorSub1(current + xStart, numPixels, output); diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index dab106524..d6a5de07a 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -54,6 +54,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// private readonly int method; + /// + /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. + /// + private readonly bool exact; + private const int ApplyPaletteGreedyMax = 4; private const int PaletteInvSizeBits = 11; @@ -69,7 +75,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// The height of the input image. /// The encoding quality. /// Quality/speed trade-off (0=fast, 6=slower-better). - public Vp8LEncoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method) + /// 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) { int pixelCount = width * height; int initialSize = pixelCount * 2; @@ -78,6 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.configuration = configuration; this.quality = Numerics.Clamp(quality, 0, 100); this.method = Numerics.Clamp(method, 0, 6); + this.exact = exact; this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); this.EncodedData = memoryAllocator.Allocate(pixelCount); @@ -603,12 +611,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void ApplyPredictFilter(int width, int height, bool usedSubtractGreen) { int nearLosslessStrength = 100; // TODO: for now always 100 - bool exact = false; // TODO: always false for now. int predBits = this.TransformBits; int transformWidth = LosslessUtils.SubSampleSize(width, predBits); int transformHeight = LosslessUtils.SubSampleSize(height, predBits); - PredictorEncoder.ResidualImage(width, height, predBits, this.EncodedData.GetSpan(), this.BgraScratch.GetSpan(), this.TransformData.GetSpan(), nearLosslessStrength, exact, usedSubtractGreen); + PredictorEncoder.ResidualImage( + width, + height, + predBits, + this.EncodedData.GetSpan(), + this.BgraScratch.GetSpan(), + this.TransformData.GetSpan(), + nearLosslessStrength, + this.exact, + usedSubtractGreen); this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); diff --git a/src/ImageSharp/Formats/WebP/WebpEncoder.cs b/src/ImageSharp/Formats/WebP/WebpEncoder.cs index dc01840da..225938d2b 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 bool Exact { get; set; } + /// 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 9fe477d0c..8d7da5a17 100644 --- a/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebpEncoderCore.cs @@ -47,6 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private readonly int entropyPasses; + /// + /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. + /// + private readonly bool exact; + /// /// The global configuration. /// @@ -65,6 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.quality = options.Quality; this.method = options.Method; this.entropyPasses = options.EntropyPasses; + this.exact = options.Exact; } /// @@ -89,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } else { - using var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method); + using var enc = new Vp8LEncoder(this.memoryAllocator, this.configuration, image.Width, image.Height, this.quality, this.method, this.exact); enc.Encode(image, stream); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 95ea65d2b..44ae674f3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -64,7 +64,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] - [WithFile(TestImages.Png.BikeSmall, PixelTypes.Rgba32, 6, 100)] public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, int method, int quality) where TPixel : unmanaged, IPixel { @@ -128,6 +127,30 @@ 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)]