diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
index f3231fa22..77bc9f7a0 100644
--- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
@@ -14,12 +14,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all values.
///
- PngBitDepth BitDepth { get; }
+ PngBitDepth? BitDepth { get; }
///
/// Gets the color type
///
- PngColorType ColorType { get; }
+ PngColorType? ColorType { get; }
///
/// Gets the filter method.
@@ -33,15 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Png
int CompressionLevel { get; }
///
- /// Gets the gamma value, that will be written
- /// the the stream, when the property
- /// is set to true. The default value is 2.2F.
+ /// Gets the gamma value, that will be written the the image.
///
/// The gamma value of the image.
- float Gamma { get; }
+ float? Gamma { get; }
///
- /// Gets quantizer for reducing the color count.
+ /// Gets the quantizer for reducing the color count.
///
IQuantizer Quantizer { get; }
@@ -49,11 +47,5 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the transparency threshold.
///
byte Threshold { get; }
-
- ///
- /// Gets a value indicating whether this instance should write
- /// gamma information to the stream. The default value is false.
- ///
- bool WriteGamma { get; }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs
index e0844ca6b..7654c1701 100644
--- a/src/ImageSharp/Formats/Png/PngChunkType.cs
+++ b/src/ImageSharp/Formats/Png/PngChunkType.cs
@@ -8,58 +8,58 @@ namespace SixLabors.ImageSharp.Formats.Png
///
internal enum PngChunkType : uint
{
- ///
- /// The first chunk in a png file. Can only exists once. Contains
- /// common information like the width and the height of the image or
- /// the used compression method.
- ///
- Header = 0x49484452U, // IHDR
-
- ///
- /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
- /// series in the RGB format.
- ///
- Palette = 0x504C5445U, // PLTE
-
///
/// The IDAT chunk contains the actual image data. The image can contains more
/// than one chunk of this type. All chunks together are the whole image.
///
- Data = 0x49444154U, // IDAT
+ Data = 0x49444154U,
///
/// This chunk must appear last. It marks the end of the PNG data stream.
/// The chunk's data field is empty.
///
- End = 0x49454E44U, // IEND
+ End = 0x49454E44U,
///
- /// This chunk specifies that the image uses simple transparency:
- /// either alpha values associated with palette entries (for indexed-color images)
- /// or a single transparent color (for grayscale and true color images).
+ /// The first chunk in a png file. Can only exists once. Contains
+ /// common information like the width and the height of the image or
+ /// the used compression method.
///
- PaletteAlpha = 0x74524E53U, // tRNS
+ Header = 0x49484452U,
///
- /// Textual information that the encoder wishes to record with the image can be stored in
- /// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
+ /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
+ /// series in the RGB format.
+ ///
+ Palette = 0x504C5445U,
+
+ ///
+ /// The eXIf data chunk which contains the Exif profile.
///
- Text = 0x74455874U, // tEXt
+ Exif = 0x65584966U,
///
/// This chunk specifies the relationship between the image samples and the desired
/// display output intensity.
///
- Gamma = 0x67414D41U, // gAMA
+ Gamma = 0x67414D41U,
///
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
///
- Physical = 0x70485973U, // pHYs
+ Physical = 0x70485973U,
///
- /// The data chunk which contains the Exif profile.
+ /// Textual information that the encoder wishes to record with the image can be stored in
+ /// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
+ ///
+ Text = 0x74455874U,
+
+ ///
+ /// This chunk specifies that the image uses simple transparency:
+ /// either alpha values associated with palette entries (for indexed-color images)
+ /// or a single transparent color (for grayscale and true color images).
///
- Exif = 0x65584966U // eXIf
+ PaletteAlpha = 0x74524E53U
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index be1914174..4bc483301 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -216,7 +216,9 @@ namespace SixLabors.ImageSharp.Formats.Png
public Image Decode(Stream stream)
where TPixel : struct, IPixel
{
- var metadata = new ImageMetaData();
+ var metaData = new ImageMetaData();
+ var pngMetaData = new PngMetaData();
+ metaData.AddOrUpdateFormatMetaData(PngFormat.Instance, pngMetaData);
this.currentStream = stream;
this.currentStream.Skip(8);
Image image = null;
@@ -231,16 +233,19 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
- this.ReadHeaderChunk(chunk.Data.Array);
+ this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
- this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
+ this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
+ break;
+ case PngChunkType.Gamma:
+ this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
if (image is null)
{
- this.InitializeImage(metadata, out image);
+ this.InitializeImage(metaData, out image);
}
deframeStream.AllocateNewBytes(chunk.Length);
@@ -259,14 +264,14 @@ namespace SixLabors.ImageSharp.Formats.Png
this.AssignTransparentMarkers(alpha);
break;
case PngChunkType.Text:
- this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
+ this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
byte[] exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
- metadata.ExifProfile = new ExifProfile(exifData);
+ metaData.ExifProfile = new ExifProfile(exifData);
}
break;
@@ -302,7 +307,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The containing image data.
public IImageInfo Identify(Stream stream)
{
- var metadata = new ImageMetaData();
+ var metaData = new ImageMetaData();
+ var pngMetaData = new PngMetaData();
+ metaData.AddOrUpdateFormatMetaData(PngFormat.Instance, pngMetaData);
this.currentStream = stream;
this.currentStream.Skip(8);
try
@@ -314,17 +321,20 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
- this.ReadHeaderChunk(chunk.Data.Array);
+ this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
- this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
+ this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
+ break;
+ case PngChunkType.Gamma:
+ this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Text:
- this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
+ this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.End:
this.isEndChunkReached = true;
@@ -348,7 +358,7 @@ namespace SixLabors.ImageSharp.Formats.Png
throw new ImageFormatException("PNG Image does not contain a header chunk");
}
- return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
+ return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metaData);
}
///
@@ -427,6 +437,18 @@ namespace SixLabors.ImageSharp.Formats.Png
metadata.VerticalResolution = vResolution;
}
+ ///
+ /// Reads the data chunk containing gamma data.
+ ///
+ /// The metadata to read to.
+ /// The data containing physical data.
+ private void ReadGammaChunk(PngMetaData pngMetadata, ReadOnlySpan data)
+ {
+ // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
+ // For example, a gamma of 1/2.2 would be stored as 45455.
+ pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F;
+ }
+
///
/// Initializes the image and various buffers needed for processing
///
@@ -1267,17 +1289,27 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Reads a header chunk from the data.
///
+ /// The png metadata.
/// The containing data.
- private void ReadHeaderChunk(ReadOnlySpan data)
+ private void ReadHeaderChunk(PngMetaData pngMetaData, ReadOnlySpan data)
{
+ byte bitDepth = data[8];
this.header = new PngHeader(
width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)),
height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)),
- bitDepth: data[8],
+ bitDepth: bitDepth,
colorType: (PngColorType)data[9],
compressionMethod: data[10],
filterMethod: data[11],
interlaceMethod: (PngInterlaceMode)data[12]);
+
+ // TODO: Figure out how we can determine the number of colors and support more bit depths.
+ if (bitDepth == 8 || bitDepth == 16)
+ {
+ pngMetaData.BitDepth = (PngBitDepth)bitDepth;
+ }
+
+ pngMetaData.ColorType = this.header.ColorType;
}
///
diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs
index 109e6ad77..435d0abbc 100644
--- a/src/ImageSharp/Formats/Png/PngEncoder.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoder.cs
@@ -4,6 +4,7 @@
using System.IO;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Png
@@ -17,12 +18,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets or sets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all values.
///
- public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
+ public PngBitDepth? BitDepth { get; set; }
///
/// Gets or sets the color type.
///
- public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
+ public PngColorType? ColorType { get; set; }
///
/// Gets or sets the filter method.
@@ -36,18 +37,15 @@ namespace SixLabors.ImageSharp.Formats.Png
public int CompressionLevel { get; set; } = 6;
///
- /// Gets or sets the gamma value, that will be written
- /// the the stream, when the property
- /// is set to true. The default value is 2.2F.
+ /// Gets or sets the gamma value, that will be written the the image.
///
- /// The gamma value of the image.
- public float Gamma { get; set; } = 2.2F;
+ public float? Gamma { get; set; }
///
/// Gets or sets quantizer for reducing the color count.
/// Defaults to the
///
- public IQuantizer Quantizer { get; set; } = new WuQuantizer();
+ public IQuantizer Quantizer { get; set; } = KnownQuantizers.Wu;
///
/// Gets or sets the transparency threshold.
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index ffe29aeca..9d9de71b1 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -4,7 +4,6 @@
using System;
using System.Buffers.Binary;
using System.IO;
-using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
@@ -45,49 +44,49 @@ namespace SixLabors.ImageSharp.Formats.Png
private readonly Crc32 crc = new Crc32();
///
- /// The png bit depth
+ /// The png filter method.
///
- private readonly PngBitDepth pngBitDepth;
+ private readonly PngFilterMethod pngFilterMethod;
///
- /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
+ /// The quantizer for reducing the color count.
///
- private readonly bool use16Bit;
+ private readonly IQuantizer quantizer;
///
- /// The png color type.
+ /// Gets or sets the CompressionLevel value
///
- private readonly PngColorType pngColorType;
+ private readonly int compressionLevel;
///
- /// The png filter method.
+ /// Gets or sets the alpha threshold value
///
- private readonly PngFilterMethod pngFilterMethod;
+ private readonly byte threshold;
///
- /// The quantizer for reducing the color count.
+ /// Gets or sets a value indicating whether to write the gamma chunk
///
- private readonly IQuantizer quantizer;
+ private bool writeGamma;
///
- /// Gets or sets the CompressionLevel value
+ /// The png bit depth
///
- private readonly int compressionLevel;
+ private PngBitDepth? pngBitDepth;
///
- /// Gets or sets the Gamma value
+ /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
///
- private readonly float gamma;
+ private bool use16Bit;
///
- /// Gets or sets the Threshold value
+ /// The png color type.
///
- private readonly byte threshold;
+ private PngColorType? pngColorType;
///
- /// Gets or sets a value indicating whether to Write Gamma
+ /// Gets or sets the Gamma value
///
- private readonly bool writeGamma;
+ private float? gamma;
///
/// The image width.
@@ -158,14 +157,12 @@ namespace SixLabors.ImageSharp.Formats.Png
{
this.memoryAllocator = memoryAllocator;
this.pngBitDepth = options.BitDepth;
- this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
this.pngColorType = options.ColorType;
this.pngFilterMethod = options.FilterMethod;
this.compressionLevel = options.CompressionLevel;
this.gamma = options.Gamma;
this.quantizer = options.Quantizer;
this.threshold = options.Threshold;
- this.writeGamma = options.WriteGamma;
}
///
@@ -183,6 +180,16 @@ namespace SixLabors.ImageSharp.Formats.Png
this.width = image.Width;
this.height = image.Height;
+ // Always take the encoder options over the metadata values.
+ PngMetaData pngMetaData = image.MetaData.GetOrAddFormatMetaData(PngFormat.Instance);
+ this.gamma = this.gamma ?? pngMetaData.Gamma;
+ this.writeGamma = this.gamma > 0;
+ this.pngColorType = this.pngColorType ?? pngMetaData.ColorType;
+
+ // TODO: We don't take full advantage of this information yet.
+ this.pngBitDepth = this.pngBitDepth ?? pngMetaData.BitDepth;
+ this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
+
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame quantized = null;
@@ -217,7 +224,7 @@ namespace SixLabors.ImageSharp.Formats.Png
width: image.Width,
height: image.Height,
bitDepth: this.bitDepth,
- colorType: this.pngColorType,
+ colorType: this.pngColorType.Value,
compressionMethod: 0, // None
filterMethod: 0,
interlaceMethod: 0); // TODO: Can't write interlaced yet.
@@ -781,10 +788,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Writes the chunk end to the stream.
///
/// The containing image data.
- private void WriteEndChunk(Stream stream)
- {
- this.WriteChunk(stream, PngChunkType.End, null);
- }
+ private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null);
///
/// Writes a chunk to the stream.
@@ -792,10 +796,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The to write to.
/// The type of chunk to write.
/// The containing data.
- private void WriteChunk(Stream stream, PngChunkType type, byte[] data)
- {
- this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
- }
+ private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
///
/// Writes a chunk of a specified length to the stream at the given offset.
diff --git a/src/ImageSharp/Formats/Png/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs
index 90dbb83b6..1eb3cdad6 100644
--- a/src/ImageSharp/Formats/Png/PngMetaData.cs
+++ b/src/ImageSharp/Formats/Png/PngMetaData.cs
@@ -8,6 +8,20 @@ namespace SixLabors.ImageSharp.Formats.Png
///
public class PngMetaData
{
- // TODO: Analyse what properties we would like to preserve.
+ ///
+ /// Gets or sets the number of bits per sample or per palette index (not per pixel).
+ /// Not all values are allowed for all values.
+ ///
+ public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
+
+ ///
+ /// Gets or sets the color type.
+ ///
+ public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
+
+ ///
+ /// Gets or sets the gamma value for the image.
+ ///
+ public float Gamma { get; set; }
}
-}
+}
\ No newline at end of file