From a41631efa3e076d4ec95d6a7d48517d68b2f8bcc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 19 Oct 2021 19:17:07 +0200 Subject: [PATCH] Add enum for webp encoding method --- .../Formats/Webp/IWebpEncoderOptions.cs | 2 +- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 27 +++++--- .../Formats/Webp/Lossy/Vp8EncIterator.cs | 2 +- .../Formats/Webp/Lossy/Vp8Encoder.cs | 37 ++++++----- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 2 +- .../Formats/Webp/WebpEncoderCore.cs | 2 +- .../Formats/Webp/WebpEncodingMethod.cs | 61 +++++++++++++++++++ .../Formats/WebP/WebpEncoderTests.cs | 8 +-- 8 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs index 4992f585d..6abf34483 100644 --- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). /// Defaults to 4. /// - int Method { get; } + WebpEncodingMethod Method { get; } /// /// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format. diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index a57c1c982..e6e8258cd 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Quality/speed trade-off (0=fast, 6=slower-better). /// - private readonly int method; + private readonly WebpEncodingMethod method; /// /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible @@ -88,7 +88,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible RGB information for better compression. /// 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) + public Vp8LEncoder( + MemoryAllocator memoryAllocator, + Configuration configuration, + int width, + int height, + int quality, + WebpEncodingMethod method, + bool exact, + bool nearLossless, + int nearLosslessQuality) { int pixelCount = width * height; int initialSize = pixelCount * 2; @@ -96,7 +105,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.memoryAllocator = memoryAllocator; this.configuration = configuration; this.quality = Numerics.Clamp(quality, 0, 100); - this.method = Numerics.Clamp(method, 0, 6); + this.method = (WebpEncodingMethod)Numerics.Clamp((int)method, 0, 6); this.exact = exact; this.nearLossless = nearLossless; this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100); @@ -424,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless bool doNotCache = false; var crunchConfigs = new List(); - if (this.method == 6 && this.quality == 100) + if (this.method == WebpEncodingMethod.BestQuality && this.quality == 100) { doNotCache = true; @@ -442,7 +451,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { // Only choose the guessed best transform. crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); - if (this.quality >= 75 && this.method == 5) + if (this.quality >= 75 && this.method == WebpEncodingMethod.Level5) { // Test with and without color cache. doNotCache = true; @@ -1615,10 +1624,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Calculates the huffman image bits. /// - private static int GetHistoBits(int method, bool usePalette, int width, int height) + private static int GetHistoBits(WebpEncodingMethod method, bool usePalette, int width, int height) { // Make tile size a function of encoding method (Range: 0 to 6). - int histoBits = (usePalette ? 9 : 7) - method; + int histoBits = (usePalette ? 9 : 7) - (int)method; while (true) { int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); @@ -1678,9 +1687,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Calculates the bits used for the transformation. /// [MethodImpl(InliningOptions.ShortMethod)] - private static int GetTransformBits(int method, int histoBits) + private static int GetTransformBits(WebpEncodingMethod method, int histoBits) { - int maxTransformBits = method < 4 ? 6 : method > 4 ? 4 : 5; + int maxTransformBits = (int)method < 4 ? 6 : (int)method > 4 ? 4 : 5; int res = histoBits > maxTransformBits ? maxTransformBits : histoBits; return res; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index dab31c7a2..ca3f8481e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -432,7 +432,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } while (this.RotateI4(this.YuvIn.AsSpan(YOffEnc))); // Note: we reuse the original samples for predictors. - var i4Alpha = totalHisto.GetAlpha(); + int i4Alpha = totalHisto.GetAlpha(); if (i4Alpha > bestAlpha) { this.SetIntra4Mode(modes); diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index f548ddb7a..16b84f2c6 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Quality/speed trade-off (0=fast, 6=slower-better). /// - private readonly int method; + private readonly WebpEncodingMethod method; /// /// Number of entropy-analysis passes (in [1..10]). @@ -99,20 +99,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Number of entropy-analysis passes (in [1..10]). /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). /// The spatial noise shaping. 0=off, 100=maximum. - public Vp8Encoder(MemoryAllocator memoryAllocator, Configuration configuration, int width, int height, int quality, int method, int entropyPasses, int filterStrength, int spatialNoiseShaping) + public Vp8Encoder( + MemoryAllocator memoryAllocator, + Configuration configuration, + int width, + int height, + int quality, + WebpEncodingMethod method, + int entropyPasses, + int filterStrength, + int spatialNoiseShaping) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; this.Width = width; this.Height = height; this.quality = Numerics.Clamp(quality, 0, 100); - this.method = Numerics.Clamp(method, 0, 6); + this.method = (WebpEncodingMethod)Numerics.Clamp((int)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 + this.rdOptLevel = method is WebpEncodingMethod.BestQuality ? Vp8RdLevel.RdOptTrellisAll + : (int)method >= 5 ? Vp8RdLevel.RdOptTrellis + : (int)method >= 3 ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int pixelCount = width * height; @@ -360,9 +369,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int targetSize = 0; // TODO: target size is hardcoded. float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. bool doSearch = targetSize > 0 || targetPsnr > 0; - bool fastProbe = (this.method == 0 || this.method == 3) && !doSearch; + bool fastProbe = (this.method == 0 || this.method == WebpEncodingMethod.Level3) && !doSearch; int numPassLeft = this.entropyPasses; - Vp8RdLevel rdOpt = this.method >= 3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; + Vp8RdLevel rdOpt = (int)this.method >= 3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; int nbMbs = this.Mbw * this.Mbh; var stats = new PassStats(targetSize, targetPsnr, QMin, QMax, this.quality); @@ -371,7 +380,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Fast mode: quick analysis pass over few mbs. Better than nothing. if (fastProbe) { - if (this.method == 3) + if (this.method == WebpEncodingMethod.Level3) { // We need more stats for method 3 to be reliable. nbMbs = nbMbs > 200 ? nbMbs >> 1 : 100; @@ -790,7 +799,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private void SetupMatrices(Vp8SegmentInfo[] dqm) { - int tlambdaScale = (this.method >= 4) ? this.spatialNoiseShaping : 0; + int tlambdaScale = (int)this.method >= 4 ? this.spatialNoiseShaping : 0; for (int i = 0; i < dqm.Length; i++) { Vp8SegmentInfo m = dqm[i]; @@ -861,14 +870,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy it.SetSegment(0); // default segment, spec-wise. int bestAlpha; - if (this.method <= 1) + if ((int)this.method <= 1) { bestAlpha = it.FastMbAnalyze(this.quality); } else { bestAlpha = it.MbAnalyzeBestIntra16Mode(); - if (this.method >= 5) + if ((int)this.method >= 5) { // We go and make a fast decision for intra4/intra16. // It's usually not a good and definitive pick, but helps seeding the stats about level bit-cost. @@ -899,7 +908,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (rdOpt > Vp8RdLevel.RdOptNone) { QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); - if (this.method >= 2) + if ((int)this.method >= 2) { QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); } @@ -912,7 +921,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). // For method <= 1, we don't re-examine the decision but just go ahead with // quantization/reconstruction. - QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= 2, this.method >= 1, this.MbHeaderLimit); + QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, (int)this.method >= 2, (int)this.method >= 1, this.MbHeaderLimit); } bool isSkipped = rd.Nz == 0; diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index bb30e2512..1eb7b3846 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public int Quality { get; set; } = 75; /// - public int Method { get; set; } = 4; + public WebpEncodingMethod Method { get; set; } = WebpEncodingMethod.Default; /// public bool UseAlphaCompression { get; set; } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 63f54f133..57c696c96 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Quality/speed trade-off (0=fast, 6=slower-better). /// - private readonly int method; + private readonly WebpEncodingMethod method; /// /// The number of entropy-analysis passes (in [1..10]). diff --git a/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs b/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs new file mode 100644 index 000000000..7d245a7e7 --- /dev/null +++ b/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Webp +{ + /// + /// Quality/speed trade-off for the encoding process (0=fast, 6=slower-better). + /// + public enum WebpEncodingMethod + { + /// + /// Fastest, but quality compromise. Equivalent to . + /// + Level0 = 0, + + /// + /// Fastest, but quality compromise. + /// + Fastest = Level0, + + /// + /// Level1. + /// + Level1 = 1, + + /// + /// Level 2. + /// + Level2 = 2, + + /// + /// Level 3. + /// + Level3 = 3, + + /// + /// Level 4. Equivalent to . + /// + Level4 = 4, + + /// + /// BestQuality trade off between speed and quality. + /// + Default = Level4, + + /// + /// Level 5. + /// + Level5 = 5, + + /// + /// Slowest option, but best quality. Equivalent to . + /// + Level6 = 6, + + /// + /// Slowest option, but best quality. + /// + BestQuality = Level6 + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index a72621241..6a0e599d0 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { Lossy = false, Quality = 100, - Method = 6 + Method = WebpEncodingMethod.BestQuality }; using Image image = provider.GetImage(); @@ -65,7 +65,7 @@ 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)] - public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, int method, int quality) + public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) where TPixel : unmanaged, IPixel { var encoder = new WebpEncoder() @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [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) + public void Encode_Lossless_WithExactFlag_Works(TestImageProvider provider, WebpEncodingMethod method) where TPixel : unmanaged, IPixel { var encoder = new WebpEncoder() @@ -223,7 +223,7 @@ 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)] - public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageProvider provider, int method, int quality) + public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) where TPixel : unmanaged, IPixel { var encoder = new WebpEncoder()