Browse Source

Add YccK, normalize YCbCr, update tests

pull/1567/head
James Jackson-South 1 year ago
parent
commit
f55b3e404c
  1. 10
      src/ImageSharp/ColorProfiles/Cmyk.cs
  2. 4
      src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
  3. 2
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs
  4. 12
      src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs
  5. 2
      src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs
  6. 2
      src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs
  7. 2
      src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs
  8. 2
      src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs
  9. 2
      src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs
  10. 2
      src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs
  11. 4
      src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs
  12. 2
      src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs
  13. 2
      src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs
  14. 2
      src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs
  15. 2
      src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs
  16. 2
      src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs
  17. 2
      src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs
  18. 2
      src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs
  19. 62
      src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs
  20. 27
      src/ImageSharp/ColorProfiles/KnownYCoefficients.cs
  21. 2
      src/ImageSharp/ColorProfiles/Rgb.cs
  22. 52
      src/ImageSharp/ColorProfiles/Y.cs
  23. 48
      src/ImageSharp/ColorProfiles/YCbCr.cs
  24. 58
      src/ImageSharp/ColorProfiles/YcbCrMatrix.cs
  25. 216
      src/ImageSharp/ColorProfiles/YccK.cs
  26. 7
      tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs
  27. 10
      tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs
  28. 10
      tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs
  29. 10
      tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs
  30. 8
      tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs
  31. 10
      tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs
  32. 2
      tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs
  33. 45
      tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs
  34. 27
      tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs
  35. 43
      tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs
  36. 27
      tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs
  37. 27
      tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs
  38. 27
      tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs
  39. 35
      tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs
  40. 13
      tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs
  41. 80
      tests/ImageSharp.Tests/ColorProfiles/RgbAndYccKConversionTests.cs
  42. 6
      tests/ImageSharp.Tests/ColorProfiles/StringRepresentationTests.cs
  43. 6
      tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs
  44. 4
      tests/ImageSharp.Tests/ColorProfiles/YTests.cs
  45. 44
      tests/ImageSharp.Tests/ColorProfiles/YccKTests.cs
  46. 26
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

10
src/ImageSharp/ColorProfiles/Cmyk.cs

@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
/// <see href="https://en.wikipedia.org/wiki/CMYK_color_model"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public readonly struct Cmyk : IColorProfile<Cmyk, Rgb>
@ -130,12 +131,12 @@ public readonly struct Cmyk : IColorProfile<Cmyk, Rgb>
public static Cmyk FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
// To CMY
Vector3 cmy = Vector3.One - source.ToScaledVector3();
Vector3 cmy = Vector3.One - source.AsVector3Unsafe();
// To CMYK
Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z)));
if (MathF.Abs(k.X - 1F) < Constants.Epsilon)
if (k.X >= 1F - Constants.Epsilon)
{
return new Cmyk(0, 0, 0, 1F);
}
@ -161,7 +162,7 @@ public readonly struct Cmyk : IColorProfile<Cmyk, Rgb>
/// <inheritdoc/>
public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
{
Vector3 rgb = (Vector3.One - new Vector3(this.C, this.M, this.Y)) * (Vector3.One - new Vector3(this.K));
Vector3 rgb = (Vector3.One - new Vector3(this.C, this.M, this.Y)) * (1F - this.K);
return Rgb.FromScaledVector3(rgb);
}
@ -171,8 +172,7 @@ public readonly struct Cmyk : IColorProfile<Cmyk, Rgb>
// TODO: We can possibly optimize this by using SIMD
for (int i = 0; i < source.Length; i++)
{
Cmyk cmyk = source[i];
destination[i] = cmyk.ToProfileConnectingSpace(options);
destination[i] = source[i].ToProfileConnectingSpace(options);
}
}

4
src/ImageSharp/ColorProfiles/ColorConversionOptions.cs

@ -46,9 +46,9 @@ public class ColorConversionOptions
public RgbWorkingSpace TargetRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb;
/// <summary>
/// Gets the Y (luma) coefficients to use in conversions from RGB.
/// Gets the YCbCr matrix to used to perform conversions from/to RGB.
/// </summary>
public Vector3 YCoefficients { get; init; } = KnownYCoefficients.BT709;
public YCbCrMatrix YCbCrMatrix { get; init; } = KnownYCbCrMatrices.BT601;
/// <summary>
/// Gets the source ICC profile.

2
src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs

@ -6,8 +6,8 @@ using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;

12
src/ImageSharp/ColorProfiles/Icc/Calculators/ClutCalculator.cs

@ -322,10 +322,14 @@ internal class ClutCalculator : IVector4Calculator
int offset = 0;
for (int i = 0; i < this.outputCount; i++)
{
float pv = (p[offset + this.n000] * dF0) + (p[offset + this.n001] * dF1) + (p[offset + this.n010] * dF2) + (p[offset + this.n011] * dF3) +
(p[offset + this.n100] * dF4) + (p[offset + this.n101] * dF5) + (p[offset + this.n110] * dF6) + (p[offset + this.n111] * dF7);
destPixel[i] = pv;
destPixel[i] = (float)((p[offset + this.n000] * dF0) +
(p[offset + this.n001] * dF1) +
(p[offset + this.n010] * dF2) +
(p[offset + this.n011] * dF3) +
(p[offset + this.n100] * dF4) +
(p[offset + this.n101] * dF5) +
(p[offset + this.n110] * dF6) +
(p[offset + this.n111] * dF7));
offset++;
}
}

