Browse Source

Use metadata as fallback forpng encoder options.

pull/693/head
James Jackson-South 8 years ago
parent
commit
df62dd66dd
  1. 18
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  2. 52
      src/ImageSharp/Formats/Png/PngChunkType.cs
  3. 58
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  4. 14
      src/ImageSharp/Formats/Png/PngEncoder.cs
  5. 61
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  6. 18
      src/ImageSharp/Formats/Png/PngMetaData.cs

18
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). /// Gets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all <see cref="ColorType"/> values. /// Not all values are allowed for all <see cref="ColorType"/> values.
/// </summary> /// </summary>
PngBitDepth BitDepth { get; } PngBitDepth? BitDepth { get; }
/// <summary> /// <summary>
/// Gets the color type /// Gets the color type
/// </summary> /// </summary>
PngColorType ColorType { get; } PngColorType? ColorType { get; }
/// <summary> /// <summary>
/// Gets the filter method. /// Gets the filter method.
@ -33,15 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Png
int CompressionLevel { get; } int CompressionLevel { get; }
/// <summary> /// <summary>
/// Gets the gamma value, that will be written /// Gets the gamma value, that will be written the the image.
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary> /// </summary>
/// <value>The gamma value of the image.</value> /// <value>The gamma value of the image.</value>
float Gamma { get; } float? Gamma { get; }
/// <summary> /// <summary>
/// Gets quantizer for reducing the color count. /// Gets the quantizer for reducing the color count.
/// </summary> /// </summary>
IQuantizer Quantizer { get; } IQuantizer Quantizer { get; }
@ -49,11 +47,5 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the transparency threshold. /// Gets the transparency threshold.
/// </summary> /// </summary>
byte Threshold { get; } byte Threshold { get; }
/// <summary>
/// Gets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
bool WriteGamma { get; }
} }
} }

52
src/ImageSharp/Formats/Png/PngChunkType.cs

@ -8,58 +8,58 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
internal enum PngChunkType : uint internal enum PngChunkType : uint
{ {
/// <summary>
/// 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.
/// </summary>
Header = 0x49484452U, // IHDR
/// <summary>
/// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
/// series in the RGB format.
/// </summary>
Palette = 0x504C5445U, // PLTE
/// <summary> /// <summary>
/// The IDAT chunk contains the actual image data. The image can contains more /// 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. /// than one chunk of this type. All chunks together are the whole image.
/// </summary> /// </summary>
Data = 0x49444154U, // IDAT Data = 0x49444154U,
/// <summary> /// <summary>
/// This chunk must appear last. It marks the end of the PNG data stream. /// This chunk must appear last. It marks the end of the PNG data stream.
/// The chunk's data field is empty. /// The chunk's data field is empty.
/// </summary> /// </summary>
End = 0x49454E44U, // IEND End = 0x49454E44U,
/// <summary> /// <summary>
/// This chunk specifies that the image uses simple transparency: /// The first chunk in a png file. Can only exists once. Contains
/// either alpha values associated with palette entries (for indexed-color images) /// common information like the width and the height of the image or
/// or a single transparent color (for grayscale and true color images). /// the used compression method.
/// </summary> /// </summary>
PaletteAlpha = 0x74524E53U, // tRNS Header = 0x49484452U,
/// <summary> /// <summary>
/// Textual information that the encoder wishes to record with the image can be stored in /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
/// tEXt chunks. Each tEXt chunk contains a keyword and a text string. /// series in the RGB format.
/// </summary>
Palette = 0x504C5445U,
/// <summary>
/// The eXIf data chunk which contains the Exif profile.
/// </summary> /// </summary>
Text = 0x74455874U, // tEXt Exif = 0x65584966U,
/// <summary> /// <summary>
/// This chunk specifies the relationship between the image samples and the desired /// This chunk specifies the relationship between the image samples and the desired
/// display output intensity. /// display output intensity.
/// </summary> /// </summary>
Gamma = 0x67414D41U, // gAMA Gamma = 0x67414D41U,
/// <summary> /// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary> /// </summary>
Physical = 0x70485973U, // pHYs Physical = 0x70485973U,
/// <summary> /// <summary>
/// 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.
/// </summary>
Text = 0x74455874U,
/// <summary>
/// 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).
/// </summary> /// </summary>
Exif = 0x65584966U // eXIf PaletteAlpha = 0x74524E53U
} }
} }

