diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
index d8af4c326..f5113d3d9 100644
--- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
@@ -61,6 +61,6 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Gets the optimize method.
///
- PngChunkFilter? OptimizeMethod { get; }
+ PngChunkFilter? ChunkFilter { get; }
}
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs
index c417ea872..d2eba47de 100644
--- a/src/ImageSharp/Formats/Png/PngEncoder.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoder.cs
@@ -63,9 +63,9 @@ namespace SixLabors.ImageSharp.Formats.Png
public PngInterlaceMode? InterlaceMethod { get; set; }
///
- /// Gets or sets the optimize method.
+ /// Gets or sets the chunk filter. This can be used to exclude some ancillary chunks from being written.
///
- public PngChunkFilter? OptimizeMethod { get; set; }
+ public PngChunkFilter? ChunkFilter { get; set; }
///
/// Encodes the image to the specified stream from the .
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 89c0b7f65..1af5929fe 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.Png
PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
IndexedImageFrame quantized;
- if (((this.options.OptimizeMethod ?? PngChunkFilter.None) & PngChunkFilter.MakeTransparentBlack) == PngChunkFilter.MakeTransparentBlack)
+ if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.MakeTransparentBlack) == PngChunkFilter.MakeTransparentBlack)
{
using (Image tempImage = image.Clone())
{
@@ -182,7 +182,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WriteHeaderChunk(stream);
- if (((this.options.OptimizeMethod ?? PngChunkFilter.None) & PngChunkFilter.ExcludeGammaChunk) != PngChunkFilter.ExcludeGammaChunk)
+ if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeGammaChunk) != PngChunkFilter.ExcludeGammaChunk)
{
this.WriteGammaChunk(stream);
}
@@ -190,17 +190,17 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePaletteChunk(stream, quantized);
this.WriteTransparencyChunk(stream, pngMetadata);
- if (((this.options.OptimizeMethod ?? PngChunkFilter.None) & PngChunkFilter.ExcludePhysicalChunk) != PngChunkFilter.ExcludePhysicalChunk)
+ if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludePhysicalChunk) != PngChunkFilter.ExcludePhysicalChunk)
{
this.WritePhysicalChunk(stream, metadata);
}
- if (((this.options.OptimizeMethod ?? PngChunkFilter.None) & PngChunkFilter.ExcludeExifChunk) != PngChunkFilter.ExcludeExifChunk)
+ if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeExifChunk) != PngChunkFilter.ExcludeExifChunk)
{
this.WriteExifChunk(stream, metadata);
}
- if (((this.options.OptimizeMethod ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) != PngChunkFilter.ExcludeTextChunks)
+ if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) != PngChunkFilter.ExcludeTextChunks)
{
this.WriteTextChunks(stream, pngMetadata);
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
index 4728b7ca8..f11a23269 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.Quantizer = source.Quantizer;
this.Threshold = source.Threshold;
this.InterlaceMethod = source.InterlaceMethod;
- this.OptimizeMethod = source.OptimizeMethod;
+ this.ChunkFilter = source.ChunkFilter;
}
///
@@ -83,6 +83,6 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Gets or sets a the optimize method.
///
- public PngChunkFilter? OptimizeMethod { get; set; }
+ public PngChunkFilter? ChunkFilter { get; set; }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
new file mode 100644
index 000000000..9d28fd89b
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs
@@ -0,0 +1,242 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.PixelFormats;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Tests.Formats.Png
+{
+ public partial class PngEncoderTests
+ {
+ [Fact]
+ public void HeaderChunk_ComesFirst()
+ {
+ // arrange
+ var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ using Image input = testFile.CreateRgba32Image();
+ using var memStream = new MemoryStream();
+
+ // act
+ input.Save(memStream, PngEncoder);
+
+ // assert
+ memStream.Position = 0;
+ Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
+ BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
+ var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ Assert.Equal(PngChunkType.Header, type);
+ }
+
+ [Fact]
+ public void EndChunk_IsLast()
+ {
+ // arrange
+ var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ using Image input = testFile.CreateRgba32Image();
+ using var memStream = new MemoryStream();
+
+ // act
+ input.Save(memStream, PngEncoder);
+
+ // assert
+ memStream.Position = 0;
+ Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
+ bool endChunkFound = false;
+ while (bytesSpan.Length > 0)
+ {
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
+ var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ Assert.False(endChunkFound);
+ if (type == PngChunkType.End)
+ {
+ endChunkFound = true;
+ }
+
+ bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ }
+ }
+
+ [Theory]
+ [InlineData(PngChunkType.Gamma)]
+ [InlineData(PngChunkType.Chroma)]
+ [InlineData(PngChunkType.EmbeddedColorProfile)]
+ [InlineData(PngChunkType.SignificantBits)]
+ [InlineData(PngChunkType.StandardRgbColourSpace)]
+ public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj)
+ {
+ // arrange
+ var chunkType = (PngChunkType)chunkTypeObj;
+ var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ using Image input = testFile.CreateRgba32Image();
+ using var memStream = new MemoryStream();
+
+ // act
+ input.Save(memStream, PngEncoder);
+
+ // assert
+ memStream.Position = 0;
+ Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
+ bool palFound = false;
+ bool dataFound = false;
+ while (bytesSpan.Length > 0)
+ {
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
+ var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ if (chunkType == type)
+ {
+ Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk");
+ }
+
+ switch (type)
+ {
+ case PngChunkType.Data:
+ dataFound = true;
+ break;
+ case PngChunkType.Palette:
+ palFound = true;
+ break;
+ }
+
+ bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ }
+ }
+
+ [Theory]
+ [InlineData(PngChunkType.Physical)]
+ [InlineData(PngChunkType.SuggestedPalette)]
+ public void Chunk_ComesBeforeIDat(object chunkTypeObj)
+ {
+ // arrange
+ var chunkType = (PngChunkType)chunkTypeObj;
+ var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ using Image input = testFile.CreateRgba32Image();
+ using var memStream = new MemoryStream();
+
+ // act
+ input.Save(memStream, PngEncoder);
+
+ // assert
+ memStream.Position = 0;
+ Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header.
+ bool dataFound = false;
+ while (bytesSpan.Length > 0)
+ {
+ int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
+ var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
+ if (chunkType == type)
+ {
+ Assert.False(dataFound, $"{chunkType} chunk should come before data chunk");
+ }
+
+ if (type == PngChunkType.Data)
+ {
+ dataFound = true;
+ }
+
+ bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ }
+ }
+
+ [Theory]
+ [InlineData(PngChunkFilter.ExcludeGammaChunk)]
+ [InlineData(PngChunkFilter.ExcludeExifChunk)]
+ [InlineData(PngChunkFilter.ExcludePhysicalChunk)]
+ [InlineData(PngChunkFilter.ExcludeTextChunks)]
+ [InlineData(PngChunkFilter.ExcludeAll)]
+ public void ExcludeFilter_Works(object filterObj)
+ {
+ // arrange
+ var chunkFilter = (PngChunkFilter)filterObj;
+ var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ using Image input = testFile.CreateRgba32Image();
+ using var memStream = new MemoryStream();
+ var encoder = new PngEncoder() { ChunkFilter = chunkFilter, TextCompressionThreshold = 8 };
+ var excludedChunkTypes = new List();
+ switch (chunkFilter)
+ {
+ case PngChunkFilter.ExcludeGammaChunk:
+ excludedChunkTypes.Add(PngChunkType.Gamma);
+ break;
+ case PngChunkFilter.ExcludeExifChunk:
+ excludedChunkTypes.Add(PngChunkType.Exif);
+ break;
+ case PngChunkFilter.ExcludePhysicalChunk:
+ excludedChunkTypes.Add(PngChunkType.Physical);
+ break;
+ case PngChunkFilter.ExcludeTextChunks:
+ excludedChunkTypes.Add(PngChunkType.Text);
+ excludedChunkTypes.Add(PngChunkType.InternationalText);
+ excludedChunkTypes.Add(PngChunkType.CompressedText);
+ break;
+ case PngChunkFilter.ExcludeAll:
+ excludedChunkTypes.Add(PngChunkType.Gamma);
+ excludedChunkTypes.Add(PngChunkType.Exif);
+ excludedChunkTypes.Add(PngChunkType.Physical);
+ excludedChunkTypes.Add(PngChunkType.Text);
+ excludedChunkTypes.Add(PngChunkType.InternationalText);
+ excludedChunkTypes.Add(PngChunkType.CompressedText);
+ break;
+ }
+
+ // 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");
+
+ bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ }
+ }
+
+ [Fact]
+ public void ExcludeFilter_WithNone_DoesNotExcludeChunks()
+ {
+ // arrange
+ var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
+ using Image input = testFile.CreateRgba32Image();
+ using var memStream = new MemoryStream();
+ var encoder = new PngEncoder() { ChunkFilter = PngChunkFilter.None, TextCompressionThreshold = 8 };
+ var expectedChunkTypes = new List()
+ {
+ PngChunkType.Header,
+ PngChunkType.Gamma,
+ PngChunkType.Palette,
+ PngChunkType.Transparency,
+ PngChunkType.InternationalText,
+ PngChunkType.Text,
+ PngChunkType.CompressedText,
+ PngChunkType.Exif,
+ PngChunkType.Physical,
+ PngChunkType.Data,
+ PngChunkType.End,
+ };
+
+ // act
+ input.Save(memStream, encoder);
+ 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.True(expectedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been present");
+
+ bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index e2051ea27..cf5f5c4db 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
-using System;
-using System.Buffers.Binary;
using System.IO;
using System.Linq;
@@ -18,7 +16,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
- public class PngEncoderTests
+ public partial class PngEncoderTests
{
private static PngEncoder PngEncoder => new PngEncoder();
@@ -221,7 +219,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[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 WorksWithAllBitDepthsOptimized(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
+ public void WorksWithAllBitDepthsAndExcludeAllFilter(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
where TPixel : unmanaged, IPixel
{
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
@@ -435,126 +433,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
- [Fact]
- public void HeaderChunk_ComesFirst()
- {
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
- using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
- input.Save(memStream, PngEncoder);
- memStream.Position = 0;
-
- // Skip header.
- Span bytesSpan = memStream.ToArray().AsSpan(8);
- BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
- Assert.Equal(PngChunkType.Header, type);
- }
-
- [Fact]
- public void EndChunk_IsLast()
- {
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
- using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
- input.Save(memStream, PngEncoder);
- memStream.Position = 0;
-
- // Skip header.
- Span bytesSpan = memStream.ToArray().AsSpan(8);
-
- bool endChunkFound = false;
- while (bytesSpan.Length > 0)
- {
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
- Assert.False(endChunkFound);
- if (type == PngChunkType.End)
- {
- endChunkFound = true;
- }
-
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
- }
- }
-
- [Theory]
- [InlineData(PngChunkType.Gamma)]
- [InlineData(PngChunkType.Chroma)]
- [InlineData(PngChunkType.EmbeddedColorProfile)]
- [InlineData(PngChunkType.SignificantBits)]
- [InlineData(PngChunkType.StandardRgbColourSpace)]
- public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj)
- {
- var chunkType = (PngChunkType)chunkTypeObj;
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
- using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
- input.Save(memStream, PngEncoder);
- memStream.Position = 0;
-
- // Skip header.
- Span bytesSpan = memStream.ToArray().AsSpan(8);
-
- bool palFound = false;
- bool dataFound = false;
- while (bytesSpan.Length > 0)
- {
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
- if (chunkType == type)
- {
- Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk");
- }
-
- switch (type)
- {
- case PngChunkType.Data:
- dataFound = true;
- break;
- case PngChunkType.Palette:
- palFound = true;
- break;
- }
-
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
- }
- }
-
- [Theory]
- [InlineData(PngChunkType.Physical)]
- [InlineData(PngChunkType.SuggestedPalette)]
- public void Chunk_ComesBeforeIDat(object chunkTypeObj)
- {
- var chunkType = (PngChunkType)chunkTypeObj;
- var testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
- using Image input = testFile.CreateRgba32Image();
- using var memStream = new MemoryStream();
- input.Save(memStream, PngEncoder);
- memStream.Position = 0;
-
- // Skip header.
- Span bytesSpan = memStream.ToArray().AsSpan(8);
-
- bool dataFound = false;
- while (bytesSpan.Length > 0)
- {
- int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4));
- var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4));
- if (chunkType == type)
- {
- Assert.False(dataFound, $"{chunkType} chunk should come before data chunk");
- }
-
- if (type == PngChunkType.Data)
- {
- dataFound = true;
- }
-
- bytesSpan = bytesSpan.Slice(4 + 4 + length + 4);
- }
- }
-
[Theory]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32)]
[WithTestPatternImages(677, 683, PixelTypes.Rgba32)]
@@ -602,7 +480,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
BitDepth = bitDepth,
Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }),
InterlaceMethod = interlaceMode,
- OptimizeMethod = optimizeMethod,
+ ChunkFilter = optimizeMethod,
};
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;