diff --git a/src/ImageProcessor/Colors/ColorTransforms.cs b/src/ImageProcessor/Colors/ColorTransforms.cs
index 5c7dc99ef..e085f35e8 100644
--- a/src/ImageProcessor/Colors/ColorTransforms.cs
+++ b/src/ImageProcessor/Colors/ColorTransforms.cs
@@ -173,6 +173,42 @@ 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 = x * x * x;
+ float y3 = y * y * y;
+ float z3 = z * z * z;
+
+ x = (x3 > 0.008856F) ? x3 : (x - 16F / 116F) / 7.787F;
+ y = (cieLabColor.L > 0.008856F * 903.3F) ? y3 : (cieLabColor.L / 903.3F);
+ z = (z3 > 0.008856F) ? z3 : (z - 16F / 116F) / 7.787F;
+
+ x *= 0.95047F;
+ //y *= 1F;
+ z *= 1.08883F;
+
+ // Then XYZ to RGB (multiplication by 100 was done above already)
+
+ float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F);
+ float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F);
+ float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F);
+
+ return Color.Compand(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 000000000..6098d40f8
--- /dev/null
+++ b/src/ImageProcessor/Colors/Colorspaces/CieLab.cs
@@ -0,0 +1,214 @@
+//
+// 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.4124F) + (color.G * 0.3576F) + (color.B * 0.1805F);
+ float y = (color.R * 0.2126F) + (color.G * 0.7152F) + (color.B * 0.0722F);
+ float z = (color.R * 0.0193F) + (color.G * 0.1192F) + (color.B * 0.9505F);
+
+ // Now to LAB
+ x /= 0.95047F;
+ //y /= 1F;
+ z /= 1.08883F;
+
+ x = x > 0.008856F ? (float) Math.Pow(x, 1F / 3F) : (903.3F * x + 16F) / 116F;
+ y = y > 0.008856F ? (float) Math.Pow(y, 1F / 3F) : (903.3F * y + 16F) / 116F;
+ z = z > 0.008856F ? (float) Math.Pow(z, 1F / 3F) : (903.3F * z + 16F) / 116F;
+
+ float l = Math.Max(0, (116F * y) - 16F);
+ float a = 500F * (x - y);
+ float b = 200F * (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();
+ }
+}
diff --git a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
index 6551b17c7..b219cd123 100644
--- a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
+++ b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
@@ -334,5 +334,85 @@ namespace ImageProcessor.Tests
Assert.Equal(color4, (Color)cmyk4);
}
}
+
+ ///
+ /// Tests the implicit conversion from to .
+ /// Comparison values obtained from
+ /// http://colormine.org/convert/rgb-to-lab
+ ///
+ [Fact]
+ public void ColorToCieLab()
+ {
+ // White
+ Color color = new Color(1, 1, 1);
+ CieLab cielab = color;
+
+ Assert.Equal(100, cielab.L, 3);
+ Assert.Equal(0.005, cielab.A, 3);
+ Assert.Equal(-0.010, cielab.B, 3);
+
+ // Black
+ Color color2 = new Color(0, 0, 0);
+ CieLab cielab2 = color2;
+ Assert.Equal(0, cielab2.L, 3);
+ Assert.Equal(0, cielab2.A, 3);
+ Assert.Equal(0, cielab2.B, 3);
+
+ //// Grey
+ Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f);
+ CieLab cielab3 = color3;
+ Assert.Equal(53.585, cielab3.L, 3);
+ Assert.Equal(0.003, cielab3.A, 3);
+ Assert.Equal(-0.006, cielab3.B, 3);
+
+ //// Cyan
+ Color color4 = new Color(0, 1, 1);
+ CieLab cielab4 = color4;
+ Assert.Equal(91.117, cielab4.L, 3);
+ Assert.Equal(-48.080, cielab4.A, 3);
+ Assert.Equal(-14.138, cielab4.B, 3);
+ }
+
+ ///
+ /// Tests the implicit conversion from to .
+ ///
+ /// Comparison values obtained from
+ /// http://colormine.org/convert/rgb-to-lab
+ [Fact]
+ public void CieLabToColor()
+ {
+ // Dark moderate pink.
+ CieLab cielab = new CieLab(36.5492f, 33.3173f, -12.0615f);
+ Color color = cielab;
+
+ Assert.Equal(color.R, 128 / 255f, 3);
+ Assert.Equal(color.G, 64 / 255f, 3);
+ Assert.Equal(color.B, 106 / 255f, 3);
+
+ // Ochre
+ CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f);
+ Color color2 = cielab2;
+
+ Assert.Equal(color2.R, 204 / 255f, 3);
+ Assert.Equal(color2.G, 119 / 255f, 3);
+ Assert.Equal(color2.B, 34 / 255f, 3);
+
+ //// White
+ CieLab cielab3 = new CieLab(0, 0, 0);
+ Color color3 = cielab3;
+
+ Assert.Equal(color3.R, 0f, 3);
+ Assert.Equal(color3.G, 0f, 3);
+ Assert.Equal(color3.B, 0f, 3);
+
+ //// Check others.
+ Random random = new Random(0);
+ for (int i = 0; i < 1000; i++)
+ {
+ Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
+ CieLab cielab4 = color4;
+ Assert.Equal(color4, (Color)cielab4);
+ }
+ }
}
}