diff --git a/src/ImageSharp/ColorProfiles/Cmyk.cs b/src/ImageSharp/ColorProfiles/Cmyk.cs index 647d96089a..ee81ff9f7e 100644 --- a/src/ImageSharp/ColorProfiles/Cmyk.cs +++ b/src/ImageSharp/ColorProfiles/Cmyk.cs @@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.ColorProfiles; /// /// Represents an CMYK (cyan, magenta, yellow, keyline) color. +/// /// [StructLayout(LayoutKind.Sequential)] public readonly struct Cmyk : IColorProfile @@ -130,12 +131,12 @@ public readonly struct Cmyk : IColorProfile public static Cmyk FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) { // To CMY - Vector3 cmy = Vector3.One - source.ToScaledVector3(); + Vector3 cmy = Vector3.One - source.AsVector3Unsafe(); // To CMYK Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z))); - if (MathF.Abs(k.X - 1F) < Constants.Epsilon) + if (k.X >= 1F - Constants.Epsilon) { return new Cmyk(0, 0, 0, 1F); } @@ -161,7 +162,7 @@ public readonly struct Cmyk : IColorProfile /// public Rgb ToProfileConnectingSpace(ColorConversionOptions options) { - Vector3 rgb = (Vector3.One - new Vector3(this.C, this.M, this.Y)) * (Vector3.One - new Vector3(this.K)); + Vector3 rgb = (Vector3.One - new Vector3(this.C, this.M, this.Y)) * (1F - this.K); return Rgb.FromScaledVector3(rgb); } @@ -171,8 +172,7 @@ public readonly struct Cmyk : IColorProfile // TODO: We can possibly optimize this by using SIMD for (int i = 0; i < source.Length; i++) { - Cmyk cmyk = source[i]; - destination[i] = cmyk.ToProfileConnectingSpace(options); + destination[i] = source[i].ToProfileConnectingSpace(options); } } diff --git a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs index 1b0ecd4590..e17660bd4f 100644 --- a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs +++ b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs @@ -46,9 +46,9 @@ public class ColorConversionOptions public RgbWorkingSpace TargetRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb; /// - /// Gets the Y (luma) coefficients to use in conversions from RGB. + /// Gets the YCbCr matrix to used to perform conversions from/to RGB. /// - public Vector3 YCoefficients { get; init; } = KnownYCoefficients.BT709; + public YCbCrMatrix YCbCrMatrix { get; init; } = KnownYCbCrMatrices.BT601; /// /// Gets the source ICC profile. diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs index 49a442fdde..bad1d4c983 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs @@ -6,8 +6,8 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.ColorProfiles.Icc; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Icc; diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs index e14a4dde6a..82d475e578 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs @@ -322,10 +322,14 @@ internal class ClutCalculator : IVector4Calculator int offset = 0; for (int i = 0; i < this.outputCount; i++) { - float pv = (p[offset + this.n000] * dF0) + (p[offset + this.n001] * dF1) + (p[offset + this.n010] * dF2) + (p[offset + this.n011] * dF3) + - (p[offset + this.n100] * dF4) + (p[offset + this.n101] * dF5) + (p[offset + this.n110] * dF6) + (p[offset + this.n111] * dF7); - - destPixel[i] = pv; + destPixel[i] = (float)((p[offset + this.n000] * dF0) + + (p[offset + this.n001] * dF1) + + (p[offset + this.n010] * dF2) + + (p[offset + this.n011] * dF3) + + (p[offset + this.n100] * dF4) + + (p[offset + this.n101] * dF5) + + (p[offset + this.n110] * dF6) + + (p[offset + this.n111] * dF7)); offset++; } } diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs index b3e26e2a29..d035bd1793 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; internal partial class CurveCalculator { diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs index 232f4349c2..c39eaf958f 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; internal partial class CurveCalculator : ISingleCalculator { diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs index a09150c9b6..253239cb79 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; internal partial class LutABCalculator { diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs index f891f66749..172d806394 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs @@ -6,7 +6,7 @@ using System.Numerics; using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; internal partial class LutABCalculator : IVector4Calculator { diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs index b4b5028ed7..d2fc5d9b55 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs @@ -3,7 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs index 2b078e09fe..94f906709a 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; /// /// Color converter for ICC profiles diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs index 3967560210..43593f0ae9 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs @@ -1,7 +1,7 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; /// /// Color converter for ICC profiles diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs index b06d3e7b0c..20df08e378 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; /// /// Color converter for ICC profiles diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs index eb096534e1..d9976dc2ac 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs @@ -6,7 +6,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; /// /// Color converter for ICC profiles diff --git a/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs index 173948a6eb..cb4d89bb53 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.ColorProfiles.Icc; diff --git a/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs index d9e42a8d97..6e95d3cb32 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.ColorProfiles.Icc; diff --git a/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs index d174529b65..d29517fca2 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.ColorProfiles.Icc; diff --git a/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs index 98e069e401..30b44ca75c 100644 --- a/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs +++ b/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.ColorProfiles.Icc; diff --git a/src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs b/src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs index 45c231aa67..a4d673488e 100644 --- a/src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs +++ b/src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; internal static class SrgbV4Profile { diff --git a/src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs b/src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs new file mode 100644 index 0000000000..e2b7bf1026 --- /dev/null +++ b/src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.ColorProfiles; + +/// +/// Provides standard YCbCr matrices for RGB to YCbCr conversion. +/// +public static class KnownYCbCrMatrices +{ +#pragma warning disable SA1137 // Elements should have the same indentation +#pragma warning disable SA1117 // Parameters should be on same line or separate lines + /// + /// ITU-R BT.601 (SD video standard). + /// + public static readonly YCbCrMatrix BT601 = new( + new Matrix4x4( + 0.299000F, 0.587000F, 0.114000F, 0F, + -0.168736F, -0.331264F, 0.500000F, 0F, + 0.500000F, -0.418688F, -0.081312F, 0F, + 0F, 0F, 0F, 1F), + new Matrix4x4( + 1.000000F, 0.000000F, 1.402000F, 0F, + 1.000000F, -0.344136F, -0.714136F, 0F, + 1.000000F, 1.772000F, 0.000000F, 0F, + 0F, 0F, 0F, 1F), + new Vector3(0F, 0.5F, 0.5F)); + + /// + /// ITU-R BT.709 (HD video, sRGB standard). + /// + public static readonly YCbCrMatrix BT709 = new( + new Matrix4x4( + 0.212600F, 0.715200F, 0.072200F, 0F, + -0.114572F, -0.385428F, 0.500000F, 0F, + 0.500000F, -0.454153F, -0.045847F, 0F, + 0F, 0F, 0F, 1F), + new Matrix4x4( + 1.000000F, 0.000000F, 1.574800F, 0F, + 1.000000F, -0.187324F, -0.468124F, 0F, + 1.000000F, 1.855600F, 0.000000F, 0F, + 0F, 0F, 0F, 1F), + new Vector3(0F, 0.5F, 0.5F)); + + /// + /// ITU-R BT.2020 (UHD/4K video standard). + /// + public static readonly YCbCrMatrix BT2020 = new( + new Matrix4x4( + 0.262700F, 0.678000F, 0.059300F, 0F, + -0.139630F, -0.360370F, 0.500000F, 0F, + 0.500000F, -0.459786F, -0.040214F, 0F, + 0F, 0F, 0F, 1F), + new Matrix4x4( + 1.000000F, 0.000000F, 1.474600F, 0F, + 1.000000F, -0.164553F, -0.571353F, 0F, + 1.000000F, 1.881400F, 0.000000F, 0F, + 0F, 0F, 0F, 1F), + new Vector3(0F, 0.5F, 0.5F)); +} diff --git a/src/ImageSharp/ColorProfiles/KnownYCoefficients.cs b/src/ImageSharp/ColorProfiles/KnownYCoefficients.cs deleted file mode 100644 index 3189f92505..0000000000 --- a/src/ImageSharp/ColorProfiles/KnownYCoefficients.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorProfiles; - -/// -/// Provides standard Y (luma) coefficient sets for weighted RGB conversions. -/// -public static class KnownYCoefficients -{ - /// - /// ITU-R BT.601 (SD video standard). - /// - public static readonly Vector3 BT601 = new(0.299F, 0.587F, 0.114F); - - /// - /// ITU-R BT.709 (HD video, sRGB standard). - /// - public static readonly Vector3 BT709 = new(0.2126F, 0.7152F, 0.0722F); - - /// - /// ITU-R BT.2020 (UHD/4K video standard). - /// - public static readonly Vector3 BT2020 = new(0.2627F, 0.6780F, 0.0593F); -} diff --git a/src/ImageSharp/ColorProfiles/Rgb.cs b/src/ImageSharp/ColorProfiles/Rgb.cs index da3af3544c..f77b73da86 100644 --- a/src/ImageSharp/ColorProfiles/Rgb.cs +++ b/src/ImageSharp/ColorProfiles/Rgb.cs @@ -225,7 +225,7 @@ public readonly struct Rgb : IProfileConnectingSpace public bool Equals(Rgb other) => this.AsVector3Unsafe() == other.AsVector3Unsafe(); - private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); + internal Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); private static Matrix4x4 GetCieXyzToRgbMatrix(RgbWorkingSpace workingSpace) { diff --git a/src/ImageSharp/ColorProfiles/Y.cs b/src/ImageSharp/ColorProfiles/Y.cs index 634eeb8582..960bf46991 100644 --- a/src/ImageSharp/ColorProfiles/Y.cs +++ b/src/ImageSharp/ColorProfiles/Y.cs @@ -17,7 +17,13 @@ public readonly struct Y : IColorProfile /// Initializes a new instance of the struct. /// /// The luminance component. - public Y(float l) => this.L = l; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Y(float l) => this.L = Numerics.Clamp(l, 0, 1); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + private Y(float l, bool _) => this.L = l; +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter /// /// Gets the luminance component. @@ -47,32 +53,11 @@ public readonly struct Y : IColorProfile [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Y left, Y right) => !left.Equals(right); - /// - public static Y FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) - { - Vector3 weights = options.YCoefficients; - float l = (weights.X * source.R) + (weights.Y * source.G) + (weights.Z * source.B); - return new Y(l); - } - - /// - public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - - // TODO: We can optimize this by using SIMD - for (int i = 0; i < source.Length; i++) - { - Rgb rgb = source[i]; - destination[i] = FromProfileConnectingSpace(options, in rgb); - } - } - /// public Vector4 ToScaledVector4() => new(this.L); /// - public static Y FromScaledVector4(Vector4 source) => new(source.X); + public static Y FromScaledVector4(Vector4 source) => new(source.X, true); /// public static void ToScaledVector4(ReadOnlySpan source, Span destination) @@ -102,6 +87,14 @@ public readonly struct Y : IColorProfile public Rgb ToProfileConnectingSpace(ColorConversionOptions options) => new(this.L, this.L, this.L); + /// + public static Y FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) + { + Matrix4x4 m = options.YCbCrMatrix.Forward; + float offset = options.YCbCrMatrix.Offset.X; + return new(Vector3.Dot(source.AsVector3Unsafe(), new Vector3(m.M11, m.M12, m.M13)) + offset); + } + /// public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) { @@ -114,6 +107,19 @@ public readonly struct Y : IColorProfile } } + /// + public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // TODO: We can optimize this by using SIMD + for (int i = 0; i < source.Length; i++) + { + Rgb rgb = source[i]; + destination[i] = FromProfileConnectingSpace(options, in rgb); + } + } + /// public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; diff --git a/src/ImageSharp/ColorProfiles/YCbCr.cs b/src/ImageSharp/ColorProfiles/YCbCr.cs index 20c898f966..6a706cd0da 100644 --- a/src/ImageSharp/ColorProfiles/YCbCr.cs +++ b/src/ImageSharp/ColorProfiles/YCbCr.cs @@ -8,15 +8,13 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.ColorProfiles; /// -/// Represents an YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification for the JFIF use with Jpeg. -/// -/// +/// Represents an YCbCr (luminance, blue chroma, red chroma) color. /// [StructLayout(LayoutKind.Sequential)] public readonly struct YCbCr : IColorProfile { private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = new(255); + private static readonly Vector3 Max = Vector3.One; /// /// Initializes a new instance of the struct. @@ -55,19 +53,19 @@ public readonly struct YCbCr : IColorProfile /// /// Gets the Y luminance component. - /// A value ranging between 0 and 255. + /// A value ranging between 0 and 1. /// public float Y { get; } /// /// Gets the Cb chroma component. - /// A value ranging between 0 and 255. + /// A value ranging between 0 and 1. /// public float Cb { get; } /// /// Gets the Cr chroma component. - /// A value ranging between 0 and 255. + /// A value ranging between 0 and 1. /// public float Cr { get; } @@ -97,17 +95,12 @@ public readonly struct YCbCr : IColorProfile { Vector3 v3 = default; v3 += this.AsVector3Unsafe(); - v3 /= Max; return new Vector4(v3, 1F); } /// public static YCbCr FromScaledVector4(Vector4 source) - { - Vector3 v3 = source.AsVector3(); - v3 *= Max; - return new YCbCr(v3, true); - } + => new(source.AsVector3(), true); /// public static void ToScaledVector4(ReadOnlySpan source, Span destination) @@ -136,16 +129,15 @@ public readonly struct YCbCr : IColorProfile /// public static YCbCr FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) { - Vector3 rgb = source.ToScaledVector3() * Max; - float r = rgb.X; - float g = rgb.Y; - float b = rgb.Z; + Vector3 rgb = source.AsVector3Unsafe(); + Matrix4x4 m = options.YCbCrMatrix.Forward; + Vector3 offset = options.YCbCrMatrix.Offset; - float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + float y = Vector3.Dot(rgb, new Vector3(m.M11, m.M12, m.M13)); + float cb = Vector3.Dot(rgb, new Vector3(m.M21, m.M22, m.M23)); + float cr = Vector3.Dot(rgb, new Vector3(m.M31, m.M32, m.M33)); - return new YCbCr(y, cb, cr); + return new YCbCr(new Vector3(y, cb, cr) + offset, true); } /// @@ -164,15 +156,15 @@ public readonly struct YCbCr : IColorProfile /// public Rgb ToProfileConnectingSpace(ColorConversionOptions options) { - float y = this.Y; - float cb = this.Cb - 128F; - float cr = this.Cr - 128F; + Matrix4x4 m = options.YCbCrMatrix.Inverse; + Vector3 offset = options.YCbCrMatrix.Offset; + Vector3 normalized = this.AsVector3Unsafe() - offset; - float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); - float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); - float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); + float r = Vector3.Dot(normalized, new Vector3(m.M11, m.M12, m.M13)); + float g = Vector3.Dot(normalized, new Vector3(m.M21, m.M22, m.M23)); + float b = Vector3.Dot(normalized, new Vector3(m.M31, m.M32, m.M33)); - return Rgb.FromScaledVector3(new Vector3(r, g, b) / Max); + return Rgb.FromScaledVector3(new Vector3(r, g, b)); } /// diff --git a/src/ImageSharp/ColorProfiles/YcbCrMatrix.cs b/src/ImageSharp/ColorProfiles/YcbCrMatrix.cs new file mode 100644 index 0000000000..2a71de18a9 --- /dev/null +++ b/src/ImageSharp/ColorProfiles/YcbCrMatrix.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; + +namespace SixLabors.ImageSharp.ColorProfiles; + +/// +/// +/// Represents a YCbCr color matrix containing forward and inverse transformation matrices, +/// and the chrominance offsets to apply for full-range encoding +/// +/// +/// These matrices must be selected to match the characteristics of the associated , +/// including its transfer function (gamma or companding) and chromaticity coordinates. Using mismatched matrices and +/// working spaces will produce incorrect conversions. +/// +/// +public readonly struct YCbCrMatrix +{ + /// + /// Initializes a new instance of the struct. + /// + /// + /// The forward transformation matrix from RGB to YCbCr. The matrix must include the + /// standard chrominance offsets in the fourth column, such as (0, 0.5, 0.5). + /// + /// + /// The inverse transformation matrix from YCbCr to RGB. This matrix expects that + /// chrominance offsets have already been subtracted prior to application. + /// + /// + /// The chrominance offsets to be added after the forward conversion, + /// and subtracted before the inverse conversion. Usually (0, 0.5, 0.5). + /// + public YCbCrMatrix(Matrix4x4 forward, Matrix4x4 inverse, Vector3 offset) + { + this.Forward = forward; + this.Inverse = inverse; + this.Offset = offset; + } + + /// + /// Gets the matrix used to convert gamma-encoded RGB to YCbCr. + /// + public Matrix4x4 Forward { get; } + + /// + /// Gets the matrix used to convert YCbCr back to gamma-encoded RGB. + /// + public Matrix4x4 Inverse { get; } + + /// + /// Gets the chrominance offset vector to apply during encoding (add) or decoding (subtract). + /// + public Vector3 Offset { get; } +} diff --git a/src/ImageSharp/ColorProfiles/YccK.cs b/src/ImageSharp/ColorProfiles/YccK.cs new file mode 100644 index 0000000000..af5e94e2d5 --- /dev/null +++ b/src/ImageSharp/ColorProfiles/YccK.cs @@ -0,0 +1,216 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.ColorProfiles; + +/// +/// Represents a YCCK (luminance, blue chroma, red chroma, black) color. +/// YCCK is not a true color space but a reversible transform of CMYK, where the CMY components +/// are converted to YCbCr using the ITU-R BT.601 standard, and the K (black) component is preserved separately. +/// +[StructLayout(LayoutKind.Sequential)] +public readonly struct YccK : IColorProfile +{ + private static readonly Vector4 Min = Vector4.Zero; + private static readonly Vector4 Max = Vector4.One; + + /// + /// Initializes a new instance of the struct. + /// + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + /// The keyline black component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public YccK(float y, float cb, float cr, float k) + : this(new Vector4(y, cb, cr, k)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the c, m, y, k components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public YccK(Vector4 vector) + { + vector = Vector4.Clamp(vector, Min, Max); + this.Y = vector.X; + this.Cb = vector.Y; + this.Cr = vector.Z; + this.K = vector.W; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + private YccK(Vector4 vector, bool _) +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter + { + this.Y = vector.X; + this.Cb = vector.Y; + this.Cr = vector.Z; + this.K = vector.W; + } + + /// + /// Gets the Y luminance component. + /// A value ranging between 0 and 1. + /// + public float Y { get; } + + /// + /// Gets the C (blue) chroma component. + /// A value ranging between 0 and 1. + /// + public float Cb { get; } + + /// + /// Gets the C (red) chroma component. + /// A value ranging between 0 and 1. + /// + public float Cr { get; } + + /// + /// Gets the keyline black color component. + /// A value ranging between 0 and 1. + /// + public float K { get; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(YccK left, YccK right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(YccK left, YccK right) => !left.Equals(right); + + /// + public Vector4 ToScaledVector4() + { + Vector4 v4 = default; + v4 += this.AsVector4Unsafe(); + return v4; + } + + /// + public static YccK FromScaledVector4(Vector4 source) + => new(source, true); + + /// + public static void ToScaledVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + public static void FromScaledVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + public Rgb ToProfileConnectingSpace(ColorConversionOptions options) + { + Matrix4x4 m = options.YCbCrMatrix.Inverse; + Vector3 offset = options.YCbCrMatrix.Offset; + Vector3 normalized = this.AsVector3Unsafe() - offset; + + float r = Vector3.Dot(normalized, new Vector3(m.M11, m.M12, m.M13)); + float g = Vector3.Dot(normalized, new Vector3(m.M21, m.M22, m.M23)); + float b = Vector3.Dot(normalized, new Vector3(m.M31, m.M32, m.M33)); + + Vector3 rgb = new Vector3(r, g, b) * (1F - this.K); + return Rgb.FromScaledVector3(rgb); + } + + /// + public static YccK FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) + { + Matrix4x4 m = options.YCbCrMatrix.Forward; + Vector3 offset = options.YCbCrMatrix.Offset; + + Vector3 rgb = source.AsVector3Unsafe(); + float k = 1F - MathF.Max(rgb.X, MathF.Max(rgb.Y, rgb.Z)); + + if (k >= 1F - Constants.Epsilon) + { + return new YccK(new Vector4(0F, 0.5F, 0.5F, 1F), true); + } + + rgb /= 1F - k; + + float y = Vector3.Dot(rgb, new Vector3(m.M11, m.M12, m.M13)); + float cb = Vector3.Dot(rgb, new Vector3(m.M21, m.M22, m.M23)); + float cr = Vector3.Dot(rgb, new Vector3(m.M31, m.M32, m.M33)); + + return new YccK(new Vector4(y, cb, cr, k) + new Vector4(offset, 0F)); + } + + /// + public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) + { + // TODO: We can possibly optimize this by using SIMD + for (int i = 0; i < source.Length; i++) + { + destination[i] = source[i].ToProfileConnectingSpace(options); + } + } + + /// + public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // TODO: We can optimize this by using SIMD + for (int i = 0; i < source.Length; i++) + { + Rgb rgb = source[i]; + destination[i] = FromProfileConnectingSpace(options, in rgb); + } + } + + /// + public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() + => ChromaticAdaptionWhitePointSource.RgbWorkingSpace; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() + => HashCode.Combine(this.Y, this.Cb, this.Cr, this.K); + + /// + public override string ToString() + => FormattableString.Invariant($"YccK({this.Y:#0.##}, {this.Cb:#0.##}, {this.Cr:#0.##}, {this.K:#0.##})"); + + /// + public override bool Equals(object? obj) + => obj is YccK other && this.Equals(other); + + /// + public bool Equals(YccK other) + => this.AsVector4Unsafe() == other.AsVector4Unsafe(); + + private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); + + private Vector4 AsVector4Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); +} diff --git a/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs b/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs index 7376ede6e5..f4078887c7 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs @@ -23,7 +23,8 @@ internal readonly struct ApproximateColorProfileComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, - IEqualityComparer + IEqualityComparer, + IEqualityComparer { private readonly float epsilon; @@ -61,6 +62,8 @@ internal readonly struct ApproximateColorProfileComparer : public bool Equals(Y x, Y y) => this.Equals(x.L, y.L); + public bool Equals(YccK x, YccK y) => this.Equals(x.Y, y.Y) && this.Equals(x.Cb, y.Cb) && this.Equals(x.Cr, y.Cr) && this.Equals(x.K, y.K); + public int GetHashCode([DisallowNull] CieLab obj) => obj.GetHashCode(); public int GetHashCode([DisallowNull] CieXyz obj) => obj.GetHashCode(); @@ -89,6 +92,8 @@ internal readonly struct ApproximateColorProfileComparer : public int GetHashCode([DisallowNull] Y obj) => obj.GetHashCode(); + public int GetHashCode([DisallowNull] YccK obj) => obj.GetHashCode(); + private bool Equals(float x, float y) { float d = x - y; diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs index 9a29b15398..15677c46f0 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs @@ -13,8 +13,9 @@ public class CieLabAndYCbCrConversionTests private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); [Theory] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(87.4179, 133.9763, 247.5308, 55.06287, 82.54838, 23.1697)] + [InlineData(1, .5F, .5F, 100, 0, 0)] + [InlineData(0, .5F, .5F, 0, 0, 0)] + [InlineData(.5F, .5F, .5F, 53.38897F, 0, 0)] public void Convert_YCbCr_to_CieLab(float y, float cb, float cr, float l, float a, float b) { // Arrange @@ -41,8 +42,9 @@ public class CieLabAndYCbCrConversionTests } [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(55.06287, 82.54838, 23.1697, 87.41701, 133.97232, 247.5314)] + [InlineData(100, 0, 0, 1, .5F, .5F)] + [InlineData(0, 0, 0, 0, .5F, .5F)] + [InlineData(53.38897F, 0, 0, .5F, .5F, .5F)] public void Convert_CieLab_to_YCbCr(float l, float a, float b, float y, float cb, float cr) { // Arrange diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs index 0d5dd60e61..62b276a1f1 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs @@ -13,8 +13,9 @@ public class CieLuvAndYCbCrConversionTests private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(36.0555, 93.6901, 10.01514, 71.8283, 119.3174, 193.9839)] + [InlineData(100, 0, 0, 1, .5F, .5F)] + [InlineData(0, 0, 0, 0, .5F, .5F)] + [InlineData(53.38897F, 0, 0, .5F, .5F, .5F)] public void Convert_CieLuv_to_YCbCr(float l, float u, float v, float y, float cb, float cr) { // Arrange @@ -42,8 +43,9 @@ public class CieLuvAndYCbCrConversionTests } [Theory] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(71.8283, 119.3174, 193.9839, 36.00565, 93.44593, 10.2234)] + [InlineData(1, .5F, .5F, 100, 0, 0)] + [InlineData(0, .5F, .5F, 0, 0, 0)] + [InlineData(.5F, .5F, .5F, 53.38897F, 0, 0)] public void Convert_YCbCr_to_CieLuv(float y, float cb, float cr, float l, float u, float v) { // Arrange diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs index 1fe3596036..f9d571e036 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs @@ -13,8 +13,9 @@ public class CieXyyAndYCbCrConversionTests private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(0.360555, 0.936901, 0.1001514, 64.0204849, 91.87107, 82.33627)] + [InlineData(.34566915F, .358496159F, .99999994F, 1, .5F, .5F)] + [InlineData(0, 0, 0, 0, .5F, .5F)] + [InlineData(.34566915F, .358496159F, .214041144F, .5F, .5F, .5F)] public void Convert_CieXyy_to_YCbCr(float x, float y, float yl, float y2, float cb, float cr) { // Arrange @@ -41,8 +42,9 @@ public class CieXyyAndYCbCrConversionTests } [Theory] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(64.0204849, 91.87107, 82.33627, 0.32114, 0.59787, 0.10976)] + [InlineData(1, .5F, .5F, .34566915F, .358496159F, .99999994F)] + [InlineData(0, .5F, .5F, 0, 0, 0)] + [InlineData(.5F, .5F, .5F, .34566915F, .358496159F, .214041144F)] public void Convert_YCbCr_to_CieXyy(float y2, float cb, float cr, float x, float y, float yl) { // Arrange diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs index 475673da84..90175a0585 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs @@ -13,8 +13,8 @@ public class CieXyzAndYCbCrConversionTests private static readonly ApproximateColorProfileComparer Comparer = new(.0002f); [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(0.360555, 0.936901, 0.1001514, 149.685, 43.52769, 21.23457)] + [InlineData(0, 0, 0, 0, .5F, .5F)] + [InlineData(.206382737F, .214041144F, .176628917F, .5F, .5F, .5F)] public void Convert_CieXyz_to_YCbCr(float x, float y, float z, float y2, float cb, float cr) { // Arrange @@ -41,8 +41,8 @@ public class CieXyzAndYCbCrConversionTests } [Theory] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(149.685, 43.52769, 21.23457, 0.38506496, 0.716878653, 0.0971045)] + [InlineData(0, .5F, .5F, 0, 0, 0)] + [InlineData(.5F, .5F, .5F, .206382737F, .214041144F, .176628917F)] public void Convert_YCbCr_to_CieXyz(float y2, float cb, float cr, float x, float y, float z) { // Arrange diff --git a/tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs index 64b47e2b97..3a5fe7c15c 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs @@ -13,8 +13,9 @@ public class CmykAndYCbCrConversionTests private static readonly ApproximateColorProfileComparer Comparer = new(.0002F); [Theory] - [InlineData(0, 0, 0, 0, 255, 128, 128)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 136.5134, 69.90555, 114.9948)] + [InlineData(0, 0, 0, 1, 0, .5F, .5F)] + [InlineData(0, 0, 0, 0, 1, .5F, .5F)] + [InlineData(0, .8570679F, .49999997F, 0, .439901F, .5339159F, .899500132F)] public void Convert_Cmyk_To_YCbCr(float c, float m, float y, float k, float y2, float cb, float cr) { // Arrange @@ -41,8 +42,9 @@ public class CmykAndYCbCrConversionTests } [Theory] - [InlineData(255, 128, 128, 0, 0, 0, 5.960464E-08)] - [InlineData(136.5134, 69.90555, 114.9948, 0.2891567, 0, 0.7951807, 0.3490196)] + [InlineData(0, .5F, .5F, 0, 0, 0, 1)] + [InlineData(1, .5F, .5F, 0, 0, 0, 0)] + [InlineData(.5F, .5F, 1F, 0, .8570679F, .49999997F, 0)] public void Convert_YCbCr_To_Cmyk(float y2, float cb, float cr, float c, float m, float y, float k) { // Arrange diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs index 0608637619..8f48277d6c 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs index 5be984b4b9..de2b4f5fae 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs @@ -2,38 +2,37 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators +namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; + +/// +/// Tests ICC +/// +[Trait("Color", "Conversion")] +public class LutABCalculatorTests { - /// - /// Tests ICC - /// - [Trait("Color", "Conversion")] - public class LutABCalculatorTests + [Theory] + [MemberData(nameof(IccConversionDataLutAB.LutAToBConversionTestData), MemberType = typeof(IccConversionDataLutAB))] + internal void LutABCalculator_WithLutAToB_ReturnsResult(IccLutAToBTagDataEntry lut, Vector4 input, Vector4 expected) { - [Theory] - [MemberData(nameof(IccConversionDataLutAB.LutAToBConversionTestData), MemberType = typeof(IccConversionDataLutAB))] - internal void LutABCalculator_WithLutAToB_ReturnsResult(IccLutAToBTagDataEntry lut, Vector4 input, Vector4 expected) - { - LutABCalculator calculator = new(lut); + LutABCalculator calculator = new(lut); - Vector4 result = calculator.Calculate(input); + Vector4 result = calculator.Calculate(input); - VectorAssert.Equal(expected, result, 4); - } + VectorAssert.Equal(expected, result, 4); + } - [Theory] - [MemberData(nameof(IccConversionDataLutAB.LutBToAConversionTestData), MemberType = typeof(IccConversionDataLutAB))] - internal void LutABCalculator_WithLutBToA_ReturnsResult(IccLutBToATagDataEntry lut, Vector4 input, Vector4 expected) - { - LutABCalculator calculator = new(lut); + [Theory] + [MemberData(nameof(IccConversionDataLutAB.LutBToAConversionTestData), MemberType = typeof(IccConversionDataLutAB))] + internal void LutABCalculator_WithLutBToA_ReturnsResult(IccLutBToATagDataEntry lut, Vector4 input, Vector4 expected) + { + LutABCalculator calculator = new(lut); - Vector4 result = calculator.Calculate(input); + Vector4 result = calculator.Calculate(input); - VectorAssert.Equal(expected, result, 4); - } + VectorAssert.Equal(expected, result, 4); } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs index d5d2736f71..6cc77247a9 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs @@ -4,23 +4,22 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators +namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; + +/// +/// Tests ICC +/// +[Trait("Color", "Conversion")] +public class LutCalculatorTests { - /// - /// Tests ICC - /// - [Trait("Color", "Conversion")] - public class LutCalculatorTests + [Theory] + [MemberData(nameof(IccConversionDataLut.LutConversionTestData), MemberType = typeof(IccConversionDataLut))] + internal void LutCalculator_WithLut_ReturnsResult(float[] lut, bool inverted, float input, float expected) { - [Theory] - [MemberData(nameof(IccConversionDataLut.LutConversionTestData), MemberType = typeof(IccConversionDataLut))] - internal void LutCalculator_WithLut_ReturnsResult(float[] lut, bool inverted, float input, float expected) - { - LutCalculator calculator = new(lut, inverted); + LutCalculator calculator = new(lut, inverted); - float result = calculator.Calculate(input); + float result = calculator.Calculate(input); - Assert.Equal(expected, result, 4f); - } + Assert.Equal(expected, result, 4f); } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs index 0493521140..14f1386eb8 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs @@ -6,34 +6,33 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators +namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; + +/// +/// Tests ICC +/// +[Trait("Color", "Conversion")] +public class LutEntryCalculatorTests { - /// - /// Tests ICC - /// - [Trait("Color", "Conversion")] - public class LutEntryCalculatorTests + [Theory] + [MemberData(nameof(IccConversionDataLutEntry.Lut8ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))] + internal void LutEntryCalculator_WithLut8_ReturnsResult(IccLut8TagDataEntry lut, Vector4 input, Vector4 expected) { - [Theory] - [MemberData(nameof(IccConversionDataLutEntry.Lut8ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))] - internal void LutEntryCalculator_WithLut8_ReturnsResult(IccLut8TagDataEntry lut, Vector4 input, Vector4 expected) - { - LutEntryCalculator calculator = new(lut); + LutEntryCalculator calculator = new(lut); - Vector4 result = calculator.Calculate(input); + Vector4 result = calculator.Calculate(input); - VectorAssert.Equal(expected, result, 4); - } + VectorAssert.Equal(expected, result, 4); + } - [Theory] - [MemberData(nameof(IccConversionDataLutEntry.Lut16ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))] - internal void LutEntryCalculator_WithLut16_ReturnsResult(IccLut16TagDataEntry lut, Vector4 input, Vector4 expected) - { - LutEntryCalculator calculator = new(lut); + [Theory] + [MemberData(nameof(IccConversionDataLutEntry.Lut16ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))] + internal void LutEntryCalculator_WithLut16_ReturnsResult(IccLut16TagDataEntry lut, Vector4 input, Vector4 expected) + { + LutEntryCalculator calculator = new(lut); - Vector4 result = calculator.Calculate(input); + Vector4 result = calculator.Calculate(input); - VectorAssert.Equal(expected, result, 4); - } + VectorAssert.Equal(expected, result, 4); } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs index 22b25b84d1..f56bf5873e 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs @@ -5,23 +5,22 @@ using System.Numerics; using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators +namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; + +/// +/// Tests ICC +/// +[Trait("Color", "Conversion")] +public class MatrixCalculatorTests { - /// - /// Tests ICC - /// - [Trait("Color", "Conversion")] - public class MatrixCalculatorTests + [Theory] + [MemberData(nameof(IccConversionDataMatrix.MatrixConversionTestData), MemberType = typeof(IccConversionDataMatrix))] + internal void MatrixCalculator_WithMatrix_ReturnsResult(Matrix4x4 matrix2D, Vector3 matrix1D, Vector4 input, Vector4 expected) { - [Theory] - [MemberData(nameof(IccConversionDataMatrix.MatrixConversionTestData), MemberType = typeof(IccConversionDataMatrix))] - internal void MatrixCalculator_WithMatrix_ReturnsResult(Matrix4x4 matrix2D, Vector3 matrix1D, Vector4 input, Vector4 expected) - { - MatrixCalculator calculator = new(matrix2D, matrix1D); + MatrixCalculator calculator = new(matrix2D, matrix1D); - Vector4 result = calculator.Calculate(input); + Vector4 result = calculator.Calculate(input); - VectorAssert.Equal(expected, result, 4); - } + VectorAssert.Equal(expected, result, 4); } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs index ca3608b8cf..aac3f42c9b 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs @@ -5,23 +5,22 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators +namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; + +/// +/// Tests ICC +/// +[Trait("Color", "Conversion")] +public class ParametricCurveCalculatorTests { - /// - /// Tests ICC - /// - [Trait("Color", "Conversion")] - public class ParametricCurveCalculatorTests + [Theory] + [MemberData(nameof(IccConversionDataTrc.ParametricCurveConversionTestData), MemberType = typeof(IccConversionDataTrc))] + internal void ParametricCurveCalculator_WithCurveEntry_ReturnsResult(IccParametricCurveTagDataEntry curve, bool inverted, float input, float expected) { - [Theory] - [MemberData(nameof(IccConversionDataTrc.ParametricCurveConversionTestData), MemberType = typeof(IccConversionDataTrc))] - internal void ParametricCurveCalculator_WithCurveEntry_ReturnsResult(IccParametricCurveTagDataEntry curve, bool inverted, float input, float expected) - { - ParametricCurveCalculator calculator = new(curve, inverted); + ParametricCurveCalculator calculator = new(curve, inverted); - float result = calculator.Calculate(input); + float result = calculator.Calculate(input); - Assert.Equal(expected, result, 4f); - } + Assert.Equal(expected, result, 4f); } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs index d86e32453d..65f02c3fb7 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs @@ -6,23 +6,22 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion; -namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators +namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators; + +/// +/// Tests ICC +/// +[Trait("Color", "Conversion")] +public class TrcCalculatorTests { - /// - /// Tests ICC - /// - [Trait("Color", "Conversion")] - public class TrcCalculatorTests + [Theory] + [MemberData(nameof(IccConversionDataTrc.TrcArrayConversionTestData), MemberType = typeof(IccConversionDataTrc))] + internal void TrcCalculator_WithCurvesArray_ReturnsResult(IccTagDataEntry[] entries, bool inverted, Vector4 input, Vector4 expected) { - [Theory] - [MemberData(nameof(IccConversionDataTrc.TrcArrayConversionTestData), MemberType = typeof(IccConversionDataTrc))] - internal void TrcCalculator_WithCurvesArray_ReturnsResult(IccTagDataEntry[] entries, bool inverted, Vector4 input, Vector4 expected) - { - TrcCalculator calculator = new(entries, inverted); + TrcCalculator calculator = new(entries, inverted); - Vector4 result = calculator.Calculate(input); + Vector4 result = calculator.Calculate(input); - VectorAssert.Equal(expected, result, 4); - } + VectorAssert.Equal(expected, result, 4); } } diff --git a/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs index aacabc8baa..1cd6f6cf41 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs @@ -15,35 +15,6 @@ public class RbgAndYConversionTests { private static readonly ApproximateColorProfileComparer Comparer = new(.001F); - [Theory] - [InlineData(0F, 0F, 0F, 0F)] - [InlineData(0.5F, 0.5F, 0.5F, 0.5F)] - [InlineData(1F, 1F, 1F, 1F)] - public void Convert_Y_To_Rgb(float y, float r, float g, float b) - { - // Arrange - Y input = new(y); - Rgb expected = new(r, g, b); - ColorProfileConverter converter = new(); - - Span inputSpan = new Y[5]; - inputSpan.Fill(input); - - Span actualSpan = new Rgb[5]; - - // Act - Rgb actual = converter.Convert(input); - converter.Convert(inputSpan, actualSpan); - - // Assert - Assert.Equal(expected, actual, Comparer); - - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], Comparer); - } - } - [Theory] [InlineData(0F, 0F, 0F, 0F)] [InlineData(0.5F, 0.5F, 0.5F, 0.5F)] @@ -52,7 +23,7 @@ public class RbgAndYConversionTests { ColorConversionOptions options = new() { - YCoefficients = KnownYCoefficients.BT601 + YCbCrMatrix = KnownYCbCrMatrices.BT601 }; Convert_Rgb_To_Y_Core(r, g, b, y, options); @@ -66,7 +37,7 @@ public class RbgAndYConversionTests { ColorConversionOptions options = new() { - YCoefficients = KnownYCoefficients.BT709 + YCbCrMatrix = KnownYCbCrMatrices.BT709 }; Convert_Rgb_To_Y_Core(r, g, b, y, options); @@ -80,7 +51,7 @@ public class RbgAndYConversionTests { ColorConversionOptions options = new() { - YCoefficients = KnownYCoefficients.BT2020 + YCbCrMatrix = KnownYCbCrMatrices.BT2020 }; Convert_Rgb_To_Y_Core(r, g, b, y, options); diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs index 91f7fc08ee..ede8226e40 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs @@ -16,9 +16,9 @@ public class RgbAndYCbCrConversionTest private static readonly ApproximateColorProfileComparer Comparer = new(.001F); [Theory] - [InlineData(255, 128, 128, 1, 1, 1)] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(128, 128, 128, 0.502, 0.502, 0.502)] + [InlineData(1, .5F, .5F, 1, 1, 1)] + [InlineData(0, .5F, .5F, 0, 0, 0)] + [InlineData(.5F, .5F, .5F, .5F, .5F, .5F)] public void Convert_YCbCr_To_Rgb(float y, float cb, float cr, float r, float g, float b) { // Arrange @@ -45,10 +45,9 @@ public class RgbAndYCbCrConversionTest } [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(1, 1, 1, 255, 128, 128)] - [InlineData(0.5, 0.5, 0.5, 127.5, 128, 128)] - [InlineData(1, 0, 0, 76.245, 84.972, 255)] + [InlineData(1, 1, 1, 1, .5F, .5F)] + [InlineData(0, 0, 0, 0, .5F, .5F)] + [InlineData(.5F, .5F, .5F, .5F, .5F, .5F)] public void Convert_Rgb_To_YCbCr(float r, float g, float b, float y, float cb, float cr) { // Arrange diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndYccKConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndYccKConversionTests.cs new file mode 100644 index 0000000000..78f424cc28 --- /dev/null +++ b/tests/ImageSharp.Tests/ColorProfiles/RgbAndYccKConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.ColorProfiles; + +namespace SixLabors.ImageSharp.Tests.ColorProfiles; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated mathematically +/// +public class RgbAndYccKConversionTests +{ + private static readonly ApproximateColorProfileComparer Comparer = new(.001F); + + [Theory] + [InlineData(1, .5F, .5F, 0, 1, 1, 1)] + [InlineData(0, .5F, .5F, 1, 0, 0, 0)] + [InlineData(.5F, .5F, .5F, 0, .5F, .5F, .5F)] + public void Convert_YccK_To_Rgb(float y, float cb, float cr, float k, float r, float g, float b) + { + // Arrange + YccK input = new(y, cb, cr, k); + Rgb expected = new(r, g, b); + ColorProfileConverter converter = new(); + + Span inputSpan = new YccK[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + Rgb actual = converter.Convert(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, Comparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], Comparer); + } + } + + [Theory] + [InlineData(1, 1, 1, 1, .5F, .5F, 0)] + [InlineData(0, 0, 0, 0, .5F, .5F, 1)] + [InlineData(.5F, .5F, .5F, 1, .5F, .5F, .5F)] + public void Convert_Rgb_To_YccK(float r, float g, float b, float y, float cb, float cr, float k) + { + // Multiple YccK representations can decode to the same RGB value. + // For example, (Y=1.0, Cb=0.5, Cr=0.5, K=0.5) and (Y=0.5, Cb=0.5, Cr=0.5, K=0.0) both yield RGB (0.5, 0.5, 0.5). + // This is expected because YccK is not a unique encoding — K modulates RGB after YCbCr decoding. + // Round-tripping RGB -> YccK -> RGB is stable, but YccK -> RGB -> YccK is not injective. + + // Arrange + Rgb input = new(r, g, b); + YccK expected = new(y, cb, cr, k); + ColorProfileConverter converter = new(); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new YccK[5]; + + // Act + YccK actual = converter.Convert(input); + converter.Convert(inputSpan, actualSpan); + + // Assert + Assert.Equal(expected, actual, Comparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], Comparer); + } + } +} diff --git a/tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs b/tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs index 0867fcfbc2..f61124d8f5 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs @@ -12,6 +12,7 @@ public class StringRepresentationTests private static readonly Vector3 One = new(1); private static readonly Vector3 Zero = new(0); private static readonly Vector3 Random = new(42.4F, 94.5F, 83.4F); + private static readonly Vector4 Random4 = new(42.4F, 94.5F, 83.4F, 1); public static readonly TheoryData TestData = new() { @@ -52,7 +53,10 @@ public class StringRepresentationTests { Rgb.Clamp(new Rgb(Random)), "Rgb(1, 1, 1)" }, { new Hsl(Random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected { new Hsv(Random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected - { new YCbCr(Random), "YCbCr(42.4, 94.5, 83.4)" }, + { new Y(Random.X), "Y(1)" }, + { new YCbCr(Random), "YCbCr(1, 1, 1)" }, + { new YccK(Random4), "YccK(1, 1, 1, 1)" }, + { new Cmyk(Random4), "Cmyk(1, 1, 1, 1)" }, }; [Theory] diff --git a/tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs b/tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs index a88d0157a3..64558a3f8a 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs @@ -15,9 +15,9 @@ public class YCbCrTests [Fact] public void YCbCrConstructorAssignsFields() { - const float y = 75F; - const float cb = 64F; - const float cr = 87F; + const float y = .75F; + const float cb = .64F; + const float cr = .87F; YCbCr yCbCr = new(y, cb, cr); Assert.Equal(y, yCbCr.Y); diff --git a/tests/ImageSharp.Tests/ColorProfiles/YTests.cs b/tests/ImageSharp.Tests/ColorProfiles/YTests.cs index 7a1799a62d..7e5e48b69e 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/YTests.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/YTests.cs @@ -1,10 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. - -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - using SixLabors.ImageSharp.ColorProfiles; namespace SixLabors.ImageSharp.Tests.ColorProfiles; diff --git a/tests/ImageSharp.Tests/ColorProfiles/YccKTests.cs b/tests/ImageSharp.Tests/ColorProfiles/YccKTests.cs new file mode 100644 index 0000000000..bfe0bdb175 --- /dev/null +++ b/tests/ImageSharp.Tests/ColorProfiles/YccKTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.ColorProfiles; + +namespace SixLabors.ImageSharp.Tests.ColorProfiles; + +/// +/// Tests the struct. +/// +[Trait("Color", "Conversion")] +public class YccKTests +{ + [Fact] + public void YccKConstructorAssignsFields() + { + const float y = .75F; + const float cb = .5F; + const float cr = .25F; + const float k = .125F; + + YccK ycckValue = new(y, cb, cr, k); + Assert.Equal(y, ycckValue.Y); + Assert.Equal(cb, ycckValue.Cb); + Assert.Equal(cr, ycckValue.Cr); + Assert.Equal(k, ycckValue.K); + } + + [Fact] + public void YccKEquality() + { + YccK x = default; + YccK y = new(1F, 1F, 1F, 1F); + Assert.True(default == default(YccK)); + Assert.False(default != default(YccK)); + Assert.Equal(default, default(YccK)); + Assert.Equal(new YccK(1, 1, 1, 1), new YccK(1, 1, 1, 1)); + Assert.Equal(new YccK(.5F, .5F, .5F, .5F), new YccK(.5F, .5F, .5F, .5F)); + + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 25e605cef2..1dba8ee1c9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.ColorProfiles; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; @@ -15,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg; [Trait("Format", "Jpg")] public class JpegColorConverterTests { - private const float MaxColorChannelValue = 255f; + private const float MaxColorChannelValue = 255F; private const float Precision = 0.1F / 255; @@ -754,7 +752,7 @@ public class JpegColorConverterTests ValidateGrayScale(original, result, i); break; case JpegColorSpace.Ycck: - ValidateCyyK(original, result, i); + ValidateYccK(original, result, i); break; case JpegColorSpace.Cmyk: ValidateCmyk(original, result, i); @@ -774,17 +772,25 @@ public class JpegColorConverterTests private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { float y = values.Component0[i]; - float cb = values.Component1[i]; - float cr = values.Component2[i]; - Rgb expected = ColorSpaceConverter.Convert(new YCbCr(y, cb, cr)); + float cb = values.Component1[i] - 128; + float cr = values.Component2[i] - 128; + float r = (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); + float g = (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); + float b = (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); + + r /= MaxColorChannelValue; + g /= MaxColorChannelValue; + b /= MaxColorChannelValue; + + Rgb expected = Rgb.Clamp(new(r, g, b)); Rgb actual = Rgb.Clamp(new(result.Component0[i], result.Component1[i], result.Component2[i])); bool equal = ColorSpaceComparer.Equals(expected, actual); Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } - private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + private static void ValidateYccK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { float y = values.Component0[i]; float cb = values.Component1[i] - 128F; @@ -792,9 +798,7 @@ public class JpegColorConverterTests float k = values.Component3[i] / 255F; float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - float g = (255F - (float)Math.Round( - y - (0.344136F * cb) - (0.714136F * cr), - MidpointRounding.AwayFromZero)) * k; + float g = (255F - (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; r /= MaxColorChannelValue;