Browse Source

Preserve color profile when encoding png's

pull/2110/head
Brian Popow 4 years ago
parent
commit
9b5d56f585
  1. 70
      src/ImageSharp/Formats/Png/PngEncoderCore.cs

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

@ -87,6 +87,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private IMemoryOwner<byte> currentScanline;
/// <summary>
/// The color profile name.
/// </summary>
private const string ProfileName = "ICC Profile";
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore" /> class.
/// </summary>
@ -134,6 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WriteHeaderChunk(stream);
this.WriteGammaChunk(stream);
this.WriteColorProfileChunk(stream, metadata);
this.WritePaletteChunk(stream, quantized);
this.WriteTransparencyChunk(stream, pngMetadata);
this.WritePhysicalChunk(stream, metadata);
@ -656,7 +662,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
/// <summary>
/// Writes an iTXT chunk, containing the XMP metdata to the stream, if such profile is present in the metadata.
/// Writes an iTXT chunk, containing the XMP metadata to the stream, if such profile is present in the metadata.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="meta">The image metadata.</param>
@ -673,7 +679,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return;
}
var xmpData = meta.XmpProfile.Data;
byte[] xmpData = meta.XmpProfile.Data;
if (xmpData.Length == 0)
{
@ -687,19 +693,48 @@ namespace SixLabors.ImageSharp.Formats.Png
PngConstants.XmpKeyword.CopyTo(payload);
int bytesWritten = PngConstants.XmpKeyword.Length;
// Write the iTxt header (all zeros in this case)
// Write the iTxt header (all zeros in this case).
payload[bytesWritten++] = 0;
payload[bytesWritten++] = 0;
payload[bytesWritten++] = 0;
payload[bytesWritten++] = 0;
payload[bytesWritten++] = 0;
// And the XMP data itself
// And the XMP data itself.
xmpData.CopyTo(payload.Slice(bytesWritten));
this.WriteChunk(stream, PngChunkType.InternationalText, payload);
}
}
/// <summary>
/// Writes the color profile chunk.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="metaData">The image meta data.</param>
private void WriteColorProfileChunk(Stream stream, ImageMetadata metaData)
{
if (metaData.IccProfile is null)
{
return;
}
string profileName = "ICC Profile";
byte[] iccProfileBytes = metaData.IccProfile.ToByteArray();
byte[] compressedData = this.GetZlibCompressedBytes(iccProfileBytes);
int payloadLength = profileName.Length + compressedData.Length + 2;
using (IMemoryOwner<byte> owner = this.memoryAllocator.Allocate<byte>(payloadLength))
{
Span<byte> outputBytes = owner.GetSpan();
PngConstants.Encoding.GetBytes(profileName).CopyTo(outputBytes);
int bytesWritten = profileName.Length;
outputBytes[bytesWritten++] = 0; // Null separator.
outputBytes[bytesWritten++] = 0; // Compression.
compressedData.CopyTo(outputBytes.Slice(bytesWritten));
this.WriteChunk(stream, PngChunkType.EmbeddedColorProfile, outputBytes);
}
}
/// <summary>
/// Writes a text chunk to the stream. Can be either a tTXt, iTXt or zTXt chunk,
/// depending whether the text contains any latin characters or should be compressed.
@ -727,13 +762,12 @@ namespace SixLabors.ImageSharp.Formats.Png
}
}
if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) ||
!string.IsNullOrWhiteSpace(textData.TranslatedKeyword)))
if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)))
{
// Write iTXt chunk.
byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword);
byte[] textBytes = textData.Value.Length > this.options.TextCompressionThreshold
? this.GetCompressedTextBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value))
? this.GetZlibCompressedBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value))
: PngConstants.TranslatedEncoding.GetBytes(textData.Value);
byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword);
@ -772,18 +806,17 @@ namespace SixLabors.ImageSharp.Formats.Png
if (textData.Value.Length > this.options.TextCompressionThreshold)
{
// Write zTXt chunk.
byte[] compressedData =
this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value));
byte[] compressedData = this.GetZlibCompressedBytes(PngConstants.Encoding.GetBytes(textData.Value));
int payloadLength = textData.Keyword.Length + compressedData.Length + 2;
using (IMemoryOwner<byte> owner = this.memoryAllocator.Allocate<byte>(payloadLength))
{
Span<byte> outputBytes = owner.GetSpan();
PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
int bytesWritten = textData.Keyword.Length;
outputBytes[bytesWritten++] = 0;
outputBytes[bytesWritten++] = 0;
outputBytes[bytesWritten++] = 0; // Null separator.
outputBytes[bytesWritten++] = 0; // Compression.
compressedData.CopyTo(outputBytes.Slice(bytesWritten));
this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes.ToArray());
this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes);
}
}
else
@ -796,9 +829,8 @@ namespace SixLabors.ImageSharp.Formats.Png
PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
int bytesWritten = textData.Keyword.Length;
outputBytes[bytesWritten++] = 0;
PngConstants.Encoding.GetBytes(textData.Value)
.CopyTo(outputBytes.Slice(bytesWritten));
this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray());
PngConstants.Encoding.GetBytes(textData.Value).CopyTo(outputBytes.Slice(bytesWritten));
this.WriteChunk(stream, PngChunkType.Text, outputBytes);
}
}
}
@ -808,15 +840,15 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Compresses a given text using Zlib compression.
/// </summary>
/// <param name="textBytes">The text bytes to compress.</param>
/// <returns>The compressed text byte array.</returns>
private byte[] GetCompressedTextBytes(byte[] textBytes)
/// <param name="dataBytes">The bytes to compress.</param>
/// <returns>The compressed byte array.</returns>
private byte[] GetZlibCompressedBytes(byte[] dataBytes)
{
using (var memoryStream = new MemoryStream())
{
using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
{
deflateStream.Write(textBytes);
deflateStream.Write(dataBytes);
}
return memoryStream.ToArray();

Loading…
Cancel
Save