58
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -216,7 +216,9 @@ namespace SixLabors.ImageSharp.Formats.Png
public Image<TPixel> Decode<TPixel>(Stream stream) public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var metadata = new ImageMetaData(); var metaData = new ImageMetaData();
var pngMetaData = new PngMetaData();
metaData.AddOrUpdateFormatMetaData(PngFormat.Instance, pngMetaData);
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
Image<TPixel> image = null; Image<TPixel> image = null;
@ -231,16 +233,19 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type) switch (chunk.Type)
{ {
case PngChunkType.Header: case PngChunkType.Header:
this.ReadHeaderChunk(chunk.Data.Array); this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader(); this.ValidateHeader();
break; break;
case PngChunkType.Physical: 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; break;
case PngChunkType.Data: case PngChunkType.Data:
if (image is null) if (image is null)
{ {
this.InitializeImage(metadata, out image); this.InitializeImage(metaData, out image);
} }
deframeStream.AllocateNewBytes(chunk.Length); deframeStream.AllocateNewBytes(chunk.Length);
@ -259,14 +264,14 @@ namespace SixLabors.ImageSharp.Formats.Png
this.AssignTransparentMarkers(alpha); this.AssignTransparentMarkers(alpha);
break; break;
case PngChunkType.Text: case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length); this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
break; break;
case PngChunkType.Exif: case PngChunkType.Exif:
if (!this.ignoreMetadata) if (!this.ignoreMetadata)
{ {
byte[] exifData = new byte[chunk.Length]; byte[] exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
metadata.ExifProfile = new ExifProfile(exifData); metaData.ExifProfile = new ExifProfile(exifData);
} }
break; break;
@ -302,7 +307,9 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public IImageInfo Identify(Stream stream) 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 = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
try try
@ -314,17 +321,20 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type) switch (chunk.Type)
{ {
case PngChunkType.Header: case PngChunkType.Header:
this.ReadHeaderChunk(chunk.Data.Array); this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader(); this.ValidateHeader();
break; break;
case PngChunkType.Physical: 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; break;
case PngChunkType.Data: case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk); this.SkipChunkDataAndCrc(chunk);
break; break;
case PngChunkType.Text: case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length); this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
break; break;
case PngChunkType.End: case PngChunkType.End:
this.isEndChunkReached = true; this.isEndChunkReached = true;
@ -348,7 +358,7 @@ namespace SixLabors.ImageSharp.Formats.Png
throw new ImageFormatException("PNG Image does not contain a header chunk"); 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);
} }
/// <summary> /// <summary>
@ -427,6 +437,18 @@ namespace SixLabors.ImageSharp.Formats.Png
metadata.VerticalResolution = vResolution; metadata.VerticalResolution = vResolution;
} }
/// <summary>
/// Reads the data chunk containing gamma data.
/// </summary>
/// <param name="pngMetadata">The metadata to read to.</param>
/// <param name="data">The data containing physical data.</param>
private void ReadGammaChunk(PngMetaData pngMetadata, ReadOnlySpan<byte> 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;
}
/// <summary> /// <summary>
/// Initializes the image and various buffers needed for processing /// Initializes the image and various buffers needed for processing
/// </summary> /// </summary>
@ -1267,17 +1289,27 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Reads a header chunk from the data. /// Reads a header chunk from the data.
/// </summary> /// </summary>
/// <param name="pngMetaData">The png metadata.</param>
/// <param name="data">The <see cref="T:ReadOnlySpan{byte}"/> containing data.</param> /// <param name="data">The <see cref="T:ReadOnlySpan{byte}"/> containing data.</param>
private void ReadHeaderChunk(ReadOnlySpan<byte> data) private void ReadHeaderChunk(PngMetaData pngMetaData, ReadOnlySpan<byte> data)
{ {
byte bitDepth = data[8];
this.header = new PngHeader( this.header = new PngHeader(
width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)), width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)),
height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)), height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)),
bitDepth: data[8], bitDepth: bitDepth,
colorType: (PngColorType)data[9], colorType: (PngColorType)data[9],
compressionMethod: data[10], compressionMethod: data[10],
filterMethod: data[11], filterMethod: data[11],
interlaceMethod: (PngInterlaceMode)data[12]); 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;
} }
/// <summary> /// <summary>

14
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -4,6 +4,7 @@
using System.IO; using System.IO;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Png 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). /// Gets or sets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all <see cref="ColorType"/> values. /// Not all values are allowed for all <see cref="ColorType"/> values.
/// </summary> /// </summary>
public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8; public PngBitDepth? BitDepth { get; set; }
/// <summary> /// <summary>
/// Gets or sets the color type. /// Gets or sets the color type.
/// </summary> /// </summary>
public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha; public PngColorType? ColorType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the filter method. /// Gets or sets the filter method.
@ -36,18 +37,15 @@ namespace SixLabors.ImageSharp.Formats.Png
public int CompressionLevel { get; set; } = 6; public int CompressionLevel { get; set; } = 6;
/// <summary> /// <summary>
/// Gets or sets the gamma value, that will be written /// Gets or sets the gamma value, that will be written the the image.
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary> /// </summary>
/// <value>The gamma value of the image.</value> public float? Gamma { get; set; }
public float Gamma { get; set; } = 2.2F;
/// <summary> /// <summary>
/// Gets or sets quantizer for reducing the color count. /// Gets or sets quantizer for reducing the color count.
/// Defaults to the <see cref="WuQuantizer"/> /// Defaults to the <see cref="WuQuantizer"/>
/// </summary> /// </summary>
public IQuantizer Quantizer { get; set; } = new WuQuantizer(); public IQuantizer Quantizer { get; set; } = KnownQuantizers.Wu;
/// <summary> /// <summary>
/// Gets or sets the transparency threshold. /// Gets or sets the transparency threshold.

