Browse Source

Add XYZ, Lab, and Lms colorspaces

af/merge-core
James Jackson-South 9 years ago
parent
commit
9089b88592
  1. 187
      src/ImageSharp/Colors/Spaces/CieLab.cs
  2. 155
      src/ImageSharp/Colors/Spaces/CieXyz.cs
  3. 24
      src/ImageSharp/Colors/Spaces/Conversion/CieConstants.cs
  4. 33
      src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Adapt.cs
  5. 35
      src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieLab.cs
  6. 52
      src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieXyz.cs
  7. 29
      src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Lms.cs
  8. 81
      src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.cs
  9. 27
      src/ImageSharp/Colors/Spaces/Conversion/IChromaticAdaptation.cs
  10. 22
      src/ImageSharp/Colors/Spaces/Conversion/IColorConversion.cs
  11. 48
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
  12. 59
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
  13. 73
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
  14. 101
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
  15. 91
      src/ImageSharp/Colors/Spaces/Conversion/VonKriesChromaticAdaptation.cs
  16. 30
      src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs
  17. 20
      src/ImageSharp/Colors/Spaces/IColorVector.cs
  18. 33
      src/ImageSharp/Colors/Spaces/ICompanding.cs
  19. 32
      src/ImageSharp/Colors/Spaces/IRgbWorkingSpace.cs
  20. 71
      src/ImageSharp/Colors/Spaces/Illuminants.cs
  21. 156
      src/ImageSharp/Colors/Spaces/Lms.cs
  22. 72
      tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndCieLabConversionTest.cs
  23. 68
      tests/ImageSharp.Tests/Colors/Colorspaces/CieXyzAndLmsConversionTest.cs
  24. 37
      tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs

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

