Browse Source

Add Rgb color spaces

pull/181/head
James Jackson-South 9 years ago
parent
commit
8bf48021a9
  1. 51
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs
  2. 66
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs
  3. 46
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs
  4. 7
      src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.cs
  5. 4
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
  6. 4
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
  7. 10
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
  8. 2
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
  9. 51
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs
  10. 68
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs
  11. 52
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs
  12. 30
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs
  13. 30
      src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs
  14. 175
      src/ImageSharp/Colors/Spaces/LinearRgb.cs
  15. 175
      src/ImageSharp/Colors/Spaces/Rgb.cs
  16. 6
      tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs
  17. 35
      tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs
  18. 127
      tests/ImageSharp.Tests/Colors/Colorspaces/RgbAndCieXyzConversionTest.cs

51
src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.CieXyz.cs

@ -7,6 +7,7 @@ namespace ImageSharp.Colors.Spaces.Conversion
{
using ImageSharp.Colors.Spaces;
using ImageSharp.Colors.Spaces.Conversion.Implementation.CieLab;
using ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
@ -15,6 +16,8 @@ namespace ImageSharp.Colors.Spaces.Conversion
{
private static readonly CieLabToCieXyzConverter CieLabToCieXyzConverter = new CieLabToCieXyzConverter();
private LinearRgbToCieXyzConverter linearRgbToCieXyzConverter;
/// <summary>
/// Converts a <see cref="CieLab"/> into a <see cref="CieXyz"/>
/// </summary>
@ -47,5 +50,53 @@ namespace ImageSharp.Colors.Spaces.Conversion
// Conversion
return this.cachedCieXyzAndLmsConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(Rgb color)
{
Guard.NotNull(color, nameof(color));
// Conversion
LinearRgb linear = RgbToLinearRgbConverter.Convert(color);
return this.ToCieXyz(linear);
}
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="CieXyz"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="CieXyz"/></returns>
public CieXyz ToCieXyz(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
// Conversion
LinearRgbToCieXyzConverter converter = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace);
CieXyz unadapted = converter.Convert(color);
// Adaptation
return color.WorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
? unadapted
: this.Adapt(unadapted, color.WorkingSpace.WhitePoint);
}
/// <summary>
/// Gets the correct converter for the given rgb working space.
/// </summary>
/// <param name="workingSpace">The source working space</param>
/// <returns>The <see cref="LinearRgbToCieXyzConverter"/></returns>
private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace)
{
if (this.linearRgbToCieXyzConverter != null && this.linearRgbToCieXyzConverter.SourceWorkingSpace.Equals(workingSpace))
{
return this.linearRgbToCieXyzConverter;
}
return this.linearRgbToCieXyzConverter = new LinearRgbToCieXyzConverter(workingSpace);
}
}
}

66
src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.LinearRgb.cs

@ -0,0 +1,66 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion
{
using ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorSpaceConverter
{
private static readonly RgbToLinearRgbConverter RgbToLinearRgbConverter = new RgbToLinearRgbConverter();
private CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter;
/// <summary>
/// Converts a <see cref="Rgb"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(Rgb color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return RgbToLinearRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="LinearRgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="LinearRgb"/></returns>
public LinearRgb ToLinearRgb(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Adaptation
CieXyz adapted = this.TargetRgbWorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
? color
: this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint);
// Conversion
CieXyzToLinearRgbConverter xyzConverter = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace);
return xyzConverter.Convert(adapted);
}
/// <summary>
/// Gets the correct converter for the given rgb working space.
/// </summary>
/// <param name="workingSpace">The target working space</param>
/// <returns>The <see cref="CieXyzToLinearRgbConverter"/></returns>
private CieXyzToLinearRgbConverter GetCieXyxToLinearRgbConverter(IRgbWorkingSpace workingSpace)
{
if (this.cieXyzToLinearRgbConverter != null && this.cieXyzToLinearRgbConverter.TargetWorkingSpace.Equals(workingSpace))
{
return this.cieXyzToLinearRgbConverter;
}
return this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(workingSpace);
}
}
}

46
src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Rgb.cs

