Browse Source

Wire up Rgb working spaces

pull/2739/head
James Jackson-South 3 years ago
parent
commit
3703cb17af
  1. 20
      src/ImageSharp/ColorProfiles/ChromaticAdaptionWhitePointSource.cs
  2. 3
      src/ImageSharp/ColorProfiles/CieLab.cs
  3. 3
      src/ImageSharp/ColorProfiles/CieLch.cs
  4. 83
      src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs
  5. 3
      src/ImageSharp/ColorProfiles/CieXyz.cs
  6. 12
      src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
  7. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
  8. 6
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
  9. 4
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
  10. 171
      src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs
  11. 56
      src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs
  12. 71
      src/ImageSharp/ColorProfiles/Companding/LCompanding.cs
  13. 75
      src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs
  14. 71
      src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs
  15. 71
      src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs
  16. 46
      src/ImageSharp/ColorProfiles/IColorProfile.cs
  17. 3
      src/ImageSharp/ColorProfiles/Lms.cs
  18. 246
      src/ImageSharp/ColorProfiles/Rgb.cs
  19. 82
      src/ImageSharp/ColorProfiles/RgbPrimariesChromaticityCoordinates.cs
  20. 114
      src/ImageSharp/ColorProfiles/RgbWorkingSpaces.cs
  21. 44
      src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs
  22. 68
      src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs
  23. 35
      src/ImageSharp/ColorProfiles/WorkingSpaces/LWorkingSpace.cs
  24. 35
      src/ImageSharp/ColorProfiles/WorkingSpaces/Rec2020WorkingSpace.cs
  25. 35
      src/ImageSharp/ColorProfiles/WorkingSpaces/Rec709WorkingSpace.cs
  26. 85
      src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs
  27. 35
      src/ImageSharp/ColorProfiles/WorkingSpaces/SRgbWorkingSpace.cs

20
src/ImageSharp/ColorProfiles/ChromaticAdaptionWhitePointSource.cs

@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// Enumerate the possible sources of the white point used in chromatic adaptation.
/// </summary>
public enum ChromaticAdaptionWhitePointSource
{
/// <summary>
/// The white point of the source color space.
/// </summary>
WhitePoint,
/// <summary>
/// The white point of the source working space.
/// </summary>
RgbWorkingSpace
}

3
src/ImageSharp/ColorProfiles/CieLab.cs

@ -172,4 +172,7 @@ public readonly struct CieLab : IProfileConnectingSpace<CieLab, CieXyz>
destination[i] = lab.ToProfileConnectingSpace(options);
}
}
/// <inheritdoc/>
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint;
}

3
src/ImageSharp/ColorProfiles/CieLch.cs

@ -163,4 +163,7 @@ public readonly struct CieLch : IColorProfile<CieLch, CieLab>
destination[i] = lch.ToProfileConnectingSpace(options);
}
}
/// <inheritdoc/>
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint;
}

83
src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs

@ -0,0 +1,83 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
// ReSharper disable CompareOfFloatsByEqualityOperator
namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// Represents the coordinates of CIEXY chromaticity space.
/// </summary>
public readonly struct CieXyChromaticityCoordinates : IEquatable<CieXyChromaticityCoordinates>
{
/// <summary>
/// Initializes a new instance of the <see cref="CieXyChromaticityCoordinates"/> struct.
/// </summary>
/// <param name="x">Chromaticity coordinate x (usually from 0 to 1)</param>
/// <param name="y">Chromaticity coordinate y (usually from 0 to 1)</param>
[MethodImpl(InliningOptions.ShortMethod)]
public CieXyChromaticityCoordinates(float x, float y)
{
this.X = x;
this.Y = y;
}
/// <summary>
/// Gets the chromaticity X-coordinate.
/// </summary>
/// <remarks>
/// Ranges usually from 0 to 1.
/// </remarks>
public readonly float X { get; }
/// <summary>
/// Gets the chromaticity Y-coordinate
/// </summary>
/// <remarks>
/// Ranges usually from 0 to 1.
/// </remarks>
public readonly float Y { get; }
/// <summary>
/// Compares two <see cref="CieXyChromaticityCoordinates"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="CieXyChromaticityCoordinates"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="CieXyChromaticityCoordinates"/> 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(InliningOptions.ShortMethod)]
public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right)
=> left.Equals(right);
/// <summary>
/// Compares two <see cref="CieXyChromaticityCoordinates"/> objects for inequality
/// </summary>
/// <param name="left">The <see cref="CieXyChromaticityCoordinates"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="CieXyChromaticityCoordinates"/> 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(InliningOptions.ShortMethod)]
public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right)
=> !left.Equals(right);
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode()
=> HashCode.Combine(this.X, this.Y);
/// <inheritdoc/>
public override string ToString()
=> FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})");
/// <inheritdoc/>
public override bool Equals(object? obj)
=> obj is CieXyChromaticityCoordinates other && this.Equals(other);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(CieXyChromaticityCoordinates other)
=> this.X.Equals(other.X) && this.Y.Equals(other.Y);
}

3
src/ImageSharp/ColorProfiles/CieXyz.cs

@ -121,4 +121,7 @@ public readonly struct CieXyz : IProfileConnectingSpace<CieXyz, CieXyz>
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
source.CopyTo(destination[..source.Length]);
}
/// <inheritdoc/>
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint;
}

12
src/ImageSharp/ColorProfiles/ColorConversionOptions.cs

