// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using static SixLabors.ImageSharp.Tests.TestImages.Webp; namespace SixLabors.ImageSharp.Tests.Formats.Webp; [Trait("Format", "Webp")] public class WebpEncoderTests { private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.NoFilter06); [Theory] [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] // If its not a webp input image, it should default to lossy. [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] [WithFile(Lossy.BikeWithExif, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] public void Encode_PreserveRatio(TestImageProvider provider, WebpFileFormatType expectedFormat) where TPixel : unmanaged, IPixel { WebpEncoder options = new(); using Image input = provider.GetImage(); using MemoryStream memoryStream = new(); input.Save(memoryStream, options); memoryStream.Position = 0; using Image output = Image.Load(memoryStream); ImageMetadata meta = output.Metadata; WebpMetadata webpMetaData = meta.GetWebpMetadata(); Assert.Equal(expectedFormat, webpMetaData.FileFormat); } [Theory] [WithFile(Flag, PixelTypes.Rgba32)] [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] public void Encode_Lossless_WithPalette_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless, Quality = 100, Method = WebpEncodingMethod.BestQuality }; using Image image = provider.GetImage(); image.VerifyEncoder(provider, "webp", string.Empty, encoder); } [Theory] [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_WithDifferentQuality_Works(TestImageProvider provider, int quality) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless, Quality = quality }; using Image image = provider.GetImage(); string testOutputDetails = string.Concat("lossless", "_q", quality); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } [Theory] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] [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, WebpEncodingMethod method, int quality) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless, Method = method, Quality = quality }; using Image image = provider.GetImage(); string testOutputDetails = string.Concat("lossless", "_m", method, "_q", quality); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } [Theory] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 15114)] public void Encode_Lossless_WithBestQuality_HasExpectedSize(TestImageProvider provider, int expectedBytes) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless, Method = WebpEncodingMethod.BestQuality }; using Image image = provider.GetImage(); using MemoryStream memoryStream = new(); image.Save(memoryStream, encoder); Assert.Equal(memoryStream.Length, expectedBytes); } [Theory] [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 85)] [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 60)] [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 40)] [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 20)] [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 10)] [WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)] public void Encode_Lossless_WithNearLosslessFlag_Works(TestImageProvider provider, int nearLosslessQuality) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless, NearLossless = true, NearLosslessQuality = nearLosslessQuality }; using Image image = provider.GetImage(); string testOutputDetails = string.Concat("nearlossless", "_q", nearLosslessQuality); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(nearLosslessQuality)); } [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_WithPreserveTransparentColor_Works(TestImageProvider provider, WebpEncodingMethod method) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless, Method = method, TransparentColorMode = WebpTransparentColorMode.Preserve }; using Image image = provider.GetImage(); string testOutputDetails = string.Concat("lossless", "_m", method); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } [Fact] public void Encode_Lossless_OneByOnePixel_Works() { // Just make sure, encoding 1 pixel by 1 pixel does not throw an exception. using Image image = new(1, 1); WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless }; using (MemoryStream memStream = new()) { image.SaveAsWebp(memStream, encoder); } } [Theory] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 20)] public void Encode_Lossy_WithDifferentQuality_Works(TestImageProvider provider, int quality) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy, Quality = quality }; using Image image = provider.GetImage(); string testOutputDetails = string.Concat("lossy", "_q", quality); 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 { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy, 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, 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(TestImageProvider provider, int snsStrength) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy, SpatialNoiseShaping = snsStrength }; using Image 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)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] [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, WebpEncodingMethod method, int quality) where TPixel : unmanaged, IPixel { WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy, Method = method, Quality = quality }; using Image image = provider.GetImage(); string testOutputDetails = string.Concat("lossy", "_m", method, "_q", quality); image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); } [Theory] [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32)] public void Encode_Lossy_WithAlpha_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { // Floating point differences result in minor pixel differences affecting compression. // Output have been manually verified. int expectedFileSize = TestEnvironment.OSArchitecture == Architecture.Arm64 ? 64060 : 64020; WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy, UseAlphaCompression = false }; using Image image = provider.GetImage(); string encodedFile = image.VerifyEncoder( provider, "webp", "with_alpha", encoder, ImageComparer.Tolerant(0.04f), referenceDecoder: new MagickReferenceDecoder()); int encodedBytes = File.ReadAllBytes(encodedFile).Length; Assert.True(encodedBytes <= expectedFileSize, $"encoded bytes are {encodedBytes} and should be smaller then expected file size of {expectedFileSize}"); } [Theory] [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32)] public void Encode_Lossy_WithAlphaUsingCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { // Floating point differences result in minor pixel differences affecting compression. // Output have been manually verified. int expectedFileSize = TestEnvironment.OSArchitecture == Architecture.Arm64 ? 16240 : 16200; WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy, UseAlphaCompression = true }; using Image image = provider.GetImage(); string encodedFile = image.VerifyEncoder( provider, "webp", "with_alpha_compressed", encoder, ImageComparer.Tolerant(0.04f), referenceDecoder: new MagickReferenceDecoder()); int encodedBytes = File.ReadAllBytes(encodedFile).Length; Assert.True(encodedBytes <= expectedFileSize, $"encoded bytes are {encodedBytes} and should be smaller then expected file size of {expectedFileSize}"); } [Theory] [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless }; image.VerifyEncoder(provider, "webp", string.Empty, encoder); } [Theory] [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] public void Encode_Lossy_WorksWithTestPattern(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy }; image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); } public static void RunEncodeLossy_WithPeakImage() { TestImageProvider provider = TestImageProvider.File(TestImageLossyFullPath); using Image image = provider.GetImage(); WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy }; image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); } [Fact] public void RunEncodeLossy_WithPeakImage_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.AllowAll); [Fact] public void RunEncodeLossy_WithPeakImage_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.DisableHWIntrinsic); private static ImageComparer GetComparer(int quality) { float tolerance = 0.01f; // ~1.0% if (quality < 30) { tolerance = 0.02f; // ~2.0% } return ImageComparer.Tolerant(tolerance); } }