From c5cc79230440137f353713b4ed035ebca71b0029 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 14 May 2025 20:18:08 +1000 Subject: [PATCH] Optimize several transforms --- src/ImageSharp/ColorProfiles/CieXyz.cs | 8 ++----- .../ColorProfiles/ColorConversionOptions.cs | 21 +++++++++++++++--- .../ColorProfileConverterExtensionsIcc.cs | 2 +- .../Icc/Calculators/ColorTrcCalculator.cs | 2 +- src/ImageSharp/ColorProfiles/Lms.cs | 17 ++------------ src/ImageSharp/ColorProfiles/Rgb.cs | 6 ++--- .../VonKriesChromaticAdaptation.cs | 12 +++++----- src/ImageSharp/ColorProfiles/YCbCr.cs | 20 +++++------------ src/ImageSharp/ColorProfiles/YcbCrMatrix.cs | 3 +++ src/ImageSharp/ColorProfiles/YccK.cs | 22 +++++-------------- 10 files changed, 48 insertions(+), 65 deletions(-) diff --git a/src/ImageSharp/ColorProfiles/CieXyz.cs b/src/ImageSharp/ColorProfiles/CieXyz.cs index 393cd57ba..94fcfb21b 100644 --- a/src/ImageSharp/ColorProfiles/CieXyz.cs +++ b/src/ImageSharp/ColorProfiles/CieXyz.cs @@ -80,12 +80,8 @@ public readonly struct CieXyz : IProfileConnectingSpace [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(CieXyz left, CieXyz right) => !left.Equals(right); - /// - /// Returns a new representing this instance. - /// - /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector3 ToVector3() => new(this.X, this.Y, this.Z); + internal Vector3 ToVector3() => new(this.X, this.Y, this.Z); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Vector4 ToVector4() @@ -203,5 +199,5 @@ public readonly struct CieXyz : IProfileConnectingSpace public bool Equals(CieXyz 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)); } diff --git a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs index e17660bd4..44c7d2ac7 100644 --- a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs +++ b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs @@ -14,11 +14,16 @@ namespace SixLabors.ImageSharp.ColorProfiles; public class ColorConversionOptions { private Matrix4x4 adaptationMatrix; + private YCbCrMatrix yCbCrMatrix; /// /// Initializes a new instance of the class. /// - public ColorConversionOptions() => this.AdaptationMatrix = KnownChromaticAdaptationMatrices.Bradford; + public ColorConversionOptions() + { + this.AdaptationMatrix = KnownChromaticAdaptationMatrices.Bradford; + this.YCbCrMatrix = KnownYCbCrMatrices.BT601; + } /// /// Gets the memory allocator. @@ -48,7 +53,15 @@ public class ColorConversionOptions /// /// Gets the YCbCr matrix to used to perform conversions from/to RGB. /// - public YCbCrMatrix YCbCrMatrix { get; init; } = KnownYCbCrMatrices.BT601; + public YCbCrMatrix YCbCrMatrix + { + get => this.yCbCrMatrix; + init + { + this.yCbCrMatrix = value; + this.TransposedYCbCrMatrix = value.Transpose(); + } + } /// /// Gets the source ICC profile. @@ -70,10 +83,12 @@ public class ColorConversionOptions init { this.adaptationMatrix = value; - Matrix4x4.Invert(value, out Matrix4x4 inverted); + _ = Matrix4x4.Invert(value, out Matrix4x4 inverted); this.InverseAdaptationMatrix = inverted; } } + internal YCbCrMatrix TransposedYCbCrMatrix { get; private set; } + internal Matrix4x4 InverseAdaptationMatrix { get; private set; } } diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs index bad1d4c98..c33f40001 100644 --- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs +++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs @@ -363,7 +363,7 @@ internal static class ColorProfileConverterExtensionsIcc if (targetParams.HasNoPerceptualHandling || (oneProfileHasV2PerceptualAdjustment && targetParams.HasV2PerceptualHandling)) { - Vector3 vector = AdjustPcsToV2BlackPoint(xyz.ToVector3()); + Vector3 vector = AdjustPcsToV2BlackPoint(xyz.AsVector3Unsafe()); // when using XYZ PCS, negative values are clipped after PCS adjustment (in DemoIccMAX) if (targetParams.PcsType == IccColorSpaceType.CieXyz) diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs index cc0b727ba..3604642c9 100644 --- a/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs +++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs @@ -58,7 +58,7 @@ internal class ColorTrcCalculator : IVector4Calculator // when data to PCS, upstream process provides scaled XYZ // but input to calculator is descaled XYZ // (see DemoMaxICC IccCmm.cpp : CIccXformMatrixTRC::Apply) - xyz = new(CieXyz.FromScaledVector4(xyz).ToVector3(), 1); + xyz = new(CieXyz.FromScaledVector4(xyz).AsVector3Unsafe(), 1); return this.curveCalculator.Calculate(xyz); } } diff --git a/src/ImageSharp/ColorProfiles/Lms.cs b/src/ImageSharp/ColorProfiles/Lms.cs index fc1e60632..3aa3d7255 100644 --- a/src/ImageSharp/ColorProfiles/Lms.cs +++ b/src/ImageSharp/ColorProfiles/Lms.cs @@ -82,13 +82,6 @@ public readonly struct Lms : IColorProfile [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Lms left, Lms right) => !left.Equals(right); - /// - /// Returns a new representing this instance. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector3 ToVector3() => new(this.L, this.M, this.S); - /// public Vector4 ToScaledVector4() { @@ -134,10 +127,7 @@ public readonly struct Lms : IColorProfile /// public static Lms FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) - { - Vector3 vector = Vector3.Transform(source.ToVector3(), options.AdaptationMatrix); - return new Lms(vector); - } + => new(Vector3.Transform(source.AsVector3Unsafe(), options.AdaptationMatrix)); /// public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) @@ -153,10 +143,7 @@ public readonly struct Lms : IColorProfile /// public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) - { - Vector3 vector = Vector3.Transform(this.ToVector3(), options.InverseAdaptationMatrix); - return new CieXyz(vector); - } + => new(Vector3.Transform(this.AsVector3Unsafe(), options.InverseAdaptationMatrix)); /// public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination) diff --git a/src/ImageSharp/ColorProfiles/Rgb.cs b/src/ImageSharp/ColorProfiles/Rgb.cs index f77b73da8..9b95278bc 100644 --- a/src/ImageSharp/ColorProfiles/Rgb.cs +++ b/src/ImageSharp/ColorProfiles/Rgb.cs @@ -128,7 +128,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.ToVector3(), GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace))); + Rgb linear = new(Vector3.Transform(source.AsVector3Unsafe(), GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace))); return FromScaledVector4(options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4())); } @@ -141,7 +141,7 @@ public readonly struct Rgb : IProfileConnectingSpace for (int i = 0; i < source.Length; i++) { // Convert to linear rgb then compress. - Rgb linear = new(Vector3.Transform(source[i].ToVector3(), matrix)); + Rgb linear = new(Vector3.Transform(source[i].AsVector3Unsafe(), matrix)); Vector4 nonlinear = options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4()); destination[i] = FromScaledVector4(nonlinear); } @@ -271,7 +271,7 @@ public readonly struct Rgb : IProfileConnectingSpace Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); - Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.ToVector3(), inverseXyzMatrix); + Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.AsVector3Unsafe(), inverseXyzMatrix); // Use transposed Rows/Columns return new Matrix4x4 diff --git a/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs index 2f9a52912..ec25d0e1c 100644 --- a/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs +++ b/src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs @@ -34,9 +34,9 @@ public static class VonKriesChromaticAdaptation return new(source.X, source.Y, source.Z); } - Vector3 sourceColorLms = Vector3.Transform(source.ToVector3(), matrix); - Vector3 sourceWhitePointLms = Vector3.Transform(from.ToVector3(), matrix); - Vector3 targetWhitePointLms = Vector3.Transform(to.ToVector3(), matrix); + Vector3 sourceColorLms = Vector3.Transform(source.AsVector3Unsafe(), matrix); + Vector3 sourceWhitePointLms = Vector3.Transform(from.AsVector3Unsafe(), matrix); + Vector3 targetWhitePointLms = Vector3.Transform(to.AsVector3Unsafe(), matrix); Vector3 vector = targetWhitePointLms / sourceWhitePointLms; Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); @@ -76,8 +76,8 @@ public static class VonKriesChromaticAdaptation ref CieXyz sourceBase = ref MemoryMarshal.GetReference(source); ref CieXyz destinationBase = ref MemoryMarshal.GetReference(destination); - Vector3 sourceWhitePointLms = Vector3.Transform(from.ToVector3(), matrix); - Vector3 targetWhitePointLms = Vector3.Transform(to.ToVector3(), matrix); + Vector3 sourceWhitePointLms = Vector3.Transform(from.AsVector3Unsafe(), matrix); + Vector3 targetWhitePointLms = Vector3.Transform(to.AsVector3Unsafe(), matrix); Vector3 vector = targetWhitePointLms / sourceWhitePointLms; @@ -86,7 +86,7 @@ public static class VonKriesChromaticAdaptation ref CieXyz sp = ref Unsafe.Add(ref sourceBase, i); ref CieXyz dp = ref Unsafe.Add(ref destinationBase, i); - Vector3 sourceColorLms = Vector3.Transform(sp.ToVector3(), matrix); + Vector3 sourceColorLms = Vector3.Transform(sp.AsVector3Unsafe(), matrix); Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); dp = new CieXyz(Vector3.Transform(targetColorLms, inverseMatrix)); diff --git a/src/ImageSharp/ColorProfiles/YCbCr.cs b/src/ImageSharp/ColorProfiles/YCbCr.cs index 6a706cd0d..8e7dc57d7 100644 --- a/src/ImageSharp/ColorProfiles/YCbCr.cs +++ b/src/ImageSharp/ColorProfiles/YCbCr.cs @@ -130,14 +130,10 @@ public readonly struct YCbCr : IColorProfile public static YCbCr FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) { Vector3 rgb = source.AsVector3Unsafe(); - Matrix4x4 m = options.YCbCrMatrix.Forward; - Vector3 offset = options.YCbCrMatrix.Offset; + Matrix4x4 m = options.TransposedYCbCrMatrix.Forward; + Vector3 offset = options.TransposedYCbCrMatrix.Offset; - 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(new Vector3(y, cb, cr) + offset, true); + return new YCbCr(Vector3.Transform(rgb, m) + offset, true); } /// @@ -156,15 +152,11 @@ public readonly struct YCbCr : IColorProfile /// public Rgb ToProfileConnectingSpace(ColorConversionOptions options) { - Matrix4x4 m = options.YCbCrMatrix.Inverse; - Vector3 offset = options.YCbCrMatrix.Offset; + Matrix4x4 m = options.TransposedYCbCrMatrix.Inverse; + Vector3 offset = options.TransposedYCbCrMatrix.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)); - - return Rgb.FromScaledVector3(new Vector3(r, g, b)); + return Rgb.FromScaledVector3(Vector3.Transform(normalized, m)); } /// diff --git a/src/ImageSharp/ColorProfiles/YcbCrMatrix.cs b/src/ImageSharp/ColorProfiles/YcbCrMatrix.cs index 2a71de18a..ccb4ea986 100644 --- a/src/ImageSharp/ColorProfiles/YcbCrMatrix.cs +++ b/src/ImageSharp/ColorProfiles/YcbCrMatrix.cs @@ -55,4 +55,7 @@ public readonly struct YCbCrMatrix /// Gets the chrominance offset vector to apply during encoding (add) or decoding (subtract). /// public Vector3 Offset { get; } + + internal YCbCrMatrix Transpose() + => new(Matrix4x4.Transpose(this.Forward), Matrix4x4.Transpose(this.Inverse), this.Offset); } diff --git a/src/ImageSharp/ColorProfiles/YccK.cs b/src/ImageSharp/ColorProfiles/YccK.cs index af5e94e2d..f05e1431b 100644 --- a/src/ImageSharp/ColorProfiles/YccK.cs +++ b/src/ImageSharp/ColorProfiles/YccK.cs @@ -131,23 +131,18 @@ public readonly struct YccK : IColorProfile /// public Rgb ToProfileConnectingSpace(ColorConversionOptions options) { - Matrix4x4 m = options.YCbCrMatrix.Inverse; - Vector3 offset = options.YCbCrMatrix.Offset; + Matrix4x4 m = options.TransposedYCbCrMatrix.Inverse; + Vector3 offset = options.TransposedYCbCrMatrix.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); + return Rgb.FromScaledVector3(Vector3.Transform(normalized, m) * (1F - this.K)); } /// public static YccK FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) { - Matrix4x4 m = options.YCbCrMatrix.Forward; - Vector3 offset = options.YCbCrMatrix.Offset; + Matrix4x4 m = options.TransposedYCbCrMatrix.Forward; + Vector3 offset = options.TransposedYCbCrMatrix.Offset; Vector3 rgb = source.AsVector3Unsafe(); float k = 1F - MathF.Max(rgb.X, MathF.Max(rgb.Y, rgb.Z)); @@ -158,12 +153,7 @@ public readonly struct YccK : IColorProfile } 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)); + return new YccK(new Vector4(Vector3.Transform(rgb, m), k) + new Vector4(offset, 0F)); } ///