Browse Source

Complete conversion tests

pull/2739/head
James Jackson-South 2 years ago
parent
commit
72dbc1308c
  1. 6
      src/ImageSharp/ColorProfiles/CieLab.cs
  2. 14
      src/ImageSharp/ColorProfiles/CieLuv.cs
  3. 7
      src/ImageSharp/ColorProfiles/CieXyz.cs
  4. 39
      src/ImageSharp/ColorProfiles/Rgb.cs
  5. 4
      tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs
  6. 108
      tests/ImageSharp.Tests/ColorProfiles/CompandingTests.cs
  7. 2
      tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs
  8. 60
      tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs

6
src/ImageSharp/ColorProfiles/CieLab.cs

@ -26,8 +26,11 @@ public readonly struct CieLab : IProfileConnectingSpace<CieLab, CieXyz>
/// <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))
{
// Not clamping as documentation about this space only indicates "usual" ranges
this.L = l;
this.A = a;
this.B = b;
}
/// <summary>
@ -38,7 +41,6 @@ public readonly struct CieLab : IProfileConnectingSpace<CieLab, CieXyz>
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;

14
src/ImageSharp/ColorProfiles/CieLuv.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.ColorProfiles;
@ -28,6 +29,19 @@ public readonly struct CieLuv : IColorProfile<CieLuv, CieXyz>
this.V = v;
}
/// <summary>
/// Initializes a new instance of the <see cref="CieLuv"/> struct.
/// </summary>
/// <param name="vector">The vector representing the l, u, v components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLuv(Vector3 vector)
: this()
{
this.L = vector.X;
this.U = vector.Y;
this.V = vector.Z;
}
/// <summary>
/// Gets the lightness dimension
/// <remarks>A value usually ranging between 0 and 100.</remarks>

7
src/ImageSharp/ColorProfiles/CieXyz.cs

@ -3,6 +3,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.ColorProfiles;
@ -20,8 +21,11 @@ public readonly struct CieXyz : IProfileConnectingSpace<CieXyz, CieXyz>
/// <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))
{
// Not clamping as documentation about this space only indicates "usual" ranges
this.X = x;
this.Y = y;
this.Z = z;
}
/// <summary>
@ -31,7 +35,6 @@ public readonly struct CieXyz : IProfileConnectingSpace<CieXyz, CieXyz>
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;

39
src/ImageSharp/ColorProfiles/Rgb.cs

