Skip to content

Commit 517ab62

Browse files
Merge branch 'main' into sn/net8
2 parents 0ab0c47 + 07fd936 commit 517ab62

22 files changed

+693
-12
lines changed

src/ImageSharp/Formats/AnimationUtilities.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.CompilerServices;
77
using System.Runtime.InteropServices;
88
using System.Runtime.Intrinsics;
9+
using System.Runtime.Intrinsics.Arm;
910
using System.Runtime.Intrinsics.X86;
1011
using SixLabors.ImageSharp.Advanced;
1112
using SixLabors.ImageSharp.Memory;
@@ -175,7 +176,52 @@ public static (bool Difference, Rectangle Bounds) DeDuplicatePixels<TPixel>(
175176
}
176177
}
177178

178-
// TODO: v4 AdvSimd when we can use .NET 8
179+
if (AdvSimd.IsSupported && remaining >= 4)
180+
{
181+
// Update offset since we may be operating on the remainder previously incremented by pixel steps of 8.
182+
x *= 2;
183+
Vector128<uint> r128 = previousFrame != null ? Vector128.Create(bg.PackedValue) : Vector128<uint>.Zero;
184+
Vector128<uint> vmb128 = Vector128<uint>.Zero;
185+
if (blend)
186+
{
187+
vmb128 = AdvSimd.CompareEqual(vmb128, vmb128);
188+
}
189+
190+
while (remaining >= 4)
191+
{
192+
Vector128<uint> p = Unsafe.Add(ref Unsafe.As<Vector256<byte>, Vector128<uint>>(ref previousBase256), x);
193+
Vector128<uint> c = Unsafe.Add(ref Unsafe.As<Vector256<byte>, Vector128<uint>>(ref currentBase256), x);
194+
195+
Vector128<uint> eq = AdvSimd.CompareEqual(p, c);
196+
Vector128<uint> r = SimdUtils.HwIntrinsics.BlendVariable(c, r128, AdvSimd.And(eq, vmb128));
197+
198+
if (nextFrame != null)
199+
{
200+
Vector128<int> n = AdvSimd.ShiftRightLogical(Unsafe.Add(ref Unsafe.As<Vector256<byte>, Vector128<uint>>(ref nextBase256), x), 24).AsInt32();
201+
eq = AdvSimd.BitwiseClear(eq, AdvSimd.CompareGreaterThan(AdvSimd.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32());
202+
}
203+
204+
Unsafe.Add(ref Unsafe.As<Vector256<byte>, Vector128<uint>>(ref resultBase256), x) = r;
205+
206+
ulong msk = ~AdvSimd.ExtractNarrowingLower(eq).AsUInt64().ToScalar();
207+
if (msk != 0)
208+
{
209+
// If is diff is found, the left side is marked by the min of previously found left side and the start position.
210+
// The right is the max of the previously found right side and the end position.
211+
int start = i + (BitOperations.TrailingZeroCount(msk) / 16);
212+
int end = i + (4 - (BitOperations.LeadingZeroCount(msk) / 16));
213+
left = Math.Min(left, start);
214+
right = Math.Max(right, end);
215+
hasRowDiff = true;
216+
hasDiff = true;
217+
}
218+
219+
x++;
220+
i += 4;
221+
remaining -= 4;
222+
}
223+
}
224+
179225
for (i = remaining; i > 0; i--)
180226
{
181227
x = (uint)(length - i);

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
@@ -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

Lines changed: 27 additions & 0 deletions
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

Lines changed: 25 additions & 1 deletion
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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>
Lines changed: 72 additions & 0 deletions
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+
}

0 commit comments

Comments
 (0)