@ -0,0 +1,187 @@
// <copyright file="CieLab.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 an CIE LAB 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
/// </summary>
public struct CieLab : IColorVector, IEquatable<CieLab>, IAlmostEquatable<CieLab, 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="CieLab"/> that has L, A, B values set to zero.
/// </summary>
public static readonly CieLab Empty = default(CieLab);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieLab"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="a">The a (green - magenta) component.</param>
/// <param name="b">The b (blue - yellow) component.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
public CieLab(float l, float a, float b)
: this(new Vector3(l, a, b), DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLab"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="a">The a (green - magenta) component.</param>
/// <param name="b">The b (blue - yellow) component.</param>
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
public CieLab(float l, float a, float b, CieXyz whitePoint)
: this(new Vector3(l, a, b), whitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLab"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, a, b components.</param>
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
public CieLab(Vector3 vector)
: this(vector, DefaultWhitePoint)
{
}
/// <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="whitePoint">The reference white point. <see cref="Illuminants"/></param>
public CieLab(Vector3 vector, CieXyz whitePoint)
: this()
{
this.backingVector = vector;
this.WhitePoint = whitePoint;
}
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 color component.
/// <remarks>A value ranging from -100 to 100. Negative is green, positive magenta.</remarks>
/// </summary>
public float A => this.backingVector.Y;
/// <summary>
/// Gets the b color component.
/// <remarks>A value ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
/// </summary>
public float B => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="CieLab"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector => this.backingVector;
/// <summary>
/// Compares two <see cref="CieLab"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieLab"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLab"/> 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 ==(CieLab left, CieLab right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieLab"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="CieLab"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLab"/> 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 !=(CieLab left, CieLab right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieLab [Empty]";
}
return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is CieLab)
{
return this.Equals((CieLab)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(CieLab other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
public bool AlmostEquals(CieLab other, float precision)
{
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

155
src/ImageSharp/Colors/Spaces/CieXyz.cs

@ -0,0 +1,155 @@
// <copyright file="CieXyz.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 an CIE 1931 color
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space"/>
/// </summary>
public struct CieXyz : IColorVector, IEquatable<CieXyz>, IAlmostEquatable<CieXyz, float>
{
/// <summary>
/// Represents a <see cref="CieXyz"/> that has Y, Cb, and Cr values set to zero.
/// </summary>
public static readonly CieXyz Empty = default(CieXyz);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieXyz"/> struct.
/// </summary>
/// <param name="x">X is a mix (a linear combination) of cone response curves chosen to be nonnegative</param>
/// <param name="y">The y luminance component.</param>
/// <param name="z">Z is quasi-equal to blue stimulation, or the S cone of the human eye.</param>
public CieXyz(float x, float y, float z)
: this(new Vector3(x, y, z))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyz"/> struct.
/// </summary>
/// <param name="vector">The vector representing the x, y, z components.</param>
public CieXyz(Vector3 vector)
: this()
{
// Not clamping as documentation about this space seems to indicate "usual" ranges
this.backingVector = vector;
}
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float X => this.backingVector.X;
/// <summary>
/// Gets the Cb chroma component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float Y => this.backingVector.Y;
/// <summary>
/// Gets the Cr chroma component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float Z => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="CieXyz"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector => this.backingVector;
/// <summary>
/// Compares two <see cref="CieXyz"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieXyz"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieXyz"/> 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 ==(CieXyz left, CieXyz right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieXyz"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="CieXyz"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieXyz"/> 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 !=(CieXyz left, CieXyz right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieXyz [ Empty ]";
}
return $"CieXyz [ X={this.X:#0.##}, Y={this.Y:#0.##}, Z={this.Z:#0.##} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is CieXyz)
{
return this.Equals((CieXyz)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(CieXyz other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
public bool AlmostEquals(CieXyz other, float precision)
{
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

24
src/ImageSharp/Colors/Spaces/Conversion/CieConstants.cs

@ -0,0 +1,24 @@
// <copyright file="CieConstants.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
/// <summary>
/// Constants use for Cie conversion calculations
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html"/>
/// </summary>
internal static class CieConstants
{
/// <summary>
/// 216F / 24389F
/// </summary>
public const float Epsilon = 0.008856452F;
/// <summary>
/// 24389F / 27F
/// </summary>
public const float Kappa = 903.2963F;
}
}

33
src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Adapt.cs

@ -0,0 +1,33 @@
// <copyright file="ColorConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
using System;
using Spaces;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorConverter
{
/// <summary>
/// Performs chromatic adaptation of given XYZ color.
/// Target white point is <see cref="WhitePoint"/>.
/// </summary>
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);
}
}
}

35
src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieLab.cs

@ -0,0 +1,35 @@
// <copyright file="ColorConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
using Implementation;
using Spaces;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorConverter
{
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="CieLab"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieLab"/></returns>
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);
}
}
}

52
src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.CieXyz.cs

@ -0,0 +1,52 @@
// <copyright file="ColorConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
using Implementation;
using Spaces;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorConverter
{
private static readonly CieLabToCieXyzConverter CieLabToCieXyzConverter = new CieLabToCieXyzConverter();
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
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;
}
/// <summary>
/// Converts a <see cref="Lms"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(Lms color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return this.cachedCieXyzAndLmsConverter.Convert(color);
}
}
}

29
src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.Lms.cs

@ -0,0 +1,29 @@
// <copyright file="ColorConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
using Implementation;
using Spaces;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorConverter
{
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="Lms"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Lms"/></returns>
public Lms ToLms(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return this.cachedCieXyzAndLmsConverter.Convert(color);
}
}
}

81
src/ImageSharp/Colors/Spaces/Conversion/ColorConverter.cs

@ -0,0 +1,81 @@
// <copyright file="ColorConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
using System.Numerics;
using Implementation;
using Spaces;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorConverter
{
private Matrix4x4 transformationMatrix;
private CieXyzAndLmsConverter cachedCieXyzAndLmsConverter;
/// <summary>
/// The default whitepoint used for converting to CieLab
/// </summary>
public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
/// <summary>
/// Initializes a new instance of the <see cref="ColorConverter"/> class.
/// </summary>
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;
}
/// <summary>
/// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space.
/// When null, no adaptation will be performed.
/// </summary>
public CieXyz WhitePoint { get; set; }
/// <summary>
/// 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: <see cref="CieLab.DefaultWhitePoint"/>.
/// </summary>
public CieXyz TargetLabWhitePoint { get; set; }
/// <summary>
/// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed.
/// </summary>
public IChromaticAdaptation ChromaticAdaptation { get; set; }
/// <summary>
/// Gets or sets transformation matrix used in conversion to <see cref="Lms"/>,
/// also used in the default Von Kries Chromatic Adaptation method.
/// </summary>
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;
}
}
}
/// <summary>
/// Gets a value indicating whether chromatic adaptation has been performed.
/// </summary>
private bool IsChromaticAdaptationPerformed => this.ChromaticAdaptation != null;
}
}

