mirror of https://github.com/SixLabors/ImageSharp
18 changed files with 1212 additions and 2 deletions
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <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; |
|||
} |
|||
@ -0,0 +1,177 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents a CIE L*a*b* 1976 color.
|
|||
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
|
|||
/// </summary>
|
|||
public readonly struct CieLab : IProfileConnectingSpace<CieLab, CieXyz> |
|||
{ |
|||
/// <summary>
|
|||
/// D50 standard illuminant.
|
|||
/// Used when reference white is not specified explicitly.
|
|||
/// </summary>
|
|||
public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; |
|||
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLab(float l, float a, float b) |
|||
: this(new Vector3(l, a, b)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l, a, b components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLab(Vector3 vector) |
|||
: this() |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.L = vector.X; |
|||
this.A = vector.Y; |
|||
this.B = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the lightness dimension.
|
|||
/// <remarks>A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
|
|||
/// </summary>
|
|||
public readonly float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the a color component.
|
|||
/// <remarks>A value usually ranging from -100 to 100. Negative is green, positive magenta.</remarks>
|
|||
/// </summary>
|
|||
public readonly float A { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the b color component.
|
|||
/// <remarks>A value usually ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
|
|||
/// </summary>
|
|||
public readonly float B { get; } |
|||
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(CieLab left, CieLab right) => 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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is CieLab other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(CieLab other) => |
|||
this.L.Equals(other.L) |
|||
&& this.A.Equals(other.A) |
|||
&& this.B.Equals(other.B); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static CieLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
|
|||
CieXyz whitePoint = options.TargetWhitePoint; |
|||
float wx = whitePoint.X, wy = whitePoint.Y, wz = whitePoint.Z; |
|||
|
|||
float xr = source.X / wx, yr = source.Y / wy, zr = source.Z / wz; |
|||
|
|||
const float inv116 = 1 / 116F; |
|||
|
|||
float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) * inv116; |
|||
float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) * inv116; |
|||
float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) * inv116; |
|||
|
|||
float l = (116F * fy) - 16F; |
|||
float a = 500F * (fx - fy); |
|||
float b = 200F * (fy - fz); |
|||
|
|||
return new CieLab(l, a, b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieLab> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyz xyz = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in xyz); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
|
|||
float l = this.L, a = this.A, b = this.B; |
|||
float fy = (l + 16) / 116F; |
|||
float fx = (a / 500F) + fy; |
|||
float fz = fy - (b / 200F); |
|||
|
|||
float fx3 = Numerics.Pow3(fx); |
|||
float fz3 = Numerics.Pow3(fz); |
|||
|
|||
float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa; |
|||
float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? Numerics.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa; |
|||
float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa; |
|||
|
|||
CieXyz whitePoint = options.WhitePoint; |
|||
Vector3 wxyz = new(whitePoint.X, whitePoint.Y, whitePoint.Z); |
|||
|
|||
// Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white)
|
|||
Vector3 xyzr = Vector3.Clamp(new Vector3(xr, yr, zr), Vector3.Zero, Vector3.One); |
|||
|
|||
return new(xyzr * wxyz); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieLab> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieLab lab = source[i]; |
|||
destination[i] = lab.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents an CIE XYZ 1931 color
|
|||
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#Definition_of_the_CIE_XYZ_color_space"/>
|
|||
/// </summary>
|
|||
public readonly struct CieXyz : IProfileConnectingSpace<CieXyz, CieXyz> |
|||
{ |
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
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 only indicates "usual" ranges
|
|||
this.X = vector.X; |
|||
this.Y = vector.Y; |
|||
this.Z = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float X { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y luminance component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Y { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Z { get; } |
|||
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(CieXyz left, CieXyz right) => 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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(CieXyz left, CieXyz right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="Vector3"/> representing this instance.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Vector3"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector3 ToVector3() => new(this.X, this.Y, this.Z); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"CieXyz({this.X:#0.##}, {this.Y:#0.##}, {this.Z:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is CieXyz other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(CieXyz other) |
|||
=> this.X.Equals(other.X) |
|||
&& this.Y.Equals(other.Y) |
|||
&& this.Z.Equals(other.Z); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieXyz FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
=> new(source.X, source.Y, source.Z); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
source.CopyTo(destination[..source.Length]); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
=> new(this.X, this.Y, this.Z); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
source.CopyTo(destination[..source.Length]); |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Provides options for color profile conversion.
|
|||
/// </summary>
|
|||
public class ColorConversionOptions |
|||
{ |
|||
private Matrix4x4 adaptationMatrix; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorConversionOptions"/> class.
|
|||
/// </summary>
|
|||
public ColorConversionOptions() => this.AdaptationMatrix = LmsAdaptationMatrix.Bradford; |
|||
|
|||
/// <summary>
|
|||
/// Gets the memory allocator.
|
|||
/// </summary>
|
|||
public MemoryAllocator MemoryAllocator { get; init; } = MemoryAllocator.Default; |
|||
|
|||
/// <summary>
|
|||
/// Gets the source white point used for chromatic adaptation in conversions from/to XYZ color space.
|
|||
/// </summary>
|
|||
public CieXyz WhitePoint { get; init; } = Illuminants.D50; |
|||
|
|||
/// <summary>
|
|||
/// Gets the destination white point used for chromatic adaptation in conversions from/to XYZ color space.
|
|||
/// </summary>
|
|||
public CieXyz TargetWhitePoint { get; init; } = Illuminants.D50; |
|||
|
|||
/// <summary>
|
|||
/// Gets the transformation matrix used in conversion to perform chromatic adaptation.
|
|||
/// </summary>
|
|||
public Matrix4x4 AdaptationMatrix |
|||
{ |
|||
get => this.adaptationMatrix; |
|||
init |
|||
{ |
|||
this.adaptationMatrix = value; |
|||
Matrix4x4.Invert(value, out Matrix4x4 inverted); |
|||
this.InverseAdaptationMatrix = inverted; |
|||
} |
|||
} |
|||
|
|||
internal Matrix4x4 InverseAdaptationMatrix { get; private set; } |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows the conversion of color profiles.
|
|||
/// </summary>
|
|||
public class ColorProfileConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorProfileConverter"/> class.
|
|||
/// </summary>
|
|||
public ColorProfileConverter() |
|||
: this(new()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorProfileConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
public ColorProfileConverter(ColorConversionOptions options) |
|||
=> this.Options = options; |
|||
|
|||
/// <summary>
|
|||
/// Gets the color profile conversion options.
|
|||
/// </summary>
|
|||
public ColorConversionOptions Options { get; } |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
internal static class ColorProfileConverterExtensionsCieLabCieXyz |
|||
{ |
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, CieLab> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
CieLab pcsFrom = source.ToProfileConnectingSpace(options); |
|||
|
|||
// Convert between PCS
|
|||
CieXyz pcsTo = pcsFrom.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
VonKriesChromaticAdaptation.Transform(options, in pcsTo); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, pcsTo); |
|||
} |
|||
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, CieLab> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<CieLab> pcsFromOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length); |
|||
Span<CieLab> pcsFrom = pcsFromOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFrom); |
|||
|
|||
// Convert between PCS.
|
|||
using IMemoryOwner<CieXyz> pcsToOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length * 2); |
|||
Span<CieXyz> pcsTo = pcsToOwner.GetSpan()[..source.Length]; |
|||
CieLab.ToProfileConnectionSpace(options, pcsFrom, pcsTo); |
|||
|
|||
// Adapt to target white point
|
|||
VonKriesChromaticAdaptation.Transform(options, pcsTo, pcsTo); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
internal static class ColorProfileConverterExtensionsCieXyzCieXyz |
|||
{ |
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, CieXyz> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
CieXyz pcsFrom = source.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
VonKriesChromaticAdaptation.Transform(options, in pcsFrom); |
|||
|
|||
// Convert between PCS
|
|||
CieXyz pcsTo = CieXyz.FromProfileConnectingSpace(options, in pcsFrom); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, pcsTo); |
|||
} |
|||
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, CieXyz> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<CieXyz> pcsFromOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsFrom = pcsFromOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFrom); |
|||
|
|||
// Adapt to target white point
|
|||
VonKriesChromaticAdaptation.Transform(options, pcsFrom, pcsFrom); |
|||
|
|||
// Convert between PCS.
|
|||
using IMemoryOwner<CieXyz> pcsToOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsTo = pcsToOwner.GetSpan(); |
|||
CieXyz.FromProfileConnectionSpace(options, pcsFrom, pcsTo); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for all color profiles.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSelf">The type of color profile.</typeparam>
|
|||
/// <typeparam name="TProfileSpace">The type of color profile connecting space.</typeparam>
|
|||
public interface IColorProfile<TSelf, TProfileSpace> : IEquatable<TSelf> |
|||
where TSelf : IColorProfile<TSelf, TProfileSpace> |
|||
where TProfileSpace : struct, IProfileConnectingSpace |
|||
{ |
|||
/// <summary>
|
|||
/// Converts the color to the profile connection space.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
/// <returns>The <typeparamref name="TProfileSpace"/>.</returns>
|
|||
public TProfileSpace ToProfileConnectingSpace(ColorConversionOptions options); |
|||
|
|||
#pragma warning disable CA1000 // Do not declare static members on generic types
|
|||
/// <summary>
|
|||
/// Converts the color from the profile connection space.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
/// <param name="source">The color profile connecting space.</param>
|
|||
/// <returns>The <typeparamref name="TSelf"/>.</returns>
|
|||
public static abstract TSelf FromProfileConnectingSpace(ColorConversionOptions options, in TProfileSpace source); |
|||
|
|||
/// <summary>
|
|||
/// Converts the span of colors to the profile connection space.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
/// <param name="source">The color span to convert from.</param>
|
|||
/// <param name="destination">The color profile span to write the results to.</param>
|
|||
public static abstract void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<TSelf> source, Span<TProfileSpace> destination); |
|||
|
|||
/// <summary>
|
|||
/// Converts the span of colors from the profile connection space.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
/// <param name="source">The color profile span to convert from.</param>
|
|||
/// <param name="destination">The color span to write the results to.</param>
|
|||
public static abstract void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<TProfileSpace> source, Span<TSelf> destination); |
|||
#pragma warning restore CA1000 // Do not declare static members on generic types
|
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for all color profile connection spaces.
|
|||
/// </summary>
|
|||
public interface IProfileConnectingSpace; |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for all color profile connection spaces.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSelf">The type of color profile.</typeparam>
|
|||
/// <typeparam name="TProfileSpace">The type of color profile connecting space.</typeparam>
|
|||
public interface IProfileConnectingSpace<TSelf, TProfileSpace> : IColorProfile<TSelf, TProfileSpace>, IProfileConnectingSpace |
|||
where TSelf : struct, IColorProfile<TSelf, TProfileSpace>, IProfileConnectingSpace |
|||
where TProfileSpace : struct, IProfileConnectingSpace; |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <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(1.09850F, 1F, 0.35585F); |
|||
|
|||
/// <summary>
|
|||
/// Direct sunlight at noon (obsoleteF)
|
|||
/// </summary>
|
|||
public static readonly CieXyz B = new(0.99072F, 1F, 0.85223F); |
|||
|
|||
/// <summary>
|
|||
/// Average / North sky Daylight (obsoleteF)
|
|||
/// </summary>
|
|||
public static readonly CieXyz C = new(0.98074F, 1F, 1.18232F); |
|||
|
|||
/// <summary>
|
|||
/// Horizon Light. ICC profile PCS
|
|||
/// </summary>
|
|||
public static readonly CieXyz D50 = new(0.96422F, 1F, 0.82521F); |
|||
|
|||
/// <summary>
|
|||
/// Mid-morning / Mid-afternoon Daylight
|
|||
/// </summary>
|
|||
public static readonly CieXyz D55 = new(0.95682F, 1F, 0.92149F); |
|||
|
|||
/// <summary>
|
|||
/// Noon Daylight: TelevisionF, sRGB color space
|
|||
/// </summary>
|
|||
public static readonly CieXyz D65 = new(0.95047F, 1F, 1.08883F); |
|||
|
|||
/// <summary>
|
|||
/// North sky Daylight
|
|||
/// </summary>
|
|||
public static readonly CieXyz D75 = new(0.94972F, 1F, 1.22638F); |
|||
|
|||
/// <summary>
|
|||
/// Equal energy
|
|||
/// </summary>
|
|||
public static readonly CieXyz E = new(1F, 1F, 1F); |
|||
|
|||
/// <summary>
|
|||
/// Cool White Fluorescent
|
|||
/// </summary>
|
|||
public static readonly CieXyz F2 = new(0.99186F, 1F, 0.67393F); |
|||
|
|||
/// <summary>
|
|||
/// D65 simulatorF, Daylight simulator
|
|||
/// </summary>
|
|||
public static readonly CieXyz F7 = new(0.95041F, 1F, 1.08747F); |
|||
|
|||
/// <summary>
|
|||
/// Philips TL84F, Ultralume 40
|
|||
/// </summary>
|
|||
public static readonly CieXyz F11 = new(1.00962F, 1F, 0.64350F); |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
internal readonly struct Lms : IColorProfile<Lms, CieXyz> |
|||
{ |
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
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 l, m, s components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Lms(Vector3 vector) |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.L = vector.X; |
|||
this.M = vector.Y; |
|||
this.S = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the L long component.
|
|||
/// <remarks>A value usually ranging between -1 and 1.</remarks>
|
|||
/// </summary>
|
|||
public readonly float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the M medium component.
|
|||
/// <remarks>A value usually ranging between -1 and 1.</remarks>
|
|||
/// </summary>
|
|||
public readonly float M { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the S short component.
|
|||
/// <remarks>A value usually ranging between -1 and 1.</remarks>
|
|||
/// </summary>
|
|||
public readonly float S { get; } |
|||
|
|||
/// <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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Lms left, Lms right) => 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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Lms left, Lms right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="Vector3"/> representing this instance.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Vector3"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector3 ToVector3() => new(this.L, this.M, this.S); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.L, this.M, this.S); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"Lms({this.L:#0.##}, {this.M:#0.##}, {this.S:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is Lms other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Lms other) |
|||
=> this.L.Equals(other.L) |
|||
&& this.M.Equals(other.M) |
|||
&& this.S.Equals(other.S); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Lms FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
Vector3 vector = Vector3.Transform(source.ToVector3(), options.AdaptationMatrix); |
|||
return new Lms(vector); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<Lms> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyz xyz = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in xyz); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
Vector3 vector = Vector3.Transform(this.ToVector3(), options.InverseAdaptationMatrix); |
|||
return new CieXyz(vector); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Lms> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Lms lms = source[i]; |
|||
destination[i] = lms.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Matrices used for transformation from <see cref="CieXyz"/> to <see cref="Lms"/>, defining the cone response domain.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Matrix 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
|
|||
/// https://web.stanford.edu/~sujason/ColorBalancing/Papers/Two%20New%20von%20Kries%20Based%20Chromatic%20Adaptation.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 |
|||
= Matrix4x4.Transpose(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 |
|||
= Matrix4x4.Transpose(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.Transpose(Matrix4x4.Identity); |
|||
|
|||
/// <summary>
|
|||
/// Bradford chromatic adaptation transform matrix (used in CMCCAT97)
|
|||
/// </summary>
|
|||
public static readonly Matrix4x4 Bradford |
|||
= Matrix4x4.Transpose(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 |
|||
= Matrix4x4.Transpose(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 |
|||
= Matrix4x4.Transpose(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 |
|||
= Matrix4x4.Transpose(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 |
|||
}); |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of the Von Kries chromatic adaptation model.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Transformation described here:
|
|||
/// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
|
|||
/// </remarks>
|
|||
public static class VonKriesChromaticAdaptation |
|||
{ |
|||
/// <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="options">The color profile conversion options.</param>
|
|||
/// <param name="source">The source color.</param>
|
|||
/// <returns>The <see cref="CieXyz"/></returns>
|
|||
public static CieXyz Transform(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
CieXyz sourceWhitePoint = options.WhitePoint; |
|||
CieXyz destinationWhitePoint = options.TargetWhitePoint; |
|||
|
|||
if (sourceWhitePoint.Equals(destinationWhitePoint)) |
|||
{ |
|||
return new(source.X, source.Y, source.Z); |
|||
} |
|||
|
|||
Matrix4x4 matrix = options.AdaptationMatrix; |
|||
|
|||
Vector3 sourceColorLms = Vector3.Transform(source.ToVector3(), matrix); |
|||
Vector3 sourceWhitePointLms = Vector3.Transform(sourceWhitePoint.ToVector3(), matrix); |
|||
Vector3 targetWhitePointLms = Vector3.Transform(destinationWhitePoint.ToVector3(), matrix); |
|||
|
|||
Vector3 vector = targetWhitePointLms / sourceWhitePointLms; |
|||
Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); |
|||
|
|||
Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); |
|||
return new CieXyz(Vector3.Transform(targetColorLms, inverseMatrix)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs a bulk 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="options">The color profile conversion options.</param>
|
|||
/// <param name="source">The span to the source colors.</param>
|
|||
/// <param name="destination">The span to the destination colors.</param>
|
|||
public static void Transform(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
int count = source.Length; |
|||
|
|||
CieXyz sourceWhitePoint = options.WhitePoint; |
|||
CieXyz destinationWhitePoint = options.TargetWhitePoint; |
|||
|
|||
if (sourceWhitePoint.Equals(destinationWhitePoint)) |
|||
{ |
|||
source.CopyTo(destination[..count]); |
|||
return; |
|||
} |
|||
|
|||
Matrix4x4 matrix = options.AdaptationMatrix; |
|||
Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); |
|||
|
|||
ref CieXyz sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref CieXyz destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
for (nuint i = 0; i < (uint)count; i++) |
|||
{ |
|||
ref CieXyz sp = ref Unsafe.Add(ref sourceBase, i); |
|||
ref CieXyz dp = ref Unsafe.Add(ref destinationBase, i); |
|||
|
|||
Vector3 sourceColorLms = Vector3.Transform(sp.ToVector3(), matrix); |
|||
Vector3 sourceWhitePointLms = Vector3.Transform(sourceWhitePoint.ToVector3(), matrix); |
|||
Vector3 targetWhitePointLms = Vector3.Transform(destinationWhitePoint.ToVector3(), matrix); |
|||
|
|||
Vector3 vector = targetWhitePointLms / sourceWhitePointLms; |
|||
Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); |
|||
dp = new CieXyz(Vector3.Transform(targetColorLms, inverseMatrix)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows the approximate comparison of color profile component values.
|
|||
/// </summary>
|
|||
internal readonly struct ApproximateColorProfileComparer : |
|||
IEqualityComparer<CieLab>, |
|||
IEqualityComparer<CieXyz>, |
|||
IEqualityComparer<Lms> |
|||
{ |
|||
private readonly float epsilon; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ApproximateColorProfileComparer"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="epsilon">The comparison error difference epsilon to use.</param>
|
|||
public ApproximateColorProfileComparer(float epsilon = 1f) => this.epsilon = epsilon; |
|||
|
|||
public bool Equals(CieLab x, CieLab y) => this.Equals(x.L, y.L) && this.Equals(x.A, y.A) && this.Equals(x.B, y.B); |
|||
|
|||
public bool Equals(CieXyz x, CieXyz y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z); |
|||
|
|||
public bool Equals(Lms x, Lms y) => this.Equals(x.L, y.L) && this.Equals(x.M, y.M) && this.Equals(x.S, y.S); |
|||
|
|||
public int GetHashCode([DisallowNull] CieLab obj) => obj.GetHashCode(); |
|||
|
|||
public int GetHashCode([DisallowNull] CieXyz obj) => obj.GetHashCode(); |
|||
|
|||
public int GetHashCode([DisallowNull] Lms obj) => obj.GetHashCode(); |
|||
|
|||
private bool Equals(float x, float y) |
|||
{ |
|||
float d = x - y; |
|||
return d >= -this.epsilon && d <= this.epsilon; |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Tests <see cref="CieXyz"/>-<see cref="CieLab"/> conversions.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Test data generated using:
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?ColorCalculator.html"/>
|
|||
/// </remarks>
|
|||
public class CieXyzAndCieLabConversionTest |
|||
{ |
|||
private static readonly ApproximateColorProfileComparer Comparer = new(.0001f); |
|||
|
|||
[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(l, a, b); |
|||
ColorConversionOptions options = new() { WhitePoint = Illuminants.D65, TargetWhitePoint = Illuminants.D65 }; |
|||
ColorProfileConverter converter = new(options); |
|||
CieXyz expected = new(x, y, z); |
|||
|
|||
Span<CieLab> inputSpan = new CieLab[5]; |
|||
inputSpan.Fill(input); |
|||
|
|||
Span<CieXyz> actualSpan = new CieXyz[5]; |
|||
|
|||
// Act
|
|||
CieXyz actual = converter.Convert<CieLab, CieXyz>(input); |
|||
converter.Convert<CieLab, CieXyz>(inputSpan, actualSpan); |
|||
|
|||
// Assert
|
|||
Assert.Equal(expected, actual, Comparer); |
|||
|
|||
for (int i = 0; i < actualSpan.Length; i++) |
|||
{ |
|||
Assert.Equal(expected, actualSpan[i], Comparer); |
|||
} |
|||
} |
|||
|
|||
[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(x, y, z); |
|||
ColorConversionOptions options = new() { WhitePoint = Illuminants.D65, TargetWhitePoint = Illuminants.D65 }; |
|||
ColorProfileConverter converter = new(options); |
|||
CieLab expected = new(l, a, b); |
|||
|
|||
Span<CieXyz> inputSpan = new CieXyz[5]; |
|||
inputSpan.Fill(input); |
|||
|
|||
Span<CieLab> actualSpan = new CieLab[5]; |
|||
|
|||
// Act
|
|||
CieLab actual = converter.Convert<CieXyz, CieLab>(input); |
|||
converter.Convert<CieXyz, CieLab>(inputSpan, actualSpan); |
|||
|
|||
// Assert
|
|||
Assert.Equal(expected, actual, Comparer); |
|||
|
|||
for (int i = 0; i < actualSpan.Length; i++) |
|||
{ |
|||
Assert.Equal(expected, actualSpan[i], Comparer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Tests <see cref="CieXyz"/>-<see cref="Lms"/> conversions.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Test data generated using original colorful library.
|
|||
/// </remarks>
|
|||
public class CieXyzAndLmsConversionTest |
|||
{ |
|||
private static readonly ApproximateColorProfileComparer Comparer = new(.0001f); |
|||
|
|||
[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(l, m, s); |
|||
ColorProfileConverter converter = new(); |
|||
CieXyz expected = new(x, y, z); |
|||
|
|||
Span<Lms> inputSpan = new Lms[5]; |
|||
inputSpan.Fill(input); |
|||
|
|||
Span<CieXyz> actualSpan = new CieXyz[5]; |
|||
|
|||
// Act
|
|||
CieXyz actual = converter.Convert<Lms, CieXyz>(input); |
|||
converter.Convert<Lms, CieXyz>(inputSpan, actualSpan); |
|||
|
|||
// Assert
|
|||
Assert.Equal(expected, actual, Comparer); |
|||
|
|||
for (int i = 0; i < actualSpan.Length; i++) |
|||
{ |
|||
Assert.Equal(expected, actualSpan[i], Comparer); |
|||
} |
|||
} |
|||
|
|||
[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(x, y, z); |
|||
ColorProfileConverter converter = new(); |
|||
Lms expected = new(l, m, s); |
|||
|
|||
Span<CieXyz> inputSpan = new CieXyz[5]; |
|||
inputSpan.Fill(input); |
|||
|
|||
Span<Lms> actualSpan = new Lms[5]; |
|||
|
|||
// Act
|
|||
Lms actual = converter.Convert<CieXyz, Lms>(input); |
|||
converter.Convert<CieXyz, Lms>(inputSpan, actualSpan); |
|||
|
|||
// Assert
|
|||
Assert.Equal(expected, actual, Comparer); |
|||
|
|||
for (int i = 0; i < actualSpan.Length; i++) |
|||
{ |
|||
Assert.Equal(expected, actualSpan[i], Comparer); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue