Skip to content

Commit b12bada

Browse files
Added CICP metadata handling to PNG de/encoder
1 parent 155bdbe commit b12bada

4 files changed

Lines changed: 61 additions & 0 deletions

File tree

src/ImageSharp/Formats/Png/PngChunkType.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ internal enum PngChunkType : uint
140140
/// <remarks>cHRM (Single)</remarks>
141141
Chroma = 0x6348524d,
142142

143+
/// <summary>
144+
/// If this chunk is present, it specifies the color space, transfer function, matrix coefficients of the image
145+
/// using the code points specified in [ITU-T-H.273]
146+
/// </summary>
147+
Cicp = 0x63494350,
148+
143149
/// <summary>
144150
/// This chunk is an ancillary chunk as defined in the PNG Specification.
145151
/// It must appear before the first IDAT chunk within a valid PNG stream.

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using SixLabors.ImageSharp.IO;
1717
using SixLabors.ImageSharp.Memory;
1818
using SixLabors.ImageSharp.Metadata;
19+
using SixLabors.ImageSharp.Metadata.Profiles.CICP;
1920
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
2021
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
2122
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@@ -183,6 +184,9 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
183184
case PngChunkType.Gamma:
184185
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
185186
break;
187+
case PngChunkType.Cicp:
188+
ReadCicpChunk(metadata, chunk.Data.GetSpan());
189+
break;
186190
case PngChunkType.FrameControl:
187191
frameCount++;
188192
if (frameCount == this.maxFrames)
@@ -352,6 +356,15 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
352356

353357
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
354358
break;
359+
case PngChunkType.Cicp:
360+
if (this.colorMetadataOnly)
361+
{
362+
this.SkipChunkDataAndCrc(chunk);
363+
break;
364+
}
365+
366+
ReadCicpChunk(metadata, chunk.Data.GetSpan());
367+
break;
355368
case PngChunkType.FrameControl:
356369
++frameCount;
357370
if (frameCount == this.maxFrames)
@@ -1392,6 +1405,26 @@ private static bool TryReadTextChunkMetadata(ImageMetadata baseMetadata, string
13921405
return false;
13931406
}
13941407

1408+
/// <summary>
1409+
/// Reads the CICP color profile chunk.
1410+
/// </summary>
1411+
/// <param name="metadata">The metadata.</param>
1412+
/// <param name="data">The bytes containing the profile.</param>
1413+
private static void ReadCicpChunk(ImageMetadata metadata, ReadOnlySpan<byte> data)
1414+
{
1415+
if (data.Length < 4)
1416+
{
1417+
// Ignore invalid cICP chunks.
1418+
return;
1419+
}
1420+
1421+
byte colorPrimaries = data[0];
1422+
byte transferFunction = data[1];
1423+
byte matrixCoefficients = data[2];
1424+
bool? fullRange = data[3] == 1 ? true : data[3] == 0 ? false : null;
1425+
metadata.CicpProfile = new CicpProfile(colorPrimaries, transferFunction, matrixCoefficients, fullRange);
1426+
}
1427+
13951428
/// <summary>
13961429
/// Reads exif data encoded into a text chunk with the name "raw profile type exif".
13971430
/// This method was used by ImageMagick, exiftool, exiv2, digiKam, etc, before the

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
166166

167167
this.WriteHeaderChunk(stream);
168168
this.WriteGammaChunk(stream);
169+
this.WriteCicpChunk(stream, metadata);
169170
this.WriteColorProfileChunk(stream, metadata);
170171
this.WritePaletteChunk(stream, quantized);
171172
this.WriteTransparencyChunk(stream, pngMetadata);
@@ -773,6 +774,26 @@ private void WriteXmpChunk(Stream stream, ImageMetadata meta)
773774
this.WriteChunk(stream, PngChunkType.InternationalText, payload);
774775
}
775776

777+
/// <summary>
778+
/// Writes the CICP profile chunk
779+
/// </summary>
780+
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
781+
/// <param name="metaData">The image meta data.</param>
782+
private void WriteCicpChunk(Stream stream, ImageMetadata metaData)
783+
{
784+
if (metaData.CicpProfile is null)
785+
{
786+
return;
787+
}
788+
789+
Span<byte> outputBytes = this.chunkDataBuffer.Span[..4];
790+
outputBytes[0] = (byte)metaData.CicpProfile.ColorPrimaries;
791+
outputBytes[1] = (byte)metaData.CicpProfile.TransferCharacteristics;
792+
outputBytes[2] = (byte)metaData.CicpProfile.MatrixCoefficients;
793+
outputBytes[3] = (byte)(metaData.CicpProfile.FullRange ? 1 : 0);
794+
this.WriteChunk(stream, PngChunkType.Cicp, outputBytes);
795+
}
796+
776797
/// <summary>
777798
/// Writes the color profile chunk.
778799
/// </summary>

tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public void ChunkTypeIdsAreCorrect()
2929
Assert.Equal(PngChunkType.Background, GetType("bKGD"));
3030
Assert.Equal(PngChunkType.EmbeddedColorProfile, GetType("iCCP"));
3131
Assert.Equal(PngChunkType.StandardRgbColourSpace, GetType("sRGB"));
32+
Assert.Equal(PngChunkType.Cicp, GetType("cICP"));
3233
Assert.Equal(PngChunkType.SignificantBits, GetType("sBIT"));
3334
Assert.Equal(PngChunkType.Histogram, GetType("hIST"));
3435
Assert.Equal(PngChunkType.SuggestedPalette, GetType("sPLT"));

0 commit comments

Comments
 (0)