diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 12770bc521..3dc6d0bf42 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -19,6 +19,7 @@ using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
@@ -205,6 +206,9 @@ namespace SixLabors.ImageSharp.Formats.Png
this.MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true);
}
+ break;
+ case PngChunkType.EmbeddedColorProfile:
+ this.ReadColorProfileChunk(metadata, chunk.Data.GetSpan());
break;
case PngChunkType.End:
goto EOF;
@@ -1174,6 +1178,58 @@ namespace SixLabors.ImageSharp.Formats.Png
return true;
}
+ ///
+ /// Reads the color profile chunk. The data is stored similar to the zTXt chunk.
+ ///
+ /// The metadata.
+ /// The bytes containing the profile.
+ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan data)
+ {
+ int zeroIndex = data.IndexOf((byte)0);
+ if (zeroIndex < PngConstants.MinTextKeywordLength || zeroIndex > PngConstants.MaxTextKeywordLength)
+ {
+ return;
+ }
+
+ byte compressionMethod = data[zeroIndex + 1];
+ if (compressionMethod != 0)
+ {
+ // Only compression method 0 is supported (zlib datastream with deflate compression).
+ return;
+ }
+
+ ReadOnlySpan keywordBytes = data.Slice(0, zeroIndex);
+ if (!this.TryReadTextKeyword(keywordBytes, out string name))
+ {
+ return;
+ }
+
+ ReadOnlySpan compressedData = data.Slice(zeroIndex + 2);
+
+ using (var memoryStream = new MemoryStream(compressedData.ToArray()))
+ using (var bufferedStream = new BufferedReadStream(this.Configuration, memoryStream))
+ using (var inflateStream = new ZlibInflateStream(bufferedStream))
+ {
+ if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
+ {
+ return;
+ }
+
+ var uncompressedBytes = new List();
+
+ // Note: this uses a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here.
+ int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
+ while (bytesRead != 0)
+ {
+ uncompressedBytes.AddRange(this.buffer.AsSpan(0, bytesRead).ToArray());
+ bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
+ }
+
+ byte[] iccpProfileBytes = uncompressedBytes.ToArray();
+ metadata.IccProfile = new IccProfile(iccpProfileBytes);
+ }
+ }
+
///
/// Compares two ReadOnlySpan<char>s in a case-insensitive method.
/// This is only needed because older frameworks are missing the extension method.