61
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Filters;
@ -45,49 +44,49 @@ namespace SixLabors.ImageSharp.Formats.Png
private readonly Crc32 crc = new Crc32(); private readonly Crc32 crc = new Crc32();
/// <summary> /// <summary>
/// The png bit depth /// The png filter method.
/// </summary> /// </summary>
private readonly PngBitDepth pngBitDepth; private readonly PngFilterMethod pngFilterMethod;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to use 16 bit encoding for supported color types. /// The quantizer for reducing the color count.
/// </summary> /// </summary>
private readonly bool use16Bit; private readonly IQuantizer quantizer;
/// <summary> /// <summary>
/// The png color type. /// Gets or sets the CompressionLevel value
/// </summary> /// </summary>
private readonly PngColorType pngColorType; private readonly int compressionLevel;
/// <summary> /// <summary>
/// The png filter method. /// Gets or sets the alpha threshold value
/// </summary> /// </summary>
private readonly PngFilterMethod pngFilterMethod; private readonly byte threshold;
/// <summary> /// <summary>
/// The quantizer for reducing the color count. /// Gets or sets a value indicating whether to write the gamma chunk
/// </summary> /// </summary>
private readonly IQuantizer quantizer; private bool writeGamma;
/// <summary> /// <summary>
/// Gets or sets the CompressionLevel value /// The png bit depth
/// </summary> /// </summary>
private readonly int compressionLevel; private PngBitDepth? pngBitDepth;
/// <summary> /// <summary>
/// Gets or sets the Gamma value /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
/// </summary> /// </summary>
private readonly float gamma; private bool use16Bit;
/// <summary> /// <summary>
/// Gets or sets the Threshold value /// The png color type.
/// </summary> /// </summary>
private readonly byte threshold; private PngColorType? pngColorType;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to Write Gamma /// Gets or sets the Gamma value
/// </summary> /// </summary>
private readonly bool writeGamma; private float? gamma;
/// <summary> /// <summary>
/// The image width. /// The image width.
@ -158,14 +157,12 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
this.memoryAllocator = memoryAllocator; this.memoryAllocator = memoryAllocator;
this.pngBitDepth = options.BitDepth; this.pngBitDepth = options.BitDepth;
this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
this.pngColorType = options.ColorType; this.pngColorType = options.ColorType;
this.pngFilterMethod = options.FilterMethod; this.pngFilterMethod = options.FilterMethod;
this.compressionLevel = options.CompressionLevel; this.compressionLevel = options.CompressionLevel;
this.gamma = options.Gamma; this.gamma = options.Gamma;
this.quantizer = options.Quantizer; this.quantizer = options.Quantizer;
this.threshold = options.Threshold; this.threshold = options.Threshold;
this.writeGamma = options.WriteGamma;
} }
/// <summary> /// <summary>
@ -183,6 +180,16 @@ namespace SixLabors.ImageSharp.Formats.Png
this.width = image.Width; this.width = image.Width;
this.height = image.Height; 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); stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame<TPixel> quantized = null; QuantizedFrame<TPixel> quantized = null;
@ -217,7 +224,7 @@ namespace SixLabors.ImageSharp.Formats.Png
width: image.Width, width: image.Width,
height: image.Height, height: image.Height,
bitDepth: this.bitDepth, bitDepth: this.bitDepth,
colorType: this.pngColorType, colorType: this.pngColorType.Value,
compressionMethod: 0, // None compressionMethod: 0, // None
filterMethod: 0, filterMethod: 0,
interlaceMethod: 0); // TODO: Can't write interlaced yet. interlaceMethod: 0); // TODO: Can't write interlaced yet.
@ -781,10 +788,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Writes the chunk end to the stream. /// Writes the chunk end to the stream.
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteEndChunk(Stream stream) private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null);
{
this.WriteChunk(stream, PngChunkType.End, null);
}
/// <summary> /// <summary>
/// Writes a chunk to the stream. /// Writes a chunk to the stream.
@ -792,10 +796,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param> /// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param> /// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
private void WriteChunk(Stream stream, PngChunkType type, byte[] data) private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
{
this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
}
/// <summary> /// <summary>
/// Writes a chunk of a specified length to the stream at the given offset. /// Writes a chunk of a specified length to the stream at the given offset.

18
src/ImageSharp/Formats/Png/PngMetaData.cs

@ -8,6 +8,20 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
public class PngMetaData public class PngMetaData
{ {
// TODO: Analyse what properties we would like to preserve. /// <summary>
/// Gets or sets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all <see cref="ColorType"/> values.
/// </summary>
public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
/// <summary>
/// Gets or sets the color type.
/// </summary>
public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets the gamma value for the image.
/// </summary>
public float Gamma { get; set; }
} }
} }
Loading…
Cancel
Save