From f7a6cd2ffad70294f8c6b321d2f2ec8c00d786a7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 29 Apr 2020 14:05:48 +0200 Subject: [PATCH] Add tests for exclude filter --- .../Formats/Png/IPngEncoderOptions.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoder.cs | 4 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 10 +- .../Formats/Png/PngEncoderOptions.cs | 4 +- .../Formats/Png/PngEncoderTests.Chunks.cs | 242 ++++++++++++++++++ .../Formats/Png/PngEncoderTests.cs | 128 +-------- 6 files changed, 255 insertions(+), 135 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs 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;