aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2021-07-05 11:26:01 +0300
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2021-07-05 11:26:01 +0300
commit3ac0c04e830c7199039979bff44d96a88cd9cc51 (patch)
tree018c60565e3414d87bc8309b3382d85b6eb5a076
parent22dd2a3dea15628a9242cb585d615b9b2e4e5422 (diff)
downloadTango-3ac0c04e830c7199039979bff44d96a88cd9cc51.tar.gz
Tango-3ac0c04e830c7199039979bff44d96a88cd9cc51.zip
Fixed RGB to LAB conversion using Colorful.
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/HunterLabColor.cs131
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/IColorVector.cs15
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/IRGBWorkingSpace.cs29
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/Illuminants.cs70
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/LChabColor.cs145
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/LChuvColor.cs154
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/LMSColor.cs104
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/LabColor.cs136
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/LinearRGBColor.cs168
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/LuvColor.cs134
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/MacbethColorChecker.cs138
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/RGBColor.cs239
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/RGBWorkingSpaces.cs111
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/XYZColor.cs95
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/xyChromaticityCoordinates.cs62
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colors/xyYColor.cs109
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Colourful.csproj65
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.Adapt.cs121
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToHunterLab.cs136
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLChab.cs134
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLChuv.cs134
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLMS.cs130
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLab.cs143
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLinearRGB.cs136
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLuv.cs143
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToRGB.cs146
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToXYZ.cs187
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToxyY.cs131
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.cs98
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/IChromaticAdaptation.cs13
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Conversion/VonKriesChromaticAdaptation.cs77
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Difference/CIE76ColorDifference.cs27
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Difference/CIE94ColorDifference.cs94
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Difference/CIEDE2000ColorDifference.cs130
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Difference/CMCColorDifference.cs109
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Difference/IColorDifference.cs15
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/CIEConstants.cs8
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/HunterLabToXYZConverter.cs50
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/XYZAndHunterLabConverterBase.cs32
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/XYZToHunterLabConverter.cs57
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/IColorConversion.cs20
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChab/LChabToLabConverter.cs46
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChab/LabToLChabConverter.cs45
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChuv/LChuvToLuvConverter.cs46
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChuv/LuvToLChuvConverter.cs45
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LMS/LMSTransformationMatrix.cs84
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LMS/XYZAndLMSConverter.cs67
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Lab/LabToXYZConverter.cs62
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Lab/XYZToLabConverter.cs84
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Luv/LuvToXYZConverter.cs69
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Luv/XYZToLuvConverter.cs90
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBAndXYZConverterBase.cs70
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBToRGBConverter.cs59
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBToXYZConverter.cs64
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/RGBToLinearRGBConverter.cs60
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/XYZToLinearRGBConverter.cs72
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/xyY/xyYAndXYZConverter.cs45
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/GammaCompanding.cs67
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/ICompanding.cs28
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/LCompanding.cs46
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/RGBPrimariesChromaticityCoordinates.cs61
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/RGBWorkingSpace.cs72
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/Rec2020Companding.cs34
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/Rec709Companding.cs29
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/sRGBCompanding.cs43
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/Angle.cs30
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/Extensions.cs125
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/MathUtils.cs62
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/MatrixFactory.cs46
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Other/CCTConverter.cs63
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Other/SaturationLChFormulas.cs30
-rw-r--r--Software/Visual_Studio/SideChains/Colourful/Properties/AssemblyInfo.cs6
-rw-r--r--Software/Visual_Studio/Tango.BL/Entities/BrushStop.cs6
-rw-r--r--Software/Visual_Studio/Tango.BL/ExtensionMethods/ColorMineExtensions.cs29
-rw-r--r--Software/Visual_Studio/Tango.BL/Tango.BL.csproj7
-rw-r--r--Software/Visual_Studio/Tango.sln35
76 files changed, 5993 insertions, 10 deletions
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/HunterLabColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/HunterLabColor.cs
new file mode 100644
index 000000000..259633805
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/HunterLabColor.cs
@@ -0,0 +1,131 @@
+using System.Globalization;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// Hunter Lab color
+ /// </summary>
+ public readonly struct HunterLabColor : IColorVector
+ {
+ /// <summary>
+ /// C standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ /// </summary>
+ public static readonly XYZColor DefaultWhitePoint = Illuminants.C;
+
+ #region Constructor
+
+ /// <param name="l">L (lightness) (from 0 to 100)</param>
+ /// <param name="a">a (usually from -100 to 100)</param>
+ /// <param name="b">b (usually from -100 to 100)</param>
+ /// <remarks>Uses <see cref="DefaultWhitePoint" /> as white point.</remarks>
+ public HunterLabColor(double l, double a, double b) : this(l, a, b, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="l">L (lightness) (from 0 to 100)</param>
+ /// <param name="a">a (usually from -100 to 100)</param>
+ /// <param name="b">b (usually from -100 to 100)</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public HunterLabColor(double l, double a, double b, XYZColor whitePoint)
+ {
+ L = l;
+ this.a = a;
+ this.b = b;
+ _whitePoint = whitePoint;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <remarks>Uses <see cref="DefaultWhitePoint" /> as white point.</remarks>
+ public HunterLabColor(Vector vector) : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public HunterLabColor(Vector vector, XYZColor whitePoint)
+ : this(vector[0], vector[1], vector[2], whitePoint)
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <summary>
+ /// L (lightness)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 100.
+ /// </remarks>
+ public double L { get; }
+
+ /// <summary>
+ /// a
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -100 to 100.
+ /// Negative values indicate green while positive values indicate magenta.
+ /// </remarks>
+ public double a { get; }
+
+ /// <summary>
+ /// b
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -100 to 100.
+ /// Negative values indicate blue and positive values indicate yellow.
+ /// </remarks>
+ public double b { get; }
+
+ /// <remarks>
+ /// <see cref="Illuminants" />
+ /// </remarks>
+ public XYZColor WhitePoint => _whitePoint ?? DefaultWhitePoint;
+
+ private readonly XYZColor? _whitePoint;
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { L, a, b };
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(HunterLabColor other) => L.Equals(other.L) && a.Equals(other.a) && b.Equals(other.b);
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is HunterLabColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = L.GetHashCode();
+ hashCode = (hashCode * 397) ^ a.GetHashCode();
+ hashCode = (hashCode * 397) ^ b.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(HunterLabColor left, HunterLabColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(HunterLabColor left, HunterLabColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "HunterLab [L={0:0.##}, a={1:0.##}, b={2:0.##}]", L, a, b);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/IColorVector.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/IColorVector.cs
new file mode 100644
index 000000000..2555db9b4
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/IColorVector.cs
@@ -0,0 +1,15 @@
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// Color represented as a vector in its color space
+ /// </summary>
+ public interface IColorVector
+ {
+ /// <summary>
+ /// Vector
+ /// </summary>
+ Vector Vector { get; }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/IRGBWorkingSpace.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/IRGBWorkingSpace.cs
new file mode 100644
index 000000000..b53e2578b
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/IRGBWorkingSpace.cs
@@ -0,0 +1,29 @@
+using Colourful.Implementation.RGB;
+
+namespace Colourful
+{
+ /// <summary>
+ /// RGB working color space
+ /// </summary>
+ public interface IRGBWorkingSpace
+ {
+ /// <summary>
+ /// Reference white of the color space
+ /// </summary>
+ XYZColor WhitePoint { get; }
+
+ /// <summary>
+ /// Chromaticity coordinates of the primaries
+ /// </summary>
+ RGBPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
+
+ /// <summary>
+ /// The companding function associated with the RGB color system.
+ /// Used for conversion to XYZ and backwards.
+ /// See this for more information:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ /// </summary>
+ ICompanding Companding { get; }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/Illuminants.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/Illuminants.cs
new file mode 100644
index 000000000..811bcfcb7
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/Illuminants.cs
@@ -0,0 +1,70 @@
+namespace Colourful
+{
+ /// <summary>
+ /// Standard illuminants
+ /// </summary>
+ /// <remarks>
+ /// Coefficients taken from:
+ /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ /// <br />
+ /// Descriptions taken from:
+ /// http://en.wikipedia.org/wiki/Standard_illuminant
+ /// </remarks>
+ public static class Illuminants
+ {
+ /// <summary>
+ /// Incandescent / Tungsten
+ /// </summary>
+ public static readonly XYZColor A = new XYZColor(1.09850, 1, 0.35585);
+
+ /// <summary>
+ /// Direct sunlight at noon (obsolete)
+ /// </summary>
+ public static readonly XYZColor B = new XYZColor(0.99072, 1, 0.85223);
+
+ /// <summary>
+ /// Average / North sky Daylight (obsolete)
+ /// </summary>
+ public static readonly XYZColor C = new XYZColor(0.98074, 1, 1.18232);
+
+ /// <summary>
+ /// Horizon Light. ICC profile PCS
+ /// </summary>
+ public static readonly XYZColor D50 = new XYZColor(0.96422, 1, 0.82521);
+
+ /// <summary>
+ /// Mid-morning / Mid-afternoon Daylight
+ /// </summary>
+ public static readonly XYZColor D55 = new XYZColor(0.95682, 1, 0.92149);
+
+ /// <summary>
+ /// Noon Daylight: Television, sRGB color space
+ /// </summary>
+ public static readonly XYZColor D65 = new XYZColor(0.95047, 1, 1.08883);
+
+ /// <summary>
+ /// North sky Daylight
+ /// </summary>
+ public static readonly XYZColor D75 = new XYZColor(0.94972, 1, 1.22638);
+
+ /// <summary>
+ /// Equal energy
+ /// </summary>
+ public static readonly XYZColor E = new XYZColor(1, 1, 1);
+
+ /// <summary>
+ /// Cool White Fluorescent
+ /// </summary>
+ public static readonly XYZColor F2 = new XYZColor(0.99186, 1, 0.67393);
+
+ /// <summary>
+ /// D65 simulator, Daylight simulator
+ /// </summary>
+ public static readonly XYZColor F7 = new XYZColor(0.95041, 1, 1.08747);
+
+ /// <summary>
+ /// Philips TL84, Ultralume 40
+ /// </summary>
+ public static readonly XYZColor F11 = new XYZColor(1.00962, 1, 0.64350);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/LChabColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/LChabColor.cs
new file mode 100644
index 000000000..141b9fef3
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/LChabColor.cs
@@ -0,0 +1,145 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// CIE L*C*h°, cylindrical form of <see cref="LabColor">CIE L*a*b* (1976)</see>
+ /// </summary>
+ public readonly struct LChabColor : IColorVector
+ {
+ /// <summary>
+ /// D50 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ /// </summary>
+ public static readonly XYZColor DefaultWhitePoint = Illuminants.D50;
+
+ #region Constructor
+
+ /// <param name="l">L* (lightness) (from 0 to 100)</param>
+ /// <param name="c">C* (chroma) (from 0 to 100)</param>
+ /// <param name="h">h° (hue in degrees) (from 0 to 360)</param>
+ public LChabColor(double l, double c, double h) : this(l, c, h, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="l">L* (lightness) (from 0 to 100)</param>
+ /// <param name="c">C* (chroma) (from 0 to 100)</param>
+ /// <param name="h">h° (hue in degrees) (from 0 to 360)</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public LChabColor(double l, double c, double h, XYZColor whitePoint)
+ {
+ L = l;
+ C = c;
+ this.h = h;
+ _whitePoint = whitePoint;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <remarks>Uses <see cref="DefaultWhitePoint" /> as white point.</remarks>
+ public LChabColor(Vector vector) : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public LChabColor(Vector vector, XYZColor whitePoint)
+ : this(vector[0], vector[1], vector[2], whitePoint)
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <summary>
+ /// L* (lightness)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 100.
+ /// </remarks>
+ public double L { get; }
+
+ /// <summary>
+ /// C* (chroma)
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from 0 to 100.
+ /// </remarks>
+ public double C { get; }
+
+ /// <summary>
+ /// h° (hue in degrees)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 360.
+ /// </remarks>
+ public double h { get; }
+
+ /// <remarks>
+ /// <see cref="Illuminants" />
+ /// </remarks>
+ public XYZColor WhitePoint => _whitePoint ?? DefaultWhitePoint;
+
+ private readonly XYZColor? _whitePoint;
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { L, C, h };
+
+ #endregion
+
+ #region Saturation
+
+ /// <summary>
+ /// Computes saturation of the color (chroma normalized by lightness)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 100.
+ /// </remarks>
+ public double Saturation => SaturationLChFormulas.GetSaturation(L, C);
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(LChabColor other) =>
+ L == other.L &&
+ C == other.C &&
+ h == other.h;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LChabColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = L.GetHashCode();
+ hashCode = (hashCode * 397) ^ C.GetHashCode();
+ hashCode = (hashCode * 397) ^ h.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LChabColor left, LChabColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LChabColor left, LChabColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "LChab [L={0:0.##}, C={1:0.##}, h={2:0.##}]", L, C, h);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/LChuvColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/LChuvColor.cs
new file mode 100644
index 000000000..dd5823ef4
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/LChuvColor.cs
@@ -0,0 +1,154 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// CIE L*C*h°, cylindrical form of <see cref="LuvColor">CIE L*u*v* (1976)</see>
+ /// </summary>
+ public readonly struct LChuvColor : IColorVector
+ {
+ /// <summary>
+ /// D65 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ /// </summary>
+ public static readonly XYZColor DefaultWhitePoint = Illuminants.D65;
+
+ #region Constructor
+
+ /// <param name="l">L* (lightness) (from 0 to 100)</param>
+ /// <param name="c">C* (chroma) (from 0 to 100)</param>
+ /// <param name="h">h° (hue in degrees) (from 0 to 360)</param>
+ public LChuvColor(double l, double c, double h) : this(l, c, h, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="l">L* (lightness) (from 0 to 100)</param>
+ /// <param name="c">C* (chroma) (from 0 to 100)</param>
+ /// <param name="h">h° (hue in degrees) (from 0 to 360)</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public LChuvColor(double l, double c, double h, XYZColor whitePoint)
+ {
+ L = l;
+ C = c;
+ this.h = h;
+ _whitePoint = whitePoint;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <remarks>Uses <see cref="DefaultWhitePoint" /> as white point.</remarks>
+ public LChuvColor(Vector vector) : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public LChuvColor(Vector vector, XYZColor whitePoint)
+ : this(vector[0], vector[1], vector[2], whitePoint)
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <summary>
+ /// L* (lightness)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 100.
+ /// </remarks>
+ public double L { get; }
+
+ /// <summary>
+ /// C* (chroma)
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from 0 to 100.
+ /// </remarks>
+ public double C { get; }
+
+ /// <summary>
+ /// h° (hue in degrees)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 360.
+ /// </remarks>
+ public double h { get; }
+
+ /// <remarks>
+ /// <see cref="Illuminants" />
+ /// </remarks>
+ public XYZColor WhitePoint => _whitePoint ?? DefaultWhitePoint;
+
+ private readonly XYZColor? _whitePoint;
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { L, C, h };
+
+ #endregion
+
+ #region Saturation
+
+ /// <summary>
+ /// Computes saturation of the color (chroma normalized by lightness)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 100.
+ /// </remarks>
+ public double Saturation => SaturationLChFormulas.GetSaturation(L, C);
+
+ /// <summary>
+ /// Constructs the color using saturation instead of chromas
+ /// </summary>
+ public static LChuvColor FromSaturation(double lightness, double hue, double saturation)
+ {
+ var chroma = SaturationLChFormulas.GetChroma(saturation, lightness);
+ return new LChuvColor(lightness, chroma, hue);
+ }
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(LChuvColor other) =>
+ L == other.L &&
+ C == other.C &&
+ h == other.h;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LChuvColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = L.GetHashCode();
+ hashCode = (hashCode * 397) ^ C.GetHashCode();
+ hashCode = (hashCode * 397) ^ h.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LChuvColor left, LChuvColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LChuvColor left, LChuvColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "LChuv [L={0:0.##}, C={1:0.##}, h={2:0.##}]", L, C, h);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/LMSColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/LMSColor.cs
new file mode 100644
index 000000000..a13b7651c
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/LMSColor.cs
@@ -0,0 +1,104 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// LMS color space represented by the response of the three types of cones of the human eye
+ /// </summary>
+ public readonly struct LMSColor : IColorVector
+ {
+ #region Constructor
+
+ /// <param name="l">L (usually from -1 to 1)</param>
+ /// <param name="m">M (usually from -1 to 1)</param>
+ /// <param name="s">S (usually from -1 to 1)</param>
+ public LMSColor(double l, double m, double s)
+ {
+ L = l;
+ M = m;
+ S = s;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions (usually from 0 to 1)</param>
+ public LMSColor(Vector vector)
+ : this(vector[0], vector[1], vector[2])
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <summary>
+ /// Long wavelengths (red) cone response (Rho)
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -1 to 1.
+ /// </remarks>
+ public double L { get; }
+
+ /// <summary>
+ /// Medium wavelengths (green) cone response (Gamma)
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -1 to 1.
+ /// </remarks>
+ public double M { get; }
+
+ /// <summary>
+ /// Short wavelengths (blue) cone response (Beta)
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -1 to 1.
+ /// </remarks>
+ public double S { get; }
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { L, M, S };
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(LMSColor other) =>
+ L == other.L &&
+ M == other.M &&
+ S == other.S;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LMSColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = L.GetHashCode();
+ hashCode = (hashCode * 397) ^ M.GetHashCode();
+ hashCode = (hashCode * 397) ^ S.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LMSColor left, LMSColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LMSColor left, LMSColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "LMS [L={0:0.##}, M={1:0.##}, S={2:0.##}]", L, M, S);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/LabColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/LabColor.cs
new file mode 100644
index 000000000..638f0763d
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/LabColor.cs
@@ -0,0 +1,136 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// CIE L*a*b* (1976) color
+ /// </summary>
+ public readonly struct LabColor : IColorVector
+ {
+ /// <summary>
+ /// D50 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ /// </summary>
+ public static readonly XYZColor DefaultWhitePoint = Illuminants.D50;
+
+ #region Constructor
+
+ /// <param name="l">L* (lightness) (from 0 to 100)</param>
+ /// <param name="a">a* (usually from -100 to 100)</param>
+ /// <param name="b">b* (usually from -100 to 100)</param>
+ /// <remarks>Uses <see cref="DefaultWhitePoint" /> as white point.</remarks>
+ public LabColor(double l, double a, double b) : this(l, a, b, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="l">L* (lightness) (from 0 to 100)</param>
+ /// <param name="a">a* (usually from -100 to 100)</param>
+ /// <param name="b">b* (usually from -100 to 100)</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public LabColor(double l, double a, double b, XYZColor whitePoint)
+ {
+ L = l;
+ this.a = a;
+ this.b = b;
+ _whitePoint = whitePoint;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <remarks>Uses <see cref="DefaultWhitePoint" /> as white point.</remarks>
+ public LabColor(Vector vector) : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public LabColor(Vector vector, XYZColor whitePoint)
+ : this(vector[0], vector[1], vector[2], whitePoint)
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <summary>
+ /// L* (lightness)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 100.
+ /// </remarks>
+ public double L { get; }
+
+ /// <summary>
+ /// a*
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -100 to 100.
+ /// Negative values indicate green while positive values indicate magenta.
+ /// </remarks>
+ public double a { get; }
+
+ /// <summary>
+ /// b*
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -100 to 100.
+ /// Negative values indicate blue and positive values indicate yellow.
+ /// </remarks>
+ public double b { get; }
+
+ /// <remarks>
+ /// <see cref="Illuminants" />
+ /// </remarks>
+ public XYZColor WhitePoint => _whitePoint ?? DefaultWhitePoint;
+
+ private readonly XYZColor? _whitePoint;
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { L, a, b };
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(LabColor other) =>
+ L == other.L &&
+ a == other.a &&
+ b == other.b;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LabColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = L.GetHashCode();
+ hashCode = (hashCode * 397) ^ a.GetHashCode();
+ hashCode = (hashCode * 397) ^ b.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LabColor left, LabColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LabColor left, LabColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "Lab [L={0:0.##}, a={1:0.##}, b={2:0.##}]", L, a, b);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/LinearRGBColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/LinearRGBColor.cs
new file mode 100644
index 000000000..bc85cb5f5
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/LinearRGBColor.cs
@@ -0,0 +1,168 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Colourful.Implementation;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// RGB color with specified <see cref="IRGBWorkingSpace">working space</see>, which has linear channels (not companded)
+ /// </summary>
+ public readonly struct LinearRGBColor : IColorVector
+ {
+ #region Other
+
+ /// <summary>
+ /// sRGB color space.
+ /// Used when working space is not specified explicitly.
+ /// </summary>
+ public static readonly IRGBWorkingSpace DefaultWorkingSpace = RGBWorkingSpaces.sRGB;
+
+ #endregion
+
+ #region Constructor
+
+ /// <param name="r">Red (from 0 to 1)</param>
+ /// <param name="g">Green (from 0 to 1)</param>
+ /// <param name="b">Blue (from 0 to 1)</param>
+ /// <remarks>Uses <see cref="DefaultWorkingSpace" /> as working space.</remarks>
+ public LinearRGBColor(double r, double g, double b)
+ : this(r, g, b, DefaultWorkingSpace)
+ {
+ }
+
+ /// <param name="r">Red (from 0 to 1)</param>
+ /// <param name="g">Green (from 0 to 1)</param>
+ /// <param name="b">Blue (from 0 to 1)</param>
+ /// <param name="workingSpace">
+ /// <see cref="RGBWorkingSpaces" />
+ /// </param>
+ public LinearRGBColor(double r, double g, double b, IRGBWorkingSpace workingSpace)
+ {
+ R = r.CheckRange(0, 1);
+ G = g.CheckRange(0, 1);
+ B = b.CheckRange(0, 1);
+ _workingSpace = workingSpace;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions (range from 0 to 1)</param>
+ /// <remarks>Uses <see cref="DefaultWorkingSpace" /> as working space.</remarks>
+ public LinearRGBColor(Vector vector)
+ : this(vector, DefaultWorkingSpace)
+ {
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions (range from 0 to 1)</param>
+ /// <param name="workingSpace">
+ /// <see cref="RGBWorkingSpaces" />
+ /// </param>
+ public LinearRGBColor(Vector vector, IRGBWorkingSpace workingSpace)
+ : this(vector[0], vector[1], vector[2], workingSpace)
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <summary>
+ /// Red
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 1.
+ /// </remarks>
+ public double R { get; }
+
+ /// <summary>
+ /// Green
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 1.
+ /// </remarks>
+ public double G { get; }
+
+ /// <summary>
+ /// Blue
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 1.
+ /// </remarks>
+ public double B { get; }
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { R, G, B };
+
+ #endregion
+
+ #region Attributes
+
+ /// <summary>
+ /// RGB color space
+ /// <seealso cref="RGBWorkingSpaces" />
+ /// </summary>
+ public IRGBWorkingSpace WorkingSpace => _workingSpace ?? DefaultWorkingSpace;
+
+ private readonly IRGBWorkingSpace _workingSpace;
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(LinearRGBColor other) =>
+ R == other.R &&
+ G == other.G &&
+ B == other.B &&
+ WorkingSpace.Equals(other.WorkingSpace);
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LinearRGBColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (base.GetHashCode() * 397) ^ WorkingSpace.GetHashCode();
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LinearRGBColor left, LinearRGBColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LinearRGBColor left, LinearRGBColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Factory methods
+
+ /// <summary>
+ /// Creates RGB color with all channels equal
+ /// </summary>
+ /// <param name="value">Grey value (from 0 to 1)</param>
+ /// <param name="workingSpace">
+ /// <see cref="RGBWorkingSpaces" />
+ /// </param>
+ public static LinearRGBColor FromGrey(double value, IRGBWorkingSpace workingSpace) => new LinearRGBColor(value, value, value, workingSpace);
+
+ /// <summary>
+ /// Creates RGB color with all channels equal
+ /// </summary>
+ /// <param name="value">Grey value (from 0 to 1)</param>
+ /// <remarks>Uses <see cref="DefaultWorkingSpace" /> as working space.</remarks>
+ public static LinearRGBColor FromGrey(double value) => FromGrey(value, DefaultWorkingSpace);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "LinearRGB [R={0:0.##}, G={1:0.##}, B={2:0.##}]", R, G, B);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/LuvColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/LuvColor.cs
new file mode 100644
index 000000000..b32128032
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/LuvColor.cs
@@ -0,0 +1,134 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// CIE L*u*v* (1976) color
+ /// </summary>
+ public readonly struct LuvColor : IColorVector
+ {
+ /// <summary>
+ /// D65 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ /// </summary>
+ public static readonly XYZColor DefaultWhitePoint = Illuminants.D65;
+
+ #region Constructor
+
+ /// <param name="l">L* (lightness) (from 0 to 100)</param>
+ /// <param name="u">u* (usually from -100 to 100)</param>
+ /// <param name="v">v* (usually from -100 to 100)</param>
+ /// <remarks>Uses <see cref="DefaultWhitePoint" /> as white point.</remarks>
+ public LuvColor(double l, double u, double v) : this(l, u, v, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="l">L* (lightness) (from 0 to 100)</param>
+ /// <param name="u">u* (usually from -100 to 100)</param>
+ /// <param name="v">v* (usually from -100 to 100)</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public LuvColor(double l, double u, double v, XYZColor whitePoint)
+ {
+ L = l;
+ this.u = u;
+ this.v = v;
+ _whitePoint = whitePoint;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <remarks>Uses <see cref="DefaultWhitePoint" /> as white point.</remarks>
+ public LuvColor(Vector vector) : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions</param>
+ /// <param name="whitePoint">Reference white (see <see cref="Illuminants" />)</param>
+ public LuvColor(Vector vector, XYZColor whitePoint)
+ : this(vector[0], vector[1], vector[2], whitePoint)
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <summary>
+ /// L* (lightness)
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 100.
+ /// </remarks>
+ public double L { get; }
+
+ /// <summary>
+ /// u*
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -100 to 100.
+ /// </remarks>
+ public double u { get; }
+
+ /// <summary>
+ /// v*
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from -100 to 100.
+ /// </remarks>
+ public double v { get; }
+
+ /// <remarks>
+ /// <see cref="Illuminants" />
+ /// </remarks>
+ public XYZColor WhitePoint => _whitePoint ?? DefaultWhitePoint;
+
+ private readonly XYZColor? _whitePoint;
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { L, u, v };
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(LuvColor other) =>
+ L == other.L &&
+ u == other.u &&
+ v == other.v;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LuvColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = L.GetHashCode();
+ hashCode = (hashCode * 397) ^ u.GetHashCode();
+ hashCode = (hashCode * 397) ^ v.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LuvColor left, LuvColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LuvColor left, LuvColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "Luv [L={0:0.##}, u={1:0.##}, v={2:0.##}]", L, u, v);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/MacbethColorChecker.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/MacbethColorChecker.cs
new file mode 100644
index 000000000..d2e2ab316
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/MacbethColorChecker.cs
@@ -0,0 +1,138 @@
+using ColorList = System.Collections.Generic.IReadOnlyList<Colourful.RGBColor>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// Colors of the Macbeth ColorChecker
+ /// </summary>
+ /// <remarks>
+ /// Values obtained from: http://xritephoto.com/documents/literature/en/ColorData-1p_EN.pdf
+ /// </remarks>
+ public static class MacbethColorChecker
+ {
+ /// <summary>
+ /// Dark skin (color #1)
+ /// </summary>
+ public static readonly RGBColor DarkSkin = RGBColor.FromRGB8bit(115, 82, 68, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Light skin (color #2)
+ /// </summary>
+ public static readonly RGBColor LightSkin = RGBColor.FromRGB8bit(194, 150, 130, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Blue sky (color #3)
+ /// </summary>
+ public static readonly RGBColor BlueSky = RGBColor.FromRGB8bit(98, 122, 157, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Foliage (color #4)
+ /// </summary>
+ public static readonly RGBColor Foliage = RGBColor.FromRGB8bit(87, 108, 67, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Blue flower (color #5)
+ /// </summary>
+ public static readonly RGBColor BlueFlower = RGBColor.FromRGB8bit(133, 128, 177, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Bluish green (color #6)
+ /// </summary>
+ public static readonly RGBColor BluishGreen = RGBColor.FromRGB8bit(103, 189, 170, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Orange (color #7)
+ /// </summary>
+ public static readonly RGBColor Orange = RGBColor.FromRGB8bit(214, 126, 44, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Purplish blue (color #8)
+ /// </summary>
+ public static readonly RGBColor PurplishBlue = RGBColor.FromRGB8bit(80, 91, 166, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Moderate red (color #9)
+ /// </summary>
+ public static readonly RGBColor ModerateRed = RGBColor.FromRGB8bit(193, 90, 99, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Purple (color #10)
+ /// </summary>
+ public static readonly RGBColor Purple = RGBColor.FromRGB8bit(94, 60, 108, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Yellow green (color #11)
+ /// </summary>
+ public static readonly RGBColor YellowGreen = RGBColor.FromRGB8bit(157, 188, 64, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Orange Yellow (color #12)
+ /// </summary>
+ public static readonly RGBColor OrangeYellow = RGBColor.FromRGB8bit(224, 163, 46, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Blue (color #13)
+ /// </summary>
+ public static readonly RGBColor Blue = RGBColor.FromRGB8bit(56, 61, 150, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Green (color #14)
+ /// </summary>
+ public static readonly RGBColor Green = RGBColor.FromRGB8bit(70, 148, 73, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Red (color #15)
+ /// </summary>
+ public static readonly RGBColor Red = RGBColor.FromRGB8bit(175, 54, 60, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Yellow (color #16)
+ /// </summary>
+ public static readonly RGBColor Yellow = RGBColor.FromRGB8bit(231, 199, 31, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Magenta (color #17)
+ /// </summary>
+ public static readonly RGBColor Magenta = RGBColor.FromRGB8bit(187, 86, 149, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Cyan (color #18)
+ /// </summary>
+ public static readonly RGBColor Cyan = RGBColor.FromRGB8bit(8, 133, 161, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// White (color #19)
+ /// </summary>
+ public static readonly RGBColor White = RGBColor.FromRGB8bit(243, 243, 242, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Neutral 8 (color #20)
+ /// </summary>
+ public static readonly RGBColor Neutral8 = RGBColor.FromRGB8bit(200, 200, 200, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Neutral 6.5 (color #21)
+ /// </summary>
+ public static readonly RGBColor Neutral6p5 = RGBColor.FromRGB8bit(160, 160, 160, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Neutral 5 (color #22)
+ /// </summary>
+ public static readonly RGBColor Neutral5 = RGBColor.FromRGB8bit(122, 122, 121, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Neutral 3.5 (color #23)
+ /// </summary>
+ public static readonly RGBColor Neutral3p5 = RGBColor.FromRGB8bit(85, 85, 85, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Black (color #24)
+ /// </summary>
+ public static readonly RGBColor Black = RGBColor.FromRGB8bit(52, 52, 52, RGBWorkingSpaces.sRGB);
+
+ /// <summary>
+ /// Array of 24 colors of the Macbeth ColorChecker
+ /// </summary>
+ public static readonly ColorList Colors = new[] { DarkSkin, LightSkin, BlueSky, Foliage, BlueFlower, BluishGreen, Orange, PurplishBlue, ModerateRed, Purple, YellowGreen, OrangeYellow, Blue, Green, Red, Yellow, Magenta, Cyan, White, Neutral8, Neutral6p5, Neutral5, Neutral3p5, Black };
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/RGBColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/RGBColor.cs
new file mode 100644
index 000000000..cf392cf32
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/RGBColor.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Colourful.Implementation;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+#if (DRAWING)
+using System.Drawing;
+
+#endif
+
+namespace Colourful
+{
+ /// <summary>
+ /// RGB color with specified <see cref="IRGBWorkingSpace">working space</see>
+ /// </summary>
+ public readonly struct RGBColor : IColorVector, IEquatable<RGBColor>
+ {
+ #region Other
+
+ /// <summary>
+ /// sRGB color space.
+ /// Used when working space is not specified explicitly.
+ /// </summary>
+ public static readonly IRGBWorkingSpace DefaultWorkingSpace = RGBWorkingSpaces.sRGB;
+
+ #endregion
+
+ #region Constructor
+
+ /// <param name="r">Red (from 0 to 1)</param>
+ /// <param name="g">Green (from 0 to 1)</param>
+ /// <param name="b">Blue (from 0 to 1)</param>
+ /// <remarks>Uses <see cref="DefaultWorkingSpace" /> as working space.</remarks>
+ public RGBColor(double r, double g, double b)
+ : this(r, g, b, DefaultWorkingSpace)
+ {
+ }
+
+ /// <param name="r">Red (from 0 to 1)</param>
+ /// <param name="g">Green (from 0 to 1)</param>
+ /// <param name="b">Blue (from 0 to 1)</param>
+ /// <param name="workingSpace">
+ /// <see cref="RGBWorkingSpaces" />
+ /// </param>
+ public RGBColor(double r, double g, double b, IRGBWorkingSpace workingSpace)
+ {
+ R = r.CheckRange(0, 1);
+ G = g.CheckRange(0, 1);
+ B = b.CheckRange(0, 1);
+ _workingSpace = workingSpace;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions (range from 0 to 1)</param>
+ /// <remarks>Uses <see cref="DefaultWorkingSpace" /> as working space.</remarks>
+ public RGBColor(Vector vector)
+ : this(vector, DefaultWorkingSpace)
+ {
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions (range from 0 to 1)</param>
+ /// <param name="workingSpace">
+ /// <see cref="RGBWorkingSpaces" />
+ /// </param>
+ public RGBColor(Vector vector, IRGBWorkingSpace workingSpace)
+ : this(vector[0], vector[1], vector[2], workingSpace)
+ {
+ }
+
+#if (DRAWING)
+/// <remarks>Uses <see cref="DefaultWorkingSpace" /> as working space.</remarks>
+ public RGBColor(Color color)
+ : this(color, DefaultWorkingSpace)
+ {
+ }
+
+ /// <param name="color"></param>
+ /// <param name="workingSpace">
+ /// <see cref="RGBWorkingSpaces" />
+ /// </param>
+ public RGBColor(Color color, IRGBWorkingSpace workingSpace)
+ : this((double)color.R / 255, (double)color.G / 255, (double)color.B / 255, workingSpace)
+ {
+ }
+
+#endif
+
+ #endregion
+
+ #region Channels
+
+ /// <summary>
+ /// Red
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 1.
+ /// </remarks>
+ public double R { get; }
+
+ /// <summary>
+ /// Green
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 1.
+ /// </remarks>
+ public double G { get; }
+
+ /// <summary>
+ /// Blue
+ /// </summary>
+ /// <remarks>
+ /// Ranges from 0 to 1.
+ /// </remarks>
+ public double B { get; }
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { R, G, B };
+
+ #endregion
+
+ #region Attributes
+
+ /// <summary>
+ /// RGB color space
+ /// <seealso cref="RGBWorkingSpaces" />
+ /// </summary>
+ public IRGBWorkingSpace WorkingSpace => _workingSpace ?? DefaultWorkingSpace;
+
+ private readonly IRGBWorkingSpace _workingSpace;
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(RGBColor other) =>
+ R == other.R &&
+ G == other.G &&
+ B == other.B &&
+ WorkingSpace.Equals(other.WorkingSpace);
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is RGBColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (base.GetHashCode() * 397) ^ WorkingSpace.GetHashCode();
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(RGBColor left, RGBColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(RGBColor left, RGBColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Factory methods
+
+ /// <summary>
+ /// Creates RGB color with all channels equal
+ /// </summary>
+ /// <param name="value">Grey value (from 0 to 1)</param>
+ /// <param name="workingSpace">
+ /// <see cref="RGBWorkingSpaces" />
+ /// </param>
+ public static RGBColor FromGrey(double value, IRGBWorkingSpace workingSpace) => new RGBColor(value, value, value, workingSpace);
+
+ /// <summary>
+ /// Creates RGB color with all channels equal
+ /// </summary>
+ /// <param name="value">Grey value (from 0 to 1)</param>
+ /// <remarks>Uses <see cref="DefaultWorkingSpace" /> as working space.</remarks>
+ public static RGBColor FromGrey(double value) => FromGrey(value, DefaultWorkingSpace);
+
+ /// <summary>
+ /// Creates RGB color from 8-bit channels
+ /// </summary>
+ /// <param name="red"></param>
+ /// <param name="green"></param>
+ /// <param name="blue"></param>
+ /// <param name="workingSpace">
+ /// <see cref="RGBWorkingSpaces" />
+ /// </param>
+ public static RGBColor FromRGB8bit(byte red, byte green, byte blue, IRGBWorkingSpace workingSpace) => new RGBColor(red / 255d, green / 255d, blue / 255d, workingSpace);
+
+
+ /// <summary>
+ /// Creates RGB color from 8-bit channels
+ /// </summary>
+ /// <remarks>Uses <see cref="DefaultWorkingSpace" /> as working space.</remarks>
+ public static RGBColor FromRGB8bit(byte red, byte green, byte blue) => FromRGB8bit(red, green, blue, DefaultWorkingSpace);
+
+ #endregion
+
+#if (DRAWING)
+ #region Color conversions
+
+ /// <summary>
+ /// Convert to <see cref="System.Drawing.Color" />.
+ /// </summary>
+ public Color ToColor() => this;
+
+ /// <summary>
+ /// Convert to <see cref="System.Drawing.Color" />.
+ /// </summary>
+ public static implicit operator Color(RGBColor input)
+ {
+ var r = (byte)Math.Round(input.R * 255).CropRange(0, 255);
+ var g = (byte)Math.Round(input.G * 255).CropRange(0, 255);
+ var b = (byte)Math.Round(input.B * 255).CropRange(0, 255);
+ var output = Color.FromArgb(r, g, b);
+ return output;
+ }
+
+ /// <summary>
+ /// Convert from <see cref="System.Drawing.Color" />.
+ /// </summary>
+ public static explicit operator RGBColor(Color color) => new RGBColor(color);
+
+ #endregion
+
+#endif
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "RGB [R={0:0.##}, G={1:0.##}, B={2:0.##}]", R, G, B);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/RGBWorkingSpaces.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/RGBWorkingSpaces.cs
new file mode 100644
index 000000000..fe20d20a0
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/RGBWorkingSpaces.cs
@@ -0,0 +1,111 @@
+using Colourful.Implementation.RGB;
+
+namespace Colourful
+{
+ /// <remarks>
+ /// Chromaticity coordinates taken from:
+ /// http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html
+ /// </remarks>
+ public static class RGBWorkingSpaces
+ {
+ /// <summary>
+ /// sRGB
+ /// </summary>
+ /// <remarks>
+ /// Uses proper companding function, according to:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ /// </remarks>
+ public static readonly RGBWorkingSpace sRGB = new RGBWorkingSpace(Illuminants.D65, new sRGBCompanding(), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6400, 0.3300), new xyChromaticityCoordinates(0.3000, 0.6000), new xyChromaticityCoordinates(0.1500, 0.0600)));
+
+ /// <summary>
+ /// Simplified sRGB (uses <see cref="GammaCompanding">gamma companding</see> instead of <see cref="sRGBCompanding" />).
+ /// See also <see cref="sRGB" />.
+ /// </summary>
+ public static readonly RGBWorkingSpace sRGBSimplified = new RGBWorkingSpace(Illuminants.D65, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6400, 0.3300), new xyChromaticityCoordinates(0.3000, 0.6000), new xyChromaticityCoordinates(0.1500, 0.0600)));
+
+ /// <summary>
+ /// Rec. 709 (ITU-R Recommendation BT.709)
+ /// </summary>
+ public static readonly RGBWorkingSpace Rec709 = new RGBWorkingSpace(Illuminants.D65, new Rec709Companding(), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.64, 0.33), new xyChromaticityCoordinates(0.30, 0.60), new xyChromaticityCoordinates(0.15, 0.06)));
+
+ /// <summary>
+ /// Rec. 2020 (ITU-R Recommendation BT.2020)
+ /// </summary>
+ public static readonly RGBWorkingSpace Rec2020 = new RGBWorkingSpace(Illuminants.D65, new Rec2020Companding(), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.708, 0.292), new xyChromaticityCoordinates(0.170, 0.797), new xyChromaticityCoordinates(0.131, 0.046)));
+
+ /// <summary>
+ /// ECI RGB v2
+ /// </summary>
+ public static readonly RGBWorkingSpace ECIRGBv2 = new RGBWorkingSpace(Illuminants.D50, new LCompanding(), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6700, 0.3300), new xyChromaticityCoordinates(0.2100, 0.7100), new xyChromaticityCoordinates(0.1400, 0.0800)));
+
+ /// <summary>
+ /// Adobe RGB (1998)
+ /// </summary>
+ public static readonly RGBWorkingSpace AdobeRGB1998 = new RGBWorkingSpace(Illuminants.D65, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6400, 0.3300), new xyChromaticityCoordinates(0.2100, 0.7100), new xyChromaticityCoordinates(0.1500, 0.0600)));
+
+ /// <summary>
+ /// Apple sRGB
+ /// </summary>
+ public static readonly RGBWorkingSpace ApplesRGB = new RGBWorkingSpace(Illuminants.D65, new GammaCompanding(1.8), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6250, 0.3400), new xyChromaticityCoordinates(0.2800, 0.5950), new xyChromaticityCoordinates(0.1550, 0.0700)));
+
+ /// <summary>
+ /// Best RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace BestRGB = new RGBWorkingSpace(Illuminants.D50, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.7347, 0.2653), new xyChromaticityCoordinates(0.2150, 0.7750), new xyChromaticityCoordinates(0.1300, 0.0350)));
+
+ /// <summary>
+ /// Beta RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace BetaRGB = new RGBWorkingSpace(Illuminants.D50, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6888, 0.3112), new xyChromaticityCoordinates(0.1986, 0.7551), new xyChromaticityCoordinates(0.1265, 0.0352)));
+
+ /// <summary>
+ /// Bruce RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace BruceRGB = new RGBWorkingSpace(Illuminants.D65, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6400, 0.3300), new xyChromaticityCoordinates(0.2800, 0.6500), new xyChromaticityCoordinates(0.1500, 0.0600)));
+
+ /// <summary>
+ /// CIE RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace CIERGB = new RGBWorkingSpace(Illuminants.E, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.7350, 0.2650), new xyChromaticityCoordinates(0.2740, 0.7170), new xyChromaticityCoordinates(0.1670, 0.0090)));
+
+ /// <summary>
+ /// ColorMatch RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace ColorMatchRGB = new RGBWorkingSpace(Illuminants.D50, new GammaCompanding(1.8), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6300, 0.3400), new xyChromaticityCoordinates(0.2950, 0.6050), new xyChromaticityCoordinates(0.1500, 0.0750)));
+
+ /// <summary>
+ /// Don RGB 4
+ /// </summary>
+ public static readonly RGBWorkingSpace DonRGB4 = new RGBWorkingSpace(Illuminants.D50, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6960, 0.3000), new xyChromaticityCoordinates(0.2150, 0.7650), new xyChromaticityCoordinates(0.1300, 0.0350)));
+
+ /// <summary>
+ /// Ekta Space PS5
+ /// </summary>
+ public static readonly RGBWorkingSpace EktaSpacePS5 = new RGBWorkingSpace(Illuminants.D50, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6950, 0.3050), new xyChromaticityCoordinates(0.2600, 0.7000), new xyChromaticityCoordinates(0.1100, 0.0050)));
+
+ /// <summary>
+ /// NTSC RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace NTSCRGB = new RGBWorkingSpace(Illuminants.C, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6700, 0.3300), new xyChromaticityCoordinates(0.2100, 0.7100), new xyChromaticityCoordinates(0.1400, 0.0800)));
+
+ /// <summary>
+ /// PAL/SECAM RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace PALSECAMRGB = new RGBWorkingSpace(Illuminants.D65, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6400, 0.3300), new xyChromaticityCoordinates(0.2900, 0.6000), new xyChromaticityCoordinates(0.1500, 0.0600)));
+
+ /// <summary>
+ /// ProPhoto RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace ProPhotoRGB = new RGBWorkingSpace(Illuminants.D50, new GammaCompanding(1.8), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.7347, 0.2653), new xyChromaticityCoordinates(0.1596, 0.8404), new xyChromaticityCoordinates(0.0366, 0.0001)));
+
+ /// <summary>
+ /// SMPTE-C RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace SMPTECRGB = new RGBWorkingSpace(Illuminants.D65, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.6300, 0.3400), new xyChromaticityCoordinates(0.3100, 0.5950), new xyChromaticityCoordinates(0.1550, 0.0700)));
+
+ /// <summary>
+ /// Wide Gamut RGB
+ /// </summary>
+ public static readonly RGBWorkingSpace WideGamutRGB = new RGBWorkingSpace(Illuminants.D50, new GammaCompanding(2.2), new RGBPrimariesChromaticityCoordinates(new xyChromaticityCoordinates(0.7350, 0.2650), new xyChromaticityCoordinates(0.1150, 0.8260), new xyChromaticityCoordinates(0.1570, 0.0180)));
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/XYZColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/XYZColor.cs
new file mode 100644
index 000000000..ac893804e
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/XYZColor.cs
@@ -0,0 +1,95 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// CIE 1931 XYZ color space
+ /// </summary>
+ public readonly struct XYZColor : IColorVector
+ {
+ #region Constructor
+
+ /// <param name="x">X (usually from 0 to 1)</param>
+ /// <param name="y">Y (usually from 0 to 1)</param>
+ /// <param name="z">Z (usually from 0 to 1)</param>
+ public XYZColor(double x, double y, double z)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions (usually from 0 to 1)</param>
+ public XYZColor(Vector vector)
+ : this(vector[0], vector[1], vector[2])
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <remarks>
+ /// Ranges usually from 0 to 1.
+ /// </remarks>
+ public double X { get; }
+
+ /// <remarks>
+ /// Ranges usually from 0 to 1.
+ /// </remarks>
+ public double Y { get; }
+
+ /// <remarks>
+ /// Ranges usually from 0 to 1.
+ /// </remarks>
+ public double Z { get; }
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { X, Y, Z };
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(XYZColor other) =>
+ X == other.X &&
+ Y == other.Y &&
+ Z == other.Z;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is XYZColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = X.GetHashCode();
+ hashCode = (hashCode * 397) ^ Y.GetHashCode();
+ hashCode = (hashCode * 397) ^ Z.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(XYZColor left, XYZColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(XYZColor left, XYZColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "XYZ [X={0:0.##}, Y={1:0.##}, Z={2:0.##}]", X, Y, Z);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/xyChromaticityCoordinates.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/xyChromaticityCoordinates.cs
new file mode 100644
index 000000000..aedfd1aa2
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/xyChromaticityCoordinates.cs
@@ -0,0 +1,62 @@
+using System.Globalization;
+
+namespace Colourful
+{
+ /// <summary>
+ /// Coordinates of CIE xy chromaticity space
+ /// </summary>
+ public readonly struct xyChromaticityCoordinates
+ {
+ /// <param name="x">Chromaticity coordinate x (usually from 0 to 1)</param>
+ /// <param name="y">Chromaticity coordinate y (usually from 0 to 1)</param>
+ public xyChromaticityCoordinates(double x, double y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+
+ /// <summary>
+ /// Chromaticity coordinate x
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from 0 to 1.
+ /// </remarks>
+ public double x { get; }
+
+ /// <summary>
+ /// Chromaticity coordinate y
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from 0 to 1.
+ /// </remarks>
+ public double y { get; }
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(xyChromaticityCoordinates other) => x.Equals(other.x) && y.Equals(other.y);
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is xyChromaticityCoordinates coordinates && Equals(coordinates);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (x.GetHashCode() * 397) ^ y.GetHashCode();
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(xyChromaticityCoordinates left, xyChromaticityCoordinates right) => left.Equals(right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(xyChromaticityCoordinates left, xyChromaticityCoordinates right) => !left.Equals(right);
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "xy [x={0:0.##}, y={1:0.##}]", x, y);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colors/xyYColor.cs b/Software/Visual_Studio/SideChains/Colourful/Colors/xyYColor.cs
new file mode 100644
index 000000000..fe73163dd
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colors/xyYColor.cs
@@ -0,0 +1,109 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful
+{
+ /// <summary>
+ /// CIE xyY color space (derived from <see cref="XYZColor" /> color space)
+ /// </summary>
+ public readonly struct xyYColor : IColorVector
+ {
+ #region Constructor
+
+ /// <param name="x">x (usually from 0 to 1) chromaticity coordinate</param>
+ /// <param name="y">y (usually from 0 to 1) chromaticity coordinate</param>
+ /// <param name="Y">Y (usually from 0 to 1)</param>
+ public xyYColor(double x, double y, double Y)
+ : this(new xyChromaticityCoordinates(x, y), Y)
+ {
+ }
+
+ /// <param name="chromaticity">Chromaticity coordinates (x and y together)</param>
+ /// <param name="Y">Y (usually from 0 to 1)</param>
+ public xyYColor(xyChromaticityCoordinates chromaticity, double Y)
+ {
+ Chromaticity = chromaticity;
+ Luminance = Y;
+ }
+
+ /// <param name="vector"><see cref="Vector" />, expected 3 dimensions (usually from 0 to 1)</param>
+ public xyYColor(Vector vector)
+ : this(vector[0], vector[1], vector[2])
+ {
+ }
+
+ #endregion
+
+ #region Channels
+
+ /// <remarks>
+ /// Ranges usually from 0 to 1.
+ /// </remarks>
+ public double x => Chromaticity.x;
+
+ /// <remarks>
+ /// Ranges usually from 0 to 1.
+ /// </remarks>
+ public double y => Chromaticity.y;
+
+ /// <summary>
+ /// Y channel (luminance)
+ /// </summary>
+ /// <remarks>
+ /// Ranges usually from 0 to 1.
+ /// </remarks>
+ public double Luminance { get; }
+
+ /// <remarks>
+ /// Chromaticity coordinates (identical to x and y)
+ /// </remarks>
+ public xyChromaticityCoordinates Chromaticity { get; }
+
+ /// <summary>
+ /// <see cref="IColorVector" />
+ /// </summary>
+ public Vector Vector => new[] { x, y, Luminance };
+
+ #endregion
+
+ #region Equality
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(xyYColor other) =>
+ x == other.x &&
+ y == other.y &&
+ Luminance == other.Luminance;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is xyYColor other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = x.GetHashCode();
+ hashCode = (hashCode * 397) ^ y.GetHashCode();
+ hashCode = (hashCode * 397) ^ Luminance.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(xyYColor left, xyYColor right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(xyYColor left, xyYColor right) => !Equals(left, right);
+
+ #endregion
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override string ToString() => string.Format(CultureInfo.InvariantCulture, "xyY [x={0:0.##}, y={1:0.##}, Y={2:0.##}]", x, y, Luminance);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Colourful.csproj b/Software/Visual_Studio/SideChains/Colourful/Colourful.csproj
new file mode 100644
index 000000000..113934620
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Colourful.csproj
@@ -0,0 +1,65 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup Label="Basic build">
+ <TargetFrameworks>net45;netstandard1.1;netstandard2.0;</TargetFrameworks>
+ <LangVersion>7.3</LangVersion>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+
+ <PropertyGroup Label="NuGet package">
+ <Authors>Tomáš Pažourek</Authors>
+ <Company>$(Authors)</Company>
+ <Copyright>$(Copyright)</Copyright>
+ <PackageLicenseExpression>MIT</PackageLicenseExpression>
+ <RepositoryUrl>https://github.com/tompazourek/Colourful</RepositoryUrl>
+ <RepositoryType>git</RepositoryType>
+ <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
+ <PackageReleaseNotes>$(RepositoryUrl)/releases</PackageReleaseNotes>
+ <PackageIcon>logo_64.png</PackageIcon>
+ <PackageTags>adobe-rgb bradford c-sharp cct chromatic-adaptation chromaticity cie-lab cie-luv cie-xyy cie-xyz color-difference color-space conversion delta-e lab lms luv rgb srgb xyz</PackageTags>
+ <Description>Open source .NET library for working with color spaces.</Description>
+ </PropertyGroup>
+
+ <PropertyGroup Label="Symbols, docs">
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <PublishRepositoryUrl>true</PublishRepositoryUrl>
+ <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
+ <EmbedUntrackedSources>true</EmbedUntrackedSources>
+ </PropertyGroup>
+
+ <PropertyGroup Label="CI build only" Condition=" '$(CI)' == 'true' ">
+ <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(TargetFramework)' == 'net45' ">
+ <DefineConstants>$(DefineConstants);DRAWING</DefineConstants>
+ </PropertyGroup>
+
+ <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
+ <Reference Include="System.Drawing" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="MinVer" Version="2.2.0">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Remove="*.DotSettings" />
+ <None Include="..\..\assets\logo_64.png" Pack="true" PackagePath="" />
+ </ItemGroup>
+
+ <Target Name="UpdateAppVeyorBuildVersion" AfterTargets="MinVer" Condition=" '$(APPVEYOR)' == 'true' ">
+ <Exec Command="appveyor UpdateBuild -Version &quot;$(MinVerVersion)&quot;" />
+ </Target>
+</Project>
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.Adapt.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.Adapt.cs
new file mode 100644
index 000000000..7ad705970
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.Adapt.cs
@@ -0,0 +1,121 @@
+using System;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Performs chromatic adaptation of given XYZ color.
+ /// Target white point is <see cref="WhitePoint" />.
+ /// </summary>
+ public XYZColor Adapt(in XYZColor color, in XYZColor sourceWhitePoint)
+ {
+ if (!IsChromaticAdaptationPerformed)
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide chromatic adaptation method and white point.");
+
+ var result = ChromaticAdaptation.Transform(color, sourceWhitePoint, WhitePoint);
+ return result;
+ }
+
+ /// <summary>
+ /// Adapts linear RGB color from the source working space to working space set in <see cref="TargetRGBWorkingSpace" />.
+ /// </summary>
+ public LinearRGBColor Adapt(in LinearRGBColor color)
+ {
+ if (!IsChromaticAdaptationPerformed)
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide chromatic adaptation method and white point.");
+
+ if (color.WorkingSpace.Equals(TargetRGBWorkingSpace))
+ return color;
+
+ // conversion to XYZ
+ var converterToXYZ = GetLinearRGBToXYZConverter(color.WorkingSpace);
+ var unadapted = converterToXYZ.Convert(color);
+
+ // adaptation
+ var adapted = ChromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, TargetRGBWorkingSpace.WhitePoint);
+
+ // conversion back to RGB
+ var converterToRGB = GetXYZToLinearRGBConverter(TargetRGBWorkingSpace);
+ var result = converterToRGB.Convert(adapted);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Adapts RGB color from the source working space to working space set in <see cref="TargetRGBWorkingSpace" />.
+ /// </summary>
+ public RGBColor Adapt(in RGBColor color)
+ {
+ var linearInput = ToLinearRGB(color);
+ var linearOutput = Adapt(linearInput);
+ var compandedOutput = ToRGB(linearOutput);
+
+ return compandedOutput;
+ }
+
+ /// <summary>
+ /// Adapts Lab color from the source white point to white point set in <see cref="TargetLabWhitePoint" />.
+ /// </summary>
+ public LabColor Adapt(in LabColor color)
+ {
+ if (!IsChromaticAdaptationPerformed)
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide chromatic adaptation method and white point.");
+
+ if (color.WhitePoint.Equals(TargetLabWhitePoint))
+ return color;
+
+ var xyzColor = ToXYZ(color);
+ var result = ToLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Adapts LChab color from the source white point to white point set in <see cref="TargetLabWhitePoint" />.
+ /// </summary>
+ public LChabColor Adapt(in LChabColor color)
+ {
+ if (!IsChromaticAdaptationPerformed)
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide chromatic adaptation method and white point.");
+
+ if (color.WhitePoint.Equals(TargetLabWhitePoint))
+ return color;
+
+ var labColor = ToLab(color);
+ var result = ToLChab(labColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Adapts Lab color from the source white point to white point set in <see cref="TargetHunterLabWhitePoint" />.
+ /// </summary>
+ public HunterLabColor Adapt(in HunterLabColor color)
+ {
+ if (!IsChromaticAdaptationPerformed)
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide chromatic adaptation method and white point.");
+
+ if (color.WhitePoint.Equals(TargetHunterLabWhitePoint))
+ return color;
+
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Adapts Luv color from the source white point to white point set in <see cref="TargetLuvWhitePoint" />.
+ /// </summary>
+ public LuvColor Adapt(in LuvColor color)
+ {
+ if (!IsChromaticAdaptationPerformed)
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide chromatic adaptation method and white point.");
+
+ if (color.WhitePoint.Equals(TargetLuvWhitePoint))
+ return color;
+
+ var xyzColor = ToXYZ(color);
+ var result = ToLuv(xyzColor);
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToHunterLab.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToHunterLab.cs
new file mode 100644
index 000000000..963ba4b40
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToHunterLab.cs
@@ -0,0 +1,136 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in RGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in LinearRGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in XYZColor color)
+ {
+ // adaptation
+ var adapted = !WhitePoint.Equals(TargetHunterLabWhitePoint) && IsChromaticAdaptationPerformed
+ ? ChromaticAdaptation.Transform(color, WhitePoint, TargetHunterLabWhitePoint)
+ : color;
+
+ // conversion
+ var converter = new XYZToHunterLabConverter(TargetHunterLabWhitePoint);
+ var result = converter.Convert(adapted);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in xyYColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in LabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in LChabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in LuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in LChuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab(in LMSColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToHunterLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to Hunter Lab color
+ /// </summary>
+ public HunterLabColor ToHunterLab<T>(T color) where T : struct, IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToHunterLab(in typedColor);
+ case LinearRGBColor typedColor:
+ return ToHunterLab(in typedColor);
+ case XYZColor typedColor:
+ return ToHunterLab(in typedColor);
+ case xyYColor typedColor:
+ return ToHunterLab(in typedColor);
+ case HunterLabColor typedColor:
+ return typedColor;
+ case LabColor typedColor:
+ return ToHunterLab(in typedColor);
+ case LChabColor typedColor:
+ return ToHunterLab(in typedColor);
+ case LuvColor typedColor:
+ return ToHunterLab(in typedColor);
+ case LChuvColor typedColor:
+ return ToHunterLab(in typedColor);
+ case LMSColor typedColor:
+ return ToHunterLab(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLChab.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLChab.cs
new file mode 100644
index 000000000..7529a0fba
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLChab.cs
@@ -0,0 +1,134 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in RGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in LinearRGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in XYZColor color)
+ {
+ var labColor = ToLab(color);
+ var result = ToLChab(labColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in xyYColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in LabColor color)
+ {
+ // adaptation to target lab white point (LabWhitePoint)
+ var adapted = IsChromaticAdaptationPerformed ? Adapt(color) : color;
+
+ // conversion (preserving white point)
+ var converter = LabToLChabConverter.Default;
+ var result = converter.Convert(adapted);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in HunterLabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in LuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in LChuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab(in LMSColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Lab) color
+ /// </summary>
+ public LChabColor ToLChab<T>(T color) where T : struct, IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToLChab(in typedColor);
+ case LinearRGBColor typedColor:
+ return ToLChab(in typedColor);
+ case XYZColor typedColor:
+ return ToLChab(in typedColor);
+ case xyYColor typedColor:
+ return ToLChab(in typedColor);
+ case HunterLabColor typedColor:
+ return ToLChab(in typedColor);
+ case LabColor typedColor:
+ return ToLChab(in typedColor);
+ case LChabColor typedColor:
+ return typedColor;
+ case LuvColor typedColor:
+ return ToLChab(in typedColor);
+ case LChuvColor typedColor:
+ return ToLChab(in typedColor);
+ case LMSColor typedColor:
+ return ToLChab(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLChuv.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLChuv.cs
new file mode 100644
index 000000000..7503e4478
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLChuv.cs
@@ -0,0 +1,134 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in RGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in LinearRGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in XYZColor color)
+ {
+ var luvColor = ToLuv(color);
+ var result = ToLChuv(luvColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in xyYColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in LabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in LChabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in HunterLabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in LuvColor color)
+ {
+ // adaptation to target luv white point (LuvWhitePoint)
+ var adapted = IsChromaticAdaptationPerformed ? Adapt(color) : color;
+
+ // conversion (preserving white point)
+ var converter = LuvToLChuvConverter.Default;
+ var result = converter.Convert(adapted);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv(in LMSColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLChuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*C*h° (Luv) color
+ /// </summary>
+ public LChuvColor ToLChuv<T>(T color) where T : IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToLChuv(in typedColor);
+ case LinearRGBColor typedColor:
+ return ToLChuv(in typedColor);
+ case XYZColor typedColor:
+ return ToLChuv(in typedColor);
+ case xyYColor typedColor:
+ return ToLChuv(in typedColor);
+ case HunterLabColor typedColor:
+ return ToLChuv(in typedColor);
+ case LabColor typedColor:
+ return ToLChuv(in typedColor);
+ case LChabColor typedColor:
+ return ToLChuv(in typedColor);
+ case LuvColor typedColor:
+ return ToLChuv(in typedColor);
+ case LChuvColor typedColor:
+ return typedColor;
+ case LMSColor typedColor:
+ return ToLChuv(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLMS.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLMS.cs
new file mode 100644
index 000000000..579c2c39b
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLMS.cs
@@ -0,0 +1,130 @@
+using System;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in RGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLMS(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in LinearRGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLMS(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in XYZColor color)
+ {
+ // conversion
+ var converter = _cachedXYZAndLMSConverter;
+ var result = converter.Convert(color);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in xyYColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLMS(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in LabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLMS(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in LChabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLMS(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in HunterLabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLMS(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in LuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLMS(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS(in LChuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLMS(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to LMS color
+ /// </summary>
+ public LMSColor ToLMS<T>(T color) where T : IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToLMS(in typedColor);
+ case LinearRGBColor typedColor:
+ return ToLMS(in typedColor);
+ case XYZColor typedColor:
+ return ToLMS(in typedColor);
+ case xyYColor typedColor:
+ return ToLMS(in typedColor);
+ case HunterLabColor typedColor:
+ return ToLMS(in typedColor);
+ case LabColor typedColor:
+ return ToLMS(in typedColor);
+ case LChabColor typedColor:
+ return ToLMS(in typedColor);
+ case LuvColor typedColor:
+ return ToLMS(in typedColor);
+ case LChuvColor typedColor:
+ return ToLMS(in typedColor);
+ case LMSColor typedColor:
+ return typedColor;
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLab.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLab.cs
new file mode 100644
index 000000000..5a1b367f3
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLab.cs
@@ -0,0 +1,143 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in RGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in LinearRGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in XYZColor color)
+ {
+ // adaptation
+ var adapted = !WhitePoint.Equals(TargetLabWhitePoint) && IsChromaticAdaptationPerformed
+ ? ChromaticAdaptation.Transform(color, WhitePoint, TargetLabWhitePoint)
+ : color;
+
+ // conversion
+ var converter = new XYZToLabConverter(TargetLabWhitePoint);
+ var result = converter.Convert(adapted);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in xyYColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in LChabColor color)
+ {
+ // conversion (preserving white point)
+ var converter = LChabToLabConverter.Default;
+ var unadapted = converter.Convert(color);
+
+ if (!IsChromaticAdaptationPerformed)
+ return unadapted;
+
+ // adaptation to target lab white point (LabWhitePoint)
+ var adapted = Adapt(unadapted);
+ return adapted;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in HunterLabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in LuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in LChuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab(in LMSColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLab(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*a*b* (1976) color
+ /// </summary>
+ public LabColor ToLab<T>(T color) where T : struct, IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToLab(in typedColor);
+ case LinearRGBColor typedColor:
+ return ToLab(in typedColor);
+ case XYZColor typedColor:
+ return ToLab(in typedColor);
+ case xyYColor typedColor:
+ return ToLab(in typedColor);
+ case HunterLabColor typedColor:
+ return ToLab(in typedColor);
+ case LabColor typedColor:
+ return typedColor;
+ case LChabColor typedColor:
+ return ToLab(in typedColor);
+ case LuvColor typedColor:
+ return ToLab(in typedColor);
+ case LChuvColor typedColor:
+ return ToLab(in typedColor);
+ case LMSColor typedColor:
+ return ToLab(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLinearRGB.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLinearRGB.cs
new file mode 100644
index 000000000..2c6a8a821
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLinearRGB.cs
@@ -0,0 +1,136 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in RGBColor color)
+ {
+ var converter = RGBToLinearRGBConverter.Default;
+
+ return converter.Convert(color);
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in XYZColor color)
+ {
+ // adaptation
+ var adapted = TargetRGBWorkingSpace.WhitePoint.Equals(WhitePoint) || !IsChromaticAdaptationPerformed
+ ? color
+ : ChromaticAdaptation.Transform(color, WhitePoint, TargetRGBWorkingSpace.WhitePoint);
+
+ // conversion to linear RGB
+ var xyzConverter = GetXYZToLinearRGBConverter(TargetRGBWorkingSpace);
+ var result = xyzConverter.Convert(adapted);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in xyYColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLinearRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in LabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLinearRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in LChabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLinearRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in HunterLabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLinearRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in LuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLinearRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in LChuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLinearRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB(in LMSColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLinearRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to linear RGB
+ /// </summary>
+ public LinearRGBColor ToLinearRGB<T>(T color) where T : IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToLinearRGB(in typedColor);
+ case LinearRGBColor typedColor:
+ return typedColor;
+ case XYZColor typedColor:
+ return ToLinearRGB(in typedColor);
+ case xyYColor typedColor:
+ return ToLinearRGB(in typedColor);
+ case HunterLabColor typedColor:
+ return ToLinearRGB(in typedColor);
+ case LabColor typedColor:
+ return ToLinearRGB(in typedColor);
+ case LChabColor typedColor:
+ return ToLinearRGB(in typedColor);
+ case LuvColor typedColor:
+ return ToLinearRGB(in typedColor);
+ case LChuvColor typedColor:
+ return ToLinearRGB(in typedColor);
+ case LMSColor typedColor:
+ return ToLinearRGB(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLuv.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLuv.cs
new file mode 100644
index 000000000..a6fd90f64
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToLuv.cs
@@ -0,0 +1,143 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in RGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in LinearRGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in XYZColor color)
+ {
+ // adaptation
+ var adapted = !WhitePoint.Equals(TargetLuvWhitePoint) && IsChromaticAdaptationPerformed
+ ? ChromaticAdaptation.Transform(color, WhitePoint, TargetLuvWhitePoint)
+ : color;
+
+ // conversion
+ var converter = new XYZToLuvConverter(TargetLuvWhitePoint);
+ var result = converter.Convert(adapted);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in xyYColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in LabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in LChabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in HunterLabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in LChuvColor color)
+ {
+ // conversion (preserving white point)
+ var converter = LChuvToLuvConverter.Default;
+ var unadapted = converter.Convert(color);
+
+ if (!IsChromaticAdaptationPerformed)
+ return unadapted;
+
+ // adaptation to target luv white point (LuvWhitePoint)
+ var adapted = Adapt(unadapted);
+ return adapted;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv(in LMSColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToLuv(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE L*u*v* (1976) color
+ /// </summary>
+ public LuvColor ToLuv<T>(T color) where T : IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToLuv(in typedColor);
+ case LinearRGBColor typedColor:
+ return ToLuv(in typedColor);
+ case XYZColor typedColor:
+ return ToLuv(in typedColor);
+ case xyYColor typedColor:
+ return ToLuv(in typedColor);
+ case HunterLabColor typedColor:
+ return ToLuv(in typedColor);
+ case LabColor typedColor:
+ return ToLuv(in typedColor);
+ case LChabColor typedColor:
+ return ToLuv(in typedColor);
+ case LuvColor typedColor:
+ return typedColor;
+ case LChuvColor typedColor:
+ return ToLuv(in typedColor);
+ case LMSColor typedColor:
+ return ToLuv(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToRGB.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToRGB.cs
new file mode 100644
index 000000000..a899e46a0
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToRGB.cs
@@ -0,0 +1,146 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ private XYZToLinearRGBConverter _lastXYZToLinearRGBConverter;
+
+ private XYZToLinearRGBConverter GetXYZToLinearRGBConverter(IRGBWorkingSpace workingSpace)
+ {
+ if (_lastXYZToLinearRGBConverter != null &&
+ _lastXYZToLinearRGBConverter.TargetRGBWorkingSpace.Equals(workingSpace))
+ return _lastXYZToLinearRGBConverter;
+
+ return _lastXYZToLinearRGBConverter = new XYZToLinearRGBConverter(workingSpace);
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in LinearRGBColor color)
+ {
+ // conversion
+ var converter = LinearRGBToRGBConverter.Default;
+
+ var result = converter.Convert(color);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in XYZColor color)
+ {
+ // conversion
+ var linear = ToLinearRGB(color);
+
+ // companding to RGB
+ var result = ToRGB(linear);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in xyYColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in LabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in LChabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in HunterLabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in LuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in LChuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB(in LMSColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToRGB(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to RGB color
+ /// </summary>
+ public RGBColor ToRGB<T>(T color) where T : IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return typedColor;
+ case LinearRGBColor typedColor:
+ return ToRGB(in typedColor);
+ case XYZColor typedColor:
+ return ToRGB(in typedColor);
+ case xyYColor typedColor:
+ return ToRGB(in typedColor);
+ case HunterLabColor typedColor:
+ return ToRGB(in typedColor);
+ case LabColor typedColor:
+ return ToRGB(in typedColor);
+ case LChabColor typedColor:
+ return ToRGB(in typedColor);
+ case LuvColor typedColor:
+ return ToRGB(in typedColor);
+ case LChuvColor typedColor:
+ return ToRGB(in typedColor);
+ case LMSColor typedColor:
+ return ToRGB(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToXYZ.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToXYZ.cs
new file mode 100644
index 000000000..eb2af21cf
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToXYZ.cs
@@ -0,0 +1,187 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ private LinearRGBToXYZConverter _lastLinearRGBToXYZConverter;
+
+ private LinearRGBToXYZConverter GetLinearRGBToXYZConverter(IRGBWorkingSpace workingSpace)
+ {
+ if (_lastLinearRGBToXYZConverter != null &&
+ _lastLinearRGBToXYZConverter.SourceRGBWorkingSpace.Equals(workingSpace))
+ return _lastLinearRGBToXYZConverter;
+
+ return _lastLinearRGBToXYZConverter = new LinearRGBToXYZConverter(workingSpace);
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in RGBColor color)
+ {
+ // uncompanding
+ var rgbConverter = RGBToLinearRGBConverter.Default;
+
+ var linear = rgbConverter.Convert(color);
+
+ // conversion
+ var result = ToXYZ(linear);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in LinearRGBColor color)
+ {
+ // conversion
+ var converterXyz = GetLinearRGBToXYZConverter(color.WorkingSpace);
+ var unadapted = converterXyz.Convert(color);
+
+ // adaptation
+ var adapted = color.WorkingSpace.WhitePoint.Equals(WhitePoint) || !IsChromaticAdaptationPerformed
+ ? unadapted
+ : Adapt(unadapted, color.WorkingSpace.WhitePoint);
+
+ return adapted;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in xyYColor color)
+ {
+ // conversion
+ var converter = xyYAndXYZConverter.Default;
+ var converted = converter.Convert(color);
+ return converted;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in LabColor color)
+ {
+ // conversion
+ var converter = LabToXYZConverter.Default;
+ var unadapted = converter.Convert(color);
+
+ // adaptation
+ var adapted = color.WhitePoint.Equals(WhitePoint) || !IsChromaticAdaptationPerformed
+ ? unadapted
+ : Adapt(unadapted, color.WhitePoint);
+
+ return adapted;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in LChabColor color)
+ {
+ // conversion to Lab
+ var labConverter = LChabToLabConverter.Default;
+
+ var labColor = labConverter.Convert(color);
+
+ // conversion to XYZ (incl. adaptation)
+ var result = ToXYZ(labColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in HunterLabColor color)
+ {
+ // conversion
+ var converter = HunterLabToXYZConverter.Default;
+
+ var unadapted = converter.Convert(color);
+
+ // adaptation
+ var adapted = color.WhitePoint.Equals(WhitePoint) || !IsChromaticAdaptationPerformed
+ ? unadapted
+ : Adapt(unadapted, color.WhitePoint);
+
+ return adapted;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in LuvColor color)
+ {
+ // conversion
+ var converter = LuvToXYZConverter.Default;
+ var unadapted = converter.Convert(color);
+
+ // adaptation
+ var adapted = color.WhitePoint.Equals(WhitePoint) || !IsChromaticAdaptationPerformed
+ ? unadapted
+ : Adapt(unadapted, color.WhitePoint);
+
+ return adapted;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in LChuvColor color)
+ {
+ // conversion to Luv
+ var luvConverter = LChuvToLuvConverter.Default;
+
+ var labColor = luvConverter.Convert(color);
+
+ // conversion to XYZ (incl. adaptation)
+ var result = ToXYZ(labColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ(in LMSColor color)
+ {
+ // conversion
+ var converter = _cachedXYZAndLMSConverter;
+ var converted = converter.Convert(color);
+ return converted;
+ }
+
+ /// <summary>
+ /// Convert to CIE 1931 XYZ color
+ /// </summary>
+ public XYZColor ToXYZ<T>(T color) where T : IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToXYZ(in typedColor);
+ case LinearRGBColor typedColor:
+ return ToXYZ(in typedColor);
+ case XYZColor typedColor:
+ return typedColor;
+ case xyYColor typedColor:
+ return ToXYZ(in typedColor);
+ case HunterLabColor typedColor:
+ return ToXYZ(in typedColor);
+ case LabColor typedColor:
+ return ToXYZ(in typedColor);
+ case LChabColor typedColor:
+ return ToXYZ(in typedColor);
+ case LuvColor typedColor:
+ return ToXYZ(in typedColor);
+ case LChuvColor typedColor:
+ return ToXYZ(in typedColor);
+ case LMSColor typedColor:
+ return ToXYZ(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToxyY.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToxyY.cs
new file mode 100644
index 000000000..823356403
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.ToxyY.cs
@@ -0,0 +1,131 @@
+using System;
+using Colourful.Implementation.Conversion;
+
+namespace Colourful.Conversion
+{
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in RGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToxyY(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in LinearRGBColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToxyY(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in XYZColor color)
+ {
+ // conversion
+ var converter = new xyYAndXYZConverter();
+ var result = converter.Convert(color);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in LabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToxyY(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in LChabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToxyY(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in HunterLabColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToxyY(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in LuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToxyY(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in LChuvColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToxyY(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY(in LMSColor color)
+ {
+ var xyzColor = ToXYZ(color);
+ var result = ToxyY(xyzColor);
+ return result;
+ }
+
+ /// <summary>
+ /// Convert to CIE xyY color
+ /// </summary>
+ public xyYColor ToxyY<T>(T color) where T : IColorVector
+ {
+ switch (color)
+ {
+ case RGBColor typedColor:
+ return ToxyY(in typedColor);
+ case LinearRGBColor typedColor:
+ return ToxyY(in typedColor);
+ case XYZColor typedColor:
+ return ToxyY(in typedColor);
+ case xyYColor typedColor:
+ return typedColor;
+ case HunterLabColor typedColor:
+ return ToxyY(in typedColor);
+ case LabColor typedColor:
+ return ToxyY(in typedColor);
+ case LChabColor typedColor:
+ return ToxyY(in typedColor);
+ case LuvColor typedColor:
+ return ToxyY(in typedColor);
+ case LChuvColor typedColor:
+ return ToxyY(in typedColor);
+ case LMSColor typedColor:
+ return ToxyY(in typedColor);
+ default:
+ throw new ArgumentException($"Cannot accept type '{typeof(T)}'.", nameof(color));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.cs
new file mode 100644
index 000000000..b32b018d1
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/ColourfulConverter.cs
@@ -0,0 +1,98 @@
+using Colourful.Implementation.Conversion;
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+
+namespace Colourful.Conversion
+{
+ /// <summary>
+ /// Converts between color spaces and makes sure that the color is adapted using chromatic adaptation.
+ /// </summary>
+ public partial class ColourfulConverter
+ {
+ /// <summary>
+ /// Constructs the converter and sets the defaults
+ /// </summary>
+ public ColourfulConverter()
+ {
+ WhitePoint = DefaultWhitePoint;
+ LMSTransformationMatrix = XYZAndLMSConverter.DefaultTransformationMatrix;
+ ChromaticAdaptation = new VonKriesChromaticAdaptation(_cachedXYZAndLMSConverter, _cachedXYZAndLMSConverter);
+
+ TargetLabWhitePoint = LabColor.DefaultWhitePoint;
+ TargetHunterLabWhitePoint = HunterLabColor.DefaultWhitePoint;
+ TargetLuvWhitePoint = LuvColor.DefaultWhitePoint;
+ TargetRGBWorkingSpace = RGBColor.DefaultWorkingSpace;
+ }
+
+ private bool IsChromaticAdaptationPerformed => ChromaticAdaptation != null;
+
+ #region Attributes
+
+ /// <summary>
+ /// Default white point
+ /// </summary>
+ public static readonly XYZColor DefaultWhitePoint = Illuminants.D65;
+
+ private Matrix _transformationMatrix;
+
+ /// <summary>
+ /// Chromatic adaptation method used. When null, no adaptation will be performed.
+ /// </summary>
+ public IChromaticAdaptation ChromaticAdaptation { get; set; }
+
+ /// <summary>
+ /// Transformation matrix used in conversion to <see cref="LMSColor" />, also used in the default Von Kries Chromatic Adaptation method.
+ /// </summary>
+ public Matrix LMSTransformationMatrix
+ {
+ get => _transformationMatrix;
+ set
+ {
+ _transformationMatrix = value;
+
+ if (_cachedXYZAndLMSConverter == null)
+ {
+ _cachedXYZAndLMSConverter = new XYZAndLMSConverter(value);
+ }
+ else
+ {
+ _cachedXYZAndLMSConverter.TransformationMatrix = value;
+ }
+ }
+ }
+
+ private XYZAndLMSConverter _cachedXYZAndLMSConverter;
+
+ /// <summary>
+ /// White point used for chromatic adaptation in conversions from/to XYZ color space.
+ /// When null, no adaptation will be performed.
+ /// <seealso cref="TargetLabWhitePoint" />
+ /// </summary>
+ public XYZColor WhitePoint { get; set; }
+
+ /// <summary>
+ /// White point used *when creating* Lab/LChab colors. (Lab/LChab colors on the input already contain the white point information)
+ /// Defaults to: <see cref="LabColor.DefaultWhitePoint" />.
+ /// </summary>
+ public XYZColor TargetLabWhitePoint { get; set; }
+
+ /// <summary>
+ /// White point used *when creating* Luv/LChuv colors. (Luv/LChuv colors on the input already contain the white point information)
+ /// Defaults to: <see cref="LuvColor.DefaultWhitePoint" />.
+ /// </summary>
+ public XYZColor TargetLuvWhitePoint { get; set; }
+
+ /// <summary>
+ /// White point used *when creating* HunterLab colors. (HunterLab colors on the input already contain the white point information)
+ /// Defaults to: <see cref="HunterLabColor.DefaultWhitePoint" />.
+ /// </summary>
+ public XYZColor TargetHunterLabWhitePoint { get; set; }
+
+ /// <summary>
+ /// Working space used *when creating* RGB colors. (RGB colors on the input already contain the working space information)
+ /// Defaults to: <see cref="RGBColor.DefaultWorkingSpace" />.
+ /// </summary>
+ public IRGBWorkingSpace TargetRGBWorkingSpace { get; set; }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/IChromaticAdaptation.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/IChromaticAdaptation.cs
new file mode 100644
index 000000000..084ba07d6
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/IChromaticAdaptation.cs
@@ -0,0 +1,13 @@
+namespace Colourful.Conversion
+{
+ /// <summary>
+ /// Chromatic adaptation.
+ /// A linear transformation of a source color (XS, YS, ZS) into a destination color (XD, YD, ZD) by a linear transformation [M]
+ /// which is dependent on the source reference white (XWS, YWS, ZWS) and the destination reference white (XWD, YWD, ZWD).
+ /// </summary>
+ public interface IChromaticAdaptation
+ {
+ /// <remarks>Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).</remarks>
+ XYZColor Transform(in XYZColor sourceColor, in XYZColor sourceWhitePoint, in XYZColor targetWhitePoint);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Conversion/VonKriesChromaticAdaptation.cs b/Software/Visual_Studio/SideChains/Colourful/Conversion/VonKriesChromaticAdaptation.cs
new file mode 100644
index 000000000..5a6b4999c
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Conversion/VonKriesChromaticAdaptation.cs
@@ -0,0 +1,77 @@
+using System;
+using Colourful.Implementation;
+using Colourful.Implementation.Conversion;
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+
+namespace Colourful.Conversion
+{
+ /// <summary>
+ /// Basic implementation of the von Kries chromatic adaptation model
+ /// </summary>
+ /// <remarks>
+ /// Transformation described here:
+ /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ /// </remarks>
+ public sealed class VonKriesChromaticAdaptation : IChromaticAdaptation
+ {
+ private readonly IColorConversion<XYZColor, LMSColor> _conversionToLMS;
+ private readonly IColorConversion<LMSColor, XYZColor> _conversionToXYZ;
+ private Matrix _cachedDiagonalMatrix;
+
+ private XYZColor _lastSourceWhitePoint;
+ private XYZColor _lastTargetWhitePoint;
+
+ /// <summary>
+ /// Constructs von Kries chromatic adaptation using default <see cref="XYZAndLMSConverter" />
+ /// </summary>
+ public VonKriesChromaticAdaptation() : this(new XYZAndLMSConverter())
+ {
+ }
+
+ /// <summary>
+ /// Transformation matrix used for the conversion (definition of the cone response domain).
+ /// <see cref="LMSTransformationMatrix" />
+ /// </summary>
+ public VonKriesChromaticAdaptation(Matrix transformationMatrix) : this(new XYZAndLMSConverter(transformationMatrix))
+ {
+ }
+
+ private VonKriesChromaticAdaptation(XYZAndLMSConverter converter) : this(converter, converter)
+ {
+ }
+
+ /// <summary>
+ /// Constructs von Kries chromatic adaptation using given converters
+ /// </summary>
+ public VonKriesChromaticAdaptation(IColorConversion<XYZColor, LMSColor> conversionToLMS, IColorConversion<LMSColor, XYZColor> conversionToXYZ)
+ {
+ _conversionToLMS = conversionToLMS ?? throw new ArgumentNullException(nameof(conversionToLMS));
+ _conversionToXYZ = conversionToXYZ ?? throw new ArgumentNullException(nameof(conversionToXYZ));
+ }
+
+ /// <summary>
+ /// Transforms XYZ color to destination reference white.
+ /// </summary>
+ public XYZColor Transform(in XYZColor sourceColor, in XYZColor sourceWhitePoint, in XYZColor targetWhitePoint)
+ {
+ if (sourceWhitePoint.Equals(targetWhitePoint))
+ return sourceColor;
+
+ var sourceColorLMS = _conversionToLMS.Convert(sourceColor);
+
+ if (sourceWhitePoint != _lastSourceWhitePoint || targetWhitePoint != _lastTargetWhitePoint)
+ {
+ var sourceWhitePointLMS = _conversionToLMS.Convert(sourceWhitePoint);
+ var targetWhitePointLMS = _conversionToLMS.Convert(targetWhitePoint);
+
+ _cachedDiagonalMatrix = MatrixFactory.CreateDiagonal(targetWhitePointLMS.L / sourceWhitePointLMS.L, targetWhitePointLMS.M / sourceWhitePointLMS.M, targetWhitePointLMS.S / sourceWhitePointLMS.S);
+ _lastSourceWhitePoint = sourceWhitePoint;
+ _lastTargetWhitePoint = targetWhitePoint;
+ }
+
+ var targetColorLMS = new LMSColor(_cachedDiagonalMatrix.MultiplyBy(sourceColorLMS.Vector));
+ var targetColor = _conversionToXYZ.Convert(targetColorLMS);
+ return targetColor;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Difference/CIE76ColorDifference.cs b/Software/Visual_Studio/SideChains/Colourful/Difference/CIE76ColorDifference.cs
new file mode 100644
index 000000000..5883151c1
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Difference/CIE76ColorDifference.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Colourful.Difference
+{
+ /// <summary>
+ /// CIE Delta-E 1976 formula
+ /// </summary>
+ public sealed class CIE76ColorDifference : IColorDifference<LabColor>
+ {
+ /// <param name="x">Reference color</param>
+ /// <param name="y">Sample color</param>
+ /// <returns>Delta-E (1976) color difference</returns>
+ public double ComputeDifference(in LabColor x, in LabColor y)
+ {
+ if (x.WhitePoint != y.WhitePoint)
+ throw new ArgumentException("Colors must have same white point to be compared.");
+
+ // Euclidean distance
+ var distance = Math.Sqrt(
+ (x.L - y.L) * (x.L - y.L) +
+ (x.a - y.a) * (x.a - y.a) +
+ (x.b - y.b) * (x.b - y.b)
+ );
+ return distance;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Difference/CIE94ColorDifference.cs b/Software/Visual_Studio/SideChains/Colourful/Difference/CIE94ColorDifference.cs
new file mode 100644
index 000000000..d50602f9c
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Difference/CIE94ColorDifference.cs
@@ -0,0 +1,94 @@
+using System;
+using Colourful.Implementation;
+
+namespace Colourful.Difference
+{
+ /// <summary>
+ /// CIE Delta-E 1994 formula
+ /// </summary>
+ /// <remarks>
+ /// Implementation notes:
+ /// http://www.brucelindbloom.com/Eqn_DeltaE_CIE94.html
+ /// </remarks>
+ public sealed class CIE94ColorDifference : IColorDifference<LabColor>
+ {
+ private const double KH = 1;
+ private const double KC = 1;
+
+ private readonly double K1;
+ private readonly double K2;
+ private readonly double KL;
+
+ /// <summary>
+ /// Construct using weighting factors for <see cref="CIE94ColorDifferenceApplication.GraphicArts" />.
+ /// </summary>
+ public CIE94ColorDifference() : this(CIE94ColorDifferenceApplication.GraphicArts)
+ {
+ }
+
+ /// <summary>
+ /// Construct using weighting factors for given application of color difference
+ /// </summary>
+ /// <param name="application">A <see cref="CIE94ColorDifferenceApplication" /> value specifying the application area. Different weighting factors are used in the computation depending on the application.</param>
+ public CIE94ColorDifference(CIE94ColorDifferenceApplication application)
+ {
+ switch (application)
+ {
+ case CIE94ColorDifferenceApplication.GraphicArts:
+ KL = 1;
+ K1 = 0.045;
+ K2 = 0.015;
+ break;
+ case CIE94ColorDifferenceApplication.Textiles:
+ KL = 2;
+ K1 = 0.048;
+ K2 = 0.014;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(application));
+ }
+ }
+
+ /// <param name="x">Reference color</param>
+ /// <param name="y">Sample color</param>
+ /// <returns>Delta-E (1994) color difference</returns>
+ public double ComputeDifference(in LabColor x, in LabColor y)
+ {
+ if (x.WhitePoint != y.WhitePoint)
+ throw new ArgumentException("Colors must have same white point to be compared.");
+
+ var da = x.a - y.a;
+ var db = x.b - y.b;
+ var dL = x.L - y.L;
+ var C1 = Math.Sqrt(x.a * x.a + x.b * x.b);
+ var C2 = Math.Sqrt(y.a * y.a + y.b * y.b);
+ var dC = C1 - C2;
+ var dH_sq = da * da + db * db - dC * dC; // dH ^ 2
+ const double SL = 1;
+ var SC = 1 + K1 * C1;
+ var SH = 1 + K2 * C1;
+ var dE94 = Math.Sqrt(
+ MathUtils.Pow2(dL / (KL * SL)) +
+ MathUtils.Pow2(dC / (KC * SC)) +
+ dH_sq / MathUtils.Pow2(KH * SH)
+ );
+ return dE94;
+ }
+ }
+
+ /// <summary>
+ /// Application area for CIE Delta-E 1994
+ /// </summary>
+ public enum CIE94ColorDifferenceApplication
+ {
+ /// <summary>
+ /// Graphic arts
+ /// </summary>
+ GraphicArts,
+
+ /// <summary>
+ /// Textiles
+ /// </summary>
+ Textiles
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Difference/CIEDE2000ColorDifference.cs b/Software/Visual_Studio/SideChains/Colourful/Difference/CIEDE2000ColorDifference.cs
new file mode 100644
index 000000000..98d0a7b88
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Difference/CIEDE2000ColorDifference.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Colourful.Implementation;
+
+namespace Colourful.Difference
+{
+ /// <summary>
+ /// CIE Delta-E 2000 formula
+ /// </summary>
+ public sealed class CIEDE2000ColorDifference : IColorDifference<LabColor>
+ {
+ // parametric weighting factors:
+ private const double k_H = 1;
+ private const double k_L = 1;
+ private const double k_C = 1;
+
+ /// <param name="x">Reference color</param>
+ /// <param name="y">Sample color</param>
+ /// <remarks>Implemented according to: Sharma, Gaurav; Wencheng Wu, Edul N. Dalal (2005). "The CIEDE2000 color-difference formula: Implementation notes, supplementary test data, and mathematical observations" (http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf)</remarks>
+ /// <returns>Delta-E (2000) color difference</returns>
+ public double ComputeDifference(in LabColor x, in LabColor y)
+ {
+ if (x.WhitePoint != y.WhitePoint)
+ throw new ArgumentException("Colors must have same white point to be compared.");
+
+ // 1. Calculate C_prime, h_prime
+ Calculate_a_prime(x.a, y.a, x.b, y.b, out var a_prime0, out var a_prime1);
+ Calculate_C_prime(a_prime0, a_prime1, x.b, y.b, out var C_prime0, out var C_prime1);
+ Calculate_h_prime(a_prime0, a_prime1, x.b, y.b, out var h_prime0, out var h_prime1);
+
+ // 2. Calculate dL_prime, dC_prime, dH_prime
+ var dL_prime = y.L - x.L; // eq. (8)
+ var dC_prime = C_prime1 - C_prime0; // eq. (9)
+ var dh_prime = Calculate_dh_prime(C_prime0, C_prime1, h_prime0, h_prime1);
+ var dH_prime = 2 * Math.Sqrt(C_prime0 * C_prime1) * MathUtils.SinDeg(dh_prime / 2); // eq. (11)
+
+ // 3. Calculate CIEDE2000 Color-Difference dE00
+ var L_prime_mean = (x.L + y.L) / 2; // eq. (12)
+ var C_prime_mean = (C_prime0 + C_prime1) / 2; // eq. (13)
+ var h_prime_mean = Calculate_h_prime_mean(h_prime0, h_prime1, C_prime0, C_prime1);
+ var T = 1 - 0.17 * MathUtils.CosDeg(h_prime_mean - 30) + 0.24 * MathUtils.CosDeg(2 * h_prime_mean)
+ + 0.32 * MathUtils.CosDeg(3 * h_prime_mean + 6) - 0.20 * MathUtils.CosDeg(4 * h_prime_mean - 63); // eq. (15)
+ var dTheta = 30 * Math.Exp(-MathUtils.Pow2((h_prime_mean - 275) / 25)); // eq. (16)
+ var R_C = 2 * Math.Sqrt(MathUtils.Pow7(C_prime_mean) / (MathUtils.Pow7(C_prime_mean) + MathUtils.Pow7(25))); // eq. (17)
+ var S_L = 1 + 0.015 * MathUtils.Pow2(L_prime_mean - 50) / Math.Sqrt(20 + MathUtils.Pow2(L_prime_mean - 50)); // eq. (18)
+ var S_C = 1 + 0.045 * C_prime_mean; // eq. (19)
+ var S_H = 1 + 0.015 * C_prime_mean * T; // eq. (20)
+ var R_T = -MathUtils.SinDeg(2 * dTheta) * R_C; // eq. (21)
+
+ var dE00 = Math.Sqrt(
+ MathUtils.Pow2(dL_prime / (k_L * S_L)) +
+ MathUtils.Pow2(dC_prime / (k_C * S_C)) +
+ MathUtils.Pow2(dH_prime / (k_H * S_H)) +
+ R_T * (dC_prime / (k_C * S_C)) * (dH_prime / (k_H * S_H))
+ ); // eq. (22)
+
+ return dE00;
+ }
+
+ private static void Calculate_a_prime(double a0, double a1, double b0, double b1, out double a_prime0, out double a_prime1)
+ {
+ var C_ab0 = Math.Sqrt(a0 * a0 + b0 * b0); // eq. (2)
+ var C_ab1 = Math.Sqrt(a1 * a1 + b1 * b1);
+
+ var C_ab_mean = (C_ab0 + C_ab1) / 2; // eq. (3)
+
+ var G = 0.5d * (1 - Math.Sqrt(MathUtils.Pow7(C_ab_mean) / (MathUtils.Pow7(C_ab_mean) + MathUtils.Pow7(25)))); // eq. (4)
+
+ a_prime0 = (1 + G) * a0; // eq. (5)
+ a_prime1 = (1 + G) * a1;
+ }
+
+ private static void Calculate_C_prime(double a_prime0, double a_prime1, double b0, double b1, out double C_prime0, out double C_prime1)
+ {
+ C_prime0 = Math.Sqrt(a_prime0 * a_prime0 + b0 * b0); // eq. (6)
+ C_prime1 = Math.Sqrt(a_prime1 * a_prime1 + b1 * b1);
+ }
+
+ private static void Calculate_h_prime(double a_prime0, double a_prime1, double b0, double b1, out double h_prime0, out double h_prime1)
+ {
+ // eq. (7)
+ var hRadians = Math.Atan2(b0, a_prime0);
+ var hDegrees = Angle.NormalizeDegree(Angle.RadianToDegree(hRadians));
+ h_prime0 = hDegrees;
+
+ hRadians = Math.Atan2(b1, a_prime1);
+ hDegrees = Angle.NormalizeDegree(Angle.RadianToDegree(hRadians));
+ h_prime1 = hDegrees;
+ }
+
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ private static double Calculate_dh_prime(double C_prime0, double C_prime1, double h_prime0, double h_prime1)
+ {
+ // eq. (10)
+ if (C_prime0 * C_prime1 == 0d)
+ return 0;
+
+ if (Math.Abs(h_prime1 - h_prime0) <= 180)
+ return h_prime1 - h_prime0;
+
+ if (h_prime1 - h_prime0 > 180)
+ return h_prime1 - h_prime0 - 360;
+
+ if (h_prime1 - h_prime0 < -180)
+ return h_prime1 - h_prime0 + 360;
+
+ return 0;
+ }
+
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ private static double Calculate_h_prime_mean(double h_prime0, double h_prime1, double C_prime0, double C_prime1)
+ {
+ // eq. (14)
+ if (C_prime0 * C_prime1 == 0d)
+ return h_prime0 + h_prime1;
+
+ if (Math.Abs(h_prime0 - h_prime1) <= 180)
+ return (h_prime0 + h_prime1) / 2;
+
+ if (Math.Abs(h_prime0 - h_prime1) > 180 &&
+ h_prime0 + h_prime1 < 360)
+ return (h_prime0 + h_prime1 + 360) / 2;
+
+ if (Math.Abs(h_prime0 - h_prime1) > 180 && h_prime0 + h_prime1 >= 360)
+ return (h_prime0 + h_prime1 - 360) / 2;
+
+ return h_prime0 + h_prime1;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Difference/CMCColorDifference.cs b/Software/Visual_Studio/SideChains/Colourful/Difference/CMCColorDifference.cs
new file mode 100644
index 000000000..65663955e
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Difference/CMCColorDifference.cs
@@ -0,0 +1,109 @@
+using System;
+using Colourful.Implementation;
+
+namespace Colourful.Difference
+{
+ /// <summary>
+ /// CMC l:c (1984) color difference
+ /// </summary>
+ /// <remarks>
+ /// Equations: http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
+ /// </remarks>
+ public sealed class CMCColorDifference : IColorDifference<LabColor>
+ {
+ /// <summary>
+ /// Chroma
+ /// </summary>
+ private readonly double _c;
+
+ /// <summary>
+ /// Lightness
+ /// </summary>
+ private readonly double _l;
+
+ /// <summary>
+ /// Constructs with given recommended threshold parameters.
+ /// </summary>
+ public CMCColorDifference(CMCColorDifferenceThreshold threshold)
+ {
+ switch (threshold)
+ {
+ case CMCColorDifferenceThreshold.Acceptability:
+ _l = 2;
+ _c = 1;
+ break;
+ case CMCColorDifferenceThreshold.Imperceptibility:
+ _l = 1;
+ _c = 1;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(threshold));
+ }
+ }
+
+ /// <summary>
+ /// Constructs with arbitrary threshold parameters.
+ /// </summary>
+ public CMCColorDifference(double lightness, double chroma)
+ {
+ _l = lightness;
+ _c = chroma;
+ }
+
+ /// <inheritdoc />
+ public double ComputeDifference(in LabColor x, in LabColor y)
+ {
+ double L1 = x.L, a1 = x.a, b1 = x.b;
+ double L2 = y.L, a2 = y.a, b2 = y.b;
+
+ var dL = L1 - L2;
+ var da = a1 - a2;
+ var db = b1 - b2;
+
+ var C1 = Math.Sqrt(a1 * a1 + b1 * b1);
+ var C2 = Math.Sqrt(a2 * a2 + b2 * b2);
+ var dC = C1 - C2;
+
+ var dH_pow2 = da * da + db * db - dC * dC;
+ var H1_rad = Math.Atan2(b1, a1);
+ var H1 = Angle.NormalizeDegree(Angle.RadianToDegree(H1_rad));
+
+ var C1_pow4 = MathUtils.Pow4(C1);
+ var F = Math.Sqrt(C1_pow4 / (C1_pow4 + 1900));
+
+ var T = H1 >= 164 && H1 <= 345
+ ? 0.56 + Math.Abs(0.2 * MathUtils.CosDeg(H1 + 168))
+ : 0.36 + Math.Abs(0.4 * MathUtils.CosDeg(H1 + 35));
+
+ var SC = 0.0638 * C1 / (1 + 0.0131 * C1) + 0.638;
+ var SL = L1 < 16
+ ? 0.511
+ : 0.040975 * L1 / (1 + 0.01765 * L1);
+
+ var SH = SC * (F * T + 1 - F);
+
+ var dE_1 = dL / (_l * SL);
+ var dE_2 = dC / (_c * SC);
+ var dE_3_pow2 = dH_pow2 / (SH * SH);
+
+ var dE = Math.Sqrt(dE_1 * dE_1 + dE_2 * dE_2 + dE_3_pow2);
+ return dE;
+ }
+ }
+
+ /// <summary>
+ /// Weighting parameters for CMC l:c
+ /// </summary>
+ public enum CMCColorDifferenceThreshold
+ {
+ /// <summary>
+ /// 2:1 (l:c)
+ /// </summary>
+ Acceptability,
+
+ /// <summary>
+ /// 1:1 (l:c)
+ /// </summary>
+ Imperceptibility
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Difference/IColorDifference.cs b/Software/Visual_Studio/SideChains/Colourful/Difference/IColorDifference.cs
new file mode 100644
index 000000000..69b7837f8
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Difference/IColorDifference.cs
@@ -0,0 +1,15 @@
+namespace Colourful.Difference
+{
+ /// <summary>
+ /// Computes distance between two vectors in color space
+ /// </summary>
+ /// <typeparam name="TColor"></typeparam>
+ public interface IColorDifference<TColor>
+ where TColor : struct
+ {
+ /// <summary>
+ /// Computes distance between color x and y.
+ /// </summary>
+ double ComputeDifference(in TColor x, in TColor y);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/CIEConstants.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/CIEConstants.cs
new file mode 100644
index 000000000..e6ebb3666
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/CIEConstants.cs
@@ -0,0 +1,8 @@
+namespace Colourful.Implementation.Conversion
+{
+ internal static class CIEConstants
+ {
+ public const double Epsilon = 216d / 24389d;
+ public const double Kappa = 24389d / 27d;
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/HunterLabToXYZConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/HunterLabToXYZConverter.cs
new file mode 100644
index 000000000..238141b6b
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/HunterLabToXYZConverter.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="HunterLabColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public sealed class HunterLabToXYZConverter : XYZAndHunterLabConverterBase, IColorConversion<HunterLabColor, XYZColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly HunterLabToXYZConverter Default = new HunterLabToXYZConverter();
+
+ /// <summary>
+ /// Converts from <see cref="HunterLabColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public XYZColor Convert(in HunterLabColor input)
+ {
+ double L = input.L, a = input.a, b = input.b;
+ double Xn = input.WhitePoint.X, Yn = input.WhitePoint.Y, Zn = input.WhitePoint.Z;
+
+ var Ka = ComputeKa(input.WhitePoint);
+ var Kb = ComputeKb(input.WhitePoint);
+
+ var Y = MathUtils.Pow2(L / 100d) * Yn;
+ var X = (a / Ka * Math.Sqrt(Y / Yn) + Y / Yn) * Xn;
+ var Z = (b / Kb * Math.Sqrt(Y / Yn) - Y / Yn) * -Zn;
+
+ var result = new XYZColor(X, Y, Z);
+ return result;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is HunterLabToXYZConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(HunterLabToXYZConverter left, HunterLabToXYZConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(HunterLabToXYZConverter left, HunterLabToXYZConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/XYZAndHunterLabConverterBase.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/XYZAndHunterLabConverterBase.cs
new file mode 100644
index 000000000..2193ff75d
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/XYZAndHunterLabConverterBase.cs
@@ -0,0 +1,32 @@
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Base class for converters between XYZ and Hunter Lab
+ /// </summary>
+ public abstract class XYZAndHunterLabConverterBase
+ {
+ /// <summary>
+ /// Computes the Ka parameter
+ /// </summary>
+ protected static double ComputeKa(XYZColor whitePoint)
+ {
+ if (whitePoint == Illuminants.C)
+ return 175;
+
+ var Ka = 100 * (175 / 198.04) * (whitePoint.X + whitePoint.Y);
+ return Ka;
+ }
+
+ /// <summary>
+ /// Computes the Kb parameter
+ /// </summary>
+ protected static double ComputeKb(XYZColor whitePoint)
+ {
+ if (whitePoint == Illuminants.C)
+ return 70;
+
+ var Ka = 100 * (70 / 218.11) * (whitePoint.Y + whitePoint.Z);
+ return Ka;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/XYZToHunterLabConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/XYZToHunterLabConverter.cs
new file mode 100644
index 000000000..d69899672
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/HunterLab/XYZToHunterLabConverter.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="HunterLabColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public sealed class XYZToHunterLabConverter : XYZAndHunterLabConverterBase, IColorConversion<XYZColor, HunterLabColor>
+ {
+ /// <summary>
+ /// Construct with <see cref="HunterLabColor.DefaultWhitePoint" />
+ /// </summary>
+ public XYZToHunterLabConverter()
+ : this(HunterLabColor.DefaultWhitePoint)
+ {
+ }
+
+ /// <summary>
+ /// Construct with arbitrary white point
+ /// </summary>
+ public XYZToHunterLabConverter(XYZColor labWhitePoint)
+ {
+ HunterLabWhitePoint = labWhitePoint;
+ }
+
+ /// <summary>
+ /// Target reference white. When not set, <see cref="LabColor.DefaultWhitePoint" /> is used.
+ /// </summary>
+ public XYZColor HunterLabWhitePoint { get; }
+
+ /// <summary>
+ /// Converts from <see cref="HunterLabColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public HunterLabColor Convert(in XYZColor input)
+ {
+ // conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
+ double X = input.X, Y = input.Y, Z = input.Z;
+ double Xn = HunterLabWhitePoint.X, Yn = HunterLabWhitePoint.Y, Zn = HunterLabWhitePoint.Z;
+
+ var Ka = ComputeKa(HunterLabWhitePoint);
+ var Kb = ComputeKb(HunterLabWhitePoint);
+
+ var L = 100 * Math.Sqrt(Y / Yn);
+ var a = Ka * ((X / Xn - Y / Yn) / Math.Sqrt(Y / Yn));
+ var b = Kb * ((Y / Yn - Z / Zn) / Math.Sqrt(Y / Yn));
+
+ if (double.IsNaN(a))
+ a = 0;
+
+ if (double.IsNaN(b))
+ b = 0;
+
+ var output = new HunterLabColor(L, a, b, HunterLabWhitePoint);
+ return output;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/IColorConversion.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/IColorConversion.cs
new file mode 100644
index 000000000..eff56c438
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/IColorConversion.cs
@@ -0,0 +1,20 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts color between two color spaces.
+ /// </summary>
+ /// <typeparam name="TInput"></typeparam>
+ /// <typeparam name="TOutput"></typeparam>
+ [SuppressMessage("ReSharper", "TypeParameterCanBeVariant")]
+ public interface IColorConversion<TInput, TOutput>
+ where TInput : struct
+ where TOutput : struct
+ {
+ /// <summary>
+ /// Converts from the input color space to the output color space.
+ /// </summary>
+ TOutput Convert(in TInput input);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChab/LChabToLabConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChab/LChabToLabConverter.cs
new file mode 100644
index 000000000..e607ae6e5
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChab/LChabToLabConverter.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="LChabColor" /> to <see cref="LabColor" />.
+ /// </summary>
+ public sealed class LChabToLabConverter : IColorConversion<LChabColor, LabColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly LChabToLabConverter Default = new LChabToLabConverter();
+
+ /// <summary>
+ /// Converts from <see cref="LChabColor" /> to <see cref="LabColor" />.
+ /// </summary>
+ public LabColor Convert(in LChabColor input)
+ {
+ double L = input.L, C = input.C, hDegrees = input.h;
+ var hRadians = Angle.DegreeToRadian(hDegrees);
+
+ var a = C * Math.Cos(hRadians);
+ var b = C * Math.Sin(hRadians);
+
+ var output = new LabColor(L, a, b, input.WhitePoint);
+ return output;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LChabToLabConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LChabToLabConverter left, LChabToLabConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LChabToLabConverter left, LChabToLabConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChab/LabToLChabConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChab/LabToLChabConverter.cs
new file mode 100644
index 000000000..39bb80d0b
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChab/LabToLChabConverter.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="LabColor" /> to <see cref="LChabColor" />.
+ /// </summary>
+ public sealed class LabToLChabConverter : IColorConversion<LabColor, LChabColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly LabToLChabConverter Default = new LabToLChabConverter();
+
+ /// <summary>
+ /// Converts from <see cref="LabColor" /> to <see cref="LChabColor" />.
+ /// </summary>
+ public LChabColor Convert(in LabColor input)
+ {
+ double L = input.L, a = input.a, b = input.b;
+ var C = Math.Sqrt(a * a + b * b);
+ var hRadians = Math.Atan2(b, a);
+ var hDegrees = Angle.NormalizeDegree(Angle.RadianToDegree(hRadians));
+
+ var output = new LChabColor(L, C, hDegrees, input.WhitePoint);
+ return output;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LabToLChabConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LabToLChabConverter left, LabToLChabConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LabToLChabConverter left, LabToLChabConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChuv/LChuvToLuvConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChuv/LChuvToLuvConverter.cs
new file mode 100644
index 000000000..ef8c06d0b
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChuv/LChuvToLuvConverter.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="LChuvColor" /> to <see cref="LuvColor" />.
+ /// </summary>
+ public sealed class LChuvToLuvConverter : IColorConversion<LChuvColor, LuvColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly LChuvToLuvConverter Default = new LChuvToLuvConverter();
+
+ /// <summary>
+ /// Converts from <see cref="LChuvColor" /> to <see cref="LuvColor" />.
+ /// </summary>
+ public LuvColor Convert(in LChuvColor input)
+ {
+ double L = input.L, C = input.C, hDegrees = input.h;
+ var hRadians = Angle.DegreeToRadian(hDegrees);
+
+ var u = C * Math.Cos(hRadians);
+ var v = C * Math.Sin(hRadians);
+
+ var output = new LuvColor(L, u, v, input.WhitePoint);
+ return output;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LChuvToLuvConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LChuvToLuvConverter left, LChuvToLuvConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LChuvToLuvConverter left, LChuvToLuvConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChuv/LuvToLChuvConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChuv/LuvToLChuvConverter.cs
new file mode 100644
index 000000000..1cb75c1fb
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LChuv/LuvToLChuvConverter.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="LuvColor" /> to <see cref="LChuvColor" />.
+ /// </summary>
+ public sealed class LuvToLChuvConverter : IColorConversion<LuvColor, LChuvColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly LuvToLChuvConverter Default = new LuvToLChuvConverter();
+
+ /// <summary>
+ /// Converts from <see cref="LuvColor" /> to <see cref="LChuvColor" />.
+ /// </summary>
+ public LChuvColor Convert(in LuvColor input)
+ {
+ double L = input.L, u = input.u, v = input.v;
+ var C = Math.Sqrt(u * u + v * v);
+ var hRadians = Math.Atan2(v, u);
+ var hDegrees = Angle.NormalizeDegree(Angle.RadianToDegree(hRadians));
+
+ var output = new LChuvColor(L, C, hDegrees, input.WhitePoint);
+ return output;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LuvToLChuvConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LuvToLChuvConverter left, LuvToLChuvConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LuvToLChuvConverter left, LuvToLChuvConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LMS/LMSTransformationMatrix.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LMS/LMSTransformationMatrix.cs
new file mode 100644
index 000000000..8e439e81d
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LMS/LMSTransformationMatrix.cs
@@ -0,0 +1,84 @@
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Matrix used for transformation from XYZ to LMS, defining the cone response domain.
+ /// Used in <see cref="Colourful.Conversion.IChromaticAdaptation" />
+ /// </summary>
+ /// <remarks>
+ /// Matrix data obtained from:
+ /// Two New von Kries Based Chromatic Adaptation Transforms Found by Numerical Optimization
+ /// S. Bianco, R. Schettini
+ /// DISCo, Department of Informatics, Systems and Communication, University of Milan-Bicocca, viale Sarca 336, 20126 Milan, Italy
+ /// http://www.ivl.disco.unimib.it/papers2003/CRA-CAT.pdf
+ /// </remarks>
+ public static class LMSTransformationMatrix
+ {
+ /// <summary>
+ /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65)
+ /// </summary>
+ public static readonly Matrix VonKriesHPEAdjusted = new Vector[]
+ {
+ new[] { 0.40024, 0.7076, -0.08081 },
+ new[] { -0.2263, 1.16532, 0.0457 },
+ new[] { 0, 0, 0.91822 },
+ };
+
+ /// <summary>
+ /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy)
+ /// </summary>
+ public static readonly Matrix VonKriesHPE = new Vector[]
+ {
+ new[] { 0.3897, 0.6890, -0.0787 },
+ new[] { -0.2298, 1.1834, 0.0464 },
+ new[] { 0.0, 0.0, 1.0 },
+ };
+
+ /// <summary>
+ /// XYZ scaling chromatic adaptation transform matrix
+ /// </summary>
+ public static readonly Matrix XYZScaling = MatrixFactory.CreateIdentity(3);
+
+ /// <summary>
+ /// Bradford chromatic adaptation transform matrix (used in CMCCAT97)
+ /// </summary>
+ public static readonly Matrix Bradford = new Vector[]
+ {
+ new[] { 0.8951, 0.2664, -0.1614 },
+ new[] { -0.7502, 1.7135, 0.0367 },
+ new[] { 0.0389, -0.0685, 1.0296 },
+ };
+
+ /// <summary>
+ /// Spectral sharpening and the Bradford transform
+ /// </summary>
+ public static readonly Matrix BradfordSharp = new Vector[]
+ {
+ new[] { 1.2694, -0.0988, -0.1706 },
+ new[] { -0.8364, 1.8006, 0.0357 },
+ new[] { 0.0297, -0.0315, 1.0018 },
+ };
+
+ /// <summary>
+ /// CMCCAT2000 (fitted from all available color data sets)
+ /// </summary>
+ public static readonly Matrix CMCCAT2000 = new Vector[]
+ {
+ new[] { 0.7982, 0.3389, -0.1371 },
+ new[] { -0.5918, 1.5512, 0.0406 },
+ new[] { 0.0008, 0.239, 0.9753 },
+ };
+
+ /// <summary>
+ /// CAT02 (optimized for minimizing CIELAB differences)
+ /// </summary>
+ public static readonly Matrix CAT02 = new Vector[]
+ {
+ new[] { 0.7328, 0.4296, -0.1624 },
+ new[] { -0.7036, 1.6975, 0.0061 },
+ new[] { 0.0030, 0.0136, 0.9834 },
+ };
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LMS/XYZAndLMSConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LMS/XYZAndLMSConverter.cs
new file mode 100644
index 000000000..f8f7b8e49
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/LMS/XYZAndLMSConverter.cs
@@ -0,0 +1,67 @@
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="LMSColor" /> and back.
+ /// </summary>
+ public sealed class XYZAndLMSConverter : IColorConversion<XYZColor, LMSColor>, IColorConversion<LMSColor, XYZColor>
+ {
+ /// <summary>
+ /// Default transformation matrix used, when no other is set. (Bradford)
+ /// <see cref="LMSTransformationMatrix" />
+ /// </summary>
+ public static readonly Matrix DefaultTransformationMatrix = LMSTransformationMatrix.Bradford;
+
+ private Matrix _transformationMatrix;
+
+ private Matrix _transformationMatrixInverse;
+
+ /// <summary>
+ /// Constructs with <see cref="DefaultTransformationMatrix" />
+ /// </summary>
+ public XYZAndLMSConverter() : this(DefaultTransformationMatrix)
+ {
+ }
+
+ /// <param name="transformationMatrix">Definition of the cone response domain (see <see cref="LMSTransformationMatrix" />), if not set <see cref="DefaultTransformationMatrix" /> will be used.</param>
+ public XYZAndLMSConverter(Matrix transformationMatrix)
+ {
+ TransformationMatrix = transformationMatrix;
+ }
+
+ /// <summary>
+ /// Transformation matrix used for the conversion (definition of the cone response domain).
+ /// <see cref="LMSTransformationMatrix" />
+ /// </summary>
+ public Matrix TransformationMatrix
+ {
+ get => _transformationMatrix;
+ internal set
+ {
+ _transformationMatrix = value;
+ _transformationMatrixInverse = TransformationMatrix.Inverse();
+ }
+ }
+
+ /// <summary>
+ /// Converts from <see cref="LMSColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public XYZColor Convert(in LMSColor input)
+ {
+ var outputVector = _transformationMatrixInverse.MultiplyBy(input.Vector);
+ var output = new XYZColor(outputVector);
+ return output;
+ }
+
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="LMSColor" />.
+ /// </summary>
+ public LMSColor Convert(in XYZColor input)
+ {
+ var outputVector = TransformationMatrix.MultiplyBy(input.Vector);
+ var output = new LMSColor(outputVector);
+ return output;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Lab/LabToXYZConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Lab/LabToXYZConverter.cs
new file mode 100644
index 000000000..716195fc9
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Lab/LabToXYZConverter.cs
@@ -0,0 +1,62 @@
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="LabColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public sealed class LabToXYZConverter : IColorConversion<LabColor, XYZColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly LabToXYZConverter Default = new LabToXYZConverter();
+
+ /// <summary>
+ /// Converts from <see cref="LabColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public XYZColor Convert(in LabColor input)
+ {
+ // conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
+ double L = input.L, a = input.a, b = input.b;
+ var fy = (L + 16) / 116d;
+ var fx = a / 500d + fy;
+ var fz = fy - b / 200d;
+
+ var fx3 = MathUtils.Pow3(fx);
+ var fz3 = MathUtils.Pow3(fz);
+
+ var xr = fx3 > CIEConstants.Epsilon ? fx3 : (116 * fx - 16) / CIEConstants.Kappa;
+ var yr = L > CIEConstants.Kappa * CIEConstants.Epsilon ? MathUtils.Pow3((L + 16) / 116d) : L / CIEConstants.Kappa;
+ var zr = fz3 > CIEConstants.Epsilon ? fz3 : (116 * fz - 16) / CIEConstants.Kappa;
+
+ double Xr = input.WhitePoint.X, Yr = input.WhitePoint.Y, Zr = input.WhitePoint.Z;
+
+ // avoids XYZ coordinates out range (restricted by 0 and XYZ reference white)
+ xr = xr.CropRange(0, 1);
+ yr = yr.CropRange(0, 1);
+ zr = zr.CropRange(0, 1);
+
+ var X = xr * Xr;
+ var Y = yr * Yr;
+ var Z = zr * Zr;
+
+ var result = new XYZColor(X, Y, Z);
+ return result;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LabToXYZConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LabToXYZConverter left, LabToXYZConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LabToXYZConverter left, LabToXYZConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Lab/XYZToLabConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Lab/XYZToLabConverter.cs
new file mode 100644
index 000000000..4e9ee17c0
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Lab/XYZToLabConverter.cs
@@ -0,0 +1,84 @@
+using System;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="LabColor" />.
+ /// </summary>
+ public sealed class XYZToLabConverter : IColorConversion<XYZColor, LabColor>
+ {
+ /// <summary>
+ /// Constructs with <see cref="LabColor.DefaultWhitePoint" />
+ /// </summary>
+ public XYZToLabConverter()
+ : this(LabColor.DefaultWhitePoint)
+ {
+ }
+
+ /// <summary>
+ /// Constructs with arbitrary white point
+ /// </summary>
+ public XYZToLabConverter(XYZColor labWhitePoint)
+ {
+ LabWhitePoint = labWhitePoint;
+ }
+
+ /// <summary>
+ /// Target reference white. When not set, <see cref="LabColor.DefaultWhitePoint" /> is used.
+ /// </summary>
+ public XYZColor LabWhitePoint { get; }
+
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="LabColor" />.
+ /// </summary>
+ public LabColor Convert(in XYZColor input)
+ {
+ // conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
+ double Xr = LabWhitePoint.X, Yr = LabWhitePoint.Y, Zr = LabWhitePoint.Z;
+
+ double xr = input.X / Xr, yr = input.Y / Yr, zr = input.Z / Zr;
+
+ var fx = f(xr);
+ var fy = f(yr);
+ var fz = f(zr);
+
+ var L = 116 * fy - 16;
+ var a = 500 * (fx - fy);
+ var b = 200 * (fy - fz);
+
+ var output = new LabColor(L, a, b, LabWhitePoint);
+ return output;
+ }
+
+ private static double f(double cr)
+ {
+ var fc = cr > CIEConstants.Epsilon ? Math.Pow(cr, 1 / 3d) : (CIEConstants.Kappa * cr + 16) / 116d;
+ return fc;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(XYZToLabConverter other)
+ {
+ if (other == null)
+ return false;
+
+ return ReferenceEquals(this, other) || LabWhitePoint.Equals(other.LabWhitePoint);
+ }
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is XYZToLabConverter other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => LabWhitePoint.GetHashCode();
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(XYZToLabConverter left, XYZToLabConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(XYZToLabConverter left, XYZToLabConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Luv/LuvToXYZConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Luv/LuvToXYZConverter.cs
new file mode 100644
index 000000000..d6f3a02d1
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Luv/LuvToXYZConverter.cs
@@ -0,0 +1,69 @@
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="LuvColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public sealed class LuvToXYZConverter : IColorConversion<LuvColor, XYZColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly LuvToXYZConverter Default = new LuvToXYZConverter();
+
+ /// <summary>
+ /// Converts from <see cref="LuvColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public XYZColor Convert(in LuvColor input)
+ {
+ // conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html
+ double L = input.L, u = input.u, v = input.v;
+
+ var u0 = Compute_u0(input.WhitePoint);
+ var v0 = Compute_v0(input.WhitePoint);
+
+ var Y = L > CIEConstants.Kappa * CIEConstants.Epsilon
+ ? MathUtils.Pow3((L + 16) / 116)
+ : L / CIEConstants.Kappa;
+
+ var a = (52 * L / (u + 13 * L * u0) - 1) / 3;
+ var b = -5 * Y;
+ var c = -1 / 3d;
+ var d = Y * (39 * L / (v + 13 * L * v0) - 5);
+
+ var X = (d - b) / (a - c);
+ var Z = X * a + b;
+
+ if (double.IsNaN(X) || X < 0)
+ X = 0;
+
+ if (double.IsNaN(Y) || Y < 0)
+ Y = 0;
+
+ if (double.IsNaN(Z) || Z < 0)
+ Z = 0;
+
+ var result = new XYZColor(X, Y, Z);
+ return result;
+ }
+
+ private static double Compute_u0(XYZColor input) => 4 * input.X / (input.X + 15 * input.Y + 3 * input.Z);
+
+ private static double Compute_v0(XYZColor input) => 9 * input.Y / (input.X + 15 * input.Y + 3 * input.Z);
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LuvToXYZConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LuvToXYZConverter left, LuvToXYZConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LuvToXYZConverter left, LuvToXYZConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Luv/XYZToLuvConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Luv/XYZToLuvConverter.cs
new file mode 100644
index 000000000..8ff59999f
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/Luv/XYZToLuvConverter.cs
@@ -0,0 +1,90 @@
+using System;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="LuvColor" />.
+ /// </summary>
+ public sealed class XYZToLuvConverter : IColorConversion<XYZColor, LuvColor>
+ {
+ /// <summary>
+ /// Constructs with <see cref="LuvColor.DefaultWhitePoint" />
+ /// </summary>
+ public XYZToLuvConverter()
+ : this(LuvColor.DefaultWhitePoint)
+ {
+ }
+
+ /// <summary>
+ /// Constructs with arbitrary white point
+ /// </summary>
+ public XYZToLuvConverter(XYZColor labWhitePoint)
+ {
+ LuvWhitePoint = labWhitePoint;
+ }
+
+ /// <summary>
+ /// Target reference white. When not set, <see cref="LuvColor.DefaultWhitePoint" /> is used.
+ /// </summary>
+ public XYZColor LuvWhitePoint { get; }
+
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="LuvColor" />.
+ /// </summary>
+ public LuvColor Convert(in XYZColor input)
+ {
+ // conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Luv.html
+
+ var yr = input.Y / LuvWhitePoint.Y;
+ var up = Compute_up(input);
+ var vp = Compute_vp(input);
+ var upr = Compute_up(LuvWhitePoint);
+ var vpr = Compute_vp(LuvWhitePoint);
+
+ var L = yr > CIEConstants.Epsilon ? 116 * Math.Pow(yr, 1 / 3d) - 16 : CIEConstants.Kappa * yr;
+
+ if (double.IsNaN(L) || L < 0)
+ L = 0;
+
+ var u = 13 * L * (up - upr);
+ var v = 13 * L * (vp - vpr);
+
+ if (double.IsNaN(u))
+ u = 0;
+
+ if (double.IsNaN(v))
+ v = 0;
+
+ return new LuvColor(L, u, v, LuvWhitePoint);
+ }
+
+ private static double Compute_up(XYZColor input) => 4 * input.X / (input.X + 15 * input.Y + 3 * input.Z);
+
+ private static double Compute_vp(XYZColor input) => 9 * input.Y / (input.X + 15 * input.Y + 3 * input.Z);
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(XYZToLuvConverter other)
+ {
+ if (other == null)
+ return false;
+
+ return ReferenceEquals(this, other) || LuvWhitePoint.Equals(other.LuvWhitePoint);
+ }
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is XYZToLuvConverter other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => LuvWhitePoint.GetHashCode();
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(XYZToLuvConverter left, XYZToLuvConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(XYZToLuvConverter left, XYZToLuvConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBAndXYZConverterBase.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBAndXYZConverterBase.cs
new file mode 100644
index 000000000..a873cfd39
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBAndXYZConverterBase.cs
@@ -0,0 +1,70 @@
+using System;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Base class for conversions between <see cref="RGBColor" /> and <see cref="XYZColor" />.
+ /// </summary>
+ public abstract class LinearRGBAndXYZConverterBase
+ {
+ /// <summary>
+ /// Computes RGB/XYZ matrix
+ /// </summary>
+ protected static Matrix GetRGBToXYZMatrix(IRGBWorkingSpace workingSpace)
+ {
+ if (workingSpace == null) throw new ArgumentNullException(nameof(workingSpace));
+
+ // for more info, see: http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+
+ var chromaticity = workingSpace.ChromaticityCoordinates;
+ double xr = chromaticity.R.x,
+ xg = chromaticity.G.x,
+ xb = chromaticity.B.x,
+ yr = chromaticity.R.y,
+ yg = chromaticity.G.y,
+ yb = chromaticity.B.y;
+
+ var Xr = xr / yr;
+ const double Yr = 1;
+ var Zr = (1 - xr - yr) / yr;
+
+ var Xg = xg / yg;
+ const double Yg = 1;
+ var Zg = (1 - xg - yg) / yg;
+
+ var Xb = xb / yb;
+ const double Yb = 1;
+ var Zb = (1 - xb - yb) / yb;
+
+ var S = new Vector[]
+ {
+ new[] { Xr, Xg, Xb },
+ new[] { Yr, Yg, Yb },
+ new[] { Zr, Zg, Zb },
+ }.Inverse();
+
+ var W = workingSpace.WhitePoint.Vector;
+
+ var SW = S.MultiplyBy(W);
+ var Sr = SW[0];
+ var Sg = SW[1];
+ var Sb = SW[2];
+
+ Matrix M = new Vector[]
+ {
+ new[] { Sr * Xr, Sg * Xg, Sb * Xb },
+ new[] { Sr * Yr, Sg * Yg, Sb * Yb },
+ new[] { Sr * Zr, Sg * Zg, Sb * Zb },
+ };
+
+ return M;
+ }
+
+ /// <summary>
+ /// Computes XYZ/RGB matrix
+ /// </summary>
+ protected static Matrix GetXYZToRGBMatrix(IRGBWorkingSpace workingSpace) => GetRGBToXYZMatrix(workingSpace).Inverse();
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBToRGBConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBToRGBConverter.cs
new file mode 100644
index 000000000..b2b4cd650
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBToRGBConverter.cs
@@ -0,0 +1,59 @@
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="LinearRGBColor" /> to <see cref="RGBColor" />.
+ /// </summary>
+ public sealed class LinearRGBToRGBConverter : IColorConversion<LinearRGBColor, RGBColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly LinearRGBToRGBConverter Default = new LinearRGBToRGBConverter();
+
+ /// <summary>
+ /// Converts from <see cref="LinearRGBColor" /> to <see cref="RGBColor" />.
+ /// </summary>
+ public RGBColor Convert(in LinearRGBColor input)
+ {
+ var result = CompandVector(input.Vector, input.WorkingSpace);
+ return result;
+ }
+
+ /// <summary>
+ /// Applying the working space companding function (<see cref="IRGBWorkingSpace.Companding" />) to uncompanded vector.
+ /// </summary>
+ private static RGBColor CompandVector(Vector uncompandedVector, IRGBWorkingSpace workingSpace)
+ {
+ var companding = workingSpace.Companding;
+ Vector compandedVector = new[]
+ {
+ companding.Companding(uncompandedVector[0]).CropRange(0, 1),
+ companding.Companding(uncompandedVector[1]).CropRange(0, 1),
+ companding.Companding(uncompandedVector[2]).CropRange(0, 1)
+ };
+ var result = new RGBColor(compandedVector, workingSpace);
+ return result;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(LinearRGBToRGBConverter other) => other != null;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LinearRGBToRGBConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LinearRGBToRGBConverter left, LinearRGBToRGBConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LinearRGBToRGBConverter left, LinearRGBToRGBConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBToXYZConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBToXYZConverter.cs
new file mode 100644
index 000000000..fda683888
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/LinearRGBToXYZConverter.cs
@@ -0,0 +1,64 @@
+using System;
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="LinearRGBColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public sealed class LinearRGBToXYZConverter : LinearRGBAndXYZConverterBase, IColorConversion<LinearRGBColor, XYZColor>
+ {
+ private readonly Matrix _conversionMatrix;
+
+ /// <param name="sourceRGBWorkingSpace">Source RGB working space</param>
+ public LinearRGBToXYZConverter(IRGBWorkingSpace sourceRGBWorkingSpace)
+ {
+ SourceRGBWorkingSpace = sourceRGBWorkingSpace;
+ _conversionMatrix = GetRGBToXYZMatrix(SourceRGBWorkingSpace);
+ }
+
+ /// <summary>
+ /// Source RGB working space
+ /// </summary>
+ public IRGBWorkingSpace SourceRGBWorkingSpace { get; }
+
+ /// <summary>
+ /// Converts from <see cref="LinearRGBColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public XYZColor Convert(in LinearRGBColor input)
+ {
+ if (!Equals(input.WorkingSpace, SourceRGBWorkingSpace))
+ throw new InvalidOperationException("Working space of input RGB color must be equal to converter source RGB working space.");
+
+ var xyz = _conversionMatrix.MultiplyBy(input.Vector);
+
+ var converted = new XYZColor(xyz);
+ return converted;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(LinearRGBToXYZConverter other)
+ {
+ if (ReferenceEquals(this, other))
+ return true;
+
+ return Equals(SourceRGBWorkingSpace, other.SourceRGBWorkingSpace);
+ }
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LinearRGBToXYZConverter other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => SourceRGBWorkingSpace?.GetHashCode() ?? 0;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LinearRGBToXYZConverter left, LinearRGBToXYZConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LinearRGBToXYZConverter left, LinearRGBToXYZConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/RGBToLinearRGBConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/RGBToLinearRGBConverter.cs
new file mode 100644
index 000000000..90122d1f8
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/RGBToLinearRGBConverter.cs
@@ -0,0 +1,60 @@
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="RGBColor" /> to <see cref="LinearRGBColor" />.
+ /// </summary>
+ public sealed class RGBToLinearRGBConverter : IColorConversion<RGBColor, LinearRGBColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly RGBToLinearRGBConverter Default = new RGBToLinearRGBConverter();
+
+ /// <summary>
+ /// Converts from <see cref="RGBColor" /> to <see cref="LinearRGBColor" />.
+ /// </summary>
+ public LinearRGBColor Convert(in RGBColor input)
+ {
+ var uncompandedVector = UncompandVector(input);
+ var converted = new LinearRGBColor(uncompandedVector, input.WorkingSpace);
+ return converted;
+ }
+
+ /// <summary>
+ /// Applying the working space inverse companding function (<see cref="IRGBWorkingSpace.Companding" />) to RGB vector.
+ /// </summary>
+ private static Vector UncompandVector(in RGBColor rgbColor)
+ {
+ var companding = rgbColor.WorkingSpace.Companding;
+ var compandedVector = rgbColor.Vector;
+ Vector uncompandedVector = new[]
+ {
+ companding.InverseCompanding(compandedVector[0]).CropRange(0, 1),
+ companding.InverseCompanding(compandedVector[1]).CropRange(0, 1),
+ companding.InverseCompanding(compandedVector[2]).CropRange(0, 1)
+ };
+ return uncompandedVector;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(RGBToLinearRGBConverter other) => other != null;
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is RGBToLinearRGBConverter;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(RGBToLinearRGBConverter left, RGBToLinearRGBConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(RGBToLinearRGBConverter left, RGBToLinearRGBConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/XYZToLinearRGBConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/XYZToLinearRGBConverter.cs
new file mode 100644
index 000000000..9f3a548d2
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/RGB/XYZToLinearRGBConverter.cs
@@ -0,0 +1,72 @@
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="LinearRGBColor" />.
+ /// </summary>
+ /// <remarks>
+ /// The target RGB working space is <see cref="RGBColor.DefaultWorkingSpace" /> when not set.
+ /// </remarks>
+ public sealed class XYZToLinearRGBConverter : LinearRGBAndXYZConverterBase, IColorConversion<XYZColor, LinearRGBColor>
+ {
+ private readonly Matrix _conversionMatrix;
+
+ /// <summary>
+ /// Constructs with <see cref="RGBColor.DefaultWorkingSpace" />.
+ /// </summary>
+ public XYZToLinearRGBConverter() : this(null)
+ {
+ }
+
+ /// <summary>
+ /// Constructs with arbitrary working space.
+ /// </summary>
+ public XYZToLinearRGBConverter(IRGBWorkingSpace targetRGBWorkingSpace)
+ {
+ TargetRGBWorkingSpace = targetRGBWorkingSpace ?? RGBColor.DefaultWorkingSpace;
+ _conversionMatrix = GetXYZToRGBMatrix(TargetRGBWorkingSpace);
+ }
+
+ /// <summary>
+ /// Target RGB working space. When not set, target RGB working space is <see cref="RGBColor.DefaultWorkingSpace" />.
+ /// </summary>
+ public IRGBWorkingSpace TargetRGBWorkingSpace { get; }
+
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="LinearRGBColor" />.
+ /// </summary>
+ public LinearRGBColor Convert(in XYZColor input)
+ {
+ var inputVector = input.Vector;
+ var uncompandedVector = _conversionMatrix.MultiplyBy(inputVector).CropRange(0, 1);
+ var result = new LinearRGBColor(uncompandedVector, TargetRGBWorkingSpace);
+ return result;
+ }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(XYZToLinearRGBConverter other)
+ {
+ if (other == null)
+ return false;
+
+ return ReferenceEquals(this, other) || TargetRGBWorkingSpace.Equals(other.TargetRGBWorkingSpace);
+ }
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is XYZToLinearRGBConverter other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => TargetRGBWorkingSpace?.GetHashCode() ?? 0;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(XYZToLinearRGBConverter left, XYZToLinearRGBConverter right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(XYZToLinearRGBConverter left, XYZToLinearRGBConverter right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/xyY/xyYAndXYZConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/xyY/xyYAndXYZConverter.cs
new file mode 100644
index 000000000..512e49f1e
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Conversion/xyY/xyYAndXYZConverter.cs
@@ -0,0 +1,45 @@
+namespace Colourful.Implementation.Conversion
+{
+ /// <summary>
+ /// Converts from <see cref="xyYColor" /> to <see cref="XYZColor" /> and back.
+ /// </summary>
+ public sealed class xyYAndXYZConverter : IColorConversion<XYZColor, xyYColor>, IColorConversion<xyYColor, XYZColor>
+ {
+ /// <summary>
+ /// Default singleton instance of the converter.
+ /// </summary>
+ public static readonly xyYAndXYZConverter Default = new xyYAndXYZConverter();
+
+ /// <summary>
+ /// Converts from <see cref="xyYColor" /> to <see cref="XYZColor" />.
+ /// </summary>
+ public XYZColor Convert(in xyYColor input)
+ {
+ // ReSharper disable CompareOfFloatsByEqualityOperator
+ if (input.y == 0)
+ return new XYZColor(0, 0, input.Luminance);
+ // ReSharper restore CompareOfFloatsByEqualityOperator
+
+ var X = input.x * input.Luminance / input.y;
+ var Y = input.Luminance;
+ var Z = (1 - input.x - input.y) * Y / input.y;
+
+ return new XYZColor(X, Y, Z);
+ }
+
+ /// <summary>
+ /// Converts from <see cref="XYZColor" /> to <see cref="xyYColor" />.
+ /// </summary>
+ public xyYColor Convert(in XYZColor input)
+ {
+ var x = input.X / (input.X + input.Y + input.Z);
+ var y = input.Y / (input.X + input.Y + input.Z);
+
+ if (double.IsNaN(x) || double.IsNaN(y))
+ return new xyYColor(0, 0, input.Y);
+
+ var Y = input.Y;
+ return new xyYColor(x, y, Y);
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/GammaCompanding.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/GammaCompanding.cs
new file mode 100644
index 000000000..5a64871b7
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/GammaCompanding.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Colourful.Implementation.RGB
+{
+ /// <summary>
+ /// Gamma companding
+ /// </summary>
+ /// <remarks>
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ /// </remarks>
+ public sealed class GammaCompanding : ICompanding
+ {
+ /// <summary>
+ /// Constructs with given gamma
+ /// </summary>
+ public GammaCompanding(double gamma)
+ {
+ Gamma = gamma;
+ }
+
+ /// <summary>
+ /// Gamma
+ /// </summary>
+ public double Gamma { get; }
+
+ /// <inheritdoc />
+ public double InverseCompanding(double channel)
+ {
+ var V = channel;
+ var v = Math.Pow(V, Gamma);
+ return v;
+ }
+
+ /// <inheritdoc />
+ public double Companding(double channel)
+ {
+ var v = channel;
+ var V = Math.Pow(v, 1 / Gamma);
+ return V;
+ }
+
+ /// <inheritdoc cref="object" />
+ [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
+ public bool Equals(GammaCompanding other)
+ {
+ if (other == null)
+ return false;
+
+ return Gamma == other.Gamma;
+ }
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is GammaCompanding other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => Gamma.GetHashCode();
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(GammaCompanding left, GammaCompanding right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(GammaCompanding left, GammaCompanding right) => !Equals(left, right);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/ICompanding.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/ICompanding.cs
new file mode 100644
index 000000000..f170307f1
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/ICompanding.cs
@@ -0,0 +1,28 @@
+namespace Colourful.Implementation.RGB
+{
+ /// <summary>
+ /// Pair of companding functions for <see cref="IRGBWorkingSpace" />.
+ /// Used for conversion to XYZ and backwards.
+ /// See also: <seealso cref="IRGBWorkingSpace.Companding" />
+ /// </summary>
+ public interface ICompanding
+ {
+ /// <summary>
+ /// Companded channel is made linear with respect to the energy.
+ /// </summary>
+ /// <remarks>
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ /// </remarks>
+ double InverseCompanding(double channel);
+
+ /// <summary>
+ /// Uncompanded channel (linear) is made nonlinear (depends on the RGB color system).
+ /// </summary>
+ /// <remarks>
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ /// </remarks>
+ double Companding(double channel);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/LCompanding.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/LCompanding.cs
new file mode 100644
index 000000000..9c8fd005d
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/LCompanding.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace Colourful.Implementation.RGB
+{
+ /// <summary>
+ /// L* companding
+ /// </summary>
+ /// <remarks>
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ /// </remarks>
+ public sealed class LCompanding : ICompanding
+ {
+ private const double Kappa = 24389d / 27d;
+ private const double Epsilon = 216d / 24389d;
+
+ /// <inheritdoc />
+ public double InverseCompanding(double channel)
+ {
+ var V = channel;
+ var v = V <= 0.08 ? 100.0 * V / Kappa : Math.Pow((V + 0.16) / 1.16, 3.0);
+ return v;
+ }
+
+ /// <inheritdoc />
+ public double Companding(double channel)
+ {
+ var v = channel;
+ var V = v <= Epsilon ? v * Kappa / 100.0 : 1.16 * Math.Pow(v, 1.0 / 3.0) - 0.16;
+ return V;
+ }
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is LCompanding;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(LCompanding left, LCompanding right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(LCompanding left, LCompanding right) => !Equals(left, right);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/RGBPrimariesChromaticityCoordinates.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/RGBPrimariesChromaticityCoordinates.cs
new file mode 100644
index 000000000..5b9ea5ff1
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/RGBPrimariesChromaticityCoordinates.cs
@@ -0,0 +1,61 @@
+namespace Colourful.Implementation.RGB
+{
+ /// <summary>
+ /// Chromaticity coordinates of RGB primaries.
+ /// One of the specifiers of <see cref="IRGBWorkingSpace" />.
+ /// </summary>
+ public readonly struct RGBPrimariesChromaticityCoordinates
+ {
+ /// <summary>
+ /// Constructs coordinates
+ /// </summary>
+ public RGBPrimariesChromaticityCoordinates(xyChromaticityCoordinates r, xyChromaticityCoordinates g, xyChromaticityCoordinates b)
+ {
+ R = r;
+ G = g;
+ B = b;
+ }
+
+ /// <summary>
+ /// Red
+ /// </summary>
+ public xyChromaticityCoordinates R { get; }
+
+ /// <summary>
+ /// Green
+ /// </summary>
+ public xyChromaticityCoordinates G { get; }
+
+ /// <summary>
+ /// Blue
+ /// </summary>
+ public xyChromaticityCoordinates B { get; }
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(RGBPrimariesChromaticityCoordinates other) =>
+ R.Equals(other.R) &&
+ G.Equals(other.G) &&
+ B.Equals(other.B);
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is RGBPrimariesChromaticityCoordinates other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = R.GetHashCode();
+ hashCode = (hashCode * 397) ^ G.GetHashCode();
+ hashCode = (hashCode * 397) ^ B.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(RGBPrimariesChromaticityCoordinates left, RGBPrimariesChromaticityCoordinates right) => left.Equals(right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(RGBPrimariesChromaticityCoordinates left, RGBPrimariesChromaticityCoordinates right) => !left.Equals(right);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/RGBWorkingSpace.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/RGBWorkingSpace.cs
new file mode 100644
index 000000000..98ee3f66d
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/RGBWorkingSpace.cs
@@ -0,0 +1,72 @@
+namespace Colourful.Implementation.RGB
+{
+ /// <summary>
+ /// Trivial implementation of <see cref="IRGBWorkingSpace" />
+ /// </summary>
+ public sealed class RGBWorkingSpace : IRGBWorkingSpace
+ {
+ /// <summary>
+ /// Constructs RGB working space using a reference white, companding, and chromacity coordinates.
+ /// </summary>
+ public RGBWorkingSpace(XYZColor referenceWhite, ICompanding companding, RGBPrimariesChromaticityCoordinates chromaticityCoordinates)
+ {
+ WhitePoint = referenceWhite;
+ Companding = companding;
+ ChromaticityCoordinates = chromaticityCoordinates;
+ }
+
+ /// <summary>
+ /// Reference white point
+ /// </summary>
+ public XYZColor WhitePoint { get; }
+
+ /// <summary>
+ /// Chromacity coordinates
+ /// </summary>
+ public RGBPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
+
+ /// <summary>
+ /// Companding
+ /// </summary>
+ public ICompanding Companding { get; }
+
+ #region Overrides
+
+ /// <inheritdoc cref="object" />
+ public bool Equals(IRGBWorkingSpace other)
+ {
+ if (other == null)
+ return false;
+
+ if (ReferenceEquals(this, other))
+ return true;
+
+ return Equals(WhitePoint, other.WhitePoint)
+ && ChromaticityCoordinates.Equals(other.ChromaticityCoordinates)
+ && Companding.Equals(other.Companding);
+ }
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is IRGBWorkingSpace other && Equals(other);
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = WhitePoint.GetHashCode();
+ hashCode = (hashCode * 397) ^ ChromaticityCoordinates.GetHashCode();
+ hashCode = (hashCode * 397) ^ (Companding != null ? Companding.GetHashCode() : 0);
+ return hashCode;
+ }
+ }
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(RGBWorkingSpace left, RGBWorkingSpace right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(RGBWorkingSpace left, RGBWorkingSpace right) => !Equals(left, right);
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/Rec2020Companding.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/Rec2020Companding.cs
new file mode 100644
index 000000000..eb6765824
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/Rec2020Companding.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Colourful.Implementation.RGB
+{
+ /// <summary>
+ /// Rec. 2020 companding function (for 12-bit).
+ /// </summary>
+ /// <remarks>
+ /// http://en.wikipedia.org/wiki/Rec._2020
+ /// For 10-bits, companding is identical to <see cref="Colourful.Implementation.RGB.Rec709Companding" />
+ /// </remarks>
+ public sealed class Rec2020Companding : ICompanding
+ {
+ private const double Alpha = 1.09929682680944;
+ private const double Beta = 0.018053968510807;
+ private const double InverseBeta = Beta * 4.5;
+
+ /// <inheritdoc />
+ public double InverseCompanding(double channel)
+ {
+ var V = channel;
+ var L = V < InverseBeta ? V / 4.5 : Math.Pow((V + Alpha - 1.0) / Alpha, 1 / 0.45);
+ return L;
+ }
+
+ /// <inheritdoc />
+ public double Companding(double channel)
+ {
+ var L = channel;
+ var V = L < Beta ? 4.5 * L : Alpha * Math.Pow(L, 0.45) - (Alpha - 1.0);
+ return V;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/Rec709Companding.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/Rec709Companding.cs
new file mode 100644
index 000000000..48c5b511e
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/Rec709Companding.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Colourful.Implementation.RGB
+{
+ /// <summary>
+ /// Rec. 709 companding function
+ /// </summary>
+ /// <remarks>
+ /// http://en.wikipedia.org/wiki/Rec._709
+ /// </remarks>
+ public sealed class Rec709Companding : ICompanding
+ {
+ /// <inheritdoc />
+ public double InverseCompanding(double channel)
+ {
+ var V = channel;
+ var L = V < 0.081 ? V / 4.5 : Math.Pow((V + 0.099) / 1.099, 1 / 0.45);
+ return L;
+ }
+
+ /// <inheritdoc />
+ public double Companding(double channel)
+ {
+ var L = channel;
+ var V = L < 0.018 ? 4.5 * L : 1.099 * Math.Pow(L, 0.45) - 0.099;
+ return V;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/sRGBCompanding.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/sRGBCompanding.cs
new file mode 100644
index 000000000..c3689f14e
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/RGB/sRGBCompanding.cs
@@ -0,0 +1,43 @@
+using System;
+
+namespace Colourful.Implementation.RGB
+{
+ /// <summary>
+ /// sRGB companding
+ /// </summary>
+ /// <remarks>
+ /// For more info see:
+ /// http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ /// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
+ /// </remarks>
+ public sealed class sRGBCompanding : ICompanding
+ {
+ /// <inheritdoc />
+ public double InverseCompanding(double channel)
+ {
+ var V = channel;
+ var v = V <= 0.04045 ? V / 12.92 : Math.Pow((V + 0.055) / 1.055, 2.4);
+ return v;
+ }
+
+ /// <inheritdoc />
+ public double Companding(double channel)
+ {
+ var v = channel;
+ var V = v <= 0.0031308 ? 12.92 * v : 1.055 * Math.Pow(v, 1 / 2.4d) - 0.055;
+ return V;
+ }
+
+ /// <inheritdoc cref="object" />
+ public override bool Equals(object obj) => obj is sRGBCompanding;
+
+ /// <inheritdoc cref="object" />
+ public override int GetHashCode() => 1;
+
+ /// <inheritdoc cref="object" />
+ public static bool operator ==(sRGBCompanding left, sRGBCompanding right) => Equals(left, right);
+
+ /// <inheritdoc cref="object" />
+ public static bool operator !=(sRGBCompanding left, sRGBCompanding right) => !Equals(left, right);
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/Angle.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/Angle.cs
new file mode 100644
index 000000000..a50fbe7c5
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/Angle.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace Colourful.Implementation
+{
+ /// <summary>
+ /// Angle unit conversion helpers
+ /// </summary>
+ internal static class Angle
+ {
+ private const double TwoPI = 2 * Math.PI;
+
+ public static double RadianToDegree(double rad)
+ {
+ var deg = 360 * (rad / TwoPI);
+ return deg;
+ }
+
+ public static double DegreeToRadian(double deg)
+ {
+ var rad = TwoPI * (deg / 360d);
+ return rad;
+ }
+
+ public static double NormalizeDegree(double deg)
+ {
+ var d = deg % 360d;
+ return d >= 0 ? d : d + 360d;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/Extensions.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/Extensions.cs
new file mode 100644
index 000000000..87a231dc3
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/Extensions.cs
@@ -0,0 +1,125 @@
+using System;
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+
+namespace Colourful.Implementation
+{
+ internal static class Extensions
+ {
+ public static double CheckRange(this double value, double min, double max)
+ {
+ if (value < min)
+ throw new ArgumentOutOfRangeException(nameof(value), value, "The minimum value is " + min);
+
+ if (value > max)
+ throw new ArgumentOutOfRangeException(nameof(value), value, "The maximum value is " + max);
+
+ return value;
+ }
+
+ public static double CropRange(this double value, double min, double max)
+ {
+ if (value < min)
+ return min;
+
+ if (value > max)
+ return max;
+
+ return value;
+ }
+
+ public static Vector CropRange(this Vector vector, double min, double max)
+ {
+ var croppedVector = new double[vector.Count];
+
+ for (var i = 0; i < vector.Count; i++)
+ {
+ if (vector[i] < min)
+ {
+ croppedVector[i] = min;
+ }
+ else if (vector[i] > max)
+ {
+ croppedVector[i] = max;
+ }
+ else
+ {
+ croppedVector[i] = vector[i];
+ }
+ }
+
+ // ReSharper disable once CoVariantArrayConversion
+ return croppedVector;
+ }
+
+ /// <summary>
+ /// Matrix inverse for 3 by 3 matrices
+ /// </summary>
+ /// <param name="matrix"></param>
+ /// <returns></returns>
+ public static Matrix Inverse(this Matrix matrix)
+ {
+ if (matrix.Count != 3 || matrix[0].Count != 3)
+ throw new ArgumentOutOfRangeException(nameof(matrix), "Inversion is supported only on 3 by 3 matrices.");
+
+ var A = matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1];
+ var D = -(matrix[0][1] * matrix[2][2] - matrix[0][2] * matrix[2][1]);
+ var G = matrix[0][1] * matrix[1][2] - matrix[0][2] * matrix[1][1];
+ var B = -(matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0]);
+ var E = matrix[0][0] * matrix[2][2] - matrix[0][2] * matrix[2][0];
+ var H = -(matrix[0][0] * matrix[1][2] - matrix[0][2] * matrix[1][0]);
+ var C = matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0];
+ var F = -(matrix[0][0] * matrix[2][1] - matrix[0][1] * matrix[2][0]);
+ var I = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
+ var det = matrix[0][0] * A + matrix[0][1] * B + matrix[0][2] * C;
+ Matrix result = new Vector[]
+ {
+ new[] { A / det, D / det, G / det },
+ new[] { B / det, E / det, H / det },
+ new[] { C / det, F / det, I / det },
+ };
+ return result;
+ }
+
+ public static Vector MultiplyBy(this Matrix matrix, Vector vector)
+ {
+ if (matrix[0].Count != vector.Count)
+ throw new ArgumentOutOfRangeException(nameof(matrix), "Non-conformable matrices and vectors cannot be multiplied.");
+
+ var result = new double[matrix.Count];
+
+ for (var i = 0; i < matrix.Count; ++i) // each row of matrix
+ {
+ for (var k = 0; k < vector.Count; ++k) // each element of vector
+ {
+ result[i] += matrix[i][k] * vector[k];
+ }
+ }
+
+ // ReSharper disable once CoVariantArrayConversion
+ return result;
+ }
+
+ public static Matrix MultiplyBy(this Matrix matrix1, Matrix matrix2)
+ {
+ if (matrix1[0].Count != matrix2.Count)
+ throw new ArgumentOutOfRangeException(nameof(matrix1), "Non-conformable matrices cannot be multiplied.");
+
+ var result = MatrixFactory.CreateEmpty(matrix1.Count, matrix2[0].Count);
+
+ for (var i = 0; i < matrix1.Count; ++i) // each row of 1
+ {
+ for (var j = 0; j < matrix2[0].Count; ++j) // each column of 2
+ {
+ for (var k = 0; k < matrix1[0].Count; ++k)
+ {
+ result[i][j] += matrix1[i][k] * matrix2[k][j];
+ }
+ }
+ }
+
+ // ReSharper disable once CoVariantArrayConversion
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/MathUtils.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/MathUtils.cs
new file mode 100644
index 000000000..dfe396808
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/MathUtils.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace Colourful.Implementation
+{
+ /// <summary>
+ /// Math helper functions
+ /// </summary>
+ internal static class MathUtils
+ {
+ /// <summary>
+ /// Compute x^2
+ /// </summary>
+ /// <param name="x">Base</param>
+ /// <returns>Result of the exponentiation</returns>
+ public static double Pow2(double x) => x * x;
+
+ /// <summary>
+ /// Compute x^3
+ /// </summary>
+ /// <param name="x">Base</param>
+ /// <returns>Result of the exponentiation</returns>
+ public static double Pow3(double x) => x * x * x;
+
+ /// <summary>
+ /// Compute x^4
+ /// </summary>
+ /// <param name="x">Base</param>
+ /// <returns>Result of the exponentiation</returns>
+ public static double Pow4(double x) => x * x * (x * x);
+
+ /// <summary>
+ /// Compute x^7
+ /// </summary>
+ /// <param name="x">Base</param>
+ /// <returns>Result of the exponentiation</returns>
+ public static double Pow7(double x) => x * x * x * (x * x * x) * x;
+
+ /// <summary>
+ /// Compute sine of angle in degrees
+ /// </summary>
+ /// <param name="x">Given angle</param>
+ /// <returns></returns>
+ public static double SinDeg(double x)
+ {
+ var x_rad = Angle.DegreeToRadian(x);
+ var y = Math.Sin(x_rad);
+ return y;
+ }
+
+ /// <summary>
+ /// Compute cosine of angle in degrees
+ /// </summary>
+ /// <param name="x">Given angle</param>
+ /// <returns></returns>
+ public static double CosDeg(double x)
+ {
+ var x_rad = Angle.DegreeToRadian(x);
+ var y = Math.Cos(x_rad);
+ return y;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/MatrixFactory.cs b/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/MatrixFactory.cs
new file mode 100644
index 000000000..c80d80dc4
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Implementation/Utils/MatrixFactory.cs
@@ -0,0 +1,46 @@
+using Vector = System.Collections.Generic.IReadOnlyList<double>;
+using Matrix = System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<double>>;
+
+namespace Colourful.Implementation
+{
+ internal static class MatrixFactory
+ {
+ public static double[][] CreateEmpty(int rows, int columns)
+ {
+ var result = new double[rows][];
+ for (var i = 0; i < rows; i++)
+ {
+ result[i] = new double[columns];
+ }
+
+ return result;
+ }
+
+ public static Matrix CreateIdentity(int size)
+ {
+ var result = new double[size][];
+ for (var i = 0; i < size; i++)
+ {
+ result[i] = new double[size];
+ result[i][i] = 1;
+ }
+
+ // ReSharper disable once CoVariantArrayConversion
+ return result;
+ }
+
+ public static Matrix CreateDiagonal(params double[] items)
+ {
+ var size = items.Length;
+ var result = new double[size][];
+ for (var i = 0; i < size; i++)
+ {
+ result[i] = new double[size];
+ result[i][i] = items[i];
+ }
+
+ // ReSharper disable once CoVariantArrayConversion
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Other/CCTConverter.cs b/Software/Visual_Studio/SideChains/Colourful/Other/CCTConverter.cs
new file mode 100644
index 000000000..0a7c8939f
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Other/CCTConverter.cs
@@ -0,0 +1,63 @@
+using System;
+using Colourful.Implementation;
+
+namespace Colourful
+{
+ /// <summary>
+ /// Can compute chromaticity from CCT (Correlated color temperature) and also
+ /// compute CCT of given chromaticity.
+ /// </summary>
+ public static class CCTConverter
+ {
+ /// <summary>
+ /// Returns chromaticity coordinates of given CCT (specified in K)
+ /// </summary>
+ public static xyChromaticityCoordinates GetChromaticityOfCCT(double temperature)
+ {
+ // approximation described here: http://en.wikipedia.org/wiki/Planckian_locus#Approximation
+
+ double x_c;
+
+ if (temperature <= 4000) // correctly 1667 <= T <= 4000
+ x_c = -0.2661239 * (1000000000 / MathUtils.Pow3(temperature)) - 0.2343580 * (1000000 / MathUtils.Pow2(temperature)) + 0.8776956 * (1000 / temperature) + 0.179910;
+
+ else // correctly 4000 <= T <= 25000
+ x_c = -3.0258469 * (1000000000 / MathUtils.Pow3(temperature)) + 2.1070379 * (1000000 / MathUtils.Pow2(temperature)) + 0.2226347 * (1000 / temperature) + 0.240390;
+
+ double y_c;
+
+ if (temperature <= 2222) // correctly 1667 <= T <= 2222
+ y_c = -1.1063814 * MathUtils.Pow3(x_c) - 1.34811020 * MathUtils.Pow2(x_c) + 2.18555832 * x_c - 0.20219683;
+
+ else if (temperature <= 4000) // correctly 2222 <= T <= 4000
+ y_c = -0.9549476 * MathUtils.Pow3(x_c) - 1.37418593 * MathUtils.Pow2(x_c) + 2.09137015 * x_c - 0.16748867;
+
+ else // correctly 4000 <= T <= 25000
+ y_c = +3.0817580 * MathUtils.Pow3(x_c) - 5.87338670 * MathUtils.Pow2(x_c) + 3.75112997 * x_c - 0.37001483;
+
+ return new xyChromaticityCoordinates(x_c, y_c);
+ }
+
+ /// <summary>
+ /// Returns CCT (specified in K) of given chromaticity coordinates
+ /// </summary>
+ /// <remarks>Ranges usually from around 0 to 25000</remarks>
+ public static double GetCCTOfChromaticity(in xyChromaticityCoordinates chromaticity)
+ {
+ // approximation described here: http://en.wikipedia.org/wiki/Color_temperature#Approximation
+
+ const double xe = 0.3366;
+ const double ye = 0.1735;
+ const double A0 = -949.86315;
+ const double A1 = 6253.80338;
+ const double t1 = 0.92159;
+ const double A2 = 28.70599;
+ const double t2 = 0.20039;
+ const double A3 = 0.00004;
+ const double t3 = 0.07125;
+ var n = (chromaticity.x - xe) / (chromaticity.y - ye);
+ var cct = A0 + A1 * Math.Exp(-n / t1) + A2 * Math.Exp(-n / t2) + A3 * Math.Exp(-n / t3);
+ return cct;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Other/SaturationLChFormulas.cs b/Software/Visual_Studio/SideChains/Colourful/Other/SaturationLChFormulas.cs
new file mode 100644
index 000000000..24ecb4d20
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Other/SaturationLChFormulas.cs
@@ -0,0 +1,30 @@
+namespace Colourful
+{
+ /// <summary>
+ /// Extensions useful for <see cref="LChabColor" /> and <see cref="LChuvColor" /> color spaces.
+ /// </summary>
+ internal static class SaturationLChFormulas
+ {
+ /// <summary>
+ /// Returns saturation of the color (chroma normalized by lightness)
+ /// </summary>
+ public static double GetSaturation(double L, double C)
+ {
+ var result = 100 * (C / L);
+
+ if (double.IsNaN(result))
+ return 0;
+
+ return result;
+ }
+
+ /// <summary>
+ /// Gets chroma from saturation and lightness
+ /// </summary>
+ public static double GetChroma(double saturation, double L)
+ {
+ var result = L * (saturation / 100);
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/SideChains/Colourful/Properties/AssemblyInfo.cs b/Software/Visual_Studio/SideChains/Colourful/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..d01c7b045
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/Colourful/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+using System;
+using System.Runtime.InteropServices;
+
+[assembly: CLSCompliant(true)]
+[assembly: ComVisible(false)]
+[assembly: Guid("d11f6be9-3dcb-45b7-a076-4d476236c3cb")] \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.BL/Entities/BrushStop.cs b/Software/Visual_Studio/Tango.BL/Entities/BrushStop.cs
index 2fd65cdd4..adc7629b1 100644
--- a/Software/Visual_Studio/Tango.BL/Entities/BrushStop.cs
+++ b/Software/Visual_Studio/Tango.BL/Entities/BrushStop.cs
@@ -206,7 +206,7 @@ namespace Tango.BL.Entities
_yellow = cmyk.Y;
_black = cmyk.K;
- Lab lab = rgb.To<Lab>();
+ Lab lab = rgb.ToLabUsingColorful();
_l = lab.L;
_a = lab.A;
_b = lab.B;
@@ -589,14 +589,14 @@ namespace Tango.BL.Entities
{
case ColorSpaces.RGB:
cmyk = rgb.To<Cmyk>();
- lab = rgb.To<Lab>();
+ lab = rgb.ToLabUsingColorful();
break;
case ColorSpaces.CMYK:
rgb = cmyk.To<Rgb>();
lab = cmyk.To<Lab>();
break;
case ColorSpaces.LAB:
- rgb = lab.To<Rgb>();
+ rgb = lab.ToRgbUsingColorful();
cmyk = lab.To<Cmyk>();
break;
case ColorSpaces.Catalog:
diff --git a/Software/Visual_Studio/Tango.BL/ExtensionMethods/ColorMineExtensions.cs b/Software/Visual_Studio/Tango.BL/ExtensionMethods/ColorMineExtensions.cs
new file mode 100644
index 000000000..de8c7f69b
--- /dev/null
+++ b/Software/Visual_Studio/Tango.BL/ExtensionMethods/ColorMineExtensions.cs
@@ -0,0 +1,29 @@
+using ColorMine.ColorSpaces;
+using Colourful;
+using Colourful.Conversion;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+public static class ColorMineExtensions
+{
+ public static Rgb ToRgbUsingColorful(this Lab lab)
+ {
+ LabColor labColor = new LabColor(lab.L, lab.A, lab.B);
+ var converter = new ColourfulConverter { WhitePoint = Illuminants.D65 };
+ var output = converter.ToRGB(labColor);
+ var color = output.ToColor();
+ return new Rgb(color.R, color.G, color.B);
+ }
+
+ public static Lab ToLabUsingColorful(this Rgb rgb)
+ {
+ RGBColor rgbColor = RGBColor.FromRGB8bit((byte)rgb.R, (byte)rgb.G, (byte)rgb.B);
+ var converter = new ColourfulConverter { WhitePoint = Illuminants.D65 };
+ var output = converter.ToLab(rgbColor);
+ return new Lab(output.L, output.a, output.b);
+ }
+}
+
diff --git a/Software/Visual_Studio/Tango.BL/Tango.BL.csproj b/Software/Visual_Studio/Tango.BL/Tango.BL.csproj
index b036df6dd..b7341d589 100644
--- a/Software/Visual_Studio/Tango.BL/Tango.BL.csproj
+++ b/Software/Visual_Studio/Tango.BL/Tango.BL.csproj
@@ -450,6 +450,7 @@
<Compile Include="Enumerations\TangoUpdateStatuses.cs" />
<Compile Include="Enumerations\RmlQualifications.cs" />
<Compile Include="ExtensionMethods\ColorCatalogItemsExtensions.cs" />
+ <Compile Include="ExtensionMethods\ColorMineExtensions.cs" />
<Compile Include="Helpers\EventTypeTextConverter.cs" />
<Compile Include="Helpers\SegmentsCsvHelper.cs" />
<Compile Include="IObservableEntityDTO.cs" />
@@ -622,6 +623,10 @@
</None>
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\..\..\..\..\..\Users\Roy\Desktop\Colourful-master\src\Colourful\Colourful.csproj">
+ <Project>{1254c6b8-dedb-45f9-a880-04556ea1aa1c}</Project>
+ <Name>Colourful</Name>
+ </ProjectReference>
<ProjectReference Include="..\DataStore\Tango.DataStore\Tango.DataStore.csproj">
<Project>{e0364dfa-0721-4637-9d32-9d22aac109d6}</Project>
<Name>Tango.DataStore</Name>
@@ -674,7 +679,7 @@
</Target>
<ProjectExtensions>
<VisualStudio>
- <UserProperties BuildVersion_StartDate="2000/1/1" BuildVersion_UseGlobalSettings="False" BuildVersion_BuildVersioningStyle="None.None.Increment.TimeStamp" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_AssemblyInfoFilename="Properties\AssemblyInfo.cs" />
+ <UserProperties BuildVersion_AssemblyInfoFilename="Properties\AssemblyInfo.cs" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_BuildVersioningStyle="None.None.Increment.TimeStamp" BuildVersion_UseGlobalSettings="False" BuildVersion_StartDate="2000/1/1" />
</VisualStudio>
</ProjectExtensions>
</Project> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.sln b/Software/Visual_Studio/Tango.sln
index cc1eaf6fd..997e6a361 100644
--- a/Software/Visual_Studio/Tango.sln
+++ b/Software/Visual_Studio/Tango.sln
@@ -469,6 +469,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.BitTypesGenerator.CLI
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.MachineStudio.MachineManager", "MachineStudio\Modules\Tango.MachineStudio.MachineManager\Tango.MachineStudio.MachineManager.csproj", "{6C784296-CCF9-469C-A7C1-4C13305E1203}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Colourful", "..\..\..\..\..\Users\Roy\Desktop\Colourful-master\src\Colourful\Colourful.csproj", "{1254C6B8-DEDB-45F9-A880-04556EA1AA1C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -4464,6 +4466,26 @@ Global
{6C784296-CCF9-469C-A7C1-4C13305E1203}.Release|x64.Build.0 = Release|Any CPU
{6C784296-CCF9-469C-A7C1-4C13305E1203}.Release|x86.ActiveCfg = Release|Any CPU
{6C784296-CCF9-469C-A7C1-4C13305E1203}.Release|x86.Build.0 = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|ARM.Build.0 = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|x64.Build.0 = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Debug|x86.Build.0 = Debug|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|ARM.ActiveCfg = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|ARM.Build.0 = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|ARM64.Build.0 = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|x64.ActiveCfg = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|x64.Build.0 = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|x86.ActiveCfg = Release|Any CPU
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -4630,14 +4652,15 @@ Global
{6B13E186-ADE2-4D97-9643-8132E00FC207} = {8336A702-9C49-4C9E-ADCC-1886A666D3BD}
{B356201F-F958-4AC9-BBEB-E4EAE9DA9EC6} = {5F6BBAA8-EAD0-4B18-97E5-55B4F56DD760}
{6C784296-CCF9-469C-A7C1-4C13305E1203} = {B2AF4F3F-2828-47C3-8F3E-A0EA0BD66FF8}
+ {1254C6B8-DEDB-45F9-A880-04556EA1AA1C} = {EC62BC9C-F2FE-4333-B7E4-110E38D43958}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {7986F7F4-A86A-4994-B1B6-0988D7F057B6}
- BuildVersion_BuildVersioningStyle = None.None.Increment.DeltaBaseYearDayOfYear
- BuildVersion_UpdateAssemblyVersion = True
- BuildVersion_UpdateFileVersion = False
- BuildVersion_StartDate = 2000/1/1
- BuildVersion_AssemblyInfoFilename = Properties\AssemblyInfo.cs
BuildVersion_UseGlobalSettings = False
+ BuildVersion_AssemblyInfoFilename = Properties\AssemblyInfo.cs
+ BuildVersion_StartDate = 2000/1/1
+ BuildVersion_UpdateFileVersion = False
+ BuildVersion_UpdateAssemblyVersion = True
+ BuildVersion_BuildVersioningStyle = None.None.Increment.DeltaBaseYearDayOfYear
+ SolutionGuid = {7986F7F4-A86A-4994-B1B6-0988D7F057B6}
EndGlobalSection
EndGlobal