27
src/ImageSharp/Colors/Spaces/Conversion/IChromaticAdaptation.cs

@ -0,0 +1,27 @@
// <copyright file="IChromaticAdaptation.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
using Spaces;
/// <summary>
/// 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).
/// </summary>
public interface IChromaticAdaptation
{
/// <summary>
/// Performs a linear transformation of a source color in to the destination color.
/// </summary>
/// <remarks>Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).</remarks>
/// <param name="sourceColor">The source color.</param>
/// <param name="sourceWhitePoint">The source white point.</param>
/// <param name="targetWhitePoint">The target white point.</param>
/// <returns>The <see cref="CieXyz"/></returns>
CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint);
}
}

22
src/ImageSharp/Colors/Spaces/Conversion/IColorConversion.cs

@ -0,0 +1,22 @@
// <copyright file="IColorConversion.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
/// <summary>
/// Converts color between two color spaces.
/// </summary>
/// <typeparam name="T">The input color type.</typeparam>
/// <typeparam name="TResult">The result color type.</typeparam>
public interface IColorConversion<in T, out TResult>
{
/// <summary>
/// Performs the conversion from the <see cref="T"/> input to an instance of the output <see cref="TResult"/> type.
/// </summary>
/// <param name="input">The input color instance.</param>
/// <returns>The <see cref="TResult"/></returns>
TResult Convert(T input);
}
}

48
src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs

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

59
src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs

@ -0,0 +1,59 @@
// <copyright file="CieXyzToCieLabConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion.Implementation
{
using System;
using Spaces;
/// <summary>
/// Converts from <see cref="CieXyz"/> to <see cref="CieLab"/>.
/// </summary>
public class CieXyzToCieLabConverter : IColorConversion<CieXyz, CieLab>
{
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToCieLabConverter"/> class.
/// </summary>
public CieXyzToCieLabConverter()
: this(CieLab.DefaultWhitePoint)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToCieLabConverter"/> class.
/// </summary>
/// <param name="labWhitePoint">The target reference lab white point</param>
public CieXyzToCieLabConverter(CieXyz labWhitePoint)
{
this.LabWhitePoint = labWhitePoint;
}
/// <summary>
/// Gets the target reference whitepoint. When not set, <see cref="CieLab.DefaultWhitePoint"/> is used.
/// </summary>
public CieXyz LabWhitePoint { get; }
/// <inheritdoc />
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);
}
}
}

73
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs

@ -0,0 +1,73 @@
// <copyright file="CieXyzAndLmsConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Numerics;
namespace ImageSharp.Colors.Conversion.Implementation
{
using Spaces;
public class CieXyzAndLmsConverter : IColorConversion<CieXyz, Lms>, IColorConversion<Lms, CieXyz>
{
/// <summary>
/// Default transformation matrix used, when no other is set. (Bradford)
/// <see cref="LmsAdaptationMatrix"/>
/// </summary>
public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford;
private Matrix4x4 inverseTransformationMatrix;
private Matrix4x4 transformationMatrix;
/// <summary>
/// Transformation matrix used for the conversion (definition of the cone response domain).
/// <see cref="LmsAdaptationMatrix"/>
/// </summary>
public Matrix4x4 TransformationMatrix
{
get { return this.transformationMatrix; }
internal set
{
this.transformationMatrix = value;
Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzAndLmsConverter"/> class.
/// </summary>
public CieXyzAndLmsConverter()
: this(DefaultTransformationMatrix)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzAndLmsConverter"/> class.
/// </summary>
/// <param name="transformationMatrix">
/// Definition of the cone response domain (see <see cref="LmsAdaptationMatrix"/>),
/// if not set <see cref="DefaultTransformationMatrix"/> will be used.
/// </param>
public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix)
{
this.TransformationMatrix = transformationMatrix;
}
/// <inheritdoc/>
public Lms Convert(CieXyz input)
{
Guard.NotNull(input, nameof(input));
Vector3 vector = Vector3.Transform(input.Vector, this.transformationMatrix);
return new Lms(vector);
}
/// <inheritdoc/>
public CieXyz Convert(Lms input)
{
Vector3 vector = Vector3.Transform(input.Vector, this.inverseTransformationMatrix);
return new CieXyz(vector);
}
}
}

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

@ -0,0 +1,101 @@
// <copyright file="LmsAdaptationMatrix.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
namespace ImageSharp.Colors.Conversion.Implementation
{
using System.Numerics;
/// <summary>
/// AdaptionMatrix3X3 used for transformation from XYZ to LMS, defining the cone response domain.
/// Used in <see cref="IChromaticAdaptation"/>
/// </summary>
/// <remarks>
/// 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
/// </remarks>
public static class LmsAdaptationMatrix
{
/// <summary>
/// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65)
/// </summary>
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.
};
/// <summary>
/// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy)
/// </summary>
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
};
/// <summary>
/// XYZ scaling chromatic adaptation transform matrix
/// </summary>
public static readonly Matrix4x4 XYZScaling = Matrix4x4.Identity;
/// <summary>
/// Bradford chromatic adaptation transform matrix (used in CMCCAT97)
/// </summary>
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
};
/// <summary>
/// Spectral sharpening and the Bradford transform
/// </summary>
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
};
/// <summary>
/// CMCCAT2000 (fitted from all available color data sets)
/// </summary>
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
};
/// <summary>
/// CAT02 (optimized for minimizing CIELAB differences)
/// </summary>
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
};
}
}

91
src/ImageSharp/Colors/Spaces/Conversion/VonKriesChromaticAdaptation.cs

@ -0,0 +1,91 @@
// <copyright file="VonKriesChromaticAdaptation.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Conversion
{
using System.Numerics;
using Implementation;
using Spaces;
/// <summary>
/// Basic implementation of the von Kries chromatic adaptation model
/// </summary>
/// <remarks>
/// Transformation described here:
/// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
/// </remarks>
public class VonKriesChromaticAdaptation : IChromaticAdaptation
{
private readonly IColorConversion<CieXyz, Lms> conversionToLms;
private readonly IColorConversion<Lms, CieXyz> conversionToXyz;
/// <summary>
/// Initializes a new instance of the <see cref="VonKriesChromaticAdaptation"/> class.
/// </summary>
public VonKriesChromaticAdaptation()
: this(new CieXyzAndLmsConverter())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="VonKriesChromaticAdaptation"/> class.
/// </summary>
/// <param name="transformationMatrix">
/// The transformation matrix used for the conversion (definition of the cone response domain).
/// <see cref="LmsAdaptationMatrix"/>
/// </param>
public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix)
: this(new CieXyzAndLmsConverter(transformationMatrix))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="VonKriesChromaticAdaptation"/> class.
/// </summary>
/// <param name="converter"></param>
private VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter)
: this(converter, converter)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="VonKriesChromaticAdaptation"/> class.
/// </summary>
/// <param name="conversionToLms">The <see cref="Lms"/> color converter.</param>
/// <param name="conversionToCieXyz">The <see cref="CieXyz"/> color converter.</param>
public VonKriesChromaticAdaptation(IColorConversion<CieXyz, Lms> conversionToLms, IColorConversion<Lms, CieXyz> conversionToCieXyz)
{
Guard.NotNull(conversionToLms, nameof(conversionToLms));
Guard.NotNull(conversionToCieXyz, nameof(conversionToCieXyz));
this.conversionToLms = conversionToLms;
this.conversionToXyz = conversionToCieXyz;
}
/// <inheritdoc/>
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);
}
}
}

