Skip to content

Commit 87103b7

Browse files
Merge pull request #550 from SixLabors/js/add-png-filter-options
Add the ability to choose the filter method to use when encoding a png
2 parents fcafc2e + 2fccea5 commit 87103b7

File tree

6 files changed

+125
-17
lines changed

6 files changed

+125
-17
lines changed

src/ImageSharp/Formats/Png/IPngEncoderOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ internal interface IPngEncoderOptions
1515
/// </summary>
1616
PngColorType PngColorType { get; }
1717

18+
/// <summary>
19+
/// Gets the png filter method.
20+
/// </summary>
21+
PngFilterMethod PngFilterMethod { get; }
22+
1823
/// <summary>
1924
/// Gets the compression level 1-9.
2025
/// <remarks>Defaults to 6.</remarks>

src/ImageSharp/Formats/Png/PngEncoder.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ namespace SixLabors.ImageSharp.Formats.Png
1414
public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
1515
{
1616
/// <summary>
17-
/// Gets or sets the png color type
17+
/// Gets or sets the png color type.
1818
/// </summary>
1919
public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
2020

21+
/// <summary>
22+
/// Gets or sets the png filter method.
23+
/// </summary>
24+
public PngFilterMethod PngFilterMethod { get; set; } = PngFilterMethod.Adaptive;
25+
2126
/// <summary>
2227
/// Gets or sets the compression level 1-9.
2328
/// <remarks>Defaults to 6.</remarks>

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ internal sealed class PngEncoderCore : IDisposable
4646
/// </summary>
4747
private readonly PngColorType pngColorType;
4848

49+
/// <summary>
50+
/// The png filter method.
51+
/// </summary>
52+
private readonly PngFilterMethod pngFilterMethod;
53+
4954
/// <summary>
5055
/// The quantizer for reducing the color count.
5156
/// </summary>
@@ -145,6 +150,7 @@ public PngEncoderCore(MemoryManager memoryManager, IPngEncoderOptions options)
145150
{
146151
this.memoryManager = memoryManager;
147152
this.pngColorType = options.PngColorType;
153+
this.pngFilterMethod = options.PngFilterMethod;
148154
this.compressionLevel = options.CompressionLevel;
149155
this.gamma = options.Gamma;
150156
this.quantizer = options.Quantizer;
@@ -272,7 +278,7 @@ private void CollectGrayscaleBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
272278
/// </summary>
273279
/// <typeparam name="TPixel">The pixel format.</typeparam>
274280
/// <param name="rowSpan">The row span.</param>
275-
private void CollecTPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
281+
private void CollectTPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
276282
where TPixel : struct, IPixel<TPixel>
277283
{
278284
if (this.bytesPerPixel == 4)
@@ -292,7 +298,7 @@ private void CollecTPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
292298
/// <typeparam name="TPixel">The pixel format.</typeparam>
293299
/// <param name="rowSpan">The row span.</param>
294300
/// <param name="row">The row.</param>
295-
/// <returns>The <see cref="T:byte[]"/></returns>
301+
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
296302
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, int row)
297303
where TPixel : struct, IPixel<TPixel>
298304
{
@@ -307,11 +313,35 @@ private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan,
307313
this.CollectGrayscaleBytes(rowSpan);
308314
break;
309315
default:
310-
this.CollecTPixelBytes(rowSpan);
316+
this.CollectTPixelBytes(rowSpan);
311317
break;
312318
}
313319

314-
return this.GetOptimalFilteredScanline();
320+
switch (this.pngFilterMethod)
321+
{
322+
case PngFilterMethod.None:
323+
NoneFilter.Encode(this.rawScanline.Span, this.result.Span);
324+
return this.result;
325+
326+
case PngFilterMethod.Sub:
327+
SubFilter.Encode(this.rawScanline.Span, this.sub.Span, this.bytesPerPixel, out int _);
328+
return this.sub;
329+
330+
case PngFilterMethod.Up:
331+
UpFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.up.Span, out int _);
332+
return this.up;
333+
334+
case PngFilterMethod.Average:
335+
AverageFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.average.Span, this.bytesPerPixel, out int _);
336+
return this.average;
337+
338+
case PngFilterMethod.Paeth:
339+
PaethFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.paeth.Span, this.bytesPerPixel, out int _);
340+
return this.paeth;
341+
342+
default:
343+
return this.GetOptimalFilteredScanline();
344+
}
315345
}
316346

317347
/// <summary>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Formats
5+
{
6+
/// <summary>
7+
/// Provides enumeration of available PNG filter methods.
8+
/// </summary>
9+
public enum PngFilterMethod
10+
{
11+
/// <summary>
12+
/// With the None filter, the scanline is transmitted unmodified.
13+
/// </summary>
14+
None,
15+
16+
/// <summary>
17+
/// The Sub filter transmits the difference between each byte and the value of the corresponding
18+
/// byte of the prior pixel.
19+
/// </summary>
20+
Sub,
21+
22+
/// <summary>
23+
/// The Up filter is just like the <see cref="Sub"/> filter except that the pixel immediately above the current pixel,
24+
/// rather than just to its left, is used as the predictor.
25+
/// </summary>
26+
Up,
27+
28+
/// <summary>
29+
/// The Average filter uses the average of the two neighboring pixels (left and above) to predict the value of a pixel.
30+
/// </summary>
31+
Average,
32+
33+
/// <summary>
34+
/// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left),
35+
/// then chooses as predictor the neighboring pixel closest to the computed value.
36+
/// </summary>
37+
Paeth,
38+
39+
/// <summary>
40+
/// Computes the output scanline using all five filters, and selects the filter that gives the smallest sum of
41+
/// absolute values of outputs.
42+
/// This method usually outperforms any single fixed filter choice.
43+
/// </summary>
44+
Adaptive,
45+
}
46+
}

tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,31 @@ public class PngEncoderTests
3232
PngColorType.GrayscaleWithAlpha,
3333
};
3434

35+
public static readonly TheoryData<PngFilterMethod> PngFilterMethods = new TheoryData<PngFilterMethod>
36+
{
37+
PngFilterMethod.None,
38+
PngFilterMethod.Sub,
39+
PngFilterMethod.Up,
40+
PngFilterMethod.Average,
41+
PngFilterMethod.Paeth,
42+
PngFilterMethod.Adaptive
43+
};
44+
3545
/// <summary>
3646
/// All types except Palette
3747
/// </summary>
3848
public static readonly TheoryData<int> CompressionLevels = new TheoryData<int>
39-
{
49+
{
4050
1, 2, 3, 4, 5, 6, 7, 8, 9
4151
};
4252

4353
public static readonly TheoryData<int> PaletteSizes = new TheoryData<int>
44-
{
54+
{
4555
30, 55, 100, 201, 255
4656
};
4757

4858
public static readonly TheoryData<int> PaletteLargeOnly = new TheoryData<int>
49-
{
59+
{
5060
80, 100, 120, 230
5161
};
5262

@@ -60,31 +70,39 @@ public class PngEncoderTests
6070
public void WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType)
6171
where TPixel : struct, IPixel<TPixel>
6272
{
63-
TestPngEncoderCore(provider, pngColorType, appendPngColorType: true);
73+
TestPngEncoderCore(provider, pngColorType, PngFilterMethod.Adaptive, appendPngColorType: true);
6474
}
6575

6676
[Theory]
6777
[WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)]
6878
public void IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType)
6979
where TPixel : struct, IPixel<TPixel>
7080
{
71-
TestPngEncoderCore(provider, pngColorType, appendPixelType: true, appendPngColorType: true);
81+
TestPngEncoderCore(provider, pngColorType, PngFilterMethod.Adaptive, appendPixelType: true, appendPngColorType: true);
82+
}
83+
84+
[Theory]
85+
[WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)]
86+
public void WorksWithAllFilterMethods<TPixel>(TestImageProvider<TPixel> provider, PngFilterMethod pngFilterMethod)
87+
where TPixel : struct, IPixel<TPixel>
88+
{
89+
TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, pngFilterMethod, appendPngFilterMethod: true);
7290
}
7391

7492
[Theory]
7593
[WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)]
7694
public void WorksWithAllCompressionLevels<TPixel>(TestImageProvider<TPixel> provider, int compressionLevel)
7795
where TPixel : struct, IPixel<TPixel>
7896
{
79-
TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, compressionLevel, appendCompressionLevel: true);
97+
TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, PngFilterMethod.Adaptive, compressionLevel, appendCompressionLevel: true);
8098
}
8199

82100
[Theory]
83101
[WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)]
84102
public void PaletteColorType_WuQuantizer<TPixel>(TestImageProvider<TPixel> provider, int paletteSize)
85103
where TPixel : struct, IPixel<TPixel>
86104
{
87-
TestPngEncoderCore(provider, PngColorType.Palette, paletteSize: paletteSize, appendPaletteSize: true);
105+
TestPngEncoderCore(provider, PngColorType.Palette, PngFilterMethod.Adaptive, paletteSize: paletteSize, appendPaletteSize: true);
88106
}
89107

90108
private static bool HasAlpha(PngColorType pngColorType) =>
@@ -93,9 +111,11 @@ private static bool HasAlpha(PngColorType pngColorType) =>
93111
private static void TestPngEncoderCore<TPixel>(
94112
TestImageProvider<TPixel> provider,
95113
PngColorType pngColorType,
114+
PngFilterMethod pngFilterMethod,
96115
int compressionLevel = 6,
97116
int paletteSize = 255,
98117
bool appendPngColorType = false,
118+
bool appendPngFilterMethod = false,
99119
bool appendPixelType = false,
100120
bool appendCompressionLevel = false,
101121
bool appendPaletteSize = false)
@@ -111,14 +131,16 @@ private static void TestPngEncoderCore<TPixel>(
111131
var encoder = new PngEncoder
112132
{
113133
PngColorType = pngColorType,
134+
PngFilterMethod = pngFilterMethod,
114135
CompressionLevel = compressionLevel,
115136
Quantizer = new WuQuantizer(paletteSize)
116137
};
117138

118-
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : "";
119-
string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : "";
120-
string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : "";
121-
string debugInfo = $"{pngColorTypeInfo}{compressionLevelInfo}{paletteSizeInfo}";
139+
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
140+
string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty;
141+
string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty;
142+
string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty;
143+
string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}";
122144
//string referenceInfo = $"{pngColorTypeInfo}";
123145

124146
// Does DebugSave & load reference CompareToReferenceInput():

tests/Images/External

Submodule External updated 23 files

0 commit comments

Comments
 (0)