diff --git a/src/ImageSharp/Colors/Spaces/CieLab.cs b/src/ImageSharp/Colors/Spaces/CieLab.cs
new file mode 100644
index 0000000000..e5edfcc793
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/CieLab.cs
@@ -0,0 +1,187 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents an CIE LAB 1976 color.
+ ///
+ ///
+ public struct CieLab : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// D50 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ ///
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D50;
+
+ ///
+ /// Represents a that has L, A, B values set to zero.
+ ///
+ public static readonly CieLab Empty = default(CieLab);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ /// Uses as white point.
+ public CieLab(float l, float a, float b)
+ : this(new Vector3(l, a, b), DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ /// The reference white point.
+ public CieLab(float l, float a, float b, CieXyz whitePoint)
+ : this(new Vector3(l, a, b), whitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, a, b components.
+ /// Uses as white point.
+ public CieLab(Vector3 vector)
+ : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l a b components.
+ /// The reference white point.
+ public CieLab(Vector3 vector, CieXyz whitePoint)
+ : this()
+ {
+ this.backingVector = vector;
+ this.WhitePoint = whitePoint;
+ }
+
+ public CieXyz WhitePoint { get; }
+
+ ///
+ /// Gets the lightness dimension.
+ /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
+ ///
+ public float L => this.backingVector.X;
+
+ ///
+ /// Gets the a color component.
+ /// A value ranging from -100 to 100. Negative is green, positive magenta.
+ ///
+ public float A => this.backingVector.Y;
+
+ ///
+ /// Gets the b color component.
+ /// A value ranging from -100 to 100. Negative is blue, positive is yellow
+ ///
+ public float B => this.backingVector.Z;
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector => this.backingVector;
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(CieLab left, CieLab right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for inequality
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(CieLab left, CieLab right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieLab [Empty]";
+ }
+
+ return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is CieLab)
+ {
+ return this.Equals((CieLab)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(CieLab other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ public bool AlmostEquals(CieLab other, float precision)
+ {
+ Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/CieXyz.cs b/src/ImageSharp/Colors/Spaces/CieXyz.cs
new file mode 100644
index 0000000000..2e4a73e2da
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/CieXyz.cs
@@ -0,0 +1,155 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents an CIE 1931 color
+ ///
+ ///
+ public struct CieXyz : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has Y, Cb, and Cr values set to zero.
+ ///
+ public static readonly CieXyz Empty = default(CieXyz);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative
+ /// The y luminance component.
+ /// Z is quasi-equal to blue stimulation, or the S cone of the human eye.
+ public CieXyz(float x, float y, float z)
+ : this(new Vector3(x, y, z))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, z components.
+ public CieXyz(Vector3 vector)
+ : this()
+ {
+ // Not clamping as documentation about this space seems to indicate "usual" ranges
+ this.backingVector = vector;
+ }
+
+ ///
+ /// Gets the Y luminance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float X => this.backingVector.X;
+
+ ///
+ /// Gets the Cb chroma component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float Y => this.backingVector.Y;
+
+ ///
+ /// Gets the Cr chroma component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float Z => this.backingVector.Z;
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector => this.backingVector;
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(CieXyz left, CieXyz right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(CieXyz left, CieXyz right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieXyz [ Empty ]";
+ }
+
+ return $"CieXyz [ X={this.X:#0.##}, Y={this.Y:#0.##}, Z={this.Z:#0.##} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is CieXyz)
+ {
+ return this.Equals((CieXyz)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(CieXyz other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ public bool AlmostEquals(CieXyz other, float precision)
+ {
+ Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/CieConstants.cs b/src/ImageSharp/Colors/Spaces/Conversion/CieConstants.cs
new file mode 100644
index 0000000000..2134ea214d
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/CieConstants.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ ///
+ /// Constants use for Cie conversion calculations
+ ///
+ ///
+ internal static class CieConstants
+ {
+ ///
+ /// 216F / 24389F
+ ///
+ public const float Epsilon = 0.008856452F;
+
+ ///
+ /// 24389F / 27F
+ ///
+ public const float Kappa = 903.2963F;
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Adapt.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Adapt.cs
new file mode 100644
index 0000000000..fc768d3ea1
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Adapt.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using System;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ ///
+ /// Performs chromatic adaptation of given XYZ color.
+ /// Target white point is .
+ ///
+ public CieXyz Adapt(CieXyz color, CieXyz sourceWhitePoint)
+ {
+ Guard.NotNull(color, nameof(color));
+ Guard.NotNull(sourceWhitePoint, nameof(sourceWhitePoint));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ return this.ChromaticAdaptation.Transform(color, sourceWhitePoint, this.WhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieLab.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieLab.cs
new file mode 100644
index 0000000000..8ccefc5326
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieLab.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Adaptation
+ CieXyz adapted = !this.WhitePoint.Equals(this.TargetLabWhitePoint) && this.IsChromaticAdaptationPerformed
+ ? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetLabWhitePoint)
+ : color;
+
+ // Conversion
+ CieXyzToCieLabConverter converter = new CieXyzToCieLabConverter(this.TargetLabWhitePoint);
+ return converter.Convert(adapted);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieXyz.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieXyz.cs
new file mode 100644
index 0000000000..66f3b25e77
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieXyz.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ private static readonly CieLabToCieXyzConverter CieLabToCieXyzConverter = new CieLabToCieXyzConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+
+ CieXyz unadapted = CieLabToCieXyzConverter.Convert(color);
+
+ // Adaptation
+ CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
+ ? unadapted
+ : this.Adapt(unadapted, color.WhitePoint);
+
+ return adapted;
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return this.cachedCieXyzAndLmsConverter.Convert(color);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Lms.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Lms.cs
new file mode 100644
index 0000000000..12d4ca9432
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Lms.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return this.cachedCieXyzAndLmsConverter.Convert(color);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.cs
new file mode 100644
index 0000000000..93d04e7e19
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.cs
@@ -0,0 +1,81 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using System.Numerics;
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ private Matrix4x4 transformationMatrix;
+ private CieXyzAndLmsConverter cachedCieXyzAndLmsConverter;
+
+ ///
+ /// The default whitepoint used for converting to CieLab
+ ///
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ColorConverter()
+ {
+ // Note the order here this is important.
+ this.WhitePoint = DefaultWhitePoint;
+ this.LmsAdaptationMatrix = CieXyzAndLmsConverter.DefaultTransformationMatrix;
+ this.ChromaticAdaptation = new VonKriesChromaticAdaptation(this.cachedCieXyzAndLmsConverter, this.cachedCieXyzAndLmsConverter);
+ this.TargetLabWhitePoint = CieLab.DefaultWhitePoint;
+ }
+
+ ///
+ /// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space.
+ /// When null, no adaptation will be performed.
+ ///
+ public CieXyz WhitePoint { get; set; }
+
+ ///
+ /// Gets or sets the white point used *when creating* Lab/LChab colors. (Lab/LChab colors on the input already contain the white point information)
+ /// Defaults to: .
+ ///
+ public CieXyz TargetLabWhitePoint { get; set; }
+
+ ///
+ /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed.
+ ///
+ public IChromaticAdaptation ChromaticAdaptation { get; set; }
+
+ ///
+ /// Gets or sets transformation matrix used in conversion to ,
+ /// also used in the default Von Kries Chromatic Adaptation method.
+ ///
+ public Matrix4x4 LmsAdaptationMatrix
+ {
+ get { return this.transformationMatrix; }
+ set
+ {
+ this.transformationMatrix = value;
+
+ if (this.cachedCieXyzAndLmsConverter == null)
+ {
+ this.cachedCieXyzAndLmsConverter = new CieXyzAndLmsConverter(value);
+ }
+ else
+ {
+ this.cachedCieXyzAndLmsConverter.TransformationMatrix = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether chromatic adaptation has been performed.
+ ///
+ private bool IsChromaticAdaptationPerformed => this.ChromaticAdaptation != null;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/IChromaticAdaptation.cs b/src/ImageSharp/Colors/Spaces/Conversion/IChromaticAdaptation.cs
new file mode 100644
index 0000000000..d97d1bd1c6
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/IChromaticAdaptation.cs
@@ -0,0 +1,27 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using Spaces;
+
+ ///
+ /// Chromatic adaptation.
+ /// A linear transformation of a source color (XS, YS, ZS) into a destination color (XD, YD, ZD) by a linear transformation [M]
+ /// which is dependent on the source reference white (XWS, YWS, ZWS) and the destination reference white (XWD, YWD, ZWD).
+ ///
+ public interface IChromaticAdaptation
+ {
+ ///
+ /// Performs a linear transformation of a source color in to the destination color.
+ ///
+ /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).
+ /// The source color.
+ /// The source white point.
+ /// The target white point.
+ /// The
+ CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/IColorConversion.cs b/src/ImageSharp/Colors/Spaces/Conversion/IColorConversion.cs
new file mode 100644
index 0000000000..ad653da947
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/IColorConversion.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ ///
+ /// Converts color between two color spaces.
+ ///
+ /// The input color type.
+ /// The result color type.
+ public interface IColorConversion
+ {
+ ///
+ /// Performs the conversion from the input to an instance of the output type.
+ ///
+ /// The input color instance.
+ /// The
+ TResult Convert(T input);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
new file mode 100644
index 0000000000..1d9ab6269d
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion.Implementation
+{
+ using System;
+ using Spaces;
+
+ ///
+ /// Converts from to .
+ ///
+ public class CieLabToCieXyzConverter : IColorConversion
+ {
+ ///
+ public CieXyz Convert(CieLab input)
+ {
+ Guard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
+ float l = input.L, a = input.A, b = input.B;
+ float fy = (l + 16) / 116F;
+ float fx = a / 500F + fy;
+ float fz = fy - b / 200F;
+
+ float fx3 = (float)Math.Pow(fx, 3D);
+ float fz3 = (float)Math.Pow(fz, 3D);
+
+ float xr = fx3 > CieConstants.Epsilon ? fx3 : (116F * fx - 16F) / CieConstants.Kappa;
+ float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? (float)Math.Pow((l + 16F) / 116F, 3D) : l / CieConstants.Kappa;
+ float zr = fz3 > CieConstants.Epsilon ? fz3 : (116F * fz - 16F) / CieConstants.Kappa;
+
+ float wx = input.WhitePoint.X, wy = input.WhitePoint.Y, wz = input.WhitePoint.Z;
+
+ // Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white)
+ xr = xr.Clamp(0, 1F);
+ yr = yr.Clamp(0, 1F);
+ zr = zr.Clamp(0, 1F);
+
+ float x = xr * wx;
+ float y = yr * wy;
+ float z = zr * wz;
+
+ return new CieXyz(x, y, z);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
new file mode 100644
index 0000000000..ddd7c4bb2e
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion.Implementation
+{
+ using System;
+ using Spaces;
+
+ ///
+ /// Converts from to .
+ ///
+ public class CieXyzToCieLabConverter : IColorConversion
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CieXyzToCieLabConverter()
+ : this(CieLab.DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The target reference lab white point
+ public CieXyzToCieLabConverter(CieXyz labWhitePoint)
+ {
+ this.LabWhitePoint = labWhitePoint;
+ }
+
+ ///
+ /// Gets the target reference whitepoint. When not set, is used.
+ ///
+ public CieXyz LabWhitePoint { get; }
+
+ ///
+ public CieLab Convert(CieXyz input)
+ {
+ Guard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
+ float wx = this.LabWhitePoint.X, wy = this.LabWhitePoint.Y, wz = this.LabWhitePoint.Z;
+
+ float xr = input.X / wx, yr = input.Y / wy, zr = input.Z / wz;
+
+ float fx = xr > CieConstants.Epsilon ? (float)Math.Pow(xr, 0.333333333333333D) : (CieConstants.Kappa * xr + 16F) / 116F;
+ float fy = yr > CieConstants.Epsilon ? (float)Math.Pow(yr, 0.333333333333333D) : (CieConstants.Kappa * yr + 16F) / 116F;
+ float fz = zr > CieConstants.Epsilon ? (float)Math.Pow(zr, 0.333333333333333D) : (CieConstants.Kappa * zr + 16F) / 116F;
+
+ float l = (116F * fy) - 16F;
+ float a = 500F * (fx - fy);
+ float b = 200F * (fy - fz);
+
+ return new CieLab(l, a, b, this.LabWhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
new file mode 100644
index 0000000000..69899e0dbd
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+using System.Numerics;
+
+namespace ImageSharp.Colors.Conversion.Implementation
+{
+ using Spaces;
+
+ public class CieXyzAndLmsConverter : IColorConversion, IColorConversion
+ {
+ ///
+ /// Default transformation matrix used, when no other is set. (Bradford)
+ ///
+ ///
+ public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford;
+
+ private Matrix4x4 inverseTransformationMatrix;
+ private Matrix4x4 transformationMatrix;
+
+ ///
+ /// Transformation matrix used for the conversion (definition of the cone response domain).
+ ///
+ ///
+ public Matrix4x4 TransformationMatrix
+ {
+ get { return this.transformationMatrix; }
+ internal set
+ {
+ this.transformationMatrix = value;
+ Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CieXyzAndLmsConverter()
+ : this(DefaultTransformationMatrix)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Definition of the cone response domain (see ),
+ /// if not set will be used.
+ ///
+ public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix)
+ {
+ this.TransformationMatrix = transformationMatrix;
+ }
+
+ ///
+ public Lms Convert(CieXyz input)
+ {
+ Guard.NotNull(input, nameof(input));
+
+ Vector3 vector = Vector3.Transform(input.Vector, this.transformationMatrix);
+ return new Lms(vector);
+ }
+
+ ///
+ public CieXyz Convert(Lms input)
+ {
+ Vector3 vector = Vector3.Transform(input.Vector, this.inverseTransformationMatrix);
+ return new CieXyz(vector);
+ }
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
new file mode 100644
index 0000000000..fe3ded527a
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
@@ -0,0 +1,101 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.Colors.Conversion.Implementation
+{
+ using System.Numerics;
+
+ ///
+ /// AdaptionMatrix3X3 used for transformation from XYZ to LMS, defining the cone response domain.
+ /// Used in
+ ///
+ ///
+ /// AdaptionMatrix3X3 data obtained from:
+ /// Two New von Kries Based Chromatic Adaptation Transforms Found by Numerical Optimization
+ /// S. Bianco, R. Schettini
+ /// DISCo, Department of Informatics, Systems and Communication, University of Milan-Bicocca, viale Sarca 336, 20126 Milan, Italy
+ /// http://www.ivl.disco.unimib.it/papers2003/CRA-CAT.pdf
+ ///
+ public static class LmsAdaptationMatrix
+ {
+ ///
+ /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65)
+ ///
+ public static readonly Matrix4x4 VonKriesHPEAdjusted
+ = new Matrix4x4()
+ {
+ M11 = 0.40024F, M12 = 0.7076F, M13 = -0.08081F,
+ M21 = -0.2263F, M22 = 1.16532F, M23 = 0.0457F,
+ M31 = 0, M32 = 0, M33 = 0.91822F,
+ M44 = 1F // Important for inverse transforms.
+ };
+
+ ///
+ /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy)
+ ///
+ public static readonly Matrix4x4 VonKriesHPE
+ = new Matrix4x4
+ {
+ M11 = 0.3897F, M12 = 0.6890F, M13 = -0.0787F,
+ M21 = -0.2298F, M22 = 1.1834F, M23 = 0.0464F,
+ M31 = 0, M32 = 0, M33 = 1F,
+ M44 = 1F
+ };
+
+ ///
+ /// XYZ scaling chromatic adaptation transform matrix
+ ///
+ public static readonly Matrix4x4 XYZScaling = Matrix4x4.Identity;
+
+ ///
+ /// Bradford chromatic adaptation transform matrix (used in CMCCAT97)
+ ///
+ public static readonly Matrix4x4 Bradford
+ = new Matrix4x4
+ {
+ M11 = 0.8951F, M12 = 0.2664F, M13 = -0.1614F,
+ M21 = -0.7502F, M22 = 1.7135F, M23 = 0.0367F,
+ M31 = 0.0389F, M32 = -0.0685F, M33 = 1.0296F,
+ M44 = 1F
+ };
+
+ ///
+ /// Spectral sharpening and the Bradford transform
+ ///
+ public static readonly Matrix4x4 BradfordSharp
+ = new Matrix4x4
+ {
+ M11 = 1.2694F, M12 = -0.0988F, M13 = -0.1706F,
+ M21 = -0.8364F, M22 = 1.8006F, M23 = 0.0357F,
+ M31 = 0.0297F, M32 = -0.0315F, M33 = 1.0018F,
+ M44 = 1F
+ };
+
+ ///
+ /// CMCCAT2000 (fitted from all available color data sets)
+ ///
+ public static readonly Matrix4x4 CMCCAT2000
+ = new Matrix4x4
+ {
+ M11 = 0.7982F, M12 = 0.3389F, M13 = -0.1371F,
+ M21 = -0.5918F, M22 = 1.5512F, M23 = 0.0406F,
+ M31 = 0.0008F, M32 = 0.239F, M33 = 0.9753F,
+ M44 = 1F
+ };
+
+ ///
+ /// CAT02 (optimized for minimizing CIELAB differences)
+ ///
+ public static readonly Matrix4x4 CAT02
+ = new Matrix4x4
+ {
+ M11 = 0.7328F, M12 = 0.4296F, M13 = -0.1624F,
+ M21 = -0.7036F, M22 = 1.6975F, M23 = 0.0061F,
+ M31 = 0.0030F, M32 = 0.0136F, M33 = 0.9834F,
+ M44 = 1F
+ };
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/VonKriesChromaticAdaptation.cs b/src/ImageSharp/Colors/Spaces/Conversion/VonKriesChromaticAdaptation.cs
new file mode 100644
index 0000000000..a5b92d06db
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/VonKriesChromaticAdaptation.cs
@@ -0,0 +1,91 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using System.Numerics;
+
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Basic implementation of the von Kries chromatic adaptation model
+ ///
+ ///
+ /// Transformation described here:
+ /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ ///
+ public class VonKriesChromaticAdaptation : IChromaticAdaptation
+ {
+ private readonly IColorConversion conversionToLms;
+
+ private readonly IColorConversion conversionToXyz;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public VonKriesChromaticAdaptation()
+ : this(new CieXyzAndLmsConverter())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The transformation matrix used for the conversion (definition of the cone response domain).
+ ///
+ ///
+ public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix)
+ : this(new CieXyzAndLmsConverter(transformationMatrix))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ private VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter)
+ : this(converter, converter)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The color converter.
+ /// The color converter.
+ public VonKriesChromaticAdaptation(IColorConversion conversionToLms, IColorConversion conversionToCieXyz)
+ {
+ Guard.NotNull(conversionToLms, nameof(conversionToLms));
+ Guard.NotNull(conversionToCieXyz, nameof(conversionToCieXyz));
+
+ this.conversionToLms = conversionToLms;
+ this.conversionToXyz = conversionToCieXyz;
+ }
+
+ ///
+ public CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint)
+ {
+ Guard.NotNull(sourceColor, nameof(sourceColor));
+ Guard.NotNull(sourceWhitePoint, nameof(sourceWhitePoint));
+ Guard.NotNull(targetWhitePoint, nameof(targetWhitePoint));
+
+ if (sourceWhitePoint.Equals(targetWhitePoint))
+ {
+ return sourceColor;
+ }
+
+ Lms sourceColorLms = this.conversionToLms.Convert(sourceColor);
+ Lms sourceWhitePointLms = this.conversionToLms.Convert(sourceWhitePoint);
+ Lms targetWhitePointLms = this.conversionToLms.Convert(targetWhitePoint);
+
+ Vector3 vector = new Vector3(targetWhitePointLms.L / sourceWhitePointLms.L, targetWhitePointLms.M / sourceWhitePointLms.M, targetWhitePointLms.S / sourceWhitePointLms.S);
+
+ Lms targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.Vector));
+ return this.conversionToXyz.Convert(targetColorLms);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs b/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs
new file mode 100644
index 0000000000..dc3dc57f0b
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ using System;
+
+ ///
+ /// Defines a generalized method that a value type or class implements to create
+ /// a type-specific method for determining approximate equality of instances.
+ ///
+ /// The type of objects to compare.
+ /// The object specifying the type to specify precision with.
+ public interface IAlmostEquatable
+ where TPrecision : struct, IComparable
+ {
+ ///
+ /// Indicates whether the current object is equal to another object of the same type
+ /// when compared to the specified precision level.
+ ///
+ /// An object to compare with this object.
+ /// The object specifying the level of precision.
+ ///
+ /// true if the current object is equal to the other parameter; otherwise, false.
+ ///
+ bool AlmostEquals(TColor other, TPrecision precision);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/IColorVector.cs b/src/ImageSharp/Colors/Spaces/IColorVector.cs
new file mode 100644
index 0000000000..10ad9d30f4
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/IColorVector.cs
@@ -0,0 +1,20 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ using System.Numerics;
+
+ ///
+ /// Color represented as a vector in its color space
+ ///
+ public interface IColorVector
+ {
+ ///
+ /// The vector representation of the color
+ ///
+ Vector3 Vector { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/ICompanding.cs b/src/ImageSharp/Colors/Spaces/ICompanding.cs
new file mode 100644
index 0000000000..f20946b4fb
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/ICompanding.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ ///
+ /// Pair of companding functions for .
+ /// Used for conversion to and backwards.
+ /// See also:
+ ///
+ public interface ICompanding
+ {
+ ///
+ /// Companded channel is made linear with respect to the energy.
+ ///
+ ///
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ ///
+ float InverseCompanding(float channel);
+
+ ///
+ /// Uncompanded channel (linear) is made nonlinear (depends on the RGB color system).
+ ///
+ ///
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ ///
+ float Companding(float channel);
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/IRgbWorkingSpace.cs b/src/ImageSharp/Colors/Spaces/IRgbWorkingSpace.cs
new file mode 100644
index 0000000000..cc759753bf
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/IRgbWorkingSpace.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ ///
+ /// Encasulates the RGB working color space
+ ///
+ public interface IRgbWorkingSpace
+ {
+ ///
+ /// Gets the reference white of the color space
+ ///
+ CieXyz WhitePoint { get; }
+
+ ///
+ /// Chromaticity coordinates of the primaries
+ ///
+ // RGBPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
+
+ ///
+ /// The companding function associated with the RGB color system.
+ /// Used for conversion to XYZ and backwards.
+ /// See this for more information:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ ///
+ ICompanding Companding { get; }
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/Illuminants.cs b/src/ImageSharp/Colors/Spaces/Illuminants.cs
new file mode 100644
index 0000000000..51ef4d8d95
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Illuminants.cs
@@ -0,0 +1,71 @@
+namespace ImageSharp.Colors.Spaces
+{
+ ///
+ /// The well known standard illuminants.
+ /// Standard illuminants provide a basis for comparing images or colors recorded under different lighting
+ ///
+ ///
+ /// Coefficients taken from:
+ /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ ///
+ /// Descriptions taken from:
+ /// http://en.wikipedia.org/wiki/Standard_illuminant
+ ///
+ public static class Illuminants
+ {
+ ///
+ /// Incandescent / Tungsten
+ ///
+ public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F);
+
+ ///
+ /// Direct sunlight at noon (obsoleteF)
+ ///
+ public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F);
+
+ ///
+ /// Average / North sky Daylight (obsoleteF)
+ ///
+ public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F);
+
+ ///
+ /// Horizon Light. ICC profile PCS
+ ///
+ public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F);
+
+ ///
+ /// Mid-morning / Mid-afternoon Daylight
+ ///
+ public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F);
+
+ ///
+ /// Noon Daylight: TelevisionF, sRGB color space
+ ///
+ public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F);
+
+ ///
+ /// North sky Daylight
+ ///
+ public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F);
+
+ ///
+ /// Equal energy
+ ///
+ public static readonly CieXyz E = new CieXyz(1F, 1F, 1F);
+
+ ///
+ /// Cool White Fluorescent
+ ///
+ public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F);
+
+ ///
+ /// D65 simulatorF, Daylight simulator
+ ///
+ public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F);
+
+ ///
+ /// Philips TL84F, Ultralume 40
+ ///
+ public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Lms.cs b/src/ImageSharp/Colors/Spaces/Lms.cs
new file mode 100644
index 0000000000..75ad6710b0
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Lms.cs
@@ -0,0 +1,156 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// LMS is a color space represented by the response of the three types of cones of the human eye,
+ /// named after their responsivity (sensitivity) at long, medium and short wavelengths.
+ ///
+ ///
+ public struct Lms : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has Y, Cb, and Cr values set to zero.
+ ///
+ public static readonly Lms Empty = default(Lms);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// L represents the responsivity at long wavelengths.
+ /// M represents the responsivity at medium wavelengths.
+ /// S represents the responsivity at short wavelengths.
+ public Lms(float l, float m, float s)
+ : this(new Vector3(l, m, s))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, z components.
+ public Lms(Vector3 vector)
+ : this()
+ {
+ // Not clamping as documentation about this space seems to indicate "usual" ranges
+ this.backingVector = vector;
+ }
+
+ ///
+ /// Gets the L long component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float L => this.backingVector.X;
+
+ ///
+ /// Gets the M medium component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float M => this.backingVector.Y;
+
+ ///
+ /// Gets the S short component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float S => this.backingVector.Z;
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector => this.backingVector;
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(Lms left, Lms right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(Lms left, Lms right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Lms [ Empty ]";
+ }
+
+ return $"Lms [ L={this.L:#0.##}, M={this.M:#0.##}, S={this.S:#0.##} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is Lms)
+ {
+ return this.Equals((Lms)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(Lms other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ public bool AlmostEquals(Lms other, float precision)
+ {
+ Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs
new file mode 100644
index 0000000000..5be2f27acf
--- /dev/null
+++ b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs
@@ -0,0 +1,72 @@
+namespace ImageSharp.Tests
+{
+ using ImageSharp.Colors.Conversion;
+ using System.Collections.Generic;
+ using ImageSharp.Colors.Spaces;
+ using Xunit;
+
+ ///
+ /// Tests - conversions.
+ ///
+ ///
+ /// Test data generated using:
+ /// http://www.brucelindbloom.com/index.html?ColorCalculator.html
+ ///
+ public class CieXyzAndCieLabConversionTest
+ {
+ private static readonly IEqualityComparer FloatComparerLabPrecision = new ApproximateFloatComparer(4);
+ private static readonly IEqualityComparer FloatComparerXyzPrecision = new ApproximateFloatComparer(6);
+
+ ///
+ /// Tests conversion from to ().
+ ///
+ [Theory]
+ [InlineData(100, 0, 0, 0.95047, 1, 1.08883)]
+ [InlineData(0, 0, 0, 0, 0, 0)]
+ [InlineData(0, 431.0345, 0, 0.95047, 0, 0)]
+ [InlineData(100, -431.0345, 172.4138, 0, 1, 0)]
+ [InlineData(0, 0, -172.4138, 0, 0, 1.08883)]
+ [InlineData(45.6398, 39.8753, 35.2091, 0.216938, 0.150041, 0.048850)]
+ [InlineData(77.1234, -40.1235, 78.1120, 0.358530, 0.517372, 0.076273)]
+ [InlineData(10, -400, 20, 0, 0.011260, 0)]
+ public void Convert_Lab_to_XYZ(float l, float a, float b, float x, float y, float z)
+ {
+ // Arrange
+ CieLab input = new CieLab(l, a, b, Illuminants.D65);
+ ColorConverter converter = new ColorConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 };
+
+ // Act
+ CieXyz output = converter.ToCieXyz(input);
+
+ // Assert
+ Assert.Equal(output.X, x, FloatComparerXyzPrecision);
+ Assert.Equal(output.Y, y, FloatComparerXyzPrecision);
+ Assert.Equal(output.Z, z, FloatComparerXyzPrecision);
+ }
+
+ ///
+ /// Tests conversion from () to .
+ ///
+ [Theory]
+ [InlineData(0.95047, 1, 1.08883, 100, 0, 0)]
+ [InlineData(0, 0, 0, 0, 0, 0)]
+ [InlineData(0.95047, 0, 0, 0, 431.0345, 0)]
+ [InlineData(0, 1, 0, 100, -431.0345, 172.4138)]
+ [InlineData(0, 0, 1.08883, 0, 0, -172.4138)]
+ [InlineData(0.216938, 0.150041, 0.048850, 45.6398, 39.8753, 35.2091)]
+ public void Convert_XYZ_to_Lab(float x, float y, float z, float l, float a, float b)
+ {
+ // Arrange
+ CieXyz input = new CieXyz(x, y, z);
+ ColorConverter converter = new ColorConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 };
+
+ // Act
+ CieLab output = converter.ToCieLab(input);
+
+ // Assert
+ Assert.Equal(output.L, l, FloatComparerLabPrecision);
+ Assert.Equal(output.A, a, FloatComparerLabPrecision);
+ Assert.Equal(output.B, b, FloatComparerLabPrecision);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndLmsConversionTest.cs
new file mode 100644
index 0000000000..d60b72c6d9
--- /dev/null
+++ b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndLmsConversionTest.cs
@@ -0,0 +1,68 @@
+namespace ImageSharp.Tests
+{
+ using ImageSharp.Colors.Conversion;
+ using System.Collections.Generic;
+ using ImageSharp.Colors.Spaces;
+ using Xunit;
+
+ ///
+ /// Tests - conversions.
+ ///
+ ///
+ /// Test data generated using original colorful library.
+ ///
+ public class CieXyzAndLmsConversionTest
+ {
+ private static readonly IEqualityComparer FloatComparer = new ApproximateFloatComparer(6);
+
+ ///
+ /// Tests conversion from () to .
+ ///
+ [Theory]
+ [InlineData(0.941428535, 1.040417467, 1.089532651, 0.95047, 1, 1.08883)]
+ [InlineData(0, 0, 0, 0, 0, 0)]
+ [InlineData(0.850765697, -0.713042594, 0.036973283, 0.95047, 0, 0)]
+ [InlineData(0.2664, 1.7135, -0.0685, 0, 1, 0)]
+ [InlineData(-0.175737162, 0.039960061, 1.121059368, 0, 0, 1.08883)]
+ [InlineData(0.2262677362, 0.0961411609, 0.0484570397, 0.216938, 0.150041, 0.048850)]
+ public void Convert_Lms_to_CieXyz(float l, float m, float s, float x, float y, float z)
+ {
+ // Arrange
+ Lms input = new Lms(x, y, z);
+ ColorConverter converter = new ColorConverter();
+
+ // Act
+ CieXyz output = converter.ToCieXyz(input);
+
+ // Assert
+ Assert.Equal(output.X, l, FloatComparer);
+ Assert.Equal(output.Y, m, FloatComparer);
+ Assert.Equal(output.Z, s, FloatComparer);
+ }
+
+ ///
+ /// Tests conversion from () to .
+ ///
+ [Theory]
+ [InlineData(0.95047, 1, 1.08883, 0.941428535, 1.040417467, 1.089532651)]
+ [InlineData(0, 0, 0, 0, 0, 0)]
+ [InlineData(0.95047, 0, 0, 0.850765697, -0.713042594, 0.036973283)]
+ [InlineData(0, 1, 0, 0.2664, 1.7135, -0.0685)]
+ [InlineData(0, 0, 1.08883, -0.175737162, 0.039960061, 1.121059368)]
+ [InlineData(0.216938, 0.150041, 0.048850, 0.2262677362, 0.0961411609, 0.0484570397)]
+ public void Convert_CieXyz_to_Lms(float x, float y, float z, float l, float m, float s)
+ {
+ // Arrange
+ CieXyz input = new CieXyz(x, y, z);
+ ColorConverter converter = new ColorConverter();
+
+ // Act
+ Lms output = converter.ToLms(input);
+
+ // Assert
+ Assert.Equal(output.L, l, FloatComparer);
+ Assert.Equal(output.M, m, FloatComparer);
+ Assert.Equal(output.S, s, FloatComparer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs
new file mode 100644
index 0000000000..b9476ce291
--- /dev/null
+++ b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs
@@ -0,0 +1,37 @@
+using ImageSharp.Colors.Conversion;
+using ImageSharp.Colors.Conversion.Implementation;
+using ImageSharp.Colors.Spaces;
+
+namespace ImageSharp.Tests
+{
+ using System.Collections.Generic;
+ using Xunit;
+
+ public class ColorConverterAdaptTest
+ {
+ private static readonly IEqualityComparer FloatComparer = new ApproximateFloatComparer(4);
+
+ [Theory]
+ [InlineData(0, 0, 0, 0, 0, 0)]
+ [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)]
+ public void Adapt_CieXyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2)
+ {
+ // Arrange
+ CieXyz input = new CieXyz(x1, y1, z1);
+ CieXyz expectedOutput = new CieXyz(x2, y2, z2);
+ ColorConverter converter = new ColorConverter
+ {
+ ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XYZScaling),
+ WhitePoint = Illuminants.D50
+ };
+
+ // Action
+ CieXyz output = converter.Adapt(input, Illuminants.D65);
+
+ // Assert
+ Assert.Equal(output.X, expectedOutput.X, FloatComparer);
+ Assert.Equal(output.Y, expectedOutput.Y, FloatComparer);
+ Assert.Equal(output.Z, expectedOutput.Z, FloatComparer);
+ }
+ }
+}
\ No newline at end of file