30
src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs

@ -0,0 +1,30 @@
// <copyright file="IAlmostEquatable.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;
/// <summary>
/// Defines a generalized method that a value type or class implements to create
/// a type-specific method for determining approximate equality of instances.
/// </summary>
/// <typeparam name="TColor">The type of objects to compare.</typeparam>
/// <typeparam name="TPrecision">The object specifying the type to specify precision with.</typeparam>
public interface IAlmostEquatable<in TColor, in TPrecision>
where TPrecision : struct, IComparable<TPrecision>
{
/// <summary>
/// Indicates whether the current object is equal to another object of the same type
/// when compared to the specified precision level.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <param name="precision">The object specifying the level of precision.</param>
/// <returns>
/// true if the current object is equal to the other parameter; otherwise, false.
/// </returns>
bool AlmostEquals(TColor other, TPrecision precision);
}
}

20
src/ImageSharp/Colors/Spaces/IColorVector.cs

@ -0,0 +1,20 @@
// <copyright file="IColorVector.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.Numerics;
/// <summary>
/// Color represented as a vector in its color space
/// </summary>
public interface IColorVector
{
/// <summary>
/// The vector representation of the color
/// </summary>
Vector3 Vector { get; }
}
}

33
src/ImageSharp/Colors/Spaces/ICompanding.cs

@ -0,0 +1,33 @@
// <copyright file="ICompanding.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
{
/// <summary>
/// Pair of companding functions for <see cref="IRgbWorkingSpace"/>.
/// Used for conversion to <see cref="CieXyz"/> and backwards.
/// See also: <seealso cref="IRgbWorkingSpace.Companding"/>
/// </summary>
public interface ICompanding
{
/// <summary>
/// Companded channel is made linear with respect to the energy.
/// </summary>
/// <remarks>
/// For more info see:
/// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
/// </remarks>
float InverseCompanding(float channel);
/// <summary>
/// Uncompanded channel (linear) is made nonlinear (depends on the RGB color system).
/// </summary>
/// <remarks>
/// For more info see:
/// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
/// </remarks>
float Companding(float channel);
}
}

32
src/ImageSharp/Colors/Spaces/IRgbWorkingSpace.cs

@ -0,0 +1,32 @@
// <copyright file="IRgbWorkingSpace.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
{
/// <summary>
/// Encasulates the RGB working color space
/// </summary>
public interface IRgbWorkingSpace
{
/// <summary>
/// Gets the reference white of the color space
/// </summary>
CieXyz WhitePoint { get; }
/// <summary>
/// Chromaticity coordinates of the primaries
/// </summary>
// RGBPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
/// <summary>
/// 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
/// </summary>
ICompanding Companding { get; }
}
}

71
src/ImageSharp/Colors/Spaces/Illuminants.cs

@ -0,0 +1,71 @@
namespace ImageSharp.Colors.Spaces
{
/// <summary>
/// The well known standard illuminants.
/// Standard illuminants provide a basis for comparing images or colors recorded under different lighting
/// </summary>
/// <remarks>
/// Coefficients taken from:
/// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
/// <br />
/// Descriptions taken from:
/// http://en.wikipedia.org/wiki/Standard_illuminant
/// </remarks>
public static class Illuminants
{
/// <summary>
/// Incandescent / Tungsten
/// </summary>
public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F);
/// <summary>
/// Direct sunlight at noon (obsoleteF)
/// </summary>
public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F);
/// <summary>
/// Average / North sky Daylight (obsoleteF)
/// </summary>
public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F);
/// <summary>
/// Horizon Light. ICC profile PCS
/// </summary>
public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F);
/// <summary>
/// Mid-morning / Mid-afternoon Daylight
/// </summary>
public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F);
/// <summary>
/// Noon Daylight: TelevisionF, sRGB color space
/// </summary>
public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F);
/// <summary>
/// North sky Daylight
/// </summary>
public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F);
/// <summary>
/// Equal energy
/// </summary>
public static readonly CieXyz E = new CieXyz(1F, 1F, 1F);
/// <summary>
/// Cool White Fluorescent
/// </summary>
public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F);
/// <summary>
/// D65 simulatorF, Daylight simulator
/// </summary>
public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F);
/// <summary>
/// Philips TL84F, Ultralume 40
/// </summary>
public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F);
}
}

