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 . ///