diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs
index a008bf8ea2..cc41cf5a29 100644
--- a/src/ImageSharp/Formats/Png/PngChunkType.cs
+++ b/src/ImageSharp/Formats/Png/PngChunkType.cs
@@ -140,6 +140,12 @@ internal enum PngChunkType : uint
/// cHRM (Single)
Chroma = 0x6348524d,
+ ///
+ /// If this chunk is present, it specifies the color space, transfer function, matrix coefficients of the image
+ /// using the code points specified in [ITU-T-H.273]
+ ///
+ Cicp = 0x63494350,
+
///
/// This chunk is an ancillary chunk as defined in the PNG Specification.
/// It must appear before the first IDAT chunk within a valid PNG stream.
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index d8305a3f57..acb97ed893 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -16,6 +16,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.CICP;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@@ -183,6 +184,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngChunkType.Gamma:
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
break;
+ case PngChunkType.Cicp:
+ ReadCicpChunk(metadata, chunk.Data.GetSpan());
+ break;
case PngChunkType.FrameControl:
frameCount++;
if (frameCount == this.maxFrames)
@@ -352,6 +356,15 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
break;
+ case PngChunkType.Cicp:
+ if (this.colorMetadataOnly)
+ {
+ this.SkipChunkDataAndCrc(chunk);
+ break;
+ }
+
+ ReadCicpChunk(metadata, chunk.Data.GetSpan());
+ break;
case PngChunkType.FrameControl:
++frameCount;
if (frameCount == this.maxFrames)
@@ -1392,6 +1405,26 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
return false;
}
+ ///
+ /// Reads the CICP color profile chunk.
+ ///
+ /// The metadata.
+ /// The bytes containing the profile.
+ private static void ReadCicpChunk(ImageMetadata metadata, ReadOnlySpan data)
+ {
+ if (data.Length < 4)
+ {
+ // Ignore invalid cICP chunks.
+ return;
+ }
+
+ byte colorPrimaries = data[0];
+ byte transferFunction = data[1];
+ byte matrixCoefficients = data[2];
+ bool? fullRange = data[3] == 1 ? true : data[3] == 0 ? false : null;
+ metadata.CicpProfile = new CicpProfile(colorPrimaries, transferFunction, matrixCoefficients, fullRange);
+ }
+
///
/// Reads exif data encoded into a text chunk with the name "raw profile type exif".
/// This method was used by ImageMagick, exiftool, exiv2, digiKam, etc, before the
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 04e3b1d840..a86fd17cca 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -166,6 +166,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
this.WriteHeaderChunk(stream);
this.WriteGammaChunk(stream);
+ this.WriteCicpChunk(stream, metadata);
this.WriteColorProfileChunk(stream, metadata);
this.WritePaletteChunk(stream, quantized);
this.WriteTransparencyChunk(stream, pngMetadata);
@@ -773,6 +774,26 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
this.WriteChunk(stream, PngChunkType.InternationalText, payload);
}
+ ///
+ /// Writes the CICP profile chunk
+ ///
+ /// The containing image data.
+ /// The image meta data.
+ private void WriteCicpChunk(Stream stream, ImageMetadata metaData)
+ {
+ if (metaData.CicpProfile is null)
+ {
+ return;
+ }
+
+ Span outputBytes = this.chunkDataBuffer.Span[..4];
+ outputBytes[0] = (byte)metaData.CicpProfile.ColorPrimaries;
+ outputBytes[1] = (byte)metaData.CicpProfile.TransferCharacteristics;
+ outputBytes[2] = (byte)metaData.CicpProfile.MatrixCoefficients;
+ outputBytes[3] = (byte)(metaData.CicpProfile.FullRange ? 1 : 0);
+ this.WriteChunk(stream, PngChunkType.Cicp, outputBytes);
+ }
+
///
/// Writes the color profile chunk.
///
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
index 06cb079e5b..02e8dc7dfb 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
@@ -29,6 +29,7 @@ public class PngChunkTypeTests
Assert.Equal(PngChunkType.Background, GetType("bKGD"));
Assert.Equal(PngChunkType.EmbeddedColorProfile, GetType("iCCP"));
Assert.Equal(PngChunkType.StandardRgbColourSpace, GetType("sRGB"));
+ Assert.Equal(PngChunkType.Cicp, GetType("cICP"));
Assert.Equal(PngChunkType.SignificantBits, GetType("sBIT"));
Assert.Equal(PngChunkType.Histogram, GetType("hIST"));
Assert.Equal(PngChunkType.SuggestedPalette, GetType("sPLT"));