@ -0,0 +1,46 @@
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion
{
using ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb;
/// <summary>
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
/// </summary>
public partial class ColorSpaceConverter
{
private static readonly LinearRgbToRgbConverter LinearRgbToRgbConverter = new LinearRgbToRgbConverter();
/// <summary>
/// Converts a <see cref="LinearRgb"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(LinearRgb color)
{
Guard.NotNull(color, nameof(color));
// Conversion
return LinearRgbToRgbConverter.Convert(color);
}
/// <summary>
/// Converts a <see cref="CieXyz"/> into a <see cref="Rgb"/>
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The <see cref="Rgb"/></returns>
public Rgb ToRgb(CieXyz color)
{
Guard.NotNull(color, nameof(color));
// Conversion
LinearRgb linear = this.ToLinearRgb(color);
// Compand
return this.ToRgb(linear);
}
}
}

7
src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.cs

@ -33,6 +33,7 @@ namespace ImageSharp.Colors.Spaces.Conversion
this.LmsAdaptationMatrix = CieXyzAndLmsConverter.DefaultTransformationMatrix;
this.ChromaticAdaptation = new VonKriesChromaticAdaptation(this.cachedCieXyzAndLmsConverter, this.cachedCieXyzAndLmsConverter);
this.TargetLabWhitePoint = CieLab.DefaultWhitePoint;
this.TargetRgbWorkingSpace = Rgb.DefaultWorkingSpace;
}
/// <summary>
@ -47,6 +48,12 @@ namespace ImageSharp.Colors.Spaces.Conversion
/// </summary>
public CieXyz TargetLabWhitePoint { get; set; }
/// <summary>
/// Gets or sets the target working space used *when creating* RGB colors. (RGB colors on the input already contain the working space information)
/// Defaults to: <see cref="Rgb.DefaultWorkingSpace"/>.
/// </summary>
public IRgbWorkingSpace TargetRgbWorkingSpace { get; set; }
/// <summary>
/// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed.
/// </summary>

4
src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs

@ -10,12 +10,12 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieLab
/// <summary>
/// Converts from <see cref="CieLab"/> to <see cref="CieXyz"/>.
/// </summary>
public class CieLabToCieXyzConverter : IColorConversion<CieLab, CieXyz>
internal class CieLabToCieXyzConverter : IColorConversion<CieLab, CieXyz>
{
/// <inheritdoc/>
public CieXyz Convert(CieLab input)
{
Guard.NotNull(input, nameof(input));
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
float l = input.L, a = input.A, b = input.B;

4
src/ImageSharp/Colors/Spaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs

@ -10,7 +10,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieLab
/// <summary>
/// Converts from <see cref="CieXyz"/> to <see cref="CieLab"/>.
/// </summary>
public class CieXyzToCieLabConverter : IColorConversion<CieXyz, CieLab>
internal class CieXyzToCieLabConverter : IColorConversion<CieXyz, CieLab>
{
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToCieLabConverter"/> class.
@ -37,7 +37,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.CieLab
/// <inheritdoc />
public CieLab Convert(CieXyz input)
{
Guard.NotNull(input, nameof(input));
DebugGuard.NotNull(input, nameof(input));
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
float wx = this.LabWhitePoint.X, wy = this.LabWhitePoint.Y, wz = this.LabWhitePoint.Z;

10
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs

@ -12,7 +12,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Lms
/// <summary>
/// Color converter between CIE XYZ and LMS
/// </summary>
public class CieXyzAndLmsConverter : IColorConversion<CieXyz, Lms>, IColorConversion<Lms, CieXyz>
internal class CieXyzAndLmsConverter : IColorConversion<CieXyz, Lms>, IColorConversion<Lms, CieXyz>
{
/// <summary>
/// Default transformation matrix used, when no other is set. (Bradford)
@ -44,7 +44,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Lms
}
/// <summary>
/// Gets transformation matrix used for the conversion (definition of the cone response domain).
/// Gets or sets the transformation matrix used for the conversion (definition of the cone response domain).
/// <see cref="LmsAdaptationMatrix"/>
/// </summary>
public Matrix4x4 TransformationMatrix
@ -54,7 +54,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Lms
return this.transformationMatrix;
}
internal set
set
{
this.transformationMatrix = value;
Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix);
@ -64,7 +64,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Lms
/// <inheritdoc/>
public Lms Convert(CieXyz input)
{
Guard.NotNull(input, nameof(input));
DebugGuard.NotNull(input, nameof(input));
Vector3 vector = Vector3.Transform(input.Vector, this.transformationMatrix);
return new Lms(vector);
@ -73,6 +73,8 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Lms
/// <inheritdoc/>
public CieXyz Convert(Lms input)
{
DebugGuard.NotNull(input, nameof(input));
Vector3 vector = Vector3.Transform(input.Vector, this.inverseTransformationMatrix);
return new CieXyz(vector);
}

2
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs

@ -25,7 +25,7 @@ namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Lms
/// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65)
/// </summary>
public static readonly Matrix4x4 VonKriesHPEAdjusted
= new Matrix4x4()
= new Matrix4x4
{
M11 = 0.40024F, M12 = 0.7076F, M13 = -0.08081F,
M21 = -0.2263F, M22 = 1.16532F, M23 = 0.0457F,

51
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs

@ -0,0 +1,51 @@
// <copyright file="LinearRgbToRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb
{
using System.Numerics;
using Rgb = ImageSharp.Colors.Spaces.Rgb;
/// <summary>
/// Color converter between CieXyz and LinearRgb
/// </summary>
internal class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase, IColorConversion<CieXyz, LinearRgb>
{
private readonly Matrix4x4 conversionMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToLinearRgbConverter"/> class.
/// </summary>
public CieXyzToLinearRgbConverter()
: this(Rgb.DefaultWorkingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CieXyzToLinearRgbConverter"/> class.
/// </summary>
/// <param name="workingSpace">The target working space.</param>
public CieXyzToLinearRgbConverter(IRgbWorkingSpace workingSpace)
{
this.TargetWorkingSpace = workingSpace;
this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace);
}
/// <summary>
/// Gets the target working space
/// </summary>
public IRgbWorkingSpace TargetWorkingSpace { get; }
/// <inheritdoc/>
public LinearRgb Convert(CieXyz input)
{
DebugGuard.NotNull(input, nameof(input));
Vector3 vector = Vector3.Transform(input.Vector, this.conversionMatrix);
return new LinearRgb(vector, this.TargetWorkingSpace);
}
}
}

68
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs

@ -0,0 +1,68 @@
// <copyright file="LinearRgbAndCieXyzConverterBase.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb
{
using System.Numerics;
/// <summary>
/// Provides base methods for converting between Rgb and CieXyz color spaces.
/// </summary>
internal abstract class LinearRgbAndCieXyzConverterBase
{
/// <summary>
/// Geturns the correct matrix to convert between the Rgb and CieXyz color space.
/// </summary>
/// <param name="workingSpace">The Rgb working space.</param>
/// <returns>The <see cref="Matrix4x4"/> based on the chromaticity and working space.</returns>
public static Matrix4x4 GetRgbToCieXyzMatrix(IRgbWorkingSpace 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;
const float Yr = 1;
float mZr = (1 - xr - yr) / yr;
float mXg = xg / yg;
const float Yg = 1;
float mZg = (1 - xg - yg) / yg;
float mXb = xb / yb;
const float Yb = 1;
float mZb = (1 - xb - yb) / yb;
Matrix4x4 xyzMatrix = new Matrix4x4
{
M11 = mXr, M12 = mXg, M13 = mXb,
M21 = Yr, M22 = Yg, M23 = Yb,
M31 = mZr, M32 = mZg, M33 = mZb,
M44 = 1F
};
Matrix4x4 inverseXyzMatrix;
Matrix4x4.Invert(xyzMatrix, out inverseXyzMatrix);
Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix);
// TODO: Is there a built in method for this?
return new Matrix4x4
{
M11 = vector.X * mXr, M12 = vector.Y * mXg, M13 = vector.Z * mXb,
M21 = vector.X * Yr, M22 = vector.Y * Yg, M23 = vector.Z * Yb,
M31 = vector.X * mZr, M32 = vector.Y * mZg, M33 = vector.Z * mZb,
M44 = 1F
};
}
}
}

52
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs

@ -0,0 +1,52 @@
// <copyright file="LinearRgbToCieXyzConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb
{
using System.Numerics;
using Rgb = ImageSharp.Colors.Spaces.Rgb;
/// <summary>
/// Color converter between LinearRgb and CieXyz
/// </summary>
internal class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase, IColorConversion<LinearRgb, CieXyz>
{
private readonly Matrix4x4 conversionMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgbToCieXyzConverter"/> class.
/// </summary>
public LinearRgbToCieXyzConverter()
: this(Rgb.DefaultWorkingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgbToCieXyzConverter"/> class.
/// </summary>
/// <param name="workingSpace">The target working space.</param>
public LinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace)
{
this.SourceWorkingSpace = workingSpace;
this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace);
}
/// <summary>
/// Gets the source working space
/// </summary>
public IRgbWorkingSpace SourceWorkingSpace { get; }
/// <inheritdoc/>
public CieXyz Convert(LinearRgb input)
{
DebugGuard.NotNull(input, nameof(input));
Guard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal.");
Vector3 vector = Vector3.Transform(input.Vector, this.conversionMatrix);
return new CieXyz(vector);
}
}
}

30
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs

@ -0,0 +1,30 @@
// <copyright file="LinearRgbToRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb
{
using System.Numerics;
using Rgb = ImageSharp.Colors.Spaces.Rgb;
/// <summary>
/// Color converter between LinearRgb and Rgb
/// </summary>
public class LinearRgbToRgbConverter : IColorConversion<LinearRgb, Rgb>
{
/// <inheritdoc/>
public Rgb Convert(LinearRgb input)
{
DebugGuard.NotNull(input, nameof(input));
Vector3 vector = input.Vector;
vector.X = input.WorkingSpace.Companding.Compress(vector.X);
vector.Y = input.WorkingSpace.Companding.Compress(vector.Y);
vector.Z = input.WorkingSpace.Companding.Compress(vector.Z);
return new Rgb(vector, input.WorkingSpace);
}
}
}

30
src/ImageSharp/Colors/Spaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs

@ -0,0 +1,30 @@
// <copyright file="RgbToLinearRgbConverter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb
{
using System.Numerics;
using Rgb = ImageSharp.Colors.Spaces.Rgb;
/// <summary>
/// Color converter between Rgb and LinearRgb
/// </summary>
public class RgbToLinearRgbConverter : IColorConversion<Rgb, LinearRgb>
{
/// <inheritdoc/>
public LinearRgb Convert(Rgb input)
{
Guard.NotNull(input, nameof(input));
Vector3 vector = input.Vector;
vector.X = input.WorkingSpace.Companding.Expand(vector.X);
vector.Y = input.WorkingSpace.Companding.Expand(vector.Y);
vector.Z = input.WorkingSpace.Companding.Expand(vector.Z);
return new LinearRgb(vector, input.WorkingSpace);
}
}
}

175
src/ImageSharp/Colors/Spaces/LinearRgb.cs

@ -0,0 +1,175 @@
// <copyright file="LinearRgb.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an linear Rgb color with specified <see cref="IRgbWorkingSpace"/> working space
/// </summary>
public struct LinearRgb : IColorVector, IEquatable<LinearRgb>, IAlmostEquatable<LinearRgb, float>
{
/// <summary>
/// Represents a <see cref="LinearRgb"/> that has R, G, and B values set to zero.
/// </summary>
public static readonly LinearRgb Empty = default(LinearRgb);
/// <summary>
/// The default LinearRgb working space
/// </summary>
private static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgb"/> 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>
public LinearRgb(float r, float g, float b)
: this(new Vector3(r, g, b))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
/// </summary>
/// <param name="vector">The vector representing the r, g, b components.</param>
public LinearRgb(Vector3 vector)
: this(vector, DefaultWorkingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
/// </summary>
/// <param name="vector">The vector representing the r, g, b components.</param>
/// <param name="workingSpace">The LinearRgb working space.</param>
public LinearRgb(Vector3 vector, IRgbWorkingSpace workingSpace)
: this()
{
// Clamp to 0-1 range.
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One);
this.WorkingSpace = workingSpace;
}
/// <summary>
/// Gets the red component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float R => this.backingVector.X;
/// <summary>
/// Gets the green component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float G => this.backingVector.Y;
/// <summary>
/// Gets the blue component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float B => this.backingVector.Z;
/// <summary>
/// Gets the LinearRgb color space <seealso cref="RgbWorkingSpaces"/>
/// </summary>
public IRgbWorkingSpace WorkingSpace { get; }
/// <summary>
/// Gets a value indicating whether this <see cref="LinearRgb"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector => this.backingVector;
/// <summary>
/// Compares two <see cref="LinearRgb"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="LinearRgb"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="LinearRgb"/> 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 ==(LinearRgb left, LinearRgb right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="LinearRgb"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="LinearRgb"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="LinearRgb"/> 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 !=(LinearRgb left, LinearRgb right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "LinearRgb [ Empty ]";
}
return $"LinearRgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is LinearRgb)
{
return this.Equals((LinearRgb)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(LinearRgb other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
public bool AlmostEquals(LinearRgb other, float precision)
{
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

175
src/ImageSharp/Colors/Spaces/Rgb.cs

@ -0,0 +1,175 @@
// <copyright file="Rgb.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Colors.Spaces
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an RGB color with specified <see cref="IRgbWorkingSpace"/> working space
/// </summary>
public struct Rgb : IColorVector, IEquatable<Rgb>, IAlmostEquatable<Rgb, float>
{
/// <summary>
/// Represents a <see cref="Rgb"/> that has R, G, and B values set to zero.
/// </summary>
public static readonly Rgb Empty = default(Rgb);
/// <summary>
/// The default rgb working space
/// </summary>
internal static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private readonly Vector3 backingVector;
/// <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>
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="vector">The vector representing the r, g, b components.</param>
public Rgb(Vector3 vector)
: this(vector, DefaultWorkingSpace)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Rgb"/> struct.
/// </summary>
/// <param name="vector">The vector representing the r, g, b components.</param>
/// <param name="workingSpace">The rgb working space.</param>
public Rgb(Vector3 vector, IRgbWorkingSpace workingSpace)
: this()
{
// Clamp to 0-1 range.
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One);
this.WorkingSpace = workingSpace;
}
/// <summary>
/// Gets the red component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float R => this.backingVector.X;
/// <summary>
/// Gets the green component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float G => this.backingVector.Y;
/// <summary>
/// Gets the blue component.
/// <remarks>A value usually ranging between 0 and 1.</remarks>
/// </summary>
public float B => this.backingVector.Z;
/// <summary>
/// Gets the Rgb color space <seealso cref="RgbWorkingSpaces"/>
/// </summary>
public IRgbWorkingSpace WorkingSpace { get; }
/// <summary>
/// Gets a value indicating whether this <see cref="Rgb"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <inheritdoc />
public Vector3 Vector => this.backingVector;
/// <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>
public static bool operator ==(Rgb left, Rgb right)
{
return 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>
public static bool operator !=(Rgb left, Rgb right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.backingVector.GetHashCode();
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Rgb [ Empty ]";
}
return $"Rgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Rgb)
{
return this.Equals((Rgb)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Rgb other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <inheritdoc/>
public bool AlmostEquals(Rgb other, float precision)
{
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
}

6
tests/ImageSharp.Benchmarks/Color/ColorspaceConvert.cs → tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs

@ -9,7 +9,7 @@
using ColorSpaceConverter = ImageSharp.Colors.Spaces.Conversion.ColorSpaceConverter;
public class ColorspaceConvert
public class ColorspaceCieXyzToLmsConvert
{
private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F);
@ -21,13 +21,13 @@
[Benchmark(Baseline = true, Description = "Colourful Convert")]
public LMSColor SystemDrawingColorEqual()
public LMSColor ColourfulConvert()
{
return ColourfulConverter.ToLMS(XYZColor);
}
[Benchmark(Description = "ImageSharp Convert")]
public Lms ColorEqual()
public Lms ColorSpaceConvert()
{
return ColorSpaceConverter.ToLms(CieXyz);
}

35
tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs

@ -0,0 +1,35 @@
namespace ImageSharp.Benchmarks.Color
{
using BenchmarkDotNet.Attributes;
using Colourful;
using Colourful.Conversion;
using ImageSharp.Colors.Spaces;
using ColorSpaceConverter = ImageSharp.Colors.Spaces.Conversion.ColorSpaceConverter;
public class ColorspaceCieXyzToRgbConvert
{
private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F);
private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883);
private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter();
private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter();
[Benchmark(Baseline = true, Description = "Colourful Convert")]
public RGBColor ColourfulConvert()
{
return ColourfulConverter.ToRGB(XYZColor);
}
[Benchmark(Description = "ImageSharp Convert")]
public Rgb ColorSpaceConvert()
{
return ColorSpaceConverter.ToRgb(CieXyz);
}
}
}

127
tests/ImageSharp.Tests/Colors/Colorspaces/RgbAndCieXyzConversionTest.cs

@ -0,0 +1,127 @@
namespace ImageSharp.Tests
{
using System.Collections.Generic;
using ImageSharp.Colors.Spaces;
using ImageSharp.Colors.Spaces.Conversion;
using Xunit;
/// <summary>
/// Tests <see cref="CieXyz"/>-<see cref="CieLab"/> conversions.
/// </summary>
/// <remarks>
/// Test data generated using:
/// http://www.brucelindbloom.com/index.html?ColorCalculator.html
/// </remarks>
public class RgbAndCieXyzConversionTest
{
private static readonly IEqualityComparer<float> FloatComparerPrecision = new ApproximateFloatComparer(6);
/// <summary>
/// Tests conversion from <see cref="CieXyz"/> (<see cref="Illuminants.D50"/>)
/// to <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>).
/// </summary>
[Theory]
[InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)]
[InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)]
[InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)]
[InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)]
[InlineData(0, 0, 0, 0, 0, 0)]
[InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)]
public void Convert_XYZ_D50_to_SRGB(float x, float y, float z, float r, float g, float b)
{
// Arrange
CieXyz input = new CieXyz(x, y, z);
ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb };
// Act
Rgb output = converter.ToRgb(input);
// Assert
Assert.Equal(output.WorkingSpace, Rgb.DefaultWorkingSpace);
Assert.Equal(output.R, r, FloatComparerPrecision);
Assert.Equal(output.G, g, FloatComparerPrecision);
Assert.Equal(output.B, b, FloatComparerPrecision);
}
/// <summary>
/// Tests conversion
/// from <see cref="CieXyz"/> (<see cref="Illuminants.D65"/>)
/// to <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>).
/// </summary>
[Theory]
[InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)]
[InlineData(0, 1.000000, 0, 0, 1, 0)]
[InlineData(0.950470, 0, 0, 1, 0, 0.254967)]
[InlineData(0, 0, 1.088830, 0, 0.235458, 1)]
[InlineData(0, 0, 0, 0, 0, 0)]
[InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)]
public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b)
{
// Arrange
CieXyz input = new CieXyz(x, y, z);
ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb };
// Act
Rgb output = converter.ToRgb(input);
// Assert
Assert.Equal(output.WorkingSpace, Rgb.DefaultWorkingSpace);
Assert.Equal(output.R, r, FloatComparerPrecision);
Assert.Equal(output.G, g, FloatComparerPrecision);
Assert.Equal(output.B, b, FloatComparerPrecision);
}
/// <summary>
/// Tests conversion from <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>)
/// to <see cref="CieXyz"/> (<see cref="Illuminants.D50"/>).
/// </summary>
[Theory]
[InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)]
[InlineData(0, 0, 0, 0, 0, 0)]
[InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)]
[InlineData(0, 1, 0, 0.385065, 0.716879, 0.097105)]
[InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)]
[InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)]
public void Convert_SRGB_to_XYZ_D50(float r, float g, float b, float x, float y, float z)
{
// Arrange
Rgb input = new Rgb(r, g, b);
ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50 };
// Act
CieXyz output = converter.ToCieXyz(input);
// Assert
Assert.Equal(output.X, x, FloatComparerPrecision);
Assert.Equal(output.Y, y, FloatComparerPrecision);
Assert.Equal(output.Z, z, FloatComparerPrecision);
}
/// <summary>
/// Tests conversion from <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>)
/// to <see cref="CieXyz"/> (<see cref="Illuminants.D65"/>).
/// </summary>
[Theory]
[InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)]
[InlineData(0, 0, 0, 0, 0, 0)]
[InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)]
[InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)]
[InlineData(0, 0, 1, 0.180437, 0.072175, 0.950304)]
[InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)]
public void Convert_SRGB_to_XYZ_D65(float r, float g, float b, float x, float y, float z)
{
// Arrange
Rgb input = new Rgb(r, g, b);
ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 };
// Act
CieXyz output = converter.ToCieXyz(input);
// Assert
Assert.Equal(output.X, x, FloatComparerPrecision);
Assert.Equal(output.Y, y, FloatComparerPrecision);
Assert.Equal(output.Z, z, FloatComparerPrecision);
}
}
}
Loading…
Cancel
Save