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 EqualityDataColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), new Bgra32(0, 0, 0), typeof(Bgra32) }, - { new Bgra32(0, 0, 0, 0), new Bgra32(0, 0, 0, 0), typeof(Bgra32) }, - { new Bgra32(100, 100, 0, 0), new Bgra32(100, 100, 0, 0), typeof(Bgra32) }, - { new Bgra32(255, 255, 255), new Bgra32(255, 255, 255), typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab) }, - { new CieLab(1f, 1f, 1f), new CieLab(1f, 1f, 1f), typeof(CieLab) }, - { new CieLab(0f, -100f, -100f), new CieLab(0f, -100f, -100f), typeof(CieLab) }, - { new CieLab(0f, 100f, -100f), new CieLab(0f, 100f, -100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 100f), new CieLab(0f, -100f, 100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 50f), new CieLab(0f, -100f, 50f), typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380f), typeof(CieXyz) }, - { new CieXyz(780f, 780f, 780f), new CieXyz(780f, 780f, 780f), typeof(CieXyz) }, - { new CieXyz(380f, 780f, 780f), new CieXyz(380f, 780f, 780f), typeof(CieXyz) }, - { new CieXyz(50f, 20f, 60f), new CieXyz(50f, 20f, 60f), typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0f), typeof(Cmyk) }, - { new Cmyk(1f, 1f, 1f, 1f), new Cmyk(1f, 1f, 1f, 1f), typeof(Cmyk) }, - { new Cmyk(10f, 10f, 10f, 10f), new Cmyk(10f, 10f, 10f, 10f), typeof(Cmyk) }, - { new Cmyk(.4f, .5f, .1f, .2f), new Cmyk(.4f, .5f, .1f, .2f), typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0f), typeof(Hsl) }, - { new Hsl(360f, 1f, 1f), new Hsl(360f, 1f, 1f), typeof(Hsl) }, - { new Hsl(100f, .5f, .1f), new Hsl(100f, .5f, .1f), typeof(Hsl) }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0f), typeof(Hsv) }, - { new Hsv(360f, 1f, 1f), new Hsv(360f, 1f, 1f), typeof(Hsv) }, - { new Hsv(100f, .5f, .1f), new Hsv(100f, .5f, .1f), typeof(Hsv) }, - { new YCbCr(0, 0, 0), new YCbCr(0, 0, 0), typeof(YCbCr) }, - { new YCbCr(255, 255, 255), new YCbCr(255, 255, 255), typeof(YCbCr) }, - { new YCbCr(100, 100, 0), new YCbCr(100, 100, 0), typeof(YCbCr) }, - }; - public static readonly TheoryData NotEqualityDataNulls = new TheoryData() { @@ -94,18 +61,6 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), null, typeof(Short4) }, }; - public static readonly TheoryData NotEqualityDataNullsColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), null, typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), null, typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), null, typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), null, typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), null, typeof(Hsl) }, - { new Hsv(360f, 1f, 1f), null, typeof(Hsv) }, - { new YCbCr(0, 0, 0), null, typeof(YCbCr) }, - }; - public static readonly TheoryData NotEqualityDataDifferentObjects = new TheoryData() { @@ -115,16 +70,6 @@ namespace ImageSharp.Tests.Colors { new Rgba1010102(Vector4.One), new Bgra5551(Vector4.Zero), null }, }; - public static readonly TheoryData NotEqualityDataDifferentObjectsColorSpaces = - new TheoryData() - { - // Valid objects of different types but not equal - { new Bgra32(0, 0, 0), new CieLab(0f, 0f, 0f), null }, - { new CieXyz(380f, 380f, 380f), new Cmyk(0f, 0f, 0f, 0f), null }, - { new Hsl(0f, 0f, 0f), new Hsv(360f, 1f, 1f), null }, - { new YCbCr(0, 0, 0), new Hsv(360f, 1f, 1f), null }, - }; - public static readonly TheoryData NotEqualityData = new TheoryData() { @@ -149,92 +94,8 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), new Short4(Vector4.Zero), typeof(Short4) }, }; - public static readonly TheoryData NotEqualityDataColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), new Bgra32(0, 1, 0), typeof(Bgra32) }, - { new Bgra32(0, 0, 0, 0), new Bgra32(0, 1, 0, 0), typeof(Bgra32) }, - { new Bgra32(100, 100, 0, 0), new Bgra32(100, 0, 0, 0), typeof(Bgra32) }, - { new Bgra32(255, 255, 255), new Bgra32(255, 0, 255), typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 1f, 0f), typeof(CieLab) }, - { new CieLab(1f, 1f, 1f), new CieLab(1f, 0f, 1f), typeof(CieLab) }, - { new CieLab(0f, -100f, -100f), new CieLab(0f, 100f, -100f), typeof(CieLab) }, - { new CieLab(0f, 100f, -100f), new CieLab(0f, -100f, -100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 100f), new CieLab(0f, 100f, 100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 50f), new CieLab(0f, 100f, 20f), typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 0f, 380f), typeof(CieXyz) }, - { new CieXyz(780f, 780f, 780f), new CieXyz(780f, 0f, 780f), typeof(CieXyz) }, - { new CieXyz(380f, 780f, 780f), new CieXyz(380f, 0f, 780f), typeof(CieXyz) }, - { new CieXyz(50f, 20f, 60f), new CieXyz(50f, 0f, 60f), typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 1f, 0f, 0f), typeof(Cmyk) }, - { new Cmyk(1f, 1f, 1f, 1f), new Cmyk(1f, 1f, 0f, 1f), typeof(Cmyk) }, - { new Cmyk(10f, 10f, 10f, 10f), new Cmyk(10f, 10f, 0f, 10f), typeof(Cmyk) }, - { new Cmyk(.4f, .5f, .1f, .2f), new Cmyk(.4f, .5f, 5f, .2f), typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 5f, 0f), typeof(Hsl) }, - { new Hsl(360f, 1f, 1f), new Hsl(360f, .5f, 1f), typeof(Hsl) }, - { new Hsl(100f, .5f, .1f), new Hsl(100f, 9f, .1f), typeof(Hsl) }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 1f, 0f), typeof(Hsv) }, - { new Hsv(360f, 1f, 1f), new Hsv(0f, 1f, 1f), typeof(Hsv) }, - { new Hsv(100f, .5f, .1f), new Hsv(2f, .5f, .1f), typeof(Hsv) }, - { new YCbCr(0, 0, 0), new YCbCr(0, 1, 0), typeof(YCbCr) }, - { new YCbCr(255, 255, 255), new YCbCr(255, 0, 255), typeof(YCbCr) }, - { new YCbCr(100, 100, 0), new YCbCr(100, 20, 0), typeof(YCbCr) }, - }; - - public static readonly TheoryData AlmostEqualsData = - new TheoryData() - { - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), 0f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .0001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .0005f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, .001f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, .0001f), typeof(CieLab), .0001f }, - { new CieLab(0f, 0f, 0f), new CieLab(.0005f, 0f, 0f), typeof(CieLab), .0005f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380f), typeof(CieXyz), 0f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380.001f, 380f, 380f), typeof(CieXyz), .01f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380.001f, 380f), typeof(CieXyz), .01f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380.001f), typeof(CieXyz), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0f), typeof(Cmyk), 0f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0.001f, 0f, 0f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0.001f, 0f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0.001f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0.001f), typeof(Cmyk), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0f), typeof(Hsl), 0f }, - { new Hsl(0f, 0f, 0f), new Hsl(0.001f, 0f, 0f), typeof(Hsl), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0.001f, 0f), typeof(Hsl), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0.001f), typeof(Hsl), .01f }, - { new Hsv(360f, 1f, 1f), new Hsv(360f, 1f, 1f), typeof(Hsv), 0f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0f), typeof(Hsv), 0f }, - { new Hsv(0f, 0f, 0f), new Hsv(0.001f, 0f, 0f), typeof(Hsv), .01f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0.001f, 0f), typeof(Hsv), .01f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0.001f), typeof(Hsv), .01f }, - }; - - public static readonly TheoryData AlmostNotEqualsData = - new TheoryData() - { - { new CieLab(0f, 0f, 0f), new CieLab(0.1f, 0f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0.1f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0.1f), typeof(CieLab), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380.1f, 380f, 380f), typeof(CieXyz), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380.1f, 380f), typeof(CieXyz), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380.1f), typeof(CieXyz), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0.1f, 0f, 0f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0.1f, 0f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0.1f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0.1f), typeof(Cmyk), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0.1f, 0f, 0f), typeof(Hsl), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0.1f, 0f), typeof(Hsl), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0.1f), typeof(Hsl), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0.1f, 0f, 0f), typeof(Hsv), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0.1f, 0f), typeof(Hsv), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0.1f), typeof(Hsv), .001f }, - }; - [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void Equality(object first, object second, Type type) { // Act @@ -246,11 +107,8 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityDataNulls))] - [MemberData(nameof(NotEqualityDataNullsColorSpaces))] [MemberData(nameof(NotEqualityDataDifferentObjects))] - [MemberData(nameof(NotEqualityDataDifferentObjectsColorSpaces))] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEquality(object first, object second, Type type) { // Act @@ -262,7 +120,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void HashCodeEqual(object first, object second, Type type) { // Act @@ -274,7 +131,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityDataDifferentObjects))] - [MemberData(nameof(NotEqualityDataDifferentObjectsColorSpaces))] public void HashCodeNotEqual(object first, object second, Type type) { // Act @@ -286,13 +142,12 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void EqualityObject(object first, object second, Type type) { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) dynamic firstObject = Convert.ChangeType(first, type); dynamic secondObject = Convert.ChangeType(second, type); @@ -305,13 +160,12 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEqualityObject(object first, object second, Type type) { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) dynamic firstObject = Convert.ChangeType(first, type); dynamic secondObject = Convert.ChangeType(second, type); @@ -324,13 +178,12 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void EqualityOperator(object first, object second, Type type) { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) dynamic firstObject = Convert.ChangeType(first, type); dynamic secondObject = Convert.ChangeType(second, type); @@ -343,13 +196,12 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEqualityOperator(object first, object second, Type type) { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) dynamic firstObject = Convert.ChangeType(first, type); dynamic secondObject = Convert.ChangeType(second, type); @@ -359,41 +211,5 @@ namespace ImageSharp.Tests.Colors // Assert Assert.True(notEqual); } - - [Theory] - [MemberData(nameof(AlmostEqualsData))] - public void AlmostEquals(object first, object second, Type type, float precision) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); - - // Assert - Assert.True(almostEqual); - } - - [Theory] - [MemberData(nameof(AlmostNotEqualsData))] - public void AlmostNotEquals(object first, object second, Type type, float precision) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); - - // Assert - Assert.False(almostEqual); - } } } diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs new file mode 100644 index 0000000000..5be2f27acf --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs @@ -0,0 +1,72 @@ +namespace ImageSharp.Tests +{ + using ImageSharp.Colors.Conversion; + using System.Collections.Generic; + using ImageSharp.Colors.Spaces; + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// http://www.brucelindbloom.com/index.html?ColorCalculator.html + /// + public class CieXyzAndCieLabConversionTest + { + private static readonly IEqualityComparer FloatComparerLabPrecision = new ApproximateFloatComparer(4); + private static readonly IEqualityComparer FloatComparerXyzPrecision = new ApproximateFloatComparer(6); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 431.0345, 0, 0.95047, 0, 0)] + [InlineData(100, -431.0345, 172.4138, 0, 1, 0)] + [InlineData(0, 0, -172.4138, 0, 0, 1.08883)] + [InlineData(45.6398, 39.8753, 35.2091, 0.216938, 0.150041, 0.048850)] + [InlineData(77.1234, -40.1235, 78.1120, 0.358530, 0.517372, 0.076273)] + [InlineData(10, -400, 20, 0, 0.011260, 0)] + public void Convert_Lab_to_XYZ(float l, float a, float b, float x, float y, float z) + { + // Arrange + CieLab input = new CieLab(l, a, b, Illuminants.D65); + ColorConverter converter = new ColorConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(output.X, x, FloatComparerXyzPrecision); + Assert.Equal(output.Y, y, FloatComparerXyzPrecision); + Assert.Equal(output.Z, z, FloatComparerXyzPrecision); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0, 431.0345, 0)] + [InlineData(0, 1, 0, 100, -431.0345, 172.4138)] + [InlineData(0, 0, 1.08883, 0, 0, -172.4138)] + [InlineData(0.216938, 0.150041, 0.048850, 45.6398, 39.8753, 35.2091)] + public void Convert_XYZ_to_Lab(float x, float y, float z, float l, float a, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorConverter converter = new ColorConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieLab output = converter.ToCieLab(input); + + // Assert + Assert.Equal(output.L, l, FloatComparerLabPrecision); + Assert.Equal(output.A, a, FloatComparerLabPrecision); + Assert.Equal(output.B, b, FloatComparerLabPrecision); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndLmsConversionTest.cs new file mode 100644 index 0000000000..d60b72c6d9 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndLmsConversionTest.cs @@ -0,0 +1,68 @@ +namespace ImageSharp.Tests +{ + using ImageSharp.Colors.Conversion; + using System.Collections.Generic; + using ImageSharp.Colors.Spaces; + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using original colorful library. + /// + public class CieXyzAndLmsConversionTest + { + private static readonly IEqualityComparer FloatComparer = new ApproximateFloatComparer(6); + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.941428535, 1.040417467, 1.089532651, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.850765697, -0.713042594, 0.036973283, 0.95047, 0, 0)] + [InlineData(0.2664, 1.7135, -0.0685, 0, 1, 0)] + [InlineData(-0.175737162, 0.039960061, 1.121059368, 0, 0, 1.08883)] + [InlineData(0.2262677362, 0.0961411609, 0.0484570397, 0.216938, 0.150041, 0.048850)] + public void Convert_Lms_to_CieXyz(float l, float m, float s, float x, float y, float z) + { + // Arrange + Lms input = new Lms(x, y, z); + ColorConverter converter = new ColorConverter(); + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(output.X, l, FloatComparer); + Assert.Equal(output.Y, m, FloatComparer); + Assert.Equal(output.Z, s, FloatComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 0.941428535, 1.040417467, 1.089532651)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0.850765697, -0.713042594, 0.036973283)] + [InlineData(0, 1, 0, 0.2664, 1.7135, -0.0685)] + [InlineData(0, 0, 1.08883, -0.175737162, 0.039960061, 1.121059368)] + [InlineData(0.216938, 0.150041, 0.048850, 0.2262677362, 0.0961411609, 0.0484570397)] + public void Convert_CieXyz_to_Lms(float x, float y, float z, float l, float m, float s) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorConverter converter = new ColorConverter(); + + // Act + Lms output = converter.ToLms(input); + + // Assert + Assert.Equal(output.L, l, FloatComparer); + Assert.Equal(output.M, m, FloatComparer); + Assert.Equal(output.S, s, FloatComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs new file mode 100644 index 0000000000..b9476ce291 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs @@ -0,0 +1,37 @@ +using ImageSharp.Colors.Conversion; +using ImageSharp.Colors.Conversion.Implementation; +using ImageSharp.Colors.Spaces; + +namespace ImageSharp.Tests +{ + using System.Collections.Generic; + using Xunit; + + public class ColorConverterAdaptTest + { + private static readonly IEqualityComparer FloatComparer = new ApproximateFloatComparer(4); + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] + public void Adapt_CieXyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorConverter converter = new ColorConverter + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XYZScaling), + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(output.X, expectedOutput.X, FloatComparer); + Assert.Equal(output.Y, expectedOutput.Y, FloatComparer); + Assert.Equal(output.Z, expectedOutput.Z, FloatComparer); + } + } +} \ No newline at end of file