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 716357877..558729ec8 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit 71635787778ba442087f326ec49a116ba19c7f60
+Subproject commit 558729ec87bcf52f22362175842f88a81ccfc483