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