Browse Source

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.
pull/3181/head
Stefan Nikolei 1 month ago
parent
commit
ab7effe1c5
  1. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
  2. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
  3. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
  4. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
  5. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
  6. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
  7. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
  8. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
  9. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
  10. 70
      src/ImageSharp/ColorProfiles/Rgb.cs
  11. 58
      src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs
  12. 69
      src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs
  13. 9
      src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs

4
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<TFrom, TTo>();
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<TFrom, TTo>();
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix);
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix);
// Convert between PCS.
CieLab.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo);

4
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<TFrom, TTo>();
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<TFrom, TTo>();
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);

4
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<TFrom, TTo>();
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<TFrom, TTo>();
VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix);
VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix);
// Convert between PCS.
using IMemoryOwner<Rgb> pcsToOwner = options.MemoryAllocator.Allocate<Rgb>(source.Length);

4
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<TFrom, TTo>();
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<TFrom, TTo>();
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix);
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix);
// Convert between PCS.
using IMemoryOwner<CieLab> pcsToOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length);

4
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<TFrom, TTo>();
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<TFrom, TTo>();
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);

4
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<TFrom, TTo>();
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<TFrom, TTo>();
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix);
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix);
// Convert between PCS.
using IMemoryOwner<Rgb> pcsToOwner = options.MemoryAllocator.Allocate<Rgb>(source.Length);

4
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<TFrom, TTo>();
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<TFrom, TTo>();
VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix);
VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix);
// Convert between PCS.
using IMemoryOwner<CieLab> pcsToOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length);

4
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<TFrom, TTo>();
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<TFrom, TTo>();
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);

4
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<TFrom, TTo>();
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<TFrom, TTo>();
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix);
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix, options.InverseAdaptationMatrix);
// Convert between PCS.
Rgb.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo);

70
src/ImageSharp/ColorProfiles/Rgb.cs

@ -272,7 +272,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
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<Rgb, CieXyz>
{
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, CieXyz>
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));
}
/// <inheritdoc/>
@ -306,7 +306,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
{
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<Rgb, CieXyz>
internal Vector3 AsVector3Unsafe() => Unsafe.As<Rgb, Vector3>(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<float> ReadVector512(ref float src)
{

58
src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs

@ -25,6 +25,47 @@ public static class VonKriesChromaticAdaptation
/// <param name="matrix">The chromatic adaptation matrix.</param>
/// <returns>The <see cref="CieXyz"/></returns>
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);
}
/// <summary>
/// Performs a bulk linear transformation of a source color in to the destination color.
/// </summary>
/// <remarks>Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).</remarks>
/// <param name="source">The span to the source colors.</param>
/// <param name="destination">The span to the destination colors.</param>
/// <param name="whitePoints">The conversion white points.</param>
/// <param name="matrix">The chromatic adaptation matrix.</param>
public static void Transform(
ReadOnlySpan<CieXyz> source,
Span<CieXyz> destination,
(CieXyz From, CieXyz To) whitePoints,
Matrix4x4 matrix)
{
Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix);
Transform(source, destination, whitePoints, matrix, inverseMatrix);
}
/// <summary>
/// Performs a linear transformation of a source color in to the destination color using a
/// precomputed inverse of <paramref name="matrix"/>.
/// </summary>
/// <remarks>
/// For callers that already hold the inverse, e.g. <see cref="ColorConversionOptions.InverseAdaptationMatrix"/>,
/// to avoid repeating <see cref="Matrix4x4.Invert"/> on every call.
/// </remarks>
/// <param name="source">The source color.</param>
/// <param name="whitePoints">The conversion white points.</param>
/// <param name="matrix">The chromatic adaptation matrix.</param>
/// <param name="inverseMatrix">The inverse of <paramref name="matrix"/>.</param>
/// <returns>The <see cref="CieXyz"/></returns>
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));
}
/// <summary>
/// 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 <paramref name="matrix"/>.
/// </summary>
/// <remarks>Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).</remarks>
/// <remarks>
/// For callers that already hold the inverse, e.g. <see cref="ColorConversionOptions.InverseAdaptationMatrix"/>,
/// to avoid repeating <see cref="Matrix4x4.Invert"/> on every call.
/// </remarks>
/// <param name="source">The span to the source colors.</param>
/// <param name="destination">The span to the destination colors.</param>
/// <param name="whitePoints">The conversion white points.</param>
/// <param name="matrix">The chromatic adaptation matrix.</param>
public static void Transform(
/// <param name="inverseMatrix">The inverse of <paramref name="matrix"/>.</param>
internal static void Transform(
ReadOnlySpan<CieXyz> source,
Span<CieXyz> 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);

69
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;
}
/// <summary>
@ -31,6 +37,17 @@ public abstract class RgbWorkingSpace
/// </summary>
public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
/// <summary>
/// Gets the matrix transforming linear rgb coordinates in this working space to CIE XYZ.
/// </summary>
internal Matrix4x4 RgbToCieXyzMatrix { get; }
/// <summary>
/// Gets the inverse of <see cref="RgbToCieXyzMatrix"/>, transforming CIE XYZ to linear rgb
/// coordinates in this working space.
/// </summary>
internal Matrix4x4 CieXyzToRgbMatrix { get; }
/// <summary>
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
@ -84,4 +101,56 @@ public abstract class RgbWorkingSpace
/// <inheritdoc/>
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
};
}
}

9
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 <see cref="VonKriesChromaticAdaptation.Transform(in CieXyz, ValueTuple{CieXyz, CieXyz}, Matrix4x4)"/> and then compare.
/// via <see cref="VonKriesChromaticAdaptation.Transform(in CieXyz, ValueTuple{CieXyz, CieXyz}, Matrix4x4, Matrix4x4)"/> and then compare.
/// This rejects channel-swapped and appearance profiles while allowing real sRGB.
/// </summary>
/// <remarks>
@ -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

Loading…
Cancel
Save