2
src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.CalculationType.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
internal partial class CurveCalculator
{

2
src/ImageSharp/ColorProfiles/Icc/Calculators/CurveCalculator.cs

@ -5,7 +5,7 @@
using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
internal partial class CurveCalculator : ISingleCalculator
{

2
src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.CalculationType.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
internal partial class LutABCalculator
{

2
src/ImageSharp/ColorProfiles/Icc/Calculators/LutABCalculator.cs

@ -6,7 +6,7 @@ using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
internal partial class LutABCalculator : IVector4Calculator
{

2
src/ImageSharp/ColorProfiles/Icc/Calculators/TrcCalculator.cs

@ -3,7 +3,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;

2
src/ImageSharp/ColorProfiles/Icc/IccConverterBase.Checks.cs

@ -4,7 +4,7 @@
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
/// <summary>
/// Color converter for ICC profiles

4
src/ImageSharp/ColorProfiles/Icc/IccConverterBase.ConversionMethod.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
/// <summary>
/// Color converter for ICC profiles

2
src/ImageSharp/ColorProfiles/Icc/IccConverterbase.Conversions.cs

@ -4,7 +4,7 @@
using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
/// <summary>
/// Color converter for ICC profiles

2
src/ImageSharp/ColorProfiles/Icc/IccConverterbase.cs

@ -6,7 +6,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
/// <summary>
/// Color converter for ICC profiles

2
src/ImageSharp/ColorProfiles/Icc/IccDataToDataConverter.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Icc;

2
src/ImageSharp/ColorProfiles/Icc/IccDataToPcsConverter.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Icc;

2
src/ImageSharp/ColorProfiles/Icc/IccPcsToDataConverter.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Icc;

2
src/ImageSharp/ColorProfiles/Icc/IccPcsToPcsConverter.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Icc;

2
src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs

@ -5,7 +5,7 @@
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
internal static class SrgbV4Profile
{

62
src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs

@ -0,0 +1,62 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// Provides standard YCbCr matrices for RGB to YCbCr conversion.
/// </summary>
public static class KnownYCbCrMatrices
{
#pragma warning disable SA1137 // Elements should have the same indentation
#pragma warning disable SA1117 // Parameters should be on same line or separate lines
/// <summary>
/// ITU-R BT.601 (SD video standard).
/// </summary>
public static readonly YCbCrMatrix BT601 = new(
new Matrix4x4(
0.299000F, 0.587000F, 0.114000F, 0F,
-0.168736F, -0.331264F, 0.500000F, 0F,
0.500000F, -0.418688F, -0.081312F, 0F,
0F, 0F, 0F, 1F),
new Matrix4x4(
1.000000F, 0.000000F, 1.402000F, 0F,
1.000000F, -0.344136F, -0.714136F, 0F,
1.000000F, 1.772000F, 0.000000F, 0F,
0F, 0F, 0F, 1F),
new Vector3(0F, 0.5F, 0.5F));
/// <summary>
/// ITU-R BT.709 (HD video, sRGB standard).
/// </summary>
public static readonly YCbCrMatrix BT709 = new(
new Matrix4x4(
0.212600F, 0.715200F, 0.072200F, 0F,
-0.114572F, -0.385428F, 0.500000F, 0F,
0.500000F, -0.454153F, -0.045847F, 0F,
0F, 0F, 0F, 1F),
new Matrix4x4(
1.000000F, 0.000000F, 1.574800F, 0F,
1.000000F, -0.187324F, -0.468124F, 0F,
1.000000F, 1.855600F, 0.000000F, 0F,
0F, 0F, 0F, 1F),
new Vector3(0F, 0.5F, 0.5F));
/// <summary>
/// ITU-R BT.2020 (UHD/4K video standard).
/// </summary>
public static readonly YCbCrMatrix BT2020 = new(
new Matrix4x4(
0.262700F, 0.678000F, 0.059300F, 0F,
-0.139630F, -0.360370F, 0.500000F, 0F,
0.500000F, -0.459786F, -0.040214F, 0F,
0F, 0F, 0F, 1F),
new Matrix4x4(
1.000000F, 0.000000F, 1.474600F, 0F,
1.000000F, -0.164553F, -0.571353F, 0F,
1.000000F, 1.881400F, 0.000000F, 0F,
0F, 0F, 0F, 1F),
new Vector3(0F, 0.5F, 0.5F));
}

27
src/ImageSharp/ColorProfiles/KnownYCoefficients.cs

@ -1,27 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// Provides standard Y (luma) coefficient sets for weighted RGB conversions.
/// </summary>
public static class KnownYCoefficients
{
/// <summary>
/// ITU-R BT.601 (SD video standard).
/// </summary>
public static readonly Vector3 BT601 = new(0.299F, 0.587F, 0.114F);
/// <summary>
/// ITU-R BT.709 (HD video, sRGB standard).
/// </summary>
public static readonly Vector3 BT709 = new(0.2126F, 0.7152F, 0.0722F);
/// <summary>
/// ITU-R BT.2020 (UHD/4K video standard).
/// </summary>
public static readonly Vector3 BT2020 = new(0.2627F, 0.6780F, 0.0593F);
}

2
src/ImageSharp/ColorProfiles/Rgb.cs

@ -225,7 +225,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
public bool Equals(Rgb other)
=> this.AsVector3Unsafe() == other.AsVector3Unsafe();
private Vector3 AsVector3Unsafe() => Unsafe.As<Rgb, Vector3>(ref Unsafe.AsRef(in this));
internal Vector3 AsVector3Unsafe() => Unsafe.As<Rgb, Vector3>(ref Unsafe.AsRef(in this));
private static Matrix4x4 GetCieXyzToRgbMatrix(RgbWorkingSpace workingSpace)
{

52
src/ImageSharp/ColorProfiles/Y.cs

@ -17,7 +17,13 @@ public readonly struct Y : IColorProfile<Y, Rgb>
/// Initializes a new instance of the <see cref="Y"/> struct.
/// </summary>
/// <param name="l">The luminance component.</param>
public Y(float l) => this.L = l;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Y(float l) => this.L = Numerics.Clamp(l, 0, 1);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
private Y(float l, bool _) => this.L = l;
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
/// <summary>
/// Gets the luminance component.
@ -47,32 +53,11 @@ public readonly struct Y : IColorProfile<Y, Rgb>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Y left, Y right) => !left.Equals(right);
/// <inheritdoc/>
public static Y FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
Vector3 weights = options.YCoefficients;
float l = (weights.X * source.R) + (weights.Y * source.G) + (weights.Z * source.B);
return new Y(l);
}
/// <inheritdoc/>
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<Y> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
// TODO: We can optimize this by using SIMD
for (int i = 0; i < source.Length; i++)
{
Rgb rgb = source[i];
destination[i] = FromProfileConnectingSpace(options, in rgb);
}
}
/// <inheritdoc/>
public Vector4 ToScaledVector4() => new(this.L);
/// <inheritdoc/>
public static Y FromScaledVector4(Vector4 source) => new(source.X);
public static Y FromScaledVector4(Vector4 source) => new(source.X, true);
/// <inheritdoc/>
public static void ToScaledVector4(ReadOnlySpan<Y> source, Span<Vector4> destination)
@ -102,6 +87,14 @@ public readonly struct Y : IColorProfile<Y, Rgb>
public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
=> new(this.L, this.L, this.L);
/// <inheritdoc/>
public static Y FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
Matrix4x4 m = options.YCbCrMatrix.Forward;
float offset = options.YCbCrMatrix.Offset.X;
return new(Vector3.Dot(source.AsVector3Unsafe(), new Vector3(m.M11, m.M12, m.M13)) + offset);
}
/// <inheritdoc/>
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Y> source, Span<Rgb> destination)
{
@ -114,6 +107,19 @@ public readonly struct Y : IColorProfile<Y, Rgb>
}
}
/// <inheritdoc/>
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<Y> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
// TODO: We can optimize this by using SIMD
for (int i = 0; i < source.Length; i++)
{
Rgb rgb = source[i];
destination[i] = FromProfileConnectingSpace(options, in rgb);
}
}
/// <inheritdoc/>
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace;

48
src/ImageSharp/ColorProfiles/YCbCr.cs

@ -8,15 +8,13 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// Represents an YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification for the JFIF use with Jpeg.
/// <see href="http://en.wikipedia.org/wiki/YCbCr"/>
/// <see href="http://www.ijg.org/files/T-REC-T.871-201105-I!!PDF-E.pdf"/>
/// Represents an YCbCr (luminance, blue chroma, red chroma) color.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public readonly struct YCbCr : IColorProfile<YCbCr, Rgb>
{
private static readonly Vector3 Min = Vector3.Zero;
private static readonly Vector3 Max = new(255);
private static readonly Vector3 Max = Vector3.One;
/// <summary>
/// Initializes a new instance of the <see cref="YCbCr"/> struct.
@ -55,19 +53,19 @@ public readonly struct YCbCr : IColorProfile<YCbCr, Rgb>
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float Y { get; }
/// <summary>
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float Cb { get; }
/// <summary>
/// Gets the Cr chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float Cr { get; }
@ -97,17 +95,12 @@ public readonly struct YCbCr : IColorProfile<YCbCr, Rgb>
{
Vector3 v3 = default;
v3 += this.AsVector3Unsafe();
v3 /= Max;
return new Vector4(v3, 1F);
}
/// <inheritdoc/>
public static YCbCr FromScaledVector4(Vector4 source)
{
Vector3 v3 = source.AsVector3();
v3 *= Max;
return new YCbCr(v3, true);
}
=> new(source.AsVector3(), true);
/// <inheritdoc/>
public static void ToScaledVector4(ReadOnlySpan<YCbCr> source, Span<Vector4> destination)
@ -136,16 +129,15 @@ public readonly struct YCbCr : IColorProfile<YCbCr, Rgb>
/// <inheritdoc/>
public static YCbCr FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
Vector3 rgb = source.ToScaledVector3() * Max;
float r = rgb.X;
float g = rgb.Y;
float b = rgb.Z;
Vector3 rgb = source.AsVector3Unsafe();
Matrix4x4 m = options.YCbCrMatrix.Forward;
Vector3 offset = options.YCbCrMatrix.Offset;
float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
float y = Vector3.Dot(rgb, new Vector3(m.M11, m.M12, m.M13));
float cb = Vector3.Dot(rgb, new Vector3(m.M21, m.M22, m.M23));
float cr = Vector3.Dot(rgb, new Vector3(m.M31, m.M32, m.M33));
return new YCbCr(y, cb, cr);
return new YCbCr(new Vector3(y, cb, cr) + offset, true);
}
/// <inheritdoc/>
@ -164,15 +156,15 @@ public readonly struct YCbCr : IColorProfile<YCbCr, Rgb>
/// <inheritdoc/>
public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
{
float y = this.Y;
float cb = this.Cb - 128F;
float cr = this.Cr - 128F;
Matrix4x4 m = options.YCbCrMatrix.Inverse;
Vector3 offset = options.YCbCrMatrix.Offset;
Vector3 normalized = this.AsVector3Unsafe() - offset;
float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);
float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
float r = Vector3.Dot(normalized, new Vector3(m.M11, m.M12, m.M13));
float g = Vector3.Dot(normalized, new Vector3(m.M21, m.M22, m.M23));
float b = Vector3.Dot(normalized, new Vector3(m.M31, m.M32, m.M33));
return Rgb.FromScaledVector3(new Vector3(r, g, b) / Max);
return Rgb.FromScaledVector3(new Vector3(r, g, b));
}
/// <inheritdoc/>

58
src/ImageSharp/ColorProfiles/YcbCrMatrix.cs

@ -0,0 +1,58 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// <para>
/// Represents a YCbCr color matrix containing forward and inverse transformation matrices,
/// and the chrominance offsets to apply for full-range encoding
/// </para>
/// <para>
/// These matrices must be selected to match the characteristics of the associated <see cref="RgbWorkingSpace"/>,
/// including its transfer function (gamma or companding) and chromaticity coordinates. Using mismatched matrices and
/// working spaces will produce incorrect conversions.
/// </para>
/// </summary>
public readonly struct YCbCrMatrix
{
/// <summary>
/// Initializes a new instance of the <see cref="YCbCrMatrix"/> struct.
/// </summary>
/// <param name="forward">
/// The forward transformation matrix from RGB to YCbCr. The matrix must include the
/// standard chrominance offsets in the fourth column, such as <c>(0, 0.5, 0.5)</c>.
/// </param>
/// <param name="inverse">
/// The inverse transformation matrix from YCbCr to RGB. This matrix expects that
/// chrominance offsets have already been subtracted prior to application.
/// </param>
/// <param name="offset">
/// The chrominance offsets to be added after the forward conversion,
/// and subtracted before the inverse conversion. Usually <c>(0, 0.5, 0.5)</c>.
/// </param>
public YCbCrMatrix(Matrix4x4 forward, Matrix4x4 inverse, Vector3 offset)
{
this.Forward = forward;
this.Inverse = inverse;
this.Offset = offset;
}
/// <summary>
/// Gets the matrix used to convert gamma-encoded RGB to YCbCr.
/// </summary>
public Matrix4x4 Forward { get; }
/// <summary>
/// Gets the matrix used to convert YCbCr back to gamma-encoded RGB.
/// </summary>
public Matrix4x4 Inverse { get; }
/// <summary>
/// Gets the chrominance offset vector to apply during encoding (add) or decoding (subtract).
/// </summary>
public Vector3 Offset { get; }
}

216
src/ImageSharp/ColorProfiles/YccK.cs