@ -2,6 +2,8 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.ColorProfiles;
@ -33,6 +35,16 @@ public class ColorConversionOptions
/// </summary>
public CieXyz TargetWhitePoint { get; init; } = Illuminants.D50;
/// <summary>
/// Gets the source working space used for companding in conversions from/to XYZ color space.
/// </summary>
public RgbWorkingSpace RgbWorkingSpace { get; init; } = RgbWorkingSpaces.SRgb;
/// <summary>
/// Gets the destination working space used for companding in conversions from/to XYZ color space.
/// </summary>
public RgbWorkingSpace TargetRgbWorkingSpace { get; init; } = RgbWorkingSpaces.SRgb;
/// <summary>
/// Gets the transformation matrix used in conversion to perform chromatic adaptation.
/// </summary>

4
src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs

@ -21,7 +21,7 @@ internal static class ColorProfileConverterExtensionsCieLabCieXyz
CieXyz pcsTo = pcsFrom.ToProfileConnectingSpace(options);
// Adapt to target white point
VonKriesChromaticAdaptation.Transform(options, in pcsTo);
VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, in pcsTo);
// Convert to output from PCS
return TTo.FromProfileConnectingSpace(options, pcsTo);
@ -44,7 +44,7 @@ internal static class ColorProfileConverterExtensionsCieLabCieXyz
CieLab.ToProfileConnectionSpace(options, pcsFrom, pcsTo);
// Adapt to target white point
VonKriesChromaticAdaptation.Transform(options, pcsTo, pcsTo);
VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, pcsTo, pcsTo);
// Convert to output from PCS
TTo.FromProfileConnectionSpace(options, pcsTo, destination);

6
src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
@ -18,7 +18,7 @@ internal static class ColorProfileConverterExtensionsCieXyzCieLab
CieXyz pcsFrom = source.ToProfileConnectingSpace(options);
// Adapt to target white point
VonKriesChromaticAdaptation.Transform(options, in pcsFrom);
VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, in pcsFrom);
// Convert between PCS
CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFrom);
@ -39,7 +39,7 @@ internal static class ColorProfileConverterExtensionsCieXyzCieLab
TFrom.ToProfileConnectionSpace(options, source, pcsFrom);
// Adapt to target white point
VonKriesChromaticAdaptation.Transform(options, pcsFrom, pcsFrom);
VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, pcsFrom, pcsFrom);
// Convert between PCS.
using IMemoryOwner<CieLab> pcsToOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length);

4
src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs

@ -18,7 +18,7 @@ internal static class ColorProfileConverterExtensionsCieXyzCieXyz
CieXyz pcsFrom = source.ToProfileConnectingSpace(options);
// Adapt to target white point
VonKriesChromaticAdaptation.Transform(options, in pcsFrom);
VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, in pcsFrom);
// Convert between PCS
CieXyz pcsTo = CieXyz.FromProfileConnectingSpace(options, in pcsFrom);
@ -39,7 +39,7 @@ internal static class ColorProfileConverterExtensionsCieXyzCieXyz
TFrom.ToProfileConnectionSpace(options, source, pcsFrom);
// Adapt to target white point
VonKriesChromaticAdaptation.Transform(options, pcsFrom, pcsFrom);
VonKriesChromaticAdaptation.Transform<TFrom, TTo>(options, pcsFrom, pcsFrom);
// Convert between PCS.
using IMemoryOwner<CieXyz> pcsToOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length);

171
src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs

@ -0,0 +1,171 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Collections.Concurrent;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.ColorProfiles.Companding;
/// <summary>
/// Companding utilities that allow the accelerated compression-expansion of color channels.
/// </summary>
public static class CompandingUtilities
{
private const int Length = Scale + 2; // 256kb @ 16bit precision.
private const int Scale = (1 << 16) - 1;
private static readonly ConcurrentDictionary<(Type, double), Lazy<float[]>> LookupTables = new();
/// <summary>
/// Lazily creates and stores a companding lookup table using the given function and modifier.
/// </summary>
/// <typeparam name="T">The type of companding function.</typeparam>
/// <param name="compandingFunction">The companding function.</param>
/// <param name="modifier">A modifier to pass to the function.</param>
/// <returns>The <see cref="float"/> array.</returns>
public static Lazy<float[]> GetLookupTable<T>(Func<double, double, double> compandingFunction, double modifier = 0)
=> LookupTables.GetOrAdd((typeof(T), modifier), args => new(() => CreateLookupTableImpl(compandingFunction, args.Item2), true));
/// <summary>
/// Creates a companding lookup table using the given function.
/// </summary>
/// <param name="compandingFunction">The companding function.</param>
/// <param name="modifier">A modifier to pass to the function.</param>
/// <returns>The <see cref="float"/> array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float[] CreateLookupTableImpl(Func<double, double, double> compandingFunction, double modifier = 0)
{
float[] result = new float[Length];
for (int i = 0; i < result.Length; i++)
{
double d = (double)i / Scale;
d = compandingFunction(d, modifier);
result[i] = (float)d;
}
return result;
}
/// <summary>
/// Performs the companding operation on the given vectors using the given table.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
/// <param name="table">The lookup table.</param>
public static void Compand(Span<Vector4> vectors, float[] table)
{
DebugGuard.MustBeGreaterThanOrEqualTo(table.Length, Length, nameof(table));
if (Avx2.IsSupported && vectors.Length >= 2)
{
CompandAvx2(vectors, table);
if (Numerics.Modulo2(vectors.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
ref Vector4 last = ref MemoryMarshal.GetReference(vectors[^1..]);
last = Compand(last, table);
}
}
else
{
CompandScalar(vectors, table);
}
}
/// <summary>
/// Performs the companding operation on the given vector using the given table.
/// </summary>
/// <param name="vector">The vector.</param>
/// <param name="table">The lookup table.</param>
/// <returns>The <see cref="Vector4"/></returns>
public static Vector4 Compand(Vector4 vector, float[] table)
{
DebugGuard.MustBeGreaterThanOrEqualTo(table.Length, Length, nameof(table));
Vector4 zero = Vector4.Zero;
Vector4 scale = new(Scale);
Vector4 multiplied = Numerics.Clamp(vector * Scale, zero, scale);
float f0 = multiplied.X;
float f1 = multiplied.Y;
float f2 = multiplied.Z;
uint i0 = (uint)f0;
uint i1 = (uint)f1;
uint i2 = (uint)f2;
// Alpha is already a linear representation of opacity so we do not want to convert it.
vector.X = Numerics.Lerp(table[i0], table[i0 + 1], f0 - (int)i0);
vector.Y = Numerics.Lerp(table[i1], table[i1 + 1], f1 - (int)i1);
vector.Z = Numerics.Lerp(table[i2], table[i2 + 1], f2 - (int)i2);
return vector;
}
private static unsafe void CompandAvx2(Span<Vector4> vectors, float[] table)
{
fixed (float* tablePointer = &MemoryMarshal.GetArrayDataReference(table))
{
Vector256<float> scale = Vector256.Create((float)Scale);
Vector256<float> zero = Vector256<float>.Zero;
Vector256<int> offset = Vector256.Create(1);
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> vectorsBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(vectors));
ref Vector256<float> vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length / 2u);
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
{
Vector256<float> multiplied = Avx.Multiply(scale, vectorsBase);
multiplied = Avx.Min(Avx.Max(zero, multiplied), scale);
Vector256<int> truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied);
Vector256<float> truncatedF = Avx.ConvertToVector256Single(truncated);
Vector256<float> low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float));
Vector256<float> high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float));
// Alpha is already a linear representation of opacity so we do not want to convert it.
Vector256<float> companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF));
vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl);
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
}
}
private static unsafe void CompandScalar(Span<Vector4> vectors, float[] table)
{
fixed (float* tablePointer = &MemoryMarshal.GetArrayDataReference(table))
{
Vector4 zero = Vector4.Zero;
Vector4 scale = new(Scale);
ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors);
ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length);
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
{
Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale);
float f0 = multiplied.X;
float f1 = multiplied.Y;
float f2 = multiplied.Z;
uint i0 = (uint)f0;
uint i1 = (uint)f1;
uint i2 = (uint)f2;
// Alpha is already a linear representation of opacity so we do not want to convert it.
vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0);
vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1);
vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2);
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
}
}
}

56
src/ImageSharp/ColorProfiles/Companding/GammaCompanding.cs

@ -0,0 +1,56 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.ColorProfiles.Companding;
/// <summary>
/// Implements gamma companding.
/// </summary>
/// <remarks>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
public static class GammaCompanding
{
private static Func<double, double, double> CompressFunction => (d, m) => Math.Pow(d, 1 / m);
private static Func<double, double, double> ExpandFunction => Math.Pow;
/// <summary>
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
/// <param name="gamma">The gamma value.</param>
public static void Compress(Span<Vector4> vectors, double gamma)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<GammaCompandingKey>(CompressFunction, gamma).Value);
/// <summary>
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
/// <param name="gamma">The gamma value.</param>
public static void Expand(Span<Vector4> vectors, double gamma)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<GammaCompandingKey>(ExpandFunction, gamma).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <param name="gamma">The gamma value.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Compress(Vector4 vector, double gamma)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<GammaCompandingKey>(CompressFunction, gamma).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <param name="gamma">The gamma value.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Expand(Vector4 vector, double gamma)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<GammaCompandingKey>(ExpandFunction, gamma).Value);
private class GammaCompandingKey;
}

71
src/ImageSharp/ColorProfiles/Companding/LCompanding.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.ColorProfiles.Companding;
/// <summary>
/// Implements L* companding.
/// </summary>
/// <remarks>
/// For more info see:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
public static class LCompanding
{
private static Func<double, double, double> CompressFunction
=> (d, _) =>
{
if (d <= CieConstants.Epsilon)
{
return (d * CieConstants.Kappa) / 100;
}
return (1.16 * Math.Pow(d, 0.3333333)) - 0.16;
};
private static Func<double, double, double> ExpandFunction
=> (d, _) =>
{
if (d <= 0.08)
{
return (100 * d) / CieConstants.Kappa;
}
return Numerics.Pow3(((float)(d + 0.16f)) / 1.16f);
};
/// <summary>
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public static void Compress(Span<Vector4> vectors)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<LCompandingKey>(CompressFunction).Value);
/// <summary>
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public static void Expand(Span<Vector4> vectors)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<LCompandingKey>(ExpandFunction).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Compress(Vector4 vector)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<LCompandingKey>(CompressFunction).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Expand(Vector4 vector)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<LCompandingKey>(ExpandFunction).Value);
private class LCompandingKey;
}

