📷 A modern, cross-platform, 2D Graphics library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

753 lines
30 KiB

// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// ReSharper disable InconsistentNaming
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
namespace SixLabors.ImageSharp.Tests.Formats.Png;
[Trait("Format", "Png")]
public partial class PngEncoderTests
{
private static PngEncoder PngEncoder => new();
public static readonly TheoryData<string, PngBitDepth> PngBitDepthFiles =
new()
{
{ TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 },
{ TestImages.Png.Bpp1, PngBitDepth.Bit1 }
};
public static readonly TheoryData<string, PngBitDepth, PngColorType> PngTrnsFiles =
new()
{
{ TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale },
{ TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale },
{ TestImages.Png.Gray4BitTrans, PngBitDepth.Bit4, PngColorType.Grayscale },
{ TestImages.Png.L8BitTrans, PngBitDepth.Bit8, PngColorType.Grayscale },
{ TestImages.Png.GrayTrns16BitInterlaced, PngBitDepth.Bit16, PngColorType.Grayscale },
{ TestImages.Png.Rgb24BppTrans, PngBitDepth.Bit8, PngColorType.Rgb },
{ TestImages.Png.Rgb48BppTrans, PngBitDepth.Bit16, PngColorType.Rgb }
};
/// <summary>
/// All types except Palette
/// </summary>
public static readonly TheoryData<PngColorType> PngColorTypes = new()
{
PngColorType.RgbWithAlpha,
PngColorType.Rgb,
PngColorType.Grayscale,
PngColorType.GrayscaleWithAlpha,
};
public static readonly TheoryData<PngFilterMethod> PngFilterMethods = new()
{
PngFilterMethod.None,
PngFilterMethod.Sub,
PngFilterMethod.Up,
PngFilterMethod.Average,
PngFilterMethod.Paeth,
PngFilterMethod.Adaptive
};
/// <summary>
/// All types except Palette
/// </summary>
public static readonly TheoryData<PngCompressionLevel> CompressionLevels
= new()
{
PngCompressionLevel.Level0,
PngCompressionLevel.Level1,
PngCompressionLevel.Level2,
PngCompressionLevel.Level3,
PngCompressionLevel.Level4,
PngCompressionLevel.Level5,
PngCompressionLevel.Level6,
PngCompressionLevel.Level7,
PngCompressionLevel.Level8,
PngCompressionLevel.Level9,
};
public static readonly TheoryData<int> PaletteSizes = new()
{
30, 55, 100, 201, 255
};
public static readonly TheoryData<int> PaletteLargeOnly = new()
{
80, 100, 120, 230
};
public static readonly PngInterlaceMode[] InterlaceMode =
[
PngInterlaceMode.None,
PngInterlaceMode.Adam7
];
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new()
{
{ TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter },
{ TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio },
{ TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio }
};
[Fact]
public void PngEncoderDefaultInstanceHasNullQuantizer() => Assert.Null(PngEncoder.Quantizer);
[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<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType)
where TPixel : unmanaged, IPixel<TPixel>
=> TestPngEncoderCore(
provider,
pngColorType,
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
PngInterlaceMode.None,
appendPngColorType: true);
[Theory]
[WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)]
public void IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
pngColorType,
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
appendPngColorType: true,
appendPixelType: true);
}
}
[Theory]
[WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)]
public void WorksWithAllFilterMethods<TPixel>(TestImageProvider<TPixel> provider, PngFilterMethod pngFilterMethod)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
PngColorType.RgbWithAlpha,
pngFilterMethod,
PngBitDepth.Bit8,
interlaceMode,
appendPngFilterMethod: true);
}
}
[Theory]
[WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)]
public void WorksWithAllCompressionLevels<TPixel>(TestImageProvider<TPixel> provider, PngCompressionLevel compressionLevel)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
PngColorType.RgbWithAlpha,
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
compressionLevel,
appendCompressionLevel: true);
}
}
[Theory]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)]
public void WorksWithAllBitDepths<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Investigate WuQuantizer to see if we can reduce memory pressure.
if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess)
{
return;
}
foreach (object[] filterMethod in PngFilterMethods)
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
pngColorType,
(PngFilterMethod)filterMethod[0],
pngBitDepth,
interlaceMode,
appendPngColorType: true,
appendPixelType: true,
appendPngBitDepth: true);
}
}
}
[Theory]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
[WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)]
public void WorksWithAllBitDepthsAndExcludeAllFilter<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (object[] filterMethod in PngFilterMethods)
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
pngColorType,
(PngFilterMethod)filterMethod[0],
pngBitDepth,
interlaceMode,
appendPngColorType: true,
appendPixelType: true,
appendPngBitDepth: true,
optimizeMethod: PngChunkFilter.ExcludeAll);
}
}
}
[Theory]
[WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)]
public void PaletteColorType_WuQuantizer<TPixel>(TestImageProvider<TPixel> provider, int paletteSize)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Investigate WuQuantizer to see if we can reduce memory pressure.
if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess)
{
return;
}
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
PngColorType.Palette,
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
paletteSize: paletteSize,
appendPaletteSize: true);
}
}
[Theory]
[WithBlankImages(1, 1, PixelTypes.Rgba32)]
public void WritesFileMarker<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
using MemoryStream ms = new();
image.Save(ms, 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);
}
[Theory]
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
TestFile testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using MemoryStream memStream = new();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
ImageMetadata meta = output.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
Assert.Equal(resolutionUnit, meta.ResolutionUnits);
}
[Theory]
[MemberData(nameof(PngBitDepthFiles))]
public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth)
{
TestFile testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using MemoryStream memStream = new();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
PngMetadata meta = output.Metadata.GetPngMetadata();
Assert.Equal(pngBitDepth, meta.BitDepth);
}
[Theory]
[InlineData(PngColorType.Palette)]
[InlineData(PngColorType.RgbWithAlpha)]
[InlineData(PngColorType.GrayscaleWithAlpha)]
public void Encode_WithTransparentColorBehaviorClear_Works(PngColorType colorType)
{
// arrange
using Image<Rgba32> image = new(50, 50);
PngEncoder encoder = new()
{
TransparentColorMode = TransparentColorMode.Clear,
ColorType = colorType
};
Rgba32 rgba32 = Color.Blue.ToPixel<Rgba32>();
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
// Half of the test image should be transparent.
if (y > 25)
{
rgba32.A = 0;
}
for (int x = 0; x < image.Width; x++)
{
rowSpan[x] = Rgba32.FromRgba32(rgba32);
}
}
});
// act
using MemoryStream memStream = new();
image.Save(memStream, encoder);
// assert
memStream.Position = 0;
using Image<Rgba32> actual = Image.Load<Rgba32>(memStream);
Rgba32 expectedColor = Color.Blue.ToPixel<Rgba32>();
if (colorType is PngColorType.Grayscale or PngColorType.GrayscaleWithAlpha)
{
byte luminance = ColorNumerics.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B);
expectedColor = new Rgba32(luminance, luminance, luminance);
}
actual.ProcessPixelRows(accessor =>
{
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> rowSpan = accessor.GetRowSpan(y);
if (y > 25)
{
expectedColor = transparent;
}
for (int x = 0; x < accessor.Width; x++)
{
Assert.Equal(expectedColor, rowSpan[x]);
}
}
});
}
[Theory]
[WithFile(TestImages.Png.APng, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.FrameOffset, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Issue2882, PixelTypes.Rgba32)]
public void Encode_APng<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance);
using MemoryStream memStream = new();
image.Save(memStream, PngEncoder);
memStream.Position = 0;
image.DebugSave(provider: provider, encoder: PngEncoder, null, false);
using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
// Some loss from original, due to palette matching accuracy.
ImageComparer.TolerantPercentage(0.172F).VerifySimilarity(output, image);
Assert.Equal(image.Frames.Count, output.Frames.Count);
PngMetadata originalMetadata = image.Metadata.GetPngMetadata();
PngMetadata outputMetadata = output.Metadata.GetPngMetadata();
Assert.Equal(originalMetadata.RepeatCount, outputMetadata.RepeatCount);
Assert.Equal(originalMetadata.AnimateRootFrame, outputMetadata.AnimateRootFrame);
for (int i = 0; i < image.Frames.Count; i++)
{
PngFrameMetadata originalFrameMetadata = image.Frames[i].Metadata.GetPngMetadata();
PngFrameMetadata outputFrameMetadata = output.Frames[i].Metadata.GetPngMetadata();
Assert.Equal(originalFrameMetadata.FrameDelay, outputFrameMetadata.FrameDelay);
Assert.Equal(originalFrameMetadata.BlendMode, outputFrameMetadata.BlendMode);
Assert.Equal(originalFrameMetadata.DisposalMode, outputFrameMetadata.DisposalMode);
}
}
[Theory]
[WithFile(TestImages.Gif.Leo, PixelTypes.Rgba32)]
[WithFile(TestImages.Gif.Issues.Issue2866, PixelTypes.Rgba32)]
public void Encode_AnimatedFormatTransform_FromGif<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows)
{
return;
}
using Image<TPixel> image = provider.GetImage(GifDecoder.Instance);
// Save the image for visual inspection.
provider.Utility.SaveTestOutputFile(image, "png", PngEncoder, "animated");
// Now compare the debug output with the reference output.
// We do this because the transcoding encoding is lossy and encoding will lead to differences.
// From the unencoded image, we can see that the image is visually the same.
static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them.
image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "png", encoder: PngEncoder, predicate: Predicate);
// Now save the image and load it again to compare the metadata.
using MemoryStream memStream = new();
image.Save(memStream, PngEncoder);
memStream.Position = 0;
using Image<TPixel> encoded = Image.Load<TPixel>(memStream);
GifMetadata gif = image.Metadata.GetGifMetadata();
PngMetadata png = encoded.Metadata.GetPngMetadata();
Assert.Equal(gif.RepeatCount, png.RepeatCount);
for (int i = 0; i < image.Frames.Count; i++)
{
GifFrameMetadata gifF = image.Frames[i].Metadata.GetGifMetadata();
PngFrameMetadata pngF = encoded.Frames[i].Metadata.GetPngMetadata();
Assert.Equal(gifF.FrameDelay, (int)(pngF.FrameDelay.ToDouble() * 100));
switch (gifF.DisposalMode)
{
case FrameDisposalMode.RestoreToBackground:
Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode);
break;
case FrameDisposalMode.RestoreToPrevious:
Assert.Equal(FrameDisposalMode.RestoreToPrevious, pngF.DisposalMode);
break;
case FrameDisposalMode.Unspecified:
case FrameDisposalMode.DoNotDispose:
default:
Assert.Equal(FrameDisposalMode.DoNotDispose, pngF.DisposalMode);
break;
}
}
}
[Theory]
[WithFile(TestImages.Webp.Lossless.Animated, PixelTypes.Rgba32)]
public void Encode_AnimatedFormatTransform_FromWebp<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.RunsOnCI && !TestEnvironment.IsWindows)
{
return;
}
using Image<TPixel> image = provider.GetImage(WebpDecoder.Instance);
using MemoryStream memStream = new();
image.Save(memStream, PngEncoder);
memStream.Position = 0;
using Image<TPixel> output = Image.Load<TPixel>(memStream);
ImageComparer.Exact.VerifySimilarity(output, image);
WebpMetadata webp = image.Metadata.GetWebpMetadata();
PngMetadata png = output.Metadata.GetPngMetadata();
Assert.Equal(webp.RepeatCount, png.RepeatCount);
for (int i = 0; i < image.Frames.Count; i++)
{
WebpFrameMetadata webpF = image.Frames[i].Metadata.GetWebpMetadata();
PngFrameMetadata pngF = output.Frames[i].Metadata.GetPngMetadata();
Assert.Equal(webpF.FrameDelay, (uint)(pngF.FrameDelay.ToDouble() * 1000));
switch (webpF.BlendMode)
{
case FrameBlendMode.Source:
Assert.Equal(FrameBlendMode.Source, pngF.BlendMode);
break;
case FrameBlendMode.Over:
default:
Assert.Equal(FrameBlendMode.Over, pngF.BlendMode);
break;
}
switch (webpF.DisposalMode)
{
case FrameDisposalMode.RestoreToBackground:
Assert.Equal(FrameDisposalMode.RestoreToBackground, pngF.DisposalMode);
break;
case FrameDisposalMode.DoNotDispose:
default:
Assert.Equal(FrameDisposalMode.DoNotDispose, pngF.DisposalMode);
break;
}
}
}
[Theory]
[MemberData(nameof(PngTrnsFiles))]
public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType)
{
TestFile testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image();
PngMetadata inMeta = input.Metadata.GetPngMetadata();
Assert.True(inMeta.TransparentColor.HasValue);
using MemoryStream memStream = new();
input.Save(memStream, PngEncoder);
memStream.Position = 0;
using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
PngMetadata outMeta = output.Metadata.GetPngMetadata();
Assert.True(outMeta.TransparentColor.HasValue);
Assert.Equal(inMeta.TransparentColor, outMeta.TransparentColor);
Assert.Equal(pngBitDepth, outMeta.BitDepth);
Assert.Equal(pngColorType, outMeta.ColorType);
}
[Theory]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32)]
[WithTestPatternImages(677, 683, PixelTypes.Rgba32)]
public void Encode_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
PngColorType.Rgb,
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
appendPngColorType: true,
appendPixelType: true);
}
}
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
public void EncodeWorksWithoutSsse3Intrinsics(TestImageProvider<Rgba32> provider)
{
static void RunTest(string serialized)
{
TestImageProvider<Rgba32> provider =
FeatureTestRunner.DeserializeForXunit<TestImageProvider<Rgba32>>(serialized);
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
PngColorType.Rgb,
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
appendPngColorType: true,
appendPixelType: true);
}
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableHWIntrinsic,
provider);
}
[Fact]
public void EncodeFixesInvalidOptions()
{
// https://github.com/SixLabors/ImageSharp/issues/935
using MemoryStream ms = new();
TestFile testFile = TestFile.Create(TestImages.Png.Issue935);
using Image<Rgba32> image = testFile.CreateRgba32Image(PngDecoder.Instance);
image.Save(ms, new PngEncoder { ColorType = PngColorType.RgbWithAlpha });
}
// https://github.com/SixLabors/ImageSharp/issues/2469
[Theory]
[WithFile(TestImages.Png.Issue2469, PixelTypes.Rgba32)]
public void Issue2469_Quantized_Encode_Artifacts<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance);
PngEncoder encoder = new() { BitDepth = PngBitDepth.Bit8, ColorType = PngColorType.Palette };
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder);
using Image<Rgba32> encoded = Image.Load<Rgba32>(actualOutputFile);
encoded.CompareToReferenceOutput(ImageComparer.Exact, provider);
}
// https://github.com/SixLabors/ImageSharp/issues/2668
[Theory]
[WithFile(TestImages.Png.Issue2668, PixelTypes.Rgba32)]
public void Issue2668_Quantized_Encode_Alpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance);
image.Mutate(x => x.Resize(100, 100));
PngEncoder encoder = new() { BitDepth = PngBitDepth.Bit8, ColorType = PngColorType.Palette };
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder);
using Image<Rgba32> encoded = Image.Load<Rgba32>(actualOutputFile);
encoded.CompareToReferenceOutput(ImageComparer.Exact, provider);
}
[Fact]
public void Issue_2862()
{
// Create a grayscale palette (or any other palette with colors that are very close to each other):
Rgba32[] palette = [.. Enumerable.Range(0, 256).Select(i => new Rgba32((byte)i, (byte)i, (byte)i))];
using Image<Rgba32> image = new(254, 4);
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
image[x, y] = palette[x];
}
}
PaletteQuantizer quantizer = new(
palette.Select(Color.FromPixel).ToArray(),
new QuantizerOptions { ColorMatchingMode = ColorMatchingMode.Hybrid });
using MemoryStream ms = new();
image.Save(ms, new PngEncoder
{
ColorType = PngColorType.Palette,
BitDepth = PngBitDepth.Bit8,
Quantizer = quantizer
});
ms.Position = 0;
using Image<Rgba32> encoded = Image.Load<Rgba32>(ms);
ImageComparer.Exact.VerifySimilarity(image, encoded);
}
private static void TestPngEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
PngColorType pngColorType,
PngFilterMethod pngFilterMethod,
PngBitDepth bitDepth,
PngInterlaceMode interlaceMode,
PngCompressionLevel compressionLevel = PngCompressionLevel.DefaultCompression,
int paletteSize = 255,
bool appendPngColorType = false,
bool appendPngFilterMethod = false,
bool appendPixelType = false,
bool appendCompressionLevel = false,
bool appendPaletteSize = false,
bool appendPngBitDepth = false,
PngChunkFilter optimizeMethod = PngChunkFilter.None)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
PngEncoder encoder = new()
{
ColorType = pngColorType,
FilterMethod = pngFilterMethod,
CompressionLevel = compressionLevel,
BitDepth = bitDepth,
Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }),
InterlaceMethod = interlaceMode,
ChunkFilter = optimizeMethod,
};
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty;
string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty;
string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty;
string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty;
string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : string.Empty;
string debugInfo = pngColorTypeInfo + pngFilterMethodInfo + compressionLevelInfo + paletteSizeInfo + pngBitDepthInfo + pngInterlaceModeInfo;
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType);
// Compare to the Magick reference decoder.
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
// We compare using both our decoder and the reference decoder as pixel transformation
// occurs within the encoder itself leaving the input image unaffected.
// This means we are benefiting from testing our decoder also.
using FileStream fileStream = File.OpenRead(actualOutputFile);
using Image<TPixel> imageSharpImage = PngDecoder.Instance.Decode<TPixel>(DecoderOptions.Default, fileStream);
fileStream.Position = 0;
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, fileStream);
ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage);
}
}