Skip to content

Commit 966dd46

Browse files
Merge pull request #1019 from Sheyne/master
Allow inferring of some PngEncoderOptions
2 parents 7859e71 + 819b2fa commit 966dd46

File tree

5 files changed

+129
-26
lines changed

5 files changed

+129
-26
lines changed

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
144144
this.height = image.Height;
145145

146146
ImageMetadata metadata = image.Metadata;
147-
PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
148-
PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
147+
PngMetadata pngMetadata = metadata.GetPngMetadata();
148+
PngEncoderOptionsHelpers.AdjustOptions<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
149149
IQuantizedFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
150150
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);
151151

src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,28 @@ namespace SixLabors.ImageSharp.Formats.Png
1414
internal static class PngEncoderOptionsHelpers
1515
{
1616
/// <summary>
17-
/// Adjusts the options.
17+
/// Adjusts the options based upon the given metadata.
1818
/// </summary>
1919
/// <param name="options">The options.</param>
2020
/// <param name="pngMetadata">The PNG metadata.</param>
2121
/// <param name="use16Bit">if set to <c>true</c> [use16 bit].</param>
2222
/// <param name="bytesPerPixel">The bytes per pixel.</param>
23-
public static void AdjustOptions(
23+
public static void AdjustOptions<TPixel>(
2424
PngEncoderOptions options,
2525
PngMetadata pngMetadata,
2626
out bool use16Bit,
2727
out int bytesPerPixel)
28+
where TPixel : struct, IPixel<TPixel>
2829
{
2930
// Always take the encoder options over the metadata values.
30-
options.Gamma = options.Gamma ?? pngMetadata.Gamma;
31-
options.ColorType = options.ColorType ?? pngMetadata.ColorType;
32-
options.BitDepth = options.BitDepth ?? pngMetadata.BitDepth;
33-
options.InterlaceMethod = options.InterlaceMethod ?? pngMetadata.InterlaceMethod;
31+
options.Gamma ??= pngMetadata.Gamma;
32+
33+
// Use options, then check metadata, if nothing set there then we suggest
34+
// a sensible default based upon the pixel format.
35+
options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType<TPixel>();
36+
options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth<TPixel>();
37+
38+
options.InterlaceMethod ??= pngMetadata.InterlaceMethod;
3439

3540
use16Bit = options.BitDepth == PngBitDepth.Bit16;
3641
bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit);
@@ -129,24 +134,68 @@ public static byte CalculateBitDepth<TPixel>(
129134
/// <returns>Bytes per pixel.</returns>
130135
private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit)
131136
{
132-
switch (pngColorType)
137+
return pngColorType switch
133138
{
134-
case PngColorType.Grayscale:
135-
return use16Bit ? 2 : 1;
139+
PngColorType.Grayscale => use16Bit ? 2 : 1,
140+
PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2,
141+
PngColorType.Palette => 1,
142+
PngColorType.Rgb => use16Bit ? 6 : 3,
136143

137-
case PngColorType.GrayscaleWithAlpha:
138-
return use16Bit ? 4 : 2;
139-
140-
case PngColorType.Palette:
141-
return 1;
144+
// PngColorType.RgbWithAlpha
145+
_ => use16Bit ? 8 : 4,
146+
};
147+
}
142148

143-
case PngColorType.Rgb:
144-
return use16Bit ? 6 : 3;
149+
/// <summary>
150+
/// Returns a suggested <see cref="PngColorType"/> for the given <typeparamref name="TPixel"/>
151+
/// This is not exhaustive but covers many common pixel formats.
152+
/// </summary>
153+
private static PngColorType SuggestColorType<TPixel>()
154+
where TPixel : struct, IPixel<TPixel>
155+
{
156+
return typeof(TPixel) switch
157+
{
158+
Type t when t == typeof(A8) => PngColorType.GrayscaleWithAlpha,
159+
Type t when t == typeof(Argb32) => PngColorType.RgbWithAlpha,
160+
Type t when t == typeof(Bgr24) => PngColorType.Rgb,
161+
Type t when t == typeof(Bgra32) => PngColorType.RgbWithAlpha,
162+
Type t when t == typeof(L8) => PngColorType.Grayscale,
163+
Type t when t == typeof(L16) => PngColorType.Grayscale,
164+
Type t when t == typeof(La16) => PngColorType.GrayscaleWithAlpha,
165+
Type t when t == typeof(La32) => PngColorType.GrayscaleWithAlpha,
166+
Type t when t == typeof(Rgb24) => PngColorType.Rgb,
167+
Type t when t == typeof(Rgba32) => PngColorType.RgbWithAlpha,
168+
Type t when t == typeof(Rgb48) => PngColorType.Rgb,
169+
Type t when t == typeof(Rgba64) => PngColorType.RgbWithAlpha,
170+
Type t when t == typeof(RgbaVector) => PngColorType.RgbWithAlpha,
171+
_ => PngColorType.RgbWithAlpha
172+
};
173+
}
145174

146-
// PngColorType.RgbWithAlpha
147-
default:
148-
return use16Bit ? 8 : 4;
149-
}
175+
/// <summary>
176+
/// Returns a suggested <see cref="PngBitDepth"/> for the given <typeparamref name="TPixel"/>
177+
/// This is not exhaustive but covers many common pixel formats.
178+
/// </summary>
179+
private static PngBitDepth SuggestBitDepth<TPixel>()
180+
where TPixel : struct, IPixel<TPixel>
181+
{
182+
return typeof(TPixel) switch
183+
{
184+
Type t when t == typeof(A8) => PngBitDepth.Bit8,
185+
Type t when t == typeof(Argb32) => PngBitDepth.Bit8,
186+
Type t when t == typeof(Bgr24) => PngBitDepth.Bit8,
187+
Type t when t == typeof(Bgra32) => PngBitDepth.Bit8,
188+
Type t when t == typeof(L8) => PngBitDepth.Bit8,
189+
Type t when t == typeof(L16) => PngBitDepth.Bit16,
190+
Type t when t == typeof(La16) => PngBitDepth.Bit8,
191+
Type t when t == typeof(La32) => PngBitDepth.Bit16,
192+
Type t when t == typeof(Rgb24) => PngBitDepth.Bit8,
193+
Type t when t == typeof(Rgba32) => PngBitDepth.Bit8,
194+
Type t when t == typeof(Rgb48) => PngBitDepth.Bit16,
195+
Type t when t == typeof(Rgba64) => PngBitDepth.Bit16,
196+
Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16,
197+
_ => PngBitDepth.Bit8
198+
};
150199
}
151200
}
152201
}

