diff --git a/src/ImageSharp/Colors/Spaces/CieLab.cs b/src/ImageSharp/Colors/Spaces/CieLab.cs index 80e8a41a3..984e211a9 100644 --- a/src/ImageSharp/Colors/Spaces/CieLab.cs +++ b/src/ImageSharp/Colors/Spaces/CieLab.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Colors.Spaces using System.Numerics; /// - /// Represents an CIE LAB 1976 color. + /// Represents a CIE L*a*b* 1976 color. /// /// public struct CieLab : IColorVector, IEquatable, IAlmostEquatable @@ -68,7 +68,7 @@ namespace ImageSharp.Colors.Spaces /// /// Initializes a new instance of the struct. /// - /// The vector representing the l a b components. + /// The vector representing the l, a, b components. /// The reference white point. public CieLab(Vector3 vector, CieXyz whitePoint) : this() diff --git a/src/ImageSharp/Colors/Spaces/CieLch.cs b/src/ImageSharp/Colors/Spaces/CieLch.cs new file mode 100644 index 000000000..e232a3eb4 --- /dev/null +++ b/src/ImageSharp/Colors/Spaces/CieLch.cs @@ -0,0 +1,209 @@ +// +// 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 the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. + /// + /// + public struct CieLch : IColorVector, IEquatable, IAlmostEquatable + { + /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; + + /// + /// Represents a that has L, C, H values set to zero. + /// + public static readonly CieLch Empty = default(CieLch); + + /// + /// The backing vector for SIMD support. + /// + private readonly Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// Uses as white point. + public CieLch(float l, float c, float h) + : this(new Vector3(l, c, h), DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// The reference white point. + public CieLch(float l, float c, float h, CieXyz whitePoint) + : this(new Vector3(l, c, h), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// Uses as white point. + public CieLch(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// The reference white point. + public CieLch(Vector3 vector, CieXyz whitePoint) + : this() + { + this.backingVector = vector; + this.WhitePoint = whitePoint; + } + + /// + /// Gets the reference white point of this color + /// + public CieXyz WhitePoint { get; } + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public float L => this.backingVector.X; + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 100. + /// + public float C => this.backingVector.Y; + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. + /// + public float H => 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 ==(CieLch left, CieLch 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 !=(CieLch left, CieLch right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return this.backingVector.GetHashCode(); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieLch [Empty]"; + } + + return $"CieLch [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is CieLch) + { + return this.Equals((CieLch)obj); + } + + return false; + } + + /// + public bool Equals(CieLch other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + public bool AlmostEquals(CieLch other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X <= precision + && result.Y <= precision + && result.Z <= precision; + } + + /// + /// Computes the saturation of the color (chroma normalized by lightness) + /// + /// + /// A value ranging from 0 to 100. + /// + /// The + public float Saturation() + { + float result = 100 * (this.C / this.L); + + if (float.IsNaN(result)) + { + return 0; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs index 5123fd0a2..55c54a5a3 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs @@ -102,5 +102,51 @@ namespace ImageSharp.Colors.Spaces.Conversion CieXyz xyzColor = this.ToCieXyz(color); return this.ToCieLab(xyzColor); } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public HunterLab Adapt(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + if (!this.IsChromaticAdaptationPerformed) + { + throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); + } + + if (color.WhitePoint.Equals(this.TargetHunterLabWhitePoint)) + { + return color; + } + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLch Adapt(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + if (!this.IsChromaticAdaptationPerformed) + { + throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); + } + + if (color.WhitePoint.Equals(this.TargetLabWhitePoint)) + { + return color; + } + + CieLab labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs index 7b0b69a68..418366401 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs @@ -7,12 +7,18 @@ namespace ImageSharp.Colors.Spaces.Conversion { using ImageSharp.Colors.Spaces; using ImageSharp.Colors.Spaces.Conversion.Implementation.CieLab; + using ImageSharp.Colors.Spaces.Conversion.Implementation.CieLch; /// /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation. /// public partial class ColorSpaceConverter { + /// + /// The converter for converting between CieLch to CieLab. + /// + private static readonly CieLchToCieLabConverter CieLchToCieLabConverter = new CieLchToCieLabConverter(); + /// /// Converts a into a /// @@ -83,5 +89,26 @@ namespace ImageSharp.Colors.Spaces.Conversion CieXyz xyzColor = this.ToCieXyz(color); return this.ToCieLab(xyzColor); } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion (perserving white point) + CieLab unadapted = CieLchToCieLabConverter.Convert(color); + + if (!this.IsChromaticAdaptationPerformed) + { + return unadapted; + } + + // Adaptation + return this.Adapt(unadapted); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs new file mode 100644 index 000000000..0ad1f53d2 --- /dev/null +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Colors.Spaces.Conversion +{ + using ImageSharp.Colors.Spaces.Conversion.Implementation.CieLch; + + /// + /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation. + /// + public partial class ColorSpaceConverter + { + /// + /// The converter for converting between CieLab to CieLch. + /// + private static readonly CieLabToCieLchConverter CieLabToCieLchConverter = new CieLabToCieLchConverter(); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(CieXyz color) + { + Guard.NotNull(color, nameof(color)); + + CieLab labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + CieLab labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + CieLab labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + // Adaptation + CieLab adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color; + + // Conversion + return CieLabToCieLchConverter.Convert(adapted); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(Lms color) + { + Guard.NotNull(color, nameof(color)); + + CieLab labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(HunterLab color) + { + Guard.NotNull(color, nameof(color)); + + CieLab labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs index ab42c1043..b5a708dec 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs @@ -107,6 +107,22 @@ namespace ImageSharp.Colors.Spaces.Conversion return adapted; } + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + // Conversion to Lab + CieLab labColor = CieLchToCieLabConverter.Convert(color); + + // Conversion to XYZ (incl. adaptation) + return this.ToCieXyz(labColor); + } + /// /// Gets the correct converter for the given rgb working space. /// diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs index 630b1cfbe..33fad16c7 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs @@ -81,5 +81,18 @@ namespace ImageSharp.Colors.Spaces.Conversion CieXyz xyzColor = this.ToCieXyz(color); return this.ToHunterLab(xyzColor); } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs index 41cba6f67..1cf577d11 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -53,7 +53,7 @@ namespace ImageSharp.Colors.Spaces.Conversion /// /// The color to convert. /// The - public LinearRgb ToLinearRGB(HunterLab color) + public LinearRgb ToLinearRgb(HunterLab color) { Guard.NotNull(color, nameof(color)); @@ -66,7 +66,7 @@ namespace ImageSharp.Colors.Spaces.Conversion /// /// The color to convert. /// The - public LinearRgb ToLinearRGB(CieLab color) + public LinearRgb ToLinearRgb(CieLab color) { Guard.NotNull(color, nameof(color)); @@ -79,7 +79,20 @@ namespace ImageSharp.Colors.Spaces.Conversion /// /// The color to convert. /// The - public LinearRgb ToLinearRGB(Lms color) + public LinearRgb ToLinearRgb(Lms color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(CieLch color) { Guard.NotNull(color, nameof(color)); diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs index c53e999ec..de9f765ce 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs @@ -76,5 +76,18 @@ namespace ImageSharp.Colors.Spaces.Conversion CieXyz xyzColor = this.ToCieXyz(color); return this.ToLms(xyzColor); } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs index c82e554e4..879f915dc 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -81,5 +81,18 @@ namespace ImageSharp.Colors.Spaces.Conversion CieXyz xyzColor = this.ToCieXyz(color); return this.ToRgb(xyzColor); } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(CieLch color) + { + Guard.NotNull(color, nameof(color)); + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs new file mode 100644 index 000000000..c3721bdf5 --- /dev/null +++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieLch +{ + using ImageSharp.Colors.Spaces; + + /// + /// Converts from to . + /// + public class CieLchToCieLabConverter : IColorConversion + { + /// + public CieLab Convert(CieLch input) + { + DebugGuard.NotNull(input, nameof(input)); + + // Conversion algorithm described here: https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC + float l = input.L, c = input.C, hDegrees = input.H; + float hRadians = MathF.DegreeToRadian(hDegrees); + + float a = c * MathF.Cos(hRadians); + float b = c * MathF.Sin(hRadians); + + return new CieLab(l, a, b, input.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs new file mode 100644 index 000000000..075aed500 --- /dev/null +++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieLch +{ + using ImageSharp.Colors.Spaces; + + /// + /// Converts from to . + /// + internal class CieLabToCieLchConverter : IColorConversion + { + /// + public CieLch Convert(CieLab input) + { + DebugGuard.NotNull(input, nameof(input)); + + // Conversion algorithm described here: https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC + float l = input.L, a = input.A, b = input.B; + float c = MathF.Sqrt((a * a) + (b * b)); + float hRadians = MathF.Atan2(b, a); + float hDegrees = MathF.RadianToDegree(hRadians); + + if (hDegrees > 360) + { + hDegrees -= 360; + } + else if (hDegrees < 0) + { + hDegrees += 360; + } + + return new CieLch(l, c, hDegrees, input.WhitePoint); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs index 3a0d818d4..988b400e3 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs @@ -48,7 +48,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Lms /// /// XYZ scaling chromatic adaptation transform matrix /// - public static readonly Matrix4x4 XYZScaling = Matrix4x4.Transpose(Matrix4x4.Identity); + public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity); /// /// Bradford chromatic adaptation transform matrix (used in CMCCAT97) diff --git a/src/ImageSharp/Common/Helpers/MathF.cs b/src/ImageSharp/Common/Helpers/MathF.cs index 2ee700789..7da6b0d77 100644 --- a/src/ImageSharp/Common/Helpers/MathF.cs +++ b/src/ImageSharp/Common/Helpers/MathF.cs @@ -19,28 +19,89 @@ namespace ImageSharp /// public const float PI = (float)Math.PI; - /// Returns the absolute value of a single-precision floating-point number. - /// A number that is greater than or equal to , but less than or equal to . - /// A single-precision floating-point number, x, such that 0 ≤ x ≤. + /// + /// Returns the absolute value of a single-precision floating-point number. + /// + /// + /// A number that is greater than or equal to , but less than or equal to . + /// + /// + /// A single-precision floating-point number, x, such that 0 ≤ x ≤. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Abs(float f) { return Math.Abs(f); } - /// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number. - /// A single-precision floating-point number. - /// The smallest integral value that is greater than or equal to . + /// + /// Returns the angle whose tangent is the quotient of two specified numbers. + /// + /// The y coordinate of a point. + /// The x coordinate of a point. + /// + /// An angle, θ, measured in radians, such that -π≤θ≤π, and tan(θ) = y / x, where + /// (x, y) is a point in the Cartesian plane. Observe the following: For (x, y) in + /// quadrant 1, 0 < θ < π/2.For (x, y) in quadrant 2, π/2 < θ≤π.For (x, y) in quadrant + /// 3, -π < θ < -π/2.For (x, y) in quadrant 4, -π/2 < θ < 0.For points on the boundaries + /// of the quadrants, the return value is the following:If y is 0 and x is not negative, + /// θ = 0.If y is 0 and x is negative, θ = π.If y is positive and x is 0, θ = π/2.If + /// y is negative and x is 0, θ = -π/2.If y is 0 and x is 0, θ = 0. If x or y is + /// , or if x and y are either or + /// , the method returns . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Atan2(float y, float x) + { + return (float)Math.Atan2(y, x); + } + + /// + /// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number. + /// + /// A single-precision floating-point number. + /// + /// The smallest integral value that is greater than or equal to . /// If is equal to , , /// or , that value is returned. - /// Note that this method returns a instead of an integral type. + /// Note that this method returns a instead of an integral type. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Ceiling(float f) { return (float)Math.Ceiling(f); } - /// Returns e raised to the specified power. + /// + /// Returns the cosine of the specified angle. + /// + /// An angle, measured in radians. + /// + /// The cosine of . If is equal to , , + /// or , this method returns . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cos(float f) + { + return (float)Math.Cos(f); + } + + /// + /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle. + /// + /// The angle in degrees. + /// + /// The representing the degree as radians. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DegreeToRadian(float degree) + { + return degree * (PI / 180F); + } + + /// + /// Returns e raised to the specified power. + /// /// A number specifying a power. /// /// The number e raised to the power . @@ -53,9 +114,12 @@ namespace ImageSharp return (float)Math.Exp(f); } - /// Returns the largest integer less than or equal to the specified single-precision floating-point number. + /// + /// Returns the largest integer less than or equal to the specified single-precision floating-point number. + /// /// A single-precision floating-point number. - /// The largest integer less than or equal to . + /// + /// The largest integer less than or equal to . /// If is equal to , , /// or , that value is returned. /// @@ -65,10 +129,13 @@ namespace ImageSharp return (float)Math.Floor(f); } - /// Returns the larger of two single-precision floating-point numbers. + /// + /// Returns the larger of two single-precision floating-point numbers. + /// /// The first of two single-precision floating-point numbers to compare. /// The second of two single-precision floating-point numbers to compare. - /// Parameter or , whichever is larger. + /// + /// Parameter or , whichever is larger. /// If , or , or both and are /// equal to , is returned. /// @@ -78,19 +145,25 @@ namespace ImageSharp return Math.Max(val1, val2); } - /// Returns the smaller of two single-precision floating-point numbers. + /// + /// Returns the smaller of two single-precision floating-point numbers. + /// /// The first of two single-precision floating-point numbers to compare. /// The second of two single-precision floating-point numbers to compare. - /// Parameter or , whichever is smaller. + /// + /// Parameter or , whichever is smaller. /// If , , or both and are equal - /// to , is returned. + /// to , is returned. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Min(float val1, float val2) { return Math.Min(val1, val2); } - /// Returns a specified number raised to the specified power. + /// + /// Returns a specified number raised to the specified power. + /// /// A single-precision floating-point number to be raised to a power. /// A single-precision floating-point number that specifies a power. /// The number raised to the power . @@ -100,7 +173,22 @@ namespace ImageSharp return (float)Math.Pow(x, y); } - /// Rounds a single-precision floating-point value to the nearest integral value. + /// + /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle. + /// + /// The angle in radians. + /// + /// The representing the degree as radians. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RadianToDegree(float radian) + { + return radian / (PI / 180F); + } + + /// + /// Rounds a single-precision floating-point value to the nearest integral value. + /// /// A single-precision floating-point number to be rounded. /// /// The integer nearest . @@ -113,7 +201,9 @@ namespace ImageSharp return (float)Math.Round(f); } - /// Returns the sine of the specified angle. + /// + /// Returns the sine of the specified angle. + /// /// An angle, measured in radians. /// /// The sine of . @@ -126,8 +216,10 @@ namespace ImageSharp return (float)Math.Sin(f); } - /// Returns the square root of a specified number. - /// The number whose square root is to be found. + /// + /// Returns the square root of a specified number. + /// + /// The number whose square root is to be found. /// /// One of the values in the following table. /// parameter Return value Zero or positive The positive square root of . diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/CieLabAndCieLchConversionTests.cs new file mode 100644 index 000000000..426e21bb4 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/Colorspaces/CieLabAndCieLchConversionTests.cs @@ -0,0 +1,76 @@ +namespace ImageSharp.Tests.Colors.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.Colors.Spaces; + using ImageSharp.Colors.Spaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLabAndCieLchConversionTests + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_Lch_to_Lab(float l, float c, float h, float l2, float a, float b) + { + // Arrange + CieLch input = new CieLch(l, c, h); + + // Act + CieLab output = Converter.ToCieLab(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(a, output.A, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + public void Convert_Lab_to_LCHab(float l, float a, float b, float l2, float c, float h) + { + // Arrange + CieLab input = new CieLab(l, a, b); + + // Act + CieLch output = Converter.ToCieLch(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(c, output.C, FloatRoundingComparer); + Assert.Equal(h, output.H, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs index 539085477..8e6088bdc 100644 --- a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs +++ b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs @@ -11,7 +11,7 @@ /// /// /// Test data generated using: - /// http://www.brucelindbloom.com/index.html?ColorCalculator.html + /// /// public class CieXyzAndCieLabConversionTest { @@ -29,7 +29,7 @@ [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) + 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); @@ -54,7 +54,7 @@ [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) + 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); diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndHunterLabConversionTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndHunterLabConversionTest.cs index 575e330d3..b5e482f61 100644 --- a/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndHunterLabConversionTest.cs +++ b/tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndHunterLabConversionTest.cs @@ -11,7 +11,7 @@ /// /// /// Test data generated using: - /// http://www.brucelindbloom.com/index.html?ColorCalculator.html + /// /// public class CieXyzAndHunterLabConversionTest { @@ -23,7 +23,7 @@ [Theory] [InlineData(0, 0, 0, 0, 0, 0)] [InlineData(100, 0, 0, 0.98074, 1, 1.18232)] // C white point is HunterLab 100, 0, 0 - public void Convert_HunterLab_to_XYZ(float l, float a, float b, float x, float y, float z) + public void Convert_HunterLab_to_Xyz(float l, float a, float b, float x, float y, float z) { // Arrange HunterLab input = new HunterLab(l, a, b); @@ -44,7 +44,7 @@ [Theory] [InlineData(0, 0, 0, 0, 0, 0)] [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] // D65 white point is HunerLab 100, 0, 0 (adaptation to C performed) - public void Convert_HunterLab_to_XYZ_D65(float l, float a, float b, float x, float y, float z) + public void Convert_HunterLab_to_Xyz_D65(float l, float a, float b, float x, float y, float z) { // Arrange HunterLab input = new HunterLab(l, a, b); @@ -65,7 +65,7 @@ [Theory] [InlineData(0, 0, 0, 0, 0, 0)] [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] // D65 white point is HunterLab 100, 0, 0 (adaptation to C performed) - public void Convert_XYZ_D65_to_HunterLab(float x, float y, float z, float l, float a, float b) + public void Convert_Xyz_D65_to_HunterLab(float x, float y, float z, float l, float a, float b) { // Arrange CieXyz input = new CieXyz(x, y, z); diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs index ac9a87ce1..a5b0f7ea9 100644 --- a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs +++ b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs @@ -8,6 +8,12 @@ namespace ImageSharp.Tests using Xunit; + /// + /// Tests methods. + /// Test data generated using: + /// + /// + /// public class ColorConverterAdaptTest { private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(3); @@ -76,7 +82,7 @@ namespace ImageSharp.Tests [Theory] [InlineData(0, 0, 0, 0, 0, 0)] [InlineData(0.5, 0.5, 0.5, 0.510286, 0.501489, 0.378970)] - public void Adapt_XYZ_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) + public void Adapt_Xyz_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) { // Arrange CieXyz input = new CieXyz(x1, y1, z1); @@ -105,7 +111,7 @@ namespace ImageSharp.Tests CieXyz expectedOutput = new CieXyz(x2, y2, z2); ColorSpaceConverter converter = new ColorSpaceConverter { - ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XYZScaling), + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), WhitePoint = Illuminants.D50 }; @@ -121,14 +127,14 @@ namespace ImageSharp.Tests [Theory] [InlineData(0, 0, 0, 0, 0, 0)] [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] - public void Adapt_XYZ_D65_To_D50_XYZScaling(float x1, float y1, float z1, float x2, float y2, float z2) + public void Adapt_Xyz_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); ColorSpaceConverter converter = new ColorSpaceConverter { - ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XYZScaling), + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), WhitePoint = Illuminants.D50 }; diff --git a/tests/ImageSharp.Tests/Helpers/MathFTests.cs b/tests/ImageSharp.Tests/Helpers/MathFTests.cs index 7f3fb77d0..f381f2a77 100644 --- a/tests/ImageSharp.Tests/Helpers/MathFTests.cs +++ b/tests/ImageSharp.Tests/Helpers/MathFTests.cs @@ -18,12 +18,24 @@ Assert.Equal(MathF.Ceiling(0.3333F), (float)Math.Ceiling(0.3333F)); } + [Fact] + public void MathF_Cos_Is_Equal() + { + Assert.Equal(MathF.Cos(0.3333F), (float)Math.Cos(0.3333F)); + } + [Fact] public void MathF_Abs_Is_Equal() { Assert.Equal(MathF.Abs(-0.3333F), (float)Math.Abs(-0.3333F)); } + [Fact] + public void MathF_Atan2_Is_Equal() + { + Assert.Equal(MathF.Atan2(1.2345F, 1.2345F), (float)Math.Atan2(1.2345F, 1.2345F)); + } + [Fact] public void MathF_Exp_Is_Equal() { @@ -65,5 +77,17 @@ { Assert.Equal(MathF.Sqrt(2F), (float)Math.Sqrt(2F)); } + + [Fact] + public void Convert_Degree_To_Radian() + { + Assert.Equal((float)(Math.PI / 2D), MathF.DegreeToRadian(90F), new FloatRoundingComparer(6)); + } + + [Fact] + public void Convert_Radian_To_Degree() + { + Assert.Equal(60F, MathF.RadianToDegree((float)(Math.PI / 3D)), new FloatRoundingComparer(5)); + } } } \ No newline at end of file