@ -0,0 +1,216 @@
// 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>
/// Represents a YCCK (luminance, blue chroma, red chroma, black) color.
/// YCCK is not a true color space but a reversible transform of CMYK, where the CMY components
/// are converted to YCbCr using the ITU-R BT.601 standard, and the K (black) component is preserved separately.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public readonly struct YccK : IColorProfile<YccK, Rgb>
{
private static readonly Vector4 Min = Vector4.Zero;
private static readonly Vector4 Max = Vector4.One;
/// <summary>
/// Initializes a new instance of the <see cref="YccK"/> struct.
/// </summary>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
/// <param name="k">The keyline black component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public YccK(float y, float cb, float cr, float k)
: this(new Vector4(y, cb, cr, k))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="YccK"/> struct.
/// </summary>
/// <param name="vector">The vector representing the c, m, y, k components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public YccK(Vector4 vector)
{
vector = Vector4.Clamp(vector, Min, Max);
this.Y = vector.X;
this.Cb = vector.Y;
this.Cr = vector.Z;
this.K = vector.W;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
private YccK(Vector4 vector, bool _)
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
{
this.Y = vector.X;
this.Cb = vector.Y;
this.Cr = vector.Z;
this.K = vector.W;
}
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float Y { get; }
/// <summary>
/// Gets the C (blue) chroma component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float Cb { get; }
/// <summary>
/// Gets the C (red) chroma component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float Cr { get; }
/// <summary>
/// Gets the keyline black color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float K { get; }
/// <summary>
/// Compares two <see cref="YccK"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="YccK"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="YccK"/> 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 ==(YccK left, YccK right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="YccK"/> objects for inequality.
/// </summary>
/// <param name="left">The <see cref="YccK"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="YccK"/> 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 !=(YccK left, YccK right) => !left.Equals(right);
/// <inheritdoc/>
public Vector4 ToScaledVector4()
{
Vector4 v4 = default;
v4 += this.AsVector4Unsafe();
return v4;
}
/// <inheritdoc/>
public static YccK FromScaledVector4(Vector4 source)
=> new(source, true);
/// <inheritdoc/>
public static void ToScaledVector4(ReadOnlySpan<YccK> source, Span<Vector4> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
MemoryMarshal.Cast<YccK, Vector4>(source).CopyTo(destination);
}
/// <inheritdoc/>
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<YccK> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
MemoryMarshal.Cast<Vector4, YccK>(source).CopyTo(destination);
}
/// <inheritdoc/>
public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
{
Matrix4x4 m = options.YCbCrMatrix.Inverse;
Vector3 offset = options.YCbCrMatrix.Offset;
Vector3 normalized = this.AsVector3Unsafe() - offset;
float r = Vector3.Dot(normalized, new Vector3(m.M11, m.M12, m.M13));
float g = Vector3.Dot(normalized, new Vector3(m.M21, m.M22, m.M23));
float b = Vector3.Dot(normalized, new Vector3(m.M31, m.M32, m.M33));
Vector3 rgb = new Vector3(r, g, b) * (1F - this.K);
return Rgb.FromScaledVector3(rgb);
}
/// <inheritdoc/>
public static YccK FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
Matrix4x4 m = options.YCbCrMatrix.Forward;
Vector3 offset = options.YCbCrMatrix.Offset;
Vector3 rgb = source.AsVector3Unsafe();
float k = 1F - MathF.Max(rgb.X, MathF.Max(rgb.Y, rgb.Z));
if (k >= 1F - Constants.Epsilon)
{
return new YccK(new Vector4(0F, 0.5F, 0.5F, 1F), true);
}
rgb /= 1F - k;
float y = Vector3.Dot(rgb, new Vector3(m.M11, m.M12, m.M13));
float cb = Vector3.Dot(rgb, new Vector3(m.M21, m.M22, m.M23));
float cr = Vector3.Dot(rgb, new Vector3(m.M31, m.M32, m.M33));
return new YccK(new Vector4(y, cb, cr, k) + new Vector4(offset, 0F));
}
/// <inheritdoc/>
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<YccK> source, Span<Rgb> destination)
{
// TODO: We can possibly optimize this by using SIMD
for (int i = 0; i < source.Length; i++)
{
destination[i] = source[i].ToProfileConnectingSpace(options);
}
}
/// <inheritdoc/>
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<YccK> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
// TODO: We can optimize this by using SIMD
for (int i = 0; i < source.Length; i++)
{
Rgb rgb = source[i];
destination[i] = FromProfileConnectingSpace(options, in rgb);
}
}
/// <inheritdoc/>
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
=> HashCode.Combine(this.Y, this.Cb, this.Cr, this.K);
/// <inheritdoc/>
public override string ToString()
=> FormattableString.Invariant($"YccK({this.Y:#0.##}, {this.Cb:#0.##}, {this.Cr:#0.##}, {this.K:#0.##})");
/// <inheritdoc/>
public override bool Equals(object? obj)
=> obj is YccK other && this.Equals(other);
/// <inheritdoc/>
public bool Equals(YccK other)
=> this.AsVector4Unsafe() == other.AsVector4Unsafe();
private Vector3 AsVector3Unsafe() => Unsafe.As<YccK, Vector3>(ref Unsafe.AsRef(in this));
private Vector4 AsVector4Unsafe() => Unsafe.As<YccK, Vector4>(ref Unsafe.AsRef(in this));
}

7
tests/ImageSharp.Tests/ColorProfiles/ApproximateColorProfileComparer.cs

