Skip to content

Commit d9ac175

Browse files
Merge branch 'main' into js/fix-2595
2 parents 291e9e7 + 0b36698 commit d9ac175

16 files changed

+584
-1
lines changed

src/ImageSharp/Formats/Png/PngChunkType.cs

+6
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

+33
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using SixLabors.ImageSharp.Memory;
1818
using SixLabors.ImageSharp.Memory.Internals;
1919
using SixLabors.ImageSharp.Metadata;
20+
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
2021
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
2122
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
2223
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@@ -191,6 +192,9 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
191192
case PngChunkType.Gamma:
192193
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
193194
break;
195+
case PngChunkType.Cicp:
196+
ReadCicpChunk(metadata, chunk.Data.GetSpan());
197+
break;
194198
case PngChunkType.FrameControl:
195199
frameCount++;
196200
if (frameCount == this.maxFrames)
@@ -360,6 +364,15 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
360364

361365
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
362366
break;
367+
case PngChunkType.Cicp:
368+
if (this.colorMetadataOnly)
369+
{
370+
this.SkipChunkDataAndCrc(chunk);
371+
break;
372+
}
373+
374+
ReadCicpChunk(metadata, chunk.Data.GetSpan());
375+
break;
363376
case PngChunkType.FrameControl:
364377
++frameCount;
365378
if (frameCount == this.maxFrames)
@@ -1426,6 +1439,26 @@ private static bool TryReadTextChunkMetadata(ImageMetadata baseMetadata, string
14261439
return false;
14271440
}
14281441

1442+
/// <summary>
1443+
/// Reads the CICP color profile chunk.
1444+
/// </summary>
1445+
/// <param name="metadata">The metadata.</param>
1446+
/// <param name="data">The bytes containing the profile.</param>
1447+
private static void ReadCicpChunk(ImageMetadata metadata, ReadOnlySpan<byte> data)
1448+
{
1449+
if (data.Length < 4)
1450+
{
1451+
// Ignore invalid cICP chunks.
1452+
return;
1453+
}
1454+
1455+
byte colorPrimaries = data[0];
1456+
byte transferFunction = data[1];
1457+
byte matrixCoefficients = data[2];
1458+
bool? fullRange = data[3] == 1 ? true : data[3] == 0 ? false : null;
1459+
metadata.CicpProfile = new CicpProfile(colorPrimaries, transferFunction, matrixCoefficients, fullRange);
1460+
}
1461+
14291462
/// <summary>
14301463
/// Reads exif data encoded into a text chunk with the name "raw profile type exif".
14311464
/// This method was used by ImageMagick, exiftool, exiv2, digiKam, etc, before the

src/ImageSharp/Formats/Png/PngEncoderCore.cs

+27
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
177177

178178
this.WriteHeaderChunk(stream);
179179
this.WriteGammaChunk(stream);
180+
this.WriteCicpChunk(stream, metadata);
180181
this.WriteColorProfileChunk(stream, metadata);
181182
this.WritePaletteChunk(stream, quantized);
182183
this.WriteTransparencyChunk(stream, pngMetadata);
@@ -852,6 +853,32 @@ private void WriteXmpChunk(Stream stream, ImageMetadata meta)
852853
this.WriteChunk(stream, PngChunkType.InternationalText, payload);
853854
}
854855

856+
/// <summary>
857+
/// Writes the CICP profile chunk
858+
/// </summary>
859+
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
860+
/// <param name="metaData">The image meta data.</param>
861+
private void WriteCicpChunk(Stream stream, ImageMetadata metaData)
862+
{
863+
if (metaData.CicpProfile is null)
864+
{
865+
return;
866+
}
867+
868+
// by spec, the matrix coefficients must be set to Identity
869+
if (metaData.CicpProfile.MatrixCoefficients != Metadata.Profiles.Cicp.CicpMatrixCoefficients.Identity)
870+
{
871+
throw new NotSupportedException("CICP matrix coefficients other than Identity are not supported in PNG");
872+
}
873+
874+
Span<byte> outputBytes = this.chunkDataBuffer.Span[..4];
875+
outputBytes[0] = (byte)metaData.CicpProfile.ColorPrimaries;
876+
outputBytes[1] = (byte)metaData.CicpProfile.TransferCharacteristics;
877+
outputBytes[2] = (byte)metaData.CicpProfile.MatrixCoefficients;
878+
outputBytes[3] = (byte)(metaData.CicpProfile.FullRange ? 1 : 0);
879+
this.WriteChunk(stream, PngChunkType.Cicp, outputBytes);
880+
}
881+
855882
/// <summary>
856883
/// Writes the color profile chunk.
857884
/// </summary>

src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

+25-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Buffers;
55
using System.Buffers.Binary;
6+
using SixLabors.ImageSharp.Common.Helpers;
67
using SixLabors.ImageSharp.Formats.Webp.Lossless;
78
using SixLabors.ImageSharp.Formats.Webp.Lossy;
89
using SixLabors.ImageSharp.IO;
@@ -339,10 +340,33 @@ private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata,
339340
return;
340341
}
341342

342-
metadata.ExifProfile = new ExifProfile(exifData);
343+
ExifProfile exifProfile = new(exifData);
344+
345+
// Set the resolution from the metadata.
346+
double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution);
347+
double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution);
348+
349+
if (horizontalValue > 0 && verticalValue > 0)
350+
{
351+
metadata.HorizontalResolution = horizontalValue;
352+
metadata.VerticalResolution = verticalValue;
353+
metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile);
354+
}
355+
356+
metadata.ExifProfile = exifProfile;
343357
}
344358
}
345359

360+
private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag<Rational> tag)
361+
{
362+
if (exifProfile.TryGetValue(tag, out IExifValue<Rational>? resolution))
363+
{
364+
return resolution.Value.ToDouble();
365+
}
366+
367+
return 0;
368+
}
369+
346370
/// <summary>
347371
/// Reads the XMP profile the stream.
348372
/// </summary>

src/ImageSharp/Metadata/ImageFrameMetadata.cs

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using SixLabors.ImageSharp.Formats;
5+
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
56
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
67
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
78
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
@@ -43,6 +44,7 @@ internal ImageFrameMetadata(ImageFrameMetadata other)
4344
this.IccProfile = other.IccProfile?.DeepClone();
4445
this.IptcProfile = other.IptcProfile?.DeepClone();
4546
this.XmpProfile = other.XmpProfile?.DeepClone();
47+
this.CicpProfile = other.CicpProfile?.DeepClone();
4648
}
4749

4850
/// <summary>
@@ -65,6 +67,11 @@ internal ImageFrameMetadata(ImageFrameMetadata other)
6567
/// </summary>
6668
public IptcProfile? IptcProfile { get; set; }
6769

70+
/// <summary>
71+
/// Gets or sets the CICP profile
72+
/// </summary>
73+
public CicpProfile? CicpProfile { get; set; }
74+
6875
/// <inheritdoc/>
6976
public ImageFrameMetadata DeepClone() => new(this);
7077

src/ImageSharp/Metadata/ImageMetadata.cs

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using SixLabors.ImageSharp.Formats;
5+
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
56
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
67
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
78
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
@@ -68,6 +69,7 @@ private ImageMetadata(ImageMetadata other)
6869
this.IccProfile = other.IccProfile?.DeepClone();
6970
this.IptcProfile = other.IptcProfile?.DeepClone();
7071
this.XmpProfile = other.XmpProfile?.DeepClone();
72+
this.CicpProfile = other.CicpProfile?.DeepClone();
7173

7274
// NOTE: This clone is actually shallow but we share the same format
7375
// instances for all images in the configuration.
@@ -157,6 +159,11 @@ public double VerticalResolution
157159
/// </summary>
158160
public IptcProfile? IptcProfile { get; set; }
159161

162+
/// <summary>
163+
/// Gets or sets the CICP profile.
164+
/// </summary>
165+
public CicpProfile? CicpProfile { get; set; }
166+
160167
/// <summary>
161168
/// Gets the original format, if any, the image was decode from.
162169
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp;
5+
6+
/// <summary>
7+
/// Represents a Cicp profile as per ITU-T H.273 / ISO/IEC 23091-2_2019 providing access to color space information
8+
/// </summary>
9+
public sealed class CicpProfile : IDeepCloneable<CicpProfile>
10+
{
11+
/// <summary>
12+
/// Initializes a new instance of the <see cref="CicpProfile"/> class.
13+
/// </summary>
14+
public CicpProfile()
15+
: this(2, 2, 2, null)
16+
{
17+
}
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="CicpProfile"/> class.
21+
/// </summary>
22+
/// <param name="colorPrimaries">The color primaries as number according to ITU-T H.273 / ISO/IEC 23091-2_2019.</param>
23+
/// <param name="transferCharacteristics">The transfer characteristics as number according to ITU-T H.273 / ISO/IEC 23091-2_2019.</param>
24+
/// <param name="matrixCoefficients">The matrix coefficients as number according to ITU-T H.273 / ISO/IEC 23091-2_2019.</param>
25+
/// <param name="fullRange">The full range flag, or null if unknown.</param>
26+
public CicpProfile(byte colorPrimaries, byte transferCharacteristics, byte matrixCoefficients, bool? fullRange)
27+
{
28+
this.ColorPrimaries = Enum.IsDefined(typeof(CicpColorPrimaries), colorPrimaries) ? (CicpColorPrimaries)colorPrimaries : CicpColorPrimaries.Unspecified;
29+
this.TransferCharacteristics = Enum.IsDefined(typeof(CicpTransferCharacteristics), transferCharacteristics) ? (CicpTransferCharacteristics)transferCharacteristics : CicpTransferCharacteristics.Unspecified;
30+
this.MatrixCoefficients = Enum.IsDefined(typeof(CicpMatrixCoefficients), matrixCoefficients) ? (CicpMatrixCoefficients)matrixCoefficients : CicpMatrixCoefficients.Unspecified;
31+
this.FullRange = fullRange ?? (this.MatrixCoefficients == CicpMatrixCoefficients.Identity);
32+
}
33+
34+
/// <summary>
35+
/// Initializes a new instance of the <see cref="CicpProfile"/> class
36+
/// by making a copy from another CICP profile.
37+
/// </summary>
38+
/// <param name="other">The other CICP profile, where the clone should be made from.</param>
39+
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>>
40+
private CicpProfile(CicpProfile other)
41+
{
42+
Guard.NotNull(other, nameof(other));
43+
44+
this.ColorPrimaries = other.ColorPrimaries;
45+
this.TransferCharacteristics = other.TransferCharacteristics;
46+
this.MatrixCoefficients = other.MatrixCoefficients;
47+
this.FullRange = other.FullRange;
48+
}
49+
50+
/// <summary>
51+
/// Gets or sets the color primaries
52+
/// </summary>
53+
public CicpColorPrimaries ColorPrimaries { get; set; }
54+
55+
/// <summary>
56+
/// Gets or sets the transfer characteristics
57+
/// </summary>
58+
public CicpTransferCharacteristics TransferCharacteristics { get; set; }
59+
60+
/// <summary>
61+
/// Gets or sets the matrix coefficients
62+
/// </summary>
63+
public CicpMatrixCoefficients MatrixCoefficients { get; set; }
64+
65+
/// <summary>
66+
/// Gets or sets a value indicating whether the colors use the full numeric range
67+
/// </summary>
68+
public bool FullRange { get; set; }
69+
70+
/// <inheritdoc/>
71+
public CicpProfile DeepClone() => new(this);
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp;
5+
6+
#pragma warning disable CA1707 // Underscores in enum members
7+
8+
/// <summary>
9+
/// Color primaries according to ITU-T H.273 / ISO/IEC 23091-2_2019 subclause 8.1
10+
/// </summary>
11+
public enum CicpColorPrimaries : byte
12+
{
13+
/// <summary>
14+
/// Rec. ITU-R BT.709-6
15+
/// IEC 61966-2-1 sRGB or sYCC
16+
/// IEC 61966-2-4
17+
/// SMPTE RP 177 (1993) Annex B
18+
/// </summary>
19+
ItuRBt709_6 = 1,
20+
21+
/// <summary>
22+
/// Image characteristics are unknown or are determined by the application.
23+
/// </summary>
24+
Unspecified = 2,
25+
26+
/// <summary>
27+
/// Rec. ITU-R BT.470-6 System M (historical)
28+
/// </summary>
29+
ItuRBt470_6M = 4,
30+
31+
/// <summary>
32+
/// Rec. ITU-R BT.601-7 625
33+
/// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
34+
/// </summary>
35+
ItuRBt601_7_625 = 5,
36+
37+
/// <summary>
38+
/// Rec. ITU-R BT.601-7 525
39+
/// Rec. ITU-R BT.1700-0 NTSC
40+
/// SMPTE ST 170 (2004)
41+
/// (functionally the same as the value 7)
42+
/// </summary>
43+
ItuRBt601_7_525 = 6,
44+
45+
/// <summary>
46+
/// SMPTE ST 240 (1999)
47+
/// (functionally the same as the value 6)
48+
/// </summary>
49+
SmpteSt240 = 7,
50+
51+
/// <summary>
52+
/// Generic film (colour filters using Illuminant C)
53+
/// </summary>
54+
GenericFilm = 8,
55+
56+
/// <summary>
57+
/// Rec. ITU-R BT.2020-2
58+
/// Rec. ITU-R BT.2100-2
59+
/// </summary>
60+
ItuRBt2020_2 = 9,
61+
62+
/// <summary>
63+
/// SMPTE ST 428-1 (2019)
64+
/// (CIE 1931 XYZ as in ISO 11664-1)
65+
/// </summary>
66+
SmpteSt428_1 = 10,
67+
68+
/// <summary>
69+
/// SMPTE RP 431-2 (2011)
70+
/// DCI P3
71+
/// </summary>
72+
SmpteRp431_2 = 11,
73+
74+
/// <summary>
75+
/// SMPTE ST 432-1 (2010)
76+
/// P3 D65 / Display P3
77+
/// </summary>
78+
SmpteEg432_1 = 12,
79+
80+
/// <summary>
81+
/// EBU Tech.3213-E
82+
/// </summary>
83+
EbuTech3213E = 22,
84+
}
85+
86+
#pragma warning restore CA1707 // Underscores in enum members

0 commit comments

Comments
 (0)