diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index e5d0b09cf..770afa3c5 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -59,7 +59,13 @@ namespace SixLabors.ImageSharp.Formats.Png PngInterlaceMode? InterlaceMethod { get; } /// - /// Gets chunk filter method. + /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. + /// When set to true, all ancillary chunks will be skipped. + /// + bool IgnoreMetadata { get; } + + /// + /// Gets the chunk filter method. This allows to filter ancillary chunks. /// PngChunkFilter? ChunkFilter { get; } diff --git a/src/ImageSharp/Formats/Png/PngChunkFilter.cs b/src/ImageSharp/Formats/Png/PngChunkFilter.cs index 04f27fb73..31d3012a6 100644 --- a/src/ImageSharp/Formats/Png/PngChunkFilter.cs +++ b/src/ImageSharp/Formats/Png/PngChunkFilter.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Png ExcludeTextChunks = 1 << 3, /// - /// All possible optimizations. + /// All ancillary chunks will be excluded. /// ExcludeAll = ~None } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 53cec6e4e..062e56c1d 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -34,22 +34,19 @@ namespace SixLabors.ImageSharp.Formats.Png /// public IQuantizer Quantizer { get; set; } - /// - /// Gets or sets the transparency threshold. - /// + /// public byte Threshold { get; set; } = byte.MaxValue; /// public PngInterlaceMode? InterlaceMethod { get; set; } - /// - /// Gets or sets the chunk filter. This can be used to exclude some ancillary chunks from being written. - /// + /// public PngChunkFilter? ChunkFilter { get; set; } - /// - /// Gets or sets a value indicating whether fully transparent pixels should be converted to black pixels. - /// + /// + public bool IgnoreMetadata { get; set; } + + /// public bool MakeTransparentBlack { get; set; } /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs index 2b43edd12..d0eb1a843 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs @@ -30,6 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.Threshold = source.Threshold; this.InterlaceMethod = source.InterlaceMethod; this.ChunkFilter = source.ChunkFilter; + this.IgnoreMetadata = source.IgnoreMetadata; this.MakeTransparentBlack = source.MakeTransparentBlack; } @@ -60,11 +61,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// public PngInterlaceMode? InterlaceMethod { get; set; } - /// - /// Gets or sets a the optimize method. - /// + /// public PngChunkFilter? ChunkFilter { get; set; } + /// + public bool IgnoreMetadata { get; set; } + /// public bool MakeTransparentBlack { get; set; } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index 3a8e528cc..52d7fe6d0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -40,6 +40,11 @@ namespace SixLabors.ImageSharp.Formats.Png use16Bit = options.BitDepth == PngBitDepth.Bit16; bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit); + if (options.IgnoreMetadata) + { + options.ChunkFilter = PngChunkFilter.ExcludeAll; + } + // Ensure we are not allowing impossible combinations. if (!PngConstants.ColorTypes.ContainsKey(options.ColorType.Value)) { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs index fa1544816..0418b36c8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs @@ -144,6 +144,58 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [Fact] + public void IgnoreMetadata_WillExcludeAllAncillaryChunks() + { + // arrange + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + var encoder = new PngEncoder() { IgnoreMetadata = true, TextCompressionThreshold = 8 }; + var expectedChunkTypes = new Dictionary() + { + { PngChunkType.Header, false }, + { PngChunkType.Palette, false }, + { PngChunkType.Data, false }, + { PngChunkType.End, false } + }; + var excludedChunkTypes = new List() + { + PngChunkType.Gamma, + PngChunkType.Exif, + PngChunkType.Physical, + PngChunkType.Text, + PngChunkType.InternationalText, + PngChunkType.CompressedText, + }; + + // act + input.Save(memStream, encoder); + + // assert + Assert.True(excludedChunkTypes.Count > 0); + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + while (bytesSpan.Length > 0) + { + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded"); + if (expectedChunkTypes.ContainsKey(chunkType)) + { + expectedChunkTypes[chunkType] = true; + } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + } + + // all expected chunk types should have been seen at least once. + foreach (PngChunkType chunkType in expectedChunkTypes.Keys) + { + Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once"); + } + } + [Theory] [InlineData(PngChunkFilter.ExcludeGammaChunk)] [InlineData(PngChunkFilter.ExcludeExifChunk)]