75
src/ImageSharp/ColorProfiles/Companding/Rec2020Companding.cs

@ -0,0 +1,75 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.ColorProfiles.Companding;
/// <summary>
/// Implements Rec. 2020 companding function.
/// </summary>
/// <remarks>
/// <see href="http://en.wikipedia.org/wiki/Rec._2020"/>
/// </remarks>
public static class Rec2020Companding
{
private const double Alpha = 1.09929682680944;
private const double AlphaMinusOne = Alpha - 1;
private const double Beta = 0.018053968510807;
private const double InverseBeta = Beta * 4.5;
private const double Epsilon = 1 / 0.45;
private static Func<double, double, double> CompressFunction
=> (d, _) =>
{
if (d < Beta)
{
return 4.5 * d;
}
return (Alpha * Math.Pow(d, 0.45)) - AlphaMinusOne;
};
private static Func<double, double, double> ExpandFunction
=> (d, _) =>
{
if (d < InverseBeta)
{
return d / 4.5;
}
return Math.Pow((d + AlphaMinusOne) / Alpha, Epsilon);
};
/// <summary>
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public static void Compress(Span<Vector4> vectors)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<Rec2020CompandingKey>(CompressFunction).Value);
/// <summary>
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public static void Expand(Span<Vector4> vectors)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<Rec2020CompandingKey>(ExpandFunction).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Compress(Vector4 vector)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<Rec2020CompandingKey>(CompressFunction).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Expand(Vector4 vector)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<Rec2020CompandingKey>(ExpandFunction).Value);
private class Rec2020CompandingKey;
}

71
src/ImageSharp/ColorProfiles/Companding/Rec709Companding.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.ColorProfiles.Companding;
/// <summary>
/// Implements the Rec. 709 companding function.
/// </summary>
/// <remarks>
/// http://en.wikipedia.org/wiki/Rec._709
/// </remarks>
public static class Rec709Companding
{
private const double Epsilon = 1 / 0.45;
private static Func<double, double, double> CompressFunction
=> (d, _) =>
{
if (d < 0.018)
{
return 4.5 * d;
}
return (1.099 * Math.Pow(d, 0.45)) - 0.099;
};
private static Func<double, double, double> ExpandFunction
=> (d, _) =>
{
if (d < 0.081)
{
return d / 4.5;
}
return Math.Pow((d + 0.099) / 1.099, Epsilon);
};
/// <summary>
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public static void Compress(Span<Vector4> vectors)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<Rec2020CompandingKey>(CompressFunction).Value);
/// <summary>
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public static void Expand(Span<Vector4> vectors)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<Rec2020CompandingKey>(ExpandFunction).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Compress(Vector4 vector)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<Rec2020CompandingKey>(CompressFunction).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Expand(Vector4 vector)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<Rec2020CompandingKey>(ExpandFunction).Value);
private class Rec2020CompandingKey;
}

71
src/ImageSharp/ColorProfiles/Companding/SRgbCompanding.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.ColorProfiles.Companding;
/// <summary>
/// Implements sRGB companding.
/// </summary>
/// <remarks>
/// For more info see:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
public static class SRgbCompanding
{
private static Func<double, double, double> CompressFunction
=> (d, _) =>
{
if (d <= (0.04045 / 12.92))
{
return d * 12.92;
}
return (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055;
};
private static Func<double, double, double> ExpandFunction
=> (d, _) =>
{
if (d <= 0.04045)
{
return d / 12.92;
}
return Math.Pow((d + 0.055) / 1.055, 2.4);
};
/// <summary>
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public static void Compress(Span<Vector4> vectors)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<SRgbCompandingKey>(CompressFunction).Value);
/// <summary>
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public static void Expand(Span<Vector4> vectors)
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetLookupTable<SRgbCompandingKey>(ExpandFunction).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Compress(Vector4 vector)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<SRgbCompandingKey>(CompressFunction).Value);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static Vector4 Expand(Vector4 vector)
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetLookupTable<SRgbCompandingKey>(ExpandFunction).Value);
private class SRgbCompandingKey;
}

46
src/ImageSharp/ColorProfiles/IColorProfile{TSelf,TProfileSpace}.cs → src/ImageSharp/ColorProfiles/IColorProfile.cs

@ -1,24 +1,29 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// Defines the contract for all color profiles.
/// </summary>
public interface IColorProfile
{
/// <summary>
/// Gets the chromatic adaption white point source.
/// </summary>
/// <returns>The <see cref="GetChromaticAdaptionWhitePointSource"/>.</returns>
public static abstract ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource();
}
/// <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>
public interface IColorProfile<TSelf, TProfileSpace> : IColorProfile, 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.
@ -28,14 +33,6 @@ public interface IColorProfile<TSelf, TProfileSpace> : IEquatable<TSelf>
/// <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>
@ -43,5 +40,20 @@ public interface IColorProfile<TSelf, TProfileSpace> : IEquatable<TSelf>
/// <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);
/// <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);
/// <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);
#pragma warning restore CA1000 // Do not declare static members on generic types
}

3
src/ImageSharp/ColorProfiles/Lms.cs

@ -133,4 +133,7 @@ internal readonly struct Lms : IColorProfile<Lms, CieXyz>
destination[i] = lms.ToProfileConnectingSpace(options);
}
}
/// <inheritdoc/>
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint;
}

246
src/ImageSharp/ColorProfiles/Rgb.cs

@ -0,0 +1,246 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
namespace SixLabors.ImageSharp.ColorProfiles;
internal readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
{
private static readonly Vector3 Min = Vector3.Zero;
private static readonly Vector3 Max = Vector3.One;
/// <summary>
/// Initializes a new instance of the <see cref="Rgb"/> struct.
/// </summary>
/// <param name="r">The red component ranging between 0 and 1.</param>
/// <param name="g">The green component ranging between 0 and 1.</param>
/// <param name="b">The blue component ranging between 0 and 1.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rgb(float r, float g, float b)
: this(new Vector3(r, g, b))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgb"/> struct.
/// </summary>
/// <param name="source">The vector representing the r, g, b components.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Rgb(Vector3 source)
{
source = Vector3.Clamp(source, Min, Max);
this.R = source.X;
this.G = source.Y;
this.B = source.Z;
}
/// <summary>
/// Gets the red component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public readonly float R { get; }
/// <summary>
/// Gets the green component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public readonly float G { get; }
/// <summary>
/// Gets the blue component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public readonly float B { get; }
/// <summary>
/// Compares two <see cref="Rgb"/> objects for equality.
/// </summary>
/// <param name="left">The <see cref="Rgb"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rgb"/> 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 ==(Rgb left, Rgb right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Rgb"/> objects for inequality.
/// </summary>
/// <param name="left">The <see cref="Rgb"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rgb"/> 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 !=(Rgb left, Rgb right) => !left.Equals(right);
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B);
/// <inheritdoc/>
public override string ToString() => FormattableString.Invariant($"Rgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})");
/// <inheritdoc/>
public override bool Equals(object? obj) => obj is Rgb other && this.Equals(other);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rgb other)
=> this.R.Equals(other.R)
&& this.G.Equals(other.G)
&& this.B.Equals(other.B);
/// <inheritdoc/>
public static Rgb FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
{
// Convert to linear rgb then compress.
Rgb linear = new(Vector3.Transform(source.ToVector3(), GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace)));
return FromScaledVector4(options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4()));
}
/// <inheritdoc/>
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<Rgb> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
Matrix4x4 matrix = GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace);
for (int i = 0; i < source.Length; i++)
{
// Convert to linear rgb then compress.
Rgb linear = new(Vector3.Transform(source[i].ToVector3(), matrix));
Vector4 nonlinear = options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4());
destination[i] = FromScaledVector4(nonlinear);
}
}
/// <inheritdoc/>
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options)
{
// First expand to linear rgb
Rgb linear = FromScaledVector4(options.RgbWorkingSpace.Expand(this.ToScaledVector4()));
// Then convert to xyz
return new CieXyz(Vector3.Transform(linear.ToScaledVector3(), GetRgbToCieXyzMatrix(options.RgbWorkingSpace)));
}
/// <inheritdoc/>
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<CieXyz> destination)
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
Matrix4x4 matrix = GetRgbToCieXyzMatrix(options.RgbWorkingSpace);
for (int i = 0; i < source.Length; i++)
{
Rgb rgb = source[i];
// First expand to linear rgb
Rgb linear = FromScaledVector4(options.RgbWorkingSpace.Expand(rgb.ToScaledVector4()));
// Then convert to xyz
destination[i] = new CieXyz(Vector3.Transform(linear.ToScaledVector3(), matrix));
}
}
/// <inheritdoc/>
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
/// <summary>
/// Initializes the pixel instance from a generic scaled <see cref="Vector3"/>.
/// </summary>
/// <param name="source">The vector to load the pixel from.</param>
/// <returns>The <see cref="Rgb"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb FromScaledVector3(Vector3 source) => new(source);
/// <summary>
/// Initializes the pixel instance from a generic scaled <see cref="Vector4"/>.
/// </summary>
/// <param name="source">The vector to load the pixel from.</param>
/// <returns>The <see cref="Rgb"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb FromScaledVector4(Vector4 source) => new(source.X, source.Y, source.Z);
/// <summary>
/// Expands the color into a generic ("scaled") <see cref="Vector3"/> representation
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
/// The vector components are typically expanded in least to greatest significance order.
/// </summary>
/// <returns>The <see cref="Vector3"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToScaledVector3() => new(this.R, this.G, this.B);
/// <summary>
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
/// The vector components are typically expanded in least to greatest significance order.
/// </summary>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4() => new(this.R, this.G, this.B, 1f);
private static Matrix4x4 GetCieXyzToRgbMatrix(RgbWorkingSpace workingSpace)
{
Matrix4x4 matrix = GetRgbToCieXyzMatrix(workingSpace);
Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix);
return inverseMatrix;
}
private static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpace workingSpace)
{
DebugGuard.NotNull(workingSpace, nameof(workingSpace));
RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates;
float xr = chromaticity.R.X;
float xg = chromaticity.G.X;
float xb = chromaticity.B.X;
float yr = chromaticity.R.Y;
float yg = chromaticity.G.Y;
float yb = chromaticity.B.Y;
float mXr = xr / yr;
float mZr = (1 - xr - yr) / yr;
float mXg = xg / yg;
float mZg = (1 - xg - yg) / yg;
float mXb = xb / yb;
float mZb = (1 - xb - yb) / yb;
Matrix4x4 xyzMatrix = new()
{
M11 = mXr,
M21 = mXg,
M31 = mXb,
M12 = 1F,
M22 = 1F,
M32 = 1F,
M13 = mZr,
M23 = mZg,
M33 = mZb,
M44 = 1F
};
Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix);
Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.ToVector3(), inverseXyzMatrix);
// Use transposed Rows/Columns
// TODO: Is there a built in method for this multiplication?
return new Matrix4x4
{
M11 = vector.X * mXr,
M21 = vector.Y * mXg,
M31 = vector.Z * mXb,
M12 = vector.X * 1,
M22 = vector.Y * 1,
M32 = vector.Z * 1,
M13 = vector.X * mZr,
M23 = vector.Y * mZg,
M33 = vector.Z * mZb,
M44 = 1F
};
}
}

82
src/ImageSharp/ColorProfiles/RgbPrimariesChromaticityCoordinates.cs

@ -0,0 +1,82 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// Represents the chromaticity coordinates of RGB primaries.
/// One of the specifiers of <see cref="RgbWorkingSpace"/>.
/// </summary>
public readonly struct RgbPrimariesChromaticityCoordinates : IEquatable<RgbPrimariesChromaticityCoordinates>
{
/// <summary>
/// Initializes a new instance of the <see cref="RgbPrimariesChromaticityCoordinates"/> struct.
/// </summary>
/// <param name="r">The chromaticity coordinates of the red channel.</param>
/// <param name="g">The chromaticity coordinates of the green channel.</param>
/// <param name="b">The chromaticity coordinates of the blue channel.</param>
public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b)
{
this.R = r;
this.G = g;
this.B = b;
}
/// <summary>
/// Gets the chromaticity coordinates of the red channel.
/// </summary>
public CieXyChromaticityCoordinates R { get; }
/// <summary>
/// Gets the chromaticity coordinates of the green channel.
/// </summary>
public CieXyChromaticityCoordinates G { get; }
/// <summary>
/// Gets the chromaticity coordinates of the blue channel.
/// </summary>
public CieXyChromaticityCoordinates B { get; }
/// <summary>
/// Compares two <see cref="RgbPrimariesChromaticityCoordinates"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="RgbPrimariesChromaticityCoordinates"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RgbPrimariesChromaticityCoordinates"/> 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 ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right)
=> left.Equals(right);
/// <summary>
/// Compares two <see cref="RgbPrimariesChromaticityCoordinates"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="RgbPrimariesChromaticityCoordinates"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RgbPrimariesChromaticityCoordinates"/> 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 !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right)
=> !left.Equals(right);
/// <inheritdoc/>
public override bool Equals(object? obj)
=> obj is RgbPrimariesChromaticityCoordinates other && this.Equals(other);
/// <inheritdoc/>
public bool Equals(RgbPrimariesChromaticityCoordinates other)
=> this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B);
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B);
}

114
src/ImageSharp/ColorProfiles/RgbWorkingSpaces.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Companding;
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
namespace SixLabors.ColorProfiles;
/// <summary>
/// Chromaticity coordinates based on: <see href="http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html"/>
/// </summary>
public static class RgbWorkingSpaces
{
/// <summary>
/// sRgb working space.
/// </summary>
/// <remarks>
/// Uses proper companding function, according to:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_Rgb_to_XYZ.html"/>
/// </remarks>
public static readonly RgbWorkingSpace SRgb = new SRgbWorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// Simplified sRgb working space (uses <see cref="GammaCompanding">gamma companding</see> instead of <see cref="SRgbCompanding"/>).
/// See also <see cref="SRgb"/>.
/// </summary>
public static readonly RgbWorkingSpace SRgbSimplified = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// Rec. 709 (ITU-R Recommendation BT.709) working space.
/// </summary>
public static readonly RgbWorkingSpace Rec709 = new Rec709WorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F)));
/// <summary>
/// Rec. 2020 (ITU-R Recommendation BT.2020F) working space.
/// </summary>
public static readonly RgbWorkingSpace Rec2020 = new Rec2020WorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F)));
/// <summary>
/// ECI Rgb v2 working space.
/// </summary>
public static readonly RgbWorkingSpace ECIRgbv2 = new LWorkingSpace(Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F)));
/// <summary>
/// Adobe Rgb (1998) working space.
/// </summary>
public static readonly RgbWorkingSpace AdobeRgb1998 = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// Apple sRgb working space.
/// </summary>
public static readonly RgbWorkingSpace ApplesRgb = new GammaWorkingSpace(1.8F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F)));
/// <summary>
/// Best Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace BestRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F)));
/// <summary>
/// Beta Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace BetaRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F)));
/// <summary>
/// Bruce Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace BruceRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// CIE Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace CIERgb = new GammaWorkingSpace(2.2F, Illuminants.E, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F)));
/// <summary>
/// ColorMatch Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace ColorMatchRgb = new GammaWorkingSpace(1.8F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F)));
/// <summary>
/// Don Rgb 4 working space.
/// </summary>
public static readonly RgbWorkingSpace DonRgb4 = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F)));
/// <summary>
/// Ekta Space PS5 working space.
/// </summary>
public static readonly RgbWorkingSpace EktaSpacePS5 = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F)));
/// <summary>
/// NTSC Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace NTSCRgb = new GammaWorkingSpace(2.2F, Illuminants.C, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F)));
/// <summary>
/// PAL/SECAM Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace PALSECAMRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
/// <summary>
/// ProPhoto Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace ProPhotoRgb = new GammaWorkingSpace(1.8F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F)));
/// <summary>
/// SMPTE-C Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace SMPTECRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F)));
/// <summary>
/// Wide Gamut Rgb working space.
/// </summary>
public static readonly RgbWorkingSpace WideGamutRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F)));
}

44
src/ImageSharp/ColorProfiles/VonKriesChromaticAdaptation.cs

@ -19,16 +19,25 @@ public static class VonKriesChromaticAdaptation
/// <summary>
/// Performs a linear transformation of a source color in to the destination color.
/// </summary>
/// <typeparam name="TFrom">The type of color profile to convert from.</typeparam>
/// <typeparam name="TTo">The type of color profile to convert to.</typeparam>
/// <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)
public static CieXyz Transform<TFrom, TTo>(ColorConversionOptions options, in CieXyz source)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
CieXyz sourceWhitePoint = options.WhitePoint;
CieXyz destinationWhitePoint = options.TargetWhitePoint;
CieXyz sourceWhitePoint = TFrom.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
? options.WhitePoint
: options.RgbWorkingSpace.WhitePoint;
if (sourceWhitePoint.Equals(destinationWhitePoint))
CieXyz targetWhitePoint = TTo.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
? options.TargetWhitePoint
: options.TargetRgbWorkingSpace.WhitePoint;
if (sourceWhitePoint.Equals(targetWhitePoint))
{
return new(source.X, source.Y, source.Z);
}
@ -37,7 +46,7 @@ public static class VonKriesChromaticAdaptation
Vector3 sourceColorLms = Vector3.Transform(source.ToVector3(), matrix);
Vector3 sourceWhitePointLms = Vector3.Transform(sourceWhitePoint.ToVector3(), matrix);
Vector3 targetWhitePointLms = Vector3.Transform(destinationWhitePoint.ToVector3(), matrix);
Vector3 targetWhitePointLms = Vector3.Transform(targetWhitePoint.ToVector3(), matrix);
Vector3 vector = targetWhitePointLms / sourceWhitePointLms;
Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms);
@ -49,19 +58,28 @@ public static class VonKriesChromaticAdaptation
/// <summary>
/// Performs a bulk linear transformation of a source color in to the destination color.
/// </summary>
/// <typeparam name="TFrom">The type of color profile to convert from.</typeparam>
/// <typeparam name="TTo">The type of color profile to convert to.</typeparam>
/// <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)
public static void Transform<TFrom, TTo>(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieXyz> destination)
where TFrom : struct, IColorProfile
where TTo : struct, IColorProfile
{
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
int count = source.Length;
CieXyz sourceWhitePoint = options.WhitePoint;
CieXyz destinationWhitePoint = options.TargetWhitePoint;
CieXyz sourceWhitePoint = TFrom.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
? options.WhitePoint
: options.RgbWorkingSpace.WhitePoint;
CieXyz targetWhitePoint = TTo.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
? options.TargetWhitePoint
: options.TargetRgbWorkingSpace.WhitePoint;
if (sourceWhitePoint.Equals(destinationWhitePoint))
if (sourceWhitePoint.Equals(targetWhitePoint))
{
source.CopyTo(destination[..count]);
return;
@ -73,16 +91,18 @@ public static class VonKriesChromaticAdaptation
ref CieXyz sourceBase = ref MemoryMarshal.GetReference(source);
ref CieXyz destinationBase = ref MemoryMarshal.GetReference(destination);
Vector3 sourceWhitePointLms = Vector3.Transform(sourceWhitePoint.ToVector3(), matrix);
Vector3 targetWhitePointLms = Vector3.Transform(targetWhitePoint.ToVector3(), matrix);
Vector3 vector = targetWhitePointLms / sourceWhitePointLms;
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));
}

68
src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs

@ -0,0 +1,68 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.Companding;
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
/// <summary>
/// The gamma working space.
/// </summary>
public sealed class GammaWorkingSpace : RgbWorkingSpace
{
/// <summary>
/// Initializes a new instance of the <see cref="GammaWorkingSpace" /> class.
/// </summary>
/// <param name="gamma">The gamma value.</param>
/// <param name="referenceWhite">The reference white point.</param>
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
public GammaWorkingSpace(float gamma, CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates)
: base(referenceWhite, chromaticityCoordinates) => this.Gamma = gamma;
/// <summary>
/// Gets the gamma value.
/// </summary>
public float Gamma { get; }
/// <inheritdoc/>
public override void Compress(Span<Vector4> vectors) => GammaCompanding.Compress(vectors, this.Gamma);
/// <inheritdoc/>
public override void Expand(Span<Vector4> vectors) => GammaCompanding.Expand(vectors, this.Gamma);
/// <inheritdoc/>
public override Vector4 Compress(Vector4 vector) => GammaCompanding.Compress(vector, this.Gamma);
/// <inheritdoc/>
public override Vector4 Expand(Vector4 vector) => GammaCompanding.Expand(vector, this.Gamma);
/// <inheritdoc/>
public override bool Equals(object? obj)
{
if (obj is null)
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj is GammaWorkingSpace other)
{
return this.Gamma.Equals(other.Gamma)
&& this.WhitePoint.Equals(other.WhitePoint)
&& this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates);
}
return false;
}
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(
this.WhitePoint,
this.ChromaticityCoordinates,
this.Gamma);
}

