// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { using SixLabors.ImageSharp.Quantizers; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; public class PngEncoderTests { private const float ToleranceThresholdForPaletteEncoder = 0.01f / 100; /// /// All types except Palette /// public static readonly TheoryData PngColorTypes = new TheoryData() { PngColorType.RgbWithAlpha, PngColorType.Rgb, PngColorType.Grayscale, PngColorType.GrayscaleWithAlpha, }; /// /// All types except Palette /// public static readonly TheoryData CompressionLevels = new TheoryData() { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; public static readonly TheoryData PaletteSizes = new TheoryData() { 30, 55, 100, 201, 255 }; public static readonly TheoryData PaletteLargeOnly = new TheoryData() { 80, 100, 120, 230 }; [Theory] [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(PngColorTypes), 47, 8, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(PngColorTypes), 49, 7, PixelTypes.Rgba32)] [WithSolidFilledImages(nameof(PngColorTypes), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)] public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) where TPixel : struct, IPixel { TestPngEncoderCore(provider, pngColorType, appendPngColorType: true); } [Theory] [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) where TPixel : struct, IPixel { TestPngEncoderCore(provider, pngColorType, appendPixelType: true); } [Theory] [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] public void WorksWithAllCompressionLevels(TestImageProvider provider, int compressionLevel) where TPixel : struct, IPixel { TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, compressionLevel, appendCompressionLevel: true); } [Theory] [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] public void PaletteColorType_WuQuantizer_File( TestImageProvider provider, int paletteSize) where TPixel : struct, IPixel { this.PaletteColorType_WuQuantizer(provider, paletteSize); } [Theory] [WithTestPatternImages(nameof(PaletteSizes), 72, 72, PixelTypes.Rgba32)] public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { var encoder = new PngEncoder { PngColorType = PngColorType.Palette, PaletteSize = paletteSize, Quantizer = new WuQuantizer() }; image.VerifyEncoder(provider, "png", $"PaletteSize-{paletteSize}", encoder, appendPixelTypeToFileName: false); } } private static bool HasAlpha(PngColorType pngColorType) => pngColorType == PngColorType.GrayscaleWithAlpha || pngColorType == PngColorType.RgbWithAlpha; private static void TestPngEncoderCore( TestImageProvider provider, PngColorType pngColorType, int compressionLevel = 6, bool appendPngColorType = false, bool appendPixelType = false, bool appendCompressionLevel = false) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { if (!HasAlpha(pngColorType)) { image.Mutate(c => c.Opacity(1)); } var encoder = new PngEncoder { PngColorType = pngColorType, CompressionLevel = compressionLevel}; string pngColorTypeInfo = appendPixelType ? pngColorType.ToString() : ""; string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : ""; string debugInfo = $"{pngColorTypeInfo}{compressionLevelInfo}"; string referenceInfo = $"{pngColorTypeInfo}"; // Does DebugSave & load reference CompareToReferenceInput(): string path = ((ITestImageProvider)provider).Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(path); string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", referenceInfo, appendPixelType); using (var encodedImage = Image.Load(referenceOutputFile, referenceDecoder)) { ImageComparer comparer = pngColorType== PngColorType.Palette ? ImageComparer.Tolerant(ToleranceThresholdForPaletteEncoder) : ImageComparer.Exact; comparer.CompareImagesOrFrames(image, encodedImage); } } } [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void WritesFileMarker(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) using (var ms = new MemoryStream()) { image.Save(ms, new PngEncoder()); byte[] data = ms.ToArray().Take(8).ToArray(); byte[] expected = { 0x89, // Set the high bit. 0x50, // P 0x4E, // N 0x47, // G 0x0D, // Line ending CRLF 0x0A, // Line ending CRLF 0x1A, // EOF 0x0A // LF }; Assert.Equal(expected, data); } } } }