mirror of https://github.com/SixLabors/ImageSharp
13 changed files with 516 additions and 18 deletions
@ -0,0 +1,155 @@ |
|||
// <copyright file="CieXyy.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 xyY 1931 color
|
|||
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space"/>
|
|||
/// </summary>
|
|||
public struct CieXyy : IColorVector, IEquatable<CieXyy>, IAlmostEquatable<CieXyy, float> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="CieXyy"/> that has X, Y, and Y values set to zero.
|
|||
/// </summary>
|
|||
public static readonly CieXyy Empty = default(CieXyy); |
|||
|
|||
/// <summary>
|
|||
/// The backing vector for SIMD support.
|
|||
/// </summary>
|
|||
private readonly Vector3 backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyy"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The x chroma component.</param>
|
|||
/// <param name="y">The y chroma component.</param>
|
|||
/// <param name="yl">The y luminance component.</param>
|
|||
public CieXyy(float x, float y, float yl) |
|||
: this(new Vector3(x, y, yl)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyy"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the x, y, Y components.</param>
|
|||
public CieXyy(Vector3 vector) |
|||
: this() |
|||
{ |
|||
// Not clamping as documentation about this space seems to indicate "usual" ranges
|
|||
this.backingVector = vector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the X chrominance component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float X => this.backingVector.X; |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y chrominance component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Y => this.backingVector.Y; |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y luminance component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Yl => this.backingVector.Z; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="CieXyy"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <inheritdoc />
|
|||
public Vector3 Vector => this.backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieXyy"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="CieXyy"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="CieXyy"/> 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 ==(CieXyy left, CieXyy right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieXyy"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="CieXyy"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="CieXyy"/> 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 !=(CieXyy left, CieXyy right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.backingVector.GetHashCode(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
if (this.IsEmpty) |
|||
{ |
|||
return "CieXyy [ Empty ]"; |
|||
} |
|||
|
|||
return $"CieXyy [ X={this.X:#0.##}, Y={this.Y:#0.##}, Yl={this.Yl:#0.##} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj is CieXyy) |
|||
{ |
|||
return this.Equals((CieXyy)obj); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(CieXyy other) |
|||
{ |
|||
return this.backingVector.Equals(other.backingVector); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool AlmostEquals(CieXyy other, float precision) |
|||
{ |
|||
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); |
|||
|
|||
return result.X <= precision |
|||
&& result.Y <= precision |
|||
&& result.Z <= precision; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion |
|||
{ |
|||
using ImageSharp.Colors.Spaces.Conversion.Implementation.CieXyy; |
|||
|
|||
/// <summary>
|
|||
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
|
|||
/// </summary>
|
|||
public partial class ColorSpaceConverter |
|||
{ |
|||
private static readonly CieXyzAndCieXyyConverter CieXyzAndCieXyyConverter = new CieXyzAndCieXyyConverter(); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieXyz"/> into a <see cref="CieXyy"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="CieXyy"/></returns>
|
|||
public CieXyy ToCieXyy(CieXyz color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
return CieXyzAndCieXyyConverter.Convert(color); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieLab"/> into a <see cref="CieXyy"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="CieXyy"/></returns>
|
|||
public CieXyy ToCieXyy(CieLab color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCieXyy(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieLch"/> into a <see cref="CieXyy"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="CieXyy"/></returns>
|
|||
public CieXyy ToCieXyy(CieLch color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCieXyy(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="HunterLab"/> into a <see cref="CieXyy"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="CieXyy"/></returns>
|
|||
public CieXyy ToCieXyy(HunterLab color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCieXyy(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieXyy"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="CieXyy"/></returns>
|
|||
public CieXyy ToCieXyy(LinearRgb color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCieXyy(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Rgb"/> into a <see cref="CieXyy"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="CieXyy"/></returns>
|
|||
public CieXyy ToCieXyy(Rgb color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCieXyy(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Lms"/> into a <see cref="CieXyy"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="CieXyy"/></returns>
|
|||
public CieXyy ToCieXyy(Lms color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCieXyy(xyzColor); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// <copyright file="CieXyy.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieXyy |
|||
{ |
|||
using ImageSharp.Colors.Spaces; |
|||
|
|||
/// <summary>
|
|||
/// Color converter between CIE XYZ and CIE xyY
|
|||
/// <see href="http://www.brucelindbloom.com/"/> for formulas.
|
|||
/// </summary>
|
|||
internal class CieXyzAndCieXyyConverter : IColorConversion<CieXyz, CieXyy>, IColorConversion<CieXyy, CieXyz> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public CieXyy Convert(CieXyz input) |
|||
{ |
|||
DebugGuard.NotNull(input, nameof(input)); |
|||
|
|||
float x = input.X / (input.X + input.Y + input.Z); |
|||
float y = input.Y / (input.X + input.Y + input.Z); |
|||
|
|||
if (float.IsNaN(x) || float.IsNaN(y)) |
|||
{ |
|||
return new CieXyy(0, 0, input.Y); |
|||
} |
|||
|
|||
return new CieXyy(x, y, input.Y); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz Convert(CieXyy input) |
|||
{ |
|||
DebugGuard.NotNull(input, nameof(input)); |
|||
|
|||
if (MathF.Abs(input.Y) < Constants.Epsilon) |
|||
{ |
|||
return new CieXyz(0, 0, input.Yl); |
|||
} |
|||
|
|||
float x = (input.X * input.Yl) / input.Y; |
|||
float y = input.Yl; |
|||
float z = ((1 - input.X - input.Y) * y) / input.Y; |
|||
|
|||
return new CieXyz(x, y, z); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
namespace ImageSharp.Tests.Colors.Colorspaces |
|||
{ |
|||
using System.Collections.Generic; |
|||
|
|||
using ImageSharp.Colors.Spaces; |
|||
using ImageSharp.Colors.Spaces.Conversion; |
|||
|
|||
using Xunit; |
|||
|
|||
/// <summary>
|
|||
/// Tests <see cref="CieXyz"/>-<see cref="CieXyy"/> conversions.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Test data generated using:
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?ColorCalculator.html"/>
|
|||
/// </remarks>
|
|||
public class CieXyzAndCieXyyConversionTest |
|||
{ |
|||
private static readonly IEqualityComparer<float> FloatRoundingComparer = new FloatRoundingComparer(4); |
|||
|
|||
private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); |
|||
|
|||
[Theory] |
|||
[InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] |
|||
[InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] |
|||
[InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] |
|||
[InlineData(0, 0, 0, 0.538842, 0.000000, 0.000000)] |
|||
public void Convert_xyY_to_XYZ(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) |
|||
{ |
|||
// Arrange
|
|||
CieXyy input = new CieXyy(x, y, yl); |
|||
|
|||
// Act
|
|||
CieXyz output = Converter.ToCieXyz(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(xyzX, output.X, FloatRoundingComparer); |
|||
Assert.Equal(xyzY, output.Y, FloatRoundingComparer); |
|||
Assert.Equal(xyzZ, output.Z, FloatRoundingComparer); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] |
|||
[InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] |
|||
[InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] |
|||
[InlineData(0.231809, 0, 0.077528, 0.749374, 0.000000, 0.000000)] |
|||
public void Convert_XYZ_to_xyY(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) |
|||
{ |
|||
// Arrange
|
|||
CieXyz input = new CieXyz(xyzX, xyzY, xyzZ); |
|||
|
|||
// Act
|
|||
CieXyy output = Converter.ToCieXyy(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(x, output.X, FloatRoundingComparer); |
|||
Assert.Equal(y, output.Y, FloatRoundingComparer); |
|||
Assert.Equal(yl, output.Yl, FloatRoundingComparer); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue