From b12badadee65b0270f20741624e45875929abb1f Mon Sep 17 00:00:00 2001 From: Tammo Hinrichs Date: Tue, 21 Nov 2023 14:51:46 +0100 Subject: [PATCH] Added CICP metadata handling to PNG de/encoder --- src/ImageSharp/Formats/Png/PngChunkType.cs | 6 ++++ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 33 +++++++++++++++++++ src/ImageSharp/Formats/Png/PngEncoderCore.cs | 21 ++++++++++++ .../Formats/Png/PngChunkTypeTests.cs | 1 + 4 files changed, 61 insertions(+) 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"));