From 2fccea58707b3d56c0a57858b94fa0858e5ada72 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 29 Apr 2018 01:22:46 +1000 Subject: [PATCH] Add the ability to choose the filter method to use when encoding a png --- .../Formats/Png/IPngEncoderOptions.cs | 5 ++ src/ImageSharp/Formats/Png/PngEncoder.cs | 7 ++- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 38 +++++++++++++-- src/ImageSharp/Formats/PngFilterMethod.cs | 46 +++++++++++++++++++ .../Formats/Png/PngEncoderTests.cs | 44 +++++++++++++----- tests/Images/External | 2 +- 6 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 src/ImageSharp/Formats/PngFilterMethod.cs diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 3f48c4e26..796a13a5e 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// PngColorType PngColorType { get; } + /// + /// Gets the png filter method. + /// + PngFilterMethod PngFilterMethod { get; } + /// /// Gets the compression level 1-9. /// Defaults to 6. diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 993dc6586..b39a6353e 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -14,10 +14,15 @@ namespace SixLabors.ImageSharp.Formats.Png public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions { /// - /// Gets or sets the png color type + /// Gets or sets the png color type. /// public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; + /// + /// Gets or sets the png filter method. + /// + public PngFilterMethod PngFilterMethod { get; set; } = PngFilterMethod.Adaptive; + /// /// Gets or sets the compression level 1-9. /// Defaults to 6. diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index b95e102c7..f17c9009a 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -46,6 +46,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// private readonly PngColorType pngColorType; + /// + /// The png filter method. + /// + private readonly PngFilterMethod pngFilterMethod; + /// /// The quantizer for reducing the color count. /// @@ -145,6 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Png { this.memoryManager = memoryManager; this.pngColorType = options.PngColorType; + this.pngFilterMethod = options.PngFilterMethod; this.compressionLevel = options.CompressionLevel; this.gamma = options.Gamma; this.quantizer = options.Quantizer; @@ -272,7 +278,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The pixel format. /// The row span. - private void CollecTPixelBytes(ReadOnlySpan rowSpan) + private void CollectTPixelBytes(ReadOnlySpan rowSpan) where TPixel : struct, IPixel { if (this.bytesPerPixel == 4) @@ -292,7 +298,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The row span. /// The row. - /// The + /// The private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, int row) where TPixel : struct, IPixel { @@ -307,11 +313,35 @@ namespace SixLabors.ImageSharp.Formats.Png this.CollectGrayscaleBytes(rowSpan); break; default: - this.CollecTPixelBytes(rowSpan); + this.CollectTPixelBytes(rowSpan); break; } - return this.GetOptimalFilteredScanline(); + switch (this.pngFilterMethod) + { + case PngFilterMethod.None: + NoneFilter.Encode(this.rawScanline.Span, this.result.Span); + return this.result; + + case PngFilterMethod.Sub: + SubFilter.Encode(this.rawScanline.Span, this.sub.Span, this.bytesPerPixel, out int _); + return this.sub; + + case PngFilterMethod.Up: + UpFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.up.Span, out int _); + return this.up; + + case PngFilterMethod.Average: + AverageFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.average.Span, this.bytesPerPixel, out int _); + return this.average; + + case PngFilterMethod.Paeth: + PaethFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.paeth.Span, this.bytesPerPixel, out int _); + return this.paeth; + + default: + return this.GetOptimalFilteredScanline(); + } } /// diff --git a/src/ImageSharp/Formats/PngFilterMethod.cs b/src/ImageSharp/Formats/PngFilterMethod.cs new file mode 100644 index 000000000..73c405625 --- /dev/null +++ b/src/ImageSharp/Formats/PngFilterMethod.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Provides enumeration of available PNG filter methods. + /// + public enum PngFilterMethod + { + /// + /// With the None filter, the scanline is transmitted unmodified. + /// + None, + + /// + /// The Sub filter transmits the difference between each byte and the value of the corresponding + /// byte of the prior pixel. + /// + Sub, + + /// + /// The Up filter is just like the filter except that the pixel immediately above the current pixel, + /// rather than just to its left, is used as the predictor. + /// + Up, + + /// + /// The Average filter uses the average of the two neighboring pixels (left and above) to predict the value of a pixel. + /// + Average, + + /// + /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), + /// then chooses as predictor the neighboring pixel closest to the computed value. + /// + Paeth, + + /// + /// Computes the output scanline using all five filters, and selects the filter that gives the smallest sum of + /// absolute values of outputs. + /// This method usually outperforms any single fixed filter choice. + /// + Adaptive, + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 017f217ac..e9ae98d10 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -32,21 +32,31 @@ namespace SixLabors.ImageSharp.Tests PngColorType.GrayscaleWithAlpha, }; + public static readonly TheoryData PngFilterMethods = new TheoryData + { + PngFilterMethod.None, + PngFilterMethod.Sub, + PngFilterMethod.Up, + PngFilterMethod.Average, + PngFilterMethod.Paeth, + PngFilterMethod.Adaptive + }; + /// /// 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 }; @@ -60,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) where TPixel : struct, IPixel { - TestPngEncoderCore(provider, pngColorType, appendPngColorType: true); + TestPngEncoderCore(provider, pngColorType, PngFilterMethod.Adaptive, appendPngColorType: true); } [Theory] @@ -68,7 +78,15 @@ namespace SixLabors.ImageSharp.Tests public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) where TPixel : struct, IPixel { - TestPngEncoderCore(provider, pngColorType, appendPixelType: true, appendPngColorType: true); + TestPngEncoderCore(provider, pngColorType, PngFilterMethod.Adaptive, appendPixelType: true, appendPngColorType: true); + } + + [Theory] + [WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)] + public void WorksWithAllFilterMethods(TestImageProvider provider, PngFilterMethod pngFilterMethod) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, pngFilterMethod, appendPngFilterMethod: true); } [Theory] @@ -76,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests public void WorksWithAllCompressionLevels(TestImageProvider provider, int compressionLevel) where TPixel : struct, IPixel { - TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, compressionLevel, appendCompressionLevel: true); + TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, PngFilterMethod.Adaptive, compressionLevel, appendCompressionLevel: true); } [Theory] @@ -84,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) where TPixel : struct, IPixel { - TestPngEncoderCore(provider, PngColorType.Palette, paletteSize: paletteSize, appendPaletteSize: true); + TestPngEncoderCore(provider, PngColorType.Palette, PngFilterMethod.Adaptive, paletteSize: paletteSize, appendPaletteSize: true); } private static bool HasAlpha(PngColorType pngColorType) => @@ -93,9 +111,11 @@ namespace SixLabors.ImageSharp.Tests private static void TestPngEncoderCore( TestImageProvider provider, PngColorType pngColorType, + PngFilterMethod pngFilterMethod, int compressionLevel = 6, int paletteSize = 255, bool appendPngColorType = false, + bool appendPngFilterMethod = false, bool appendPixelType = false, bool appendCompressionLevel = false, bool appendPaletteSize = false) @@ -111,14 +131,16 @@ namespace SixLabors.ImageSharp.Tests var encoder = new PngEncoder { PngColorType = pngColorType, + PngFilterMethod = pngFilterMethod, CompressionLevel = compressionLevel, Quantizer = new WuQuantizer(paletteSize) }; - string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : ""; - string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : ""; - string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : ""; - string debugInfo = $"{pngColorTypeInfo}{compressionLevelInfo}{paletteSizeInfo}"; + 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 debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}"; //string referenceInfo = $"{pngColorTypeInfo}"; // Does DebugSave & load reference CompareToReferenceInput(): diff --git a/tests/Images/External b/tests/Images/External index f1c585d0b..558729ec8 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit f1c585d0b931504d33ae2741ede72c0bf5ae5cb7 +Subproject commit 558729ec87bcf52f22362175842f88a81ccfc483