diff --git a/.editorconfig b/.editorconfig
index c28089d720..af1e5b44c1 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -104,8 +104,8 @@ dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:war
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_other_operators = always_for_clarity:suggestion
# Expression-level preferences
-dotnet_style_object_initializer = true:warning
-dotnet_style_collection_initializer = true:warning
+dotnet_style_object_initializer = true:error
+dotnet_style_collection_initializer = true:error
dotnet_style_explicit_tuple_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
@@ -135,9 +135,9 @@ csharp_style_prefer_null_check_over_type_check = true:warning
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules
[*.{cs,csx,cake}]
# 'var' preferences
-csharp_style_var_for_built_in_types = false:warning
-csharp_style_var_when_type_is_apparent = false:warning
-csharp_style_var_elsewhere = false:warning
+csharp_style_var_for_built_in_types = false:error
+csharp_style_var_when_type_is_apparent = false:error
+csharp_style_var_elsewhere = false:error
# Expression-bodied members
csharp_style_expression_bodied_methods = true:warning
csharp_style_expression_bodied_constructors = true:warning
@@ -160,7 +160,7 @@ csharp_style_pattern_local_over_anonymous_function = true:warning
csharp_style_deconstructed_variable_declaration = true:warning
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_range_operator = true:warning
-csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
+csharp_style_implicit_object_creation_when_type_is_apparent = true:error
# "Null" checking preferences
csharp_style_throw_expression = true:warning
csharp_style_conditional_delegate_call = true:warning
diff --git a/.gitattributes b/.gitattributes
index b5f742ab47..f7bd4d061e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -136,3 +136,10 @@
*.ico filter=lfs diff=lfs merge=lfs -text
*.cur filter=lfs diff=lfs merge=lfs -text
*.ani filter=lfs diff=lfs merge=lfs -text
+*.heic filter=lfs diff=lfs merge=lfs -text
+*.hif filter=lfs diff=lfs merge=lfs -text
+*.avif filter=lfs diff=lfs merge=lfs -text
+###############################################################################
+# Handle ICC files by git lfs
+###############################################################################
+*.icc filter=lfs diff=lfs merge=lfs -text
diff --git a/shared-infrastructure b/shared-infrastructure
index 1dbfb576c8..5e13cde851 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit 1dbfb576c83507645265c79e03369b66cdc0379f
+Subproject commit 5e13cde851a3d6e95d0dfdde2a57071f1efda9c3
diff --git a/src/ImageSharp/ColorProfiles/CieLab.cs b/src/ImageSharp/ColorProfiles/CieLab.cs
index 377cc20a99..98cb2e613a 100644
--- a/src/ImageSharp/ColorProfiles/CieLab.cs
+++ b/src/ImageSharp/ColorProfiles/CieLab.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -82,6 +83,49 @@ public CieLab(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 += new Vector3(0, 128F, 128F);
+ v3 /= new Vector3(100F, 255F, 255F);
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static CieLab FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= new Vector3(100F, 255, 255);
+ v3 -= new Vector3(0, 128F, 128F);
+ return new CieLab(v3);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static CieLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
@@ -136,7 +180,7 @@ public CieXyz ToProfileConnectingSpace(ColorConversionOptions options)
float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? Numerics.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa;
float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa;
- CieXyz whitePoint = options.WhitePoint;
+ CieXyz whitePoint = options.SourceWhitePoint;
Vector3 wxyz = new(whitePoint.X, whitePoint.Y, whitePoint.Z);
Vector3 xyzr = new(xr, yr, zr);
diff --git a/src/ImageSharp/ColorProfiles/CieLch.cs b/src/ImageSharp/ColorProfiles/CieLch.cs
index 1319783406..2e495b9a77 100644
--- a/src/ImageSharp/ColorProfiles/CieLch.cs
+++ b/src/ImageSharp/ColorProfiles/CieLch.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -50,7 +51,7 @@ public CieLch(Vector3 vector)
///
/// Gets the a chroma component.
- /// A value ranging from 0 to 200.
+ /// A value ranging from -200 to 200.
///
public float C { get; }
@@ -82,6 +83,49 @@ public CieLch(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLch left, CieLch right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 += new Vector3(0, 200, 0);
+ v3 /= new Vector3(100, 400, 360);
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static CieLch FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= new Vector3(100, 400, 360);
+ v3 -= new Vector3(0, 200, 0);
+ return new CieLch(v3);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static CieLch FromProfileConnectingSpace(ColorConversionOptions options, in CieLab source)
{
diff --git a/src/ImageSharp/ColorProfiles/CieLchuv.cs b/src/ImageSharp/ColorProfiles/CieLchuv.cs
index 7fd95feb19..c131d4bc64 100644
--- a/src/ImageSharp/ColorProfiles/CieLchuv.cs
+++ b/src/ImageSharp/ColorProfiles/CieLchuv.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -51,7 +52,7 @@ public CieLchuv(Vector3 vector)
///
/// Gets the a chroma component.
- /// A value ranging from 0 to 200.
+ /// A value ranging from -200 to 200.
///
public float C { get; }
@@ -81,6 +82,49 @@ public CieLchuv(Vector3 vector)
///
public static bool operator !=(CieLchuv left, CieLchuv right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 += new Vector3(0, 200, 0);
+ v3 /= new Vector3(100, 400, 360);
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static CieLchuv FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= new Vector3(100, 400, 360);
+ v3 -= new Vector3(0, 200, 0);
+ return new CieLchuv(v3);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static CieLchuv FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
{
diff --git a/src/ImageSharp/ColorProfiles/CieLuv.cs b/src/ImageSharp/ColorProfiles/CieLuv.cs
index 97e2826f74..bcc958cb48 100644
--- a/src/ImageSharp/ColorProfiles/CieLuv.cs
+++ b/src/ImageSharp/ColorProfiles/CieLuv.cs
@@ -84,6 +84,18 @@ public CieLuv(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLuv left, CieLuv right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4() => throw new NotImplementedException();
+
+ ///
+ public static CieLuv FromScaledVector4(Vector4 source) => throw new NotImplementedException();
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination) => throw new NotImplementedException();
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination) => throw new NotImplementedException();
+
///
public static CieLuv FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
{
@@ -143,7 +155,7 @@ public CieXyz ToProfileConnectingSpace(ColorConversionOptions options)
// Use doubles here for accuracy.
// Conversion algorithm described here:
// http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html
- CieXyz whitePoint = options.WhitePoint;
+ CieXyz whitePoint = options.SourceWhitePoint;
double l = this.L, u = this.U, v = this.V;
diff --git a/src/ImageSharp/ColorProfiles/CieXyy.cs b/src/ImageSharp/ColorProfiles/CieXyy.cs
index 62873df147..7fa943e9ea 100644
--- a/src/ImageSharp/ColorProfiles/CieXyy.cs
+++ b/src/ImageSharp/ColorProfiles/CieXyy.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -83,6 +84,38 @@ public CieXyy(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ => new(this.AsVector3Unsafe(), 1F);
+
+ ///
+ public static CieXyy FromScaledVector4(Vector4 source)
+ => new(source.AsVector3());
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static CieXyy FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
{
diff --git a/src/ImageSharp/ColorProfiles/CieXyz.cs b/src/ImageSharp/ColorProfiles/CieXyz.cs
index 07f9b47f9b..d64857606e 100644
--- a/src/ImageSharp/ColorProfiles/CieXyz.cs
+++ b/src/ImageSharp/ColorProfiles/CieXyz.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -88,6 +89,47 @@ public CieXyz(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToVector3() => new(this.X, this.Y, this.Z);
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 *= 32768F / 65535;
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static CieXyz FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= 65535 / 32768F;
+ return new CieXyz(v3);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static CieXyz FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
=> new(source.X, source.Y, source.Z);
diff --git a/src/ImageSharp/ColorProfiles/Cmyk.cs b/src/ImageSharp/ColorProfiles/Cmyk.cs
index e924904497..4359fa4a96 100644
--- a/src/ImageSharp/ColorProfiles/Cmyk.cs
+++ b/src/ImageSharp/ColorProfiles/Cmyk.cs
@@ -89,6 +89,32 @@ public Cmyk(Vector4 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector4 v4 = default;
+ v4 += this.AsVector4Unsafe();
+ return v4;
+ }
+
+ ///
+ public static Cmyk FromScaledVector4(Vector4 source)
+ => new(source);
+
+ ///
+ 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 static Cmyk FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
diff --git a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
index 1eb118834a..0f05c7e403 100644
--- a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
+++ b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -27,7 +28,7 @@ public class ColorConversionOptions
///
/// Gets the source white point used for chromatic adaptation in conversions from/to XYZ color space.
///
- public CieXyz WhitePoint { get; init; } = KnownIlluminants.D50;
+ public CieXyz SourceWhitePoint { get; init; } = KnownIlluminants.D50;
///
/// Gets the destination white point used for chromatic adaptation in conversions from/to XYZ color space.
@@ -37,13 +38,23 @@ public class ColorConversionOptions
///
/// Gets the source working space used for companding in conversions from/to XYZ color space.
///
- public RgbWorkingSpace RgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb;
+ public RgbWorkingSpace SourceRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb;
///
/// Gets the destination working space used for companding in conversions from/to XYZ color space.
///
public RgbWorkingSpace TargetRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb;
+ ///
+ /// Gets the source ICC profile.
+ ///
+ public IccProfile? SourceIccProfile { get; init; }
+
+ ///
+ /// Gets the target ICC profile.
+ ///
+ public IccProfile? TargetIccProfile { get; init; }
+
///
/// Gets the transformation matrix used in conversion to perform chromatic adaptation.
/// for further information. Default is Bradford.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs
index 18b90a622a..fa3f3d7e21 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs
@@ -33,8 +33,8 @@ public ColorProfileConverter(ColorConversionOptions options)
where TTo : struct, IColorProfile
{
CieXyz sourceWhitePoint = TFrom.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
- ? this.Options.WhitePoint
- : this.Options.RgbWorkingSpace.WhitePoint;
+ ? this.Options.SourceWhitePoint
+ : this.Options.SourceRgbWorkingSpace.WhitePoint;
CieXyz targetWhitePoint = TTo.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
? this.Options.TargetWhitePoint
@@ -42,4 +42,7 @@ public ColorProfileConverter(ColorConversionOptions options)
return (sourceWhitePoint, targetWhitePoint);
}
+
+ internal bool ShouldUseIccProfiles()
+ => this.Options.SourceIccProfile != null && this.Options.TargetIccProfile != null;
}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
index 41ae4b08fa..a2dd5d9ced 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -33,6 +38,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
index 04937e927e..096622564c 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -32,6 +37,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
index 47e4d2a80a..51be13799c 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -33,6 +38,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
index 6b1575d04c..3bab4e7b16 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -32,6 +37,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
index 8f56a5a663..5188511476 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -29,6 +34,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
index 9cc0bd9436..c56bf214b9 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -32,6 +37,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs
new file mode 100644
index 0000000000..b191337895
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs
@@ -0,0 +1,401 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.ColorProfiles.Icc;
+using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsIcc
+{
+ internal static TTo ConvertUsingIccProfile(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ // TODO: Validation of ICC Profiles against color profile. Is this possible?
+ if (converter.Options.SourceIccProfile is null)
+ {
+ throw new InvalidOperationException("Source ICC profile is missing.");
+ }
+
+ if (converter.Options.TargetIccProfile is null)
+ {
+ throw new InvalidOperationException("Target ICC profile is missing.");
+ }
+
+ ConversionParams sourceParams = new(converter.Options.SourceIccProfile, toPcs: true);
+ ConversionParams targetParams = new(converter.Options.TargetIccProfile, toPcs: false);
+
+ ColorProfileConverter pcsConverter = new(new ColorConversionOptions
+ {
+ MemoryAllocator = converter.Options.MemoryAllocator,
+ SourceWhitePoint = new CieXyz(converter.Options.SourceIccProfile.Header.PcsIlluminant),
+ TargetWhitePoint = new CieXyz(converter.Options.TargetIccProfile.Header.PcsIlluminant),
+ });
+
+ Vector4 sourcePcs = sourceParams.Converter.Calculate(source.ToScaledVector4());
+ Vector4 targetPcs;
+
+ // if both profiles need PCS adjustment, they both share the same unadjusted PCS space
+ // cancelling out the need to make the adjustment
+ // TODO: handle PCS adjustment for absolute intent? would make this a lot more complicated
+ // TODO: alternatively throw unsupported error, since most profiles headers contain perceptual (i've encountered a couple of relative intent, but so far no saturation or absolute)
+ if (sourceParams.AdjustPcsForPerceptual ^ targetParams.AdjustPcsForPerceptual)
+ {
+ targetPcs = GetTargetPcsWithPerceptualV2Adjustment(sourcePcs, sourceParams, targetParams, pcsConverter);
+ }
+ else
+ {
+ targetPcs = GetTargetPcsWithoutAdjustment(sourcePcs, sourceParams, targetParams, pcsConverter);
+ }
+
+ // Convert to the target space.
+ return TTo.FromScaledVector4(targetParams.Converter.Calculate(targetPcs));
+ }
+
+ // TODO: update to match workflow of the function above
+ internal static void ConvertUsingIccProfile(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ // TODO: Validation of ICC Profiles against color profile. Is this possible?
+ if (converter.Options.SourceIccProfile is null)
+ {
+ throw new InvalidOperationException("Source ICC profile is missing.");
+ }
+
+ if (converter.Options.TargetIccProfile is null)
+ {
+ throw new InvalidOperationException("Target ICC profile is missing.");
+ }
+
+ Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(destination));
+
+ ColorProfileConverter pcsConverter = new(new ColorConversionOptions()
+ {
+ MemoryAllocator = converter.Options.MemoryAllocator,
+
+ // TODO: Double check this but I think these are normalized values.
+ SourceWhitePoint = CieXyz.FromScaledVector4(new(converter.Options.SourceIccProfile.Header.PcsIlluminant, 1F)),
+ TargetWhitePoint = CieXyz.FromScaledVector4(new(converter.Options.TargetIccProfile.Header.PcsIlluminant, 1F)),
+ });
+
+ IccDataToPcsConverter sourceConverter = new(converter.Options.SourceIccProfile);
+ IccPcsToDataConverter targetConverter = new(converter.Options.TargetIccProfile);
+ IccColorSpaceType sourcePcsType = converter.Options.SourceIccProfile.Header.ProfileConnectionSpace;
+ IccColorSpaceType targetPcsType = converter.Options.TargetIccProfile.Header.ProfileConnectionSpace;
+ IccVersion sourceVersion = converter.Options.SourceIccProfile.Header.Version;
+ IccVersion targetVersion = converter.Options.TargetIccProfile.Header.Version;
+
+ using IMemoryOwner pcsBuffer = converter.Options.MemoryAllocator.Allocate(source.Length);
+ Span pcsNormalized = pcsBuffer.GetSpan();
+
+ // First normalize the values.
+ TFrom.ToScaledVector4(source, pcsNormalized);
+
+ // Now convert to the PCS space.
+ sourceConverter.Calculate(pcsNormalized, pcsNormalized);
+
+ // Profile connecting spaces can only be Lab, XYZ.
+ if (sourcePcsType is IccColorSpaceType.CieLab && targetPcsType is IccColorSpaceType.CieXyz)
+ {
+ // Convert from Lab to XYZ.
+ using IMemoryOwner pcsFromBuffer = converter.Options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromBuffer.GetSpan();
+ CieLab.FromScaledVector4(pcsNormalized, pcsFrom);
+
+ using IMemoryOwner pcsToBuffer = converter.Options.MemoryAllocator.Allocate(source.Length);
+ Span pcsTo = pcsToBuffer.GetSpan();
+ pcsConverter.Convert(pcsFrom, pcsTo);
+
+ // Convert to the target normalized PCS space.
+ CieXyz.ToScaledVector4(pcsTo, pcsNormalized);
+ }
+ else if (sourcePcsType is IccColorSpaceType.CieXyz && targetPcsType is IccColorSpaceType.CieLab)
+ {
+ // Convert from XYZ to Lab.
+ using IMemoryOwner pcsFromBuffer = converter.Options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromBuffer.GetSpan();
+ CieXyz.FromScaledVector4(pcsNormalized, pcsFrom);
+
+ using IMemoryOwner pcsToBuffer = converter.Options.MemoryAllocator.Allocate(source.Length);
+ Span pcsTo = pcsToBuffer.GetSpan();
+ pcsConverter.Convert(pcsFrom, pcsTo);
+
+ // Convert to the target normalized PCS space.
+ CieLab.ToScaledVector4(pcsTo, pcsNormalized);
+ }
+ else if (sourcePcsType is IccColorSpaceType.CieXyz && targetPcsType is IccColorSpaceType.CieXyz)
+ {
+ // Convert from XYZ to XYZ.
+ using IMemoryOwner pcsFromToBuffer = converter.Options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFromTo = pcsFromToBuffer.GetSpan();
+ CieXyz.FromScaledVector4(pcsNormalized, pcsFromTo);
+
+ pcsConverter.Convert(pcsFromTo, pcsFromTo);
+
+ // Convert to the target normalized PCS space.
+ CieXyz.ToScaledVector4(pcsFromTo, pcsNormalized);
+ }
+ else if (sourcePcsType is IccColorSpaceType.CieLab && targetPcsType is IccColorSpaceType.CieLab)
+ {
+ // Convert from Lab to Lab.
+ if (sourceVersion.Major == 4 && targetVersion.Major == 2)
+ {
+ // Convert from Lab v4 to Lab v2.
+ LabToLabV2(pcsNormalized, pcsNormalized);
+ }
+ else if (sourceVersion.Major == 2 && targetVersion.Major == 4)
+ {
+ // Convert from Lab v2 to Lab v4.
+ LabV2ToLab(pcsNormalized, pcsNormalized);
+ }
+
+ using IMemoryOwner pcsFromToBuffer = converter.Options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFromTo = pcsFromToBuffer.GetSpan();
+ CieLab.FromScaledVector4(pcsNormalized, pcsFromTo);
+
+ pcsConverter.Convert(pcsFromTo, pcsFromTo);
+
+ // Convert to the target normalized PCS space.
+ CieLab.ToScaledVector4(pcsFromTo, pcsNormalized);
+ }
+
+ // Convert to the target space.
+ targetConverter.Calculate(pcsNormalized, pcsNormalized);
+ TTo.FromScaledVector4(pcsNormalized, destination);
+ }
+
+ private static Vector4 GetTargetPcsWithoutAdjustment(
+ Vector4 sourcePcs,
+ ConversionParams sourceParams,
+ ConversionParams targetParams,
+ ColorProfileConverter pcsConverter)
+ {
+ // Profile connecting spaces can only be Lab, XYZ.
+ // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
+ // so ensure that Lab is using the correct encoding when a 16-bit LUT is used
+ switch (sourceParams.PcsType)
+ {
+ // Convert from Lab to XYZ.
+ case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieXyz:
+ {
+ sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs;
+ CieLab lab = CieLab.FromScaledVector4(sourcePcs);
+ CieXyz xyz = pcsConverter.Convert(in lab);
+
+ // DemoMaxICC clips negatives as part of IccUtil.cpp : icLabToXYZ > icICubeth
+ xyz = new CieXyz(Vector3.Clamp(xyz.ToVector3(), Vector3.Zero, new Vector3(float.MaxValue, float.MaxValue, float.MaxValue)));
+ return xyz.ToScaledVector4();
+ }
+
+ // Convert from XYZ to Lab.
+ case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieLab:
+ {
+ CieXyz xyz = CieXyz.FromScaledVector4(sourcePcs);
+ CieLab lab = pcsConverter.Convert(in xyz);
+ Vector4 targetPcs = lab.ToScaledVector4();
+ return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs;
+ }
+
+ // Convert from XYZ to XYZ.
+ case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieXyz:
+ {
+ CieXyz xyz = CieXyz.FromScaledVector4(sourcePcs);
+ CieXyz targetXyz = pcsConverter.Convert(in xyz);
+ return targetXyz.ToScaledVector4();
+ }
+
+ // Convert from Lab to Lab.
+ case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieLab:
+ {
+ // if both source and target LUT use same v2 LAB encoding, no need to correct them
+ if (sourceParams.Is16BitLutEntry && targetParams.Is16BitLutEntry)
+ {
+ CieLab sourceLab = CieLab.FromScaledVector4(sourcePcs);
+ CieLab targetLab = pcsConverter.Convert(in sourceLab);
+ return targetLab.ToScaledVector4();
+ }
+ else
+ {
+ sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs;
+ CieLab sourceLab = CieLab.FromScaledVector4(sourcePcs);
+ CieLab targetLab = pcsConverter.Convert(in sourceLab);
+ Vector4 targetPcs = targetLab.ToScaledVector4();
+ return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs;
+ }
+ }
+
+ default:
+ throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} to target PCS {targetParams.PcsType} is not supported");
+ }
+ }
+
+ ///
+ /// Effectively this is with an extra step in the middle.
+ /// It adjusts PCS by compensating for the black point used for perceptual intent in v2 profiles.
+ /// The adjustment needs to be performed in XYZ space, potentially an overhead of 2 more conversions.
+ /// Not required if both spaces need adjustment, since they both have the same understanding of the PCS.
+ /// Not compatible with PCS adjustment for absolute intent.
+ ///
+ /// The source PCS values.
+ /// The source profile parameters.
+ /// The target profile parameters.
+ /// The converter to use for the PCS adjustments.
+ /// Thrown when the source or target PCS is not supported.
+ private static Vector4 GetTargetPcsWithPerceptualV2Adjustment(
+ Vector4 sourcePcs,
+ ConversionParams sourceParams,
+ ConversionParams targetParams,
+ ColorProfileConverter pcsConverter)
+ {
+ // all conversions are funneled through XYZ in case PCS adjustments need to be made
+ CieXyz xyz;
+
+ switch (sourceParams.PcsType)
+ {
+ // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
+ // so convert Lab to modern v4 encoding when returned from a 16-bit LUT
+ case IccColorSpaceType.CieLab:
+ sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs;
+ CieLab lab = CieLab.FromScaledVector4(sourcePcs);
+ xyz = pcsConverter.Convert(in lab);
+
+ // DemoMaxICC clips negatives as part of IccUtil.cpp : icLabToXYZ > icICubeth
+ xyz = new CieXyz(Vector3.Max(xyz.ToVector3(), Vector3.Zero));
+ break;
+ case IccColorSpaceType.CieXyz:
+ xyz = CieXyz.FromScaledVector4(sourcePcs);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} is not supported");
+ }
+
+ // when converting from device to PCS with v2 perceptual intent
+ // the black point needs to be adjusted to v4 after converting the PCS values
+ if (sourceParams.AdjustPcsForPerceptual)
+ {
+ xyz = new CieXyz(AdjustPcsFromV2BlackPoint(xyz.ToVector3()));
+ }
+
+ // when converting from PCS to device with v2 perceptual intent
+ // the black point needs to be adjusted to v2 before converting the PCS values
+ if (targetParams.AdjustPcsForPerceptual)
+ {
+ xyz = new CieXyz(AdjustPcsToV2BlackPoint(xyz.ToVector3()));
+ }
+
+ Vector4 targetPcs;
+ switch (targetParams.PcsType)
+ {
+ // 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
+ // so convert Lab back to legacy encoding before using in a 16-bit LUT
+ case IccColorSpaceType.CieLab:
+ CieLab lab = pcsConverter.Convert(in xyz);
+ targetPcs = lab.ToScaledVector4();
+ return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs;
+ case IccColorSpaceType.CieXyz:
+ return xyz.ToScaledVector4();
+ default:
+ throw new ArgumentOutOfRangeException($"Target PCS {targetParams.PcsType} is not supported");
+ }
+ }
+
+ // as per DemoIccMAX icPerceptual values in IccCmm.h
+ // refBlack = 0.00336F, 0.0034731F, 0.00287F
+ // refWhite = 0.9642F, 1.0000F, 0.8249F
+ // scale = 1 - (refBlack / refWhite)
+ // offset = refBlack
+ private static Vector3 AdjustPcsFromV2BlackPoint(Vector3 xyz)
+ => (xyz * new Vector3(0.9965153F, 0.9965269F, 0.9965208F)) + new Vector3(0.00336F, 0.0034731F, 0.00287F);
+
+ // as per DemoIccMAX icPerceptual values in IccCmm.h
+ // refBlack = 0.00336F, 0.0034731F, 0.00287F
+ // refWhite = 0.9642F, 1.0000F, 0.8249F
+ // scale = 1 / (1 - (refBlack / refWhite))
+ // offset = -refBlack * scale
+ private static Vector3 AdjustPcsToV2BlackPoint(Vector3 xyz)
+ => (xyz * new Vector3(1.0034969F, 1.0034852F, 1.0034913F)) - new Vector3(0.0033717495F, 0.0034852044F, 0.0028800198F);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector4 LabToLabV2(Vector4 input)
+ => input * 65280F / 65535F;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector4 LabV2ToLab(Vector4 input)
+ => input * 65535F / 65280F;
+
+ private static void LabToLabV2(Span source, Span destination)
+ => LabToLab(source, destination, 65280F / 65535F);
+
+ private static void LabV2ToLab(Span source, Span destination)
+ => LabToLab(source, destination, 65535F / 65280F);
+
+ private static void LabToLab(Span source, Span destination, [ConstantExpected] float scale)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ Vector vScale = new(scale);
+ int i = 0;
+
+ // SIMD loop
+ int simdBatchSize = Vector.Count / 4; // Number of Vector4 elements per SIMD batch
+ for (; i <= source.Length - simdBatchSize; i += simdBatchSize)
+ {
+ // Load the vector from source span
+ Vector v = Unsafe.ReadUnaligned>(ref Unsafe.As(ref source[i]));
+
+ // Scale the vector
+ v *= vScale;
+
+ // Write the scaled vector to the destination span
+ Unsafe.WriteUnaligned(ref Unsafe.As(ref destination[i]), v);
+ }
+
+ // Scalar fallback for remaining elements
+ for (; i < source.Length; i++)
+ {
+ destination[i] = source[i] * scale;
+ }
+ }
+ else
+ {
+ // Scalar fallback if SIMD is not supported
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i] * scale;
+ }
+ }
+ }
+
+ private class ConversionParams
+ {
+ private readonly IccProfile profile;
+
+ internal ConversionParams(IccProfile profile, bool toPcs)
+ {
+ this.profile = profile;
+ this.Converter = toPcs ? new IccDataToPcsConverter(profile) : new IccPcsToDataConverter(profile);
+ }
+
+ internal IccConverterBase Converter { get; }
+
+ internal IccProfileHeader Header => this.profile.Header;
+
+ internal IccRenderingIntent Intent => this.Header.RenderingIntent;
+
+ internal IccColorSpaceType PcsType => this.Header.ProfileConnectionSpace;
+
+ internal IccVersion Version => this.Header.Version;
+
+ internal bool AdjustPcsForPerceptual => this.Intent == IccRenderingIntent.Perceptual && this.Version.Major == 2;
+
+ internal bool Is16BitLutEntry => this.Converter.Is16BitLutEntry;
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
index 415dd94c3f..badbcc6831 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -33,6 +38,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
index a13f645778..cd7d5e4d65 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -32,6 +37,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
index c1c75dea1b..2a4b64b1ca 100644
--- a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
@@ -12,6 +12,11 @@ public static TTo Convert(this ColorProfileConverter converter, in T
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS
@@ -33,6 +38,12 @@ public static void Convert(this ColorProfileConverter converter, Rea
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
ColorConversionOptions options = converter.Options;
// Convert to input PCS.
diff --git a/src/ImageSharp/ColorProfiles/Hsl.cs b/src/ImageSharp/ColorProfiles/Hsl.cs
index 2c98c7df99..6b84f955b8 100644
--- a/src/ImageSharp/ColorProfiles/Hsl.cs
+++ b/src/ImageSharp/ColorProfiles/Hsl.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -83,6 +84,38 @@ public Hsl(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Hsl left, Hsl right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ => new(this.AsVector3Unsafe() / 360F, 1F);
+
+ ///
+ public static Hsl FromScaledVector4(Vector4 source)
+ => new(source.AsVector3() * 360F);
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static Hsl FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
diff --git a/src/ImageSharp/ColorProfiles/Hsv.cs b/src/ImageSharp/ColorProfiles/Hsv.cs
index 7535f2463d..a7735bb195 100644
--- a/src/ImageSharp/ColorProfiles/Hsv.cs
+++ b/src/ImageSharp/ColorProfiles/Hsv.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -81,6 +82,38 @@ public Hsv(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Hsv left, Hsv right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ => new(this.AsVector3Unsafe() / 360F, 1F);
+
+ ///
+ public static Hsv FromScaledVector4(Vector4 source)
+ => new(source.AsVector3() * 360F);
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static Hsv FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
diff --git a/src/ImageSharp/ColorProfiles/HunterLab.cs b/src/ImageSharp/ColorProfiles/HunterLab.cs
index 43ad2ac5c0..d53d4c8134 100644
--- a/src/ImageSharp/ColorProfiles/HunterLab.cs
+++ b/src/ImageSharp/ColorProfiles/HunterLab.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -80,6 +81,49 @@ public HunterLab(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(HunterLab left, HunterLab right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 += new Vector3(0, 128F, 128F);
+ v3 /= new Vector3(100F, 255F, 255F);
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static HunterLab FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= new Vector3(100F, 255, 255);
+ v3 -= new Vector3(0, 128F, 128F);
+ return new HunterLab(v3);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static HunterLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
{
@@ -127,7 +171,7 @@ public CieXyz ToProfileConnectingSpace(ColorConversionOptions options)
{
// Conversion algorithm described here:
// http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
- CieXyz whitePoint = options.WhitePoint;
+ CieXyz whitePoint = options.SourceWhitePoint;
float l = this.L, a = this.A, b = this.B;
float xn = whitePoint.X, yn = whitePoint.Y, zn = whitePoint.Z;
diff --git a/src/ImageSharp/ColorProfiles/IColorProfile.cs b/src/ImageSharp/ColorProfiles/IColorProfile.cs
index 6a1b2ee8d0..f678fb2c96 100644
--- a/src/ImageSharp/ColorProfiles/IColorProfile.cs
+++ b/src/ImageSharp/ColorProfiles/IColorProfile.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Numerics;
+
namespace SixLabors.ImageSharp.ColorProfiles;
///
@@ -15,18 +17,60 @@ public interface IColorProfile
public static abstract ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource();
}
+///
+/// Defines the contract for all color profiles.
+///
+/// The type of color profile.
+public interface IColorProfile : IColorProfile, IEquatable
+ where TSelf : IColorProfile
+{
+ ///
+ /// Expands the pixel into a generic ("scaled") representation
+ /// with values scaled and clamped between 0 and 1.
+ /// The vector components are typically expanded in least to greatest significance order.
+ ///
+ /// The .
+ Vector4 ToScaledVector4();
+
+#pragma warning disable CA1000 // Do not declare static members on generic types
+ ///
+ /// Initializes the color instance from a generic a generic ("scaled") representation
+ /// with values scaled and clamped between 0 and 1.
+ ///
+ /// The vector to load the pixel from.
+ /// The .
+ public static abstract TSelf FromScaledVector4(Vector4 source);
+
+ ///
+ /// Converts the span of colors to a generic ("scaled") representation
+ /// with values scaled and clamped between 0 and 1.
+ ///
+ /// The color span to convert from.
+ /// The vector span to write the results to.
+ public static abstract void ToScaledVector4(ReadOnlySpan source, Span destination);
+
+ ///
+ /// Converts the span of colors from a generic ("scaled") representation
+ /// with values scaled and clamped between 0 and 1.
+ ///
+ /// The vector span to convert from.
+ /// The color span to write the results to.
+ public static abstract void FromScaledVector4(ReadOnlySpan source, Span destination);
+#pragma warning restore CA1000 // Do not declare static members on generic types
+}
+
///
/// Defines the contract for all color profiles.
///
/// The type of color profile.
/// The type of color profile connecting space.
-public interface IColorProfile : IColorProfile, IEquatable
+public interface IColorProfile : IColorProfile
where TSelf : IColorProfile
where TProfileSpace : struct, IProfileConnectingSpace
{
#pragma warning disable CA1000 // Do not declare static members on generic types
///
- /// Converts the color from the profile connection space.
+ /// Initializes the color instance from the profile connection space.
///
/// The color profile conversion options.
/// The color profile connecting space.
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs
new file mode 100644
index 0000000000..e14a4dde6a
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs
@@ -0,0 +1,502 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+///
+/// Implements interpolation methods for color profile lookup tables.
+/// Adapted from ICC Reference implementation:
+/// https://github.com/InternationalColorConsortium/DemoIccMAX/blob/79ecb74135ad47bac7d42692905a079839b7e105/IccProfLib/IccTagLut.cpp
+///
+internal class ClutCalculator : IVector4Calculator
+{
+ private readonly int inputCount;
+ private readonly int outputCount;
+ private readonly float[] lut;
+ private readonly byte[] gridPointCount;
+ private readonly byte[] maxGridPoint;
+ private readonly int[] indexFactor;
+ private readonly int[] dimSize;
+ private readonly int nodeCount;
+ private readonly float[][] nodes;
+ private readonly float[] g;
+ private readonly uint[] ig;
+ private readonly float[] s;
+ private readonly float[] df;
+ private readonly uint[] nPower;
+ private int n000;
+ private int n001;
+ private int n010;
+ private int n011;
+ private int n100;
+ private int n101;
+ private int n110;
+ private int n111;
+ private int n1000;
+
+ public ClutCalculator(IccClut clut)
+ {
+ Guard.NotNull(clut, nameof(clut));
+ Guard.MustBeGreaterThan(clut.InputChannelCount, 0, nameof(clut.InputChannelCount));
+ Guard.MustBeGreaterThan(clut.OutputChannelCount, 0, nameof(clut.OutputChannelCount));
+
+ this.inputCount = clut.InputChannelCount;
+ this.outputCount = clut.OutputChannelCount;
+ this.g = new float[this.inputCount];
+ this.ig = new uint[this.inputCount];
+ this.s = new float[this.inputCount];
+ this.nPower = new uint[16];
+ this.lut = clut.Values;
+ this.nodeCount = (int)Math.Pow(2, clut.InputChannelCount);
+ this.df = new float[this.nodeCount];
+ this.nodes = new float[this.nodeCount][];
+ this.dimSize = new int[this.inputCount];
+ this.gridPointCount = clut.GridPointCount;
+ this.maxGridPoint = new byte[this.inputCount];
+ for (int i = 0; i < this.inputCount; i++)
+ {
+ this.maxGridPoint[i] = (byte)(this.gridPointCount[i] - 1);
+ }
+
+ this.dimSize[this.inputCount - 1] = this.outputCount;
+ for (int i = this.inputCount - 2; i >= 0; i--)
+ {
+ this.dimSize[i] = this.dimSize[i + 1] * this.gridPointCount[i + 1];
+ }
+
+ this.indexFactor = this.CalculateIndexFactor();
+ }
+
+ public unsafe Vector4 Calculate(Vector4 value)
+ {
+ Vector4 result = default;
+ switch (this.inputCount)
+ {
+ case 1:
+ this.Interpolate1d((float*)&value, (float*)&result);
+ break;
+ case 2:
+ this.Interpolate2d((float*)&value, (float*)&result);
+ break;
+ case 3:
+ this.Interpolate3d((float*)&value, (float*)&result);
+ break;
+ case 4:
+ this.Interpolate4d((float*)&value, (float*)&result);
+ break;
+ default:
+ this.InterpolateNd((float*)&value, (float*)&result);
+ break;
+ }
+
+ return result;
+ }
+
+ private int[] CalculateIndexFactor()
+ {
+ int[] factors = new int[16];
+ switch (this.inputCount)
+ {
+ case 1:
+ factors[0] = this.n000 = 0;
+ factors[1] = this.n001 = this.dimSize[0];
+ break;
+ case 2:
+ factors[0] = this.n000 = 0;
+ factors[1] = this.n001 = this.dimSize[0];
+ factors[2] = this.n010 = this.dimSize[1];
+ factors[3] = this.n011 = this.n001 + this.n010;
+ break;
+ case 3:
+ factors[0] = this.n000 = 0;
+ factors[1] = this.n001 = this.dimSize[0];
+ factors[2] = this.n010 = this.dimSize[1];
+ factors[3] = this.n011 = this.n001 + this.n010;
+ factors[4] = this.n100 = this.dimSize[2];
+ factors[5] = this.n101 = this.n100 + this.n001;
+ factors[6] = this.n110 = this.n100 + this.n010;
+ factors[7] = this.n111 = this.n110 + this.n001;
+ break;
+ case 4:
+ factors[0] = 0;
+ factors[1] = this.n001 = this.dimSize[0];
+ factors[2] = this.n010 = this.dimSize[1];
+ factors[3] = factors[2] + factors[1];
+ factors[4] = this.n100 = this.dimSize[2];
+ factors[5] = factors[4] + factors[1];
+ factors[6] = factors[4] + factors[2];
+ factors[7] = factors[4] + factors[3];
+ factors[8] = this.n1000 = this.dimSize[3];
+ factors[9] = factors[8] + factors[1];
+ factors[10] = factors[8] + factors[2];
+ factors[11] = factors[8] + factors[3];
+ factors[12] = factors[8] + factors[4];
+ factors[13] = factors[8] + factors[5];
+ factors[14] = factors[8] + factors[6];
+ factors[15] = factors[8] + factors[7];
+ break;
+ default:
+ // Initialize ND interpolation variables.
+ factors[0] = 0;
+ int count;
+ for (count = 0; count < this.inputCount; count++)
+ {
+ this.nPower[count] = (uint)(1 << (this.inputCount - 1 - count));
+ }
+
+ uint[] nPower = [0, 1];
+ count = 0;
+ int nFlag = 1;
+ for (uint j = 1; j < this.nodeCount; j++)
+ {
+ if (j == nPower[1])
+ {
+ factors[j] = this.dimSize[count];
+ nPower[0] = (uint)(1 << count);
+ count++;
+ nPower[1] = (uint)(1 << count);
+ nFlag = 1;
+ }
+ else
+ {
+ factors[j] = factors[nPower[0]] + factors[nFlag];
+ nFlag++;
+ }
+ }
+
+ break;
+ }
+
+ return factors;
+ }
+
+ ///
+ /// One dimensional interpolation function.
+ ///
+ /// The input pixel values, which will be interpolated.
+ /// The interpolated output pixels.
+ private unsafe void Interpolate1d(float* srcPixel, float* destPixel)
+ {
+ byte mx = this.maxGridPoint[0];
+
+ float x = UnitClip(srcPixel[0]) * mx;
+
+ uint ix = (uint)x;
+
+ float u = x - ix;
+
+ if (ix == mx)
+ {
+ ix--;
+ u = 1.0f;
+ }
+
+ float nu = (float)(1.0 - u);
+
+ int i;
+ Span p = this.lut.AsSpan((int)(ix * this.n001));
+
+ // Normalize grid units.
+ float dF0 = nu;
+ float dF1 = u;
+
+ int offset = 0;
+ for (i = 0; i < this.outputCount; i++)
+ {
+ destPixel[i] = (float)((p[offset + this.n000] * dF0) + (p[offset + this.n001] * dF1));
+ offset++;
+ }
+ }
+
+ ///
+ /// Two dimensional interpolation function.
+ ///
+ /// The input pixel values, which will be interpolated.
+ /// The interpolated output pixels.
+ private unsafe void Interpolate2d(float* srcPixel, float* destPixel)
+ {
+ byte mx = this.maxGridPoint[0];
+ byte my = this.maxGridPoint[1];
+
+ float x = UnitClip(srcPixel[0]) * mx;
+ float y = UnitClip(srcPixel[1]) * my;
+
+ uint ix = (uint)x;
+ uint iy = (uint)y;
+
+ float u = x - ix;
+ float t = y - iy;
+
+ if (ix == mx)
+ {
+ ix--;
+ u = 1.0f;
+ }
+
+ if (iy == my)
+ {
+ iy--;
+ t = 1.0f;
+ }
+
+ float nt = (float)(1.0 - t);
+ float nu = (float)(1.0 - u);
+
+ int i;
+ Span p = this.lut.AsSpan((int)((ix * this.n001) + (iy * this.n010)));
+
+ // Normalize grid units.
+ float dF0 = nt * nu;
+ float dF1 = nt * u;
+ float dF2 = t * nu;
+ float dF3 = t * u;
+
+ int offset = 0;
+ for (i = 0; i < this.outputCount; i++)
+ {
+ destPixel[i] = (float)((p[offset + this.n000] * dF0) + (p[offset + this.n001] * dF1) + (p[offset + this.n010] * dF2) + (p[offset + this.n011] * dF3));
+ offset++;
+ }
+ }
+
+ ///
+ /// Three dimensional interpolation function.
+ ///
+ /// The input pixel values, which will be interpolated.
+ /// The interpolated output pixels.
+ private unsafe void Interpolate3d(float* srcPixel, float* destPixel)
+ {
+ byte mx = this.maxGridPoint[0];
+ byte my = this.maxGridPoint[1];
+ byte mz = this.maxGridPoint[2];
+
+ float x = UnitClip(srcPixel[0]) * mx;
+ float y = UnitClip(srcPixel[1]) * my;
+ float z = UnitClip(srcPixel[2]) * mz;
+
+ uint ix = (uint)x;
+ uint iy = (uint)y;
+ uint iz = (uint)z;
+
+ float u = x - ix;
+ float t = y - iy;
+ float s = z - iz;
+
+ if (ix == mx)
+ {
+ ix--;
+ u = 1.0f;
+ }
+
+ if (iy == my)
+ {
+ iy--;
+ t = 1.0f;
+ }
+
+ if (iz == mz)
+ {
+ iz--;
+ s = 1.0f;
+ }
+
+ float ns = (float)(1.0 - s);
+ float nt = (float)(1.0 - t);
+ float nu = (float)(1.0 - u);
+
+ Span p = this.lut.AsSpan((int)((ix * this.n001) + (iy * this.n010) + (iz * this.n100)));
+
+ // Normalize grid units
+ float dF0 = ns * nt * nu;
+ float dF1 = ns * nt * u;
+ float dF2 = ns * t * nu;
+ float dF3 = ns * t * u;
+ float dF4 = s * nt * nu;
+ float dF5 = s * nt * u;
+ float dF6 = s * t * nu;
+ float dF7 = s * t * u;
+
+ 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;
+ offset++;
+ }
+ }
+
+ ///
+ /// Four dimensional interpolation function.
+ ///
+ /// The input pixel values, which will be interpolated.
+ /// The interpolated output pixels.
+ private unsafe void Interpolate4d(float* srcPixel, float* destPixel)
+ {
+ byte mw = this.maxGridPoint[0];
+ byte mx = this.maxGridPoint[1];
+ byte my = this.maxGridPoint[2];
+ byte mz = this.maxGridPoint[3];
+
+ float w = UnitClip(srcPixel[0]) * mw;
+ float x = UnitClip(srcPixel[1]) * mx;
+ float y = UnitClip(srcPixel[2]) * my;
+ float z = UnitClip(srcPixel[3]) * mz;
+
+ uint iw = (uint)w;
+ uint ix = (uint)x;
+ uint iy = (uint)y;
+ uint iz = (uint)z;
+
+ float v = w - iw;
+ float u = x - ix;
+ float t = y - iy;
+ float s = z - iz;
+
+ if (iw == mw)
+ {
+ iw--;
+ v = 1.0f;
+ }
+
+ if (ix == mx)
+ {
+ ix--;
+ u = 1.0f;
+ }
+
+ if (iy == my)
+ {
+ iy--;
+ t = 1.0f;
+ }
+
+ if (iz == mz)
+ {
+ iz--;
+ s = 1.0f;
+ }
+
+ float ns = (float)(1.0 - s);
+ float nt = (float)(1.0 - t);
+ float nu = (float)(1.0 - u);
+ float nv = (float)(1.0 - v);
+
+ Span p = this.lut.AsSpan((int)((iw * this.n001) + (ix * this.n010) + (iy * this.n100) + (iz * this.n1000)));
+
+ // Normalize grid units.
+ float[] dF =
+ [
+ ns * nt * nu * nv,
+ ns * nt * nu * v,
+ ns * nt * u * nv,
+ ns * nt * u * v,
+ ns * t * nu * nv,
+ ns * t * nu * v,
+ ns * t * u * nv,
+ ns * t * u * v,
+ s * nt * nu * nv,
+ s * nt * nu * v,
+ s * nt * u * nv,
+ s * nt * u * v,
+ s * t * nu * nv,
+ s * t * nu * v,
+ s * t * u * nv,
+ s * t * u * v,
+ ];
+
+ int offset = 0;
+ for (int i = 0; i < this.outputCount; i++)
+ {
+ float pv = 0.0f;
+ for (int j = 0; j < 16; j++)
+ {
+ pv += p[offset + this.indexFactor[j]] * dF[j];
+ }
+
+ destPixel[i] = pv;
+ offset++;
+ }
+ }
+
+ ///
+ /// Generic N-dimensional interpolation function.
+ ///
+ /// The input pixel values, which will be interpolated.
+ /// The interpolated output pixels.
+ private unsafe void InterpolateNd(float* srcPixel, float* destPixel)
+ {
+ int index = 0;
+ for (int i = 0; i < this.inputCount; i++)
+ {
+ this.g[i] = UnitClip(srcPixel[i]) * this.maxGridPoint[i];
+ this.ig[i] = (uint)this.g[i];
+ this.s[this.inputCount - 1 - i] = this.g[i] - this.ig[i];
+ if (this.ig[i] == this.maxGridPoint[i])
+ {
+ this.ig[i]--;
+ this.s[this.inputCount - 1 - i] = 1.0f;
+ }
+
+ index += (int)this.ig[i] * this.dimSize[i];
+ }
+
+ Span p = this.lut.AsSpan(index);
+ float[] temp = new float[2];
+ bool nFlag = false;
+
+ for (int i = 0; i < this.nodeCount; i++)
+ {
+ this.df[i] = 1.0f;
+ }
+
+ for (int i = 0; i < this.inputCount; i++)
+ {
+ temp[0] = 1.0f - this.s[i];
+ temp[1] = this.s[i];
+ index = (int)this.nPower[i];
+ for (int j = 0; j < this.nodeCount; j++)
+ {
+ this.df[j] *= temp[nFlag ? 1 : 0];
+ if ((j + 1) % index == 0)
+ {
+ nFlag = !nFlag;
+ }
+ }
+
+ nFlag = false;
+ }
+
+ int offset = 0;
+ for (int i = 0; i < this.outputCount; i++)
+ {
+ float pv = 0;
+ for (int j = 0; j < this.nodeCount; j++)
+ {
+ pv += p[offset + this.indexFactor[j]] * this.df[j];
+ }
+
+ destPixel[i] = pv;
+ offset++;
+ }
+ }
+
+ private static float UnitClip(float v)
+ {
+ if (v < 0)
+ {
+ return 0;
+ }
+
+ if (v > 1.0)
+ {
+ return 1.0f;
+ }
+
+ return v;
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs
new file mode 100644
index 0000000000..ecd9a6f50b
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/ColorTrcCalculator.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+internal class ColorTrcCalculator : IVector4Calculator
+{
+ private readonly TrcCalculator curveCalculator;
+ private Matrix4x4 matrix;
+ private readonly bool toPcs;
+
+ public ColorTrcCalculator(
+ IccXyzTagDataEntry redMatrixColumn,
+ IccXyzTagDataEntry greenMatrixColumn,
+ IccXyzTagDataEntry blueMatrixColumn,
+ IccTagDataEntry redTrc,
+ IccTagDataEntry greenTrc,
+ IccTagDataEntry blueTrc,
+ bool toPcs)
+ {
+ this.toPcs = toPcs;
+ this.curveCalculator = new TrcCalculator([redTrc, greenTrc, blueTrc], !toPcs);
+
+ Vector3 mr = redMatrixColumn.Data[0];
+ Vector3 mg = greenMatrixColumn.Data[0];
+ Vector3 mb = blueMatrixColumn.Data[0];
+ this.matrix = new Matrix4x4(mr.X, mr.Y, mr.Z, 0, mg.X, mg.Y, mg.Z, 0, mb.X, mb.Y, mb.Z, 0, 0, 0, 0, 1);
+
+ if (!toPcs)
+ {
+ Matrix4x4.Invert(this.matrix, out this.matrix);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 Calculate(Vector4 value)
+ {
+ if (this.toPcs)
+ {
+ // when data to PCS, output from calculator is descaled XYZ
+ // but expected return value is scaled XYZ
+ // see DemoMaxICC IccCmm.cpp : CIccXformMatrixTRC::Apply()
+ value = this.curveCalculator.Calculate(value);
+ CieXyz xyz = new(Vector4.Transform(value, this.matrix).AsVector3());
+ return xyz.ToScaledVector4();
+ }
+ else
+ {
+ // when PCS to data, input to calculator is scaled XYZ
+ // but need descaled XYZ for matrix multiplication
+ // see DemoMaxICC IccCmm.cpp : CIccXformMatrixTRC::Apply()
+ Vector4 xyz = new(CieXyz.FromScaledVector4(value).ToVector3(), 1);
+ value = Vector4.Transform(xyz, this.matrix);
+ return this.curveCalculator.Calculate(value);
+ }
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs
new file mode 100644
index 0000000000..b3e26e2a29
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+internal partial class CurveCalculator
+{
+ private enum CalculationType
+ {
+ Identity,
+ Gamma,
+ Lut,
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs
new file mode 100644
index 0000000000..232f4349c2
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+#nullable disable
+
+using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+internal partial class CurveCalculator : ISingleCalculator
+{
+ private readonly LutCalculator lutCalculator;
+ private readonly float gamma;
+ private readonly CalculationType type;
+
+ public CurveCalculator(IccCurveTagDataEntry entry, bool inverted)
+ {
+ if (entry.IsIdentityResponse)
+ {
+ this.type = CalculationType.Identity;
+ }
+ else if (entry.IsGamma)
+ {
+ this.gamma = entry.Gamma;
+ if (inverted)
+ {
+ this.gamma = 1f / this.gamma;
+ }
+
+ this.type = CalculationType.Gamma;
+ }
+ else
+ {
+ this.lutCalculator = new LutCalculator(entry.CurveData, inverted);
+ this.type = CalculationType.Lut;
+ }
+ }
+
+ public float Calculate(float value)
+ => this.type switch
+ {
+ CalculationType.Identity => value,
+ CalculationType.Gamma => MathF.Pow(value, this.gamma), // TODO: This could be optimized using a LUT. See SrgbCompanding
+ CalculationType.Lut => this.lutCalculator.Calculate(value),
+ _ => throw new InvalidOperationException("Invalid calculation type"),
+ };
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/GrayTrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/GrayTrcCalculator.cs
new file mode 100644
index 0000000000..8d823c1e95
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/GrayTrcCalculator.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+internal class GrayTrcCalculator : IVector4Calculator
+{
+ private readonly TrcCalculator calculator;
+
+ public GrayTrcCalculator(IccTagDataEntry grayTrc, bool toPcs)
+ => this.calculator = new TrcCalculator(new IccTagDataEntry[] { grayTrc }, !toPcs);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 Calculate(Vector4 value) => this.calculator.Calculate(value);
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ISingleCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ISingleCalculator.cs
new file mode 100644
index 0000000000..ce9b7d2f9b
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/ISingleCalculator.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+///
+/// Represents an ICC calculator with a single floating point value and result
+///
+internal interface ISingleCalculator
+{
+ ///
+ /// Calculates a result from the given value
+ ///
+ /// The input value
+ /// The calculated result
+ float Calculate(float value);
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/IVector4Calculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/IVector4Calculator.cs
new file mode 100644
index 0000000000..9beea79503
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/IVector4Calculator.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+///
+/// Represents an ICC calculator with values and results
+///
+internal interface IVector4Calculator
+{
+ ///
+ /// Calculates a result from the given values
+ ///
+ /// The input values
+ /// The calculated result
+ Vector4 Calculate(Vector4 value);
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs
new file mode 100644
index 0000000000..a09150c9b6
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+internal partial class LutABCalculator
+{
+ private enum CalculationType
+ {
+ AtoB = 1 << 3,
+ BtoA = 1 << 4,
+
+ SingleCurve = 1,
+ CurveMatrix = 2,
+ CurveClut = 3,
+ Full = 4,
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs
new file mode 100644
index 0000000000..f891f66749
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs
@@ -0,0 +1,135 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+#nullable disable
+
+using System.Numerics;
+using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+internal partial class LutABCalculator : IVector4Calculator
+{
+ private CalculationType type;
+ private TrcCalculator curveACalculator;
+ private TrcCalculator curveBCalculator;
+ private TrcCalculator curveMCalculator;
+ private MatrixCalculator matrixCalculator;
+ private ClutCalculator clutCalculator;
+
+ public LutABCalculator(IccLutAToBTagDataEntry entry)
+ {
+ Guard.NotNull(entry, nameof(entry));
+ this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues);
+ this.type |= CalculationType.AtoB;
+ }
+
+ public LutABCalculator(IccLutBToATagDataEntry entry)
+ {
+ Guard.NotNull(entry, nameof(entry));
+ this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues);
+ this.type |= CalculationType.BtoA;
+ }
+
+ public Vector4 Calculate(Vector4 value)
+ {
+ switch (this.type)
+ {
+ case CalculationType.Full | CalculationType.AtoB:
+ value = this.curveACalculator.Calculate(value);
+ value = this.clutCalculator.Calculate(value);
+ value = this.curveMCalculator.Calculate(value);
+ value = this.matrixCalculator.Calculate(value);
+ return this.curveBCalculator.Calculate(value);
+
+ case CalculationType.Full | CalculationType.BtoA:
+ value = this.curveBCalculator.Calculate(value);
+ value = this.matrixCalculator.Calculate(value);
+ value = this.curveMCalculator.Calculate(value);
+ value = this.clutCalculator.Calculate(value);
+ return this.curveACalculator.Calculate(value);
+
+ case CalculationType.CurveClut | CalculationType.AtoB:
+ value = this.curveACalculator.Calculate(value);
+ value = this.clutCalculator.Calculate(value);
+ return this.curveBCalculator.Calculate(value);
+
+ case CalculationType.CurveClut | CalculationType.BtoA:
+ value = this.curveBCalculator.Calculate(value);
+ value = this.clutCalculator.Calculate(value);
+ return this.curveACalculator.Calculate(value);
+
+ case CalculationType.CurveMatrix | CalculationType.AtoB:
+ value = this.curveMCalculator.Calculate(value);
+ value = this.matrixCalculator.Calculate(value);
+ return this.curveBCalculator.Calculate(value);
+
+ case CalculationType.CurveMatrix | CalculationType.BtoA:
+ value = this.curveBCalculator.Calculate(value);
+ value = this.matrixCalculator.Calculate(value);
+ return this.curveMCalculator.Calculate(value);
+
+ case CalculationType.SingleCurve | CalculationType.AtoB:
+ case CalculationType.SingleCurve | CalculationType.BtoA:
+ return this.curveBCalculator.Calculate(value);
+
+ default:
+ throw new InvalidOperationException("Invalid calculation type");
+ }
+ }
+
+ private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagDataEntry[] curveM, Vector3? matrix3x1, Matrix4x4? matrix3x3, IccClut clut)
+ {
+ bool hasACurve = curveA != null;
+ bool hasBCurve = curveB != null;
+ bool hasMCurve = curveM != null;
+ bool hasMatrix = matrix3x1 != null && matrix3x3 != null;
+ bool hasClut = clut != null;
+
+ if (hasBCurve && hasMatrix && hasMCurve && hasClut && hasACurve)
+ {
+ this.type = CalculationType.Full;
+ }
+ else if (hasBCurve && hasClut && hasACurve)
+ {
+ this.type = CalculationType.CurveClut;
+ }
+ else if (hasBCurve && hasMatrix && hasMCurve)
+ {
+ this.type = CalculationType.CurveMatrix;
+ }
+ else if (hasBCurve)
+ {
+ this.type = CalculationType.SingleCurve;
+ }
+ else
+ {
+ throw new InvalidIccProfileException("AToB or BToA tag has an invalid configuration");
+ }
+
+ if (hasACurve)
+ {
+ this.curveACalculator = new TrcCalculator(curveA, false);
+ }
+
+ if (hasBCurve)
+ {
+ this.curveBCalculator = new TrcCalculator(curveB, false);
+ }
+
+ if (hasMCurve)
+ {
+ this.curveMCalculator = new TrcCalculator(curveM, false);
+ }
+
+ if (hasMatrix)
+ {
+ this.matrixCalculator = new MatrixCalculator(matrix3x3.Value, matrix3x1.Value);
+ }
+
+ if (hasClut)
+ {
+ this.clutCalculator = new ClutCalculator(clut);
+ }
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutCalculator.cs
new file mode 100644
index 0000000000..824310b3ad
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutCalculator.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+internal class LutCalculator : ISingleCalculator
+{
+ private readonly float[] lut;
+ private readonly bool inverse;
+
+ public LutCalculator(float[] lut, bool inverse)
+ {
+ Guard.NotNull(lut, nameof(lut));
+
+ this.lut = lut;
+ this.inverse = inverse;
+ }
+
+ public float Calculate(float value)
+ {
+ if (this.inverse)
+ {
+ return this.LookupInverse(value);
+ }
+
+ return this.Lookup(value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float Lookup(float value)
+ {
+ float factor = value * (this.lut.Length - 1);
+ int index = (int)factor;
+ float low = this.lut[index];
+
+ float high = 1F;
+ if (index < this.lut.Length - 1)
+ {
+ high = this.lut[index + 1];
+ }
+
+ return low + ((high - low) * (factor - index));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float LookupInverse(float value)
+ {
+ int index = Array.BinarySearch(this.lut, value);
+ if (index >= 0)
+ {
+ return index / (float)(this.lut.Length - 1);
+ }
+
+ index = ~index;
+ if (index == 0)
+ {
+ return 0;
+ }
+ else if (index == this.lut.Length)
+ {
+ return 1;
+ }
+
+ float high = this.lut[index];
+ float low = this.lut[index - 1];
+
+ float valuePercent = (value - low) / (high - low);
+ float lutRange = 1 / (float)(this.lut.Length - 1);
+ float lutLow = (index - 1) / (float)(this.lut.Length - 1);
+
+ return lutLow + (valuePercent * lutRange);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/LutEntryCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutEntryCalculator.cs
new file mode 100644
index 0000000000..c52aeefa42
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/LutEntryCalculator.cs
@@ -0,0 +1,80 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+#nullable disable
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+internal class LutEntryCalculator : IVector4Calculator
+{
+ private LutCalculator[] inputCurve;
+ private LutCalculator[] outputCurve;
+ private ClutCalculator clutCalculator;
+ private Matrix4x4 matrix;
+ private bool doTransform;
+
+ public LutEntryCalculator(IccLut8TagDataEntry lut)
+ {
+ Guard.NotNull(lut, nameof(lut));
+ this.Init(lut.InputValues, lut.OutputValues, lut.ClutValues, lut.Matrix);
+ this.Is16Bit = false;
+ }
+
+ public LutEntryCalculator(IccLut16TagDataEntry lut)
+ {
+ Guard.NotNull(lut, nameof(lut));
+ this.Init(lut.InputValues, lut.OutputValues, lut.ClutValues, lut.Matrix);
+ this.Is16Bit = true;
+ }
+
+ internal bool Is16Bit { get; }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 Calculate(Vector4 value)
+ {
+ if (this.doTransform)
+ {
+ value = Vector4.Transform(value, Matrix4x4.Transpose(this.matrix));
+ }
+
+ value = CalculateLut(this.inputCurve, value);
+ value = this.clutCalculator.Calculate(value);
+ return CalculateLut(this.outputCurve, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector4 CalculateLut(LutCalculator[] lut, Vector4 value)
+ {
+ ref float f = ref Unsafe.As(ref value);
+ for (int i = 0; i < lut.Length; i++)
+ {
+ Unsafe.Add(ref f, i) = lut[i].Calculate(Unsafe.Add(ref f, i));
+ }
+
+ return value;
+ }
+
+ private void Init(IccLut[] inputCurve, IccLut[] outputCurve, IccClut clut, Matrix4x4 matrix)
+ {
+ this.inputCurve = InitLut(inputCurve);
+ this.outputCurve = InitLut(outputCurve);
+ this.clutCalculator = new ClutCalculator(clut);
+ this.matrix = matrix;
+
+ this.doTransform = !matrix.IsIdentity && inputCurve.Length == 3;
+ }
+
+ private static LutCalculator[] InitLut(IccLut[] curves)
+ {
+ LutCalculator[] calculators = new LutCalculator[curves.Length];
+ for (int i = 0; i < curves.Length; i++)
+ {
+ calculators[i] = new LutCalculator(curves[i].Values, false);
+ }
+
+ return calculators;
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/MatrixCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/MatrixCalculator.cs
new file mode 100644
index 0000000000..6be1fdbf95
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/MatrixCalculator.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+internal class MatrixCalculator : IVector4Calculator
+{
+ private Matrix4x4 matrix2D;
+ private Vector4 matrix1D;
+
+ public MatrixCalculator(Matrix4x4 matrix3x3, Vector3 matrix3x1)
+ {
+ this.matrix2D = matrix3x3;
+ this.matrix1D = new Vector4(matrix3x1, 0);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 Calculate(Vector4 value)
+ {
+ Vector4 transformed = Vector4.Transform(value, this.matrix2D);
+ return Vector4.Add(this.matrix1D, transformed);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/ParametricCurveCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/ParametricCurveCalculator.cs
new file mode 100644
index 0000000000..2a3945e270
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/ParametricCurveCalculator.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+internal class ParametricCurveCalculator : ISingleCalculator
+{
+ private readonly IccParametricCurve curve;
+ private readonly IccParametricCurveType type;
+ private const IccParametricCurveType InvertedFlag = (IccParametricCurveType)(1 << 3);
+
+ public ParametricCurveCalculator(IccParametricCurveTagDataEntry entry, bool inverted)
+ {
+ Guard.NotNull(entry, nameof(entry));
+ this.curve = entry.Curve;
+ this.type = entry.Curve.Type;
+
+ if (inverted)
+ {
+ this.type |= InvertedFlag;
+ }
+ }
+
+ public float Calculate(float value)
+ => this.type switch
+ {
+ IccParametricCurveType.Type1 => this.CalculateGamma(value),
+ IccParametricCurveType.Cie122_1996 => this.CalculateCie122(value),
+ IccParametricCurveType.Iec61966_3 => this.CalculateIec61966(value),
+ IccParametricCurveType.SRgb => this.CalculateSRgb(value),
+ IccParametricCurveType.Type5 => this.CalculateType5(value),
+ IccParametricCurveType.Type1 | InvertedFlag => this.CalculateInvertedGamma(value),
+ IccParametricCurveType.Cie122_1996 | InvertedFlag => this.CalculateInvertedCie122(value),
+ IccParametricCurveType.Iec61966_3 | InvertedFlag => this.CalculateInvertedIec61966(value),
+ IccParametricCurveType.SRgb | InvertedFlag => this.CalculateInvertedSRgb(value),
+ IccParametricCurveType.Type5 | InvertedFlag => this.CalculateInvertedType5(value),
+ _ => throw new InvalidIccProfileException("ParametricCurve"),
+ };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateGamma(float value) => MathF.Pow(value, this.curve.G);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateCie122(float value)
+ {
+ if (value >= -this.curve.B / this.curve.A)
+ {
+ return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G);
+ }
+
+ return 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateIec61966(float value)
+ {
+ if (value >= -this.curve.B / this.curve.A)
+ {
+ return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G) + this.curve.C;
+ }
+
+ return this.curve.C;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateSRgb(float value)
+ {
+ if (value >= this.curve.D)
+ {
+ return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G);
+ }
+
+ return this.curve.C * value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateType5(float value)
+ {
+ if (value >= this.curve.D)
+ {
+ return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G) + this.curve.E;
+ }
+
+ return (this.curve.C * value) + this.curve.F;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateInvertedGamma(float value)
+ => MathF.Pow(value, 1 / this.curve.G);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateInvertedCie122(float value)
+ => (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateInvertedIec61966(float value)
+ {
+ if (value >= this.curve.C)
+ {
+ return (MathF.Pow(value - this.curve.C, 1 / this.curve.G) - this.curve.B) / this.curve.A;
+ }
+
+ return -this.curve.B / this.curve.A;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateInvertedSRgb(float value)
+ {
+ if (value >= MathF.Pow((this.curve.A * this.curve.D) + this.curve.B, this.curve.G))
+ {
+ return (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A;
+ }
+
+ return value / this.curve.C;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float CalculateInvertedType5(float value)
+ {
+ if (value >= (this.curve.C * this.curve.D) + this.curve.F)
+ {
+ return (MathF.Pow(value - this.curve.E, 1 / this.curve.G) - this.curve.B) / this.curve.A;
+ }
+
+ return (value - this.curve.F) / this.curve.C;
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs b/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs
new file mode 100644
index 0000000000..b4b5028ed7
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+
+internal class TrcCalculator : IVector4Calculator
+{
+ private readonly ISingleCalculator[] calculators;
+
+ public TrcCalculator(IccTagDataEntry[] entries, bool inverted)
+ {
+ Guard.NotNull(entries, nameof(entries));
+
+ this.calculators = new ISingleCalculator[entries.Length];
+ for (int i = 0; i < entries.Length; i++)
+ {
+ this.calculators[i] = entries[i] switch
+ {
+ IccCurveTagDataEntry curve => new CurveCalculator(curve, inverted),
+ IccParametricCurveTagDataEntry parametricCurve => new ParametricCurveCalculator(parametricCurve, inverted),
+ _ => throw new InvalidIccProfileException("Invalid Entry."),
+ };
+ }
+ }
+
+ public unsafe Vector4 Calculate(Vector4 value)
+ {
+ ref float f = ref Unsafe.As(ref value);
+ for (int i = 0; i < this.calculators.Length; i++)
+ {
+ Unsafe.Add(ref f, i) = this.calculators[i].Calculate(Unsafe.Add(ref f, i));
+ }
+
+ return value;
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs
new file mode 100644
index 0000000000..2b078e09fe
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs
@@ -0,0 +1,155 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+#nullable disable
+
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+///
+/// Color converter for ICC profiles
+///
+internal abstract partial class IccConverterBase
+{
+ private static ConversionMethod GetConversionMethod(IccProfile profile, IccRenderingIntent renderingIntent) => profile.Header.Class switch
+ {
+ IccProfileClass.InputDevice or
+ IccProfileClass.DisplayDevice or
+ IccProfileClass.OutputDevice or
+ IccProfileClass.ColorSpace => CheckMethod1(profile, renderingIntent),
+ IccProfileClass.DeviceLink or IccProfileClass.Abstract => CheckMethod2(profile),
+ _ => ConversionMethod.Invalid,
+ };
+
+ private static ConversionMethod CheckMethod1(IccProfile profile, IccRenderingIntent renderingIntent)
+ {
+ ConversionMethod method = CheckMethodD(profile, renderingIntent);
+ if (method != ConversionMethod.Invalid)
+ {
+ return method;
+ }
+
+ method = CheckMethodA(profile, renderingIntent);
+ if (method != ConversionMethod.Invalid)
+ {
+ return method;
+ }
+
+ method = CheckMethodA0(profile);
+ if (method != ConversionMethod.Invalid)
+ {
+ return method;
+ }
+
+ method = CheckMethodTrc(profile);
+ if (method != ConversionMethod.Invalid)
+ {
+ return method;
+ }
+
+ return ConversionMethod.Invalid;
+ }
+
+ private static ConversionMethod CheckMethodD(IccProfile profile, IccRenderingIntent renderingIntent)
+ {
+ if ((HasTag(profile, IccProfileTag.DToB0) || HasTag(profile, IccProfileTag.BToD0))
+ && renderingIntent == IccRenderingIntent.Perceptual)
+ {
+ return ConversionMethod.D0;
+ }
+
+ if ((HasTag(profile, IccProfileTag.DToB1) || HasTag(profile, IccProfileTag.BToD1))
+ && renderingIntent == IccRenderingIntent.MediaRelativeColorimetric)
+ {
+ return ConversionMethod.D1;
+ }
+
+ if ((HasTag(profile, IccProfileTag.DToB2) || HasTag(profile, IccProfileTag.BToD2))
+ && renderingIntent == IccRenderingIntent.Saturation)
+ {
+ return ConversionMethod.D2;
+ }
+
+ if ((HasTag(profile, IccProfileTag.DToB3) || HasTag(profile, IccProfileTag.BToD3))
+ && renderingIntent == IccRenderingIntent.AbsoluteColorimetric)
+ {
+ return ConversionMethod.D3;
+ }
+
+ return ConversionMethod.Invalid;
+ }
+
+ private static ConversionMethod CheckMethodA(IccProfile profile, IccRenderingIntent renderingIntent)
+ {
+ if ((HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.BToA0))
+ && renderingIntent == IccRenderingIntent.Perceptual)
+ {
+ return ConversionMethod.A0;
+ }
+
+ if ((HasTag(profile, IccProfileTag.AToB1) || HasTag(profile, IccProfileTag.BToA1))
+ && renderingIntent == IccRenderingIntent.MediaRelativeColorimetric)
+ {
+ return ConversionMethod.A1;
+ }
+
+ if ((HasTag(profile, IccProfileTag.AToB2) || HasTag(profile, IccProfileTag.BToA2))
+ && renderingIntent == IccRenderingIntent.Saturation)
+ {
+ return ConversionMethod.A2;
+ }
+
+ return ConversionMethod.Invalid;
+ }
+
+ private static ConversionMethod CheckMethodA0(IccProfile profile)
+ {
+ bool valid = HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.BToA0);
+ return valid ? ConversionMethod.A0 : ConversionMethod.Invalid;
+ }
+
+ private static ConversionMethod CheckMethodTrc(IccProfile profile)
+ {
+ if (HasTag(profile, IccProfileTag.RedMatrixColumn)
+ && HasTag(profile, IccProfileTag.GreenMatrixColumn)
+ && HasTag(profile, IccProfileTag.BlueMatrixColumn)
+ && HasTag(profile, IccProfileTag.RedTrc)
+ && HasTag(profile, IccProfileTag.GreenTrc)
+ && HasTag(profile, IccProfileTag.BlueTrc))
+ {
+ return ConversionMethod.ColorTrc;
+ }
+
+ if (HasTag(profile, IccProfileTag.GrayTrc))
+ {
+ return ConversionMethod.GrayTrc;
+ }
+
+ return ConversionMethod.Invalid;
+ }
+
+ private static ConversionMethod CheckMethod2(IccProfile profile)
+ {
+ if (HasTag(profile, IccProfileTag.DToB0) || HasTag(profile, IccProfileTag.BToD0))
+ {
+ return ConversionMethod.D0;
+ }
+
+ if (HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.AToB0))
+ {
+ return ConversionMethod.A0;
+ }
+
+ return ConversionMethod.Invalid;
+ }
+
+ private static bool HasTag(IccProfile profile, IccProfileTag tag)
+ => profile.Entries.Any(t => t.TagSignature == tag);
+
+ private static IccTagDataEntry GetTag(IccProfile profile, IccProfileTag tag)
+ => Array.Find(profile.Entries, t => t.TagSignature == tag);
+
+ private static T GetTag(IccProfile profile, IccProfileTag tag)
+ where T : IccTagDataEntry
+ => profile.Entries.OfType().FirstOrDefault(t => t.TagSignature == tag);
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs
new file mode 100644
index 0000000000..3967560210
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+///
+/// Color converter for ICC profiles
+///
+internal abstract partial class IccConverterBase
+{
+ ///
+ /// Conversion methods with ICC profiles
+ ///
+ private enum ConversionMethod
+ {
+ ///
+ /// Conversion using anything but Multi Process Elements with perceptual rendering intent
+ ///
+ A0,
+
+ ///
+ /// Conversion using anything but Multi Process Elements with relative colorimetric rendering intent
+ ///
+ A1,
+
+ ///
+ /// Conversion using anything but Multi Process Elements with saturation rendering intent
+ ///
+ A2,
+
+ ///
+ /// Conversion using Multi Process Elements with perceptual rendering intent
+ ///
+ D0,
+
+ ///
+ /// Conversion using Multi Process Elements with relative colorimetric rendering intent
+ ///
+ D1,
+
+ ///
+ /// Conversion using Multi Process Elements with saturation rendering intent
+ ///
+ D2,
+
+ ///
+ /// Conversion using Multi Process Elements with absolute colorimetric rendering intent
+ ///
+ D3,
+
+ ///
+ /// Conversion of more than one channel using tone reproduction curves
+ ///
+ ColorTrc,
+
+ ///
+ /// Conversion of exactly one channel using a tone reproduction curve
+ ///
+ GrayTrc,
+
+ ///
+ /// No valid conversion method available or found
+ ///
+ Invalid,
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs
new file mode 100644
index 0000000000..530459f93c
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+///
+/// Color converter for ICC profiles
+///
+internal abstract partial class IccConverterBase
+{
+ private IVector4Calculator calculator;
+
+ public bool Is16BitLutEntry => this.calculator is LutEntryCalculator { Is16Bit: true };
+
+ ///
+ /// Checks the profile for available conversion methods and gathers all the information's necessary for it.
+ ///
+ /// The profile to use for the conversion.
+ /// True if the conversion is to the Profile Connection Space.
+ /// The wanted rendering intent. Can be ignored if not available.
+ /// Invalid conversion method.
+ protected void Init(IccProfile profile, bool toPcs, IccRenderingIntent renderingIntent)
+ => this.calculator = GetConversionMethod(profile, renderingIntent) switch
+ {
+ ConversionMethod.D0 => toPcs ?
+ InitD(profile, IccProfileTag.DToB0) :
+ InitD(profile, IccProfileTag.BToD0),
+ ConversionMethod.D1 => toPcs ?
+ InitD(profile, IccProfileTag.DToB1) :
+ InitD(profile, IccProfileTag.BToD1),
+ ConversionMethod.D2 => toPcs ?
+ InitD(profile, IccProfileTag.DToB2) :
+ InitD(profile, IccProfileTag.BToD2),
+ ConversionMethod.D3 => toPcs ?
+ InitD(profile, IccProfileTag.DToB3) :
+ InitD(profile, IccProfileTag.BToD3),
+ ConversionMethod.A0 => toPcs ?
+ InitA(profile, IccProfileTag.AToB0) :
+ InitA(profile, IccProfileTag.BToA0),
+ ConversionMethod.A1 => toPcs ?
+ InitA(profile, IccProfileTag.AToB1) :
+ InitA(profile, IccProfileTag.BToA1),
+ ConversionMethod.A2 => toPcs ?
+ InitA(profile, IccProfileTag.AToB2) :
+ InitA(profile, IccProfileTag.BToA2),
+ ConversionMethod.ColorTrc => InitColorTrc(profile, toPcs),
+ ConversionMethod.GrayTrc => InitGrayTrc(profile, toPcs),
+ _ => throw new InvalidIccProfileException("Invalid conversion method."),
+ };
+
+ private static IVector4Calculator InitA(IccProfile profile, IccProfileTag tag)
+ => GetTag(profile, tag) switch
+ {
+ IccLut8TagDataEntry lut8 => new LutEntryCalculator(lut8),
+ IccLut16TagDataEntry lut16 => new LutEntryCalculator(lut16),
+ IccLutAToBTagDataEntry lutAtoB => new LutABCalculator(lutAtoB),
+ IccLutBToATagDataEntry lutBtoA => new LutABCalculator(lutBtoA),
+ _ => throw new InvalidIccProfileException("Invalid entry."),
+ };
+
+ private static IVector4Calculator InitD(IccProfile profile, IccProfileTag tag)
+ {
+ IccMultiProcessElementsTagDataEntry entry = GetTag(profile, tag)
+ ?? throw new InvalidIccProfileException("Entry is null.");
+
+ throw new NotImplementedException("Multi process elements are not supported");
+ }
+
+ private static ColorTrcCalculator InitColorTrc(IccProfile profile, bool toPcs)
+ {
+ IccXyzTagDataEntry redMatrixColumn = GetTag(profile, IccProfileTag.RedMatrixColumn);
+ IccXyzTagDataEntry greenMatrixColumn = GetTag(profile, IccProfileTag.GreenMatrixColumn);
+ IccXyzTagDataEntry blueMatrixColumn = GetTag(profile, IccProfileTag.BlueMatrixColumn);
+
+ IccTagDataEntry redTrc = GetTag(profile, IccProfileTag.RedTrc);
+ IccTagDataEntry greenTrc = GetTag(profile, IccProfileTag.GreenTrc);
+ IccTagDataEntry blueTrc = GetTag(profile, IccProfileTag.BlueTrc);
+
+ if (redMatrixColumn == null ||
+ greenMatrixColumn == null ||
+ blueMatrixColumn == null ||
+ redTrc == null ||
+ greenTrc == null ||
+ blueTrc == null)
+ {
+ throw new InvalidIccProfileException("Missing matrix column or channel.");
+ }
+
+ return new ColorTrcCalculator(
+ redMatrixColumn,
+ greenMatrixColumn,
+ blueMatrixColumn,
+ redTrc,
+ greenTrc,
+ blueTrc,
+ toPcs);
+ }
+
+ private static GrayTrcCalculator InitGrayTrc(IccProfile profile, bool toPcs)
+ {
+ IccTagDataEntry entry = GetTag(profile, IccProfileTag.GrayTrc);
+ return new GrayTrcCalculator(entry, toPcs);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs
new file mode 100644
index 0000000000..eb096534e1
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+#nullable disable
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+///
+/// Color converter for ICC profiles
+///
+internal abstract partial class IccConverterBase
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ICC profile to use for the conversions
+ /// True if the conversion is to the profile connection space (PCS); False if the conversion is to the data space
+ protected IccConverterBase(IccProfile profile, bool toPcs)
+ {
+ Guard.NotNull(profile, nameof(profile));
+ this.Init(profile, toPcs, profile.Header.RenderingIntent);
+ }
+
+ ///
+ /// Converts colors with the initially provided ICC profile
+ ///
+ /// The value to convert
+ /// The converted value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 Calculate(Vector4 value) => this.calculator.Calculate(value);
+
+ ///
+ /// Converts colors with the initially provided ICC profile
+ ///
+ /// The source colors
+ /// The destination colors
+ public void Calculate(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = this.Calculate(source[i]);
+ }
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs
new file mode 100644
index 0000000000..173948a6eb
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc;
+
+///
+/// Color converter for ICC profiles
+///
+internal class IccDataToDataConverter : IccConverterBase
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ICC profile to use for the conversions
+ public IccDataToDataConverter(IccProfile profile)
+ : base(profile, true) // toPCS is true because in this case the PCS space is also a data space
+ {
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs
new file mode 100644
index 0000000000..d9e42a8d97
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc;
+
+///
+/// Color converter for ICC profiles
+///
+internal class IccDataToPcsConverter : IccConverterBase
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ICC profile to use for the conversions
+ public IccDataToPcsConverter(IccProfile profile)
+ : base(profile, true)
+ {
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs
new file mode 100644
index 0000000000..d174529b65
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc;
+
+///
+/// Color converter for ICC profiles
+///
+internal class IccPcsToDataConverter : IccConverterBase
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ICC profile to use for the conversions
+ public IccPcsToDataConverter(IccProfile profile)
+ : base(profile, false)
+ {
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs
new file mode 100644
index 0000000000..98e069e401
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc;
+
+///
+/// Color converter for ICC profiles
+///
+internal class IccPcsToPcsConverter : IccConverterBase
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ICC profile to use for the conversions
+ public IccPcsToPcsConverter(IccProfile profile)
+ : base(profile, true)
+ {
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/IccProfileConverter.cs b/src/ImageSharp/ColorProfiles/Icc/IccProfileConverter.cs
new file mode 100644
index 0000000000..c8c4de4a60
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/IccProfileConverter.cs
@@ -0,0 +1,144 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.Numerics;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.ColorProfiles.Icc;
+
+///
+/// Allows the conversion between ICC profiles.
+///
+internal static class IccProfileConverter
+{
+ ///
+ /// Performs a conversion of the image pixels based on the input and output ICC profiles.
+ ///
+ /// The image to convert.
+ /// The input ICC profile.
+ /// The output ICC profile.
+ public static void Convert(Image image, IccProfile inputIccProfile, IccProfile outputIccProfile)
+ => image.AcceptVisitor(new IccProfileConverterVisitor(inputIccProfile, outputIccProfile));
+
+ ///
+ /// Performs a conversion of the image pixels based on the input and output ICC profiles.
+ ///
+ /// The type of pixel.
+ /// The image to convert.
+ /// The input ICC profile.
+ /// The output ICC profile.
+ public static void Convert(Image image, IccProfile inputIccProfile, IccProfile outputIccProfile)
+ where TPixel : unmanaged, IPixel
+ {
+ IccDataToPcsConverter converterDataToPcs = new(inputIccProfile);
+ IccPcsToDataConverter converterPcsToData = new(outputIccProfile);
+ Configuration configuration = image.Configuration;
+
+ image.ProcessPixelRows(accessor =>
+ {
+ ColorProfileConverter converter = new(new ColorConversionOptions()
+ {
+ SourceWhitePoint = new CieXyz(inputIccProfile.Header.PcsIlluminant),
+ TargetWhitePoint = new CieXyz(outputIccProfile.Header.PcsIlluminant),
+ });
+
+ // TODO: Our Xxy/Lab conversion are dependent on the version number. We are applying the conversion using V4
+ // but we should use the correct algorithm per version. This includes Lab/Lab Xyz/Xyz.
+ using IMemoryOwner vectors = configuration.MemoryAllocator.Allocate(accessor.Width);
+ Span vectorsSpan = vectors.GetSpan();
+
+ // TODO: For debugging - remove.
+ // It appears we have a scaling problem. The pcs values differ by on average 0.000001.
+ Span temp = new Vector4[vectorsSpan.Length];
+
+ for (int y = 0; y < accessor.Height; y++)
+ {
+ Span row = accessor.GetRowSpan(y);
+ PixelOperations.Instance.ToVector4(configuration, row, vectorsSpan, PixelConversionModifiers.Scale);
+
+ if (inputIccProfile.Header.ProfileConnectionSpace == IccColorSpaceType.CieLab &&
+ outputIccProfile.Header.ProfileConnectionSpace == IccColorSpaceType.CieXyz)
+ {
+ for (int x = 0; x < vectorsSpan.Length; x++)
+ {
+ Vector4 pcs = converterDataToPcs.Calculate(vectorsSpan[x]);
+ temp[x] = pcs;
+ pcs = PcsToLab(pcs);
+ CieLab lab = new(pcs.X, pcs.Y, pcs.Z);
+ CieXyz xyz = converter.Convert(in lab);
+ pcs = XyzToPcs(pcs, xyz);
+
+ vectorsSpan[x] = converterPcsToData.Calculate(pcs);
+ }
+ }
+ else if (inputIccProfile.Header.ProfileConnectionSpace == IccColorSpaceType.CieXyz &&
+ outputIccProfile.Header.ProfileConnectionSpace == IccColorSpaceType.CieLab)
+ {
+ for (int x = 0; x < vectorsSpan.Length; x++)
+ {
+ Vector4 pcs = converterDataToPcs.Calculate(vectorsSpan[x]);
+ CieXyz xyz = new(pcs.X, pcs.Y, pcs.Z);
+ CieLab lab = converter.Convert(in xyz);
+ pcs = LabToPcs(pcs, lab);
+ vectorsSpan[x] = converterPcsToData.Calculate(pcs);
+ }
+ }
+ else
+ {
+ for (int x = 0; x < vectorsSpan.Length; x++)
+ {
+ Vector4 pcs = converterDataToPcs.Calculate(vectorsSpan[x]);
+ vectorsSpan[x] = converterPcsToData.Calculate(pcs);
+ }
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(configuration, vectorsSpan, row, PixelConversionModifiers.Scale);
+ }
+ });
+
+ image.Metadata.IccProfile = outputIccProfile;
+ }
+
+ private static unsafe Vector4 PcsToLab(Vector4 input)
+ {
+ Vector3* v = (Vector3*)&input;
+ v[0] *= new Vector3(100f, 255, 255);
+ v[0] -= new Vector3(0, 128F, 128F);
+ return input;
+ }
+
+ private static unsafe Vector4 LabToPcs(Vector4 input, CieLab lab)
+ {
+ Vector3* v = (Vector3*)&input;
+ v[0] = new Vector3(lab.L, lab.A + 128F, lab.B + 128F);
+ v[0] /= 100F;
+ return input;
+ }
+
+ private static unsafe Vector4 XyzToPcs(Vector4 input, CieXyz xyz)
+ {
+ Vector3* v = (Vector3*)&input;
+ v[0] = xyz.ToVector3();
+ v[0] *= 32768 / 65535f;
+ return input;
+ }
+
+ private readonly struct IccProfileConverterVisitor : IImageVisitor
+ {
+ private readonly IccProfile inputIccProfile;
+ private readonly IccProfile outputIccProfile;
+
+ public IccProfileConverterVisitor(IccProfile inputIccProfile, IccProfile outputIccProfile)
+ {
+ this.inputIccProfile = inputIccProfile;
+ this.outputIccProfile = outputIccProfile;
+ }
+
+ public void Visit(Image image)
+ where TPixel : unmanaged, IPixel => Convert(image, this.inputIccProfile, this.outputIccProfile);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs b/src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs
new file mode 100644
index 0000000000..45c231aa67
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+//
+
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
+
+internal static class SrgbV4Profile
+{
+ // Generated using the sRGB-v4.icc profile found at https://github.com/saucecontrol/Compact-ICC-Profiles
+ private static ReadOnlySpan Data => new byte[]
+ {
+ 0, 0, 1, 224, 108, 99, 109, 115, 4, 32, 0, 0, 109, 110, 116, 114, 82, 71, 66, 32, 88, 89, 90, 32, 7, 226, 0, 3, 0,
+ 20, 0, 9, 0, 14, 0, 29, 97, 99, 115, 112, 77, 83, 70, 84, 0, 0, 0, 0, 115, 97, 119, 115, 99, 116, 114, 108, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45, 104, 97, 110, 100, 163, 178, 171,
+ 223, 92, 167, 3, 18, 168, 85, 164, 236, 53, 122, 209, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 100, 101, 115, 99, 0, 0, 0, 252, 0, 0, 0, 36, 99,
+ 112, 114, 116, 0, 0, 1, 32, 0, 0, 0, 34, 119, 116, 112, 116, 0, 0, 1, 68, 0, 0, 0, 20, 99, 104, 97, 100, 0, 0,
+ 1, 88, 0, 0, 0, 44, 114, 88, 89, 90, 0, 0, 1, 132, 0, 0, 0, 20, 103, 88, 89, 90, 0, 0, 1, 152, 0, 0, 0,
+ 20, 98, 88, 89, 90, 0, 0, 1, 172, 0, 0, 0, 20, 114, 84, 82, 67, 0, 0, 1, 192, 0, 0, 0, 32, 103, 84, 82, 67,
+ 0, 0, 1, 192, 0, 0, 0, 32, 98, 84, 82, 67, 0, 0, 1, 192, 0, 0, 0, 32, 109, 108, 117, 99, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 12, 101, 110, 85, 83, 0, 0, 0, 8, 0, 0, 0, 28, 0, 115, 0, 82, 0, 71, 0, 66, 109, 108,
+ 117, 99, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 12, 101, 110, 85, 83, 0, 0, 0, 6, 0, 0, 0, 28, 0, 67, 0,
+ 67, 0, 48, 0, 33, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45, 115, 102, 51, 50,
+ 0, 0, 0, 0, 0, 1, 12, 63, 0, 0, 5, 221, 255, 255, 243, 38, 0, 0, 7, 144, 0, 0, 253, 146, 255, 255, 251, 161, 255,
+ 255, 253, 162, 0, 0, 3, 220, 0, 0, 192, 113, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 111, 160, 0, 0, 56, 242, 0, 0,
+ 3, 143, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 98, 150, 0, 0, 183, 137, 0, 0, 24, 218, 88, 89, 90, 32, 0, 0, 0,
+ 0, 0, 0, 36, 160, 0, 0, 15, 133, 0, 0, 182, 196, 112, 97, 114, 97, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 102, 105,
+ 0, 0, 242, 167, 0, 0, 13, 89, 0, 0, 19, 208, 0, 0, 10, 91,
+ };
+
+ private static readonly Lazy LazyIccProfile = new(() => GetIccProfile());
+
+ public static IccProfile GetProfile() => LazyIccProfile.Value;
+
+ private static IccProfile GetIccProfile()
+ {
+ byte[] buffer = new byte[Data.Length];
+ Data.CopyTo(buffer);
+ return new IccProfile(buffer);
+ }
+}
+
diff --git a/src/ImageSharp/ColorProfiles/Lms.cs b/src/ImageSharp/ColorProfiles/Lms.cs
index 5a6791b2d7..b3d29c9c9b 100644
--- a/src/ImageSharp/ColorProfiles/Lms.cs
+++ b/src/ImageSharp/ColorProfiles/Lms.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -89,6 +90,49 @@ public Lms(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToVector3() => new(this.L, this.M, this.S);
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 += new Vector3(1F);
+ v3 /= 2F;
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static Lms FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= 2F;
+ v3 -= new Vector3(1F);
+ return new Lms(v3);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static Lms FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
{
diff --git a/src/ImageSharp/ColorProfiles/Rgb.cs b/src/ImageSharp/ColorProfiles/Rgb.cs
index 6698e12cb8..eb98318b55 100644
--- a/src/ImageSharp/ColorProfiles/Rgb.cs
+++ b/src/ImageSharp/ColorProfiles/Rgb.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -81,6 +82,49 @@ public Rgb(Vector3 source)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rgb left, Rgb right) => !left.Equals(right);
+ ///
+ /// Initializes the color instance from a generic scaled .
+ ///
+ /// The vector to load the color from.
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Rgb FromScaledVector4(Vector4 source)
+ => new(source.AsVector3());
+
+ ///
+ /// Expands the color into a generic ("scaled") representation
+ /// with values scaled and usually clamped between 0 and 1.
+ /// The vector components are typically expanded in least to greatest significance order.
+ ///
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 ToScaledVector4()
+ => new(this.ToScaledVector3(), 1F);
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static Rgb FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
{
@@ -108,10 +152,10 @@ public static void FromProfileConnectionSpace(ColorConversionOptions options, Re
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options)
{
// First expand to linear rgb
- Rgb linear = FromScaledVector4(options.RgbWorkingSpace.Expand(this.ToScaledVector4()));
+ Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(this.ToScaledVector4()));
// Then convert to xyz
- return new CieXyz(Vector3.Transform(linear.ToScaledVector3(), GetRgbToCieXyzMatrix(options.RgbWorkingSpace)));
+ return new CieXyz(Vector3.Transform(linear.ToScaledVector3(), GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace)));
}
///
@@ -119,13 +163,13 @@ public static void ToProfileConnectionSpace(ColorConversionOptions options, Read
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
- Matrix4x4 matrix = GetRgbToCieXyzMatrix(options.RgbWorkingSpace);
+ Matrix4x4 matrix = GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace);
for (int i = 0; i < source.Length; i++)
{
Rgb rgb = source[i];
// First expand to linear rgb
- Rgb linear = FromScaledVector4(options.RgbWorkingSpace.Expand(rgb.ToScaledVector4()));
+ Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(rgb.ToScaledVector4()));
// Then convert to xyz
destination[i] = new CieXyz(Vector3.Transform(linear.ToScaledVector3(), matrix));
@@ -133,7 +177,8 @@ public static void ToProfileConnectionSpace(ColorConversionOptions options, Read
}
///
- public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
+ public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
+ => ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
///
/// Initializes the color instance from a generic scaled .
@@ -141,19 +186,8 @@ public static void ToProfileConnectionSpace(ColorConversionOptions options, Read
/// The vector to load the color from.
/// The .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Rgb FromScaledVector3(Vector3 source) => new(Vector3.Clamp(source, Vector3.Zero, Vector3.One));
-
- ///
- /// Initializes the color instance from a generic scaled .
- ///
- /// The vector to load the color from.
- /// The .
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Rgb FromScaledVector4(Vector4 source)
- {
- source = Vector4.Clamp(source, Vector4.Zero, Vector4.One);
- return new(source.X, source.Y, source.Z);
- }
+ public static Rgb FromScaledVector3(Vector3 source)
+ => new(source);
///
/// Initializes the color instance for a source clamped between 0 and 1
@@ -161,7 +195,8 @@ public static Rgb FromScaledVector4(Vector4 source)
/// The source to load the color from.
/// The .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Rgb Clamp(Rgb source) => new(Vector3.Clamp(new(source.R, source.G, source.B), Vector3.Zero, Vector3.One));
+ public static Rgb Clamp(Rgb source)
+ => new(Vector3.Clamp(source.AsVector3Unsafe(), Vector3.Zero, Vector3.One));
///
/// Expands the color into a generic ("scaled") representation
@@ -170,24 +205,12 @@ public static Rgb FromScaledVector4(Vector4 source)
///
/// The .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector3 ToScaledVector3() => Clamp(this).ToVector3();
-
- ///
- /// Expands the color into a generic representation.
- /// The vector components are typically expanded in least to greatest significance order.
- ///
- /// The .
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector3 ToVector3() => new(this.R, this.G, this.B);
-
- ///
- /// Expands the color into a generic ("scaled") representation
- /// with values scaled and usually clamped between 0 and 1.
- /// The vector components are typically expanded in least to greatest significance order.
- ///
- /// The .
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ToScaledVector4() => new(this.ToScaledVector3(), 1f);
+ public Vector3 ToScaledVector3()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ return v3;
+ }
///
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B);
diff --git a/src/ImageSharp/ColorProfiles/YCbCr.cs b/src/ImageSharp/ColorProfiles/YCbCr.cs
index 03bd1d3120..dfd9351dcc 100644
--- a/src/ImageSharp/ColorProfiles/YCbCr.cs
+++ b/src/ImageSharp/ColorProfiles/YCbCr.cs
@@ -4,6 +4,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@@ -82,6 +83,47 @@ public YCbCr(Vector3 vector)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(YCbCr left, YCbCr right) => !left.Equals(right);
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ 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);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
///
public static YCbCr FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
new file mode 100644
index 0000000000..4e8ea61742
--- /dev/null
+++ b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+#if !NET9_0_OR_GREATER
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp;
+
+internal static class Vector4Extensions
+{
+ ///
+ /// Reinterprets a as a new .
+ ///
+ /// The vector to reinterpret.
+ /// reinterpreted as a new .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector3 AsVector3(this Vector4 value) => value.AsVector128().AsVector3();
+}
+#endif
diff --git a/src/ImageSharp/Formats/ColorProfileHandling.cs b/src/ImageSharp/Formats/ColorProfileHandling.cs
new file mode 100644
index 0000000000..e6f4b0a6a0
--- /dev/null
+++ b/src/ImageSharp/Formats/ColorProfileHandling.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats;
+
+///
+/// Provides enumeration of methods that control how ICC profiles are handled during decode.
+///
+public enum ColorProfileHandling
+{
+ ///
+ /// Leaves any embedded ICC color profiles intact.
+ ///
+ Preserve,
+
+ ///
+ /// Transforms the pixels of the image based on the conversion of any embedded ICC color profiles to sRGB V4 profile.
+ /// The original profile is then replaced.
+ ///
+ Convert
+}
diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs
index 3b16159b7e..f606d2fff2 100644
--- a/src/ImageSharp/Formats/DecoderOptions.cs
+++ b/src/ImageSharp/Formats/DecoderOptions.cs
@@ -60,5 +60,10 @@ public sealed class DecoderOptions
///
public SegmentIntegrityHandling SegmentIntegrityHandling { get; init; } = SegmentIntegrityHandling.IgnoreNonCritical;
+ ///
+ /// Gets a value that controls how ICC profiles are handled during decode.
+ ///
+ public ColorProfileHandling ColorProfileHandling { get; init; }
+
internal void SetConfiguration(Configuration configuration) => this.configuration = configuration;
}
diff --git a/src/ImageSharp/Formats/ImageDecoder.cs b/src/ImageSharp/Formats/ImageDecoder.cs
index e58cd6a6de..0a8144712a 100644
--- a/src/ImageSharp/Formats/ImageDecoder.cs
+++ b/src/ImageSharp/Formats/ImageDecoder.cs
@@ -1,8 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.ColorProfiles.Icc;
+using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@@ -23,6 +26,7 @@ public Image Decode(DecoderOptions options, Stream stream)
stream,
s => this.Decode(options, s, default));
+ TransformColorProfile(options, image);
this.SetDecoderFormat(options.Configuration, image);
return image;
@@ -36,6 +40,7 @@ public Image Decode(DecoderOptions options, Stream stream)
stream,
s => this.Decode(options, s, default));
+ TransformColorProfile(options, image);
this.SetDecoderFormat(options.Configuration, image);
return image;
@@ -46,11 +51,12 @@ public async Task> DecodeAsync(DecoderOptions options, Str
where TPixel : unmanaged, IPixel
{
Image image = await WithSeekableMemoryStreamAsync(
- options,
- stream,
- (s, ct) => this.Decode(options, s, ct),
- cancellationToken).ConfigureAwait(false);
+ options,
+ stream,
+ (s, ct) => this.Decode(options, s, ct),
+ cancellationToken).ConfigureAwait(false);
+ TransformColorProfile(options, image);
this.SetDecoderFormat(options.Configuration, image);
return image;
@@ -65,6 +71,7 @@ public async Task DecodeAsync(DecoderOptions options, Stream stream, Canc
(s, ct) => this.Decode(options, s, ct),
cancellationToken).ConfigureAwait(false);
+ TransformColorProfile(options, image);
this.SetDecoderFormat(options.Configuration, image);
return image;
@@ -159,6 +166,25 @@ protected static void ScaleToTargetSize(DecoderOptions options, Image image)
}
}
+ ///
+ /// Converts the decoded image color profile if present to a V4 sRGB profile.
+ ///
+ /// The decoder options.
+ /// The image.
+ protected static void TransformColorProfile(DecoderOptions options, Image image)
+ {
+ if (options.ColorProfileHandling == ColorProfileHandling.Preserve)
+ {
+ return;
+ }
+
+ IccProfile? profile = image.Metadata?.IccProfile;
+ if (profile is not null)
+ {
+ IccProfileConverter.Convert(image, profile, SrgbV4Profile.GetProfile());
+ }
+ }
+
///
/// Determines whether the decoded image should be resized.
///
diff --git a/src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs b/src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs
index 59fa59bb97..2fb15aed29 100644
--- a/src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs
+++ b/src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs
@@ -23,7 +23,9 @@ public Image Decode(T options, Stream stream)
stream,
s => this.Decode(options, s, default));
+ TransformColorProfile(options.GeneralOptions, image);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
+
return image;
}
@@ -35,7 +37,9 @@ public Image Decode(T options, Stream stream)
stream,
s => this.Decode(options, s, default));
+ TransformColorProfile(options.GeneralOptions, image);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
+
return image;
}
@@ -49,7 +53,9 @@ public async Task> DecodeAsync(T options, Stream stream, C
(s, ct) => this.Decode(options, s, ct),
cancellationToken).ConfigureAwait(false);
+ TransformColorProfile(options.GeneralOptions, image);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
+
return image;
}
@@ -62,7 +68,9 @@ public async Task DecodeAsync(T options, Stream stream, CancellationToken
(s, ct) => this.Decode(options, s, ct),
cancellationToken).ConfigureAwait(false);
+ TransformColorProfile(options.GeneralOptions, image);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
+
return image;
}
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs
index e88dd8d9e1..f3ce6cd79c 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs
@@ -4,24 +4,21 @@
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
///
-/// Provides methods to read ICC data types
+/// Provides methods to read ICC data types.
///
internal sealed partial class IccDataReader
{
///
- /// Reads an 8bit lookup table
+ /// Reads an 8bit lookup table.
///
- /// The read LUT
- public IccLut ReadLut8()
- {
- return new IccLut(this.ReadBytes(256));
- }
+ /// The read LUT.
+ public IccLut ReadLut8() => new(this.ReadBytes(256));
///
- /// Reads a 16bit lookup table
+ /// Reads a 16bit lookup table.
///
- /// The number of entries
- /// The read LUT
+ /// The number of entries.
+ /// The read LUT.
public IccLut ReadLut16(int count)
{
var values = new ushort[count];
@@ -34,16 +31,16 @@ public IccLut ReadLut16(int count)
}
///
- /// Reads a CLUT depending on type
+ /// Reads a CLUT depending on type.
///
- /// Input channel count
- /// Output channel count
+ /// Input channel count.
+ /// Output channel count.
/// If true, it's read as CLUTf32,
- /// else read as either CLUT8 or CLUT16 depending on embedded information
- /// The read CLUT
+ /// else read as either CLUT8 or CLUT16 depending on embedded information.
+ /// The read CLUT.
public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat)
{
- // Grid-points are always 16 bytes long but only 0-inChCount are used
+ // Grid-points are always 16 bytes long but only 0-inChCount are used.
var gridPointCount = new byte[inChannelCount];
Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount);
@@ -67,15 +64,14 @@ public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat)
}
///
- /// Reads an 8 bit CLUT
+ /// Reads an 8 bit CLUT.
///
- /// Input channel count
- /// Output channel count
- /// Grid point count for each CLUT channel
- /// The read CLUT8
+ /// Input channel count.
+ /// Output channel count.
+ /// Grid point count for each CLUT channel.
+ /// The read CLUT8.
public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPointCount)
{
- int start = this.currentIndex;
int length = 0;
for (int i = 0; i < inChannelCount; i++)
{
@@ -86,27 +82,26 @@ public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPoi
const float Max = byte.MaxValue;
- var values = new float[length][];
+ float[] values = new float[length * outChannelCount];
+ int offset = 0;
for (int i = 0; i < length; i++)
{
- values[i] = new float[outChannelCount];
for (int j = 0; j < outChannelCount; j++)
{
- values[i][j] = this.data[this.currentIndex++] / Max;
+ values[offset++] = this.data[this.currentIndex++] / Max;
}
}
- this.currentIndex = start + (length * outChannelCount);
- return new IccClut(values, gridPointCount, IccClutDataType.UInt8);
+ return new IccClut(values, gridPointCount, IccClutDataType.UInt8, outChannelCount);
}
///
- /// Reads a 16 bit CLUT
+ /// Reads a 16 bit CLUT.
///
- /// Input channel count
- /// Output channel count
- /// Grid point count for each CLUT channel
- /// The read CLUT16
+ /// Input channel count.
+ /// Output channel count.
+ /// Grid point count for each CLUT channel.
+ /// The read CLUT16.
public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPointCount)
{
int start = this.currentIndex;
@@ -120,27 +115,27 @@ public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPo
const float Max = ushort.MaxValue;
- var values = new float[length][];
+ float[] values = new float[length * outChannelCount];
+ int offset = 0;
for (int i = 0; i < length; i++)
{
- values[i] = new float[outChannelCount];
for (int j = 0; j < outChannelCount; j++)
{
- values[i][j] = this.ReadUInt16() / Max;
+ values[offset++] = this.ReadUInt16() / Max;
}
}
this.currentIndex = start + (length * outChannelCount * 2);
- return new IccClut(values, gridPointCount, IccClutDataType.UInt16);
+ return new IccClut(values, gridPointCount, IccClutDataType.UInt16, outChannelCount);
}
///
- /// Reads a 32bit floating point CLUT
+ /// Reads a 32bit floating point CLUT.
///
- /// Input channel count
- /// Output channel count
- /// Grid point count for each CLUT channel
- /// The read CLUTf32
+ /// Input channel count.
+ /// Output channel count.
+ /// Grid point count for each CLUT channel.
+ /// The read CLUTf32.
public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount)
{
int start = this.currentIndex;
@@ -152,17 +147,17 @@ public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount)
length /= inChCount;
- var values = new float[length][];
+ float[] values = new float[length * outChCount];
+ int offset = 0;
for (int i = 0; i < length; i++)
{
- values[i] = new float[outChCount];
for (int j = 0; j < outChCount; j++)
{
- values[i][j] = this.ReadSingle();
+ values[offset++] = this.ReadSingle();
}
}
this.currentIndex = start + (length * outChCount * 4);
- return new IccClut(values, gridPointCount, IccClutDataType.Float);
+ return new IccClut(values, gridPointCount, IccClutDataType.Float, outChCount);
}
}
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
index 61ecda4aab..ecc9bfbffb 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
@@ -17,16 +17,23 @@ internal sealed partial class IccDataReader
/// The read matrix
public float[,] ReadMatrix(int xCount, int yCount, bool isSingle)
{
- var matrix = new float[xCount, yCount];
- for (int y = 0; y < yCount; y++)
+ float[,] matrix = new float[xCount, yCount];
+
+ if (isSingle)
{
- for (int x = 0; x < xCount; x++)
+ for (int y = 0; y < yCount; y++)
{
- if (isSingle)
+ for (int x = 0; x < xCount; x++)
{
matrix[x, y] = this.ReadSingle();
}
- else
+ }
+ }
+ else
+ {
+ for (int y = 0; y < yCount; y++)
+ {
+ for (int x = 0; x < xCount; x++)
{
matrix[x, y] = this.ReadFix16();
}
@@ -44,14 +51,17 @@ internal sealed partial class IccDataReader
/// The read matrix
public float[] ReadMatrix(int yCount, bool isSingle)
{
- var matrix = new float[yCount];
- for (int i = 0; i < yCount; i++)
+ float[] matrix = new float[yCount];
+ if (isSingle)
{
- if (isSingle)
+ for (int i = 0; i < yCount; i++)
{
matrix[i] = this.ReadSingle();
}
- else
+ }
+ else
+ {
+ for (int i = 0; i < yCount; i++)
{
matrix[i] = this.ReadFix16();
}
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
index 47d946d443..7a526ef1af 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
@@ -16,55 +16,37 @@ internal sealed partial class IccDataReader
/// Reads an ushort
///
/// the value
- public ushort ReadUInt16()
- {
- return BinaryPrimitives.ReadUInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2));
- }
+ public ushort ReadUInt16() => BinaryPrimitives.ReadUInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2));
///
/// Reads a short
///
/// the value
- public short ReadInt16()
- {
- return BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2));
- }
+ public short ReadInt16() => BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2));
///
/// Reads an uint
///
/// the value
- public uint ReadUInt32()
- {
- return BinaryPrimitives.ReadUInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4));
- }
+ public uint ReadUInt32() => BinaryPrimitives.ReadUInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4));
///
/// Reads an int
///
/// the value
- public int ReadInt32()
- {
- return BinaryPrimitives.ReadInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4));
- }
+ public int ReadInt32() => BinaryPrimitives.ReadInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4));
///
/// Reads an ulong
///
/// the value
- public ulong ReadUInt64()
- {
- return BinaryPrimitives.ReadUInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8));
- }
+ public ulong ReadUInt64() => BinaryPrimitives.ReadUInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8));
///
/// Reads a long
///
/// the value
- public long ReadInt64()
- {
- return BinaryPrimitives.ReadInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8));
- }
+ public long ReadInt64() => BinaryPrimitives.ReadInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8));
///
/// Reads a float.
@@ -152,10 +134,7 @@ public string ReadUnicodeString(int length)
/// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits.
///
/// The number as double
- public float ReadUFix8()
- {
- return this.ReadUInt16() / 256f;
- }
+ public float ReadUFix8() => this.ReadUInt16() / 256f;
///
/// Reads a number of bytes and advances the index.
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
index ddfc625152..c1b22e82bf 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
@@ -8,21 +8,19 @@
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
///
-/// Provides methods to read ICC data types
+/// Provides methods to read ICC data types.
///
internal sealed partial class IccDataReader
{
///
- /// Reads a tag data entry
+ /// Reads a tag data entry.
///
- /// The table entry with reading information
- /// the tag data entry
+ /// The table entry with reading information.
+ /// The tag data entry.
public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info)
{
this.currentIndex = (int)info.Offset;
- IccTypeSignature type = this.ReadTagDataEntryHeader();
-
- switch (type)
+ switch (this.ReadTagDataEntryHeader())
{
case IccTypeSignature.Chromaticity:
return this.ReadChromaticityTagDataEntry();
@@ -103,10 +101,10 @@ public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info)
///
/// Reads the header of a
///
- /// The read signature
+ /// The read signature.
public IccTypeSignature ReadTagDataEntryHeader()
{
- var type = (IccTypeSignature)this.ReadUInt32();
+ IccTypeSignature type = (IccTypeSignature)this.ReadUInt32();
this.AddIndex(4); // 4 bytes are not used
return type;
}
@@ -114,7 +112,7 @@ public IccTypeSignature ReadTagDataEntryHeader()
///
/// Reads the header of a and checks if it's the expected value
///
- /// expected value to check against
+ /// The expected value to check against.
public void ReadCheckTagDataEntryHeader(IccTypeSignature expected)
{
IccTypeSignature type = this.ReadTagDataEntryHeader();
@@ -127,8 +125,8 @@ public void ReadCheckTagDataEntryHeader(IccTypeSignature expected)
///
/// Reads a with an unknown
///
- /// The size of the entry in bytes
- /// The read entry
+ /// The size of the entry in bytes.
+ /// The read entry.
public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size)
{
int count = (int)size - 8; // 8 is the tag header size
@@ -138,7 +136,7 @@ public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size)
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry()
{
ushort channelCount = this.ReadUInt16();
@@ -152,7 +150,7 @@ public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry()
}
else
{
- // The type is not know, so the values need be read
+ // The type is not know, so the values need be read.
double[][] values = new double[channelCount][];
for (int i = 0; i < channelCount; i++)
{
@@ -166,7 +164,7 @@ public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry()
{
uint colorantCount = this.ReadUInt32();
@@ -177,7 +175,7 @@ public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry()
{
uint colorantCount = this.ReadUInt32();
@@ -193,7 +191,7 @@ public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccCurveTagDataEntry ReadCurveTagDataEntry()
{
uint pointCount = this.ReadUInt32();
@@ -222,7 +220,7 @@ public IccCurveTagDataEntry ReadCurveTagDataEntry()
///
/// Reads a
///
- /// The size of the entry in bytes
+ /// The size of the entry in bytes.
/// The read entry
public IccDataTagDataEntry ReadDataTagDataEntry(uint size)
{
@@ -240,16 +238,13 @@ public IccDataTagDataEntry ReadDataTagDataEntry(uint size)
///
/// Reads a
///
- /// The read entry
- public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry()
- {
- return new IccDateTimeTagDataEntry(this.ReadDateTime());
- }
+ /// The read entry.
+ public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() => new IccDateTimeTagDataEntry(this.ReadDateTime());
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccLut16TagDataEntry ReadLut16TagDataEntry()
{
byte inChCount = this.data[this.AddIndex(1)];
@@ -287,7 +282,7 @@ public IccLut16TagDataEntry ReadLut16TagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccLut8TagDataEntry ReadLut8TagDataEntry()
{
byte inChCount = this.data[this.AddIndex(1)];
@@ -322,7 +317,7 @@ public IccLut8TagDataEntry ReadLut8TagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@@ -381,7 +376,7 @@ public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccLutBToATagDataEntry ReadLutBtoATagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@@ -440,21 +435,18 @@ public IccLutBToATagDataEntry ReadLutBtoATagDataEntry()
///
/// Reads a
///
- /// The read entry
- public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry()
- {
- return new IccMeasurementTagDataEntry(
+ /// The read entry.
+ public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() => new(
observer: (IccStandardObserver)this.ReadUInt32(),
xyzBacking: this.ReadXyzNumber(),
geometry: (IccMeasurementGeometry)this.ReadUInt32(),
flare: this.ReadUFix16(),
illuminant: (IccStandardIlluminant)this.ReadUInt32());
- }
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@@ -519,7 +511,7 @@ CultureInfo ReadCulture(string language, string country)
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry()
{
int start = this.currentIndex - 8;
@@ -547,7 +539,7 @@ public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry(
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry()
{
int vendorFlag = this.ReadInt32();
@@ -569,15 +561,12 @@ public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry()
/// Reads a
///
/// The read entry
- public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry()
- {
- return new IccParametricCurveTagDataEntry(this.ReadParametricCurve());
- }
+ public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() => new(this.ReadParametricCurve());
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry()
{
uint count = this.ReadUInt32();
@@ -593,7 +582,7 @@ public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@@ -620,7 +609,7 @@ public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTag
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry()
{
int start = this.currentIndex - 8; // 8 is the tag header size
@@ -646,8 +635,8 @@ public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry()
///
/// Reads a
///
- /// The size of the entry in bytes
- /// The read entry
+ /// The size of the entry in bytes.
+ /// The read entry.
public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
@@ -663,27 +652,21 @@ public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size)
///
/// Reads a
///
- /// The read entry
- public IccSignatureTagDataEntry ReadSignatureTagDataEntry()
- {
- return new IccSignatureTagDataEntry(this.ReadAsciiString(4));
- }
+ /// The read entry.
+ public IccSignatureTagDataEntry ReadSignatureTagDataEntry() => new(this.ReadAsciiString(4));
///
/// Reads a
///
- /// The size of the entry in bytes
- /// The read entry
- public IccTextTagDataEntry ReadTextTagDataEntry(uint size)
- {
- return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size
- }
+ /// The size of the entry in bytes.
+ /// The read entry.
+ public IccTextTagDataEntry ReadTextTagDataEntry(uint size) => new(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size
///
/// Reads a
///
- /// The size of the entry in bytes
- /// The read entry
+ /// The size of the entry in bytes.
+ /// The read entry.
public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
@@ -699,8 +682,8 @@ public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size)
///
/// Reads a
///
- /// The size of the entry in bytes
- /// The read entry
+ /// The size of the entry in bytes.
+ /// The read entry.
public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 2;
@@ -716,8 +699,8 @@ public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size)
///
/// Reads a
///
- /// The size of the entry in bytes
- /// The read entry
+ /// The size of the entry in bytes.
+ /// The read entry.
public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 4;
@@ -733,8 +716,8 @@ public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size)
///
/// Reads a
///
- /// The size of the entry in bytes
- /// The read entry
+ /// The size of the entry in bytes.
+ /// The read entry.
public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size)
{
uint count = (size - 8) / 8;
@@ -750,8 +733,8 @@ public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size)
///
/// Reads a
///
- /// The size of the entry in bytes
- /// The read entry
+ /// The size of the entry in bytes.
+ /// The read entry.
public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size)
{
int count = (int)size - 8; // 8 is the tag header size
@@ -763,20 +746,17 @@ public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size)
///
/// Reads a
///
- /// The read entry
- public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry()
- {
- return new IccViewingConditionsTagDataEntry(
+ /// The read entry.
+ public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() => new(
illuminantXyz: this.ReadXyzNumber(),
surroundXyz: this.ReadXyzNumber(),
illuminant: (IccStandardIlluminant)this.ReadUInt32());
- }
///
/// Reads a
///
- /// The size of the entry in bytes
- /// The read entry
+ /// The size of the entry in bytes.
+ /// The read entry.
public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size)
{
uint count = (size - 8) / 12;
@@ -792,7 +772,7 @@ public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size)
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry()
{
string unicodeValue, scriptcodeValue;
@@ -832,7 +812,7 @@ public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry()
{
uint productNameCount = this.ReadUInt32();
@@ -856,7 +836,7 @@ public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry()
///
/// Reads a
///
- /// The read entry
+ /// The read entry.
public IccScreeningTagDataEntry ReadScreeningTagDataEntry()
{
var flags = (IccScreeningFlag)this.ReadInt32();
@@ -873,7 +853,7 @@ public IccScreeningTagDataEntry ReadScreeningTagDataEntry()
///
/// Reads a
///
- /// The size of the entry in bytes
+ /// The size of the entry in bytes.
/// The read entry
public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size)
{
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
index 703a3896bb..29394c0820 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
@@ -4,15 +4,15 @@
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
///
-/// Provides methods to write ICC data types
+/// Provides methods to write ICC data types.
///
internal sealed partial class IccDataWriter
{
///
- /// Writes an 8bit lookup table
+ /// Writes an 8bit lookup table.
///
- /// The LUT to write
- /// The number of bytes written
+ /// The LUT to write.
+ /// The number of bytes written.
public int WriteLut8(IccLut value)
{
foreach (float item in value.Values)
@@ -24,10 +24,10 @@ public int WriteLut8(IccLut value)
}
///
- /// Writes an 16bit lookup table
+ /// Writes an 16bit lookup table.
///
- /// The LUT to write
- /// The number of bytes written
+ /// The LUT to write.
+ /// The number of bytes written.
public int WriteLut16(IccLut value)
{
foreach (float item in value.Values)
@@ -39,10 +39,10 @@ public int WriteLut16(IccLut value)
}
///
- /// Writes an color lookup table
+ /// Writes an color lookup table.
///
- /// The CLUT to write
- /// The number of bytes written
+ /// The CLUT to write.
+ /// The number of bytes written.
public int WriteClut(IccClut value)
{
int count = this.WriteArray(value.GridPointCount);
@@ -67,57 +67,48 @@ public int WriteClut(IccClut value)
}
///
- /// Writes a 8bit color lookup table
+ /// Writes a 8bit color lookup table.
///
- /// The CLUT to write
- /// The number of bytes written
+ /// The CLUT to write.
+ /// The number of bytes written.
public int WriteClut8(IccClut value)
{
int count = 0;
- foreach (float[] inArray in value.Values)
+ foreach (float item in value.Values)
{
- foreach (float item in inArray)
- {
- count += this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue));
- }
+ count += this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue));
}
return count;
}
///
- /// Writes a 16bit color lookup table
+ /// Writes a 16bit color lookup table.
///
- /// The CLUT to write
- /// The number of bytes written
+ /// The CLUT to write.
+ /// The number of bytes written.
public int WriteClut16(IccClut value)
{
int count = 0;
- foreach (float[] inArray in value.Values)
+ foreach (float item in value.Values)
{
- foreach (float item in inArray)
- {
- count += this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue));
- }
+ count += this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue));
}
return count;
}
///
- /// Writes a 32bit float color lookup table
+ /// Writes a 32bit float color lookup table.
///
- /// The CLUT to write
- /// The number of bytes written
+ /// The CLUT to write.
+ /// The number of bytes written.
public int WriteClutF32(IccClut value)
{
int count = 0;
- foreach (float[] inArray in value.Values)
+ foreach (float item in value.Values)
{
- foreach (float item in inArray)
- {
- count += this.WriteSingle(item);
- }
+ count += this.WriteSingle(item);
}
return count;
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
index 1e5f359e09..636cc90a57 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
@@ -61,15 +61,21 @@ public int WriteMatrix(Matrix4x4 value, bool isSingle)
public int WriteMatrix(in DenseMatrix value, bool isSingle)
{
int count = 0;
- for (int y = 0; y < value.Rows; y++)
+ if (isSingle)
{
- for (int x = 0; x < value.Columns; x++)
+ for (int y = 0; y < value.Rows; y++)
{
- if (isSingle)
+ for (int x = 0; x < value.Columns; x++)
{
count += this.WriteSingle(value[x, y]);
}
- else
+ }
+ }
+ else
+ {
+ for (int y = 0; y < value.Rows; y++)
+ {
+ for (int x = 0; x < value.Columns; x++)
{
count += this.WriteFix16(value[x, y]);
}
@@ -88,15 +94,22 @@ public int WriteMatrix(in DenseMatrix value, bool isSingle)
public int WriteMatrix(float[,] value, bool isSingle)
{
int count = 0;
- for (int y = 0; y < value.GetLength(1); y++)
+
+ if (isSingle)
{
- for (int x = 0; x < value.GetLength(0); x++)
+ for (int y = 0; y < value.GetLength(1); y++)
{
- if (isSingle)
+ for (int x = 0; x < value.GetLength(0); x++)
{
count += this.WriteSingle(value[x, y]);
}
- else
+ }
+ }
+ else
+ {
+ for (int y = 0; y < value.GetLength(1); y++)
+ {
+ for (int x = 0; x < value.GetLength(0); x++)
{
count += this.WriteFix16(value[x, y]);
}
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
index 1f9c101fb5..6019a0bff7 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
@@ -231,7 +231,7 @@ public int WriteLut8TagDataEntry(IccLut8TagDataEntry value)
{
int count = this.WriteByte((byte)value.InputChannelCount);
count += this.WriteByte((byte)value.OutputChannelCount);
- count += this.WriteByte((byte)value.ClutValues.Values[0].Length);
+ count += this.WriteByte((byte)value.ClutValues.OutputChannelCount);
count += this.WriteEmpty(1);
count += this.WriteMatrix(value.Matrix, false);
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs
index e0c6f4c962..27af2a91f7 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs
@@ -14,7 +14,7 @@ internal enum IccFormulaCurveType : ushort
Type1 = 0,
///
- /// Type 1: Y = a * log10 (b * X^γ + c) + d
+ /// Type 2: Y = a * log10 (b * X^γ + c) + d
///
Type2 = 1,
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs
index c1cb3f10f0..c6b4b65773 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
index ac78318f2b..da015b2b07 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
@@ -190,7 +190,6 @@ private void InitializeHeader()
return;
}
- IccReader reader = new();
this.header = IccReader.ReadHeader(this.data);
}
@@ -207,7 +206,6 @@ private void InitializeEntries()
return;
}
- IccReader reader = new();
this.entries = IccReader.ReadTagData(this.data);
}
}
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs
index 45074c9a6e..074712d302 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs
@@ -83,28 +83,19 @@ private static IccTagDataEntry[] ReadTagData(IccDataReader reader)
{
IccTagTableEntry[] tagTable = ReadTagTable(reader);
List entries = new(tagTable.Length);
- Dictionary store = new();
foreach (IccTagTableEntry tag in tagTable)
{
IccTagDataEntry entry;
- if (store.TryGetValue(tag.Offset, out IccTagDataEntry? value))
+
+ try
{
- entry = value;
+ entry = reader.ReadTagDataEntry(tag);
}
- else
+ catch
{
- try
- {
- entry = reader.ReadTagDataEntry(tag);
- }
- catch
- {
- // Ignore tags that could not be read
- continue;
- }
-
- store.Add(tag.Offset, entry);
+ // Ignore tags that could not be read
+ continue;
}
entry.TagSignature = tag.Signature;
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs
index 12228f3f58..f7a99645bb 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
@@ -41,9 +41,7 @@ protected IccTagDataEntry(IccTypeSignature signature, IccProfileTag tagSignature
///
public override bool Equals(object? obj)
- {
- return obj is IccTagDataEntry entry && this.Equals(entry);
- }
+ => obj is IccTagDataEntry entry && this.Equals(entry);
///
public virtual bool Equals(IccTagDataEntry? other)
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs
index 26a882810e..bbec7ce43e 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs
@@ -1,20 +1,21 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
///
-/// Color Lookup Table
+/// Color Lookup Table.
///
internal sealed class IccClut : IEquatable
{
///
/// Initializes a new instance of the class.
///
- /// The CLUT values
- /// The gridpoint count
- /// The data type of this CLUT
- public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type)
+ /// The CLUT values.
+ /// The gridpoint count.
+ /// The data type of this CLUT.
+ /// The output channels count.
+ public IccClut(float[] values, byte[] gridPointCount, IccClutDataType type, int outputChannelCount)
{
Guard.NotNull(values, nameof(values));
Guard.NotNull(gridPointCount, nameof(gridPointCount));
@@ -22,91 +23,33 @@ public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type)
this.Values = values;
this.DataType = type;
this.InputChannelCount = gridPointCount.Length;
- this.OutputChannelCount = values[0].Length;
+ this.OutputChannelCount = outputChannelCount;
this.GridPointCount = gridPointCount;
this.CheckValues();
}
///
- /// Initializes a new instance of the class.
- ///
- /// The CLUT values
- /// The gridpoint count
- public IccClut(ushort[][] values, byte[] gridPointCount)
- {
- Guard.NotNull(values, nameof(values));
- Guard.NotNull(gridPointCount, nameof(gridPointCount));
-
- const float Max = ushort.MaxValue;
-
- this.Values = new float[values.Length][];
- for (int i = 0; i < values.Length; i++)
- {
- this.Values[i] = new float[values[i].Length];
- for (int j = 0; j < values[i].Length; j++)
- {
- this.Values[i][j] = values[i][j] / Max;
- }
- }
-
- this.DataType = IccClutDataType.UInt16;
- this.InputChannelCount = gridPointCount.Length;
- this.OutputChannelCount = values[0].Length;
- this.GridPointCount = gridPointCount;
- this.CheckValues();
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The CLUT values
- /// The gridpoint count
- public IccClut(byte[][] values, byte[] gridPointCount)
- {
- Guard.NotNull(values, nameof(values));
- Guard.NotNull(gridPointCount, nameof(gridPointCount));
-
- const float Max = byte.MaxValue;
-
- this.Values = new float[values.Length][];
- for (int i = 0; i < values.Length; i++)
- {
- this.Values[i] = new float[values[i].Length];
- for (int j = 0; j < values[i].Length; j++)
- {
- this.Values[i][j] = values[i][j] / Max;
- }
- }
-
- this.DataType = IccClutDataType.UInt8;
- this.InputChannelCount = gridPointCount.Length;
- this.OutputChannelCount = values[0].Length;
- this.GridPointCount = gridPointCount;
- this.CheckValues();
- }
-
- ///
- /// Gets the values that make up this table
+ /// Gets the values that make up this table.
///
- public float[][] Values { get; }
+ public float[] Values { get; }
///
- /// Gets the CLUT data type (important when writing a profile)
+ /// Gets the CLUT data type (important when writing a profile).
///
public IccClutDataType DataType { get; }
///
- /// Gets the number of input channels
+ /// Gets the number of input channels.
///
public int InputChannelCount { get; }
///
- /// Gets the number of output channels
+ /// Gets the number of output channels.
///
public int OutputChannelCount { get; }
///
- /// Gets the number of grid points per input channel
+ /// Gets the number of grid points per input channel.
///
public byte[] GridPointCount { get; }
@@ -134,15 +77,12 @@ public bool Equals(IccClut? other)
public override bool Equals(object? obj) => obj is IccClut other && this.Equals(other);
///
- public override int GetHashCode()
- {
- return HashCode.Combine(
+ public override int GetHashCode() => HashCode.Combine(
this.Values,
this.DataType,
this.InputChannelCount,
this.OutputChannelCount,
this.GridPointCount);
- }
private bool EqualsValuesArray(IccClut other)
{
@@ -153,7 +93,7 @@ private bool EqualsValuesArray(IccClut other)
for (int i = 0; i < this.Values.Length; i++)
{
- if (!this.Values[i].AsSpan().SequenceEqual(other.Values[i]))
+ if (!this.Values.SequenceEqual(other.Values))
{
return false;
}
@@ -167,17 +107,13 @@ private void CheckValues()
Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount));
Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount));
- bool isLengthDifferent = this.Values.Any(t => t.Length != this.OutputChannelCount);
- Guard.IsFalse(isLengthDifferent, nameof(this.Values), "The number of output values varies");
-
int length = 0;
for (int i = 0; i < this.InputChannelCount; i++)
{
length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount);
}
- length /= this.InputChannelCount;
-
- Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points");
+ // TODO: Disabled this check, not sure if this check is correct.
+ // Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points");
}
}
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs
index e7d7461d5d..a71cbfaf5a 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
@@ -49,9 +49,7 @@ public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize)
/// True if the parameter is equal to the parameter; otherwise, false.
///
public static bool operator ==(IccTagTableEntry left, IccTagTableEntry right)
- {
- return left.Equals(right);
- }
+ => left.Equals(right);
///
/// Compares two objects for equality.
@@ -62,9 +60,7 @@ public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize)
/// True if the parameter is not equal to the parameter; otherwise, false.
///
public static bool operator !=(IccTagTableEntry left, IccTagTableEntry right)
- {
- return !left.Equals(right);
- }
+ => !left.Equals(right);
///
public override bool Equals(object? obj) => obj is IccTagTableEntry other && this.Equals(other);
diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs
index adf386614d..528b3e76d4 100644
--- a/src/ImageSharp/PixelFormats/IPixel.cs
+++ b/src/ImageSharp/PixelFormats/IPixel.cs
@@ -23,7 +23,8 @@ public interface IPixel : IPixel, IEquatable
static abstract PixelOperations CreatePixelOperations();
///
- /// Initializes the pixel instance from a generic scaled .
+ /// Initializes the pixel instance from a generic a generic ("scaled") representation
+ /// with values scaled and clamped between 0 and 1
///
/// The vector to load the pixel from.
/// The .
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs
index 0aa7bad237..b03a54c585 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs
@@ -61,7 +61,8 @@ public Rgb24(byte r, byte g, byte b)
/// The instance of to convert.
/// An instance of .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static implicit operator Rgb24(Rgb color) => FromScaledVector4(new Vector4(color.ToVector3(), 1f));
+ public static implicit operator Rgb24(Rgb color)
+ => FromScaledVector4(new Vector4(color.ToScaledVector3(), 1F));
///
/// Compares two objects for equality.
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs
index 0491553430..507d6d70b6 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs
@@ -187,7 +187,7 @@ public uint PackedValue
/// The instance of to convert.
/// An instance of .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static implicit operator Rgba32(Rgb color) => FromScaledVector4(new Vector4(color.ToVector3(), 1F));
+ public static implicit operator Rgba32(Rgb color) => FromScaledVector4(new Vector4(color.ToScaledVector3(), 1F));
///
/// Compares two objects for equality.
diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs
index 6cd8df3fc7..b847e3ac54 100644
--- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs
+++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs
@@ -13,7 +13,7 @@ public class RgbWorkingSpaceAdapt
private static readonly RGBColor RGBColor = new(0.206162, 0.260277, 0.746717);
- private static readonly ColorProfileConverter ColorProfileConverter = new(new ColorConversionOptions { RgbWorkingSpace = KnownRgbWorkingSpaces.WideGamutRgb, TargetRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb });
+ private static readonly ColorProfileConverter ColorProfileConverter = new(new ColorConversionOptions { SourceRgbWorkingSpace = KnownRgbWorkingSpaces.WideGamutRgb, TargetRgbWorkingSpace = KnownRgbWorkingSpaces.SRgb });
private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.WideGamutRGB).ToRGB(RGBWorkingSpaces.sRGB).Build();
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchConversionTests.cs
index 9a894c7760..d6e3738952 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchConversionTests.cs
@@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
/// Test data generated using:
///
///
+[Trait("Color", "Conversion")]
public class CieLabAndCieLchConversionTests
{
private static readonly ApproximateColorProfileComparer Comparer = new(.0001f);
@@ -30,7 +31,7 @@ public void Convert_Lch_to_Lab(float l, float c, float h, float l2, float a, flo
// Arrange
CieLch input = new(l, c, h);
CieLab expected = new(l2, a, b);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D50 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D50 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLch[5];
@@ -65,7 +66,7 @@ public void Convert_Lab_to_Lch(float l, float a, float b, float l2, float c, flo
// Arrange
CieLab input = new(l, a, b);
CieLch expected = new(l2, c, h);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D50 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D50 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLab[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchuvConversionTests.cs
index 4b1b5e1a56..73fa7128fa 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchuvConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLchuvConversionTests.cs
@@ -24,7 +24,7 @@ public void Convert_Lchuv_To_Lab(float l, float c, float h, float l2, float a, f
// Arrange
CieLchuv input = new(l, c, h);
CieLab expected = new(l2, a, b);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLchuv[5];
@@ -53,7 +53,7 @@ public void Convert_Lab_To_Lchuv(float l, float a, float b, float l2, float c, f
// Arrange
CieLab input = new(l, a, b);
CieLchuv expected = new(l2, c, h);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLab[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLuvConversionTests.cs
index 44756c779a..0846bdda3f 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLuvConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLabAndCieLuvConversionTests.cs
@@ -24,7 +24,7 @@ public void Convert_CieLuv_To_CieLab(float l, float u, float v, float l2, float
// Arrange
CieLuv input = new(l, u, v);
CieLab expected = new(l2, a, b);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
@@ -53,7 +53,7 @@ public void Convert_CieLab_To_CieLuv(float l, float a, float b, float l2, float
// Arrange
CieLab input = new(l, a, b);
CieLuv expected = new(l2, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLab[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLabTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLabTests.cs
index 3c015259b1..69fabc7508 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLabTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLabTests.cs
@@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
///
/// Tests the struct.
///
+[Trait("Color", "Conversion")]
public class CieLabTests
{
[Fact]
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieLuvConversionTests.cs
index 598d4af335..12313281fa 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieLuvConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLchAndCieLuvConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLch_to_CieLuv(float l, float c, float h, float l2, float
// Arrange
CieLch input = new(l, c, h);
CieLuv expected = new(l2, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLch[5];
@@ -48,7 +48,7 @@ public void Convert_CieLuv_to_CieLch(float l2, float u, float v, float l, float
// Arrange
CieLuv input = new(l2, u, v);
CieLch expected = new(l, c, h);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLchConversionTests.cs
index a3e0b45e0d..857bdb3da1 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLchConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLchConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLch_To_CieLchuv(float l2, float c2, float h2, float l, fl
// Arrange
CieLch input = new(l2, c2, h2);
CieLchuv expected = new(l, c, h);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLch[5];
@@ -48,7 +48,7 @@ public void Convert_CieLchuv_To_CieLch(float l, float c, float h, float l2, floa
// Arrange
CieLchuv input = new(l, c, h);
CieLch expected = new(l2, c2, h2);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLchuv[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLuvConversionTests.cs
index 465237490d..424cb8cc77 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLuvConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCieLuvConversionTests.cs
@@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
/// Test data generated using:
///
///
+[Trait("Color", "Conversion")]
public class CieLchuvAndCieLuvConversionTests
{
private static readonly ApproximateColorProfileComparer Comparer = new(.0001F);
@@ -30,7 +31,7 @@ public void Convert_CieLchuv_to_CieLuv(float l, float c, float h, float l2, floa
// Arrange
CieLchuv input = new(l, c, h);
CieLuv expected = new(l2, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLchuv[5];
@@ -66,7 +67,7 @@ public void Convert_CieLuv_to_CieLchuv(float l, float u, float v, float l2, floa
// Arrange
CieLuv input = new(l, u, v);
CieLchuv expected = new(l2, c, h);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCmykConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCmykConversionTests.cs
index 60ac3da16e..3c8a93ee12 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCmykConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvAndCmykConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_Cmyk_to_CieLchuv(float c2, float m, float y, float k, float
// Arrange
Cmyk input = new(c2, m, y, k);
CieLchuv expected = new(l, c, h);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new Cmyk[5];
@@ -49,7 +49,7 @@ public void Convert_CieLchuv_to_Cmyk(float l, float c, float h, float c2, float
// Arrange
CieLchuv input = new(l, c, h);
Cmyk expected = new(c2, m, y, k);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLchuv[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvTests.cs
index 0b737cdfca..3fe550a5ba 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLchuvTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLchuvTests.cs
@@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
///
/// Tests the struct.
///
+[Trait("Color", "Conversion")]
public class CieLchuvTests
{
[Fact]
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndCieXyyConversionTests.cs
index e73edcda7c..08e73a1a71 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndCieXyyConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndCieXyyConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLuv_to_CieXyy(float l, float u, float v, float x, float y
// Arrange
CieLuv input = new(l, u, v);
CieXyy expected = new(x, y, yl);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
@@ -49,7 +49,7 @@ public void Convert_CieXyy_to_CieLuv(float x, float y, float yl, float l, float
// Arrange
CieXyy input = new(x, y, yl);
CieLuv expected = new(l, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieXyy[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHslConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHslConversionTests.cs
index b178b22b20..bfcd236c75 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHslConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHslConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLuv_to_Hsl(float l, float u, float v, float h, float s, f
// Arrange
CieLuv input = new(l, u, v);
Hsl expected = new(h, s, l2);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
@@ -49,7 +49,7 @@ public void Convert_Hsl_to_CieLuv(float h, float s, float l2, float l, float u,
// Arrange
Hsl input = new(h, s, l2);
CieLuv expected = new(l, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new Hsl[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHsvConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHsvConversionTests.cs
index 2866093377..8a25f95b7b 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHsvConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHsvConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLuv_to_Hsv(float l, float u, float v, float h, float s, f
// Arrange
CieLuv input = new(l, u, v);
Hsv expected = new(h, s, v2);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
@@ -49,7 +49,7 @@ public void Convert_Hsv_to_CieLuv(float h, float s, float v2, float l, float u,
// Arrange
Hsv input = new(h, s, v2);
CieLuv expected = new(l, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new Hsv[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs
index 73b605fb62..1c667f6794 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLuv_To_HunterLab(float l, float u, float v, float l2, flo
// Arrange
CieLuv input = new(l, u, v);
HunterLab expected = new(l2, a, b);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D50 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
@@ -49,7 +49,7 @@ public void Convert_HunterLab_To_CieLuv(float l2, float a, float b, float l, flo
// Arrange
HunterLab input = new(l2, a, b);
CieLuv expected = new(l, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D50, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new HunterLab[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndLmsConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndLmsConversionTests.cs
index 812ca44ddc..812b2b61e5 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndLmsConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndLmsConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLuv_to_Lms(float l, float u, float v, float l2, float m,
// Arrange
CieLuv input = new(l, u, v);
Lms expected = new(l2, m, s);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
@@ -49,7 +49,7 @@ public void Convert_Lms_to_CieLuv(float l2, float m, float s, float l, float u,
// Arrange
Lms input = new(l2, m, s);
CieLuv expected = new(l, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new Lms[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndRgbConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndRgbConversionTests.cs
index f1da6e33fd..1af802326e 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndRgbConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndRgbConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLuv_to_Rgb(float l, float u, float v, float r, float g, f
// Arrange
CieLuv input = new(l, u, v);
Rgb expected = new(r, g, b);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
@@ -49,7 +49,7 @@ public void Convert_Rgb_to_CieLuv(float r, float g, float b, float l, float u, f
// Arrange
Rgb input = new(r, g, b);
CieLuv expected = new(l, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new Rgb[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs
index fa7e2ece3f..0d5dd60e61 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs
@@ -20,7 +20,7 @@ public void Convert_CieLuv_to_YCbCr(float l, float u, float v, float y, float cb
// Arrange
CieLuv input = new(l, u, v);
YCbCr expected = new(y, cb, cr);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
@@ -49,7 +49,7 @@ public void Convert_YCbCr_to_CieLuv(float y, float cb, float cr, float l, float
// Arrange
YCbCr input = new(y, cb, cr);
CieLuv expected = new(l, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new YCbCr[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieLuvTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieLuvTests.cs
index db903a0bf5..173491081d 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieLuvTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieLuvTests.cs
@@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
///
/// Tests the struct.
///
+[Trait("Color", "Conversion")]
public class CieLuvTests
{
[Fact]
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyChromaticityCoordinatesTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyChromaticityCoordinatesTests.cs
index a85a08a21e..8bc71f1e18 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieXyChromaticityCoordinatesTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieXyChromaticityCoordinatesTests.cs
@@ -8,6 +8,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
///
/// Tests the struct.
///
+[Trait("Color", "Conversion")]
public class CieXyChromaticityCoordinatesTests
{
[Fact]
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyyTests.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyyTests.cs
index 245512f8a8..80904c5df1 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieXyyTests.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieXyyTests.cs
@@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
///
/// Tests the struct.
///
+[Trait("Color", "Conversion")]
public class CieXyyTests
{
[Fact]
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLabConversionTest.cs
index cb4d028895..76fceec413 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLabConversionTest.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLabConversionTest.cs
@@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
/// Test data generated using:
///
///
+[Trait("Color", "Conversion")]
public class CieXyzAndCieLabConversionTest
{
private static readonly ApproximateColorProfileComparer Comparer = new(.0001f);
@@ -29,7 +30,7 @@ public void Convert_Lab_to_Xyz(float l, float a, float b, float x, float y, floa
{
// Arrange
CieLab input = new(l, a, b);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
CieXyz expected = new(x, y, z);
@@ -62,7 +63,7 @@ public void Convert_Xyz_to_Lab(float x, float y, float z, float l, float a, floa
{
// Arrange
CieXyz input = new(x, y, z);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
CieLab expected = new(l, a, b);
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLuvConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLuvConversionTest.cs
index 944b990054..b269818ae8 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLuvConversionTest.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieLuvConversionTest.cs
@@ -29,7 +29,7 @@ public void Convert_Xyz_To_Luv(float x, float y, float z, float l, float u, floa
CieXyz input = new(x, y, z);
CieLuv expected = new(l, u, v);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieXyz[5];
@@ -64,7 +64,7 @@ public void Convert_Luv_To_Xyz(float l, float u, float v, float x, float y, floa
CieLuv input = new(l, u, v);
CieXyz expected = new(x, y, z);
- ColorConversionOptions options = new() { WhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
+ ColorConversionOptions options = new() { SourceWhitePoint = KnownIlluminants.D65, TargetWhitePoint = KnownIlluminants.D65 };
ColorProfileConverter converter = new(options);
Span inputSpan = new CieLuv[5];
diff --git a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieXyyConversionTest.cs
index 7b1d0ac781..48bb6c1e16 100644
--- a/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieXyyConversionTest.cs
+++ b/tests/ImageSharp.Tests/ColorProfiles/CieXyzAndCieXyyConversionTest.cs
@@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles;
/// Test data generated using:
///