@ -23,7 +23,8 @@ internal readonly struct ApproximateColorProfileComparer :
IEqualityComparer<Hsl>,
IEqualityComparer<Hsv>,
IEqualityComparer<HunterLab>,
IEqualityComparer<Y>
IEqualityComparer<Y>,
IEqualityComparer<YccK>
{
private readonly float epsilon;
@ -61,6 +62,8 @@ internal readonly struct ApproximateColorProfileComparer :
public bool Equals(Y x, Y y) => this.Equals(x.L, y.L);
public bool Equals(YccK x, YccK y) => this.Equals(x.Y, y.Y) && this.Equals(x.Cb, y.Cb) && this.Equals(x.Cr, y.Cr) && this.Equals(x.K, y.K);
public int GetHashCode([DisallowNull] CieLab obj) => obj.GetHashCode();
public int GetHashCode([DisallowNull] CieXyz obj) => obj.GetHashCode();
@ -89,6 +92,8 @@ internal readonly struct ApproximateColorProfileComparer :
public int GetHashCode([DisallowNull] Y obj) => obj.GetHashCode();
public int GetHashCode([DisallowNull] YccK obj) => obj.GetHashCode();
private bool Equals(float x, float y)
{
float d = x - y;

10
tests/ImageSharp.Tests/ColorProfiles/CieLabAndYCbCrConversionTests.cs

@ -13,8 +13,9 @@ public class CieLabAndYCbCrConversionTests
private static readonly ApproximateColorProfileComparer Comparer = new(.0002F);
[Theory]
[InlineData(0, 128, 128, 0, 0, 0)]
[InlineData(87.4179, 133.9763, 247.5308, 55.06287, 82.54838, 23.1697)]
[InlineData(1, .5F, .5F, 100, 0, 0)]
[InlineData(0, .5F, .5F, 0, 0, 0)]
[InlineData(.5F, .5F, .5F, 53.38897F, 0, 0)]
public void Convert_YCbCr_to_CieLab(float y, float cb, float cr, float l, float a, float b)
{
// Arrange
@ -41,8 +42,9 @@ public class CieLabAndYCbCrConversionTests
}
[Theory]
[InlineData(0, 0, 0, 0, 128, 128)]
[InlineData(55.06287, 82.54838, 23.1697, 87.41701, 133.97232, 247.5314)]
[InlineData(100, 0, 0, 1, .5F, .5F)]
[InlineData(0, 0, 0, 0, .5F, .5F)]
[InlineData(53.38897F, 0, 0, .5F, .5F, .5F)]
public void Convert_CieLab_to_YCbCr(float l, float a, float b, float y, float cb, float cr)
{
// Arrange

10
tests/ImageSharp.Tests/ColorProfiles/CieLuvAndYCbCrConversionTests.cs

@ -13,8 +13,9 @@ public class CieLuvAndYCbCrConversionTests
private static readonly ApproximateColorProfileComparer Comparer = new(.0002F);
[Theory]
[InlineData(0, 0, 0, 0, 128, 128)]
[InlineData(36.0555, 93.6901, 10.01514, 71.8283, 119.3174, 193.9839)]
[InlineData(100, 0, 0, 1, .5F, .5F)]
[InlineData(0, 0, 0, 0, .5F, .5F)]
[InlineData(53.38897F, 0, 0, .5F, .5F, .5F)]
public void Convert_CieLuv_to_YCbCr(float l, float u, float v, float y, float cb, float cr)
{
// Arrange
@ -42,8 +43,9 @@ public class CieLuvAndYCbCrConversionTests
}
[Theory]
[InlineData(0, 128, 128, 0, 0, 0)]
[InlineData(71.8283, 119.3174, 193.9839, 36.00565, 93.44593, 10.2234)]
[InlineData(1, .5F, .5F, 100, 0, 0)]
[InlineData(0, .5F, .5F, 0, 0, 0)]
[InlineData(.5F, .5F, .5F, 53.38897F, 0, 0)]
public void Convert_YCbCr_to_CieLuv(float y, float cb, float cr, float l, float u, float v)
{
// Arrange

10
tests/ImageSharp.Tests/ColorProfiles/CieXyyAndYCbCrConversionTests.cs

@ -13,8 +13,9 @@ public class CieXyyAndYCbCrConversionTests
private static readonly ApproximateColorProfileComparer Comparer = new(.0002f);
[Theory]
[InlineData(0, 0, 0, 0, 128, 128)]
[InlineData(0.360555, 0.936901, 0.1001514, 64.0204849, 91.87107, 82.33627)]
[InlineData(.34566915F, .358496159F, .99999994F, 1, .5F, .5F)]
[InlineData(0, 0, 0, 0, .5F, .5F)]
[InlineData(.34566915F, .358496159F, .214041144F, .5F, .5F, .5F)]
public void Convert_CieXyy_to_YCbCr(float x, float y, float yl, float y2, float cb, float cr)
{
// Arrange
@ -41,8 +42,9 @@ public class CieXyyAndYCbCrConversionTests
}
[Theory]
[InlineData(0, 128, 128, 0, 0, 0)]
[InlineData(64.0204849, 91.87107, 82.33627, 0.32114, 0.59787, 0.10976)]
[InlineData(1, .5F, .5F, .34566915F, .358496159F, .99999994F)]
[InlineData(0, .5F, .5F, 0, 0, 0)]
[InlineData(.5F, .5F, .5F, .34566915F, .358496159F, .214041144F)]
public void Convert_YCbCr_to_CieXyy(float y2, float cb, float cr, float x, float y, float yl)
{
// Arrange

8
tests/ImageSharp.Tests/ColorProfiles/CieXyzAndYCbCrConversionTests.cs

@ -13,8 +13,8 @@ public class CieXyzAndYCbCrConversionTests
private static readonly ApproximateColorProfileComparer Comparer = new(.0002f);
[Theory]
[InlineData(0, 0, 0, 0, 128, 128)]
[InlineData(0.360555, 0.936901, 0.1001514, 149.685, 43.52769, 21.23457)]
[InlineData(0, 0, 0, 0, .5F, .5F)]
[InlineData(.206382737F, .214041144F, .176628917F, .5F, .5F, .5F)]
public void Convert_CieXyz_to_YCbCr(float x, float y, float z, float y2, float cb, float cr)
{
// Arrange
@ -41,8 +41,8 @@ public class CieXyzAndYCbCrConversionTests
}
[Theory]
[InlineData(0, 128, 128, 0, 0, 0)]
[InlineData(149.685, 43.52769, 21.23457, 0.38506496, 0.716878653, 0.0971045)]
[InlineData(0, .5F, .5F, 0, 0, 0)]
[InlineData(.5F, .5F, .5F, .206382737F, .214041144F, .176628917F)]
public void Convert_YCbCr_to_CieXyz(float y2, float cb, float cr, float x, float y, float z)
{
// Arrange

10
tests/ImageSharp.Tests/ColorProfiles/CmykAndYCbCrConversionTests.cs

@ -13,8 +13,9 @@ public class CmykAndYCbCrConversionTests
private static readonly ApproximateColorProfileComparer Comparer = new(.0002F);
[Theory]
[InlineData(0, 0, 0, 0, 255, 128, 128)]
[InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 136.5134, 69.90555, 114.9948)]
[InlineData(0, 0, 0, 1, 0, .5F, .5F)]
[InlineData(0, 0, 0, 0, 1, .5F, .5F)]
[InlineData(0, .8570679F, .49999997F, 0, .439901F, .5339159F, .899500132F)]
public void Convert_Cmyk_To_YCbCr(float c, float m, float y, float k, float y2, float cb, float cr)
{
// Arrange
@ -41,8 +42,9 @@ public class CmykAndYCbCrConversionTests
}
[Theory]
[InlineData(255, 128, 128, 0, 0, 0, 5.960464E-08)]
[InlineData(136.5134, 69.90555, 114.9948, 0.2891567, 0, 0.7951807, 0.3490196)]
[InlineData(0, .5F, .5F, 0, 0, 0, 1)]
[InlineData(1, .5F, .5F, 0, 0, 0, 0)]
[InlineData(.5F, .5F, 1F, 0, .8570679F, .49999997F, 0)]
public void Convert_YCbCr_To_Cmyk(float y2, float cb, float cr, float c, float m, float y, float k)
{
// Arrange

2
tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/CurveCalculatorTests.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion;

45
tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutABCalculatorTests.cs

@ -2,38 +2,37 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion;
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators;
/// <summary>
/// Tests ICC <see cref="LutABCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class LutABCalculatorTests
{
/// <summary>
/// Tests ICC <see cref="LutABCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class LutABCalculatorTests
[Theory]
[MemberData(nameof(IccConversionDataLutAB.LutAToBConversionTestData), MemberType = typeof(IccConversionDataLutAB))]
internal void LutABCalculator_WithLutAToB_ReturnsResult(IccLutAToBTagDataEntry lut, Vector4 input, Vector4 expected)
{
[Theory]
[MemberData(nameof(IccConversionDataLutAB.LutAToBConversionTestData), MemberType = typeof(IccConversionDataLutAB))]
internal void LutABCalculator_WithLutAToB_ReturnsResult(IccLutAToBTagDataEntry lut, Vector4 input, Vector4 expected)
{
LutABCalculator calculator = new(lut);
LutABCalculator calculator = new(lut);
Vector4 result = calculator.Calculate(input);
Vector4 result = calculator.Calculate(input);
VectorAssert.Equal(expected, result, 4);
}
VectorAssert.Equal(expected, result, 4);
}
[Theory]
[MemberData(nameof(IccConversionDataLutAB.LutBToAConversionTestData), MemberType = typeof(IccConversionDataLutAB))]
internal void LutABCalculator_WithLutBToA_ReturnsResult(IccLutBToATagDataEntry lut, Vector4 input, Vector4 expected)
{
LutABCalculator calculator = new(lut);
[Theory]
[MemberData(nameof(IccConversionDataLutAB.LutBToAConversionTestData), MemberType = typeof(IccConversionDataLutAB))]
internal void LutABCalculator_WithLutBToA_ReturnsResult(IccLutBToATagDataEntry lut, Vector4 input, Vector4 expected)
{
LutABCalculator calculator = new(lut);
Vector4 result = calculator.Calculate(input);
Vector4 result = calculator.Calculate(input);
VectorAssert.Equal(expected, result, 4);
}
VectorAssert.Equal(expected, result, 4);
}
}

27
tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutCalculatorTests.cs

@ -4,23 +4,22 @@
using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion;
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators;
/// <summary>
/// Tests ICC <see cref="LutCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class LutCalculatorTests
{
/// <summary>
/// Tests ICC <see cref="LutCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class LutCalculatorTests
[Theory]
[MemberData(nameof(IccConversionDataLut.LutConversionTestData), MemberType = typeof(IccConversionDataLut))]
internal void LutCalculator_WithLut_ReturnsResult(float[] lut, bool inverted, float input, float expected)
{
[Theory]
[MemberData(nameof(IccConversionDataLut.LutConversionTestData), MemberType = typeof(IccConversionDataLut))]
internal void LutCalculator_WithLut_ReturnsResult(float[] lut, bool inverted, float input, float expected)
{
LutCalculator calculator = new(lut, inverted);
LutCalculator calculator = new(lut, inverted);
float result = calculator.Calculate(input);
float result = calculator.Calculate(input);
Assert.Equal(expected, result, 4f);
}
Assert.Equal(expected, result, 4f);
}
}

43
tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/LutEntryCalculatorTests.cs

@ -6,34 +6,33 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion;
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators;
/// <summary>
/// Tests ICC <see cref="LutEntryCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class LutEntryCalculatorTests
{
/// <summary>
/// Tests ICC <see cref="LutEntryCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class LutEntryCalculatorTests
[Theory]
[MemberData(nameof(IccConversionDataLutEntry.Lut8ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))]
internal void LutEntryCalculator_WithLut8_ReturnsResult(IccLut8TagDataEntry lut, Vector4 input, Vector4 expected)
{
[Theory]
[MemberData(nameof(IccConversionDataLutEntry.Lut8ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))]
internal void LutEntryCalculator_WithLut8_ReturnsResult(IccLut8TagDataEntry lut, Vector4 input, Vector4 expected)
{
LutEntryCalculator calculator = new(lut);
LutEntryCalculator calculator = new(lut);
Vector4 result = calculator.Calculate(input);
Vector4 result = calculator.Calculate(input);
VectorAssert.Equal(expected, result, 4);
}
VectorAssert.Equal(expected, result, 4);
}
[Theory]
[MemberData(nameof(IccConversionDataLutEntry.Lut16ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))]
internal void LutEntryCalculator_WithLut16_ReturnsResult(IccLut16TagDataEntry lut, Vector4 input, Vector4 expected)
{
LutEntryCalculator calculator = new(lut);
[Theory]
[MemberData(nameof(IccConversionDataLutEntry.Lut16ConversionTestData), MemberType = typeof(IccConversionDataLutEntry))]
internal void LutEntryCalculator_WithLut16_ReturnsResult(IccLut16TagDataEntry lut, Vector4 input, Vector4 expected)
{
LutEntryCalculator calculator = new(lut);
Vector4 result = calculator.Calculate(input);
Vector4 result = calculator.Calculate(input);
VectorAssert.Equal(expected, result, 4);
}
VectorAssert.Equal(expected, result, 4);
}
}

27
tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/MatrixCalculatorTests.cs

@ -5,23 +5,22 @@ using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion;
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators;
/// <summary>
/// Tests ICC <see cref="MatrixCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class MatrixCalculatorTests
{
/// <summary>
/// Tests ICC <see cref="MatrixCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class MatrixCalculatorTests
[Theory]
[MemberData(nameof(IccConversionDataMatrix.MatrixConversionTestData), MemberType = typeof(IccConversionDataMatrix))]
internal void MatrixCalculator_WithMatrix_ReturnsResult(Matrix4x4 matrix2D, Vector3 matrix1D, Vector4 input, Vector4 expected)
{
[Theory]
[MemberData(nameof(IccConversionDataMatrix.MatrixConversionTestData), MemberType = typeof(IccConversionDataMatrix))]
internal void MatrixCalculator_WithMatrix_ReturnsResult(Matrix4x4 matrix2D, Vector3 matrix1D, Vector4 input, Vector4 expected)
{
MatrixCalculator calculator = new(matrix2D, matrix1D);
MatrixCalculator calculator = new(matrix2D, matrix1D);
Vector4 result = calculator.Calculate(input);
Vector4 result = calculator.Calculate(input);
VectorAssert.Equal(expected, result, 4);
}
VectorAssert.Equal(expected, result, 4);
}
}

27
tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/ParametricCurveCalculatorTests.cs

@ -5,23 +5,22 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion;
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators;
/// <summary>
/// Tests ICC <see cref="ParametricCurveCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class ParametricCurveCalculatorTests
{
/// <summary>
/// Tests ICC <see cref="ParametricCurveCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class ParametricCurveCalculatorTests
[Theory]
[MemberData(nameof(IccConversionDataTrc.ParametricCurveConversionTestData), MemberType = typeof(IccConversionDataTrc))]
internal void ParametricCurveCalculator_WithCurveEntry_ReturnsResult(IccParametricCurveTagDataEntry curve, bool inverted, float input, float expected)
{
[Theory]
[MemberData(nameof(IccConversionDataTrc.ParametricCurveConversionTestData), MemberType = typeof(IccConversionDataTrc))]
internal void ParametricCurveCalculator_WithCurveEntry_ReturnsResult(IccParametricCurveTagDataEntry curve, bool inverted, float input, float expected)
{
ParametricCurveCalculator calculator = new(curve, inverted);
ParametricCurveCalculator calculator = new(curve, inverted);
float result = calculator.Calculate(input);
float result = calculator.Calculate(input);
Assert.Equal(expected, result, 4f);
}
Assert.Equal(expected, result, 4f);
}
}

27
tests/ImageSharp.Tests/ColorProfiles/Icc/Calculators/TrcCalculatorTests.cs

@ -6,23 +6,22 @@ using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Tests.TestDataIcc.Conversion;
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators
namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc.Calculators;
/// <summary>
/// Tests ICC <see cref="TrcCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class TrcCalculatorTests
{
/// <summary>
/// Tests ICC <see cref="TrcCalculator"/>
/// </summary>
[Trait("Color", "Conversion")]
public class TrcCalculatorTests
[Theory]
[MemberData(nameof(IccConversionDataTrc.TrcArrayConversionTestData), MemberType = typeof(IccConversionDataTrc))]
internal void TrcCalculator_WithCurvesArray_ReturnsResult(IccTagDataEntry[] entries, bool inverted, Vector4 input, Vector4 expected)
{
[Theory]
[MemberData(nameof(IccConversionDataTrc.TrcArrayConversionTestData), MemberType = typeof(IccConversionDataTrc))]
internal void TrcCalculator_WithCurvesArray_ReturnsResult(IccTagDataEntry[] entries, bool inverted, Vector4 input, Vector4 expected)
{
TrcCalculator calculator = new(entries, inverted);
TrcCalculator calculator = new(entries, inverted);
Vector4 result = calculator.Calculate(input);
Vector4 result = calculator.Calculate(input);
VectorAssert.Equal(expected, result, 4);
}
VectorAssert.Equal(expected, result, 4);
}
}

35
tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs

@ -15,35 +15,6 @@ public class RbgAndYConversionTests
{
private static readonly ApproximateColorProfileComparer Comparer = new(.001F);
[Theory]
[InlineData(0F, 0F, 0F, 0F)]
[InlineData(0.5F, 0.5F, 0.5F, 0.5F)]
[InlineData(1F, 1F, 1F, 1F)]
public void Convert_Y_To_Rgb(float y, float r, float g, float b)
{
// Arrange
Y input = new(y);
Rgb expected = new(r, g, b);
ColorProfileConverter converter = new();
Span<Y> inputSpan = new Y[5];
inputSpan.Fill(input);
Span<Rgb> actualSpan = new Rgb[5];
// Act
Rgb actual = converter.Convert<Y, Rgb>(input);
converter.Convert<Y, Rgb>(inputSpan, actualSpan);
// Assert
Assert.Equal(expected, actual, Comparer);
for (int i = 0; i < actualSpan.Length; i++)
{
Assert.Equal(expected, actualSpan[i], Comparer);
}
}
[Theory]
[InlineData(0F, 0F, 0F, 0F)]
[InlineData(0.5F, 0.5F, 0.5F, 0.5F)]
@ -52,7 +23,7 @@ public class RbgAndYConversionTests
{
ColorConversionOptions options = new()
{
YCoefficients = KnownYCoefficients.BT601
YCbCrMatrix = KnownYCbCrMatrices.BT601
};
Convert_Rgb_To_Y_Core(r, g, b, y, options);
@ -66,7 +37,7 @@ public class RbgAndYConversionTests
{
ColorConversionOptions options = new()
{
YCoefficients = KnownYCoefficients.BT709
YCbCrMatrix = KnownYCbCrMatrices.BT709
};
Convert_Rgb_To_Y_Core(r, g, b, y, options);
@ -80,7 +51,7 @@ public class RbgAndYConversionTests
{
ColorConversionOptions options = new()
{
YCoefficients = KnownYCoefficients.BT2020
YCbCrMatrix = KnownYCbCrMatrices.BT2020
};
Convert_Rgb_To_Y_Core(r, g, b, y, options);

13
tests/ImageSharp.Tests/ColorProfiles/RgbAndYCbCrConversionTest.cs

@ -16,9 +16,9 @@ public class RgbAndYCbCrConversionTest
private static readonly ApproximateColorProfileComparer Comparer = new(.001F);
[Theory]
[InlineData(255, 128, 128, 1, 1, 1)]
[InlineData(0, 128, 128, 0, 0, 0)]
[InlineData(128, 128, 128, 0.502, 0.502, 0.502)]
[InlineData(1, .5F, .5F, 1, 1, 1)]
[InlineData(0, .5F, .5F, 0, 0, 0)]
[InlineData(.5F, .5F, .5F, .5F, .5F, .5F)]
public void Convert_YCbCr_To_Rgb(float y, float cb, float cr, float r, float g, float b)
{
// Arrange
@ -45,10 +45,9 @@ public class RgbAndYCbCrConversionTest
}
[Theory]
[InlineData(0, 0, 0, 0, 128, 128)]
[InlineData(1, 1, 1, 255, 128, 128)]
[InlineData(0.5, 0.5, 0.5, 127.5, 128, 128)]
[InlineData(1, 0, 0, 76.245, 84.972, 255)]
[InlineData(1, 1, 1, 1, .5F, .5F)]
[InlineData(0, 0, 0, 0, .5F, .5F)]
[InlineData(.5F, .5F, .5F, .5F, .5F, .5F)]
public void Convert_Rgb_To_YCbCr(float r, float g, float b, float y, float cb, float cr)
{
// Arrange

80
tests/ImageSharp.Tests/ColorProfiles/RgbAndYccKConversionTests.cs

@ -0,0 +1,80 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorProfiles;
namespace SixLabors.ImageSharp.Tests.ColorProfiles;
/// <summary>
/// Tests <see cref="Rgb"/>-<see cref="YccK"/> conversions.
/// </summary>
/// <remarks>
/// Test data generated mathematically
/// </remarks>
public class RgbAndYccKConversionTests
{
private static readonly ApproximateColorProfileComparer Comparer = new(.001F);
[Theory]
[InlineData(1, .5F, .5F, 0, 1, 1, 1)]
[InlineData(0, .5F, .5F, 1, 0, 0, 0)]
[InlineData(.5F, .5F, .5F, 0, .5F, .5F, .5F)]
public void Convert_YccK_To_Rgb(float y, float cb, float cr, float k, float r, float g, float b)
{
// Arrange
YccK input = new(y, cb, cr, k);
Rgb expected = new(r, g, b);
ColorProfileConverter converter = new();
Span<YccK> inputSpan = new YccK[5];
inputSpan.Fill(input);
Span<Rgb> actualSpan = new Rgb[5];
// Act
Rgb actual = converter.Convert<YccK, Rgb>(input);
converter.Convert<YccK, Rgb>(inputSpan, actualSpan);
// Assert
Assert.Equal(expected, actual, Comparer);
for (int i = 0; i < actualSpan.Length; i++)
{
Assert.Equal(expected, actualSpan[i], Comparer);
}
}
[Theory]
[InlineData(1, 1, 1, 1, .5F, .5F, 0)]
[InlineData(0, 0, 0, 0, .5F, .5F, 1)]
[InlineData(.5F, .5F, .5F, 1, .5F, .5F, .5F)]
public void Convert_Rgb_To_YccK(float r, float g, float b, float y, float cb, float cr, float k)
{
// Multiple YccK representations can decode to the same RGB value.
// For example, (Y=1.0, Cb=0.5, Cr=0.5, K=0.5) and (Y=0.5, Cb=0.5, Cr=0.5, K=0.0) both yield RGB (0.5, 0.5, 0.5).
// This is expected because YccK is not a unique encoding — K modulates RGB after YCbCr decoding.
// Round-tripping RGB -> YccK -> RGB is stable, but YccK -> RGB -> YccK is not injective.
// Arrange
Rgb input = new(r, g, b);
YccK expected = new(y, cb, cr, k);
ColorProfileConverter converter = new();
Span<Rgb> inputSpan = new Rgb[5];
inputSpan.Fill(input);
Span<YccK> actualSpan = new YccK[5];
// Act
YccK actual = converter.Convert<Rgb, YccK>(input);
converter.Convert<Rgb, YccK>(inputSpan, actualSpan);
// Assert
Assert.Equal(expected, actual, Comparer);
for (int i = 0; i < actualSpan.Length; i++)
{
Assert.Equal(expected, actualSpan[i], Comparer);
}
}
}

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

@ -12,6 +12,7 @@ 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);
private static readonly Vector4 Random4 = new(42.4F, 94.5F, 83.4F, 1);
public static readonly TheoryData<object, string> TestData = new()
{
@ -52,7 +53,10 @@ public class StringRepresentationTests
{ 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)" },
{ new Y(Random.X), "Y(1)" },
{ new YCbCr(Random), "YCbCr(1, 1, 1)" },
{ new YccK(Random4), "YccK(1, 1, 1, 1)" },
{ new Cmyk(Random4), "Cmyk(1, 1, 1, 1)" },
};
[Theory]

6
tests/ImageSharp.Tests/ColorProfiles/YCbCrTests.cs

@ -15,9 +15,9 @@ public class YCbCrTests
[Fact]
public void YCbCrConstructorAssignsFields()
{
const float y = 75F;
const float cb = 64F;
const float cr = 87F;
const float y = .75F;
const float cb = .64F;
const float cr = .87F;
YCbCr yCbCr = new(y, cb, cr);
Assert.Equal(y, yCbCr.Y);

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

@ -1,10 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorProfiles;
namespace SixLabors.ImageSharp.Tests.ColorProfiles;

44
tests/ImageSharp.Tests/ColorProfiles/YccKTests.cs

@ -0,0 +1,44 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorProfiles;
namespace SixLabors.ImageSharp.Tests.ColorProfiles;
/// <summary>
/// Tests the <see cref="YccK"/> struct.
/// </summary>
[Trait("Color", "Conversion")]
public class YccKTests
{
[Fact]
public void YccKConstructorAssignsFields()
{
const float y = .75F;
const float cb = .5F;
const float cr = .25F;
const float k = .125F;
YccK ycckValue = new(y, cb, cr, k);
Assert.Equal(y, ycckValue.Y);
Assert.Equal(cb, ycckValue.Cb);
Assert.Equal(cr, ycckValue.Cr);
Assert.Equal(k, ycckValue.K);
}
[Fact]
public void YccKEquality()
{
YccK x = default;
YccK y = new(1F, 1F, 1F, 1F);
Assert.True(default == default(YccK));
Assert.False(default != default(YccK));
Assert.Equal(default, default(YccK));
Assert.Equal(new YccK(1, 1, 1, 1), new YccK(1, 1, 1, 1));
Assert.Equal(new YccK(.5F, .5F, .5F, .5F), new YccK(.5F, .5F, .5F, .5F));
Assert.False(x.Equals(y));
Assert.False(x.Equals((object)y));
Assert.False(x.GetHashCode().Equals(y.GetHashCode()));
}
}

26
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Memory;
@ -15,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg;
[Trait("Format", "Jpg")]
public class JpegColorConverterTests
{
private const float MaxColorChannelValue = 255f;
private const float MaxColorChannelValue = 255F;
private const float Precision = 0.1F / 255;
@ -754,7 +752,7 @@ public class JpegColorConverterTests
ValidateGrayScale(original, result, i);
break;
case JpegColorSpace.Ycck:
ValidateCyyK(original, result, i);
ValidateYccK(original, result, i);
break;
case JpegColorSpace.Cmyk:
ValidateCmyk(original, result, i);
@ -774,17 +772,25 @@ public class JpegColorConverterTests
private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i)
{
float y = values.Component0[i];
float cb = values.Component1[i];
float cr = values.Component2[i];
Rgb expected = ColorSpaceConverter.Convert<YCbCr, Rgb>(new YCbCr(y, cb, cr));
float cb = values.Component1[i] - 128;
float cr = values.Component2[i] - 128;
float r = (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
float g = (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);
float b = (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
r /= MaxColorChannelValue;
g /= MaxColorChannelValue;
b /= MaxColorChannelValue;
Rgb expected = Rgb.Clamp(new(r, g, b));
Rgb actual = Rgb.Clamp(new(result.Component0[i], result.Component1[i], result.Component2[i]));
bool equal = ColorSpaceComparer.Equals(expected, actual);
Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}");
}
private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i)
private static void ValidateYccK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i)
{
float y = values.Component0[i];
float cb = values.Component1[i] - 128F;
@ -792,9 +798,7 @@ public class JpegColorConverterTests
float k = values.Component3[i] / 255F;
float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k;
float g = (255F - (float)Math.Round(
y - (0.344136F * cb) - (0.714136F * cr),
MidpointRounding.AwayFromZero)) * k;
float g = (255F - (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k;
float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k;
r /= MaxColorChannelValue;

Loading…
Cancel
Save