@ -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 indica ting w hether to Write Gamma
/// Gets or sets the Gamma value
/// </summary>
/// </summary>
private readonly bool writeG amma;
private float? g amma;
/// <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 ( 8 u , ( 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 , 2 5 6 ) ;
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 ( i mag e. MetaDa ta. ExifProfile ? . Values . Count > 0 )
if ( meta . ExifProfile ? . Values . Count > 0 )
{
{
i mag e. MetaDa ta. SyncProfiles ( ) ;
meta . SyncProfiles ( ) ;
this . WriteChunk ( stream , PngChunkType . Exif , i mag e. MetaDa ta. 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 = = 1 6 ? 1 6 : 8 ;
int scanlineLength = width * this . bitDepth * this . bytesPerPixel ;
int amount = scanlineLength % mod ;
if ( amount ! = 0 )
{
scanlineLength + = mod - amount ;
}
return scanlineLength / mod ;
}
}
}
}
}