diff --git a/src/ImageSharp/Formats/AnimationUtilities.cs b/src/ImageSharp/Formats/AnimationUtilities.cs index 67ee72e95..288f3d132 100644 --- a/src/ImageSharp/Formats/AnimationUtilities.cs +++ b/src/ImageSharp/Formats/AnimationUtilities.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -175,7 +176,52 @@ internal static class AnimationUtilities } } - // TODO: v4 AdvSimd when we can use .NET 8 + if (AdvSimd.IsSupported && remaining >= 4) + { + // Update offset since we may be operating on the remainder previously incremented by pixel steps of 8. + x *= 2; + Vector128 r128 = previousFrame != null ? Vector128.Create(bg.PackedValue) : Vector128.Zero; + Vector128 vmb128 = Vector128.Zero; + if (blend) + { + vmb128 = AdvSimd.CompareEqual(vmb128, vmb128); + } + + while (remaining >= 4) + { + Vector128 p = Unsafe.Add(ref Unsafe.As, Vector128>(ref previousBase256), x); + Vector128 c = Unsafe.Add(ref Unsafe.As, Vector128>(ref currentBase256), x); + + Vector128 eq = AdvSimd.CompareEqual(p, c); + Vector128 r = SimdUtils.HwIntrinsics.BlendVariable(c, r128, AdvSimd.And(eq, vmb128)); + + if (nextFrame != null) + { + Vector128 n = AdvSimd.ShiftRightLogical(Unsafe.Add(ref Unsafe.As, Vector128>(ref nextBase256), x), 24).AsInt32(); + eq = AdvSimd.BitwiseClear(eq, AdvSimd.CompareGreaterThan(AdvSimd.ShiftRightLogical(c, 24).AsInt32(), n).AsUInt32()); + } + + Unsafe.Add(ref Unsafe.As, Vector128>(ref resultBase256), x) = r; + + ulong msk = ~AdvSimd.ExtractNarrowingLower(eq).AsUInt64().ToScalar(); + if (msk != 0) + { + // If is diff is found, the left side is marked by the min of previously found left side and the start position. + // The right is the max of the previously found right side and the end position. + int start = i + (BitOperations.TrailingZeroCount(msk) / 16); + int end = i + (4 - (BitOperations.LeadingZeroCount(msk) / 16)); + left = Math.Min(left, start); + right = Math.Max(right, end); + hasRowDiff = true; + hasDiff = true; + } + + x++; + i += 4; + remaining -= 4; + } + } + for (i = remaining; i > 0; i--) { x = (uint)(length - i); diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs index a008bf8ea..cc41cf5a2 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 0cbec6fb6..3e6c91444 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -17,6 +17,7 @@ using SixLabors.ImageSharp.IO; 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; @@ -191,6 +192,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) @@ -360,6 +364,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) @@ -1426,6 +1439,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 932916dec..ddef1c9cd 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -177,6 +177,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); @@ -852,6 +853,32 @@ 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; + } + + // 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 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/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index de188b137..69a0afcd9 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -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; @@ -339,10 +340,33 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable 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 tag) + { + if (exifProfile.TryGetValue(tag, out IExifValue? resolution)) + { + return resolution.Value.ToDouble(); + } + + return 0; + } + /// /// Reads the XMP profile the stream. /// diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 03f628afa..1c0330d5d 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -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; @@ -43,6 +44,7 @@ public sealed class ImageFrameMetadata : IDeepCloneable this.IccProfile = other.IccProfile?.DeepClone(); this.IptcProfile = other.IptcProfile?.DeepClone(); this.XmpProfile = other.XmpProfile?.DeepClone(); + this.CicpProfile = other.CicpProfile?.DeepClone(); } /// @@ -65,6 +67,11 @@ public sealed class ImageFrameMetadata : IDeepCloneable /// public IptcProfile? IptcProfile { get; set; } + /// + /// Gets or sets the CICP profile + /// + public CicpProfile? CicpProfile { get; set; } + /// public ImageFrameMetadata DeepClone() => new(this); diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index e1284b50e..6b62be08f 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -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; @@ -68,6 +69,7 @@ public sealed class ImageMetadata : IDeepCloneable 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. @@ -157,6 +159,11 @@ public sealed class ImageMetadata : IDeepCloneable /// public IptcProfile? IptcProfile { get; set; } + /// + /// Gets or sets the CICP profile. + /// + public CicpProfile? CicpProfile { get; set; } + /// /// Gets the original format, if any, the image was decode from. /// diff --git a/src/ImageSharp/Metadata/Profiles/CICP/CicpProfile.cs b/src/ImageSharp/Metadata/Profiles/CICP/CicpProfile.cs new file mode 100644 index 000000000..2657903df --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/CICP/CicpProfile.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp; + +/// +/// Represents a Cicp profile as per ITU-T H.273 / ISO/IEC 23091-2_2019 providing access to color space information +/// +public sealed class CicpProfile : IDeepCloneable +{ + /// + /// Initializes a new instance of the class. + /// + public CicpProfile() + : this(2, 2, 2, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The color primaries as number according to ITU-T H.273 / ISO/IEC 23091-2_2019. + /// The transfer characteristics as number according to ITU-T H.273 / ISO/IEC 23091-2_2019. + /// The matrix coefficients as number according to ITU-T H.273 / ISO/IEC 23091-2_2019. + /// The full range flag, or null if unknown. + 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); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another CICP profile. + /// + /// The other CICP profile, where the clone should be made from. + /// is null.> + 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; + } + + /// + /// Gets or sets the color primaries + /// + public CicpColorPrimaries ColorPrimaries { get; set; } + + /// + /// Gets or sets the transfer characteristics + /// + public CicpTransferCharacteristics TransferCharacteristics { get; set; } + + /// + /// Gets or sets the matrix coefficients + /// + public CicpMatrixCoefficients MatrixCoefficients { get; set; } + + /// + /// Gets or sets a value indicating whether the colors use the full numeric range + /// + public bool FullRange { get; set; } + + /// + public CicpProfile DeepClone() => new(this); +} diff --git a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpColorPrimaries.cs b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpColorPrimaries.cs new file mode 100644 index 000000000..bab888dd7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpColorPrimaries.cs @@ -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 + +/// +/// Color primaries according to ITU-T H.273 / ISO/IEC 23091-2_2019 subclause 8.1 +/// +public enum CicpColorPrimaries : byte +{ + /// + /// Rec. ITU-R BT.709-6 + /// IEC 61966-2-1 sRGB or sYCC + /// IEC 61966-2-4 + /// SMPTE RP 177 (1993) Annex B + /// + ItuRBt709_6 = 1, + + /// + /// Image characteristics are unknown or are determined by the application. + /// + Unspecified = 2, + + /// + /// Rec. ITU-R BT.470-6 System M (historical) + /// + ItuRBt470_6M = 4, + + /// + /// Rec. ITU-R BT.601-7 625 + /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + /// + ItuRBt601_7_625 = 5, + + /// + /// 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) + /// + ItuRBt601_7_525 = 6, + + /// + /// SMPTE ST 240 (1999) + /// (functionally the same as the value 6) + /// + SmpteSt240 = 7, + + /// + /// Generic film (colour filters using Illuminant C) + /// + GenericFilm = 8, + + /// + /// Rec. ITU-R BT.2020-2 + /// Rec. ITU-R BT.2100-2 + /// + ItuRBt2020_2 = 9, + + /// + /// SMPTE ST 428-1 (2019) + /// (CIE 1931 XYZ as in ISO 11664-1) + /// + SmpteSt428_1 = 10, + + /// + /// SMPTE RP 431-2 (2011) + /// DCI P3 + /// + SmpteRp431_2 = 11, + + /// + /// SMPTE ST 432-1 (2010) + /// P3 D65 / Display P3 + /// + SmpteEg432_1 = 12, + + /// + /// EBU Tech.3213-E + /// + EbuTech3213E = 22, +} + +#pragma warning restore CA1707 // Underscores in enum members diff --git a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpMatrixCoefficients.cs b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpMatrixCoefficients.cs new file mode 100644 index 000000000..931beac84 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpMatrixCoefficients.cs @@ -0,0 +1,96 @@ +// 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 + +/// +/// Matrix coefficients according to ITU-T H.273 / ISO/IEC 23091-2_2019 subclause 8.3 +/// +public enum CicpMatrixCoefficients : byte +{ + /// + /// The identity matrix. + /// IEC 61966-2-1 sRGB + /// SMPTE ST 428-1 (2019) + /// + Identity = 0, + + /// + /// Rec. ITU-R BT.709-6 + /// IEC 61966-2-4 xvYCC709 + /// SMPTE RP 177 (1993) Annex B + /// + ItuRBt709_6 = 1, + + /// + /// Image characteristics are unknown or are determined by the application. + /// + Unspecified = 2, + + /// + /// FCC Title 47 Code of Federal Regulations 73.682 (a) (20) + /// + Fcc47 = 4, + + /// + /// Rec. ITU-R BT.601-7 625 + /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + /// IEC 61966-2-1 sYCC + /// IEC 61966-2-4 xvYCC601 + /// (functionally the same as the value 6) + /// + ItuRBt601_7_625 = 5, + + /// + /// Rec. ITU-R BT.601-7 525 + /// Rec. ITU-R BT.1700-0 NTSC + /// SMPTE ST 170 (2004) + /// (functionally the same as the value 5) + /// + ItuRBt601_7_525 = 6, + + /// + /// SMPTE ST 240 (1999) + /// + SmpteSt240 = 7, + + /// + /// YCgCo + /// + YCgCo = 8, + + /// + /// Rec. ITU-R BT.2020-2 (non-constant luminance) + /// Rec. ITU-R BT.2100-2 Y′CbCr + /// + ItuRBt2020_2_Ncl = 9, + + /// + /// Rec. ITU-R BT.2020-2 (constant luminance) + /// + ItuRBt2020_2_Cl = 10, + + /// + /// SMPTE ST 2085 (2015) + /// + SmpteSt2085 = 11, + + /// + /// Chromaticity-derived non-constant luminance system + /// + ChromaDerivedNcl = 12, + + /// + /// Chromaticity-derived constant luminance system + /// + ChromaDerivedCl = 13, + + /// + /// Rec. ITU-R BT.2100-2 ICtCp + /// + ICtCp = 14, +} + +#pragma warning restore CA1707 // Underscores in enum members diff --git a/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpTransferCharacteristics.cs b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpTransferCharacteristics.cs new file mode 100644 index 000000000..86eea0b70 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/CICP/Enums/CicpTransferCharacteristics.cs @@ -0,0 +1,109 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Cicp; + +#pragma warning disable CA1707 // Underscores in enum values + +/// +/// Transfer characteristics according to ITU-T H.273 / ISO/IEC 23091-2_2019 subclause 8.2 +/// /// +public enum CicpTransferCharacteristics : byte +{ + /// + /// Rec. ITU-R BT.709-6 + /// (functionally the same as the values 6, 14 and 15) + /// + ItuRBt709_6 = 1, + + /// + /// Image characteristics are unknown or are determined by the application. + /// + Unspecified = 2, + + /// + /// Assumed display gamma 2.2 + /// Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + /// + Gamma2_2 = 4, + + /// + /// Assumed display gamma 2.8 + /// Rec. ITU-R BT.470-6 System B, G (historical) + /// + Gamma2_8 = 5, + + /// + /// Rec. ITU-R BT.601-7 525 or 625 + /// Rec. ITU-R BT.1700-0 NTSC + /// SMPTE ST 170 (2004) + /// (functionally the same as the values 1, 14 and 15) + /// + ItuRBt601_7 = 6, + + /// + /// SMPTE ST 240 (1999) + /// + SmpteSt240 = 7, + + /// + /// Linear transfer characteristics + /// + Linear = 8, + + /// + /// Logarithmic transfer characteristic (100:1 range) + /// + Log100 = 9, + + /// + /// Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range) + /// + Log100Sqrt = 10, + + /// + /// IEC 61966-2-4 + /// + Iec61966_2_4 = 11, + + /// + /// Rec. ITU-R BT.1361-0 extended colour gamut system (historical) + /// + ItuRBt1361_0 = 12, + + /// + /// IEC 61966-2-1 sRGB or sYCC / Display P3 + /// + Iec61966_2_1 = 13, + + /// + /// Rec. ITU-R BT.2020-2 (10-bit system) + /// (functionally the same as the values 1, 6 and 15) + /// + ItuRBt2020_2_10bit = 14, + + /// + /// Rec. ITU-R BT.2020-2 (12-bit system) + /// (functionally the same as the values 1, 6 and 14) + /// /// + ItuRBt2020_2_12bit = 15, + + /// + /// SMPTE ST 2084 (2014) for 10-, 12-, 14- and 16-bit systems + /// Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system + /// + SmpteSt2084 = 16, + + /// + /// SMPTE ST 428-1 (2019) + /// + SmpteSt428_1 = 17, + + /// + /// ARIB STD-B67 (2015) + /// Rec. ITU-R BT.2100-2 hybrid log-gamma (HLG) system + /// + AribStdB67 = 18, +} + +#pragma warning restore CA1707 // Underscores in enum members diff --git a/src/ImageSharp/Metadata/Profiles/CICP/T-REC-H.273-202107-S!!PDF-E.pdf b/src/ImageSharp/Metadata/Profiles/CICP/T-REC-H.273-202107-S!!PDF-E.pdf new file mode 100644 index 000000000..12086dd77 Binary files /dev/null and b/src/ImageSharp/Metadata/Profiles/CICP/T-REC-H.273-202107-S!!PDF-E.pdf differ diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs index 2a8f80ebe..5473a602f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs @@ -40,6 +40,9 @@ public partial struct Rgb24 Span greenChannel, Span blueChannel, ReadOnlySpan source) - => SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); + { + GuardUnpackIntoRgbPlanes(redChannel, greenChannel, blueChannel, source); + SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); + } } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 9d93d27ac..beebec828 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -165,7 +165,7 @@ public partial class PixelOperations } /// - /// Bulk operation that packs 3 seperate RGB channels to . + /// Bulk operation that packs 3 separate RGB channels to . /// The destination must have a padding of 3. /// /// A to the red values. @@ -198,7 +198,7 @@ public partial class PixelOperations /// /// Bulk operation that unpacks pixels from - /// into 3 seperate RGB channels. The destination must have a padding of 3. + /// into 3 separate RGB channels. /// /// A to the red values. /// A to the green values. @@ -210,7 +210,9 @@ public partial class PixelOperations Span blueChannel, ReadOnlySpan source) { - int count = redChannel.Length; + GuardUnpackIntoRgbPlanes(redChannel, greenChannel, blueChannel, source); + + int count = source.Length; Rgba32 rgba32 = default; @@ -227,6 +229,14 @@ public partial class PixelOperations } } + [MethodImpl(InliningOptions.ShortMethod)] + internal static void GuardUnpackIntoRgbPlanes(Span redChannel, Span greenChannel, Span blueChannel, ReadOnlySpan source) + { + Guard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + Guard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + Guard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); + } + [MethodImpl(InliningOptions.ShortMethod)] internal static void GuardPackFromRgbPlanes(ReadOnlySpan greenChannel, ReadOnlySpan blueChannel, Span destination, int count) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index deea9217f..c8d93f6e9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -353,4 +353,15 @@ public partial class JpegDecoderTests Assert.Equal(65503, image.Width); Assert.Equal(65503, image.Height); } + + // https://github.com/SixLabors/ImageSharp/issues/2517 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2517, PixelTypes.Rgba32)] + public void Issue2517_DecodeWorks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index aed84a7d9..5842c8e1a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -87,7 +88,7 @@ public partial class JpegEncoderTests { using Image image = provider.GetImage(); - var encoder = new JpegEncoder + JpegEncoder encoder = new() { Quality = quality, ColorType = colorType, @@ -164,8 +165,8 @@ public partial class JpegEncoderTests [InlineData(JpegEncodingColor.YCbCrRatio444)] public async Task Encode_IsCancellable(JpegEncodingColor colorType) { - var cts = new CancellationTokenSource(); - using var pausedStream = new PausedStream(new MemoryStream()); + CancellationTokenSource cts = new(); + using PausedStream pausedStream = new(new MemoryStream()); pausedStream.OnWaiting(s => { // after some writing @@ -181,14 +182,37 @@ public partial class JpegEncoderTests } }); - using var image = new Image(5000, 5000); + using Image image = new(5000, 5000); await Assert.ThrowsAsync(async () => { - var encoder = new JpegEncoder() { ColorType = colorType }; + JpegEncoder encoder = new() { ColorType = colorType }; await image.SaveAsync(pausedStream, encoder, cts.Token); }); } + // https://github.com/SixLabors/ImageSharp/issues/2595 + [Theory] + [WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Bgra32)] + [WithFile(TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, PixelTypes.Rgb24)] + public static void Issue2595(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.Mutate(x => x.Crop(132, 1606)); + + int[] quality = new int[] { 100, 50 }; + JpegEncodingColor[] colors = new[] { JpegEncodingColor.YCbCrRatio444, JpegEncodingColor.YCbCrRatio420 }; + for (int i = 0; i < quality.Length; i++) + { + int q = quality[i]; + for (int j = 0; j < colors.Length; j++) + { + JpegEncodingColor c = colors[j]; + image.VerifyEncoder(provider, "jpeg", $"{q}-{c}", new JpegEncoder() { Quality = q, ColorType = c }, GetComparer(q, c)); + } + } + } + /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation /// @@ -225,7 +249,7 @@ public partial class JpegEncoderTests { using Image image = provider.GetImage(); - var encoder = new JpegEncoder + JpegEncoder encoder = new() { Quality = quality, ColorType = colorType diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs index 06cb079e5..02e8dc7df 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")); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 6301f341c..c38b13075 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -4,6 +4,7 @@ using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -493,4 +494,32 @@ public class WebpDecoderTests [Fact] public void DecodeLossyWithComplexFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithComplexFilterTest, HwIntrinsics.DisableHWIntrinsic); + + [Theory] + [InlineData(Lossy.BikeWithExif)] + public void Decode_VerifyRatio(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + using Image image = WebpDecoder.Instance.Decode(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + + Assert.Equal(37.8, meta.HorizontalResolution); + Assert.Equal(37.8, meta.VerticalResolution); + Assert.Equal(PixelResolutionUnit.PixelsPerCentimeter, meta.ResolutionUnits); + } + + [Theory] + [InlineData(Lossy.BikeWithExif)] + public void Identify_VerifyRatio(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo image = WebpDecoder.Instance.Identify(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + + Assert.Equal(37.8, meta.HorizontalResolution); + Assert.Equal(37.8, meta.VerticalResolution); + Assert.Equal(PixelResolutionUnit.PixelsPerCentimeter, meta.ResolutionUnits); + } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/CICP/CicpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/CICP/CicpProfileTests.cs new file mode 100644 index 000000000..76e2d35c4 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/CICP/CicpProfileTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Cicp; + +public class CicpProfileTests +{ + [Theory] + [WithFile(TestImages.Png.AdamHeadsHlg, PixelTypes.Rgba64)] + public async Task ReadCicpMetadata_FromPng_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = await provider.GetImageAsync(PngDecoder.Instance); + + CicpProfile actual = image.Metadata.CicpProfile ?? image.Frames.RootFrame.Metadata.CicpProfile; + CicpProfileContainsExpectedValues(actual); + } + + [Fact] + public void WritingPng_PreservesCicpProfile() + { + // arrange + using var image = new Image(1, 1); + var original = CreateCicpProfile(); + image.Metadata.CicpProfile = original; + var encoder = new PngEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + CicpProfile actual = reloadedImage.Metadata.CicpProfile ?? reloadedImage.Frames.RootFrame.Metadata.CicpProfile; + CicpProfileIsValidAndEqual(actual, original); + } + + private static void CicpProfileContainsExpectedValues(CicpProfile cicp) + { + Assert.NotNull(cicp); + Assert.Equal(CicpColorPrimaries.ItuRBt2020_2, cicp.ColorPrimaries); + Assert.Equal(CicpTransferCharacteristics.AribStdB67, cicp.TransferCharacteristics); + Assert.Equal(CicpMatrixCoefficients.Identity, cicp.MatrixCoefficients); + Assert.True(cicp.FullRange); + } + + private static CicpProfile CreateCicpProfile() + { + var profile = new CicpProfile() + { + ColorPrimaries = CicpColorPrimaries.ItuRBt2020_2, + TransferCharacteristics = CicpTransferCharacteristics.SmpteSt2084, + MatrixCoefficients = CicpMatrixCoefficients.Identity, + FullRange = true, + }; + return profile; + } + + private static void CicpProfileIsValidAndEqual(CicpProfile actual, CicpProfile original) + { + Assert.NotNull(actual); + Assert.Equal(actual.ColorPrimaries, original.ColorPrimaries); + Assert.Equal(actual.TransferCharacteristics, original.TransferCharacteristics); + Assert.Equal(actual.MatrixCoefficients, original.MatrixCoefficients); + Assert.Equal(actual.FullRange, original.FullRange); + } + + private static Image WriteAndRead(Image image, IImageEncoder encoder) + { + using (var memStream = new MemoryStream()) + { + image.Save(memStream, encoder); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index b6ea20f15..7e862f7d4 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -61,6 +61,7 @@ public static class TestImages public const string TestPattern31x31 = "Png/testpattern31x31.png"; public const string TestPattern31x31HalfTransparent = "Png/testpattern31x31-halftransparent.png"; public const string XmpColorPalette = "Png/xmp-colorpalette.png"; + public const string AdamHeadsHlg = "Png/adamHeadsHLG.png"; // Animated // https://philip.html5.org/tests/apng/tests.html @@ -307,6 +308,7 @@ public static class TestImages public const string Issue2478_JFXX = "Jpg/issues/issue-2478-jfxx.jpg"; public const string Issue2564 = "Jpg/issues/issue-2564.jpg"; public const string HangBadScan = "Jpg/issues/Hang_C438A851.jpg"; + public const string Issue2517 = "Jpg/issues/issue2517-bad-d7.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/issue2517-bad-d7.jpg b/tests/Images/Input/Jpg/issues/issue2517-bad-d7.jpg new file mode 100644 index 000000000..002fd8c36 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue2517-bad-d7.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:650a933db9c4f76fa3e6a8ed35d061a5740c613acd1026d99461eb014d8947b2 +size 179015 diff --git a/tests/Images/Input/Png/adamHeadsHLG.png b/tests/Images/Input/Png/adamHeadsHLG.png new file mode 100644 index 000000000..f5d26ac50 --- /dev/null +++ b/tests/Images/Input/Png/adamHeadsHLG.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c50691da3b3af21ff4f8fc30f1313bc412b84fb0a07a5bf3b8b14eae7581ade +size 201440