156
src/ImageSharp/Colors/Spaces/Lms.cs

@ -0,0 +1,156 @@
// <copyright file="Lms.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>
/// 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.
/// <see href="https://en.wikipedia.org/wiki/LMS_color_space"/>
/// </summary>
public struct Lms : IColorVector, IEquatable<Lms>, IAlmostEquatable<Lms, float>
{
/// <summary>
/// Represents a <see cref="Lms"/> that has Y, Cb, and Cr values set to zero.
/// </summary>
public static readonly Lms Empty = default(Lms);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Lms"/> struct.
/// </summary>
/// <param name="l">L represents the responsivity at long wavelengths.</param>
/// <param name="m">M represents the responsivity at medium wavelengths.</param>
/// <param name="s">S represents the responsivity at short wavelengths.</param>
public Lms(float l, float m, float s)
: this(new Vector3(l, m, s))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Lms"/> struct.
/// </summary>
/// <param name="vector">The vector representing the x, y, z components.</param>
public Lms(Vector3 vector)
: this()
{
// Not clamping as documentation about this space seems to indicate "usual" ranges
this.backingVector = vector;
}
/// <summary>
/// Gets the L long component.
/// <remarks>A value usually ranging between -1 and 1.</remarks>
/// </summary>
public float L => this.backingVector.X;
/// <summary>
/// Gets the M medium component.
/// <remarks>A value usually ranging between -1 and 1.</remarks>
/// </summary>
public float M => this.backingVector.Y;
/// <summary>
/// Gets the S short component.
/// <remarks>A value usually ranging between -1 and 1.</remarks>
/// </summary>
public float S => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="Lms"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector => this.backingVector;
/// <summary>
/// Compares two <see cref="Lms"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Lms"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Lms"/> 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 ==(Lms left, Lms right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Lms"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Lms"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Lms"/> 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 !=(Lms left, Lms right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Lms [ Empty ]";
}
return $"Lms [ L={this.L:#0.##}, M={this.M:#0.##}, S={this.S:#0.##} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Lms)
{
return this.Equals((Lms)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Lms other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
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;
}
}
}

72
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;
/// <summary>
/// Tests <see cref="CieXyz"/>-<see cref="CieLab"/> conversions.
/// </summary>
/// <remarks>
/// Test data generated using:
/// http://www.brucelindbloom.com/index.html?ColorCalculator.html
/// </remarks>
public class CieXyzAndCieLabConversionTest
{
private static readonly IEqualityComparer<float> FloatComparerLabPrecision = new ApproximateFloatComparer(4);
private static readonly IEqualityComparer<float> FloatComparerXyzPrecision = new ApproximateFloatComparer(6);
/// <summary>
/// Tests conversion from <see cref="CieLab"/> to <see cref="CieXyz"/> (<see cref="Illuminants.D65"/>).
/// </summary>
[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);
}
/// <summary>
/// Tests conversion from <see cref="CieXyz"/> (<see cref="Illuminants.D65"/>) to <see cref="CieLab"/>.
/// </summary>
[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);
}
}
}

68
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;
/// <summary>
/// Tests <see cref="CieXyz"/>-<see cref="CieLab"/> conversions.
/// </summary>
/// <remarks>
/// Test data generated using original colorful library.
/// </remarks>
public class CieXyzAndLmsConversionTest
{
private static readonly IEqualityComparer<float> FloatComparer = new ApproximateFloatComparer(6);
/// <summary>
/// Tests conversion from <see cref="CieXyz"/> (<see cref="Illuminants.D65"/>) to <see cref="Lms"/>.
/// </summary>
[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);
}
/// <summary>
/// Tests conversion from <see cref="CieXyz"/> (<see cref="Illuminants.D65"/>) to <see cref="Lms"/>.
/// </summary>
[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);
}
}
}

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