diff --git a/src/ImageProcessor/Colors/Colorspaces/CieXyz.cs b/src/ImageProcessor/Colors/Colorspaces/CieXyz.cs
new file mode 100644
index 000000000..db8f57b38
--- /dev/null
+++ b/src/ImageProcessor/Colors/Colorspaces/CieXyz.cs
@@ -0,0 +1,176 @@
+//
+// 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 1931 color
+ ///
+ ///
+ public struct CieXyz : IEquatable
+ {
+ ///
+ /// 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 Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The y luminance component.
+ /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative
+ /// Z is quasi-equal to blue stimulation, or the S cone of the human eye.
+ public CieXyz(float x, float y, float z)
+ : this()
+ {
+ // ToDo: check clamp values
+ this.backingVector.X = x.Clamp(0, 2);
+ this.backingVector.Y = y.Clamp(0, 2);
+ this.backingVector.Z = z.Clamp(0, 2);
+ }
+
+ ///
+ /// Gets the Y luminance component.
+ /// A value ranging between 380 and 780.
+ ///
+ public float X => this.backingVector.X;
+
+ ///
+ /// Gets the Cb chroma component.
+ /// A value ranging between 380 and 780.
+ ///
+ public float Y => this.backingVector.Y;
+
+ ///
+ /// Gets the Cr chroma component.
+ /// A value ranging between 380 and 780.
+ ///
+ public float Z => this.backingVector.Z;
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.backingVector.Equals(default(Vector3));
+
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ ///
+ /// The instance of to convert.
+ ///
+ ///
+ /// An instance of .
+ ///
+ public static implicit operator CieXyz(Color color)
+ {
+ var r = color.R;
+ var g = color.G;
+ var b = color.B;
+
+ // assume sRGB
+ r = r > 0.04045 ? (float)Math.Pow(((r + 0.055) / 1.055), 2.4) : (float)(r / 12.92);
+ g = g > 0.04045 ? (float)Math.Pow(((g + 0.055) / 1.055), 2.4) : (float)(g / 12.92);
+ b = b > 0.04045 ? (float)Math.Pow(((b + 0.055) / 1.055), 2.4) : (float)(b / 12.92);
+
+ var x = (r * 0.41239079926595F) + (g * 0.35758433938387F) + (b * 0.18048078840183F);
+ var y = (r * 0.21263900587151F) + (g * 0.71516867876775F) + (b * 0.072192315360733F);
+ var z = (r * 0.019330818715591F) + (g * 0.11919477979462F) + (b * 0.95053215224966F);
+
+ return new CieXyz(x, y, z);
+ }
+
+ ///
+ /// 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 bool Equals(object obj)
+ {
+ if (obj is CieXyz)
+ {
+ CieXyz color = (CieXyz)obj;
+
+ return this.backingVector == color.backingVector;
+ }
+
+ return false;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return GetHashCode(this);
+ }
+
+ ///
+ 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 bool Equals(CieXyz other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ /// 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(CieXyz color) => color.backingVector.GetHashCode();
+ }
+}
diff --git a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
index 6551b17c7..efbab03a5 100644
--- a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
+++ b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
@@ -91,6 +91,18 @@ namespace ImageProcessor.Tests
Assert.Equal(1f, color3.A, 1);
}
+ [Fact]
+ public void ColorToCieXyz()
+ {
+ Color color = new Color(1, 1, 1);
+ CieXyz cieXyz = color;
+
+ Assert.Equal(0.9505F, cieXyz.X, 4);
+ Assert.Equal(1.0000F, cieXyz.Y, 4);
+ Assert.Equal(1.089F, cieXyz.Z, 3);
+
+ }
+
///
/// Tests the implicit conversion from to .
///