src/ImageSharp/Formats/Png/PngMetadata.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ private PngMetadata(PngMetadata other)
4444
/// Gets or sets the number of bits per sample or per palette index (not per pixel).
4545
/// Not all values are allowed for all <see cref="ColorType"/> values.
4646
/// </summary>
47-
public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
47+
public PngBitDepth? BitDepth { get; set; }
4848

4949
/// <summary>
5050
/// Gets or sets the color type.
5151
/// </summary>
52-
public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
52+
public PngColorType? ColorType { get; set; }
5353

5454
/// <summary>
5555
/// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image.

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,54 @@ public void WorksWithAllBitDepths<TPixel>(TestImageProvider<TPixel> provider, Pn
201201
}
202202
}
203203

204+
[Theory]
205+
[WithBlankImages(1, 1, PixelTypes.A8, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
206+
[WithBlankImages(1, 1, PixelTypes.Argb32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
207+
[WithBlankImages(1, 1, PixelTypes.Bgr565, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
208+
[WithBlankImages(1, 1, PixelTypes.Bgra4444, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
209+
[WithBlankImages(1, 1, PixelTypes.Byte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
210+
[WithBlankImages(1, 1, PixelTypes.HalfSingle, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
211+
[WithBlankImages(1, 1, PixelTypes.HalfVector2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
212+
[WithBlankImages(1, 1, PixelTypes.HalfVector4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
213+
[WithBlankImages(1, 1, PixelTypes.NormalizedByte2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
214+
[WithBlankImages(1, 1, PixelTypes.NormalizedByte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
215+
[WithBlankImages(1, 1, PixelTypes.NormalizedShort4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
216+
[WithBlankImages(1, 1, PixelTypes.Rg32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
217+
[WithBlankImages(1, 1, PixelTypes.Rgba1010102, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
218+
[WithBlankImages(1, 1, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
219+
[WithBlankImages(1, 1, PixelTypes.RgbaVector, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
220+
[WithBlankImages(1, 1, PixelTypes.Short2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
221+
[WithBlankImages(1, 1, PixelTypes.Short4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
222+
[WithBlankImages(1, 1, PixelTypes.Rgb24, PngColorType.Rgb, PngBitDepth.Bit8)]
223+
[WithBlankImages(1, 1, PixelTypes.Bgr24, PngColorType.Rgb, PngBitDepth.Bit8)]
224+
[WithBlankImages(1, 1, PixelTypes.Bgra32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
225+
[WithBlankImages(1, 1, PixelTypes.Rgb48, PngColorType.Rgb, PngBitDepth.Bit16)]
226+
[WithBlankImages(1, 1, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)]
227+
[WithBlankImages(1, 1, PixelTypes.Bgra5551, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)]
228+
[WithBlankImages(1, 1, PixelTypes.L8, PngColorType.Grayscale, PngBitDepth.Bit8)]
229+
[WithBlankImages(1, 1, PixelTypes.L16, PngColorType.Grayscale, PngBitDepth.Bit16)]
230+
[WithBlankImages(1, 1, PixelTypes.La16, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)]
231+
[WithBlankImages(1, 1, PixelTypes.La32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)]
232+
public void InfersColorTypeAndBitDepth<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType, PngBitDepth pngBitDepth)
233+
where TPixel : struct, IPixel<TPixel>
234+
{
235+
using (Stream stream = new MemoryStream())
236+
{
237+
var encoder = new PngEncoder();
238+
encoder.Encode(provider.GetImage(), stream);
239+
240+
stream.Seek(0, SeekOrigin.Begin);
241+
242+
var decoder = new PngDecoder();
243+
244+
Image image = decoder.Decode(Configuration.Default, stream);
245+
246+
PngMetadata metadata = image.Metadata.GetPngMetadata();
247+
Assert.Equal(pngColorType, metadata.ColorType);
248+
Assert.Equal(pngBitDepth, metadata.BitDepth);
249+
}
250+
}
251+
204252
[Theory]
205253
[WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)]
206254
public void PaletteColorType_WuQuantizer<TPixel>(TestImageProvider<TPixel> provider, int paletteSize)

tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
@@ -62,9 +62,15 @@ public enum PixelTypes
6262

6363
L8 = 1 << 23,
6464

65+
L16 = 1 << 24,
66+
67+
La16 = 1 << 25,
68+
69+
La32 = 1 << 26,
70+
6571
// TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper
6672

6773
// "All" is handled as a separate, individual case instead of using bitwise OR
6874
All = 30
6975
}
70-
}
76+
}

0 commit comments

Comments
 (0)