diff --git a/src/ImageSharp/Colors/Spaces/CieXyy.cs b/src/ImageSharp/Colors/Spaces/CieXyy.cs
new file mode 100644
index 000000000..cf33c1473
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/CieXyy.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 xyY 1931 color
+ ///
+ ///
+ public struct CieXyy : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has X, Y, and Y values set to zero.
+ ///
+ public static readonly CieXyy Empty = default(CieXyy);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The x chroma component.
+ /// The y chroma component.
+ /// The y luminance component.
+ public CieXyy(float x, float y, float yl)
+ : this(new Vector3(x, y, yl))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, Y components.
+ public CieXyy(Vector3 vector)
+ : this()
+ {
+ // Not clamping as documentation about this space seems to indicate "usual" ranges
+ this.backingVector = vector;
+ }
+
+ ///
+ /// Gets the X chrominance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float X => this.backingVector.X;
+
+ ///
+ /// Gets the Y chrominance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float Y => this.backingVector.Y;
+
+ ///
+ /// Gets the Y luminance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float Yl => 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 ==(CieXyy left, CieXyy 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 !=(CieXyy left, CieXyy right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieXyy [ Empty ]";
+ }
+
+ return $"CieXyy [ X={this.X:#0.##}, Y={this.Y:#0.##}, Yl={this.Yl:#0.##} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is CieXyy)
+ {
+ return this.Equals((CieXyy)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(CieXyy other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ public bool AlmostEquals(CieXyy 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
index 2e4a73e2d..9bf68a36d 100644
--- a/src/ImageSharp/Colors/Spaces/CieXyz.cs
+++ b/src/ImageSharp/Colors/Spaces/CieXyz.cs
@@ -10,13 +10,13 @@ namespace ImageSharp.Colors.Spaces
using System.Numerics;
///
- /// Represents an CIE 1931 color
- ///
+ /// Represents an CIE XYZ 1931 color
+ ///
///
public struct CieXyz : IColorVector, IEquatable, IAlmostEquatable
{
///
- /// Represents a that has Y, Cb, and Cr values set to zero.
+ /// Represents a that has X, Y, and Z values set to zero.
///
public static readonly CieXyz Empty = default(CieXyz);
@@ -48,19 +48,19 @@ namespace ImageSharp.Colors.Spaces
}
///
- /// Gets the Y luminance component.
+ /// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative.
/// A value usually ranging between 0 and 1.
///
public float X => this.backingVector.X;
///
- /// Gets the Cb chroma component.
+ /// Gets the Y luminance component.
/// A value usually ranging between 0 and 1.
///
public float Y => this.backingVector.Y;
///
- /// Gets the Cr chroma component.
+ /// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response
/// A value usually ranging between 0 and 1.
///
public float Z => this.backingVector.Z;
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs
index 418366401..dfc5fbe4a 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs
@@ -90,6 +90,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToCieLab(xyzColor);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyy.cs
new file mode 100644
index 000000000..692b13f94
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyy.cs
@@ -0,0 +1,113 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces.Conversion
+{
+ using ImageSharp.Colors.Spaces.Conversion.Implementation.CieXyy;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorSpaceConverter
+ {
+ private static readonly CieXyzAndCieXyyConverter CieXyzAndCieXyyConverter = new CieXyzAndCieXyyConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ return CieXyzAndCieXyyConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs
index b5a708dec..c04f1093b 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs
@@ -41,6 +41,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return adapted;
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return CieXyzAndCieXyyConverter.Convert(color);
+ }
+
///
/// Converts a into a
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs
index 33fad16c7..961b43fd2 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs
@@ -94,5 +94,18 @@ namespace ImageSharp.Colors.Spaces.Conversion
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs
index 1cf577d11..c7389918e 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs
@@ -100,6 +100,19 @@ namespace ImageSharp.Colors.Spaces.Conversion
return this.ToLinearRgb(xyzColor);
}
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToLinearRgb(xyzColor);
+ }
+
///
/// Gets the correct converter for the given rgb working space.
///
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs
index de9f765ce..74a6dd639 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs
@@ -89,5 +89,18 @@ namespace ImageSharp.Colors.Spaces.Conversion
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs
index 879f915dc..50b79bd2b 100644
--- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs
@@ -94,5 +94,18 @@ namespace ImageSharp.Colors.Spaces.Conversion
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToRgb(xyzColor);
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs
new file mode 100644
index 000000000..dedb95ff8
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieXyy
+{
+ using ImageSharp.Colors.Spaces;
+
+ ///
+ /// Color converter between CIE XYZ and CIE xyY
+ /// for formulas.
+ ///
+ internal class CieXyzAndCieXyyConverter : IColorConversion, IColorConversion
+ {
+ ///
+ public CieXyy Convert(CieXyz input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ float x = input.X / (input.X + input.Y + input.Z);
+ float y = input.Y / (input.X + input.Y + input.Z);
+
+ if (float.IsNaN(x) || float.IsNaN(y))
+ {
+ return new CieXyy(0, 0, input.Y);
+ }
+
+ return new CieXyy(x, y, input.Y);
+ }
+
+ ///
+ public CieXyz Convert(CieXyy input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ if (MathF.Abs(input.Y) < Constants.Epsilon)
+ {
+ return new CieXyz(0, 0, input.Yl);
+ }
+
+ float x = (input.X * input.Yl) / input.Y;
+ float y = input.Yl;
+ float z = ((1 - input.X - input.Y) * y) / input.Y;
+
+ return new CieXyz(x, y, z);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj
index a19bed604..fe37a26eb 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -33,6 +33,9 @@
+
+
+
All
diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieXyyConversionTest.cs
new file mode 100644
index 000000000..bf892c9cc
--- /dev/null
+++ b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieXyyConversionTest.cs
@@ -0,0 +1,61 @@
+namespace ImageSharp.Tests.Colors.Colorspaces
+{
+ using System.Collections.Generic;
+
+ using ImageSharp.Colors.Spaces;
+ using ImageSharp.Colors.Spaces.Conversion;
+
+ using Xunit;
+
+ ///
+ /// Tests - conversions.
+ ///
+ ///
+ /// Test data generated using:
+ ///
+ ///
+ public class CieXyzAndCieXyyConversionTest
+ {
+ private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4);
+
+ private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter();
+
+ [Theory]
+ [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)]
+ [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)]
+ [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)]
+ [InlineData(0, 0, 0, 0.538842, 0.000000, 0.000000)]
+ public void Convert_xyY_to_XYZ(float xyzX, float xyzY, float xyzZ, float x, float y, float yl)
+ {
+ // Arrange
+ CieXyy input = new CieXyy(x, y, yl);
+
+ // Act
+ CieXyz output = Converter.ToCieXyz(input);
+
+ // Assert
+ Assert.Equal(xyzX, output.X, FloatRoundingComparer);
+ Assert.Equal(xyzY, output.Y, FloatRoundingComparer);
+ Assert.Equal(xyzZ, output.Z, FloatRoundingComparer);
+ }
+
+ [Theory]
+ [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)]
+ [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)]
+ [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)]
+ [InlineData(0.231809, 0, 0.077528, 0.749374, 0.000000, 0.000000)]
+ public void Convert_XYZ_to_xyY(float xyzX, float xyzY, float xyzZ, float x, float y, float yl)
+ {
+ // Arrange
+ CieXyz input = new CieXyz(xyzX, xyzY, xyzZ);
+
+ // Act
+ CieXyy output = Converter.ToCieXyy(input);
+
+ // Assert
+ Assert.Equal(x, output.X, FloatRoundingComparer);
+ Assert.Equal(y, output.Y, FloatRoundingComparer);
+ Assert.Equal(yl, output.Yl, FloatRoundingComparer);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs
index 1ea4d3ce5..c82fc6d80 100644
--- a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs
+++ b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorSpaceEqualityTests.cs
@@ -16,26 +16,49 @@ namespace ImageSharp.Tests.Colors
///
public class ColorSpaceEqualityTests
{
+ public static readonly TheoryData EmptyData =
+ new TheoryData
+ {
+ CieLab.Empty,
+ CieLch.Empty,
+ CieXyz.Empty,
+ CieXyy.Empty,
+ HunterLab.Empty,
+ Lms.Empty,
+ LinearRgb.Empty,
+ Rgb.Empty,
+ };
+
public static readonly TheoryData