@ -21,6 +21,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb(float r, float g, float b)
{
// Not clamping as this space can exceed "usual" ranges
this.R = r;
this.G = g;
this.B = b;
@ -31,7 +32,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
/// </summary>
/// <param name="source">The vector representing the r, g, b components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Rgb(Vector3 source)
public Rgb(Vector3 source)
{
this.R = source.X;
this.G = source.Y;
@ -149,20 +150,32 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
/// <summary>
/// Initializes the pixel instance from a generic scaled <see cref="Vector3"/>.
/// Initializes the color instance from a generic scaled <see cref="Vector3"/>.
/// </summary>
/// <param name="source">The vector to load the pixel from.</param>
/// <param name="source">The vector to load the color from.</param>
/// <returns>The <see cref="Rgb"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb FromScaledVector3(Vector3 source) => new(source);
public static Rgb FromScaledVector3(Vector3 source) => new(Vector3.Clamp(source, Vector3.Zero, Vector3.One));
/// <summary>
/// Initializes the pixel instance from a generic scaled <see cref="Vector4"/>.
/// Initializes the color instance from a generic scaled <see cref="Vector4"/>.
/// </summary>
/// <param name="source">The vector to load the pixel from.</param>
/// <param name="source">The vector to load the color from.</param>
/// <returns>The <see cref="Rgb"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb FromScaledVector4(Vector4 source) => new(source.X, source.Y, source.Z);
public static Rgb FromScaledVector4(Vector4 source)
{
source = Vector4.Clamp(source, Vector4.Zero, Vector4.One);
return new(source.X, source.Y, source.Z);
}
/// <summary>
/// Initializes the color instance for a source clamped between <value>0</value> and <value>1</value>
/// </summary>
/// <param name="source">The source to load the color from.</param>
/// <returns>The <see cref="Rgb"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb Clamp(Rgb source) => new(Vector3.Clamp(new(source.R, source.G, source.B), Vector3.Zero, Vector3.One));
/// <summary>
/// Expands the color into a generic ("scaled") <see cref="Vector3"/> representation
@ -171,7 +184,15 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
/// </summary>
/// <returns>The <see cref="Vector3"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToScaledVector3() => new(this.R, this.G, this.B);
public Vector3 ToScaledVector3() => Clamp(this).ToVector3();
/// <summary>
/// Expands the color into a generic <see cref="Vector3"/> representation.
/// The vector components are typically expanded in least to greatest significance order.
/// </summary>
/// <returns>The <see cref="Vector3"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToVector3() => new(this.R, this.G, this.B);
/// <summary>
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
@ -180,7 +201,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
/// </summary>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4() => new(this.R, this.G, this.B, 1f);
public Vector4 ToScaledVector4() => new(this.ToScaledVector3(), 1f);
private static Matrix4x4 GetCieXyzToRgbMatrix(RgbWorkingSpace workingSpace)
{

4
tests/ImageSharp.Tests/ColorProfiles/CieLuvAndHunterLabConversionTests.cs

@ -15,7 +15,7 @@ public class CieLuvAndHunterLabConversionTests
[Theory]
[InlineData(0, 0, 0, 0, 0, 0)]
[InlineData(36.0555, 93.6901, 10.01514, 30.59289, 48.55542, 9.80487)]
public void Convert_CieLuv_to_HunterLab(float l, float u, float v, float l2, float a, float b)
public void Convert_CieLuv_To_HunterLab(float l, float u, float v, float l2, float a, float b)
{
// Arrange
CieLuv input = new(l, u, v);
@ -44,7 +44,7 @@ public class CieLuvAndHunterLabConversionTests
[Theory]
[InlineData(0, 0, 0, 0, 0, 0)]
[InlineData(30.59289, 48.55542, 9.80487, 36.0555, 93.6901, 10.01514)]
public void Convert_HunterLab_to_CieLuv(float l2, float a, float b, float l, float u, float v)
public void Convert_HunterLab_To_CieLuv(float l2, float a, float b, float l, float u, float v)
{
// Arrange
HunterLab input = new(l2, a, b);

108
tests/ImageSharp.Tests/ColorProfiles/CompandingTests.cs

@ -0,0 +1,108 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.Companding;
namespace SixLabors.ImageSharp.Tests.ColorProfiles;
/// <summary>
/// Tests various companding algorithms. Expanded numbers are hand calculated from formulas online.
/// </summary>
public class CompandingTests
{
private static readonly ApproximateFloatComparer Comparer = new(.000001F);
[Fact]
public void Rec2020Companding_IsCorrect()
{
Vector4 input = new(.667F);
Vector4 e = Rec2020Companding.Expand(input);
Vector4 c = Rec2020Companding.Compress(e);
CompandingIsCorrectImpl(e, c, .44847462F, input);
}
[Fact]
public void Rec709Companding_IsCorrect()
{
Vector4 input = new(.667F);
Vector4 e = Rec709Companding.Expand(input);
Vector4 c = Rec709Companding.Compress(e);
CompandingIsCorrectImpl(e, c, .4483577F, input);
}
[Fact]
public void SRgbCompanding_IsCorrect()
{
Vector4 input = new(.667F);
Vector4 e = SRgbCompanding.Expand(input);
Vector4 c = SRgbCompanding.Compress(e);
CompandingIsCorrectImpl(e, c, .40242353F, input);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(30)]
public void SRgbCompanding_Expand_VectorSpan(int length)
{
Random rnd = new(42);
Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
Vector4[] expected = new Vector4[source.Length];
for (int i = 0; i < source.Length; i++)
{
expected[i] = SRgbCompanding.Expand(source[i]);
}
SRgbCompanding.Expand(source);
Assert.Equal(expected, source, Comparer);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(30)]
public void SRgbCompanding_Compress_VectorSpan(int length)
{
Random rnd = new(42);
Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
Vector4[] expected = new Vector4[source.Length];
for (int i = 0; i < source.Length; i++)
{
expected[i] = SRgbCompanding.Compress(source[i]);
}
SRgbCompanding.Compress(source);
Assert.Equal(expected, source, Comparer);
}
[Fact]
public void GammaCompanding_IsCorrect()
{
const double gamma = 2.2;
Vector4 input = new(.667F);
Vector4 e = GammaCompanding.Expand(input, gamma);
Vector4 c = GammaCompanding.Compress(e, gamma);
CompandingIsCorrectImpl(e, c, .41027668F, input);
}
[Fact]
public void LCompanding_IsCorrect()
{
Vector4 input = new(.667F);
Vector4 e = LCompanding.Expand(input);
Vector4 c = LCompanding.Compress(e);
CompandingIsCorrectImpl(e, c, .36236193F, input);
}
private static void CompandingIsCorrectImpl(Vector4 e, Vector4 c, float expanded, Vector4 compressed)
{
// W (alpha) is already the linear representation of the color.
Assert.Equal(new Vector4(expanded, expanded, expanded, e.W), e, Comparer);
Assert.Equal(compressed, c, Comparer);
}
}

2
tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs

@ -3,7 +3,7 @@
using SixLabors.ImageSharp.ColorProfiles;
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Conversion;
namespace SixLabors.ImageSharp.Tests.ColorProfiles;
/// <summary>
/// Tests <see cref="Rgb"/>-<see cref="Hsl"/> conversions.

60
tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs

@ -0,0 +1,60 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles;
namespace SixLabors.ImageSharp.Tests.ColorProfiles;
public class StringRepresentationTests
{
private static readonly Vector3 One = new(1);
private static readonly Vector3 Zero = new(0);
private static readonly Vector3 Random = new(42.4F, 94.5F, 83.4F);
public static readonly TheoryData<object, string> TestData = new()
{
{ new CieLab(Zero), "CieLab(0, 0, 0)" },
{ new CieLch(Zero), "CieLch(0, 0, 0)" },
{ new CieLchuv(Zero), "CieLchuv(0, 0, 0)" },
{ new CieLuv(Zero), "CieLuv(0, 0, 0)" },
{ new CieXyz(Zero), "CieXyz(0, 0, 0)" },
{ new CieXyy(Zero), "CieXyy(0, 0, 0)" },
{ new HunterLab(Zero), "HunterLab(0, 0, 0)" },
{ new Lms(Zero), "Lms(0, 0, 0)" },
{ new Rgb(Zero), "Rgb(0, 0, 0)" },
{ new Hsl(Zero), "Hsl(0, 0, 0)" },
{ new Hsv(Zero), "Hsv(0, 0, 0)" },
{ new YCbCr(Zero), "YCbCr(0, 0, 0)" },
{ new CieLab(One), "CieLab(1, 1, 1)" },
{ new CieLch(One), "CieLch(1, 1, 1)" },
{ new CieLchuv(One), "CieLchuv(1, 1, 1)" },
{ new CieLuv(One), "CieLuv(1, 1, 1)" },
{ new CieXyz(One), "CieXyz(1, 1, 1)" },
{ new CieXyy(One), "CieXyy(1, 1, 1)" },
{ new HunterLab(One), "HunterLab(1, 1, 1)" },
{ new Lms(One), "Lms(1, 1, 1)" },
{ new Rgb(One), "Rgb(1, 1, 1)" },
{ new Hsl(One), "Hsl(1, 1, 1)" },
{ new Hsv(One), "Hsv(1, 1, 1)" },
{ new YCbCr(One), "YCbCr(1, 1, 1)" },
{ new CieXyChromaticityCoordinates(1, 1), "CieXyChromaticityCoordinates(1, 1)" },
{ new CieLab(Random), "CieLab(42.4, 94.5, 83.4)" },
{ new CieLch(Random), "CieLch(42.4, 94.5, 83.4)" },
{ new CieLchuv(Random), "CieLchuv(42.4, 94.5, 83.4)" },
{ new CieLuv(Random), "CieLuv(42.4, 94.5, 83.4)" },
{ new CieXyz(Random), "CieXyz(42.4, 94.5, 83.4)" },
{ new CieXyy(Random), "CieXyy(42.4, 94.5, 83.4)" },
{ new HunterLab(Random), "HunterLab(42.4, 94.5, 83.4)" },
{ new Lms(Random), "Lms(42.4, 94.5, 83.4)" },
{ new Rgb(Random), "Rgb(42.4, 94.5, 83.4)" },
{ Rgb.Clamp(new Rgb(Random)), "Rgb(1, 1, 1)" },
{ new Hsl(Random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected
{ new Hsv(Random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected
{ new YCbCr(Random), "YCbCr(42.4, 94.5, 83.4)" },
};
[Theory]
[MemberData(nameof(TestData))]
public void StringRepresentationsAreCorrect(object color, string text) => Assert.Equal(text, color.ToString());
}
Loading…
Cancel
Save