diff --git a/src/ImageSharp/Colors/ColorspaceTransforms.cs b/src/ImageSharp/Colors/ColorspaceTransforms.cs
deleted file mode 100644
index cda7022705..0000000000
--- a/src/ImageSharp/Colors/ColorspaceTransforms.cs
+++ /dev/null
@@ -1,288 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp
-{
- using System;
- using System.Numerics;
- using Colors.Spaces;
-
- ///
- /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255.
- /// The color components are stored in red, green, blue, and alpha order.
- ///
- ///
- /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
- /// as it avoids the need to create new values for modification operations.
- ///
- public partial struct Color
- {
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Color(Bgra32 color)
- {
- return new Color(color.R, color.G, color.B, color.A);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Color(Cmyk cmykColor)
- {
- float r = (1 - cmykColor.C) * (1 - cmykColor.K);
- float g = (1 - cmykColor.M) * (1 - cmykColor.K);
- float b = (1 - cmykColor.Y) * (1 - cmykColor.K);
- return new Color(r, g, b, 1);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Color(YCbCr color)
- {
- float y = color.Y;
- float cb = color.Cb - 128;
- float cr = color.Cr - 128;
-
- byte r = (byte)(y + (1.402F * cr)).Clamp(0, 255);
- byte g = (byte)(y - (0.34414F * cb) - (0.71414F * cr)).Clamp(0, 255);
- byte b = (byte)(y + (1.772F * cb)).Clamp(0, 255);
-
- 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(CieXyz color)
- {
- float x = color.X / 100F;
- float y = color.Y / 100F;
- float z = color.Z / 100F;
-
- // 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);
-
- Vector4 vector = new Vector4(r, g, b, 1).Compress();
- return new Color(vector);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Color(Hsv color)
- {
- float s = color.S;
- float v = color.V;
-
- if (Math.Abs(s) < Constants.Epsilon)
- {
- return new Color(v, v, v, 1);
- }
-
- float h = (Math.Abs(color.H - 360) < Constants.Epsilon) ? 0 : color.H / 60;
- int i = (int)Math.Truncate(h);
- float f = h - i;
-
- float p = v * (1.0F - s);
- float q = v * (1.0F - (s * f));
- float t = v * (1.0F - (s * (1.0F - f)));
-
- float r, g, b;
- switch (i)
- {
- case 0:
- r = v;
- g = t;
- b = p;
- break;
-
- case 1:
- r = q;
- g = v;
- b = p;
- break;
-
- case 2:
- r = p;
- g = v;
- b = t;
- break;
-
- case 3:
- r = p;
- g = q;
- b = v;
- break;
-
- case 4:
- r = t;
- g = p;
- b = v;
- break;
-
- default:
- r = v;
- g = p;
- b = q;
- break;
- }
-
- return new Color(r, g, b, 1);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Color(Hsl color)
- {
- float rangedH = color.H / 360F;
- float r = 0;
- float g = 0;
- float b = 0;
- float s = color.S;
- float l = color.L;
-
- if (Math.Abs(l) > Constants.Epsilon)
- {
- if (Math.Abs(s) < Constants.Epsilon)
- {
- r = g = b = l;
- }
- else
- {
- float temp2 = (l < 0.5f) ? l * (1f + s) : l + s - (l * s);
- float temp1 = (2f * l) - temp2;
-
- r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F);
- g = GetColorComponent(temp1, temp2, rangedH);
- b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F);
- }
- }
-
- return new Color(r, g, b, 1);
- }
-
- ///
- /// 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 - 0.137931F) / 7.787F;
- y = (cieLabColor.L > 7.999625F) ? y3 : (cieLabColor.L / 903.3F);
- z = (z3 > 0.008856F) ? z3 : (z - 0.137931F) / 7.787F;
-
- x *= 0.95047F;
- 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 new Color(new Vector4(r, g, b, 1F).Compress());
- }
-
- ///
- /// Gets the color component from the given values.
- ///
- /// The first value.
- /// The second value.
- /// The third value.
- ///
- /// The .
- ///
- private static float GetColorComponent(float first, float second, float third)
- {
- third = MoveIntoRange(third);
- if (third < 0.1666667F)
- {
- return first + ((second - first) * 6.0f * third);
- }
-
- if (third < 0.5)
- {
- return second;
- }
-
- if (third < 0.6666667F)
- {
- return first + ((second - first) * (0.6666667F - third) * 6.0f);
- }
-
- return first;
- }
-
- ///
- /// Moves the specific value within the acceptable range for
- /// conversion.
- /// Used for converting colors to this type.
- ///
- /// The value to shift.
- ///
- /// The .
- ///
- private static float MoveIntoRange(float value)
- {
- if (value < 0.0)
- {
- value += 1.0f;
- }
- else if (value > 1.0)
- {
- value -= 1.0f;
- }
-
- return value;
- }
- }
-}
diff --git a/src/ImageSharp/Colors/Spaces/Bgra32.cs b/src/ImageSharp/Colors/Spaces/Bgra32.cs
deleted file mode 100644
index cbd1d61194..0000000000
--- a/src/ImageSharp/Colors/Spaces/Bgra32.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-//
-// 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 BGRA (blue, green, red, alpha) color.
- ///
- public struct Bgra32 : IEquatable
- {
- ///
- /// Represents a 32 bit that has B, G, R, and A values set to zero.
- ///
- public static readonly Bgra32 Empty = default(Bgra32);
-
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector4 VectorMin = Vector4.Zero;
-
- ///
- /// Max range used for clamping
- ///
- private static readonly Vector4 VectorMax = new Vector4(255);
-
- ///
- /// The backing vector for SIMD support.
- ///
- private readonly Vector4 backingVector;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The blue component of this .
- /// The green component of this .
- /// The red component of this .
- /// The alpha component of this .
- public Bgra32(byte b, byte g, byte r, byte a = 255)
- : this()
- {
- this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), VectorMin, VectorMax);
- }
-
- ///
- /// Gets the blue component of the color
- ///
- public byte B => (byte)this.backingVector.X;
-
- ///
- /// Gets the green component of the color
- ///
- public byte G => (byte)this.backingVector.Y;
-
- ///
- /// Gets the red component of the color
- ///
- public byte R => (byte)this.backingVector.Z;
-
- ///
- /// Gets the alpha component of the color
- ///
- public byte A => (byte)this.backingVector.W;
-
- ///
- /// Gets the integer representation of the color.
- ///
- public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24);
-
- ///
- /// Gets a value indicating whether this is empty.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool IsEmpty => this.Equals(Empty);
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- ///
- /// The instance of to convert.
- ///
- ///
- /// An instance of .
- ///
- public static implicit operator Bgra32(Color color)
- {
- return new Bgra32(color.B, color.G, color.R, color.A);
- }
-
- ///
- /// 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 ==(Bgra32 left, Bgra32 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 !=(Bgra32 left, Bgra32 right)
- {
- return !left.Equals(right);
- }
-
- ///
- public override bool Equals(object obj)
- {
- if (obj is Bgra32)
- {
- Bgra32 color = (Bgra32)obj;
-
- return this.backingVector == color.backingVector;
- }
-
- return false;
- }
-
- ///
- public override int GetHashCode()
- {
- return this.backingVector.GetHashCode();
- }
-
- ///
- public override string ToString()
- {
- if (this.IsEmpty)
- {
- return "Bgra32 [ Empty ]";
- }
-
- return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]";
- }
-
- ///
- public bool Equals(Bgra32 other)
- {
- return this.backingVector.Equals(other.backingVector);
- }
- }
-}
diff --git a/src/ImageSharp/Colors/Spaces/CieLab.cs b/src/ImageSharp/Colors/Spaces/CieLab.cs
index ecc1bca5ad..e5edfcc793 100644
--- a/src/ImageSharp/Colors/Spaces/CieLab.cs
+++ b/src/ImageSharp/Colors/Spaces/CieLab.cs
@@ -13,22 +13,18 @@ namespace ImageSharp.Colors.Spaces
/// Represents an CIE LAB 1976 color.
///
///
- public struct CieLab : IEquatable, IAlmostEquatable
+ public struct CieLab : IColorVector, IEquatable, IAlmostEquatable
{
///
- /// Represents a that has L, A, B values set to zero.
- ///
- public static readonly CieLab Empty = default(CieLab);
-
- ///
- /// Min range used for clamping
+ /// D50 standard illuminant.
+ /// Used when reference white is not specified explicitly.
///
- private static readonly Vector3 VectorMin = new Vector3(0, -100, -100);
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D50;
///
- /// Max range used for clamping
+ /// Represents a that has L, A, B values set to zero.
///
- private static readonly Vector3 VectorMax = new Vector3(100);
+ public static readonly CieLab Empty = default(CieLab);
///
/// The backing vector for SIMD support.
@@ -41,12 +37,48 @@ namespace ImageSharp.Colors.Spaces
/// The lightness dimension.
/// The a (green - magenta) component.
/// The b (blue - yellow) component.
+ /// Uses as white point.
public CieLab(float l, float a, float b)
+ : this(new Vector3(l, a, b), DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ /// The reference white point.
+ public CieLab(float l, float a, float b, CieXyz whitePoint)
+ : this(new Vector3(l, a, b), whitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, a, b components.
+ /// Uses as white point.
+ public CieLab(Vector3 vector)
+ : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l a b components.
+ /// The reference white point.
+ public CieLab(Vector3 vector, CieXyz whitePoint)
: this()
{
- this.backingVector = Vector3.Clamp(new Vector3(l, a, b), VectorMin, VectorMax);
+ this.backingVector = vector;
+ this.WhitePoint = whitePoint;
}
+ public CieXyz WhitePoint { get; }
+
///
/// Gets the lightness dimension.
/// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
@@ -55,13 +87,13 @@ namespace ImageSharp.Colors.Spaces
///
/// Gets the a color component.
- /// Negative is green, positive magenta.
+ /// A value ranging from -100 to 100. Negative is green, positive magenta.
///
public float A => this.backingVector.Y;
///
/// Gets the b color component.
- /// Negative is blue, positive is yellow
+ /// A value ranging from -100 to 100. Negative is blue, positive is yellow
///
public float B => this.backingVector.Z;
@@ -71,40 +103,8 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
- ///
- /// 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
- Vector4 vector = color.ToVector4().Expand();
- float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F);
- float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F);
- float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F);
-
- // Now to LAB
- x /= 0.95047F;
-
- // y /= 1F;
- z /= 1.08883F;
-
- x = x > 0.008856F ? (float)Math.Pow(x, 0.3333333F) : ((903.3F * x) + 16F) / 116F;
- y = y > 0.008856F ? (float)Math.Pow(y, 0.3333333F) : ((903.3F * y) + 16F) / 116F;
- z = z > 0.008856F ? (float)Math.Pow(z, 0.3333333F) : ((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);
- }
+ ///
+ public Vector3 Vector => this.backingVector;
///
/// Compares two objects for equality.
@@ -184,4 +184,4 @@ namespace ImageSharp.Colors.Spaces
&& 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 5bd1eac634..2e4a73e2da 100644
--- a/src/ImageSharp/Colors/Spaces/CieXyz.cs
+++ b/src/ImageSharp/Colors/Spaces/CieXyz.cs
@@ -13,7 +13,7 @@ namespace ImageSharp.Colors.Spaces
/// Represents an CIE 1931 color
///
///
- public struct CieXyz : IEquatable, IAlmostEquatable
+ public struct CieXyz : IColorVector, IEquatable, IAlmostEquatable
{
///
/// Represents a that has Y, Cb, and Cr values set to zero.
@@ -32,27 +32,36 @@ namespace ImageSharp.Colors.Spaces
/// The y luminance component.
/// Z is quasi-equal to blue stimulation, or the S cone of the human eye.
public CieXyz(float x, float y, float z)
+ : this(new Vector3(x, y, z))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, z components.
+ public CieXyz(Vector3 vector)
: this()
{
// Not clamping as documentation about this space seems to indicate "usual" ranges
- this.backingVector = new Vector3(x, y, z);
+ this.backingVector = vector;
}
///
/// Gets the Y luminance component.
- /// A value ranging between 380 and 780.
+ /// A value usually ranging between 0 and 1.
///
public float X => this.backingVector.X;
///
/// Gets the Cb chroma component.
- /// A value ranging between 380 and 780.
+ /// A value usually ranging between 0 and 1.
///
public float Y => this.backingVector.Y;
///
/// Gets the Cr chroma component.
- /// A value ranging between 380 and 780.
+ /// A value usually ranging between 0 and 1.
///
public float Z => this.backingVector.Z;
@@ -62,30 +71,8 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
- ///
- /// 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)
- {
- Vector4 vector = color.ToVector4().Expand();
-
- float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F);
- float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F);
- float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F);
-
- x *= 100F;
- y *= 100F;
- z *= 100F;
-
- return new CieXyz(x, y, z);
- }
+ ///
+ public Vector3 Vector => this.backingVector;
///
/// Compares two objects for equality.
@@ -165,4 +152,4 @@ namespace ImageSharp.Colors.Spaces
&& result.Z <= precision;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Cmyk.cs b/src/ImageSharp/Colors/Spaces/Cmyk.cs
deleted file mode 100644
index 53618312c7..0000000000
--- a/src/ImageSharp/Colors/Spaces/Cmyk.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-//
-// 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 CMYK (cyan, magenta, yellow, keyline) color.
- ///
- public struct Cmyk : IEquatable, IAlmostEquatable
- {
- ///
- /// Represents a that has C, M, Y, and K values set to zero.
- ///
- public static readonly Cmyk Empty = default(Cmyk);
-
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector4 VectorMin = Vector4.Zero;
-
- ///
- /// Max range used for clamping
- ///
- private static readonly Vector4 VectorMax = Vector4.One;
-
- ///
- /// The backing vector for SIMD support.
- ///
- private readonly Vector4 backingVector;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The cyan component.
- /// The magenta component.
- /// The yellow component.
- /// The keyline black component.
- public Cmyk(float c, float m, float y, float k)
- : this()
- {
- this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), VectorMin, VectorMax);
- }
-
- ///
- /// Gets the cyan color component.
- /// A value ranging between 0 and 1.
- ///
- public float C => this.backingVector.X;
-
- ///
- /// Gets the magenta color component.
- /// A value ranging between 0 and 1.
- ///
- public float M => this.backingVector.Y;
-
- ///
- /// Gets the yellow color component.
- /// A value ranging between 0 and 1.
- ///
- public float Y => this.backingVector.Z;
-
- ///
- /// Gets the keyline black color component.
- /// A value ranging between 0 and 1.
- ///
- public float K => this.backingVector.W;
-
- ///
- /// Gets a value indicating whether this is empty.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool IsEmpty => this.Equals(Empty);
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- ///
- /// The instance of to convert.
- ///
- ///
- /// An instance of .
- ///
- public static implicit operator Cmyk(Color color)
- {
- float c = 1f - (color.R / 255F);
- float m = 1f - (color.G / 255F);
- float y = 1f - (color.B / 255F);
-
- float k = Math.Min(c, Math.Min(m, y));
-
- if (Math.Abs(k - 1.0f) <= Constants.Epsilon)
- {
- return new Cmyk(0, 0, 0, 1);
- }
-
- c = (c - k) / (1 - k);
- m = (m - k) / (1 - k);
- y = (y - k) / (1 - k);
-
- return new Cmyk(c, m, y, k);
- }
-
- ///
- /// 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 ==(Cmyk left, Cmyk 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 !=(Cmyk left, Cmyk right)
- {
- return !left.Equals(right);
- }
-
- ///
- public override int GetHashCode()
- {
- return this.backingVector.GetHashCode();
- }
-
- ///
- public override string ToString()
- {
- if (this.IsEmpty)
- {
- return "Cmyk [Empty]";
- }
-
- return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]";
- }
-
- ///
- public override bool Equals(object obj)
- {
- if (obj is Cmyk)
- {
- return this.Equals((Cmyk)obj);
- }
-
- return false;
- }
-
- ///
- public bool Equals(Cmyk other)
- {
- return this.backingVector.Equals(other.backingVector);
- }
-
- ///
- public bool AlmostEquals(Cmyk other, float precision)
- {
- Vector4 result = Vector4.Abs(this.backingVector - other.backingVector);
-
- return result.X <= precision
- && result.Y <= precision
- && result.Z <= precision
- && result.W <= precision;
- }
- }
-}
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/CieConstants.cs b/src/ImageSharp/Colors/Spaces/Conversion/CieConstants.cs
new file mode 100644
index 0000000000..2134ea214d
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/CieConstants.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ ///
+ /// Constants use for Cie conversion calculations
+ ///
+ ///
+ internal static class CieConstants
+ {
+ ///
+ /// 216F / 24389F
+ ///
+ public const float Epsilon = 0.008856452F;
+
+ ///
+ /// 24389F / 27F
+ ///
+ public const float Kappa = 903.2963F;
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Adapt.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Adapt.cs
new file mode 100644
index 0000000000..fc768d3ea1
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Adapt.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using System;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ ///
+ /// Performs chromatic adaptation of given XYZ color.
+ /// Target white point is .
+ ///
+ public CieXyz Adapt(CieXyz color, CieXyz sourceWhitePoint)
+ {
+ Guard.NotNull(color, nameof(color));
+ Guard.NotNull(sourceWhitePoint, nameof(sourceWhitePoint));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ return this.ChromaticAdaptation.Transform(color, sourceWhitePoint, this.WhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieLab.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieLab.cs
new file mode 100644
index 0000000000..8ccefc5326
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieLab.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Adaptation
+ CieXyz adapted = !this.WhitePoint.Equals(this.TargetLabWhitePoint) && this.IsChromaticAdaptationPerformed
+ ? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetLabWhitePoint)
+ : color;
+
+ // Conversion
+ CieXyzToCieLabConverter converter = new CieXyzToCieLabConverter(this.TargetLabWhitePoint);
+ return converter.Convert(adapted);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieXyz.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieXyz.cs
new file mode 100644
index 0000000000..66f3b25e77
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieXyz.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ private static readonly CieLabToCieXyzConverter CieLabToCieXyzConverter = new CieLabToCieXyzConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+
+ CieXyz unadapted = CieLabToCieXyzConverter.Convert(color);
+
+ // Adaptation
+ CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
+ ? unadapted
+ : this.Adapt(unadapted, color.WhitePoint);
+
+ return adapted;
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return this.cachedCieXyzAndLmsConverter.Convert(color);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Lms.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Lms.cs
new file mode 100644
index 0000000000..12d4ca9432
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Lms.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return this.cachedCieXyzAndLmsConverter.Convert(color);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.cs
new file mode 100644
index 0000000000..93d04e7e19
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.cs
@@ -0,0 +1,81 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using System.Numerics;
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ public partial class ColorConverter
+ {
+ private Matrix4x4 transformationMatrix;
+ private CieXyzAndLmsConverter cachedCieXyzAndLmsConverter;
+
+ ///
+ /// The default whitepoint used for converting to CieLab
+ ///
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ColorConverter()
+ {
+ // Note the order here this is important.
+ this.WhitePoint = DefaultWhitePoint;
+ this.LmsAdaptationMatrix = CieXyzAndLmsConverter.DefaultTransformationMatrix;
+ this.ChromaticAdaptation = new VonKriesChromaticAdaptation(this.cachedCieXyzAndLmsConverter, this.cachedCieXyzAndLmsConverter);
+ this.TargetLabWhitePoint = CieLab.DefaultWhitePoint;
+ }
+
+ ///
+ /// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space.
+ /// When null, no adaptation will be performed.
+ ///
+ public CieXyz WhitePoint { get; set; }
+
+ ///
+ /// Gets or sets the white point used *when creating* Lab/LChab colors. (Lab/LChab colors on the input already contain the white point information)
+ /// Defaults to: .
+ ///
+ public CieXyz TargetLabWhitePoint { get; set; }
+
+ ///
+ /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed.
+ ///
+ public IChromaticAdaptation ChromaticAdaptation { get; set; }
+
+ ///
+ /// Gets or sets transformation matrix used in conversion to ,
+ /// also used in the default Von Kries Chromatic Adaptation method.
+ ///
+ public Matrix4x4 LmsAdaptationMatrix
+ {
+ get { return this.transformationMatrix; }
+ set
+ {
+ this.transformationMatrix = value;
+
+ if (this.cachedCieXyzAndLmsConverter == null)
+ {
+ this.cachedCieXyzAndLmsConverter = new CieXyzAndLmsConverter(value);
+ }
+ else
+ {
+ this.cachedCieXyzAndLmsConverter.TransformationMatrix = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether chromatic adaptation has been performed.
+ ///
+ private bool IsChromaticAdaptationPerformed => this.ChromaticAdaptation != null;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/IChromaticAdaptation.cs b/src/ImageSharp/Colors/Spaces/Conversion/IChromaticAdaptation.cs
new file mode 100644
index 0000000000..d97d1bd1c6
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/IChromaticAdaptation.cs
@@ -0,0 +1,27 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using Spaces;
+
+ ///
+ /// Chromatic adaptation.
+ /// A linear transformation of a source color (XS, YS, ZS) into a destination color (XD, YD, ZD) by a linear transformation [M]
+ /// which is dependent on the source reference white (XWS, YWS, ZWS) and the destination reference white (XWD, YWD, ZWD).
+ ///
+ public interface IChromaticAdaptation
+ {
+ ///
+ /// Performs a linear transformation of a source color in to the destination color.
+ ///
+ /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).
+ /// The source color.
+ /// The source white point.
+ /// The target white point.
+ /// The
+ CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/IColorConversion.cs b/src/ImageSharp/Colors/Spaces/Conversion/IColorConversion.cs
new file mode 100644
index 0000000000..ad653da947
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/IColorConversion.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ ///
+ /// Converts color between two color spaces.
+ ///
+ /// The input color type.
+ /// The result color type.
+ public interface IColorConversion
+ {
+ ///
+ /// Performs the conversion from the input to an instance of the output type.
+ ///
+ /// The input color instance.
+ /// The
+ TResult Convert(T input);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
new file mode 100644
index 0000000000..1d9ab6269d
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion.Implementation
+{
+ using System;
+ using Spaces;
+
+ ///
+ /// Converts from to .
+ ///
+ public class CieLabToCieXyzConverter : IColorConversion
+ {
+ ///
+ public CieXyz Convert(CieLab input)
+ {
+ Guard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
+ float l = input.L, a = input.A, b = input.B;
+ float fy = (l + 16) / 116F;
+ float fx = a / 500F + fy;
+ float fz = fy - b / 200F;
+
+ float fx3 = (float)Math.Pow(fx, 3D);
+ float fz3 = (float)Math.Pow(fz, 3D);
+
+ float xr = fx3 > CieConstants.Epsilon ? fx3 : (116F * fx - 16F) / CieConstants.Kappa;
+ float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? (float)Math.Pow((l + 16F) / 116F, 3D) : l / CieConstants.Kappa;
+ float zr = fz3 > CieConstants.Epsilon ? fz3 : (116F * fz - 16F) / CieConstants.Kappa;
+
+ float wx = input.WhitePoint.X, wy = input.WhitePoint.Y, wz = input.WhitePoint.Z;
+
+ // Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white)
+ xr = xr.Clamp(0, 1F);
+ yr = yr.Clamp(0, 1F);
+ zr = zr.Clamp(0, 1F);
+
+ float x = xr * wx;
+ float y = yr * wy;
+ float z = zr * wz;
+
+ return new CieXyz(x, y, z);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
new file mode 100644
index 0000000000..ddd7c4bb2e
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion.Implementation
+{
+ using System;
+ using Spaces;
+
+ ///
+ /// Converts from to .
+ ///
+ public class CieXyzToCieLabConverter : IColorConversion
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CieXyzToCieLabConverter()
+ : this(CieLab.DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The target reference lab white point
+ public CieXyzToCieLabConverter(CieXyz labWhitePoint)
+ {
+ this.LabWhitePoint = labWhitePoint;
+ }
+
+ ///
+ /// Gets the target reference whitepoint. When not set, is used.
+ ///
+ public CieXyz LabWhitePoint { get; }
+
+ ///
+ public CieLab Convert(CieXyz input)
+ {
+ Guard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
+ float wx = this.LabWhitePoint.X, wy = this.LabWhitePoint.Y, wz = this.LabWhitePoint.Z;
+
+ float xr = input.X / wx, yr = input.Y / wy, zr = input.Z / wz;
+
+ float fx = xr > CieConstants.Epsilon ? (float)Math.Pow(xr, 0.333333333333333D) : (CieConstants.Kappa * xr + 16F) / 116F;
+ float fy = yr > CieConstants.Epsilon ? (float)Math.Pow(yr, 0.333333333333333D) : (CieConstants.Kappa * yr + 16F) / 116F;
+ float fz = zr > CieConstants.Epsilon ? (float)Math.Pow(zr, 0.333333333333333D) : (CieConstants.Kappa * zr + 16F) / 116F;
+
+ float l = (116F * fy) - 16F;
+ float a = 500F * (fx - fy);
+ float b = 200F * (fy - fz);
+
+ return new CieLab(l, a, b, this.LabWhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
new file mode 100644
index 0000000000..69899e0dbd
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+using System.Numerics;
+
+namespace ImageSharp.Colors.Conversion.Implementation
+{
+ using Spaces;
+
+ public class CieXyzAndLmsConverter : IColorConversion, IColorConversion
+ {
+ ///
+ /// Default transformation matrix used, when no other is set. (Bradford)
+ ///
+ ///
+ public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford;
+
+ private Matrix4x4 inverseTransformationMatrix;
+ private Matrix4x4 transformationMatrix;
+
+ ///
+ /// Transformation matrix used for the conversion (definition of the cone response domain).
+ ///
+ ///
+ public Matrix4x4 TransformationMatrix
+ {
+ get { return this.transformationMatrix; }
+ internal set
+ {
+ this.transformationMatrix = value;
+ Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CieXyzAndLmsConverter()
+ : this(DefaultTransformationMatrix)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Definition of the cone response domain (see ),
+ /// if not set will be used.
+ ///
+ public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix)
+ {
+ this.TransformationMatrix = transformationMatrix;
+ }
+
+ ///
+ public Lms Convert(CieXyz input)
+ {
+ Guard.NotNull(input, nameof(input));
+
+ Vector3 vector = Vector3.Transform(input.Vector, this.transformationMatrix);
+ return new Lms(vector);
+ }
+
+ ///
+ public CieXyz Convert(Lms input)
+ {
+ Vector3 vector = Vector3.Transform(input.Vector, this.inverseTransformationMatrix);
+ return new CieXyz(vector);
+ }
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
new file mode 100644
index 0000000000..fe3ded527a
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
@@ -0,0 +1,101 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.Colors.Conversion.Implementation
+{
+ using System.Numerics;
+
+ ///
+ /// AdaptionMatrix3X3 used for transformation from XYZ to LMS, defining the cone response domain.
+ /// Used in
+ ///
+ ///
+ /// AdaptionMatrix3X3 data obtained from:
+ /// Two New von Kries Based Chromatic Adaptation Transforms Found by Numerical Optimization
+ /// S. Bianco, R. Schettini
+ /// DISCo, Department of Informatics, Systems and Communication, University of Milan-Bicocca, viale Sarca 336, 20126 Milan, Italy
+ /// http://www.ivl.disco.unimib.it/papers2003/CRA-CAT.pdf
+ ///
+ public static class LmsAdaptationMatrix
+ {
+ ///
+ /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65)
+ ///
+ public static readonly Matrix4x4 VonKriesHPEAdjusted
+ = new Matrix4x4()
+ {
+ M11 = 0.40024F, M12 = 0.7076F, M13 = -0.08081F,
+ M21 = -0.2263F, M22 = 1.16532F, M23 = 0.0457F,
+ M31 = 0, M32 = 0, M33 = 0.91822F,
+ M44 = 1F // Important for inverse transforms.
+ };
+
+ ///
+ /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy)
+ ///
+ public static readonly Matrix4x4 VonKriesHPE
+ = new Matrix4x4
+ {
+ M11 = 0.3897F, M12 = 0.6890F, M13 = -0.0787F,
+ M21 = -0.2298F, M22 = 1.1834F, M23 = 0.0464F,
+ M31 = 0, M32 = 0, M33 = 1F,
+ M44 = 1F
+ };
+
+ ///
+ /// XYZ scaling chromatic adaptation transform matrix
+ ///
+ public static readonly Matrix4x4 XYZScaling = Matrix4x4.Identity;
+
+ ///
+ /// Bradford chromatic adaptation transform matrix (used in CMCCAT97)
+ ///
+ public static readonly Matrix4x4 Bradford
+ = new Matrix4x4
+ {
+ M11 = 0.8951F, M12 = 0.2664F, M13 = -0.1614F,
+ M21 = -0.7502F, M22 = 1.7135F, M23 = 0.0367F,
+ M31 = 0.0389F, M32 = -0.0685F, M33 = 1.0296F,
+ M44 = 1F
+ };
+
+ ///
+ /// Spectral sharpening and the Bradford transform
+ ///
+ public static readonly Matrix4x4 BradfordSharp
+ = new Matrix4x4
+ {
+ M11 = 1.2694F, M12 = -0.0988F, M13 = -0.1706F,
+ M21 = -0.8364F, M22 = 1.8006F, M23 = 0.0357F,
+ M31 = 0.0297F, M32 = -0.0315F, M33 = 1.0018F,
+ M44 = 1F
+ };
+
+ ///
+ /// CMCCAT2000 (fitted from all available color data sets)
+ ///
+ public static readonly Matrix4x4 CMCCAT2000
+ = new Matrix4x4
+ {
+ M11 = 0.7982F, M12 = 0.3389F, M13 = -0.1371F,
+ M21 = -0.5918F, M22 = 1.5512F, M23 = 0.0406F,
+ M31 = 0.0008F, M32 = 0.239F, M33 = 0.9753F,
+ M44 = 1F
+ };
+
+ ///
+ /// CAT02 (optimized for minimizing CIELAB differences)
+ ///
+ public static readonly Matrix4x4 CAT02
+ = new Matrix4x4
+ {
+ M11 = 0.7328F, M12 = 0.4296F, M13 = -0.1624F,
+ M21 = -0.7036F, M22 = 1.6975F, M23 = 0.0061F,
+ M31 = 0.0030F, M32 = 0.0136F, M33 = 0.9834F,
+ M44 = 1F
+ };
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/Conversion/VonKriesChromaticAdaptation.cs b/src/ImageSharp/Colors/Spaces/Conversion/VonKriesChromaticAdaptation.cs
new file mode 100644
index 0000000000..a5b92d06db
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Conversion/VonKriesChromaticAdaptation.cs
@@ -0,0 +1,91 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Conversion
+{
+ using System.Numerics;
+
+ using Implementation;
+ using Spaces;
+
+ ///
+ /// Basic implementation of the von Kries chromatic adaptation model
+ ///
+ ///
+ /// Transformation described here:
+ /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ ///
+ public class VonKriesChromaticAdaptation : IChromaticAdaptation
+ {
+ private readonly IColorConversion conversionToLms;
+
+ private readonly IColorConversion conversionToXyz;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public VonKriesChromaticAdaptation()
+ : this(new CieXyzAndLmsConverter())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The transformation matrix used for the conversion (definition of the cone response domain).
+ ///
+ ///
+ public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix)
+ : this(new CieXyzAndLmsConverter(transformationMatrix))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ private VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter)
+ : this(converter, converter)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The color converter.
+ /// The color converter.
+ public VonKriesChromaticAdaptation(IColorConversion conversionToLms, IColorConversion conversionToCieXyz)
+ {
+ Guard.NotNull(conversionToLms, nameof(conversionToLms));
+ Guard.NotNull(conversionToCieXyz, nameof(conversionToCieXyz));
+
+ this.conversionToLms = conversionToLms;
+ this.conversionToXyz = conversionToCieXyz;
+ }
+
+ ///
+ public CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint)
+ {
+ Guard.NotNull(sourceColor, nameof(sourceColor));
+ Guard.NotNull(sourceWhitePoint, nameof(sourceWhitePoint));
+ Guard.NotNull(targetWhitePoint, nameof(targetWhitePoint));
+
+ if (sourceWhitePoint.Equals(targetWhitePoint))
+ {
+ return sourceColor;
+ }
+
+ Lms sourceColorLms = this.conversionToLms.Convert(sourceColor);
+ Lms sourceWhitePointLms = this.conversionToLms.Convert(sourceWhitePoint);
+ Lms targetWhitePointLms = this.conversionToLms.Convert(targetWhitePoint);
+
+ Vector3 vector = new Vector3(targetWhitePointLms.L / sourceWhitePointLms.L, targetWhitePointLms.M / sourceWhitePointLms.M, targetWhitePointLms.S / sourceWhitePointLms.S);
+
+ Lms targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.Vector));
+ return this.conversionToXyz.Convert(targetColorLms);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Hsl.cs b/src/ImageSharp/Colors/Spaces/Hsl.cs
deleted file mode 100644
index 66f4a52bcc..0000000000
--- a/src/ImageSharp/Colors/Spaces/Hsl.cs
+++ /dev/null
@@ -1,207 +0,0 @@
-//
-// 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 a Hsl (hue, saturation, lightness) color.
- ///
- public struct Hsl : IEquatable, IAlmostEquatable
- {
- ///
- /// Represents a that has H, S, and L values set to zero.
- ///
- public static readonly Hsl Empty = default(Hsl);
-
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector3 VectorMin = Vector3.Zero;
-
- ///
- /// Max range used for clamping
- ///
- private static readonly Vector3 VectorMax = new Vector3(360, 1, 1);
-
- ///
- /// The backing vector for SIMD support.
- ///
- private readonly Vector3 backingVector;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The h hue component.
- /// The s saturation component.
- /// The l value (lightness) component.
- public Hsl(float h, float s, float l)
- {
- this.backingVector = Vector3.Clamp(new Vector3(h, s, l), VectorMin, VectorMax);
- }
-
- ///
- /// Gets the hue component.
- /// A value ranging between 0 and 360.
- ///
- public float H => this.backingVector.X;
-
- ///
- /// Gets the saturation component.
- /// A value ranging between 0 and 1.
- ///
- public float S => this.backingVector.Y;
-
- ///
- /// Gets the lightness component.
- /// A value ranging between 0 and 1.
- ///
- public float L => this.backingVector.Z;
-
- ///
- /// Gets a value indicating whether this is empty.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool IsEmpty => this.Equals(Empty);
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Hsl(Color color)
- {
- float r = color.R / 255F;
- float g = color.G / 255F;
- float b = color.B / 255F;
-
- float max = Math.Max(r, Math.Max(g, b));
- float min = Math.Min(r, Math.Min(g, b));
- float chroma = max - min;
- float h = 0;
- float s = 0;
- float l = (max + min) / 2;
-
- if (Math.Abs(chroma) < Constants.Epsilon)
- {
- return new Hsl(0, s, l);
- }
-
- if (Math.Abs(r - max) < Constants.Epsilon)
- {
- h = (g - b) / chroma;
- }
- else if (Math.Abs(g - max) < Constants.Epsilon)
- {
- h = 2 + ((b - r) / chroma);
- }
- else if (Math.Abs(b - max) < Constants.Epsilon)
- {
- h = 4 + ((r - g) / chroma);
- }
-
- h *= 60;
- if (h < 0.0)
- {
- h += 360;
- }
-
- if (l <= .5f)
- {
- s = chroma / (max + min);
- }
- else
- {
- s = chroma / (2 - chroma);
- }
-
- return new Hsl(h, s, l);
- }
-
- ///
- /// 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 ==(Hsl left, Hsl 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 !=(Hsl left, Hsl right)
- {
- return !left.Equals(right);
- }
-
- ///
- public override int GetHashCode()
- {
- return this.backingVector.GetHashCode();
- }
-
- ///
- public override string ToString()
- {
- if (this.IsEmpty)
- {
- return "Hsl [ Empty ]";
- }
-
- return $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={this.L:#0.##} ]";
- }
-
- ///
- public override bool Equals(object obj)
- {
- if (obj is Hsl)
- {
- return this.Equals((Hsl)obj);
- }
-
- return false;
- }
-
- ///
- public bool Equals(Hsl other)
- {
- return this.backingVector.Equals(other.backingVector);
- }
-
- ///
- public bool AlmostEquals(Hsl other, float precision)
- {
- Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
-
- return result.X <= precision
- && result.Y <= precision
- && result.Z <= precision;
- }
- }
-}
diff --git a/src/ImageSharp/Colors/Spaces/Hsv.cs b/src/ImageSharp/Colors/Spaces/Hsv.cs
deleted file mode 100644
index b34977e2d9..0000000000
--- a/src/ImageSharp/Colors/Spaces/Hsv.cs
+++ /dev/null
@@ -1,200 +0,0 @@
-//
-// 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 a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
- ///
- public struct Hsv : IEquatable, IAlmostEquatable
- {
- ///
- /// Represents a that has H, S, and V values set to zero.
- ///
- public static readonly Hsv Empty = default(Hsv);
-
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector3 VectorMin = Vector3.Zero;
-
- ///
- /// Max range used for clamping
- ///
- private static readonly Vector3 VectorMax = new Vector3(360, 1, 1);
-
- ///
- /// The backing vector for SIMD support.
- ///
- private readonly Vector3 backingVector;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The h hue component.
- /// The s saturation component.
- /// The v value (brightness) component.
- public Hsv(float h, float s, float v)
- {
- this.backingVector = Vector3.Clamp(new Vector3(h, s, v), VectorMin, VectorMax);
- }
-
- ///
- /// Gets the hue component.
- /// A value ranging between 0 and 360.
- ///
- public float H => this.backingVector.X;
-
- ///
- /// Gets the saturation component.
- /// A value ranging between 0 and 1.
- ///
- public float S => this.backingVector.Y;
-
- ///
- /// Gets the value (brightness) component.
- /// A value ranging between 0 and 1.
- ///
- public float V => this.backingVector.Z;
-
- ///
- /// Gets a value indicating whether this is empty.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool IsEmpty => this.Equals(Empty);
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Hsv(Color color)
- {
- float r = color.R / 255F;
- float g = color.G / 255F;
- float b = color.B / 255F;
-
- float max = Math.Max(r, Math.Max(g, b));
- float min = Math.Min(r, Math.Min(g, b));
- float chroma = max - min;
- float h = 0;
- float s = 0;
- float v = max;
-
- if (Math.Abs(chroma) < Constants.Epsilon)
- {
- return new Hsv(0, s, v);
- }
-
- if (Math.Abs(r - max) < Constants.Epsilon)
- {
- h = (g - b) / chroma;
- }
- else if (Math.Abs(g - max) < Constants.Epsilon)
- {
- h = 2 + ((b - r) / chroma);
- }
- else if (Math.Abs(b - max) < Constants.Epsilon)
- {
- h = 4 + ((r - g) / chroma);
- }
-
- h *= 60;
- if (h < 0.0)
- {
- h += 360;
- }
-
- s = chroma / v;
-
- return new Hsv(h, s, v);
- }
-
- ///
- /// 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 ==(Hsv left, Hsv 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 !=(Hsv left, Hsv right)
- {
- return !left.Equals(right);
- }
-
- ///
- public override int GetHashCode()
- {
- return this.backingVector.GetHashCode();
- }
-
- ///
- public override string ToString()
- {
- if (this.IsEmpty)
- {
- return "Hsv [ Empty ]";
- }
-
- return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]";
- }
-
- ///
- public override bool Equals(object obj)
- {
- if (obj is Hsv)
- {
- return this.Equals((Hsv)obj);
- }
-
- return false;
- }
-
- ///
- public bool Equals(Hsv other)
- {
- return this.backingVector.Equals(other.backingVector);
- }
-
- ///
- public bool AlmostEquals(Hsv other, float precision)
- {
- Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
-
- return result.X <= precision
- && result.Y <= precision
- && result.Z <= precision;
- }
- }
-}
diff --git a/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs b/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs
index a2183d396c..dc3dc57f0b 100644
--- a/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs
+++ b/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs
@@ -27,4 +27,4 @@ namespace ImageSharp.Colors.Spaces
///
bool AlmostEquals(TColor other, TPrecision precision);
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/IColorVector.cs b/src/ImageSharp/Colors/Spaces/IColorVector.cs
new file mode 100644
index 0000000000..10ad9d30f4
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/IColorVector.cs
@@ -0,0 +1,20 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ using System.Numerics;
+
+ ///
+ /// Color represented as a vector in its color space
+ ///
+ public interface IColorVector
+ {
+ ///
+ /// The vector representation of the color
+ ///
+ Vector3 Vector { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/ICompanding.cs b/src/ImageSharp/Colors/Spaces/ICompanding.cs
new file mode 100644
index 0000000000..f20946b4fb
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/ICompanding.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ ///
+ /// Pair of companding functions for .
+ /// Used for conversion to and backwards.
+ /// See also:
+ ///
+ public interface ICompanding
+ {
+ ///
+ /// Companded channel is made linear with respect to the energy.
+ ///
+ ///
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ ///
+ float InverseCompanding(float channel);
+
+ ///
+ /// Uncompanded channel (linear) is made nonlinear (depends on the RGB color system).
+ ///
+ ///
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ ///
+ float Companding(float channel);
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/IRgbWorkingSpace.cs b/src/ImageSharp/Colors/Spaces/IRgbWorkingSpace.cs
new file mode 100644
index 0000000000..cc759753bf
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/IRgbWorkingSpace.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Colors.Spaces
+{
+ ///
+ /// Encasulates the RGB working color space
+ ///
+ public interface IRgbWorkingSpace
+ {
+ ///
+ /// Gets the reference white of the color space
+ ///
+ CieXyz WhitePoint { get; }
+
+ ///
+ /// Chromaticity coordinates of the primaries
+ ///
+ // RGBPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
+
+ ///
+ /// The companding function associated with the RGB color system.
+ /// Used for conversion to XYZ and backwards.
+ /// See this for more information:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ ///
+ ICompanding Companding { get; }
+ }
+}
diff --git a/src/ImageSharp/Colors/Spaces/Illuminants.cs b/src/ImageSharp/Colors/Spaces/Illuminants.cs
new file mode 100644
index 0000000000..51ef4d8d95
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Illuminants.cs
@@ -0,0 +1,71 @@
+namespace ImageSharp.Colors.Spaces
+{
+ ///
+ /// The well known standard illuminants.
+ /// Standard illuminants provide a basis for comparing images or colors recorded under different lighting
+ ///
+ ///
+ /// Coefficients taken from:
+ /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ ///
+ /// Descriptions taken from:
+ /// http://en.wikipedia.org/wiki/Standard_illuminant
+ ///
+ public static class Illuminants
+ {
+ ///
+ /// Incandescent / Tungsten
+ ///
+ public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F);
+
+ ///
+ /// Direct sunlight at noon (obsoleteF)
+ ///
+ public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F);
+
+ ///
+ /// Average / North sky Daylight (obsoleteF)
+ ///
+ public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F);
+
+ ///
+ /// Horizon Light. ICC profile PCS
+ ///
+ public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F);
+
+ ///
+ /// Mid-morning / Mid-afternoon Daylight
+ ///
+ public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F);
+
+ ///
+ /// Noon Daylight: TelevisionF, sRGB color space
+ ///
+ public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F);
+
+ ///
+ /// North sky Daylight
+ ///
+ public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F);
+
+ ///
+ /// Equal energy
+ ///
+ public static readonly CieXyz E = new CieXyz(1F, 1F, 1F);
+
+ ///
+ /// Cool White Fluorescent
+ ///
+ public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F);
+
+ ///
+ /// D65 simulatorF, Daylight simulator
+ ///
+ public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F);
+
+ ///
+ /// Philips TL84F, Ultralume 40
+ ///
+ public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Lms.cs b/src/ImageSharp/Colors/Spaces/Lms.cs
new file mode 100644
index 0000000000..75ad6710b0
--- /dev/null
+++ b/src/ImageSharp/Colors/Spaces/Lms.cs
@@ -0,0 +1,156 @@
+//
+// 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;
+
+ ///
+ /// LMS is a color space represented by the response of the three types of cones of the human eye,
+ /// named after their responsivity (sensitivity) at long, medium and short wavelengths.
+ ///
+ ///
+ public struct Lms : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has Y, Cb, and Cr values set to zero.
+ ///
+ public static readonly Lms Empty = default(Lms);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// L represents the responsivity at long wavelengths.
+ /// M represents the responsivity at medium wavelengths.
+ /// S represents the responsivity at short wavelengths.
+ public Lms(float l, float m, float s)
+ : this(new Vector3(l, m, s))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, z components.
+ public Lms(Vector3 vector)
+ : this()
+ {
+ // Not clamping as documentation about this space seems to indicate "usual" ranges
+ this.backingVector = vector;
+ }
+
+ ///
+ /// Gets the L long component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float L => this.backingVector.X;
+
+ ///
+ /// Gets the M medium component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float M => this.backingVector.Y;
+
+ ///
+ /// Gets the S short component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float S => 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 ==(Lms left, Lms 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 !=(Lms left, Lms right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Lms [ Empty ]";
+ }
+
+ return $"Lms [ L={this.L:#0.##}, M={this.M:#0.##}, S={this.S:#0.##} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is Lms)
+ {
+ return this.Equals((Lms)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(Lms other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ public bool AlmostEquals(Lms 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/YCbCr.cs b/src/ImageSharp/Colors/Spaces/YCbCr.cs
deleted file mode 100644
index ef9f4462b1..0000000000
--- a/src/ImageSharp/Colors/Spaces/YCbCr.cs
+++ /dev/null
@@ -1,165 +0,0 @@
-//
-// 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 YCbCr (luminance, blue chroma, red chroma) color conforming to the full range standard used in digital imaging systems.
- ///
- ///
- public struct YCbCr : IEquatable
- {
- ///
- /// Represents a that has Y, Cb, and Cr values set to zero.
- ///
- public static readonly YCbCr Empty = default(YCbCr);
-
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector3 VectorMin = Vector3.Zero;
-
- ///
- /// Vector which is used in clamping to the max value
- ///
- private static readonly Vector3 VectorMax = new Vector3(255);
-
- ///
- /// The backing vector for SIMD support.
- ///
- private readonly Vector3 backingVector;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The y luminance component.
- /// The cb chroma component.
- /// The cr chroma component.
- public YCbCr(byte y, byte cb, byte cr)
- : this()
- {
- this.backingVector = Vector3.Clamp(new Vector3(y, cb, cr), VectorMin, VectorMax);
- }
-
- ///
- /// Gets the Y luminance component.
- /// A value ranging between 0 and 255.
- ///
- public byte Y => (byte)this.backingVector.X;
-
- ///
- /// Gets the Cb chroma component.
- /// A value ranging between 0 and 255.
- ///
- public byte Cb => (byte)this.backingVector.Y;
-
- ///
- /// Gets the Cr chroma component.
- /// A value ranging between 0 and 255.
- ///
- public byte Cr => (byte)this.backingVector.Z;
-
- ///
- /// Gets a value indicating whether this is empty.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool IsEmpty => this.Equals(Empty);
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- ///
- /// The instance of to convert.
- ///
- ///
- /// An instance of .
- ///
- public static implicit operator YCbCr(Color color)
- {
- byte r = color.R;
- byte g = color.G;
- byte b = color.B;
-
- byte y = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b));
- byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)));
- byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)));
-
- return new YCbCr(y, cb, cr);
- }
-
- ///
- /// 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 ==(YCbCr left, YCbCr 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 !=(YCbCr left, YCbCr right)
- {
- return !left.Equals(right);
- }
-
- ///
- public override int GetHashCode()
- {
- return this.backingVector.GetHashCode();
- }
-
- ///
- public override string ToString()
- {
- if (this.IsEmpty)
- {
- return "YCbCr [ Empty ]";
- }
-
- return $"YCbCr [ Y={this.Y}, Cb={this.Cb}, Cr={this.Cr} ]";
- }
-
- ///
- public override bool Equals(object obj)
- {
- if (obj is YCbCr)
- {
- return this.Equals((YCbCr)obj);
- }
-
- return false;
- }
-
- ///
- public bool Equals(YCbCr other)
- {
- return this.backingVector.Equals(other.backingVector);
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs b/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs
deleted file mode 100644
index 9ed1c67a70..0000000000
--- a/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs
+++ /dev/null
@@ -1,493 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp.Tests
-{
- using System;
- using System.Diagnostics.CodeAnalysis;
- using ImageSharp.Colors.Spaces;
- using Xunit;
-
- ///
- /// Test conversion between the various color structs.
- ///
- ///
- /// Output values have been compared with
- /// and for accuracy.
- ///
- public class ColorConversionTests
- {
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void ColorToYCbCr()
- {
- // White
- Color color = Color.White;
- YCbCr yCbCr = color;
-
- Assert.Equal(255, yCbCr.Y);
- Assert.Equal(128, yCbCr.Cb);
- Assert.Equal(128, yCbCr.Cr);
-
- // Black
- Color color2 = Color.Black;
- YCbCr yCbCr2 = color2;
- Assert.Equal(0, yCbCr2.Y);
- Assert.Equal(128, yCbCr2.Cb);
- Assert.Equal(128, yCbCr2.Cr);
-
- // Gray
- Color color3 = Color.Gray;
- YCbCr yCbCr3 = color3;
- Assert.Equal(128, yCbCr3.Y);
- Assert.Equal(128, yCbCr3.Cb);
- Assert.Equal(128, yCbCr3.Cr);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void YCbCrToColor()
- {
- // White
- YCbCr yCbCr = new YCbCr(255, 128, 128);
- Color color = yCbCr;
-
- Assert.Equal(255, color.R);
- Assert.Equal(255, color.G);
- Assert.Equal(255, color.B);
- Assert.Equal(255, color.A);
-
- // Black
- YCbCr yCbCr2 = new YCbCr(0, 128, 128);
- Color color2 = yCbCr2;
-
- Assert.Equal(0, color2.R);
- Assert.Equal(0, color2.G);
- Assert.Equal(0, color2.B);
- Assert.Equal(255, color2.A);
-
- // Gray
- YCbCr yCbCr3 = new YCbCr(128, 128, 128);
- Color color3 = yCbCr3;
-
- Assert.Equal(128, color3.R);
- Assert.Equal(128, color3.G);
- Assert.Equal(128, color3.B);
- Assert.Equal(255, color3.A);
- }
-
- ///
- /// Tests the implicit conversion from to .
- /// Comparison values obtained from
- /// http://colormine.org/convert/rgb-to-xyz
- ///
- [Fact]
- public void ColorToCieXyz()
- {
- // White
- Color color = Color.White;
- CieXyz ciexyz = color;
-
- Assert.Equal(95.05f, ciexyz.X, 3);
- Assert.Equal(100.0f, ciexyz.Y, 3);
- Assert.Equal(108.900f, ciexyz.Z, 3);
-
- // Black
- Color color2 = Color.Black;
- CieXyz ciexyz2 = color2;
- Assert.Equal(0, ciexyz2.X, 3);
- Assert.Equal(0, ciexyz2.Y, 3);
- Assert.Equal(0, ciexyz2.Z, 3);
-
- // Gray
- Color color3 = Color.Gray;
- CieXyz ciexyz3 = color3;
- Assert.Equal(20.518, ciexyz3.X, 3);
- Assert.Equal(21.586, ciexyz3.Y, 3);
- Assert.Equal(23.507, ciexyz3.Z, 3);
-
- // Cyan
- Color color4 = Color.Cyan;
- CieXyz ciexyz4 = color4;
- Assert.Equal(53.810f, ciexyz4.X, 3);
- Assert.Equal(78.740f, ciexyz4.Y, 3);
- Assert.Equal(106.970f, ciexyz4.Z, 3);
- }
-
- ///
- /// Tests the implicit conversion from to .
- /// Comparison values obtained from
- /// http://colormine.org/convert/rgb-to-xyz
- ///
- [Fact]
- public void CieXyzToColor()
- {
- // Dark moderate pink.
- CieXyz ciexyz = new CieXyz(13.337f, 9.297f, 14.727f);
- Color color = ciexyz;
-
- Assert.Equal(128, color.R);
- Assert.Equal(64, color.G);
- Assert.Equal(106, color.B);
-
- // Ochre
- CieXyz ciexyz2 = new CieXyz(31.787f, 26.147f, 4.885f);
- Color color2 = ciexyz2;
-
- Assert.Equal(204, color2.R);
- Assert.Equal(119, color2.G);
- Assert.Equal(34, color2.B);
-
- // Black
- CieXyz ciexyz3 = new CieXyz(0, 0, 0);
- Color color3 = ciexyz3;
-
- Assert.Equal(0, color3.R);
- Assert.Equal(0, color3.G);
- Assert.Equal(0, color3.B);
-
- //// Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // CieXyz ciexyz4 = color4;
- // Assert.Equal(color4, (Color)ciexyz4);
- //}
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void ColorToHsv()
- {
- // Black
- Color b = Color.Black;
- Hsv h = b;
-
- Assert.Equal(0, h.H, 1);
- Assert.Equal(0, h.S, 1);
- Assert.Equal(0, h.V, 1);
-
- // White
- Color color = Color.White;
- Hsv hsv = color;
-
- Assert.Equal(0f, hsv.H, 1);
- Assert.Equal(0f, hsv.S, 1);
- Assert.Equal(1f, hsv.V, 1);
-
- // Dark moderate pink.
- Color color2 = new Color(128, 64, 106);
- Hsv hsv2 = color2;
-
- Assert.Equal(320.6f, hsv2.H, 1);
- Assert.Equal(0.5f, hsv2.S, 1);
- Assert.Equal(0.502f, hsv2.V, 2);
-
- // Ochre.
- Color color3 = new Color(204, 119, 34);
- Hsv hsv3 = color3;
-
- Assert.Equal(30f, hsv3.H, 1);
- Assert.Equal(0.833f, hsv3.S, 3);
- Assert.Equal(0.8f, hsv3.V, 1);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- public void HsvToColor()
- {
- // Dark moderate pink.
- Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f);
- Color color = hsv;
-
- Assert.Equal(color.R, 128);
- Assert.Equal(color.G, 64);
- Assert.Equal(color.B, 106);
-
- // Ochre
- Hsv hsv2 = new Hsv(30, 0.833f, 0.8f);
- Color color2 = hsv2;
-
- Assert.Equal(color2.R, 204);
- Assert.Equal(color2.G, 119);
- Assert.Equal(color2.B, 34);
-
- // White
- Hsv hsv3 = new Hsv(0, 0, 1);
- Color color3 = hsv3;
-
- Assert.Equal(color3.B, 255);
- Assert.Equal(color3.G, 255);
- Assert.Equal(color3.R, 255);
-
- // Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // Hsv hsv4 = color4;
- // Assert.Equal(color4, (Color)hsv4);
- //}
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void ColorToHsl()
- {
- // Black
- Color b = Color.Black;
- Hsl h = b;
-
- Assert.Equal(0, h.H, 1);
- Assert.Equal(0, h.S, 1);
- Assert.Equal(0, h.L, 1);
-
- // White
- Color color = Color.White;
- Hsl hsl = color;
-
- Assert.Equal(0f, hsl.H, 1);
- Assert.Equal(0f, hsl.S, 1);
- Assert.Equal(1f, hsl.L, 1);
-
- // Dark moderate pink.
- Color color2 = new Color(128, 64, 106);
- Hsl hsl2 = color2;
-
- Assert.Equal(320.6f, hsl2.H, 1);
- Assert.Equal(0.33f, hsl2.S, 1);
- Assert.Equal(0.376f, hsl2.L, 2);
-
- // Ochre.
- Color color3 = new Color(204, 119, 34);
- Hsl hsl3 = color3;
-
- Assert.Equal(30f, hsl3.H, 1);
- Assert.Equal(0.714f, hsl3.S, 3);
- Assert.Equal(0.467f, hsl3.L, 3);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- public void HslToColor()
- {
- // Dark moderate pink.
- Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f);
- Color color = hsl;
-
- Assert.Equal(color.R, 128);
- Assert.Equal(color.G, 64);
- Assert.Equal(color.B, 106);
-
- // Ochre
- Hsl hsl2 = new Hsl(30, 0.714f, 0.467f);
- Color color2 = hsl2;
-
- Assert.Equal(color2.R, 204);
- Assert.Equal(color2.G, 119);
- Assert.Equal(color2.B, 34);
-
- // White
- Hsl hsl3 = new Hsl(0, 0, 1);
- Color color3 = hsl3;
-
- Assert.Equal(color3.R, 255);
- Assert.Equal(color3.G, 255);
- Assert.Equal(color3.B, 255);
-
- // Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // Hsl hsl4 = color4;
- // Assert.Equal(color4, (Color)hsl4);
- //}
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void ColorToCmyk()
- {
- // White
- Color color = Color.White;
- Cmyk cmyk = color;
-
- Assert.Equal(0, cmyk.C, 1);
- Assert.Equal(0, cmyk.M, 1);
- Assert.Equal(0, cmyk.Y, 1);
- Assert.Equal(0, cmyk.K, 1);
-
- // Black
- Color color2 = Color.Black;
- Cmyk cmyk2 = color2;
- Assert.Equal(0, cmyk2.C, 1);
- Assert.Equal(0, cmyk2.M, 1);
- Assert.Equal(0, cmyk2.Y, 1);
- Assert.Equal(1, cmyk2.K, 1);
-
- // Gray
- Color color3 = Color.Gray;
- Cmyk cmyk3 = color3;
- Assert.Equal(0f, cmyk3.C, 1);
- Assert.Equal(0f, cmyk3.M, 1);
- Assert.Equal(0f, cmyk3.Y, 1);
- Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters.
-
- // Cyan
- Color color4 = Color.Cyan;
- Cmyk cmyk4 = color4;
- Assert.Equal(1, cmyk4.C, 1);
- Assert.Equal(0f, cmyk4.M, 1);
- Assert.Equal(0f, cmyk4.Y, 1);
- Assert.Equal(0f, cmyk4.K, 1);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- public void CmykToColor()
- {
- // Dark moderate pink.
- Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f);
- Color color = cmyk;
-
- Assert.Equal(color.R, 128);
- Assert.Equal(color.G, 64);
- Assert.Equal(color.B, 106);
-
- // Ochre
- Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f);
- Color color2 = cmyk2;
-
- Assert.Equal(color2.R, 204);
- Assert.Equal(color2.G, 119);
- Assert.Equal(color2.B, 34);
-
- // White
- Cmyk cmyk3 = new Cmyk(0, 0, 0, 0);
- Color color3 = cmyk3;
-
- Assert.Equal(color3.R, 255);
- Assert.Equal(color3.G, 255);
- Assert.Equal(color3.B, 255);
-
- // Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // Cmyk cmyk4 = color4;
- // 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 = Color.White;
- 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 = Color.Black;
- CieLab cielab2 = color2;
- Assert.Equal(0, cielab2.L, 3);
- Assert.Equal(0, cielab2.A, 3);
- Assert.Equal(0, cielab2.B, 3);
-
- // Gray
- Color color3 = Color.Gray;
- 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 = Color.Cyan;
- 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);
- Assert.Equal(color.G, 64);
- Assert.Equal(color.B, 106);
-
- // Ochre
- CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f);
- Color color2 = cielab2;
-
- Assert.Equal(color2.R, 204);
- Assert.Equal(color2.G, 119);
- Assert.Equal(color2.B, 34);
-
- // Black
- CieLab cielab3 = new CieLab(0, 0, 0);
- Color color3 = cielab3;
-
- Assert.Equal(color3.R, 0);
- Assert.Equal(color3.G, 0);
- Assert.Equal(color3.B, 0);
-
- // Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // CieLab cielab4 = color4;
- // Assert.Equal(color4, (Color)cielab4);
- //}
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs
index 899ce4f77a..cb1161ebee 100644
--- a/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs
+++ b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs
@@ -5,13 +5,10 @@
namespace ImageSharp.Tests
{
- using System;
using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
- using ImageSharp.Colors.Spaces;
using Xunit;
public class ColorDefinitionTests
{
diff --git a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs
index b5b09c8288..8bda0bc8cc 100644
--- a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs
+++ b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs
@@ -7,7 +7,6 @@ namespace ImageSharp.Tests.Colors
{
using System;
using System.Numerics;
- using ImageSharp.Colors.Spaces;
using Xunit;
///
@@ -38,38 +37,6 @@ namespace ImageSharp.Tests.Colors
{ new Short4(Vector4.One * 0x7FFF), new Short4(Vector4.One * 0x7FFF), typeof(Short4) },
};
- public static readonly TheoryData