diff --git a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs index 36d3460da3..ab3757131c 100644 --- a/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs +++ b/src/ImageSharp/Formats/WebP/IWebPEncoderOptions.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger /// files compared to the slowest, but best, 100. /// - float Quality { get; } + int Quality { get; } /// /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs index 8194c54b83..181fbaac15 100644 --- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs @@ -40,6 +40,11 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// private Vp8LBitWriter bitWriter; + /// + /// The quality, that will be used to encode the image. + /// + private readonly int quality; + private const int ApplyPaletteGreedyMax = 4; private const int PaletteInvSizeBits = 11; @@ -52,11 +57,13 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless /// The memory allocator. /// The width of the input image. /// The height of the input image. - public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height) + /// The encoding quality. + public Vp8LEncoder(MemoryAllocator memoryAllocator, int width, int height, int quality) { var pixelCount = width * height; int initialSize = pixelCount * 2; + this.quality = quality.Clamp(1, 100); this.bitWriter = new Vp8LBitWriter(initialSize); this.Bgra = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebPConstants.MaxPaletteSize); @@ -255,8 +262,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless // Analyze image (entropy, numPalettes etc). CrunchConfig[] crunchConfigs = this.EncoderAnalyze(image, out bool redAndBlueAlwaysZero); - int quality = 75; // TODO: quality is hardcoded for now. - // TODO : Do we want to do this multi-threaded, this will probably require a second class: // one which co-ordinates the threading and comparison and another which does the actual encoding foreach (CrunchConfig crunchConfig in crunchConfigs) @@ -297,12 +302,12 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless if (this.UsePredictorTransform) { - this.ApplyPredictFilter(this.CurrentWidth, height, quality, this.UseSubtractGreenTransform); + this.ApplyPredictFilter(this.CurrentWidth, height, this.quality, this.UseSubtractGreenTransform); } if (this.UseCrossColorTransform) { - this.ApplyCrossColorFilter(this.CurrentWidth, height, quality); + this.ApplyCrossColorFilter(this.CurrentWidth, height, this.quality); } this.bitWriter.PutBits(0, 1); // No more transforms. @@ -314,7 +319,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless this.Refs, this.CurrentWidth, height, - quality, useCache, crunchConfig, this.CacheBits, @@ -329,7 +333,6 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless private CrunchConfig[] EncoderAnalyze(Image image, out bool redAndBlueAlwaysZero) where TPixel : unmanaged, IPixel { - var configQuality = 75; // TODO: hardcoded quality for now int method = 4; // TODO: method hardcoded to 4 for now. int width = image.Width; int height = image.Height; @@ -348,7 +351,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless bool doNotCache = false; var crunchConfigs = new List(); - if (method == 6 && configQuality == 100) + if (method == 6 && this.quality == 100) { doNotCache = true; @@ -367,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless { // Only choose the guessed best transform. crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); - if (configQuality >= 75 && method == 5) + if (this.quality >= 75 && method == 5) { // Test with and without color cache. doNotCache = true; @@ -396,14 +399,14 @@ namespace SixLabors.ImageSharp.Formats.WebP.Lossless return crunchConfigs.ToArray(); } - private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, int quality, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition) + private void EncodeImage(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs[] refsArray, int width, int height, bool useCache, CrunchConfig config, int cacheBits, int histogramBits, int initBytePosition) { int histogramImageXySize = LosslessUtils.SubSampleSize(width, histogramBits) * LosslessUtils.SubSampleSize(height, histogramBits); var histogramSymbols = new ushort[histogramImageXySize]; var huffTree = new HuffmanTree[3 * WebPConstants.CodeLengthCodes]; for (int i = 0; i < huffTree.Length; i++) { - huffTree[i] = new HuffmanTree(); + huffTree[i] = default; } if (useCache) diff --git a/src/ImageSharp/Formats/WebP/MetadataExtensions.cs b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs new file mode 100644 index 0000000000..3d0aca6db6 --- /dev/null +++ b/src/ImageSharp/Formats/WebP/MetadataExtensions.cs @@ -0,0 +1,18 @@ +using SixLabors.ImageSharp.Formats.WebP; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the webp format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static WebPMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebPFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/WebP/WebPEncoder.cs b/src/ImageSharp/Formats/WebP/WebPEncoder.cs index 787cff67bc..c6770daebc 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoder.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoder.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.WebP public bool Lossy { get; set; } /// - public float Quality { get; set; } + public int Quality { get; set; } /// public int Method { get; set; } diff --git a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs index f286c4461b..a5bce7f8c1 100644 --- a/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs +++ b/src/ImageSharp/Formats/WebP/WebPEncoderCore.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.WebP /// /// Compression quality. Between 0 and 100. /// - private float quality; + private readonly int quality; /// /// Initializes a new instance of the class. @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.WebP } else { - var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height); + var enc = new Vp8LEncoder(this.memoryAllocator, image.Width, image.Height, this.quality); enc.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs index 38787cbf86..2bb606f7f1 100644 --- a/src/ImageSharp/Formats/WebP/WebPFormat.cs +++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs @@ -10,6 +10,10 @@ namespace SixLabors.ImageSharp.Formats.WebP /// public sealed class WebPFormat : IImageFormat { + private WebPFormat() + { + } + /// /// Gets the current instance. /// diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs index 2cc2c88fe7..44ffa2be3d 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebPEncoderTests.cs @@ -9,8 +9,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public class WebPEncoderTests { [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32)] - public void Encode_Lossless_Works(TestImageProvider provider) + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 100)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 80)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 20)] + public void Encode_Lossless_Works(TestImageProvider provider, int quality) where TPixel : unmanaged, IPixel { var encoder = new WebPEncoder() @@ -20,7 +22,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP using (Image image = provider.GetImage()) { - image.VerifyEncoder(provider, "webp", "lossless", encoder); + var testOutputDetails = string.Concat("lossless", "_", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } } }