|
|
@ -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.
|
|
|
/// Gets or sets the CompressionLevel value
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private readonly bool use16Bit; |
|
|
private readonly int compressionLevel; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// The png color type.
|
|
|
/// Gets or sets the alpha threshold value
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private readonly PngColorType pngColorType; |
|
|
private readonly byte threshold; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// The png filter method.
|
|
|
/// The quantizer for reducing the color count.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private readonly PngFilterMethod pngFilterMethod; |
|
|
private IQuantizer quantizer; |
|
|
|
|
|
|
|
|
/// <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,23 +180,42 @@ 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.
|
|
|
|
|
|
ImageMetaData metaData = image.MetaData; |
|
|
|
|
|
PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); |
|
|
|
|
|
this.gamma = this.gamma ?? pngMetaData.Gamma; |
|
|
|
|
|
this.writeGamma = this.gamma > 0; |
|
|
|
|
|
this.pngColorType = this.pngColorType ?? pngMetaData.ColorType; |
|
|
|
|
|
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; |
|
|
ReadOnlySpan<byte> quantizedPixelsSpan = default; |
|
|
ReadOnlySpan<byte> quantizedPixelsSpan = default; |
|
|
if (this.pngColorType == PngColorType.Palette) |
|
|
if (this.pngColorType == PngColorType.Palette) |
|
|
{ |
|
|
{ |
|
|
|
|
|
byte bits; |
|
|
|
|
|
|
|
|
|
|
|
// Use the metadata to determine what quantization depth to use if no quantizer has been set.
|
|
|
|
|
|
if (this.quantizer == null) |
|
|
|
|
|
{ |
|
|
|
|
|
bits = (byte)Math.Min(8u, (short)this.pngBitDepth); |
|
|
|
|
|
int colorSize = ImageMaths.GetColorCountForBitDepth(bits); |
|
|
|
|
|
this.quantizer = new WuQuantizer(colorSize); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Create quantized frame returning the palette and set the bit depth.
|
|
|
// Create quantized frame returning the palette and set the bit depth.
|
|
|
quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame); |
|
|
quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame); |
|
|
quantizedPixelsSpan = quantized.GetPixelSpan(); |
|
|
quantizedPixelsSpan = quantized.GetPixelSpan(); |
|
|
byte bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); |
|
|
bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); |
|
|
|
|
|
|
|
|
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
|
|
|
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
|
|
|
if (bits == 3) |
|
|
if (bits == 3) |
|
|
{ |
|
|
{ |
|
|
bits = 4; |
|
|
bits = 4; |
|
|
} |
|
|
} |
|
|
else if (bits >= 5 || bits <= 7) |
|
|
else if (bits >= 5 && bits <= 7) |
|
|
{ |
|
|
{ |
|
|
bits = 8; |
|
|
bits = 8; |
|
|
} |
|
|
} |
|
|
@ -217,7 +233,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.
|
|
|
@ -227,12 +243,12 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
// Collect the indexed pixel data
|
|
|
// Collect the indexed pixel data
|
|
|
if (quantized != null) |
|
|
if (quantized != null) |
|
|
{ |
|
|
{ |
|
|
this.WritePaletteChunk(stream, header, quantized); |
|
|
this.WritePaletteChunk(stream, quantized); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.WritePhysicalChunk(stream, image); |
|
|
this.WritePhysicalChunk(stream, metaData); |
|
|
this.WriteGammaChunk(stream); |
|
|
this.WriteGammaChunk(stream); |
|
|
this.WriteExifChunk(stream, image); |
|
|
this.WriteExifChunk(stream, metaData); |
|
|
this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream); |
|
|
this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream); |
|
|
this.WriteEndChunk(stream); |
|
|
this.WriteEndChunk(stream); |
|
|
stream.Flush(); |
|
|
stream.Flush(); |
|
|
@ -539,30 +555,27 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|
|
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|
|
/// <param name="header">The <see cref="PngHeader"/>.</param>
|
|
|
|
|
|
/// <param name="quantized">The quantized frame.</param>
|
|
|
/// <param name="quantized">The quantized frame.</param>
|
|
|
private void WritePaletteChunk<TPixel>(Stream stream, in PngHeader header, QuantizedFrame<TPixel> quantized) |
|
|
private void WritePaletteChunk<TPixel>(Stream stream, QuantizedFrame<TPixel> quantized) |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
// Grab the palette and write it to the stream.
|
|
|
// Grab the palette and write it to the stream.
|
|
|
TPixel[] palette = quantized.Palette; |
|
|
TPixel[] palette = quantized.Palette; |
|
|
byte pixelCount = palette.Length.ToByte(); |
|
|
int paletteLength = Math.Min(palette.Length, 256); |
|
|
|
|
|
int colorTableLength = paletteLength * 3; |
|
|
// Get max colors for bit depth.
|
|
|
|
|
|
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; |
|
|
|
|
|
Rgba32 rgba = default; |
|
|
Rgba32 rgba = default; |
|
|
bool anyAlpha = false; |
|
|
bool anyAlpha = false; |
|
|
|
|
|
|
|
|
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) |
|
|
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) |
|
|
using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(pixelCount)) |
|
|
using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength)) |
|
|
{ |
|
|
{ |
|
|
Span<byte> colorTableSpan = colorTable.GetSpan(); |
|
|
Span<byte> colorTableSpan = colorTable.GetSpan(); |
|
|
Span<byte> alphaTableSpan = alphaTable.GetSpan(); |
|
|
Span<byte> alphaTableSpan = alphaTable.GetSpan(); |
|
|
Span<byte> quantizedSpan = quantized.GetPixelSpan(); |
|
|
Span<byte> quantizedSpan = quantized.GetPixelSpan(); |
|
|
|
|
|
|
|
|
for (byte i = 0; i < pixelCount; i++) |
|
|
for (int i = 0; i < paletteLength; i++) |
|
|
{ |
|
|
{ |
|
|
if (quantizedSpan.IndexOf(i) > -1) |
|
|
if (quantizedSpan.IndexOf((byte)i) > -1) |
|
|
{ |
|
|
{ |
|
|
int offset = i * 3; |
|
|
int offset = i * 3; |
|
|
palette[i].ToRgba32(ref rgba); |
|
|
palette[i].ToRgba32(ref rgba); |
|
|
@ -588,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
// Write the transparency data
|
|
|
// Write the transparency data
|
|
|
if (anyAlpha) |
|
|
if (anyAlpha) |
|
|
{ |
|
|
{ |
|
|
this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, pixelCount); |
|
|
this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, paletteLength); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -596,11 +609,9 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Writes the physical dimension information to the stream.
|
|
|
/// Writes the physical dimension information to the stream.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
|
|
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|
|
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|
|
/// <param name="image">The image.</param>
|
|
|
/// <param name="meta">The image meta data.</param>
|
|
|
private void WritePhysicalChunk<TPixel>(Stream stream, Image<TPixel> image) |
|
|
private void WritePhysicalChunk(Stream stream, ImageMetaData meta) |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
|
|
|
{ |
|
|
{ |
|
|
// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
|
|
|
// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
|
|
|
// Pixels per unit, X axis: 4 bytes (unsigned integer)
|
|
|
// Pixels per unit, X axis: 4 bytes (unsigned integer)
|
|
|
@ -612,7 +623,6 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
// 1: unit is the meter
|
|
|
// 1: unit is the meter
|
|
|
//
|
|
|
//
|
|
|
// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
|
|
|
// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
|
|
|
ImageMetaData meta = image.MetaData; |
|
|
|
|
|
Span<byte> hResolution = this.chunkDataBuffer.AsSpan(0, 4); |
|
|
Span<byte> hResolution = this.chunkDataBuffer.AsSpan(0, 4); |
|
|
Span<byte> vResolution = this.chunkDataBuffer.AsSpan(4, 4); |
|
|
Span<byte> vResolution = this.chunkDataBuffer.AsSpan(4, 4); |
|
|
|
|
|
|
|
|
@ -653,16 +663,14 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the meta data.
|
|
|
/// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the meta data.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
|
|
|
|
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|
|
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|
|
/// <param name="image">The image.</param>
|
|
|
/// <param name="meta">The image meta data.</param>
|
|
|
private void WriteExifChunk<TPixel>(Stream stream, Image<TPixel> image) |
|
|
private void WriteExifChunk(Stream stream, ImageMetaData meta) |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
|
|
|
{ |
|
|
{ |
|
|
if (image.MetaData.ExifProfile?.Values.Count > 0) |
|
|
if (meta.ExifProfile?.Values.Count > 0) |
|
|
{ |
|
|
{ |
|
|
image.MetaData.SyncProfiles(); |
|
|
meta.SyncProfiles(); |
|
|
this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray()); |
|
|
this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -693,7 +701,7 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, ReadOnlySpan<byte> quantizedPixelsSpan, Stream stream) |
|
|
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, ReadOnlySpan<byte> quantizedPixelsSpan, Stream stream) |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
this.bytesPerScanline = this.width * this.bytesPerPixel; |
|
|
this.bytesPerScanline = this.CalculateScanlineLength(this.width); |
|
|
int resultLength = this.bytesPerScanline + 1; |
|
|
int resultLength = this.bytesPerScanline + 1; |
|
|
|
|
|
|
|
|
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); |
|
|
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); |
|
|
@ -781,10 +789,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 +797,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.
|
|
|
@ -827,5 +829,26 @@ namespace SixLabors.ImageSharp.Formats.Png |
|
|
|
|
|
|
|
|
stream.Write(this.buffer, 0, 4); // write the crc
|
|
|
stream.Write(this.buffer, 0, 4); // write the crc
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Calculates the scanline length.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="width">The width of the row.</param>
|
|
|
|
|
|
/// <returns>
|
|
|
|
|
|
/// The <see cref="int"/> representing the length.
|
|
|
|
|
|
/// </returns>
|
|
|
|
|
|
private int CalculateScanlineLength(int width) |
|
|
|
|
|
{ |
|
|
|
|
|
int mod = this.bitDepth == 16 ? 16 : 8; |
|
|
|
|
|
int scanlineLength = width * this.bitDepth * this.bytesPerPixel; |
|
|
|
|
|
|
|
|
|
|
|
int amount = scanlineLength % mod; |
|
|
|
|
|
if (amount != 0) |
|
|
|
|
|
{ |
|
|
|
|
|
scanlineLength += mod - amount; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return scanlineLength / mod; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |