From ab7effe1c5a8f91dcca5e48b4d53d5f16b605d33 Mon Sep 17 00:00:00 2001 From: Stefan Nikolei Date: Mon, 24 Aug 2026 13:50:55 +0200 Subject: [PATCH] Precompute and cache RGB/XYZ matrices in RgbWorkingSpace Move RgbToCieXyz and CieXyzToRgb matrix construction from Rgb into RgbWorkingSpace, computing both matrices once at construction. Pass the precomputed inverse adaptation matrix through all chromatic adaptation calls to eliminate repeated Matrix4x4.Invert invocations during color conversions. --- ...rProfileConverterExtensionsCieLabCieLab.cs | 4 +- ...rProfileConverterExtensionsCieLabCieXyz.cs | 4 +- ...olorProfileConverterExtensionsCieLabRgb.cs | 4 +- ...rProfileConverterExtensionsCieXyzCieLab.cs | 4 +- ...rProfileConverterExtensionsCieXyzCieXyz.cs | 4 +- ...olorProfileConverterExtensionsCieXyzRgb.cs | 4 +- ...olorProfileConverterExtensionsRgbCieLab.cs | 4 +- ...olorProfileConverterExtensionsRgbCieXyz.cs | 4 +- .../ColorProfileConverterExtensionsRgbRgb.cs | 4 +- src/ImageSharp/ColorProfiles/Rgb.cs | 70 ++----------------- .../VonKriesChromaticAdaptation.cs | 58 +++++++++++++-- .../WorkingSpaces/RgbWorkingSpace.cs | 69 ++++++++++++++++++ .../Metadata/Profiles/ICC/IccProfile.SRGB.cs | 9 +-- 13 files changed, 147 insertions(+), 95 deletions(-) diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs index 4d94f583ab..2677946186 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs @@ -41,7 +41,7 @@ public static class ColorProfileConverterExtensionsCieLabCieLab // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); + pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFromB); @@ -87,7 +87,7 @@ public static class ColorProfileConverterExtensionsCieLabCieLab // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS. CieLab.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs index 1de4510bc9..e1dbd10524 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs @@ -43,7 +43,7 @@ public static class ColorProfileConverterExtensionsCieLabCieXyz // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix); + pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert to output from PCS return TTo.FromProfileConnectingSpace(options, in pcsTo); @@ -87,7 +87,7 @@ public static class ColorProfileConverterExtensionsCieLabCieXyz // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert to output from PCS TTo.FromProfileConnectionSpace(options, pcsTo, destination); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs index 4f0d470806..c77234d4a3 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs @@ -41,7 +41,7 @@ public static class ColorProfileConverterExtensionsCieLabRgb // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); + pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFromB); @@ -87,7 +87,7 @@ public static class ColorProfileConverterExtensionsCieLabRgb // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS. using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs index 3bb1b2d4f8..793abcd987 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs @@ -40,7 +40,7 @@ public static class ColorProfileConverterExtensionsCieXyzCieLab // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); + pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFrom); @@ -82,7 +82,7 @@ public static class ColorProfileConverterExtensionsCieXyzCieLab // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS. using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs index dabca45793..cbd9a8d1a2 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs @@ -40,7 +40,7 @@ public static class ColorProfileConverterExtensionsCieXyzCieXyz // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); + pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert to output from PCS return TTo.FromProfileConnectingSpace(options, in pcsFrom); @@ -79,7 +79,7 @@ public static class ColorProfileConverterExtensionsCieXyzCieXyz // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert to output from PCS TTo.FromProfileConnectionSpace(options, pcsFrom, destination); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs index 1803c0839c..4d4bf5fd09 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs @@ -40,7 +40,7 @@ public static class ColorProfileConverterExtensionsCieXyzRgb // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); + pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFrom); @@ -82,7 +82,7 @@ public static class ColorProfileConverterExtensionsCieXyzRgb // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS. using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs index c2ed9a5918..8cdc290561 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs @@ -41,7 +41,7 @@ public static class ColorProfileConverterExtensionsRgbCieLab // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); + pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFromB); @@ -87,7 +87,7 @@ public static class ColorProfileConverterExtensionsRgbCieLab // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS. using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs index 9cf7ec70d9..e29673f0a2 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs @@ -43,7 +43,7 @@ public static class ColorProfileConverterExtensionsRgbCieXyz // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix); + pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert to output from PCS return TTo.FromProfileConnectingSpace(options, in pcsTo); @@ -87,7 +87,7 @@ public static class ColorProfileConverterExtensionsRgbCieXyz // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert to output from PCS TTo.FromProfileConnectionSpace(options, pcsTo, destination); diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs index 34f3f7f191..779cd0e12c 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs @@ -41,7 +41,7 @@ public static class ColorProfileConverterExtensionsRgbRgb // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); + pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFromB); @@ -87,7 +87,7 @@ public static class ColorProfileConverterExtensionsRgbRgb // Adapt to target white point (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints(); - VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); + VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix); // Convert between PCS. Rgb.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo); diff --git a/src/ImageSharp/ColorProfiles/Rgb.cs b/src/ImageSharp/ColorProfiles/Rgb.cs index c95e54192f..01a7875d67 100644 --- a/src/ImageSharp/ColorProfiles/Rgb.cs +++ b/src/ImageSharp/ColorProfiles/Rgb.cs @@ -272,7 +272,7 @@ public readonly struct Rgb : IProfileConnectingSpace public static Rgb FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) { // Convert to linear rgb then compress. - Rgb linear = new(Vector3.Transform(source.AsVector3Unsafe(), GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace))); + Rgb linear = new(Vector3.Transform(source.AsVector3Unsafe(), options.TargetRgbWorkingSpace.CieXyzToRgbMatrix)); return FromScaledVector4(options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4())); } @@ -281,7 +281,7 @@ public readonly struct Rgb : IProfileConnectingSpace { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - Matrix4x4 matrix = GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace); + Matrix4x4 matrix = options.TargetRgbWorkingSpace.CieXyzToRgbMatrix; for (int i = 0; i < source.Length; i++) { // Convert to linear rgb then compress. @@ -298,7 +298,7 @@ public readonly struct Rgb : IProfileConnectingSpace Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(this.ToScaledVector4())); // Then convert to xyz - return new CieXyz(Vector3.Transform(linear.AsVector3Unsafe(), GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace))); + return new CieXyz(Vector3.Transform(linear.AsVector3Unsafe(), options.SourceRgbWorkingSpace.RgbToCieXyzMatrix)); } /// @@ -306,7 +306,7 @@ public readonly struct Rgb : IProfileConnectingSpace { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - Matrix4x4 matrix = GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace); + Matrix4x4 matrix = options.SourceRgbWorkingSpace.RgbToCieXyzMatrix; for (int i = 0; i < source.Length; i++) { Rgb rgb = source[i]; @@ -371,68 +371,6 @@ public readonly struct Rgb : IProfileConnectingSpace internal Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this)); - private static Matrix4x4 GetCieXyzToRgbMatrix(RgbWorkingSpace workingSpace) - { - Matrix4x4 matrix = GetRgbToCieXyzMatrix(workingSpace); - Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); - return inverseMatrix; - } - - private static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpace workingSpace) - { - DebugGuard.NotNull(workingSpace, nameof(workingSpace)); - RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; - - float xr = chromaticity.R.X; - float xg = chromaticity.G.X; - float xb = chromaticity.B.X; - float yr = chromaticity.R.Y; - float yg = chromaticity.G.Y; - float yb = chromaticity.B.Y; - - float mXr = xr / yr; - float mZr = (1 - xr - yr) / yr; - - float mXg = xg / yg; - float mZg = (1 - xg - yg) / yg; - - float mXb = xb / yb; - float mZb = (1 - xb - yb) / yb; - - Matrix4x4 xyzMatrix = new() - { - M11 = mXr, - M21 = mXg, - M31 = mXb, - M12 = 1F, - M22 = 1F, - M32 = 1F, - M13 = mZr, - M23 = mZg, - M33 = mZb, - M44 = 1F - }; - - Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); - - Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.AsVector3Unsafe(), inverseXyzMatrix); - - // Use transposed Rows/Columns - return new Matrix4x4 - { - M11 = vector.X * mXr, - M21 = vector.Y * mXg, - M31 = vector.Z * mXb, - M12 = vector.X, - M22 = vector.Y, - M32 = vector.Z, - M13 = vector.X * mZr, - M23 = vector.Y * mZg, - M33 = vector.Z * mZb, - M44 = 1F - }; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector512 ReadVector512(ref float src) { diff --git a/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs index 25604001fc..2107e7ce9d 100644 --- a/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs +++ b/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs @@ -25,6 +25,47 @@ public static class VonKriesChromaticAdaptation /// The chromatic adaptation matrix. /// The public static CieXyz Transform(in CieXyz source, (CieXyz From, CieXyz To) whitePoints, Matrix4x4 matrix) + { + Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); + return Transform(in source, whitePoints, matrix, inverseMatrix); + } + + /// + /// Performs a bulk linear transformation of a source color in to the destination color. + /// + /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). + /// The span to the source colors. + /// The span to the destination colors. + /// The conversion white points. + /// The chromatic adaptation matrix. + public static void Transform( + ReadOnlySpan source, + Span destination, + (CieXyz From, CieXyz To) whitePoints, + Matrix4x4 matrix) + { + Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); + Transform(source, destination, whitePoints, matrix, inverseMatrix); + } + + /// + /// Performs a linear transformation of a source color in to the destination color using a + /// precomputed inverse of . + /// + /// + /// For callers that already hold the inverse, e.g. , + /// to avoid repeating on every call. + /// + /// The source color. + /// The conversion white points. + /// The chromatic adaptation matrix. + /// The inverse of . + /// The + internal static CieXyz Transform( + in CieXyz source, + (CieXyz From, CieXyz To) whitePoints, + Matrix4x4 matrix, + Matrix4x4 inverseMatrix) { CieXyz from = whitePoints.From; CieXyz to = whitePoints.To; @@ -41,23 +82,28 @@ public static class VonKriesChromaticAdaptation Vector3 vector = targetWhitePointLms / sourceWhitePointLms; Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); - Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); return new CieXyz(Vector3.Transform(targetColorLms, inverseMatrix)); } /// - /// Performs a bulk linear transformation of a source color in to the destination color. + /// Performs a bulk linear transformation of a source color in to the destination color using a + /// precomputed inverse of . /// - /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). + /// + /// For callers that already hold the inverse, e.g. , + /// to avoid repeating on every call. + /// /// The span to the source colors. /// The span to the destination colors. /// The conversion white points. /// The chromatic adaptation matrix. - public static void Transform( + /// The inverse of . + internal static void Transform( ReadOnlySpan source, Span destination, (CieXyz From, CieXyz To) whitePoints, - Matrix4x4 matrix) + Matrix4x4 matrix, + Matrix4x4 inverseMatrix) { Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); int count = source.Length; @@ -71,8 +117,6 @@ public static class VonKriesChromaticAdaptation return; } - Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); - ref CieXyz sourceBase = ref MemoryMarshal.GetReference(source); ref CieXyz destinationBase = ref MemoryMarshal.GetReference(destination); diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs index b278dd3177..955567db85 100644 --- a/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs +++ b/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs @@ -19,6 +19,12 @@ public abstract class RgbWorkingSpace { this.WhitePoint = referenceWhite; this.ChromaticityCoordinates = chromaticityCoordinates; + + Matrix4x4 rgbToCieXyz = CreateRgbToCieXyzMatrix(referenceWhite, chromaticityCoordinates); + this.RgbToCieXyzMatrix = rgbToCieXyz; + + _ = Matrix4x4.Invert(rgbToCieXyz, out Matrix4x4 cieXyzToRgb); + this.CieXyzToRgbMatrix = cieXyzToRgb; } /// @@ -31,6 +37,17 @@ public abstract class RgbWorkingSpace /// public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; } + /// + /// Gets the matrix transforming linear rgb coordinates in this working space to CIE XYZ. + /// + internal Matrix4x4 RgbToCieXyzMatrix { get; } + + /// + /// Gets the inverse of , transforming CIE XYZ to linear rgb + /// coordinates in this working space. + /// + internal Matrix4x4 CieXyzToRgbMatrix { get; } + /// /// Compresses the linear vectors to their nonlinear equivalents with respect to the energy. /// @@ -84,4 +101,56 @@ public abstract class RgbWorkingSpace /// public override int GetHashCode() => HashCode.Combine(this.GetType(), this.WhitePoint, this.ChromaticityCoordinates); + + private static Matrix4x4 CreateRgbToCieXyzMatrix(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + { + float xr = chromaticityCoordinates.R.X; + float xg = chromaticityCoordinates.G.X; + float xb = chromaticityCoordinates.B.X; + float yr = chromaticityCoordinates.R.Y; + float yg = chromaticityCoordinates.G.Y; + float yb = chromaticityCoordinates.B.Y; + + float mXr = xr / yr; + float mZr = (1 - xr - yr) / yr; + + float mXg = xg / yg; + float mZg = (1 - xg - yg) / yg; + + float mXb = xb / yb; + float mZb = (1 - xb - yb) / yb; + + Matrix4x4 xyzMatrix = new() + { + M11 = mXr, + M21 = mXg, + M31 = mXb, + M12 = 1F, + M22 = 1F, + M32 = 1F, + M13 = mZr, + M23 = mZg, + M33 = mZb, + M44 = 1F + }; + + Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); + + Vector3 vector = Vector3.Transform(referenceWhite.AsVector3Unsafe(), inverseXyzMatrix); + + // Use transposed Rows/Columns + return new Matrix4x4 + { + M11 = vector.X * mXr, + M21 = vector.Y * mXg, + M31 = vector.Z * mXb, + M12 = vector.X, + M22 = vector.Y, + M32 = vector.Z, + M13 = vector.X * mZr, + M23 = vector.Y * mZg, + M33 = vector.Z * mZb, + M44 = 1F + }; + } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs index bfa4ab9bdb..48c75d1b6a 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs @@ -26,7 +26,7 @@ public sealed partial class IccProfile /// 3) Require rTRC, gTRC, bTRC to exist and be identical by parameters or sampled shape. /// 4) Accept if rXYZ/gXYZ/bXYZ already match the D50-adapted sRGB colorants within tolerance. /// 5) If white point ≈ D65, adapt only the colorant columns to D50 using Bradford - /// via and then compare. + /// via and then compare. /// This rejects channel-swapped and appearance profiles while allowing real sRGB. /// /// @@ -139,10 +139,11 @@ public sealed partial class IccProfile CieXyz fromWp = new(wtpt); // Declared white CieXyz toWp = KnownIlluminants.D50; // PCS white Matrix4x4 matrix = KnownChromaticAdaptationMatrices.Bradford; + Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); - rXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(rXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); - gXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(gXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); - bXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(bXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); + rXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(rXYZ), (fromWp, toWp), matrix, inverseMatrix).AsVector3Unsafe(); + gXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(gXYZ), (fromWp, toWp), matrix, inverseMatrix).AsVector3Unsafe(); + bXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(bXYZ), (fromWp, toWp), matrix, inverseMatrix).AsVector3Unsafe(); } // Require identity mapping of primaries, no permutation