Browse Source

Add CieLch

pull/144/head
James Jackson-South 9 years ago
parent
commit
b6e508cc61
  1. 4
      src/ImageSharp/Colors/Spaces/CieLab.cs
  2. 209
      src/ImageSharp/Colors/Spaces/CieLch.cs
  3. 46
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs
  4. 27
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLab.cs
  5. 101
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs
  6. 16
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs
  7. 13
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.HunterLab.cs
  8. 19
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs
  9. 13
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Lms.cs
  10. 13
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs
  11. 30
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs
  12. 38
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs
  13. 2
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
  14. 132
      src/ImageSharp/Common/Helpers/MathF.cs
  15. 76
      tests/ImageSharp.Tests/Colors/Colorspaces/CieLabAndCieLchConversionTests.cs
  16. 6
      tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs
  17. 8
      tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndHunterLabConversionTest.cs
  18. 14
      tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs
  19. 24
      tests/ImageSharp.Tests/Helpers/MathFTests.cs

4
src/ImageSharp/Colors/Spaces/CieLab.cs

@ -10,7 +10,7 @@ namespace ImageSharp.Colors.Spaces
using System.Numerics;
/// <summary>
/// Represents an CIE LAB 1976 color.
/// Represents a CIE L*a*b* 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
/// </summary>
public struct CieLab : IColorVector, IEquatable<CieLab>, IAlmostEquatable<CieLab, float>
@ -68,7 +68,7 @@ namespace ImageSharp.Colors.Spaces
/// <summary>
/// Initializes a new instance of the <see cref="CieLab"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l a b components.</param>
/// <param name="vector">The vector representing the l, a, b components.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
public CieLab(Vector3 vector, CieXyz whitePoint)
: this()

209
src/ImageSharp/Colors/Spaces/CieLch.cs

@ -0,0 +1,209 @@
// <copyright file="CieLch.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC"/>
/// </summary>
public struct CieLch : IColorVector, IEquatable<CieLch>, IAlmostEquatable<CieLch, float>
{
/// <summary>
/// D50 standard illuminant.
/// Used when reference white is not specified explicitly.
/// </summary>
public static readonly CieXyz DefaultWhitePoint = Illuminants.D50;
/// <summary>
/// Represents a <see cref="CieLch"/> that has L, C, H values set to zero.
/// </summary>
public static readonly CieLch Empty = default(CieLch);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieLch"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="c">The chroma, relative saturation.</param>
/// <param name="h">The hue in degrees.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
public CieLch(float l, float c, float h)
: this(new Vector3(l, c, h), DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLch"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="c">The chroma, relative saturation.</param>
/// <param name="h">The hue in degrees.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
public CieLch(float l, float c, float h, CieXyz whitePoint)
: this(new Vector3(l, c, h), whitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLch"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, c, h components.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
public CieLch(Vector3 vector)
: this(vector, DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLch"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, c, h components.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
public CieLch(Vector3 vector, CieXyz whitePoint)
: this()
{
this.backingVector = vector;
this.WhitePoint = whitePoint;
}
/// <summary>
/// Gets the reference white point of this color
/// </summary>
public CieXyz WhitePoint { get; }
/// <summary>
/// Gets the lightness dimension.
/// <remarks>A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
/// </summary>
public float L => this.backingVector.X;
/// <summary>
/// Gets the a chroma component.
/// <remarks>A value ranging from 0 to 100.</remarks>
/// </summary>
public float C => this.backingVector.Y;
/// <summary>
/// Gets the h° hue component in degrees.
/// <remarks>A value ranging from 0 to 360.</remarks>
/// </summary>
public float H => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="CieLch"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector => this.backingVector;
/// <summary>
/// Compares two <see cref="CieLch"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieLch"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLch"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(CieLch left, CieLch right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieLch"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="CieLch"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLch"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(CieLch left, CieLch right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieLch [Empty]";
}
return $"CieLch [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is CieLch)
{
return this.Equals((CieLch)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(CieLch other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Computes the saturation of the color (chroma normalized by lightness)
/// </summary>
/// <remarks>
/// A value ranging from 0 to 100.
/// </remarks>
/// <returns>The <see cref="float"/></returns>
public float Saturation()
{
float result = 100 * (this.C / this.L);
if (float.IsNaN(result))
{
return 0;
}
return result;
}
}
}

46
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);
}
/// <summary>
/// Adapts <see cref="HunterLab"/> color from the source white point to white point set in <see cref="TargetHunterLabWhitePoint"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
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);
}
/// <summary>
/// Adapts <see cref="CieLch"/> color from the source white point to white point set in <see cref="TargetLabWhitePoint"/>.
/// </summary>
/// <param name="color">The color to adapt</param>
/// <returns>The adapted color</returns>
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);
}
}
}

