Skip to content

Commit

Permalink
Merge branch 'main' into js/fix-2595
Browse files Browse the repository at this point in the history
  • Loading branch information
JimBobSquarePants authored Dec 1, 2023
2 parents 291e9e7 + 0b36698 commit d9ac175
Show file tree
Hide file tree
Showing 16 changed files with 584 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/ImageSharp/Formats/Png/PngChunkType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ internal enum PngChunkType : uint
/// <remarks>cHRM (Single)</remarks>
Chroma = 0x6348524d,

/// <summary>
/// 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]
/// </summary>
Cicp = 0x63494350,

/// <summary>
/// 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.
Expand Down
33 changes: 33 additions & 0 deletions src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Memory.Internals;
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;
Expand Down Expand Up @@ -191,6 +192,9 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
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)
Expand Down Expand Up @@ -360,6 +364,15 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat

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)
Expand Down Expand Up @@ -1426,6 +1439,26 @@ private static bool TryReadTextChunkMetadata(ImageMetadata baseMetadata, string
return false;
}

/// <summary>
/// Reads the CICP color profile chunk.
/// </summary>
/// <param name="metadata">The metadata.</param>
/// <param name="data">The bytes containing the profile.</param>
private static void ReadCicpChunk(ImageMetadata metadata, ReadOnlySpan<byte> 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);
}

/// <summary>
/// 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
Expand Down
27 changes: 27 additions & 0 deletions src/ImageSharp/Formats/Png/PngEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken

this.WriteHeaderChunk(stream);
this.WriteGammaChunk(stream);
this.WriteCicpChunk(stream, metadata);
this.WriteColorProfileChunk(stream, metadata);
this.WritePaletteChunk(stream, quantized);
this.WriteTransparencyChunk(stream, pngMetadata);
Expand Down Expand Up @@ -852,6 +853,32 @@ private void WriteXmpChunk(Stream stream, ImageMetadata meta)
this.WriteChunk(stream, PngChunkType.InternationalText, payload);
}

/// <summary>
/// Writes the CICP profile chunk
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="metaData">The image meta data.</param>
private void WriteCicpChunk(Stream stream, ImageMetadata metaData)
{
if (metaData.CicpProfile is null)
{
return;
}

// by spec, the matrix coefficients must be set to Identity
if (metaData.CicpProfile.MatrixCoefficients != Metadata.Profiles.Cicp.CicpMatrixCoefficients.Identity)
{
throw new NotSupportedException("CICP matrix coefficients other than Identity are not supported in PNG");
}

Span<byte> 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);
}

/// <summary>
/// Writes the color profile chunk.
/// </summary>
Expand Down
26 changes: 25 additions & 1 deletion src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Buffers;
using System.Buffers.Binary;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO;
Expand Down Expand Up @@ -339,10 +340,33 @@ private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata,
return;
}

metadata.ExifProfile = new ExifProfile(exifData);
ExifProfile exifProfile = new(exifData);

// Set the resolution from the metadata.
double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution);
double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution);

if (horizontalValue > 0 && verticalValue > 0)
{
metadata.HorizontalResolution = horizontalValue;
metadata.VerticalResolution = verticalValue;
metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile);
}

metadata.ExifProfile = exifProfile;
}
}

private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag<Rational> tag)
{
if (exifProfile.TryGetValue(tag, out IExifValue<Rational>? resolution))
{
return resolution.Value.ToDouble();
}

return 0;
}

/// <summary>
/// Reads the XMP profile the stream.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/ImageSharp/Metadata/ImageFrameMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.

using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
Expand Down Expand Up @@ -43,6 +44,7 @@ internal ImageFrameMetadata(ImageFrameMetadata other)
this.IccProfile = other.IccProfile?.DeepClone();
this.IptcProfile = other.IptcProfile?.DeepClone();
this.XmpProfile = other.XmpProfile?.DeepClone();
this.CicpProfile = other.CicpProfile?.DeepClone();
}

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

/// <summary>
/// Gets or sets the CICP profile
/// </summary>
public CicpProfile? CicpProfile { get; set; }

/// <inheritdoc/>
public ImageFrameMetadata DeepClone() => new(this);

Expand Down
7 changes: 7 additions & 0 deletions src/ImageSharp/Metadata/ImageMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.

using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
Expand Down Expand Up @@ -68,6 +69,7 @@ private ImageMetadata(ImageMetadata other)
this.IccProfile = other.IccProfile?.DeepClone();
this.IptcProfile = other.IptcProfile?.DeepClone();
this.XmpProfile = other.XmpProfile?.DeepClone();
this.CicpProfile = other.CicpProfile?.DeepClone();

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

/// <summary>
/// Gets or sets the CICP profile.
/// </summary>
public CicpProfile? CicpProfile { get; set; }

/// <summary>
/// Gets the original format, if any, the image was decode from.
/// </summary>
Expand Down
72 changes: 72 additions & 0 deletions src/ImageSharp/Metadata/Profiles/CICP/CicpProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp;

/// <summary>
/// Represents a Cicp profile as per ITU-T H.273 / ISO/IEC 23091-2_2019 providing access to color space information
/// </summary>
public sealed class CicpProfile : IDeepCloneable<CicpProfile>
{
/// <summary>
/// Initializes a new instance of the <see cref="CicpProfile"/> class.
/// </summary>
public CicpProfile()
: this(2, 2, 2, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CicpProfile"/> class.
/// </summary>
/// <param name="colorPrimaries">The color primaries as number according to ITU-T H.273 / ISO/IEC 23091-2_2019.</param>
/// <param name="transferCharacteristics">The transfer characteristics as number according to ITU-T H.273 / ISO/IEC 23091-2_2019.</param>
/// <param name="matrixCoefficients">The matrix coefficients as number according to ITU-T H.273 / ISO/IEC 23091-2_2019.</param>
/// <param name="fullRange">The full range flag, or null if unknown.</param>
public CicpProfile(byte colorPrimaries, byte transferCharacteristics, byte matrixCoefficients, bool? fullRange)
{
this.ColorPrimaries = Enum.IsDefined(typeof(CicpColorPrimaries), colorPrimaries) ? (CicpColorPrimaries)colorPrimaries : CicpColorPrimaries.Unspecified;
this.TransferCharacteristics = Enum.IsDefined(typeof(CicpTransferCharacteristics), transferCharacteristics) ? (CicpTransferCharacteristics)transferCharacteristics : CicpTransferCharacteristics.Unspecified;
this.MatrixCoefficients = Enum.IsDefined(typeof(CicpMatrixCoefficients), matrixCoefficients) ? (CicpMatrixCoefficients)matrixCoefficients : CicpMatrixCoefficients.Unspecified;
this.FullRange = fullRange ?? (this.MatrixCoefficients == CicpMatrixCoefficients.Identity);
}

/// <summary>
/// Initializes a new instance of the <see cref="CicpProfile"/> class
/// by making a copy from another CICP profile.
/// </summary>
/// <param name="other">The other CICP profile, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>>
private CicpProfile(CicpProfile other)
{
Guard.NotNull(other, nameof(other));

this.ColorPrimaries = other.ColorPrimaries;
this.TransferCharacteristics = other.TransferCharacteristics;
this.MatrixCoefficients = other.MatrixCoefficients;
this.FullRange = other.FullRange;
}

/// <summary>
/// Gets or sets the color primaries
/// </summary>
public CicpColorPrimaries ColorPrimaries { get; set; }

/// <summary>
/// Gets or sets the transfer characteristics
/// </summary>
public CicpTransferCharacteristics TransferCharacteristics { get; set; }

/// <summary>
/// Gets or sets the matrix coefficients
/// </summary>
public CicpMatrixCoefficients MatrixCoefficients { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the colors use the full numeric range
/// </summary>
public bool FullRange { get; set; }

/// <inheritdoc/>
public CicpProfile DeepClone() => new(this);
}
86 changes: 86 additions & 0 deletions src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpColorPrimaries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp;

#pragma warning disable CA1707 // Underscores in enum members

/// <summary>
/// Color primaries according to ITU-T H.273 / ISO/IEC 23091-2_2019 subclause 8.1
/// </summary>
public enum CicpColorPrimaries : byte
{
/// <summary>
/// Rec. ITU-R BT.709-6
/// IEC 61966-2-1 sRGB or sYCC
/// IEC 61966-2-4
/// SMPTE RP 177 (1993) Annex B
/// </summary>
ItuRBt709_6 = 1,

/// <summary>
/// Image characteristics are unknown or are determined by the application.
/// </summary>
Unspecified = 2,

/// <summary>
/// Rec. ITU-R BT.470-6 System M (historical)
/// </summary>
ItuRBt470_6M = 4,

/// <summary>
/// Rec. ITU-R BT.601-7 625
/// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
/// </summary>
ItuRBt601_7_625 = 5,

/// <summary>
/// Rec. ITU-R BT.601-7 525
/// Rec. ITU-R BT.1700-0 NTSC
/// SMPTE ST 170 (2004)
/// (functionally the same as the value 7)
/// </summary>
ItuRBt601_7_525 = 6,

/// <summary>
/// SMPTE ST 240 (1999)
/// (functionally the same as the value 6)
/// </summary>
SmpteSt240 = 7,

/// <summary>
/// Generic film (colour filters using Illuminant C)
/// </summary>
GenericFilm = 8,

/// <summary>
/// Rec. ITU-R BT.2020-2
/// Rec. ITU-R BT.2100-2
/// </summary>
ItuRBt2020_2 = 9,

/// <summary>
/// SMPTE ST 428-1 (2019)
/// (CIE 1931 XYZ as in ISO 11664-1)
/// </summary>
SmpteSt428_1 = 10,

/// <summary>
/// SMPTE RP 431-2 (2011)
/// DCI P3
/// </summary>
SmpteRp431_2 = 11,

/// <summary>
/// SMPTE ST 432-1 (2010)
/// P3 D65 / Display P3
/// </summary>
SmpteEg432_1 = 12,

/// <summary>
/// EBU Tech.3213-E
/// </summary>
EbuTech3213E = 22,
}

#pragma warning restore CA1707 // Underscores in enum members
Loading

0 comments on commit d9ac175

Please sign in to comment.