diff --git a/src/ImageProcessor/Colors/ColorTransforms.cs b/src/ImageProcessor/Colors/ColorTransforms.cs
index 5c7dc99ef8..fc3a01a78b 100644
--- a/src/ImageProcessor/Colors/ColorTransforms.cs
+++ b/src/ImageProcessor/Colors/ColorTransforms.cs
@@ -173,6 +173,37 @@ namespace ImageProcessor
return new Color(r, g, b);
}
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ /// The instance of to convert.
+ ///
+ /// An instance of .
+ ///
+ public static implicit operator Color(CieLab cieLabColor)
+ {
+ // First convert back to XYZ...
+ float y = (cieLabColor.L + 16F) / 116F;
+ float x = cieLabColor.A / 500F + y;
+ float z = y - cieLabColor.B / 200F;
+
+ float x3 = (float)Math.Pow(x, 3);
+ float y3 = (float)Math.Pow(y, 3);
+ float z3 = (float)Math.Pow(z, 3);
+
+ y = (y3 > 0.008856F) ? y3 : (y - 16F / 116F) / 7.787F;
+ x = (x3 > 0.008856F) ? x3 : (x - 16F / 116F) / 7.787F;
+ z = (z3 > 0.008856F) ? z3 : (z - 16F / 116F) / 7.787F;
+
+ // Then XYZ to RGB
+ float r = (x * 3.240969941904521F) + (y * -1.537383177570093F) + (z * -0.498610760293F);
+ float g = (x * -0.96924363628087F) + (y * 1.87596750150772F) + (z * 0.041555057407175F);
+ float b = (x * 0.055630079696993F) + (y * -0.20397695888897F) + (z * 1.056971514242878F);
+
+ return Color.InverseCompand(new Color(r, g, b));
+ }
+
///
/// Gets the color component from the given values.
///
diff --git a/src/ImageProcessor/Colors/Colorspaces/CieLab.cs b/src/ImageProcessor/Colors/Colorspaces/CieLab.cs
new file mode 100644
index 0000000000..978e0f0aef
--- /dev/null
+++ b/src/ImageProcessor/Colors/Colorspaces/CieLab.cs
@@ -0,0 +1,218 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents an CIE LAB 1976 color.
+ ///
+ public struct CieLab : IEquatable
+ {
+ ///
+ /// Represents a that has L, A, B values set to zero.
+ ///
+ public static readonly CieLab Empty = default(CieLab);
+
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.0001f;
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ public CieLab(float l, float a, float b)
+ : this()
+ {
+ this.backingVector.X = ClampL(l);
+ this.backingVector.Y = ClampAB(a);
+ this.backingVector.Z = ClampAB(b);
+ }
+
+ ///
+ /// 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.
+ /// Negative is green, positive magenta.
+ ///
+ public float A => this.backingVector.Y;
+
+ ///
+ /// Gets the b color component.
+ /// 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.backingVector.Equals(default(Vector4));
+
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ ///
+ /// The instance of to convert.
+ ///
+ ///
+ /// An instance of .
+ ///
+ public static implicit operator CieLab(Color color)
+ {
+ // First convert to CIE XYZ
+ color = Color.InverseCompand(color.Limited);
+
+ float x = (color.R * 0.41239079926595F) + (color.G * 0.35758433938387F) + (color.B * 0.18048078840183F);
+ float y = (color.R * 0.21263900587151F) + (color.G * 0.71516867876775F) + (color.B * 0.072192315360733F);
+ float z = (color.R * 0.019330818715591F) + (color.G * 0.11919477979462F) + (color.B * 0.95053215224966F);
+
+ x *= 100F;
+ y *= 100F;
+ z *= 100F;
+
+ // Now to LAB
+ x /= 95.047F;
+ y /= 100F;
+ z /= 108.883F;
+
+ x = x > 0.008856 ? (float) Math.Pow(x, 1F / 3F) : (7.787F * x) + (16F / 116F);
+ y = y > 0.008856 ? (float) Math.Pow(y, 1F / 3F) : (7.787F * y) + (16F / 116F);
+ z = z > 0.008856 ? (float) Math.Pow(z, 1F / 3F) : (7.787F * z) + (16F / 116F);
+
+ float l = (116 * y) - 16;
+ float a = 500 * (x - y);
+ float b = 200 * (y - z);
+
+ return new CieLab(l, a, b);
+ }
+
+ ///
+ /// 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 bool Equals(object obj)
+ {
+ if (obj is CieLab)
+ {
+ CieLab color = (CieLab)obj;
+
+ return this.backingVector == color.backingVector;
+ }
+
+ return false;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return GetHashCode(this);
+ }
+
+ ///
+ 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 bool Equals(CieLab other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ /// Checks the range for lightness.
+ ///
+ ///
+ /// The value to check.
+ ///
+ ///
+ /// The sanitized .
+ ///
+ private static float ClampL(float value)
+ {
+ return value.Clamp(0, 100);
+ }
+
+ ///
+ /// Checks the range for components A or B.
+ ///
+ ///
+ /// The value to check.
+ ///
+ ///
+ /// The sanitized .
+ ///
+ private static float ClampAB(float value)
+ {
+ return value.Clamp(-100, 100);
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private static int GetHashCode(CieLab color) => color.backingVector.GetHashCode();
+ }
+}