27
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;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorSpaceConverter
{
/// <summary>
/// The converter for converting between CieLch to CieLab.
/// </summary>
private static readonly CieLchToCieLabConverter CieLchToCieLabConverter = new CieLchToCieLabConverter();
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="CieLab"/>
/// </summary>
@ -83,5 +89,26 @@ namespace ImageSharp.Colors.Spaces.Conversion
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLab(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
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);
}
}
}

101
src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieLch.cs

@ -0,0 +1,101 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion
{
using ImageSharp.Colors.Spaces.Conversion.Implementation.CieLch;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorSpaceConverter
{
/// <summary>
/// The converter for converting between CieLab to CieLch.
/// </summary>
private static readonly CieLabToCieLchConverter CieLabToCieLchConverter = new CieLabToCieLchConverter();
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(CieXyz color)
{
Guard.NotNull(color, nameof(color));
CieLab labColor = this.ToCieLab(color);
return this.ToCieLch(labColor);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(Rgb color)
{
Guard.NotNull(color, nameof(color));
CieLab labColor = this.ToCieLab(color);
return this.ToCieLch(labColor);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
CieLab labColor = this.ToCieLab(color);
return this.ToCieLch(labColor);
}
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(CieLab color)
{
Guard.NotNull(color, nameof(color));
// Adaptation
CieLab adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color;
// Conversion
return CieLabToCieLchConverter.Convert(adapted);
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(Lms color)
{
Guard.NotNull(color, nameof(color));
CieLab labColor = this.ToCieLab(color);
return this.ToCieLch(labColor);
}
/// <summary>
/// Converts a <see cref="HunterLab"/> into a <see cref="CieLch"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLch"/></returns>
public CieLch ToCieLch(HunterLab color)
{
Guard.NotNull(color, nameof(color));
CieLab labColor = this.ToCieLab(color);
return this.ToCieLch(labColor);
}
}
}

16
src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs

@ -107,6 +107,22 @@ namespace ImageSharp.Colors.Spaces.Conversion
return adapted;
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
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);
}
/// <summary>
/// Gets the correct converter for the given rgb working space.
/// </summary>

13
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);
}
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="HunterLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="HunterLab"/></returns>
public HunterLab ToHunterLab(CieLch color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToHunterLab(xyzColor);
}
}
}

19
src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs

@ -53,7 +53,7 @@ namespace ImageSharp.Colors.Spaces.Conversion
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRGB(HunterLab color)
public LinearRgb ToLinearRgb(HunterLab color)
{
Guard.NotNull(color, nameof(color));
@ -66,7 +66,7 @@ namespace ImageSharp.Colors.Spaces.Conversion
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRGB(CieLab color)
public LinearRgb ToLinearRgb(CieLab color)
{
Guard.NotNull(color, nameof(color));
@ -79,7 +79,20 @@ namespace ImageSharp.Colors.Spaces.Conversion
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRGB(Lms color)
public LinearRgb ToLinearRgb(Lms color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToLinearRgb(xyzColor);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(CieLch color)
{
Guard.NotNull(color, nameof(color));

13
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);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(CieLch color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToLms(xyzColor);
}
}
}

13
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);
}
/// <summary>
/// Converts a <see cref="CieLch"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(CieLch color)
{
Guard.NotNull(color, nameof(color));
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToRgb(xyzColor);
}
}
}

30
src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs

@ -0,0 +1,30 @@
// <copyright file="CieLchToCieLabConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieLch
{
using ImageSharp.Colors.Spaces;
/// <summary>
/// Converts from <see cref="CieLch"/> to <see cref="CieLab"/>.
/// </summary>
public class CieLchToCieLabConverter : IColorConversion<CieLch, CieLab>
{
/// <inheritdoc/>
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);
}
}
}

38
src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs

@ -0,0 +1,38 @@
// <copyright file="CieLabToCieLchConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieLch
{
using ImageSharp.Colors.Spaces;
/// <summary>
/// Converts from <see cref="CieLab"/> to <see cref="CieLch"/>.
/// </summary>
internal class CieLabToCieLchConverter : IColorConversion<CieLab, CieLch>
{
/// <inheritdoc/>
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);
}
}
}

2
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs

@ -48,7 +48,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Lms
/// <summary>
/// XYZ scaling chromatic adaptation transform matrix
/// </summary>
public static readonly Matrix4x4 XYZScaling = Matrix4x4.Transpose(Matrix4x4.Identity);
public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity);
/// <summary>
/// Bradford chromatic adaptation transform matrix (used in CMCCAT97)

132
src/ImageSharp/Common/Helpers/MathF.cs

@ -19,28 +19,89 @@ namespace ImageSharp
/// </summary>
public const float PI = (float)Math.PI;
/// <summary>Returns the absolute value of a single-precision floating-point number.</summary>
/// <param name="f">A number that is greater than or equal to <see cref="F:System.Single.MinValue" />, but less than or equal to <see cref="F:System.Single.MaxValue" />.</param>
/// <returns>A single-precision floating-point number, x, such that 0 ≤ x ≤<see cref="F:System.Single.MaxValue" />.</returns>
/// <summary>
/// Returns the absolute value of a single-precision floating-point number.
/// </summary>
/// <param name="f">
/// A number that is greater than or equal to <see cref="F:System.Single.MinValue" />, but less than or equal to <see cref="F:System.Single.MaxValue" />.
/// </param>
/// <returns>
/// A single-precision floating-point number, x, such that 0 ≤ x ≤<see cref="F:System.Single.MaxValue" />.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Abs(float f)
{
return Math.Abs(f);
}
/// <summary>Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number.</summary>
/// <param name="f">A single-precision floating-point number. </param>
/// <returns>The smallest integral value that is greater than or equal to <paramref name="f" />.
/// <summary>
/// Returns the angle whose tangent is the quotient of two specified numbers.
/// </summary>
/// <param name="y">The y coordinate of a point.</param>
/// <param name="x">The x coordinate of a point.</param>
/// <returns>
/// 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 &lt; θ &lt; π/2.For (x, y) in quadrant 2, π/2 &lt; θ≤π.For (x, y) in quadrant
/// 3, -π &lt; θ &lt; -π/2.For (x, y) in quadrant 4, -π/2 &lt; θ &lt; 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
/// <see cref="F:System.Single.NaN"/>, or if x and y are either <see cref="F:System.Single.PositiveInfinity"/> or
/// <see cref="F:System.Single.NegativeInfinity"/>, the method returns <see cref="F:System.Single.NaN"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Atan2(float y, float x)
{
return (float)Math.Atan2(y, x);
}
/// <summary>
/// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number.
/// </summary>
/// <param name="f">A single-precision floating-point number.</param>
/// <returns>
/// The smallest integral value that is greater than or equal to <paramref name="f" />.
/// If <paramref name="f" /> is equal to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NegativeInfinity" />,
/// or <see cref="F:System.Single.PositiveInfinity" />, that value is returned.
/// Note that this method returns a <see cref="T:System.Single" /> instead of an integral type.</returns>
/// Note that this method returns a <see cref="T:System.Single" /> instead of an integral type.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Ceiling(float f)
{
return (float)Math.Ceiling(f);
}
/// <summary>Returns e raised to the specified power.</summary>
/// <summary>
/// Returns the cosine of the specified angle.
/// </summary>
/// <param name="f">An angle, measured in radians.</param>
/// <returns>
/// The cosine of <paramref name="f"/>. If <paramref name="f"/> is equal to <see cref="F:System.Float.NaN"/>, <see cref="F:System.Float.NegativeInfinity"/>,
/// or <see cref="F:System.Float.PositiveInfinity"/>, this method returns <see cref="F:System.Float.NaN"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Cos(float f)
{
return (float)Math.Cos(f);
}
/// <summary>
/// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle.
/// </summary>
/// <param name="degree">The angle in degrees.</param>
/// <returns>
/// The <see cref="float"/> representing the degree as radians.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreeToRadian(float degree)
{
return degree * (PI / 180F);
}
/// <summary>
/// Returns e raised to the specified power.
/// </summary>
/// <param name="f">A number specifying a power.</param>
/// <returns>
/// The number e raised to the power <paramref name="f" />.
@ -53,9 +114,12 @@ namespace ImageSharp
return (float)Math.Exp(f);
}
/// <summary>Returns the largest integer less than or equal to the specified single-precision floating-point number.</summary>
/// <summary>
/// Returns the largest integer less than or equal to the specified single-precision floating-point number.
/// </summary>
/// <param name="f">A single-precision floating-point number. </param>
/// <returns>The largest integer less than or equal to <paramref name="f" />.
/// <returns>
/// The largest integer less than or equal to <paramref name="f" />.
/// If <paramref name="f" /> is equal to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NegativeInfinity" />,
/// or <see cref="F:System.Single.PositiveInfinity" />, that value is returned.
/// </returns>
@ -65,10 +129,13 @@ namespace ImageSharp
return (float)Math.Floor(f);
}
/// <summary>Returns the larger of two single-precision floating-point numbers.</summary>
/// <summary>
/// Returns the larger of two single-precision floating-point numbers.
/// </summary>
/// <param name="val1">The first of two single-precision floating-point numbers to compare. </param>
/// <param name="val2">The second of two single-precision floating-point numbers to compare. </param>
/// <returns>Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is larger.
/// <returns>
/// Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is larger.
/// If <paramref name="val1" />, or <paramref name="val2" />, or both <paramref name="val1" /> and <paramref name="val2" /> are
/// equal to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NaN" /> is returned.
/// </returns>
@ -78,19 +145,25 @@ namespace ImageSharp
return Math.Max(val1, val2);
}
/// <summary>Returns the smaller of two single-precision floating-point numbers.</summary>
/// <summary>
/// Returns the smaller of two single-precision floating-point numbers.
/// </summary>
/// <param name="val1">The first of two single-precision floating-point numbers to compare. </param>
/// <param name="val2">The second of two single-precision floating-point numbers to compare. </param>
/// <returns>Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is smaller.
/// <returns>
/// Parameter <paramref name="val1" /> or <paramref name="val2" />, whichever is smaller.
/// If <paramref name="val1" />, <paramref name="val2" />, or both <paramref name="val1" /> and <paramref name="val2" /> are equal
/// to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NaN" /> is returned.</returns>
/// to <see cref="F:System.Single.NaN" />, <see cref="F:System.Single.NaN" /> is returned.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Min(float val1, float val2)
{
return Math.Min(val1, val2);
}
/// <summary>Returns a specified number raised to the specified power.</summary>
/// <summary>
/// Returns a specified number raised to the specified power.
/// </summary>
/// <param name="x">A single-precision floating-point number to be raised to a power. </param>
/// <param name="y">A single-precision floating-point number that specifies a power. </param>
/// <returns>The number <paramref name="x" /> raised to the power <paramref name="y" />.</returns>
@ -100,7 +173,22 @@ namespace ImageSharp
return (float)Math.Pow(x, y);
}
/// <summary>Rounds a single-precision floating-point value to the nearest integral value.</summary>
/// <summary>
/// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle.
/// </summary>
/// <param name="radian">The angle in radians.</param>
/// <returns>
/// The <see cref="float"/> representing the degree as radians.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadianToDegree(float radian)
{
return radian / (PI / 180F);
}
/// <summary>
/// Rounds a single-precision floating-point value to the nearest integral value.
/// </summary>
/// <param name="f">A single-precision floating-point number to be rounded. </param>
/// <returns>
/// The integer nearest <paramref name="f" />.
@ -113,7 +201,9 @@ namespace ImageSharp
return (float)Math.Round(f);
}
/// <summary>Returns the sine of the specified angle.</summary>
/// <summary>
/// Returns the sine of the specified angle.
/// </summary>
/// <param name="f">An angle, measured in radians. </param>
/// <returns>
/// The sine of <paramref name="f" />.
@ -126,8 +216,10 @@ namespace ImageSharp
return (float)Math.Sin(f);
}
/// <summary>Returns the square root of a specified number.</summary>
/// <param name="f">The number whose square root is to be found. </param>
/// <summary>
/// Returns the square root of a specified number.
/// </summary>
/// <param name="f">The number whose square root is to be found.</param>
/// <returns>
/// One of the values in the following table.
/// <paramref name="f" /> parameter Return value Zero or positive The positive square root of <paramref name="f" />.