35
src/ImageSharp/ColorProfiles/WorkingSpaces/LWorkingSpace.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.Companding;
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
/// <summary>
/// L* working space.
/// </summary>
public sealed class LWorkingSpace : RgbWorkingSpace
{
/// <summary>
/// Initializes a new instance of the <see cref="LWorkingSpace" /> class.
/// </summary>
/// <param name="referenceWhite">The reference white point.</param>
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
public LWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates)
: base(referenceWhite, chromaticityCoordinates)
{
}
/// <inheritdoc/>
public override void Compress(Span<Vector4> vectors) => LCompanding.Compress(vectors);
/// <inheritdoc/>
public override void Expand(Span<Vector4> vectors) => LCompanding.Expand(vectors);
/// <inheritdoc/>
public override Vector4 Compress(Vector4 vector) => LCompanding.Compress(vector);
/// <inheritdoc/>
public override Vector4 Expand(Vector4 vector) => LCompanding.Expand(vector);
}

35
src/ImageSharp/ColorProfiles/WorkingSpaces/Rec2020WorkingSpace.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.Companding;
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
/// <summary>
/// Rec. 2020 (ITU-R Recommendation BT.2020F) working space.
/// </summary>
public sealed class Rec2020WorkingSpace : RgbWorkingSpace
{
/// <summary>
/// Initializes a new instance of the <see cref="Rec2020WorkingSpace" /> class.
/// </summary>
/// <param name="referenceWhite">The reference white point.</param>
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
public Rec2020WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates)
: base(referenceWhite, chromaticityCoordinates)
{
}
/// <inheritdoc/>
public override void Compress(Span<Vector4> vectors) => Rec2020Companding.Compress(vectors);
/// <inheritdoc/>
public override void Expand(Span<Vector4> vectors) => Rec2020Companding.Expand(vectors);
/// <inheritdoc/>
public override Vector4 Compress(Vector4 vector) => Rec2020Companding.Compress(vector);
/// <inheritdoc/>
public override Vector4 Expand(Vector4 vector) => Rec2020Companding.Expand(vector);
}

35
src/ImageSharp/ColorProfiles/WorkingSpaces/Rec709WorkingSpace.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.Companding;
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
/// <summary>
/// Rec. 709 (ITU-R Recommendation BT.709) working space.
/// </summary>
public sealed class Rec709WorkingSpace : RgbWorkingSpace
{
/// <summary>
/// Initializes a new instance of the <see cref="Rec709WorkingSpace" /> class.
/// </summary>
/// <param name="referenceWhite">The reference white point.</param>
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
public Rec709WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates)
: base(referenceWhite, chromaticityCoordinates)
{
}
/// <inheritdoc/>
public override void Compress(Span<Vector4> vectors) => Rec709Companding.Compress(vectors);
/// <inheritdoc/>
public override void Expand(Span<Vector4> vectors) => Rec709Companding.Expand(vectors);
/// <inheritdoc/>
public override Vector4 Compress(Vector4 vector) => Rec709Companding.Compress(vector);
/// <inheritdoc/>
public override Vector4 Expand(Vector4 vector) => Rec709Companding.Expand(vector);
}

85
src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs

@ -0,0 +1,85 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
/// <summary>
/// Base class for all implementations of <see cref="RgbWorkingSpace"/>.
/// </summary>
public abstract class RgbWorkingSpace
{
/// <summary>
/// Initializes a new instance of the <see cref="RgbWorkingSpace"/> class.
/// </summary>
/// <param name="referenceWhite">The reference white point.</param>
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
protected RgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates)
{
this.WhitePoint = referenceWhite;
this.ChromaticityCoordinates = chromaticityCoordinates;
}
/// <summary>
/// Gets the reference white point
/// </summary>
public CieXyz WhitePoint { get; }
/// <summary>
/// Gets the chromaticity of the rgb primaries.
/// </summary>
public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
/// <summary>
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public abstract void Compress(Span<Vector4> vectors);
/// <summary>
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
public abstract void Expand(Span<Vector4> vectors);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public abstract Vector4 Compress(Vector4 vector);
/// <summary>
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public abstract Vector4 Expand(Vector4 vector);
/// <inheritdoc/>
public override bool Equals(object? obj)
{
if (obj is null)
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj is RgbWorkingSpace other)
{
return this.WhitePoint.Equals(other.WhitePoint)
&& this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates);
}
return false;
}
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates);
}

35
src/ImageSharp/ColorProfiles/WorkingSpaces/SRgbWorkingSpace.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorProfiles.Companding;
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
/// <summary>
/// The sRgb working space.
/// </summary>
public sealed class SRgbWorkingSpace : RgbWorkingSpace
{
/// <summary>
/// Initializes a new instance of the <see cref="SRgbWorkingSpace" /> class.
/// </summary>
/// <param name="referenceWhite">The reference white point.</param>
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
public SRgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates)
: base(referenceWhite, chromaticityCoordinates)
{
}
/// <inheritdoc/>
public override void Compress(Span<Vector4> vectors) => SRgbCompanding.Compress(vectors);
/// <inheritdoc/>
public override void Expand(Span<Vector4> vectors) => SRgbCompanding.Expand(vectors);
/// <inheritdoc/>
public override Vector4 Compress(Vector4 vector) => SRgbCompanding.Compress(vector);
/// <inheritdoc/>
public override Vector4 Expand(Vector4 vector) => SRgbCompanding.Expand(vector);
}
Loading…
Cancel
Save