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).
/// Not all values are allowed for all <see cref="ColorType"/> values.
/// </summary>
PngBitDepth BitDepth { get; }
PngBitDepth? BitDepth { get; }
/// <summary>
/// Gets the color type
/// </summary>
PngColorType ColorType { get; }
PngColorType? ColorType { get; }
/// <summary>
/// Gets the filter method.
@ -33,15 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Png
int CompressionLevel { get; }
/// <summary>
/// Gets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// Gets the gamma value, that will be written the the image.
/// </summary>
/// <value>The gamma value of the image.</value>
float Gamma { get; }
float? Gamma { get; }
/// <summary>
/// Gets quantizer for reducing the color count.
/// Gets the quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
@ -49,11 +47,5 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the transparency threshold.
/// </summary>
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>
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>
/// 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.
/// </summary>
Data = 0x49444154U, // IDAT
Data = 0x49444154U,
/// <summary>
/// This chunk must appear last. It marks the end of the PNG data stream.
/// The chunk's data field is empty.
/// </summary>
End = 0x49454E44U, // IEND
End = 0x49454E44U,
/// <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).
/// 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>
PaletteAlpha = 0x74524E53U, // tRNS
Header = 0x49484452U,
/// <summary>
/// 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.
/// </summary>
Palette = 0x504C5445U,
/// <summary>
/// The eXIf data chunk which contains the Exif profile.
/// </summary>
Text = 0x74455874U, // tEXt
Exif = 0x65584966U,
/// <summary>
/// This chunk specifies the relationship between the image samples and the desired
/// display output intensity.
/// </summary>
Gamma = 0x67414D41U, // gAMA
Gamma = 0x67414D41U,
/// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary>
Physical = 0x70485973U, // pHYs
Physical = 0x70485973U,
/// <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>
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)
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.Skip(8);
Image<TPixel> 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
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
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);
}
/// <summary>
@ -427,6 +437,18 @@ namespace SixLabors.ImageSharp.Formats.Png
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>
/// Initializes the image and various buffers needed for processing
/// </summary>
@ -1267,17 +1289,27 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Reads a header chunk from the data.
/// </summary>
/// <param name="pngMetaData">The png metadata.</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(
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;
}
/// <summary>

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

61
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();
/// <summary>
/// The png bit depth
/// The png filter method.
/// </summary>
private readonly PngBitDepth pngBitDepth;
private readonly PngFilterMethod pngFilterMethod;
/// <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>
private readonly bool use16Bit;
private readonly IQuantizer quantizer;
/// <summary>
/// The png color type.
/// Gets or sets the CompressionLevel value
/// </summary>
private readonly PngColorType pngColorType;
private readonly int compressionLevel;
/// <summary>
/// The png filter method.
/// Gets or sets the alpha threshold value
/// </summary>
private readonly PngFilterMethod pngFilterMethod;
private readonly byte threshold;
/// <summary>
/// The quantizer for reducing the color count.
/// Gets or sets a value indicating whether to write the gamma chunk
/// </summary>
private readonly IQuantizer quantizer;
private bool writeGamma;
/// <summary>
/// Gets or sets the CompressionLevel value
/// The png bit depth
/// </summary>
private readonly int compressionLevel;
private PngBitDepth? pngBitDepth;
/// <summary>
/// Gets or sets the Gamma value
/// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
/// </summary>
private readonly float gamma;
private bool use16Bit;
/// <summary>
/// Gets or sets the Threshold value
/// The png color type.
/// </summary>
private readonly byte threshold;
private PngColorType? pngColorType;
/// <summary>
/// Gets or sets a value indicating whether to Write Gamma
/// Gets or sets the Gamma value
/// </summary>
private readonly bool writeGamma;
private float? gamma;
/// <summary>
/// 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;
}
/// <summary>
@ -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<TPixel> 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.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteEndChunk(Stream stream)
{
this.WriteChunk(stream, PngChunkType.End, null);
}
private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null);
/// <summary>
/// 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="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
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);
/// <summary>
/// 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>
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