76
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;
/// <summary>
/// Tests <see cref="CieLab"/>-<see cref="CieLch"/> conversions.
/// </summary>
/// <remarks>
/// Test data generated using:
/// <see href="http://www.brucelindbloom.com/index.html?ColorCalculator.html"/>
/// </remarks>
public class CieLabAndCieLchConversionTests
{
private static readonly IEqualityComparer<float> FloatRoundingComparer = new FloatRoundingComparer(4);
private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter();
/// <summary>
/// Tests conversion from <see cref="CieLch"/> to <see cref="CieLab"/>.
/// </summary>
[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);
}
/// <summary>
/// Tests conversion from <see cref="LabColor"/> to <see cref="LChabColor"/>.
/// </summary>
[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);
}
}
}

6
tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs

@ -11,7 +11,7 @@
/// </summary>
/// <remarks>
/// Test data generated using:
/// http://www.brucelindbloom.com/index.html?ColorCalculator.html
/// <see href="http://www.brucelindbloom.com/index.html?ColorCalculator.html"/>
/// </remarks>
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);

8
tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndHunterLabConversionTest.cs

@ -11,7 +11,7 @@
/// </summary>
/// <remarks>
/// Test data generated using:
/// http://www.brucelindbloom.com/index.html?ColorCalculator.html
/// <see href="http://www.brucelindbloom.com/index.html?ColorCalculator.html"/>
/// </remarks>
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);

14
tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs

@ -8,6 +8,12 @@ namespace ImageSharp.Tests
using Xunit;
/// <summary>
/// Tests <see cref="ColorSpaceConverter.Adapt" /> methods.
/// Test data generated using:
/// <see cref="http://www.brucelindbloom.com/index.html?ChromAdaptCalc.html"/>
/// <see cref="http://www.brucelindbloom.com/index.html?ColorCalculator.html"/>
/// </summary>
public class ColorConverterAdaptTest
{
private static readonly IEqualityComparer<float> 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
};

24
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));
}
}
}
Loading…
Cancel
Save