From 593e0545f8e6f8bc7f63a92690652779283e12f2 Mon Sep 17 00:00:00 2001 From: Wacton Date: Mon, 21 Mar 2022 09:47:55 +0000 Subject: [PATCH] Add HSL support --- README.md | 14 +- Unicolour.Tests/ConfigurationTests.cs | 62 +++---- Unicolour.Tests/ContrastTests.cs | 2 +- Unicolour.Tests/ConversionTests.cs | 132 ++++++++------- Unicolour.Tests/DifferenceTests.cs | 2 +- Unicolour.Tests/EqualityTests.cs | 39 ++++- Unicolour.Tests/Factories/ColorMineFactory.cs | 99 +++++++++++ Unicolour.Tests/Factories/ColourfulFactory.cs | 55 ++++++ .../Factories/ITestColourFactory.cs | 24 +++ Unicolour.Tests/Factories/OpenCvCsvFactory.cs | 64 +++++++ Unicolour.Tests/Factories/OpenCvFactory.cs | 121 ++++++++++++++ Unicolour.Tests/Factories/SixLaborsFactory.cs | 111 ++++++++++++ Unicolour.Tests/InterpolateConfigTests.cs | 5 +- Unicolour.Tests/InterpolateHsbTests.cs | 1 - Unicolour.Tests/InterpolateHslTests.cs | 139 +++++++++++++++ Unicolour.Tests/InterpolateMonochromeTests.cs | 1 - Unicolour.Tests/InterpolateRgbTests.cs | 1 - Unicolour.Tests/Lookups/OpenCvColours.csv | 145 ---------------- Unicolour.Tests/Lookups/TestColour.cs | 15 -- Unicolour.Tests/Lookups/TestColours.cs | 85 ---------- Unicolour.Tests/OtherLibraryTests.cs | 158 ++++++++++-------- Unicolour.Tests/ParseHexTests.cs | 2 +- Unicolour.Tests/Unicolour.Tests.csproj | 4 +- Unicolour.Tests/Utils/AssertUtils.cs | 52 +++--- Unicolour.Tests/Utils/ColorMineUtils.cs | 56 ------- .../{Lookups => Utils}/ColourLimits.cs | 2 +- Unicolour.Tests/Utils/ColourfulUtils.cs | 40 ----- .../{Lookups => Utils}/NamedColours.csv | 0 Unicolour.Tests/Utils/OpenCvColours.csv | 145 ++++++++++++++++ Unicolour.Tests/Utils/OpenCvUtils.cs | 102 ----------- Unicolour.Tests/Utils/SixLaborsUtils.cs | 72 -------- Unicolour.Tests/Utils/TestColour.cs | 36 ++++ Unicolour.Tests/Utils/TestColours.cs | 66 ++++++++ Unicolour/Alpha.cs | 26 +-- Unicolour/ColourTuple.cs | 8 + Unicolour/Configuration.cs | 15 +- Unicolour/Conversion.cs | 52 ++++++ Unicolour/Formulas/HSB to HSL.png | Bin 0 -> 14097 bytes Unicolour/Formulas/HSL to HSB.png | Bin 0 -> 13888 bytes .../{RGB to HSB.png => RGB to HSB-HSL.png} | Bin Unicolour/Hsb.cs | 34 +--- Unicolour/Hsl.cs | 28 ++++ Unicolour/Interpolation.cs | 69 +++++--- Unicolour/Lab.cs | 32 +--- Unicolour/Rgb.cs | 38 +---- Unicolour/Unicolour.cs | 70 ++++++-- Unicolour/Unicolour.csproj | 6 +- Unicolour/WhitePoint.cs | 1 + Unicolour/Xyz.cs | 34 +--- 49 files changed, 1325 insertions(+), 940 deletions(-) create mode 100644 Unicolour.Tests/Factories/ColorMineFactory.cs create mode 100644 Unicolour.Tests/Factories/ColourfulFactory.cs create mode 100644 Unicolour.Tests/Factories/ITestColourFactory.cs create mode 100644 Unicolour.Tests/Factories/OpenCvCsvFactory.cs create mode 100644 Unicolour.Tests/Factories/OpenCvFactory.cs create mode 100644 Unicolour.Tests/Factories/SixLaborsFactory.cs create mode 100644 Unicolour.Tests/InterpolateHslTests.cs delete mode 100644 Unicolour.Tests/Lookups/OpenCvColours.csv delete mode 100644 Unicolour.Tests/Lookups/TestColour.cs delete mode 100644 Unicolour.Tests/Lookups/TestColours.cs delete mode 100644 Unicolour.Tests/Utils/ColorMineUtils.cs rename Unicolour.Tests/{Lookups => Utils}/ColourLimits.cs (97%) delete mode 100644 Unicolour.Tests/Utils/ColourfulUtils.cs rename Unicolour.Tests/{Lookups => Utils}/NamedColours.csv (100%) create mode 100644 Unicolour.Tests/Utils/OpenCvColours.csv delete mode 100644 Unicolour.Tests/Utils/OpenCvUtils.cs delete mode 100644 Unicolour.Tests/Utils/SixLaborsUtils.cs create mode 100644 Unicolour.Tests/Utils/TestColour.cs create mode 100644 Unicolour.Tests/Utils/TestColours.cs create mode 100644 Unicolour/ColourTuple.cs create mode 100644 Unicolour/Formulas/HSB to HSL.png create mode 100644 Unicolour/Formulas/HSL to HSB.png rename Unicolour/Formulas/{RGB to HSB.png => RGB to HSB-HSL.png} (100%) create mode 100644 Unicolour/Hsl.cs diff --git a/README.md b/README.md index 0cd9803e..ea427e98 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Unicolour is a small set of utilities for working with colour: A `Unicolour` encapsulates a single colour and its representation across different colour spaces. It supports: - RGB - HSB/HSV +- HSL - CIE XYZ - CIE LAB @@ -38,12 +39,14 @@ var unicolour = Unicolour.FromHex("#FF1493"); var unicolour = Unicolour.FromRgb255(255, 20, 147); var unicolour = Unicolour.FromRgb(1.0, 0.078, 0.576); var unicolour = Unicolour.FromHsb(327.6, 0.922, 1.0); +var unicolour = Unicolour.FromHsl(327.6, 1.0, 0.539); ``` 3. Get representation of colour in different colour spaces: ```c# var rgb = unicolour.Rgb; var hsb = unicolour.Hsb; +var hsl = unicolour.Hsl; var xyz = unicolour.Xyz; var lab = unicolour.Lab; ``` @@ -52,6 +55,7 @@ var lab = unicolour.Lab; ```c# var interpolated = unicolour1.InterpolateRgb(unicolour2, 0.5); var interpolated = unicolour1.InterpolateHsb(unicolour2, 0.5); +var interpolated = unicolour1.InterpolateHsl(unicolour2, 0.5); ``` 5. Compare colours: @@ -70,12 +74,12 @@ XYZ configuration only requires the reference white point. ```c# var config = new Configuration( - new(0.7347, 0.2653), // RGB: red chromaticity coordinates - new(0.1152, 0.8264), // RGB: green chromaticity coordinates - new(0.1566, 0.0177), // RGB: blue chromaticity coordinates + new(0.7347, 0.2653), // RGB red chromaticity coordinates + new(0.1152, 0.8264), // RGB green chromaticity coordinates + new(0.1566, 0.0177), // RGB blue chromaticity coordinates + value => Companding.InverseGamma(value, 2.2), // RGB inverse companding function WhitePoint.From(Illuminant.D50), // RGB white point - WhitePoint.From(Illuminant.D50), // XYZ white point - value => Companding.InverseGamma(value, 2.2)); // RGB inverse companding function + WhitePoint.From(Illuminant.D50)); // XYZ white point var unicolour = Unicolour.FromRgb(config, 255, 20, 147); ``` diff --git a/Unicolour.Tests/ConfigurationTests.cs b/Unicolour.Tests/ConfigurationTests.cs index 4d2560c7..39eb6c57 100644 --- a/Unicolour.Tests/ConfigurationTests.cs +++ b/Unicolour.Tests/ConfigurationTests.cs @@ -1,7 +1,7 @@ namespace Wacton.Unicolour.Tests; using NUnit.Framework; -using Wacton.Unicolour.Tests.Lookups; +using Wacton.Unicolour.Tests.Utils; public static class ConfigurationTests { @@ -43,9 +43,9 @@ public static void StandardRgbD65ToXyzD65() var unicolourWithConfig = Unicolour.FromRgb(Configuration.Default, 0.5, 0.25, 0.75); var expectedColour = new TestColour { - Xyz = (0.200757, 0.119618, 0.506757), - Lab = (41.1553, 51.4108, -56.4485), - // Luv = (41.1553, 16.3709, -86.7190) + Xyz = new(0.200757, 0.119618, 0.506757), + Lab = new(41.1553, 51.4108, -56.4485), + // Luv = new(41.1553, 16.3709, -86.7190) }; Assert.That(rgbToXyzMatrix.Data, Is.EqualTo(expectedMatrixA).Within(0.0005)); @@ -61,9 +61,9 @@ public static void StandardRgbD65ToXyzD50() Chromaticity.StandardRgbR, Chromaticity.StandardRgbG, Chromaticity.StandardRgbB, + Companding.InverseStandardRgb, WhitePoint.From(Illuminant.D65, Observer.Standard2), - WhitePoint.From(Illuminant.D50, Observer.Standard2), - Companding.InverseStandardRgb); + WhitePoint.From(Illuminant.D50, Observer.Standard2)); // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html var expectedMatrix = new[,] @@ -76,9 +76,9 @@ public static void StandardRgbD65ToXyzD50() var unicolour = Unicolour.FromRgb(configuration, 0.5, 0.25, 0.75); var expectedColour = new TestColour { - Xyz = (0.187691, 0.115771, 0.381093), - Lab = (40.5359, 46.0847, -57.1158), - // Luv = (40.5359, 18.7523, -78.2057) + Xyz = new(0.187691, 0.115771, 0.381093), + Lab = new(40.5359, 46.0847, -57.1158), + // Luv = new(40.5359, 18.7523, -78.2057) }; Assert.That(configuration.RgbToXyzMatrix.Data, Is.EqualTo(expectedMatrix).Within(0.0000001)); @@ -92,16 +92,14 @@ public static void AdobeRgbD65ToXyzD65() AdobeChromaticityR, AdobeChromaticityG, AdobeChromaticityB, - WhitePoint.From(Illuminant.D65, Observer.Standard2), - WhitePoint.From(Illuminant.D65, Observer.Standard2), - value => Companding.InverseGamma(value, 2.19921875)); + value => Companding.InverseGamma(value, 2.19921875), WhitePoint.From(Illuminant.D65, Observer.Standard2), WhitePoint.From(Illuminant.D65, Observer.Standard2)); var unicolour = Unicolour.FromRgb(configuration, 0.5, 0.25, 0.75); var expectedColour = new TestColour { - Xyz = (0.234243, 0.134410, 0.535559), - Lab = (43.4203, 57.3600, -55.4259), - // Luv = (43.4203, 25.4480, -87.3268) + Xyz = new(0.234243, 0.134410, 0.535559), + Lab = new(43.4203, 57.3600, -55.4259), + // Luv = new(43.4203, 25.4480, -87.3268) }; AssertColour(unicolour, expectedColour); @@ -114,16 +112,14 @@ public static void AdobeRgbD65ToXyzD50() AdobeChromaticityR, AdobeChromaticityG, AdobeChromaticityB, - WhitePoint.From(Illuminant.D65, Observer.Standard2), - WhitePoint.From(Illuminant.D50, Observer.Standard2), - value => Companding.InverseGamma(value, 2.19921875)); + value => Companding.InverseGamma(value, 2.19921875), WhitePoint.From(Illuminant.D65, Observer.Standard2), WhitePoint.From(Illuminant.D50, Observer.Standard2)); var unicolour = Unicolour.FromRgb(configuration, 0.5, 0.25, 0.75); var expectedColour = new TestColour { - Xyz = (0.221673, 0.130920, 0.402670), - Lab = (42.9015, 52.4152, -55.9013), - // Luv = (42.9015, 29.0751, -78.5576) + Xyz = new(0.221673, 0.130920, 0.402670), + Lab = new(42.9015, 52.4152, -55.9013), + // Luv = new(42.9015, 29.0751, -78.5576) }; AssertColour(unicolour, expectedColour); @@ -136,16 +132,14 @@ public static void WideGamutRgbD50ToXyzD65() WideGamutChromaticityR, WideGamutChromaticityG, WideGamutChromaticityB, - WhitePoint.From(Illuminant.D50, Observer.Standard2), - WhitePoint.From(Illuminant.D65, Observer.Standard2), - value => Companding.InverseGamma(value, 2.19921875)); + value => Companding.InverseGamma(value, 2.19921875), WhitePoint.From(Illuminant.D50, Observer.Standard2), WhitePoint.From(Illuminant.D65, Observer.Standard2)); var unicolour = Unicolour.FromRgb(configuration, 0.5, 0.25, 0.75); var expectedColour = new TestColour { - Xyz = (0.251993, 0.102404, 0.550393), - Lab = (38.2704, 87.2838, -65.7493), - // Luv = (38.2704, 47.3837, -99.6819) + Xyz = new(0.251993, 0.102404, 0.550393), + Lab = new(38.2704, 87.2838, -65.7493), + // Luv = new(38.2704, 47.3837, -99.6819) }; AssertColour(unicolour, expectedColour); @@ -158,16 +152,14 @@ public static void WideGamutRgbD50ToXyzD50() WideGamutChromaticityR, WideGamutChromaticityG, WideGamutChromaticityB, - WhitePoint.From(Illuminant.D50, Observer.Standard2), - WhitePoint.From(Illuminant.D50, Observer.Standard2), - value => Companding.InverseGamma(value, 2.19921875)); + value => Companding.InverseGamma(value, 2.19921875), WhitePoint.From(Illuminant.D50, Observer.Standard2), WhitePoint.From(Illuminant.D50, Observer.Standard2)); var unicolour = Unicolour.FromRgb(configuration, 0.5, 0.25, 0.75); var expectedColour = new TestColour { - Xyz = (0.238795, 0.099490, 0.413181), - Lab = (37.7508, 82.3084, -66.1402), - // Luv = (37.7508, 55.1488, -91.6044) + Xyz = new(0.238795, 0.099490, 0.413181), + Lab = new(37.7508, 82.3084, -66.1402), + // Luv = new(37.7508, 55.1488, -91.6044) }; AssertColour(unicolour, expectedColour); @@ -175,7 +167,7 @@ public static void WideGamutRgbD50ToXyzD50() private static void AssertColour(Unicolour unicolour, TestColour expected) { - Assert.That(unicolour.Xyz.Tuple, Is.EqualTo(expected.Xyz).Within(0.001)); - Assert.That(unicolour.Lab.Tuple, Is.EqualTo(expected.Lab).Within(0.05)); + AssertUtils.AssertColourTuple(unicolour.Xyz.Tuple, expected.Xyz!, 0.001); + AssertUtils.AssertColourTuple(unicolour.Lab.Tuple, expected.Lab!, 0.05); } } \ No newline at end of file diff --git a/Unicolour.Tests/ContrastTests.cs b/Unicolour.Tests/ContrastTests.cs index 29379739..5d04caae 100644 --- a/Unicolour.Tests/ContrastTests.cs +++ b/Unicolour.Tests/ContrastTests.cs @@ -2,7 +2,7 @@ using System; using NUnit.Framework; -using Wacton.Unicolour.Tests.Lookups; +using Wacton.Unicolour.Tests.Utils; public static class ContrastTests { diff --git a/Unicolour.Tests/ConversionTests.cs b/Unicolour.Tests/ConversionTests.cs index 60f46bd6..5893bf2b 100644 --- a/Unicolour.Tests/ConversionTests.cs +++ b/Unicolour.Tests/ConversionTests.cs @@ -3,103 +3,113 @@ namespace Wacton.Unicolour.Tests; using System; using System.Drawing; using NUnit.Framework; -using Wacton.Unicolour; -using Wacton.Unicolour.Tests.Lookups; using Wacton.Unicolour.Tests.Utils; public class ConversionTests { private const double RgbTolerance = 0.00000000001; private const double HsbTolerance = 0.00000001; + private const double HslTolerance = 0.00000001; [Test] - public void NamedRgbMatchesNamedHsb() => AssertUtils.AssertNamedColours(AssertRgbToHsbConversion); + public void NamedColoursMatchRgbConversion() => AssertUtils.AssertNamedColours(AssertRgbConversion); [Test] - public void RgbSameAfterUnconversion() + public void RgbSameAfterDeconversion() { - AssertUtils.AssertNamedColours(AssertRgbUnconversion); - AssertUtils.AssertRandomRgbColours(AssertRgbUnconversion); - AssertUtils.AssertRandomRgb255Colours(AssertRgbUnconversion); + AssertUtils.AssertNamedColours(AssertRgbDeconversion); + AssertUtils.AssertRandomRgbColours(AssertRgbDeconversion); + AssertUtils.AssertRandomRgb255Colours(AssertRgb255Deconversion); } [Test] - public void HsbSameAfterUnconversion() + public void HsbSameAfterDeconversion() { - AssertUtils.AssertNamedColours(AssertHsbUnconversion); - AssertUtils.AssertRandomHsbColours(AssertHsbUnconversion); + AssertUtils.AssertNamedColours(AssertHsbDeconversion); + AssertUtils.AssertRandomHsbColours(AssertHsbDeconversion); } - private static void AssertRgbToHsbConversion(TestColour namedColour) + [Test] + public void HslSameAfterDeconversion() + { + AssertUtils.AssertNamedColours(AssertHslDeconversion); + AssertUtils.AssertRandomHslColours(AssertHslDeconversion); + } + + private static void AssertRgbConversion(TestColour namedColour) { var systemColour = ColorTranslator.FromHtml(namedColour.Hex!); var rgb = new Rgb(systemColour.R / 255.0, systemColour.G / 255.0, systemColour.B / 255.0, Configuration.Default); var hsb = Conversion.RgbToHsb(rgb); + var hsl = Conversion.RgbToHsl(rgb); + var expectedRoundedHsb = namedColour.Hsb; + var expectedRoundedHsl = namedColour.Hsl; - Assert.That(Math.Round(hsb.H), Is.EqualTo(expectedRoundedHsb.Value.h)); - Assert.That(Math.Round(hsb.S, 2), Is.EqualTo(expectedRoundedHsb.Value.s)); - Assert.That(Math.Round(hsb.B, 2), Is.EqualTo(expectedRoundedHsb.Value.b)); - } - - private static void AssertRgbUnconversion(TestColour namedColour) - { - var systemColour = ColorTranslator.FromHtml(namedColour.Hex!); - var originalRgb = new Rgb(systemColour.R / 255.0, systemColour.G / 255.0, systemColour.B / 255.0, Configuration.Default); - AssertRgbUnconversion(originalRgb); - } - - private static void AssertRgbUnconversion(int r, int g, int b) - { - var originalRgb = new Rgb(r / 255.0, g / 255.0, b / 255.0, Configuration.Default); - AssertRgbUnconversion(originalRgb); - } - - private static void AssertRgbUnconversion(double r, double g, double b) - { - var originalRgb = new Rgb(r, g, b, Configuration.Default); - AssertRgbUnconversion(originalRgb); + Assert.That(Math.Round(hsb.H), Is.EqualTo(expectedRoundedHsb!.First), namedColour.Name!); + Assert.That(Math.Round(hsb.S, 2), Is.EqualTo(expectedRoundedHsb.Second), namedColour.Name!); + Assert.That(Math.Round(hsb.B, 2), Is.EqualTo(expectedRoundedHsb.Third), namedColour.Name!); + + // within 0.02 because it seems like some of wikipedia's HSL values have questionable rounding... + Assert.That(Math.Round(hsl.H), Is.EqualTo(expectedRoundedHsl!.First), namedColour.Name!); + Assert.That(Math.Round(hsl.S, 2), Is.EqualTo(expectedRoundedHsl.Second).Within(0.02), namedColour.Name!); + Assert.That(Math.Round(hsl.L, 2), Is.EqualTo(expectedRoundedHsl.Third).Within(0.02), namedColour.Name!); } - - private static void AssertRgbUnconversion(Rgb originalRgb) + + private static void AssertRgbDeconversion(TestColour namedColour) => AssertRgbDeconversion(GetRgbTupleFromHex(namedColour.Hex!)); + private static void AssertRgb255Deconversion(ColourTuple tuple) => AssertRgbDeconversion(GetNormalisedRgb255Tuple(tuple)); + private static void AssertRgbDeconversion(ColourTuple tuple) => AssertRgbDeconversion(new Rgb(tuple.First, tuple.Second, tuple.Third, Configuration.Default)); + private static void AssertRgbDeconversion(Rgb original) { - var convertedRgb = Conversion.HsbToRgb(Conversion.RgbToHsb(originalRgb), Configuration.Default); + var deconverted = Conversion.HsbToRgb(Conversion.RgbToHsb(original), Configuration.Default); - Assert.That(convertedRgb.R, Is.EqualTo(originalRgb.R).Within(RgbTolerance)); - Assert.That(convertedRgb.G, Is.EqualTo(originalRgb.G).Within(RgbTolerance)); - Assert.That(convertedRgb.B, Is.EqualTo(originalRgb.B).Within(RgbTolerance)); - Assert.That(convertedRgb.Tuple, Is.EqualTo(originalRgb.Tuple).Within(RgbTolerance)); + Assert.That(deconverted.R, Is.EqualTo(original.R).Within(RgbTolerance)); + Assert.That(deconverted.G, Is.EqualTo(original.G).Within(RgbTolerance)); + Assert.That(deconverted.B, Is.EqualTo(original.B).Within(RgbTolerance)); + AssertUtils.AssertColourTuple(deconverted.Tuple, original.Tuple, RgbTolerance); - Assert.That(convertedRgb.RLinear, Is.EqualTo(originalRgb.RLinear).Within(RgbTolerance)); - Assert.That(convertedRgb.GLinear, Is.EqualTo(originalRgb.GLinear).Within(RgbTolerance)); - Assert.That(convertedRgb.BLinear, Is.EqualTo(originalRgb.BLinear).Within(RgbTolerance)); - Assert.That(convertedRgb.TupleLinear, Is.EqualTo(originalRgb.TupleLinear).Within(RgbTolerance)); + Assert.That(deconverted.RLinear, Is.EqualTo(original.RLinear).Within(RgbTolerance)); + Assert.That(deconverted.GLinear, Is.EqualTo(original.GLinear).Within(RgbTolerance)); + Assert.That(deconverted.BLinear, Is.EqualTo(original.BLinear).Within(RgbTolerance)); + AssertUtils.AssertColourTuple(deconverted.TupleLinear, original.TupleLinear, RgbTolerance); - Assert.That(convertedRgb.R255, Is.EqualTo(originalRgb.R255)); - Assert.That(convertedRgb.G255, Is.EqualTo(originalRgb.G255)); - Assert.That(convertedRgb.B255, Is.EqualTo(originalRgb.B255)); - Assert.That(convertedRgb.Tuple255, Is.EqualTo(originalRgb.Tuple255)); + Assert.That(deconverted.R255, Is.EqualTo(original.R255)); + Assert.That(deconverted.G255, Is.EqualTo(original.G255)); + Assert.That(deconverted.B255, Is.EqualTo(original.B255)); + AssertUtils.AssertColourTuple(deconverted.Tuple255, original.Tuple255, RgbTolerance); + } + + private static void AssertHsbDeconversion(TestColour namedColour) => AssertHsbDeconversion(namedColour.Hsb!); + private static void AssertHsbDeconversion(ColourTuple tuple) => AssertHsbDeconversion(new Hsb(tuple.First, tuple.Second, tuple.Third)); + private static void AssertHsbDeconversion(Hsb original) + { + var deconverted = Conversion.RgbToHsb(Conversion.HsbToRgb(original, Configuration.Default)); + Assert.That(deconverted.H, Is.EqualTo(original.H).Within(HsbTolerance)); + Assert.That(deconverted.S, Is.EqualTo(original.S).Within(HsbTolerance)); + Assert.That(deconverted.B, Is.EqualTo(original.B).Within(HsbTolerance)); + AssertUtils.AssertColourTuple(deconverted.Tuple, original.Tuple, HsbTolerance, true); } - private static void AssertHsbUnconversion(TestColour namedColour) + private static void AssertHslDeconversion(TestColour namedColour) => AssertHslDeconversion(namedColour.Hsl!); + private static void AssertHslDeconversion(ColourTuple tuple) => AssertHslDeconversion(new Hsl(tuple.First, tuple.Second, tuple.Third)); + private static void AssertHslDeconversion(Hsl original) { - var (h, s, b) = namedColour.Hsb.Value; - var originalHsb = new Hsb(h, s, b); - AssertHsbUnconversion(originalHsb); + var deconverted = Conversion.RgbToHsl(Conversion.HsbToRgb(Conversion.HslToHsb(original), Configuration.Default)); + Assert.That(deconverted.H, Is.EqualTo(original.H).Within(HslTolerance)); + Assert.That(deconverted.S, Is.EqualTo(original.S).Within(HslTolerance)); + Assert.That(deconverted.L, Is.EqualTo(original.L).Within(HslTolerance)); + AssertUtils.AssertColourTuple(deconverted.Tuple, original.Tuple, HslTolerance, true); } - private static void AssertHsbUnconversion(double h, double s, double b) + private static ColourTuple GetRgbTupleFromHex(string hex) { - var originalHsb = new Hsb(h, s, b); - AssertHsbUnconversion(originalHsb); + var (r255, g255, b255, _) = Wacton.Unicolour.Utils.ParseColourHex(hex); + return new(r255 / 255.0, g255 / 255.0, b255 / 255.0); } - private static void AssertHsbUnconversion(Hsb originalHsb) + private static ColourTuple GetNormalisedRgb255Tuple(ColourTuple tuple) { - var convertedHsb = Conversion.RgbToHsb(Conversion.HsbToRgb(originalHsb, Configuration.Default)); - Assert.That(convertedHsb.H, Is.EqualTo(originalHsb.H).Within(HsbTolerance)); - Assert.That(convertedHsb.S, Is.EqualTo(originalHsb.S).Within(HsbTolerance)); - Assert.That(convertedHsb.B, Is.EqualTo(originalHsb.B).Within(HsbTolerance)); - Assert.That(convertedHsb.Tuple, Is.EqualTo(originalHsb.Tuple).Within(HsbTolerance)); + var (r255, g255, b255) = tuple; + return new(r255 / 255.0, g255 / 255.0, b255 / 255.0); } } \ No newline at end of file diff --git a/Unicolour.Tests/DifferenceTests.cs b/Unicolour.Tests/DifferenceTests.cs index 6eec128b..0294375f 100644 --- a/Unicolour.Tests/DifferenceTests.cs +++ b/Unicolour.Tests/DifferenceTests.cs @@ -2,7 +2,7 @@ using System; using NUnit.Framework; -using Wacton.Unicolour.Tests.Lookups; +using Wacton.Unicolour.Tests.Utils; public static class DifferenceTests { diff --git a/Unicolour.Tests/EqualityTests.cs b/Unicolour.Tests/EqualityTests.cs index 5372c8e6..9ac51161 100644 --- a/Unicolour.Tests/EqualityTests.cs +++ b/Unicolour.Tests/EqualityTests.cs @@ -2,7 +2,6 @@ namespace Wacton.Unicolour.Tests; using System; using NUnit.Framework; -using Wacton.Unicolour; public class EqualityTests { @@ -24,6 +23,14 @@ public void EqualHsbGivesEqualObjects() AssertUnicoloursEqual(unicolour1, unicolour2); } + [Test] + public void EqualHslGivesEqualObjects() + { + var unicolour1 = GetRandomHslUnicolour(); + var unicolour2 = Unicolour.FromHsl(unicolour1.Hsl.H, unicolour1.Hsl.S, unicolour1.Hsl.L, unicolour1.Alpha.A); + AssertUnicoloursEqual(unicolour1, unicolour2); + } + [Test] public void NotEqualRgbGivesNotEqualObjects() { @@ -48,6 +55,18 @@ public void NotEqualHsbGivesNotEqualObjects() AssertUnicoloursNotEqual(unicolour1, unicolour2); } + [Test] + public void NotEqualHslGivesNotEqualObjects() + { + var unicolour1 = GetRandomHslUnicolour(); + var unicolour2 = Unicolour.FromHsl( + (unicolour1.Hsl.H + 0.1).Modulo(360), + (unicolour1.Hsl.S + 0.1).Modulo(1), + (unicolour1.Hsl.L + 0.1).Modulo(1), + (unicolour1.Alpha.A + 0.1).Modulo(1)); + AssertUnicoloursNotEqual(unicolour1, unicolour2); + } + [Test] public void DifferentConfigurationObjects() { @@ -55,17 +74,17 @@ public void DifferentConfigurationObjects() Chromaticity.StandardRgbR, new Chromaticity(0.25, 0.75), new Chromaticity(0.5, 0.5), - new WhitePoint(0.9, 1.0, 1.1), - new WhitePoint(0.95, 1.0, 1.05), - Companding.InverseStandardRgb); + Companding.InverseStandardRgb, + new WhitePoint(0.9, 1.0, 1.1), + new WhitePoint(0.95, 1.0, 1.05)); var config2 = new Configuration( Chromaticity.StandardRgbR, new Chromaticity(0.75, 0.25), new Chromaticity(0.5, 0.5), - new WhitePoint(0.9, 1.0, 1.1), - new WhitePoint(0.95001, 1.0001, 1.05001), - Companding.InverseStandardRgb); + Companding.InverseStandardRgb, + new WhitePoint(0.9, 1.0, 1.1), + new WhitePoint(0.95001, 1.0001, 1.05001)); AssertEqual(config1.ChromaticityR, config2.ChromaticityR); AssertNotEqual(config1.ChromaticityG, config2.ChromaticityG); @@ -88,6 +107,12 @@ private static Unicolour GetRandomHsbUnicolour() return Unicolour.FromHsb(h, s, b, a); } + private static Unicolour GetRandomHslUnicolour() + { + var (h, s, l, a) = (Random.NextDouble(), Random.NextDouble(), Random.NextDouble(), Random.NextDouble()); + return Unicolour.FromHsl(h, s, l, a); + } + private static void AssertUnicoloursEqual(Unicolour unicolour1, Unicolour unicolour2) { AssertEqual(unicolour1.Rgb, unicolour2.Rgb); diff --git a/Unicolour.Tests/Factories/ColorMineFactory.cs b/Unicolour.Tests/Factories/ColorMineFactory.cs new file mode 100644 index 00000000..de4ceb8e --- /dev/null +++ b/Unicolour.Tests/Factories/ColorMineFactory.cs @@ -0,0 +1,99 @@ +namespace Wacton.Unicolour.Tests.Factories; + +using System; +using System.Collections.Generic; +using Wacton.Unicolour.Tests.Utils; +using ColorMineRgb = ColorMine.ColorSpaces.Rgb; +using ColorMineHsb = ColorMine.ColorSpaces.Hsb; +using ColorMineHsl = ColorMine.ColorSpaces.Hsl; +using ColorMineXyz = ColorMine.ColorSpaces.Xyz; +using ColorMineLab = ColorMine.ColorSpaces.Lab; + +internal class ColorMineFactory : ITestColourFactory +{ + /* + * ColorMine doesn't expose linear RGB and does a bad job of converting to HSL + */ + private static readonly Tolerances BaseTolerances = new() { Rgb = 0.00000000001, Hsb = 0.0000005, Hsl = 0.0125, Xyz = 0.0005, Lab = 0.05 }; + private static readonly Tolerances FromHslTolerances = BaseTolerances with { Rgb = 0.0005, Hsb = 0.00005 }; + + public TestColour FromRgb(double r, double g, double b, string name) + { + var r255 = (int)Math.Round(r * 255.0); + var g255 = (int)Math.Round(g * 255.0); + var b255 = (int)Math.Round(b * 255.0); + return FromRgb255(r255, g255, b255, name); + } + + public TestColour FromRgb255(int r255, int g255, int b255, string name) + { + var rgb = new ColorMineRgb { R = r255, G = g255, B = b255 }; + var hsb = rgb.To(); + var hsl = rgb.To(); + var xyz = rgb.To(); + var lab = rgb.To(); + return Create(name, rgb, hsb, hsl, xyz, lab, BaseTolerances); + } + + public TestColour FromHsb(double h, double s, double b, string name) + { + var hsb = new ColorMineHsb {H = h, S = s, B = b}; + var rgb = hsb.To(); + var hsl = hsb.To(); + var xyz = hsb.To(); + var lab = hsb.To(); + return Create(name, rgb, hsb, hsl, xyz, lab, BaseTolerances); + } + + // for some reason HSL uses 0-100 despite HSB using 0-1 + public TestColour FromHsl(double h, double s, double l, string name) + { + var hsl = new ColorMineHsl {H = h, S = s * 100, L = l * 100}; + var rgb = hsl.To(); + var hsb = hsl.To(); + var xyz = hsl.To(); + var lab = hsl.To(); + return Create(name, rgb, hsb, hsl, xyz, lab, FromHslTolerances); + } + + private static TestColour Create(string name, ColorMineRgb rgb, ColorMineHsb hsb, ColorMineHsl hsl, ColorMineXyz xyz, ColorMineLab lab, Tolerances tolerances) + { + var hueExclusions = new List(); + if (HasInconsistentHue(hsb, hsl)) hueExclusions.Add("ColorMine converts via RGB and loses hue when greyscale"); + if (HasRgbTruncationError(rgb)) hueExclusions.Add("ColorMine converts via RGB and has RGB truncation errors"); + if (HasLostSaturationFromRounding(hsb, hsl)) hueExclusions.Add("ColorMine rounds lightness to 0.0 and loses saturation"); + + return new TestColour + { + Name = name, + Rgb = new(rgb.R / 255.0, rgb.G / 255.0, rgb.B / 255.0), + Hsb = new(hsb.H, hsb.S, hsb.B), + Hsl = new(hsl.H, hsl.S / 100.0, hsl.L / 100.0), + Xyz = new(xyz.X / 100.0, xyz.Y / 100.0, xyz.Z / 100.0), + Lab = new(lab.L, lab.A, lab.B), + Tolerances = tolerances, + ExcludeFromHueBasedTestReasons = hueExclusions + }; + } + + private static bool HasInconsistentHue(ColorMineHsb hsb, ColorMineHsl hsl) => Math.Abs(hsb.H - hsl.H) > 0.01; + private static bool HasRgbTruncationError(ColorMineRgb rgb) => HasTruncationError(rgb.R) || HasTruncationError(rgb.G) || HasTruncationError(rgb.B); + private static bool HasLostSaturationFromRounding(ColorMineHsb hsl, ColorMineHsl hsb) => hsl.S > 0.0 && hsb.S == 0.0 || hsb.S > 0.0 && hsl.S == 0.0; + + private static bool HasTruncationError(double value) + { + // this is what ColorMine does to RGB values + var truncated = (int) value; + + // even if the value has been "barely" truncated + // truncation to zero totally changes the outcome + if (value > 0 && truncated == 0) + { + return true; + } + + // somewhat arbitrary as to how much truncation is too much + // but generally, any early truncation will introduce errors + return Math.Abs(truncated - value) > 0.1; + } +} \ No newline at end of file diff --git a/Unicolour.Tests/Factories/ColourfulFactory.cs b/Unicolour.Tests/Factories/ColourfulFactory.cs new file mode 100644 index 00000000..dc15f1c2 --- /dev/null +++ b/Unicolour.Tests/Factories/ColourfulFactory.cs @@ -0,0 +1,55 @@ +namespace Wacton.Unicolour.Tests.Factories; + +using Colourful; +using Wacton.Unicolour.Tests.Utils; +using ColourfulRgb = Colourful.RGBColor; +using ColourfulRgbLinear = Colourful.LinearRGBColor; +using ColourfulXyz = Colourful.XYZColor; +using ColourfulLab = Colourful.LabColor; +using ColourfulIlluminants = Colourful.Illuminants; + +internal class ColourfulFactory : ITestColourFactory +{ + /* + * Colourful doesn't support HSB / HSL + */ + private static readonly Tolerances Tolerances = new() { Rgb = 0.00000000001, RgbLinear = 0.00000000001, Xyz = 0.00000000001, Lab = 0.0000005 }; + + public TestColour FromRgb(double r, double g, double b, string name) + { + var rgbLinearConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.sRGB).ToLinearRGB().Build(); + var xyzConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.sRGB).ToXYZ(ColourfulIlluminants.D65).Build(); + var labConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.sRGB).ToLab(ColourfulIlluminants.D65).Build(); + + var rgb = new ColourfulRgb(r, g, b); + var rgbLinear = rgbLinearConverter.Convert(rgb); + var xyz = xyzConverter.Convert(rgb); + var lab = labConverter.Convert(rgb); + return Create(name, rgb, rgbLinear, xyz, lab); + } + + // Colourful does not support HSB + public TestColour FromHsb(double h, double s, double b, string name) + { + return new TestColour(); + } + + // Colourful does not support HSL + public TestColour FromHsl(double h, double s, double l, string name) + { + return new TestColour(); + } + + private static TestColour Create(string name, ColourfulRgb rgb, ColourfulRgbLinear rgbLinear, ColourfulXyz xyz, ColourfulLab lab) + { + return new TestColour + { + Name = name, + Rgb = new(rgb.R, rgb.G, rgb.B), + RgbLinear = new(rgbLinear.R, rgbLinear.G, rgbLinear.B), + Xyz = new(xyz.X, xyz.Y, xyz.Z), + Lab = new(lab.L, lab.a, lab.b), + Tolerances = Tolerances + }; + } +} \ No newline at end of file diff --git a/Unicolour.Tests/Factories/ITestColourFactory.cs b/Unicolour.Tests/Factories/ITestColourFactory.cs new file mode 100644 index 00000000..b77e5adc --- /dev/null +++ b/Unicolour.Tests/Factories/ITestColourFactory.cs @@ -0,0 +1,24 @@ +namespace Wacton.Unicolour.Tests.Factories; + +using Wacton.Unicolour.Tests.Utils; + +internal interface ITestColourFactory +{ + TestColour FromRgb255(int r255, int g255, int b255) => FromRgb255(r255, g255, b255, $"RGB [{r255:000} {g255:000} {b255:000}]"); + TestColour FromRgb255(int r255, int g255, int b255, string name) + { + var r = r255 / 255.0; + var g = g255 / 255.0; + var b = b255 / 255.0; + return FromRgb(r, g, b, name); + } + + TestColour FromRgb(double r, double g, double b) => FromRgb(r, g, b, $"RGB [{r} {g} {b}]"); + TestColour FromRgb(double r, double g, double b, string name); + + TestColour FromHsb(double h, double s, double b) => FromHsb(h, s, b, $"HSB [{h} {s} {b}]"); + TestColour FromHsb(double h, double s, double b, string name); + + TestColour FromHsl(double h, double s, double l) => FromHsl(h, s, l, $"HSL [{h} {s} {l}]"); + TestColour FromHsl(double h, double s, double l, string name); +} \ No newline at end of file diff --git a/Unicolour.Tests/Factories/OpenCvCsvFactory.cs b/Unicolour.Tests/Factories/OpenCvCsvFactory.cs new file mode 100644 index 00000000..568456a1 --- /dev/null +++ b/Unicolour.Tests/Factories/OpenCvCsvFactory.cs @@ -0,0 +1,64 @@ +namespace Wacton.Unicolour.Tests.Factories; + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using Wacton.Unicolour.Tests.Utils; + +internal static class OpenCvCsvFactory +{ + private static readonly List ColoursFromCsv; + + static OpenCvCsvFactory() + { + ColoursFromCsv = File.ReadAllLines(Path.Combine("Utils", "OpenCvColours.csv")).Select(FromCsvRow).ToList(); + } + + public static TestColour FromName(string name) => ColoursFromCsv.Single(x => x.Name == name); + private static TestColour FromCsvRow(string csvRow) + { + var items = csvRow.Split(",", StringSplitOptions.TrimEntries); + return new TestColour + { + Name = items[0], + Rgb = new(FromText(items[1]), FromText(items[2]), FromText(items[3])), + Hsb = new(FromText(items[4]), FromText(items[5]), FromText(items[6])), + Hsl = new(FromText(items[7]), FromText(items[8]), FromText(items[9])), + Xyz = new(FromText(items[10]), FromText(items[11]), FromText(items[12])), + Lab = new(FromText(items[13]), FromText(items[14]), FromText(items[15])), + Tolerances = OpenCvFactory.Tolerances + }; + } + + private static double FromText(string text) => double.Parse(text); + + private static void GenerateCsvFile() + { + var rows = new List(); + foreach (var namedColour in TestColours.NamedColours) + { + var systemColour = ColorTranslator.FromHtml(namedColour.Hex!); + var (r255, g255, b255) = (systemColour.R, systemColour.G, systemColour.B); + + ITestColourFactory testColourFactory = new OpenCvFactory(); + var testColour = testColourFactory.FromRgb255(r255, g255, b255, namedColour.Name!); + + string Stringify(double value) => value.ToString(CultureInfo.InvariantCulture); + var row = new List + { + testColour.Name!, + Stringify(testColour.Rgb!.First), Stringify(testColour.Rgb.Second), Stringify(testColour.Rgb.Third), + Stringify(testColour.Hsb!.First), Stringify(testColour.Hsb.Second), Stringify(testColour.Hsb.Third), + Stringify(testColour.Xyz!.First), Stringify(testColour.Xyz.Second), Stringify(testColour.Xyz.Third), + Stringify(testColour.Lab!.First), Stringify(testColour.Lab.Second), Stringify(testColour.Lab.Third) + }; + + rows.Add(string.Join(", ", row)); + } + + File.WriteAllLines("OpenCvColours.csv", rows); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/Factories/OpenCvFactory.cs b/Unicolour.Tests/Factories/OpenCvFactory.cs new file mode 100644 index 00000000..203a418d --- /dev/null +++ b/Unicolour.Tests/Factories/OpenCvFactory.cs @@ -0,0 +1,121 @@ +namespace Wacton.Unicolour.Tests.Factories; + +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using OpenCvSharp; +using Wacton.Unicolour.Tests.Utils; + +internal class OpenCvFactory : ITestColourFactory +{ + /* + * OpenCV doesn't expose linear RGB + * ----- + * at this point I'm pretty sure OpenCV doesn't calculate RGB -> LAB correctly + * since all other libraries and online tools calculate the same LAB as Unicolour + * the LAB test tolerances are so large it's only worth testing against for regression purposes + */ + public static readonly Tolerances Tolerances = new() {Rgb = 0.005, Hsb = 0.00005, Hsl = 0.00005, Xyz = 0.0005, Lab = 50.0}; + + public TestColour FromRgb(double r, double g, double b, string name) + { + var rLinear = Companding.InverseStandardRgb(r); + var gLinear = Companding.InverseStandardRgb(g); + var bLinear = Companding.InverseStandardRgb(b); + + // it appears that OpenCV's RGB -> XYZ and RGB -> LAB conversions + // expect to receive RGB values that have already undergone linear correction... + var rgb = GetInputVec((r, g, b)); + var hsb = GetConvertedVec((r, g, b), ColorConversionCodes.RGB2HSV_FULL); + var hls = GetConvertedVec((r, g, b), ColorConversionCodes.RGB2HLS_FULL); + var xyz = GetConvertedVec((rLinear, gLinear, bLinear), ColorConversionCodes.RGB2XYZ); + var lab = GetConvertedVec((rLinear, gLinear, bLinear), ColorConversionCodes.RGB2Lab); + + return new TestColour + { + Name = name, + Rgb = new(rgb.Item0, rgb.Item1, rgb.Item2), + Hsb = new(hsb.Item0, hsb.Item1, hsb.Item2), + Hsl = new(hls.Item0, hls.Item2, hls.Item1), + Xyz = new(xyz.Item0, xyz.Item1, xyz.Item2), + Lab = new(lab.Item0, lab.Item1, lab.Item2), + Tolerances = Tolerances + }; + } + + // OpenCV can only convert to RGB from HSB + public TestColour FromHsb(double h, double s, double b, string name) + { + var rgb = GetConvertedVec((h, s, b), ColorConversionCodes.HSV2RGB); + var hsb = GetInputVec((h, s, b)); + + return new TestColour + { + Name = name, + Rgb = new(rgb.Item0, rgb.Item1, rgb.Item2), + Hsb = new(hsb.Item0, hsb.Item1, hsb.Item2), + Tolerances = Tolerances + }; + } + + // OpenCV can only convert to RGB from HSL + public TestColour FromHsl(double h, double s, double l, string name) + { + var rgb = GetConvertedVec((h, l, s), ColorConversionCodes.HLS2RGB); + var hsl = GetInputVec((h, s, l)); + + return new TestColour + { + Name = name, + Rgb = new(rgb.Item0, rgb.Item1, rgb.Item2), + Hsl = new(hsl.Item0, hsl.Item1, hsl.Item2), + Tolerances = Tolerances + }; + } + + private static Vec3f GetInputVec((double, double, double) input) + { + var (i1, i2, i3) = input; + var mat = new Mat(1, 1, MatType.CV_32FC3, new Scalar(i1, i2, i3)); + return mat.Get(0, 0); + } + + private static Vec3f GetConvertedVec((double, double, double) input, ColorConversionCodes conversionCode) + { + var (i1, i2, i3) = input; + var matIn = new Mat(1, 1, MatType.CV_32FC3, new Scalar(i1, i2, i3)); + var matOut = new Mat(1, 1, MatType.CV_32FC3); + Cv2.CvtColor(matIn, matOut, conversionCode); + return matOut.Get(0, 0); + } + + public void GenerateCsvFile() + { + var rows = new List(); + foreach (var namedColour in TestColours.NamedColours) + { + var systemColour = ColorTranslator.FromHtml(namedColour.Hex!); + var (r255, g255, b255) = (systemColour.R, systemColour.G, systemColour.B); + var r = r255 / 255.0; + var g = g255 / 255.0; + var b = b255 / 255.0; + var testColour = FromRgb(r, g, b, namedColour.Name!); + + string Stringify(double value) => value.ToString(CultureInfo.InvariantCulture); + var row = new List + { + testColour.Name!, + Stringify(testColour.Rgb!.First), Stringify(testColour.Rgb.Second), Stringify(testColour.Rgb.Third), + Stringify(testColour.Hsb!.First), Stringify(testColour.Hsb.Second), Stringify(testColour.Hsb.Third), + Stringify(testColour.Hsl!.First), Stringify(testColour.Hsl.Second), Stringify(testColour.Hsl.Third), + Stringify(testColour.Xyz!.First), Stringify(testColour.Xyz.Second), Stringify(testColour.Xyz.Third), + Stringify(testColour.Lab!.First), Stringify(testColour.Lab.Second), Stringify(testColour.Lab.Third) + }; + + rows.Add(string.Join(", ", row)); + } + + File.WriteAllLines("OpenCvColours.csv", rows); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/Factories/SixLaborsFactory.cs b/Unicolour.Tests/Factories/SixLaborsFactory.cs new file mode 100644 index 00000000..dccb3d70 --- /dev/null +++ b/Unicolour.Tests/Factories/SixLaborsFactory.cs @@ -0,0 +1,111 @@ +namespace Wacton.Unicolour.Tests.Factories; + +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Wacton.Unicolour.Tests.Utils; +using SixLaborsRgb = SixLabors.ImageSharp.ColorSpaces.Rgb; +using SixLaborsRgbLinear = SixLabors.ImageSharp.ColorSpaces.LinearRgb; +using SixLaborsHsb = SixLabors.ImageSharp.ColorSpaces.Hsv; +using SixLaborsHsl = SixLabors.ImageSharp.ColorSpaces.Hsl; +using SixLaborsXyz = SixLabors.ImageSharp.ColorSpaces.CieXyz; +using SixLaborsLab = SixLabors.ImageSharp.ColorSpaces.CieLab; +using SixLaborsIlluminants = SixLabors.ImageSharp.ColorSpaces.Illuminants; + +internal class SixLaborsFactory : ITestColourFactory +{ + /* + * SixLabors doesn't seem to do a great job of converting to LAB + * and does a poor job of converting from decimal RGB -> HSB / HSL + */ + private static readonly Tolerances BaseTolerances = new() { Rgb = 0.001, RgbLinear = 0.005, Hsb = 0.000005, Hsl = 0.000005, Xyz = 0.005, Lab = 0.1 }; + private static readonly Tolerances FromRgbTolerances = BaseTolerances with { Hsb = 0.05, Hsl = 0.05 }; + private static readonly Tolerances FromHsbTolerances = BaseTolerances; + private static readonly Tolerances FromHslTolerances = BaseTolerances with { Rgb = 0.05, Lab = 0.15 }; + + private static readonly ColorSpaceConverter Converter = new(new ColorSpaceConverterOptions + { + TargetLabWhitePoint = SixLaborsIlluminants.D65 + }); + + public TestColour FromRgb255(int r255, int g255, int b255, string name) + { + var r = r255 / 255.0; + var g = g255 / 255.0; + var b = b255 / 255.0; + var rgb = new SixLaborsRgb((float) r, (float) g, (float) b, RgbWorkingSpaces.SRgb); + return FromRgb(rgb, name, BaseTolerances); + } + + public TestColour FromRgb(double r, double g, double b, string name) + { + var rgb = new SixLaborsRgb((float) r, (float) g, (float) b, RgbWorkingSpaces.SRgb); + return FromRgb(rgb, name, FromRgbTolerances); + } + + private static TestColour FromRgb(SixLaborsRgb rgb, string name, Tolerances tolerances) + { + var rgbLinear = Converter.ToLinearRgb(rgb); + var hsb = Converter.ToHsv(rgb); + var hsl = Converter.ToHsl(rgb); + var xyz = Converter.ToCieXyz(rgb); + var lab = Converter.ToCieLab(rgb); + return Create(name, rgb, rgbLinear, hsb, hsl, xyz, lab, tolerances); + } + + public TestColour FromHsb(double h, double s, double b, string name) + { + var hsb = new SixLaborsHsb((float) h, (float) s, (float) b); + var rgb = Converter.ToRgb(hsb); + var rgbLinear = Converter.ToLinearRgb(hsb); + var hsl = Converter.ToHsl(hsb); + var xyz = Converter.ToCieXyz(hsb); + var lab = Converter.ToCieLab(hsb); + return Create(name, rgb, rgbLinear, hsb, hsl, xyz, lab, FromHsbTolerances); + } + + public TestColour FromHsl(double h, double s, double l, string name) + { + var hsl = new SixLaborsHsl((float) h, (float) s, (float) l); + var rgb = Converter.ToRgb(hsl); + var rgbLinear = Converter.ToLinearRgb(hsl); + var hsb = Converter.ToHsv(hsl); + var xyz = Converter.ToCieXyz(hsl); + var lab = Converter.ToCieLab(hsl); + return Create(name, rgb, rgbLinear, hsb, hsl, xyz, lab, FromHslTolerances); + } + + private static TestColour Create(string name, SixLaborsRgb rgb, SixLaborsRgbLinear rgbLinear, SixLaborsHsb hsb, SixLaborsHsl hsl, SixLaborsXyz xyz, SixLaborsLab lab, Tolerances tolerances) + { + var hueExclusions = new List(); + if (HasInconsistentHue(hsb, hsl)) hueExclusions.Add("SixLabors converts via RGB and loses hue when greyscale"); + if (HasLowChroma(rgb)) hueExclusions.Add("SixLabors converts via RGB and does not handle low RGB chroma"); + + return new TestColour + { + Name = name, + Rgb = new(rgb.R, rgb.G, rgb.B), + RgbLinear = new(rgbLinear.R, rgbLinear.G, rgbLinear.B), + Hsb = new(hsb.H, hsb.S, hsb.V), + Hsl = new(hsl.H, hsl.S, hsl.L), + Xyz = new(xyz.X, xyz.Y, xyz.Z), + Lab = new(lab.L, lab.A, lab.B), + Tolerances = tolerances, + ExcludeFromHueBasedTestReasons = hueExclusions + }; + } + + private static bool HasInconsistentHue(SixLaborsHsb hsb, SixLaborsHsl hsl) => Math.Abs(hsb.H - hsl.H) > 0.01; + + private static bool HasLowChroma(SixLaborsRgb rgb) + { + // SixLabors can end up with extreme values (e.g. 0 or multiple of 60 hue) if the chroma is small + // (potentially due to using floats instead of doubles) + // which causes significant deviation from Unicolour calculations with very small values + var components = new[] {rgb.R, rgb.G, rgb.B}; + var chroma = components.Max() - components.Min(); + return chroma < 0.01; + } +} \ No newline at end of file diff --git a/Unicolour.Tests/InterpolateConfigTests.cs b/Unicolour.Tests/InterpolateConfigTests.cs index 6d515c25..f5697523 100644 --- a/Unicolour.Tests/InterpolateConfigTests.cs +++ b/Unicolour.Tests/InterpolateConfigTests.cs @@ -2,7 +2,6 @@ namespace Wacton.Unicolour.Tests; using System; using NUnit.Framework; -using Wacton.Unicolour; public class InterpolateConfigTests { @@ -47,9 +46,9 @@ private static Configuration GetConfig() Chromaticity.StandardRgbR, Chromaticity.StandardRgbG, Chromaticity.StandardRgbB, + Companding.InverseStandardRgb, WhitePoint.From(Illuminant.D65), - WhitePoint.From(Illuminant.D65), - Companding.InverseStandardRgb); + WhitePoint.From(Illuminant.D65)); } private static void AssertNoError(Unicolour unicolour1, Unicolour unicolour2) diff --git a/Unicolour.Tests/InterpolateHsbTests.cs b/Unicolour.Tests/InterpolateHsbTests.cs index 9a293c69..04ff71d9 100644 --- a/Unicolour.Tests/InterpolateHsbTests.cs +++ b/Unicolour.Tests/InterpolateHsbTests.cs @@ -2,7 +2,6 @@ namespace Wacton.Unicolour.Tests; using System; using NUnit.Framework; -using Wacton.Unicolour; public class InterpolateHsbTests { diff --git a/Unicolour.Tests/InterpolateHslTests.cs b/Unicolour.Tests/InterpolateHslTests.cs new file mode 100644 index 00000000..9af68fc0 --- /dev/null +++ b/Unicolour.Tests/InterpolateHslTests.cs @@ -0,0 +1,139 @@ +namespace Wacton.Unicolour.Tests; + +using System; +using NUnit.Framework; + +public class InterpolateHslTests +{ + [Test] + public void SameColour() + { + var unicolour1 = Unicolour.FromHsl(180, 0.25, 0.75, 0.5); + var unicolour2 = Unicolour.FromHsl(180, 0.25, 0.75, 0.5); + var interpolated1 = unicolour1.InterpolateHsl(unicolour2, 0.25); + var interpolated2 = unicolour2.InterpolateHsl(unicolour1, 0.75); + var interpolated3 = unicolour1.InterpolateHsl(unicolour2, 0.75); + var interpolated4 = unicolour2.InterpolateHsl(unicolour1, 0.25); + + AssertHsla(interpolated1, (180, 0.25, 0.75, 0.5)); + AssertHsla(interpolated2, (180, 0.25, 0.75, 0.5)); + AssertHsla(interpolated3, (180, 0.25, 0.75, 0.5)); + AssertHsla(interpolated4, (180, 0.25, 0.75, 0.5)); + } + + [Test] + public void EquidistantForward() + { + var unicolour1 = Unicolour.FromHsl(0, 0, 0, 0); + var unicolour2 = Unicolour.FromHsl(180, 1, 1); + var interpolated1 = unicolour1.InterpolateHsl(unicolour2, 0.5); + var interpolated2 = unicolour2.InterpolateHsl(unicolour1, 0.5); + + AssertHsla(interpolated1, (90, 0.5, 0.5, 0.5)); + AssertHsla(interpolated2, (90, 0.5, 0.5, 0.5)); + } + + [Test] + public void EquidistantBackward() + { + var unicolour1 = Unicolour.FromHsl(0, 0, 0, 0); + var unicolour2 = Unicolour.FromHsl(340, 0.5, 0.8, 0.2); + var interpolated1 = unicolour1.InterpolateHsl(unicolour2, 0.5); + var interpolated2 = unicolour2.InterpolateHsl(unicolour1, 0.5); + + AssertHsla(interpolated1, (350, 0.25, 0.4, 0.1)); + AssertHsla(interpolated2, (350, 0.25, 0.4, 0.1)); + } + + [Test] + public void CloserToEndColour() + { + var unicolour1 = Unicolour.FromHsl(0, 1, 0); + var unicolour2 = Unicolour.FromHsl(180, 0, 1, 0.5); + var interpolated1 = unicolour1.InterpolateHsl(unicolour2, 0.75); + var interpolated2 = unicolour2.InterpolateHsl(unicolour1, 0.75); + + AssertHsla(interpolated1, (135, 0.25, 0.75, 0.625)); + AssertHsla(interpolated2, (45, 0.75, 0.25, 0.875)); + } + + [Test] + public void CloserToStartColour() + { + var unicolour1 = Unicolour.FromHsl(0, 1, 0); + var unicolour2 = Unicolour.FromHsl(180, 0, 1, 0.5); + var interpolated1 = unicolour1.InterpolateHsl(unicolour2, 0.25); + var interpolated2 = unicolour2.InterpolateHsl(unicolour1, 0.25); + + AssertHsla(interpolated1, (45, 0.75, 0.25, 0.875)); + AssertHsla(interpolated2, (135, 0.25, 0.75, 0.625)); + } + + [Test] + public void BeyondEndColour() + { + var unicolour1 = Unicolour.FromHsl(0, 0.4, 0.6, 0.8); + var unicolour2 = Unicolour.FromHsl(90, 0.6, 0.4, 0.9); + var interpolated1 = unicolour1.InterpolateHsl(unicolour2, 1.5); + var interpolated2 = unicolour2.InterpolateHsl(unicolour1, 1.5); + + AssertHsla(interpolated1, (135, 0.7, 0.3, 0.95)); + AssertHsla(interpolated2, (315, 0.3, 0.7, 0.75)); + } + + [Test] + public void BeyondStartColour() + { + var unicolour1 = Unicolour.FromHsl(0, 0.4, 0.6, 0.8); + var unicolour2 = Unicolour.FromHsl(90, 0.6, 0.4, 0.9); + var interpolated1 = unicolour1.InterpolateHsl(unicolour2, -0.5); + var interpolated2 = unicolour2.InterpolateHsl(unicolour1, -0.5); + + AssertHsla(interpolated1, (315, 0.3, 0.7, 0.75)); + AssertHsla(interpolated2, (135, 0.7, 0.3, 0.95)); + } + + [Test] + public void BeyondColourToInvalidSaturation() + { + var unicolour1 = Unicolour.FromHsl(90, 0, 0.6, 0.8); + var unicolour2 = Unicolour.FromHsl(270, 1, 0.4, 0.9); + Assert.Catch(() => unicolour1.InterpolateHsl(unicolour2, 1.0001)); + Assert.Catch(() => unicolour2.InterpolateHsl(unicolour1, 1.0001)); + Assert.Catch(() => unicolour1.InterpolateHsl(unicolour2, -0.0001)); + Assert.Catch(() => unicolour2.InterpolateHsl(unicolour1, -0.0001)); + } + + [Test] + public void BeyondColourToInvalidBrightness() + { + var unicolour1 = Unicolour.FromHsl(90, 0.6, 0, 0.8); + var unicolour2 = Unicolour.FromHsl(270, 0.4, 1, 0.9); + Assert.Catch(() => unicolour1.InterpolateHsl(unicolour2, 1.0001)); + Assert.Catch(() => unicolour2.InterpolateHsl(unicolour1, 1.0001)); + Assert.Catch(() => unicolour1.InterpolateHsl(unicolour2, -0.0001)); + Assert.Catch(() => unicolour2.InterpolateHsl(unicolour1, -0.0001)); + } + + [Test] + public void BeyondColourToInvalidAlpha() + { + var unicolour1 = Unicolour.FromHsl(90, 0.6, 0.4, 0); + var unicolour2 = Unicolour.FromHsl(270, 0.4, 0.6); + Assert.Catch(() => unicolour1.InterpolateHsl(unicolour2, 1.0001)); + Assert.Catch(() => unicolour2.InterpolateHsl(unicolour1, 1.0001)); + Assert.Catch(() => unicolour1.InterpolateHsl(unicolour2, -0.0001)); + Assert.Catch(() => unicolour2.InterpolateHsl(unicolour1, -0.0001)); + } + + private static void AssertHsla(Unicolour unicolour, (double h, double s, double l, double a) expectedHsla) + { + var actualHsl = unicolour.Hsl; + var actualAlpha = unicolour.Alpha; + + Assert.That(actualHsl.H, Is.EqualTo(expectedHsla.h).Within(0.00000000005)); + Assert.That(actualHsl.S, Is.EqualTo(expectedHsla.s).Within(0.00000000005)); + Assert.That(actualHsl.L, Is.EqualTo(expectedHsla.l).Within(0.00000000005)); + Assert.That(actualAlpha.A, Is.EqualTo(expectedHsla.a).Within(0.00000000005)); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/InterpolateMonochromeTests.cs b/Unicolour.Tests/InterpolateMonochromeTests.cs index 04150836..0f7b2c8c 100644 --- a/Unicolour.Tests/InterpolateMonochromeTests.cs +++ b/Unicolour.Tests/InterpolateMonochromeTests.cs @@ -1,7 +1,6 @@ namespace Wacton.Unicolour.Tests; using NUnit.Framework; -using Wacton.Unicolour; public class InterpolateMonochromeTests { diff --git a/Unicolour.Tests/InterpolateRgbTests.cs b/Unicolour.Tests/InterpolateRgbTests.cs index 6337ea97..04d35b6a 100644 --- a/Unicolour.Tests/InterpolateRgbTests.cs +++ b/Unicolour.Tests/InterpolateRgbTests.cs @@ -2,7 +2,6 @@ namespace Wacton.Unicolour.Tests; using System; using NUnit.Framework; -using Wacton.Unicolour; public class InterpolateRgbTests { diff --git a/Unicolour.Tests/Lookups/OpenCvColours.csv b/Unicolour.Tests/Lookups/OpenCvColours.csv deleted file mode 100644 index 1cca9245..00000000 --- a/Unicolour.Tests/Lookups/OpenCvColours.csv +++ /dev/null @@ -1,145 +0,0 @@ -Alice Blue, 0.9411764740943909, 0.9725490212440491, 1, 208.00006103515625, 0.058823518455028534, 1, 0.8754762411117554, 0.9287939667701721, 1.0789587497711182, 93.682861328125, -2.484375, -9.5625 -Antique White, 0.9803921580314636, 0.9215686321258545, 0.843137264251709, 34.28568649291992, 0.13999997079372406, 0.9803921580314636, 0.8139658570289612, 0.8464831709861755, 0.7632243633270264, 86.553955078125, 5.5, 23.359375 -Aqua, 0, 1, 1, 180, 0.9999998807907104, 1, 0.5380030274391174, 0.7873290181159973, 1.0694199800491333, 91.11328125, -48.09375, -14.125 -Aquamarine, 0.49803921580314636, 1, 0.8313725590705872, 159.84375, 0.5019606947898865, 1, 0.5639011859893799, 0.807809591293335, 0.7489018440246582, 89.447021484375, -66.78125, 28.40625 -Azure, 0.9411764740943909, 1, 1, 179.9998779296875, 0.058823518455028534, 1, 0.897400975227356, 0.9726435542106628, 1.0862669944763184, 97.747802734375, -10.53125, -3.5625 -Beige, 0.9607843160629272, 0.9607843160629272, 0.8627451062202454, 59.99992370605469, 0.10204079747200012, 0.9607843160629272, 0.8322436213493347, 0.8988521099090576, 0.8065600991249084, 91.094970703125, -8, 24.546875 -Bisque, 1, 0.8941176533699036, 0.7686274647712708, 32.5423583984375, 0.23137250542640686, 1, 0.7894670963287354, 0.8073461651802063, 0.6363427639007568, 83.673095703125, 13.46875, 36.5 -Black, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -Blanched Almond, 1, 0.9215686321258545, 0.8039215803146362, 35.99998092651367, 0.19607838988304138, 1, 0.8196672201156616, 0.8508632779121399, 0.6984653472900391, 87.2802734375, 7.796875, 33.546875 -Blue, 0, 0, 1, 240, 0.9999998807907104, 1, 0.18042300641536713, 0.0721689984202385, 0.9502270221710205, 32.293701171875, 79.1875, -107.859375 -Blue Violet, 0.5411764979362488, 0.16862745583057404, 0.886274516582489, 271.1475524902344, 0.8097344040870667, 0.886274516582489, 0.25068020820617676, 0.12621363997459412, 0.7304641008377075, 27.099609375, 64.625, -81.25 -Brown, 0.6470588445663452, 0.16470588743686676, 0.16470588743686676, 0, 0.7454544305801392, 0.6470588445663452, 0.16764701902866364, 0.09824936091899872, 0.032035328447818756, 18.353271484375, 37.6875, 25.890625 -Burlywood, 0.8705882430076599, 0.7215686440467834, 0.529411792755127, 33.7930908203125, 0.39189180731773376, 0.8705882430076599, 0.5163891315460205, 0.5156236290931702, 0.3014764189720154, 56.8359375, 19.359375, 42.4375 -Cadet Blue, 0.37254902720451355, 0.6196078658103943, 0.6274510025978088, 181.84617614746094, 0.4062499403953552, 0.6274510025978088, 0.23288553953170776, 0.2942303717136383, 0.377002090215683, 33.642578125, -17.546875, -7 -Chartreuse, 0.49803921580314636, 1, 0, 90.1176528930664, 0.9999998807907104, 1, 0.44511520862579346, 0.7602953314781189, 0.12329627573490143, 88.1103515625, -82.765625, 83.640625 -Chocolate, 0.8235294222831726, 0.4117647111415863, 0.11764705926179886, 24.999996185302734, 0.8571427464485168, 0.8235294222831726, 0.31867295503616333, 0.23902495205402374, 0.04163479432463646, 36.273193359375, 50.359375, 48.09375 -Coral, 1, 0.49803921580314636, 0.3137255012989044, 16.114280700683594, 0.6862744688987732, 1, 0.5028159618377686, 0.3702393174171448, 0.12085746228694916, 56.036376953125, 72.421875, 63.34375 -Cornflower Blue, 0.3921568691730499, 0.5843137502670288, 0.929411768913269, 218.54014587402344, 0.5780590176582336, 0.929411768913269, 0.31282591819763184, 0.3031572103500366, 0.8430083990097046, 38.653564453125, 37.96875, -74.96875 -Cornsilk, 1, 0.9725490212440491, 0.8627451062202454, 47.99995803833008, 0.13725487887859344, 1, 0.8772358298301697, 0.9356323480606079, 0.8112900257110596, 94.488525390625, -3, 29.46875 -Crimson, 0.8627451062202454, 0.0784313753247261, 0.23529411852359772, 348, 0.9090908169746399, 0.8627451062202454, 0.3058439791202545, 0.1604711264371872, 0.05760817229747772, 37.9150390625, 62.203125, 47.8125 -Cyan, 0, 1, 1, 180, 0.9999998807907104, 1, 0.5380030274391174, 0.7873290181159973, 1.0694199800491333, 91.11328125, -48.09375, -14.125 -Dark Blue, 0, 0, 0.545098066329956, 240, 0.9999997615814209, 0.545098066329956, 0.04658212512731552, 0.018632797524333, 0.24533233046531677, 3.533935546875, 24.4375, -38.578125 -Dark Cyan, 0, 0.545098066329956, 0.545098066329956, 179.99998474121094, 0.9999997615814209, 0.545098066329956, 0.13890314102172852, 0.20327484607696533, 0.2761059105396271, 24.49951171875, -18.125, -5.34375 -Dark Goldenrod, 0.7215686440467834, 0.5254902243614197, 0.04313725605607033, 42.65895080566406, 0.9402172565460205, 0.7215686440467834, 0.283547043800354, 0.2726714313030243, 0.04086246341466904, 32.8369140625, 23.109375, 43.09375 -Dark Gray, 0.6627451181411743, 0.6627451181411743, 0.6627451181411743, 0, 0, 0.6627451181411743, 0.37709841132164, 0.3967552185058594, 0.4319688677787781, 42.840576171875, 0.03125, 0.015625 -Dark Green, 0, 0.3921568691730499, 0, 120, 0.9999997019767761, 0.3921568691730499, 0.04556916654109955, 0.0911383330821991, 0.015189680270850658, 9.423828125, -18.984375, 13.71875 -Dark Khaki, 0.7411764860153198, 0.7176470756530762, 0.41960784792900085, 55.609737396240234, 0.4338623583316803, 0.7411764860153198, 0.4057421386241913, 0.4574859142303467, 0.20598962903022766, 49.98779296875, -6.875, 44.703125 -Dark Magenta, 0.545098066329956, 0, 0.545098066329956, 300, 0.9999997615814209, 0.545098066329956, 0.15307042002677917, 0.07354079931974411, 0.2503240406513214, 12.8662109375, 37.171875, -22.96875 -Dark Olive Green, 0.3333333432674408, 0.41960784792900085, 0.18431372940540314, 82.00001525878906, 0.560747504234314, 0.41960784792900085, 0.09517066180706024, 0.12651889026165009, 0.046292148530483246, 12.87841796875, -12.5625, 15.859375 -Dark Orange, 1, 0.5490196347236633, 0, 32.9411735534668, 0.9999998807907104, 1, 0.5062285661697388, 0.40022218227386475, 0.05059244483709335, 57.330322265625, 68.484375, 68.84375 -Dark Orchid, 0.6000000238418579, 0.19607843458652496, 0.800000011920929, 280.1298522949219, 0.7549018859863281, 0.800000011920929, 0.25173529982566833, 0.13413403928279877, 0.5837336778640747, 24.517822265625, 55.921875, -61.5 -Dark Red, 0.545098066329956, 0, 0, 0, 0.9999997615814209, 0.545098066329956, 0.10648829489946365, 0.05490800365805626, 0.004991707392036915, 10.174560546875, 30.28125, 16.0625 -Dark Salmon, 0.9137254953384399, 0.5882353186607361, 0.47843137383461, 15.135133743286133, 0.47639480233192444, 0.9137254953384399, 0.4802568256855011, 0.4054543673992157, 0.23703771829605103, 50.69580078125, 49.890625, 43.015625 -Dark Sea Green, 0.5607843399047852, 0.7372549176216125, 0.5607843399047852, 120, 0.23936164379119873, 0.7372549176216125, 0.3426717221736908, 0.4378833770751953, 0.32625696063041687, 48.47412109375, -31.84375, 25.796875 -Dark Slate Blue, 0.2823529541492462, 0.239215686917305, 0.545098066329956, 248.46153259277344, 0.5611510276794434, 0.545098066329956, 0.08999692648649216, 0.06578757613897324, 0.252147376537323, 6.9091796875, 21.65625, -33.015625 -Dark Slate Gray, 0.18431372940540314, 0.30980393290519714, 0.30980393290519714, 179.99993896484375, 0.4050631523132324, 0.30980393290519714, 0.053789474070072174, 0.06760461628437042, 0.08416478335857391, 5.46875, -4.3125, -1.515625 -Dark Turquoise, 0, 0.8078431487083435, 0.8196078538894653, 180.8612518310547, 0.9999998807907104, 0.8196078538894653, 0.3357378840446472, 0.48741617798805237, 0.6794284582138062, 58.85009765625, -31.9375, -12.4375 -Dark Violet, 0.5803921818733215, 0, 0.8274509906768799, 282.0852966308594, 0.9999998807907104, 0.8274509906768799, 0.23967167735099792, 0.10999131202697754, 0.6247087717056274, 24.49951171875, 59.84375, -68.859375 -Deep Pink, 1, 0.0784313753247261, 0.5764706134796143, 327.574462890625, 0.9215685129165649, 1, 0.46759656071662903, 0.23873063921928406, 0.29741615056991577, 53.814697265625, 81.453125, 34.9375 -Deep Sky Blue, 0, 0.7490196228027344, 1, 195.05882263183594, 0.9999998807907104, 1, 0.36672061681747437, 0.4447641968727112, 1.0123260021209717, 55.938720703125, 16, -68.9375 -Dim Gray, 0.4117647111415863, 0.4117647111415863, 0.4117647111415863, 0, 0, 0.4117647111415863, 0.13426454365253448, 0.14126330614089966, 0.15380096435546875, 14.166259765625, 0.109375, 0.046875 -Dodger Blue, 0.11764705926179886, 0.5647059082984924, 1, 209.60000610351562, 0.8823528289794922, 1, 0.28550490736961365, 0.27438414096832275, 0.9837203025817871, 40.753173828125, 54.34375, -93.78125 -Firebrick, 0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 0, 0.808988630771637, 0.6980392336845398, 0.19223062694072723, 0.10727573186159134, 0.02571427822113037, 22.332763671875, 43.15625, 32.078125 -Floral White, 1, 0.9803921580314636, 0.9411764740943909, 39.99991989135742, 0.058823518455028534, 1, 0.9115046262741089, 0.9592306017875671, 0.9612758755683899, 96.39892578125, 0.359375, 11.703125 -Forest Green, 0.13333334028720856, 0.545098066329956, 0.13333334028720856, 120, 0.7553955316543579, 0.545098066329956, 0.10180484503507614, 0.18919843435287476, 0.046282973140478134, 23.333740234375, -31.421875, 29.3125 -Fuchsia, 1, 0, 1, 300, 0.9999998807907104, 1, 0.5928760170936584, 0.2848399877548218, 0.9695610404014587, 60.321044921875, 98.234375, -60.828125 -Gainsboro, 0.8627451062202454, 0.8627451062202454, 0.8627451062202454, 0, 0, 0.8627451062202454, 0.6802351474761963, 0.715693473815918, 0.7792141437530518, 74.15771484375, 0.015625, 0 -Ghost White, 0.9725490212440491, 0.9725490212440491, 1, 240, 0.02745097503066063, 1, 0.9032419919967651, 0.9431107044219971, 1.0802602767944336, 94.88525390625, 2.90625, -7.65625 -Gold, 1, 0.843137264251709, 0, 50.58823013305664, 0.9999998807907104, 1, 0.6554437875747681, 0.6986526250839233, 0.10033071041107178, 76.7333984375, 19.84375, 80.140625 -Goldenrod, 0.8549019694328308, 0.6470588445663452, 0.125490203499794, 42.903221130371094, 0.8532109260559082, 0.8549019694328308, 0.426321417093277, 0.4192340672016144, 0.07212784886360168, 49.267578125, 28.4375, 56.875 -Gray, 0.7450980544090271, 0.7450980544090271, 0.7450980544090271, 0, 0, 0.7450980544090271, 0.4894065856933594, 0.5149176716804504, 0.5606186985969543, 54.7607421875, 0.03125, 0.015625 -Web Gray, 0.501960813999176, 0.501960813999176, 0.501960813999176, 0, 0, 0.501960813999176, 0.20516590774059296, 0.2158605009317398, 0.23501898348331451, 22.979736328125, 0.03125, 0.015625 -Green, 0, 1, 0, 120, 0.9999998807907104, 1, 0.3575800061225891, 0.7151600122451782, 0.11919300258159637, 87.738037109375, -86.1875, 83.171875 -Web Green, 0, 0.501960813999176, 0, 120, 0.9999997615814209, 0.501960813999176, 0.07718739658594131, 0.15437479317188263, 0.025729062035679817, 18.84765625, -28.9375, 26.015625 -Green Yellow, 0.6784313917160034, 1, 0.18431372940540314, 83.65385437011719, 0.8156861662864685, 1, 0.5350667238235474, 0.8060835003852844, 0.1542835831642151, 89.208984375, -73.375, 84.453125 -Honeydew, 0.9411764740943909, 1, 0.9411764740943909, 120, 0.058823518455028534, 1, 0.8741925954818726, 0.9633602499961853, 0.9640365839004517, 96.966552734375, -16.5625, 12.171875 -Hot Pink, 1, 0.4117647111415863, 0.7058823704719543, 330, 0.5882351994514465, 1, 0.5453130006790161, 0.3466355800628662, 0.46986567974090576, 55.8837890625, 79.96875, 12.65625 -Indian Red, 0.8039215803146362, 0.3607843220233917, 0.3607843220233917, 0, 0.5512194037437439, 0.8039215803146362, 0.3093794584274292, 0.21409708261489868, 0.12625597417354584, 33.72802734375, 50.890625, 34.703125 -Indigo, 0.29411765933036804, 0, 0.5098039507865906, 274.6153869628906, 0.9999997615814209, 0.5098039507865906, 0.06929568946361542, 0.031073689460754395, 0.21347758173942566, 3.8330078125, 23.5, -31.5625 -Ivory, 1, 1, 0.9411764740943909, 59.9998779296875, 0.058823518455028534, 1, 0.9272476434707642, 0.9907166957855225, 0.9665235280990601, 99.249267578125, -5.390625, 15.75 -Khaki, 0.9411764740943909, 0.9019607901573181, 0.5490196347236633, 53.99998474121094, 0.41666659712791443, 0.9411764740943909, 0.6896663904190063, 0.7701455354690552, 0.36036184430122375, 80.77392578125, -7.578125, 66.546875 -Lavender, 0.9019607901573181, 0.9019607901573181, 0.9803921580314636, 240, 0.07999998331069946, 0.9803921580314636, 0.7818050980567932, 0.8031823635101318, 1.0180078744888306, 82.452392578125, 8.34375, -20.53125 -Lavender Blush, 1, 0.9411764740943909, 0.9607843160629272, 340.0000305175781, 0.058823518455028534, 1, 0.8887804746627808, 0.9017353057861328, 0.9908458590507507, 91.4306640625, 13.015625, -0.921875 -Lawn Green, 0.48627451062202454, 0.9882352948188782, 0, 90.4761962890625, 0.9999998807907104, 0.9882352948188782, 0.4312170445919037, 0.7390342950820923, 0.11992476135492325, 85.9375, -81.125, 81.890625 -Lemon Chiffon, 1, 0.9803921580314636, 0.8039215803146362, 53.999969482421875, 0.19607838988304138, 1, 0.864437460899353, 0.9404037594795227, 0.7133886814117432, 95.196533203125, -8.34375, 43.84375 -Light Blue, 0.6784313917160034, 0.8470588326454163, 0.9019607901573181, 194.7368621826172, 0.24782603979110718, 0.9019607901573181, 0.5606712102890015, 0.637069046497345, 0.8418401479721069, 67.95654296875, -14.46875, -20.40625 -Light Coral, 0.9411764740943909, 0.501960813999176, 0.501960813999176, 0, 0.466666579246521, 0.9411764740943909, 0.47553157806396484, 0.3552677631378174, 0.2476925551891327, 50.152587890625, 63.46875, 40.25 -Light Cyan, 0.8784313797950745, 1, 1, 179.99993896484375, 0.12156860530376434, 1, 0.8454471826553345, 0.9458548426628113, 1.0838316679000854, 95.855712890625, -20.125, -6.578125 -Light Goldenrod, 0.9803921580314636, 0.9803921580314636, 0.8235294222831726, 59.99995422363281, 0.15999996662139893, 0.9803921580314636, 0.8524099588394165, 0.9334931969642639, 0.7448301315307617, 94.427490234375, -11.75, 38.5 -Light Gray, 0.8274509906768799, 0.8274509906768799, 0.8274509906768799, 0, 0, 0.8274509906768799, 0.619132399559021, 0.6514056324958801, 0.7092204689979553, 68.06640625, 0.015625, 0 -Light Green, 0.5647059082984924, 0.9333333373069763, 0.5647059082984924, 120, 0.39495790004730225, 0.9333333373069763, 0.47107797861099243, 0.69089674949646, 0.3723141551017761, 77.1240234375, -65.984375, 58.671875 -Light Pink, 1, 0.7137255072593689, 0.7568627595901489, 350.95892333984375, 0.2862744629383087, 1, 0.6759384274482727, 0.5856972932815552, 0.5818241834640503, 66.705322265625, 53.203125, 16.15625 -Light Salmon, 1, 0.6274510025978088, 0.47843137383461, 17.142854690551758, 0.5215685963630676, 1, 0.5732675194740295, 0.4781184494495392, 0.24616536498069763, 60.565185546875, 60.953125, 55.46875 -Light Sea Green, 0.125490203499794, 0.6980392336845398, 0.6666666865348816, 176.71231079101562, 0.8202245831489563, 0.6980392336845398, 0.23767849802970886, 0.35047221183776855, 0.43531426787376404, 42.46826171875, -29.65625, -1.625 -Light Sky Blue, 0.529411792755127, 0.8078431487083435, 0.9803921580314636, 202.95652770996094, 0.4599999189376831, 0.9803921580314636, 0.49310988187789917, 0.5619192719459534, 0.9866426587104797, 63.153076171875, 0.625, -51.046875 -Light Slate Gray, 0.46666666865348816, 0.5333333611488342, 0.6000000238418579, 210.00001525878906, 0.2222222089767456, 0.6000000238418579, 0.2215970903635025, 0.23829501867294312, 0.33560386300086975, 25.946044921875, -0.921875, -13.140625 -Light Steel Blue, 0.6901960968971252, 0.7686274647712708, 0.8705882430076599, 213.91305541992188, 0.20720715820789337, 0.8705882430076599, 0.5082481503486633, 0.5398250222206116, 0.7682933807373047, 57.84912109375, 1.390625, -27.25 -Light Yellow, 1, 1, 0.8784313797950745, 59.999942779541016, 0.12156860530376434, 1, 0.9045210480690002, 0.9816260933876038, 0.8468302488327026, 98.626708984375, -9.96875, 31.296875 -Lime, 0, 1, 0, 120, 0.9999998807907104, 1, 0.3575800061225891, 0.7151600122451782, 0.11919300258159637, 87.738037109375, -86.1875, 83.171875 -Lime Green, 0.19607843458652496, 0.8039215803146362, 0.19607843458652496, 120, 0.7560974955558777, 0.8039215803146362, 0.2372114062309265, 0.44568729400634766, 0.1036919504404068, 55.72509765625, -58.8125, 56.28125 -Linen, 0.9803921580314636, 0.9411764740943909, 0.9019607901573181, 29.999954223632812, 0.07999998331069946, 0.9803921580314636, 0.8486459255218506, 0.8835818767547607, 0.8742563128471375, 89.73388671875, 4.125, 12.6875 -Magenta, 1, 0, 1, 300, 0.9999998807907104, 1, 0.5928760170936584, 0.2848399877548218, 0.9695610404014587, 60.321044921875, 98.234375, -60.828125 -Maroon, 0.6901960968971252, 0.1882352977991104, 0.3764705955982208, 337.5, 0.727272629737854, 0.6901960968971252, 0.2107411026954651, 0.12191140651702881, 0.12306557595729828, 22.320556640625, 42.5, 17.859375 -Web Maroon, 0.501960813999176, 0, 0, 0, 0.9999997615814209, 0.501960813999176, 0.08903230726718903, 0.045907266438007355, 0.004173446912318468, 7.2998046875, 26.734375, 11.546875 -Medium Aquamarine, 0.4000000059604645, 0.8039215803146362, 0.6666666865348816, 159.61163330078125, 0.5024389624595642, 0.8039215803146362, 0.3456289768218994, 0.4938696026802063, 0.45730581879615784, 56.951904296875, -45.140625, 18.890625 -Medium Blue, 0, 0, 0.8039215803146362, 240, 0.9999998807907104, 0.8039215803146362, 0.11014744639396667, 0.04405885189771652, 0.5801093578338623, 17.3583984375, 54.703125, -74.5 -Medium Orchid, 0.729411780834198, 0.3333333432674408, 0.8274509906768799, 288.09521484375, 0.597156286239624, 0.8274509906768799, 0.35253477096557617, 0.2164035439491272, 0.6393042802810669, 33.404541015625, 61.03125, -53.953125 -Medium Purple, 0.5764706134796143, 0.43921568989753723, 0.8588235378265381, 259.62615966796875, 0.48858439922332764, 0.8588235378265381, 0.3060874342918396, 0.22905084490776062, 0.6980715990066528, 30.28564453125, 50.640625, -67.96875 -Medium Sea Green, 0.23529411852359772, 0.7019608020782471, 0.4431372582912445, 146.72268676757812, 0.6648043990135193, 0.7019608020782471, 0.2096228152513504, 0.34391117095947266, 0.21151721477508545, 41.845703125, -43.359375, 32.015625 -Medium Slate Blue, 0.48235294222831726, 0.40784314274787903, 0.9333333373069763, 248.50746154785156, 0.5630251169204712, 0.9333333373069763, 0.28545498847961426, 0.2028283178806305, 0.8327666521072388, 31.658935546875, 62.015625, -87.640625 -Medium Spring Green, 0, 0.9803921580314636, 0.6039215922355652, 156.95999145507812, 0.9999998807907104, 0.9803921580314636, 0.4001394212245941, 0.7069948315620422, 0.4210047423839569, 84.503173828125, -79.15625, 61.90625 -Medium Turquoise, 0.2823529541492462, 0.8196078538894653, 0.800000011920929, 177.81019592285156, 0.6555023193359375, 0.8196078538894653, 0.36366453766822815, 0.5133431553840637, 0.6510230898857117, 60.260009765625, -36.171875, -5.53125 -Medium Violet Red, 0.7803921699523926, 0.08235294371843338, 0.5215686559677124, 322.2471923828125, 0.8944722414016724, 0.7803921699523926, 0.28056198358535767, 0.14375197887420654, 0.23481225967407227, 30.4443359375, 54.640625, 9.328125 -Midnight Blue, 0.09803921729326248, 0.09803921729326248, 0.43921568989753723, 240, 0.7767854928970337, 0.43921568989753723, 0.036719486117362976, 0.020713143050670624, 0.15531133115291595, 1.947021484375, 9.859375, -22.6875 -Mint Cream, 0.9607843160629272, 1, 0.9803921580314636, 149.99990844726562, 0.039215680211782455, 1, 0.9066698551177979, 0.978341281414032, 1.0452386140823364, 98.150634765625, -9.359375, 2.984375 -Misty Rose, 1, 0.8941176533699036, 0.8823529481887817, 5.999993801116943, 0.11764703691005707, 1, 0.8257196545600891, 0.821847140789032, 0.8272725939750671, 84.649658203125, 19.546875, 10.859375 -Moccasin, 1, 0.8941176533699036, 0.7098039388656616, 38.10809326171875, 0.290196031332016, 1, 0.773240864276886, 0.8008556365966797, 0.5508846044540405, 83.349609375, 11.421875, 47.65625 -Navajo White, 1, 0.8705882430076599, 0.6784313917160034, 35.85364532470703, 0.3215685784816742, 1, 0.7490472197532654, 0.7652256488800049, 0.5034854412078857, 80.487060546875, 16.515625, 49.828125 -Navy Blue, 0, 0, 0.501960813999176, 240, 0.9999997615814209, 0.501960813999176, 0.03894620016217232, 0.015578435733914375, 0.20511648058891296, 2.47802734375, 17.421875, -32.34375 -Old Lace, 0.9921568632125854, 0.9607843160629272, 0.9019607901573181, 39.1303825378418, 0.09090907126665115, 0.9921568632125854, 0.8744063377380371, 0.9190149903297424, 0.879738450050354, 92.852783203125, 1.09375, 17.171875 -Olive, 0.501960813999176, 0.501960813999176, 0, 59.99998474121094, 0.9999997615814209, 0.501960813999176, 0.16621971130371094, 0.20028206706047058, 0.029902508482336998, 22.015380859375, -7.21875, 30.421875 -Olive Drab, 0.41960784792900085, 0.5568627715110779, 0.13725490868091583, 79.62619018554688, 0.7535209655761719, 0.5568627715110779, 0.16039887070655823, 0.2259306162595749, 0.05105489119887352, 25.665283203125, -23.078125, 31.984375 -Orange, 1, 0.6470588445663452, 0, 38.823524475097656, 0.9999998807907104, 1, 0.5469968318939209, 0.48175865411758423, 0.06418181210756302, 61.279296875, 57.75, 70.78125 -Orange Red, 1, 0.2705882489681244, 0, 16.235292434692383, 0.9999998807907104, 1, 0.4337330162525177, 0.2552310526371002, 0.02642732299864292, 53.61328125, 79.015625, 67.328125 -Orchid, 0.8549019694328308, 0.43921568989753723, 0.8392156958580017, 302.26416015625, 0.4862384796142578, 0.8549019694328308, 0.46843424439430237, 0.313510537147522, 0.6718415021896362, 44.287109375, 67.21875, -39.28125 -Pale Goldenrod, 0.9333333373069763, 0.9098039269447327, 0.6666666865348816, 54.70585632324219, 0.2857142388820648, 0.9333333373069763, 0.7137202620506287, 0.7879424691200256, 0.4946836233139038, 81.73828125, -9.171875, 52.34375 -Pale Green, 0.5960784554481506, 0.9843137264251709, 0.5960784554481506, 120, 0.3944222331047058, 0.9843137264251709, 0.5311088562011719, 0.7793415784835815, 0.41941505670547485, 85.955810546875, -72.625, 64.703125 -Pale Turquoise, 0.686274528503418, 0.9333333373069763, 0.9333333373069763, 179.99996948242188, 0.2647058367729187, 0.9333333373069763, 0.6368032693862915, 0.7643305063247681, 0.9226345419883728, 80.8837890625, -31.0625, -9.59375 -Pale Violet Red, 0.8588235378265381, 0.43921568989753723, 0.5764706134796143, 340.37384033203125, 0.48858439922332764, 0.8588235378265381, 0.4027522802352905, 0.28758469223976135, 0.31025686860084534, 40.93017578125, 56.546875, 15.46875 -Papaya Whip, 1, 0.9372549057006836, 0.8352941274642944, 37.14282989501953, 0.16470585763454437, 1, 0.8411519527435303, 0.8779867887496948, 0.7544852495193481, 89.459228515625, 5.296875, 29.296875 -Peach Puff, 1, 0.8549019694328308, 0.7254902124404907, 28.285701751708984, 0.2745097577571869, 1, 0.7506852746009827, 0.7490838170051575, 0.5639030337333679, 78.857421875, 22.0625, 39.046875 -Peru, 0.8039215803146362, 0.5215686559677124, 0.24705882370471954, 29.57746124267578, 0.6926828622817993, 0.8039215803146362, 0.34463950991630554, 0.3011631667613983, 0.08699263632297516, 38.299560546875, 37.796875, 45.1875 -Pink, 1, 0.7529411911964417, 0.7960784435272217, 349.5238037109375, 0.24705877900123596, 1, 0.7086877226829529, 0.6327421069145203, 0.6496396660804749, 69.879150390625, 47.5625, 11.625 -Plum, 0.8666666746139526, 0.6274510025978088, 0.8666666746139526, 300.0000305175781, 0.27601805329322815, 0.8666666746139526, 0.5543830394744873, 0.4573570787906647, 0.7429462671279907, 52.9052734375, 51.359375, -33.234375 -Powder Blue, 0.6901960968971252, 0.8784313797950745, 0.9019607901573181, 186.6667022705078, 0.23478256165981293, 0.9019607901573181, 0.5883779525756836, 0.6825222969055176, 0.8491535186767578, 72.44873046875, -21.171875, -13.609375 -Purple, 0.6274510025978088, 0.125490203499794, 0.9411764740943909, 276.9230651855469, 0.8666665554046631, 0.9411764740943909, 0.3073701858520508, 0.1479761302471161, 0.8365147113800049, 32.94677734375, 73.046875, -87.984375 -Web Purple, 0.501960813999176, 0, 0.501960813999176, 300, 0.9999997615814209, 0.501960813999176, 0.12797850370407104, 0.06148570030927658, 0.20928992331027985, 9.649658203125, 33.015625, -20.421875 -Rebecca Purple, 0.4000000059604645, 0.20000000298023224, 0.6000000238418579, 270, 0.666666567325592, 0.6000000238418579, 0.12411271035671234, 0.07492164522409439, 0.309206485748291, 9.87548828125, 31.78125, -38.859375 -Red, 1, 0, 0, 0, 0.9999998807907104, 1, 0.4124529957771301, 0.21267099678516388, 0.01933399960398674, 53.240966796875, 80.09375, 67.203125 -Rosy Brown, 0.7372549176216125, 0.5607843399047852, 0.5607843399047852, 0, 0.23936164379119873, 0.7372549176216125, 0.35519424080848694, 0.32321077585220337, 0.3034681975841522, 36.712646484375, 24.875, 11.0625 -Royal Blue, 0.2549019753932953, 0.4117647111415863, 0.8823529481887817, 225, 0.7111109495162964, 0.8823529481887817, 0.2081635594367981, 0.1666068732738495, 0.7333256006240845, 27.1484375, 52.328125, -80.0625 -Saddle Brown, 0.545098066329956, 0.2705882489681244, 0.07450980693101883, 24.9999942779541, 0.8633092045783997, 0.545098066329956, 0.12894324958324432, 0.09793803095817566, 0.018272994086146355, 12.58544921875, 23.65625, 18.765625 -Salmon, 0.9803921580314636, 0.501960813999176, 0.4470588266849518, 6.1764726638793945, 0.5439999103546143, 0.9803921580314636, 0.501841127872467, 0.36982640624046326, 0.20410597324371338, 54.083251953125, 69.3125, 51.78125 -Sandy Brown, 0.95686274766922, 0.6431372761726379, 0.3764705955982208, 27.56756591796875, 0.6065572500228882, 0.95686274766922, 0.5269815921783447, 0.46633121371269226, 0.17288833856582642, 57.208251953125, 49.90625, 59.21875 -Sea Green, 0.18039216101169586, 0.545098066329956, 0.34117648005485535, 146.45159912109375, 0.6690646409988403, 0.545098066329956, 0.12078526616096497, 0.19733065366744995, 0.12186554074287415, 23.590087890625, -28.5, 20.1875 -Seashell, 1, 0.9607843160629272, 0.9333333373069763, 24.705839157104492, 0.06666665524244308, 1, 0.8932191729545593, 0.9273865818977356, 0.9406061172485352, 93.603515625, 5.25, 10.03125 -Sienna, 0.6274510025978088, 0.32156863808631897, 0.1764705926179886, 19.304340362548828, 0.7187498807907104, 0.6274510025978088, 0.17989644408226013, 0.13699708878993988, 0.04178870469331741, 18.93310546875, 30.296875, 25.828125 -Silver, 0.7529411911964417, 0.7529411911964417, 0.7529411911964417, 0, 0, 0.7529411911964417, 0.5009996891021729, 0.5271151065826416, 0.5738986730575562, 55.926513671875, 0.015625, 0 -Sky Blue, 0.529411792755127, 0.8078431487083435, 0.9215686321258545, 197.40000915527344, 0.4255318343639374, 0.9215686321258545, 0.4705203175544739, 0.5528834462165833, 0.8676709532737732, 61.65771484375, -10.375, -35.78125 -Slate Blue, 0.4156862795352936, 0.3529411852359772, 0.8039215803146362, 248.3478240966797, 0.5609755516052246, 0.8039215803146362, 0.2061532735824585, 0.14783000946044922, 0.5950824618339539, 21.966552734375, 46.59375, -66.78125 -Slate Gray, 0.43921568989753723, 0.501960813999176, 0.5647059082984924, 210.00003051757812, 0.2222222089767456, 0.5647059082984924, 0.19433583319187164, 0.20896126329898834, 0.2938746213912964, 22.42431640625, -0.921875, -11.65625 -Snow, 1, 0.9803921580314636, 0.9803921580314636, 0, 0.019607840105891228, 1, 0.9267695546150208, 0.9653365612030029, 1.0416710376739502, 96.8994140625, 3.84375, 1.375 -Spring Green, 0, 1, 0.49803921580314636, 149.88235473632812, 0.9999998807907104, 1, 0.3958713114261627, 0.7304764986038208, 0.3208603858947754, 87.8662109375, -84.515625, 74.84375 -Steel Blue, 0.27450981736183167, 0.5098039507865906, 0.7058823704719543, 207.27273559570312, 0.6111109852790833, 0.7058823704719543, 0.1874300241470337, 0.20560768246650696, 0.46148544549942017, 24.560546875, 10.1875, -37.984375 -Tan, 0.8235294222831726, 0.7058823704719543, 0.5490196347236633, 34.285701751708984, 0.33333325386047363, 0.8235294222831726, 0.47633710503578186, 0.4823954105377197, 0.31605905294418335, 52.72216796875, 13.3125, 34.375 -Teal, 0, 0.501960813999176, 0.501960813999176, 179.99998474121094, 0.9999997615814209, 0.501960813999176, 0.11613360047340393, 0.16995322704315186, 0.23084554076194763, 19.989013671875, -16.125, -4.75 -Thistle, 0.8470588326454163, 0.7490196228027344, 0.8470588326454163, 300.00006103515625, 0.11574071645736694, 0.8470588326454163, 0.5934168100357056, 0.5681906342506409, 0.7278823256492615, 60.626220703125, 23.546875, -16.015625 -Tomato, 1, 0.38823530077934265, 0.27843138575553894, 9.130434036254883, 0.7215685248374939, 1, 0.4684373736381531, 0.3064501881599426, 0.09407974779605865, 54.351806640625, 77.125, 63.765625 -Turquoise, 0.250980406999588, 0.8784313797950745, 0.8156862854957581, 173.99998474121094, 0.7142855525016785, 0.8784313797950745, 0.4014909863471985, 0.5895079374313354, 0.6892006993293762, 69.00634765625, -46.453125, 3.671875 -Violet, 0.9333333373069763, 0.5098039507865906, 0.9333333373069763, 300, 0.4537814259529114, 0.9333333373069763, 0.5867264270782471, 0.403179794549942, 0.8555747270584106, 54.876708984375, 77.96875, -48.9375 -Wheat, 0.9607843160629272, 0.8705882430076599, 0.7019608020782471, 39.09088897705078, 0.26938769221305847, 0.9607843160629272, 0.7191405892372131, 0.7491186857223511, 0.5330684781074524, 78.302001953125, 8.078125, 42.4375 -White, 1, 1, 1, 0, 0, 1, 0.9504560232162476, 1, 1.0887540578842163, 100, 0, 0 -White Smoke, 0.9607843160629272, 0.9607843160629272, 0.9607843160629272, 0, 0, 0.9607843160629272, 0.8678600788116455, 0.9130986332893372, 0.9941397905349731, 92.205810546875, 0.015625, 0 -Yellow, 1, 1, 0, 59.99999237060547, 0.9999998807907104, 1, 0.7700330018997192, 0.9278309941291809, 0.1385270059108734, 97.137451171875, -21.546875, 94.46875 -Yellow Green, 0.6039215922355652, 0.8039215803146362, 0.19607843458652496, 79.741943359375, 0.7560974955558777, 0.8039215803146362, 0.35733717679977417, 0.5076271295547485, 0.1093229204416275, 57.45849609375, -44.4375, 58.421875 diff --git a/Unicolour.Tests/Lookups/TestColour.cs b/Unicolour.Tests/Lookups/TestColour.cs deleted file mode 100644 index 65e541ff..00000000 --- a/Unicolour.Tests/Lookups/TestColour.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Wacton.Unicolour.Tests.Lookups; - -internal class TestColour -{ - public string? Name { get; init; } - public string? Hex { get; init; } - public (double r, double g, double b)? Rgb { get; init; } - public (double r, double g, double b)? RgbLinear { get; init; } - public (double h, double s, double l)? Hsl { get; init; } - public (double h, double s, double b)? Hsb { get; init; } - public (double x, double y, double z)? Xyz { get; init; } - public (double l, double a, double b)? Lab { get; init; } - - public override string ToString() => $"Name:[{Name}] · Hex[{Hex}] · Rgb[{Rgb}] · Hsb[{Hsb}]"; -} \ No newline at end of file diff --git a/Unicolour.Tests/Lookups/TestColours.cs b/Unicolour.Tests/Lookups/TestColours.cs deleted file mode 100644 index e49fe845..00000000 --- a/Unicolour.Tests/Lookups/TestColours.cs +++ /dev/null @@ -1,85 +0,0 @@ -namespace Wacton.Unicolour.Tests.Lookups; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -// NamedColours.csv is a list of 145 colours taken from https://en.wikipedia.org/wiki/X11_color_names -internal static class TestColours -{ - private static readonly Random Random = new(); - - public static readonly List NamedColours; - public static readonly List OpenCvColours; - public static readonly List<(int r, int g, int b)> RandomRGB255s = new(); - public static readonly List<(double r255, double g255, double b255)> RandomRGBs = new(); - public static readonly List<(double h, double s, double b)> RandomHSBs = new(); - public static readonly List RandomHexs = new(); - - static TestColours() - { - NamedColours = File.ReadAllLines(Path.Combine("Lookups", "NamedColours.csv")) - .Skip(1).Select(CreateNamedColour).ToList(); - - OpenCvColours = File.ReadAllLines(Path.Combine("Lookups", "OpenCvColours.csv")) - .Select(CreateOpenCvColour).ToList(); - - for (var i = 0; i < 1000; i++) - { - RandomRGB255s.Add((Random.Next(256), Random.Next(256), Random.Next(256))); - RandomRGBs.Add((Random.NextDouble(), Random.NextDouble(), Random.NextDouble())); - RandomHSBs.Add((Random.NextDouble(), Random.NextDouble(), Random.NextDouble())); - RandomHexs.Add(GenerateRandomHex()); - } - } - - public static TestColour GetStoredOpenCvColour(string name) => OpenCvColours.Single(x => x.Name == name); - - private static TestColour CreateNamedColour(string csvRow) - { - var items = csvRow.Split(",", StringSplitOptions.TrimEntries); - - return new TestColour - { - Name = items[0], - Hex = items[1], - Rgb = (FromPercentage(items[2]), FromPercentage(items[3]), FromPercentage(items[4])), - Hsl = (FromDegrees(items[5]), FromPercentage(items[6]), FromPercentage(items[7])), - Hsb = (FromDegrees(items[5]), FromPercentage(items[8]), FromPercentage(items[9])) - }; - } - - private static TestColour CreateOpenCvColour(string csvRow) - { - var items = csvRow.Split(",", StringSplitOptions.TrimEntries); - - return new TestColour - { - Name = items[0], - Rgb = (FromText(items[1]), FromText(items[2]), FromText(items[3])), - Hsb = (FromText(items[4]), FromText(items[5]), FromText(items[6])), - Xyz = (FromText(items[7]), FromText(items[8]), FromText(items[9])), - Lab = (FromText(items[10]), FromText(items[11]), FromText(items[12])) - }; - } - - private static double FromPercentage(string text) => double.Parse(text.TrimEnd('%')) / 100.0; - private static double FromDegrees(string text) => double.Parse(text.TrimEnd('°')); - private static double FromText(string text) => double.Parse(text); - - private static string GenerateRandomHex() - { - const string hexChars = "0123456789abcdefABCDEF"; - var useHash = Random.Next(0, 2) == 0; - var length = Random.Next(0, 2) == 0 ? 6 : 8; - - var hex = useHash ? "#" : string.Empty; - for (var i = 0; i < length; i++) - { - hex += hexChars[Random.Next(hexChars.Length)]; - } - - return hex; - } -} \ No newline at end of file diff --git a/Unicolour.Tests/OtherLibraryTests.cs b/Unicolour.Tests/OtherLibraryTests.cs index b5d1fd0b..8937a811 100644 --- a/Unicolour.Tests/OtherLibraryTests.cs +++ b/Unicolour.Tests/OtherLibraryTests.cs @@ -2,116 +2,121 @@ namespace Wacton.Unicolour.Tests; using System; using NUnit.Framework; -using Wacton.Unicolour; -using Wacton.Unicolour.Tests.Lookups; +using Wacton.Unicolour.Tests.Factories; using Wacton.Unicolour.Tests.Utils; - public class OtherLibraryTests { + private static readonly OpenCvFactory OpenCvFactory = new(); + private static readonly ColourfulFactory ColourfulFactory = new(); + private static readonly ColorMineFactory ColorMineFactory = new(); + private static readonly SixLaborsFactory SixLaborsFactory = new(); + private static bool IsWindows() => Environment.OSVersion.Platform == PlatformID.Win32NT; - /* - * OPENCV: doesn't expose linear RGB, doesn't directly convert HSV -> XYZ/LAB - * COLOURFUL: doesn't support HSB! - * COLORMINE: doesn't expose linear RGB - * -------------------- - * at this point I'm pretty sure OpenCV doesn't calculate RGB -> LAB correctly - * since all other libraries and online tools calculate the same LAB as Unicolour - * the LAB test tolerances are so large is not really worth testing against - */ - private static readonly Tolerances OpenCvTolerances = new() { Rgb = 0.005, Hsb = 0.005, Xyz = 0.0005, Lab = 50.0 }; - private static readonly Tolerances ColourfulTolerances = new() { Rgb = 0.00000000001, RgbLinear = 0.00000000001, Xyz = 0.00000000001, Lab = 0.0000005 }; - private static readonly Tolerances ColorMineTolerances = new() { Rgb = 0.00000000001, Hsb = 0.0005, Xyz = 0.0005, Lab = 0.05 }; - private static readonly Tolerances SixLaborsTolerances = new() { Rgb = 0.001, RgbLinear = 0.005, Hsb = 0.0005, Xyz = 0.005, Lab = 0.1 }; - - private delegate TestColour ToOtherLibFromRgb255(int r, int g, int b); - private delegate TestColour ToOtherLibFromRgb(double r, double g, double b); - private delegate TestColour ToOtherLibFromHsb(double h, double s, double b); - private delegate TestColour ToOtherLibFromStored(string name); // specifically for OpenCv on non-Windows - [Test] public void OpenCvWindows() { // I've given up trying to make OpenCvSharp work in a dockerised unix environment... Assume.That(IsWindows()); - AssertUtils.AssertNamedColours(namedColour => AssertFromHex(namedColour.Hex!, OpenCvUtils.FromRgb255, OpenCvTolerances)); - AssertUtils.AssertRandomHexs(hex => AssertFromHex(hex, OpenCvUtils.FromRgb255, OpenCvTolerances)); - AssertUtils.AssertRandomRgb255Colours((r, g, b) => AssertFromRgb255(r, g, b, OpenCvUtils.FromRgb255, OpenCvTolerances)); - AssertUtils.AssertRandomRgbColours((r, g, b) => AssertFromRgb(r, g, b, OpenCvUtils.FromRgb, OpenCvTolerances)); - AssertUtils.AssertRandomHsbColours((h, s, b) => AssertFromHsb(h, s, b, OpenCvUtils.FromHsb, OpenCvTolerances)); + AssertUtils.AssertNamedColours(namedColour => AssertFromHex(namedColour.Hex!, OpenCvFactory)); + AssertUtils.AssertRandomHexColours(hex => AssertFromHex(hex, OpenCvFactory)); + AssertUtils.AssertRandomRgb255Colours(tuple => AssertFromRgb255(tuple, OpenCvFactory)); + AssertUtils.AssertRandomRgbColours(tuple => AssertFromRgb(tuple, OpenCvFactory)); + AssertUtils.AssertRandomHsbColours(tuple => AssertFromHsb(tuple, OpenCvFactory)); + AssertUtils.AssertRandomHslColours(tuple => AssertFromHsl(tuple, OpenCvFactory)); } [Test] public void OpenCvCrossPlatform() { + // TODO: regenerate to include "HLS" values // in order to test OpenCV in a non-windows environment, this looks up a stored precomputed value - AssertUtils.AssertNamedColours(namedColour => AssertFromStored(namedColour.Hex!, namedColour.Name!, OpenCvUtils.FromStored, OpenCvTolerances)); + AssertUtils.AssertNamedColours(namedColour => AssertFromCsvData(namedColour.Hex!, namedColour.Name!)); } [Test] public void Colourful() { - AssertUtils.AssertNamedColours(namedColour => AssertFromHex(namedColour.Hex!, ColourfulUtils.FromRgb255, ColourfulTolerances)); - AssertUtils.AssertRandomHexs(hex => AssertFromHex(hex, ColourfulUtils.FromRgb255, ColourfulTolerances)); - AssertUtils.AssertRandomRgb255Colours((r, g, b) => AssertFromRgb255(r, g, b, ColourfulUtils.FromRgb255, ColourfulTolerances)); - AssertUtils.AssertRandomRgbColours((r, g, b) => AssertFromRgb(r, g, b, ColourfulUtils.FromRgb, ColourfulTolerances)); + // no asserting random HSB colours because Colourful doesn't support HSB/HSL + AssertUtils.AssertNamedColours(namedColour => AssertFromHex(namedColour.Hex!, ColourfulFactory)); + AssertUtils.AssertRandomHexColours(hex => AssertFromHex(hex, ColourfulFactory)); + AssertUtils.AssertRandomRgb255Colours(tuple => AssertFromRgb255(tuple, ColourfulFactory)); + AssertUtils.AssertRandomRgbColours(tuple => AssertFromRgb(tuple, ColourfulFactory)); } [Test] public void ColorMine() { - AssertUtils.AssertNamedColours(namedColour => AssertFromHex(namedColour.Hex!, ColorMineUtils.FromRgb255, ColorMineTolerances)); - AssertUtils.AssertRandomHexs(hex => AssertFromHex(hex, ColorMineUtils.FromRgb255, ColorMineTolerances)); - AssertUtils.AssertRandomRgb255Colours((r, g, b) => AssertFromRgb255(r, g, b, ColorMineUtils.FromRgb255, ColorMineTolerances)); - AssertUtils.AssertRandomHsbColours((h, s, b) => AssertFromHsb(h, s, b, ColorMineUtils.FromHsb, ColorMineTolerances)); + // no asserting random RGB 0-1 colours because ColorMine only accepts RGB 255 + AssertUtils.AssertNamedColours(namedColour => AssertFromHex(namedColour.Hex!, ColorMineFactory)); + AssertUtils.AssertRandomHexColours(hex => AssertFromHex(hex, ColorMineFactory)); + AssertUtils.AssertRandomRgb255Colours(tuple => AssertFromRgb255(tuple, ColorMineFactory)); + AssertUtils.AssertRandomHsbColours(tuple => AssertFromHsb(tuple, ColorMineFactory)); + AssertUtils.AssertRandomHslColours(tuple => AssertFromHsl(tuple, ColorMineFactory)); } [Test] public void SixLabors() { - AssertUtils.AssertNamedColours(namedColour => AssertFromHex(namedColour.Hex!, SixLaborsUtils.FromRgb255, SixLaborsTolerances)); - AssertUtils.AssertRandomHexs(hex => AssertFromHex(hex, SixLaborsUtils.FromRgb255, SixLaborsTolerances)); - AssertUtils.AssertRandomRgb255Colours((r, g, b) => AssertFromRgb255(r, g, b, SixLaborsUtils.FromRgb255, SixLaborsTolerances)); - AssertUtils.AssertRandomHsbColours((h, s, b) => AssertFromHsb(h, s, b, SixLaborsUtils.FromHsb, SixLaborsTolerances)); + AssertUtils.AssertNamedColours(namedColour => AssertFromHex(namedColour.Hex!, SixLaborsFactory)); + AssertUtils.AssertRandomHexColours(hex => AssertFromHex(hex, SixLaborsFactory)); + AssertUtils.AssertRandomRgb255Colours(tuple => AssertFromRgb255(tuple, SixLaborsFactory)); + AssertUtils.AssertRandomRgbColours(tuple => AssertFromRgb(tuple, SixLaborsFactory)); + AssertUtils.AssertRandomHsbColours(tuple => AssertFromHsb(tuple, SixLaborsFactory)); + AssertUtils.AssertRandomHslColours(tuple => AssertFromHsl(tuple, SixLaborsFactory)); } - private static void AssertFromHex(string hex, ToOtherLibFromRgb255 toOtherLibColour, Tolerances tolerances) + private static void AssertFromHex(string hex, ITestColourFactory testColourFactory) { var unicolour = Unicolour.FromHex(hex); var (r255, g255, b255, _) = SystemColorUtils.HexToRgb255(hex); - var otherLibColour = toOtherLibColour(r255, g255, b255); + var testColour = testColourFactory.FromRgb255(r255, g255, b255); AssertHex(unicolour, hex); - AssertOtherColour(unicolour, otherLibColour, tolerances); + AssertTestColour(unicolour, testColour, $"HEX [{hex}]"); } - private static void AssertFromRgb255(int r, int g, int b, ToOtherLibFromRgb255 toOtherLibColour, Tolerances tolerances) + private static void AssertFromRgb255(ColourTuple tuple, ITestColourFactory testColourFactory) { - var unicolour = Unicolour.FromRgb255(r, g, b); - var otherLibColour = toOtherLibColour(r, g, b); - AssertOtherColour(unicolour, otherLibColour, tolerances); + var (first, second, third) = tuple; + var r255 = (int)(first / 255.0); + var g255 = (int)(second / 255.0); + var b255 = (int)(third / 255.0); + var unicolour = Unicolour.FromRgb255(r255, g255, b255); + var testColour = testColourFactory.FromRgb255(r255, g255, b255); + AssertTestColour(unicolour, testColour, $"RGB [{unicolour.Rgb.Tuple255}]"); } - private static void AssertFromRgb(double r, double g, double b, ToOtherLibFromRgb toOtherLibColour, Tolerances tolerances) + private static void AssertFromRgb(ColourTuple tuple, ITestColourFactory testColourFactory) { + var (r, g, b) = tuple; var unicolour = Unicolour.FromRgb(r, g, b); - var otherLibColour = toOtherLibColour(r, g, b); - AssertOtherColour(unicolour, otherLibColour, tolerances); + var testColour = testColourFactory.FromRgb(r, g, b); + AssertTestColour(unicolour, testColour, $"RGB [{unicolour.Rgb}]"); } - private static void AssertFromHsb(double h, double s, double b, ToOtherLibFromHsb toOtherLibColour, Tolerances tolerances) + private static void AssertFromHsb(ColourTuple tuple, ITestColourFactory testColourFactory) { + var (h, s, b) = tuple; var unicolour = Unicolour.FromHsb(h, s, b); - var otherLibColour = toOtherLibColour(h, s, b); - AssertOtherColour(unicolour, otherLibColour, tolerances); + var testColour = testColourFactory.FromHsb(h, s, b); + AssertTestColour(unicolour, testColour, $"HSB [{unicolour.Hsb}]"); } - private static void AssertFromStored(string hex, string name, ToOtherLibFromStored toOtherLibColour, Tolerances tolerances) + private static void AssertFromHsl(ColourTuple tuple, ITestColourFactory testColourFactory) + { + var (h, s, l) = tuple; + var unicolour = Unicolour.FromHsl(h, s, l); + var testColour = testColourFactory.FromHsl(h, s, l); + AssertTestColour(unicolour, testColour, $"HSL [{unicolour.Hsl}]"); + } + + private static void AssertFromCsvData(string hex, string name) { var unicolour = Unicolour.FromHex(hex); - var otherLibColour = toOtherLibColour(name); + var otherLibColour = OpenCvCsvFactory.FromName(name); AssertHex(unicolour, hex); - AssertOtherColour(unicolour, otherLibColour, tolerances); + AssertTestColour(unicolour, otherLibColour, $"NAME [{name}]"); } private static void AssertHex(Unicolour unicolour, string hex) @@ -123,27 +128,32 @@ private static void AssertHex(Unicolour unicolour, string hex) Assert.That(unicolour.Alpha.Hex, Is.EqualTo(expectedA.ToUpper())); } - private static void AssertOtherColour(Unicolour unicolour, TestColour otherColour, Tolerances tolerances) + private static void AssertTestColour(Unicolour unicolour, TestColour testColour, string source) { - - void AssertColourSpace((double, double, double) unicolourSpace, (double, double, double)? otherSpace, double tolerance, string spaceName) - { - string FailMessage() => $"colour: {otherColour.Name} {spaceName}"; + var colourName = testColour.Name; + var tolerances = testColour.Tolerances; + if (colourName == null) throw new ArgumentException("Malformed test colour: no name"); + if (tolerances == null) throw new ArgumentException("Malformed test colour: no tolerances"); - if (!otherSpace.HasValue) return; - Assert.That(unicolourSpace.Item1, Is.EqualTo(otherSpace.Value.Item1).Within(tolerance), FailMessage); - Assert.That(unicolourSpace.Item2, Is.EqualTo(otherSpace.Value.Item2).Within(tolerance), FailMessage); - Assert.That(unicolourSpace.Item3, Is.EqualTo(otherSpace.Value.Item3).Within(tolerance), FailMessage); - } + AssertColourTuple(unicolour.Rgb.Tuple, testColour.Rgb, tolerances.Rgb, $"{source} -> RGB"); + AssertColourTuple(unicolour.Rgb.TupleLinear, testColour.RgbLinear, tolerances.RgbLinear, $"{source} -> RGB Linear"); + AssertColourTuple(unicolour.Xyz.Tuple, testColour.Xyz, tolerances.Xyz, $"{source} -> XYZ"); + AssertColourTuple(unicolour.Lab.Tuple, testColour.Lab, tolerances.Lab, $"{source} -> LAB"); - AssertColourSpace(unicolour.Rgb.Tuple, otherColour.Rgb, tolerances.Rgb, "Rgb"); - AssertColourSpace(unicolour.Rgb.TupleLinear, otherColour.RgbLinear, tolerances.RgbLinear, "RgbLinear"); - AssertColourSpace(unicolour.Hsb.Tuple, otherColour.Hsb, tolerances.Hsb, "Hsb"); - AssertColourSpace(unicolour.Xyz.Tuple, otherColour.Xyz, tolerances.Xyz, "Xyz"); - AssertColourSpace(unicolour.Lab.Tuple, otherColour.Lab, tolerances.Lab, "Lab"); + if (testColour.ExcludeFromHueBasedTest) + { + var reasons = string.Join(", ", testColour.ExcludeFromHueBasedTestReasons); + Console.WriteLine($"Excluded test colour {source} -> HSB [{unicolour.Hsb}] / HSL [{unicolour.Hsl}] because: {reasons}"); + return; + } + + AssertColourTuple(unicolour.Hsb.Tuple, testColour.Hsb, tolerances.Hsb, $"{source} -> HSB", true); + AssertColourTuple(unicolour.Hsl.Tuple, testColour.Hsl, tolerances.Hsl, $"{source} -> HSL", true); } - - private class Tolerances { - public double Rgb, RgbLinear, Hsb, Xyz, Lab; + + private static void AssertColourTuple(ColourTuple unicolourTuple, ColourTuple? testTuple, double tolerance, string details, bool hasHue = false) + { + if (testTuple == null) return; + AssertUtils.AssertColourTuple(unicolourTuple, testTuple, tolerance, hasHue, details); } } \ No newline at end of file diff --git a/Unicolour.Tests/ParseHexTests.cs b/Unicolour.Tests/ParseHexTests.cs index 08c277f2..898afccd 100644 --- a/Unicolour.Tests/ParseHexTests.cs +++ b/Unicolour.Tests/ParseHexTests.cs @@ -10,7 +10,7 @@ public class ParseHexTests public void ParsedHexSameRgbAsSystem() { AssertUtils.AssertNamedColours(namedColour => AssertHexParse(namedColour.Hex!)); - AssertUtils.AssertRandomHexs(AssertHexParse); + AssertUtils.AssertRandomHexColours(AssertHexParse); } [Test] diff --git a/Unicolour.Tests/Unicolour.Tests.csproj b/Unicolour.Tests/Unicolour.Tests.csproj index a4e5994e..3a690aca 100644 --- a/Unicolour.Tests/Unicolour.Tests.csproj +++ b/Unicolour.Tests/Unicolour.Tests.csproj @@ -29,10 +29,10 @@ - + Always - + Always diff --git a/Unicolour.Tests/Utils/AssertUtils.cs b/Unicolour.Tests/Utils/AssertUtils.cs index fb2a7fe8..fbbf9290 100644 --- a/Unicolour.Tests/Utils/AssertUtils.cs +++ b/Unicolour.Tests/Utils/AssertUtils.cs @@ -1,47 +1,33 @@ namespace Wacton.Unicolour.Tests.Utils; using System; -using Wacton.Unicolour.Tests.Lookups; +using System.Collections.Generic; +using NUnit.Framework; internal static class AssertUtils { - public static void AssertNamedColours(Action action) - { - foreach (var namedColour in TestColours.NamedColours) - { - action(namedColour); - } - } - - public static void AssertRandomRgb255Colours(Action action) - { - foreach (var (r, g, b) in TestColours.RandomRGB255s) - { - action(r, g, b); - } - } + public static void AssertNamedColours(Action action) => AssertItems(TestColours.NamedColours, action); + public static void AssertRandomRgb255Colours(Action action) => AssertItems(TestColours.RandomRgb255Colours, action); + public static void AssertRandomRgbColours(Action action) => AssertItems(TestColours.RandomRgbColours, action); + public static void AssertRandomHsbColours(Action action) => AssertItems(TestColours.RandomHsbColours, action); + public static void AssertRandomHslColours(Action action) => AssertItems(TestColours.RandomHslColours, action); + public static void AssertRandomHexColours(Action action) => AssertItems(TestColours.RandomHexColours, action); - public static void AssertRandomRgbColours(Action action) + private static void AssertItems(List itemsToAssert, Action assertAction) { - foreach (var (r, g, b) in TestColours.RandomRGBs) + foreach (var itemToAssert in itemsToAssert) { - action(r, g, b); + assertAction(itemToAssert); } } - - public static void AssertRandomHsbColours(Action action) - { - foreach (var (r, g, b) in TestColours.RandomHSBs) - { - action(r, g, b); - } - } - - public static void AssertRandomHexs(Action action) + + public static void AssertColourTuple(ColourTuple actual, ColourTuple expected, double tolerance, bool hasHue = false, string? details = null) { - foreach (var hex in TestColours.RandomHexs) - { - action(hex); - } + double NormalisedFirst(double value) => hasHue ? value / 360.0 : value; + + string FailMessage(string channel) => $"{channel}{(details == null ? string.Empty : $" ({details})")}"; + Assert.That(NormalisedFirst(actual.First), Is.EqualTo(NormalisedFirst(expected.First)).Within(tolerance), FailMessage("Channel 1")); + Assert.That(actual.Second, Is.EqualTo(expected.Second).Within(tolerance), FailMessage("Channel 2")); + Assert.That(actual.Third, Is.EqualTo(expected.Third).Within(tolerance), FailMessage("Channel 3")); } } \ No newline at end of file diff --git a/Unicolour.Tests/Utils/ColorMineUtils.cs b/Unicolour.Tests/Utils/ColorMineUtils.cs deleted file mode 100644 index 734c87fb..00000000 --- a/Unicolour.Tests/Utils/ColorMineUtils.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace Wacton.Unicolour.Tests.Utils; - -using System; -using Wacton.Unicolour.Tests.Lookups; -using ColorMineRgb = ColorMine.ColorSpaces.Rgb; -using ColorMineHsb = ColorMine.ColorSpaces.Hsb; -using ColorMineXyz = ColorMine.ColorSpaces.Xyz; -using ColorMineLab = ColorMine.ColorSpaces.Lab; - -internal static class ColorMineUtils -{ - public static TestColour FromRgb(double r, double g, double b) => FromRgb(r, g, b, $"{r:F2} {g:F2} {b:F2}"); - private static TestColour FromRgb(double r, double g, double b, string name) - { - var r255 = (int)Math.Round(r * 255.0); - var g255 = (int)Math.Round(g * 255.0); - var b255 = (int)Math.Round(b * 255.0); - return FromRgb255(r255, g255, b255, name); - } - - public static TestColour FromRgb255(int r255, int g255, int b255) => FromRgb255(r255, g255, b255, $"{r255:000} {g255:000} {b255:000}"); - private static TestColour FromRgb255(int r255, int g255, int b255, string name) - { - var rgb = new ColorMineRgb { R = r255, G = g255, B = b255 }; - var hsb = rgb.To(); - var xyz = rgb.To(); - var lab = rgb.To(); - - return new TestColour - { - Name = name, - Rgb = (rgb.R / 255.0, rgb.G / 255.0, rgb.B / 255.0), - Hsb = (hsb.H, hsb.S, hsb.B), - Xyz = (xyz.X / 100.0, xyz.Y / 100.0, xyz.Z / 100.0), - Lab = (lab.L, lab.A, lab.B) - }; - } - - public static TestColour FromHsb(double h, double s, double b) => FromHsb(h, s, b, $"{h:F2} {s:F2} {b:F2}"); - public static TestColour FromHsb(double h, double s, double b, string name) - { - var hsb = new ColorMineHsb {H = h, S = s, B = b}; - var rgb = hsb.To(); - var xyz = hsb.To(); - var lab = hsb.To(); - - return new TestColour - { - Name = name, - Rgb = (rgb.R / 255.0, rgb.G / 255.0, rgb.B / 255.0), - Hsb = (hsb.H, hsb.S, hsb.B), - Xyz = (xyz.X / 100.0, xyz.Y / 100.0, xyz.Z / 100.0), - Lab = (lab.L, lab.A, lab.B) - }; - } -} \ No newline at end of file diff --git a/Unicolour.Tests/Lookups/ColourLimits.cs b/Unicolour.Tests/Utils/ColourLimits.cs similarity index 97% rename from Unicolour.Tests/Lookups/ColourLimits.cs rename to Unicolour.Tests/Utils/ColourLimits.cs index 8c55bf21..44f8c092 100644 --- a/Unicolour.Tests/Lookups/ColourLimits.cs +++ b/Unicolour.Tests/Utils/ColourLimits.cs @@ -1,4 +1,4 @@ -namespace Wacton.Unicolour.Tests.Lookups; +namespace Wacton.Unicolour.Tests.Utils; using System.Collections.Generic; diff --git a/Unicolour.Tests/Utils/ColourfulUtils.cs b/Unicolour.Tests/Utils/ColourfulUtils.cs deleted file mode 100644 index 347dabdb..00000000 --- a/Unicolour.Tests/Utils/ColourfulUtils.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Wacton.Unicolour.Tests.Utils; - -using Colourful; -using Wacton.Unicolour.Tests.Lookups; - -internal static class ColourfulUtils -{ - public static TestColour FromRgb255(int r255, int g255, int b255) => FromRgb255(r255, g255, b255, $"{r255:000} {g255:000} {b255:000}"); - private static TestColour FromRgb255(int r255, int g255, int b255, string name) - { - var r = r255 / 255.0; - var g = g255 / 255.0; - var b = b255 / 255.0; - return FromRgb(r, g, b, name); - } - - public static TestColour FromRgb(double r, double g, double b) => FromRgb(r, g, b, $"{r:F2} {g:F2} {b:F2}"); - private static TestColour FromRgb(double r, double g, double b, string name) - { - var rgb = new RGBColor(r, g, b); - - var rgbLinearConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.sRGB).ToLinearRGB().Build(); - var rgbLinear = rgbLinearConverter.Convert(rgb); - - var xyzConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.sRGB).ToXYZ(Illuminants.D65).Build(); - var xyz = xyzConverter.Convert(rgb); - - var labConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.sRGB).ToLab(Illuminants.D65).Build(); - var lab = labConverter.Convert(rgb); - - return new TestColour - { - Name = name, - Rgb = (rgb.R, rgb.G, rgb.B), - RgbLinear = (rgbLinear.R, rgbLinear.G, rgbLinear.B), - Xyz = (xyz.X, xyz.Y, xyz.Z), - Lab = (lab.L, lab.a, lab.b) - }; - } -} \ No newline at end of file diff --git a/Unicolour.Tests/Lookups/NamedColours.csv b/Unicolour.Tests/Utils/NamedColours.csv similarity index 100% rename from Unicolour.Tests/Lookups/NamedColours.csv rename to Unicolour.Tests/Utils/NamedColours.csv diff --git a/Unicolour.Tests/Utils/OpenCvColours.csv b/Unicolour.Tests/Utils/OpenCvColours.csv new file mode 100644 index 00000000..b640249c --- /dev/null +++ b/Unicolour.Tests/Utils/OpenCvColours.csv @@ -0,0 +1,145 @@ +Alice Blue, 0.9411764740943909, 0.9725490212440491, 1, 208.00006103515625, 0.058823518455028534, 1, 208, 1, 0.970588207244873, 0.8754762411117554, 0.9287939667701721, 1.0789587497711182, 93.682861328125, -2.484375, -9.5625 +Antique White, 0.9803921580314636, 0.9215686321258545, 0.843137264251709, 34.28568649291992, 0.13999997079372406, 0.9803921580314636, 34.28571319580078, 0.7777780294418335, 0.9117647409439087, 0.8139658570289612, 0.8464831709861755, 0.7632243633270264, 86.553955078125, 5.5, 23.359375 +Aqua, 0, 1, 1, 180, 0.9999998807907104, 1, 180, 1, 0.5, 0.5380030274391174, 0.7873290181159973, 1.0694199800491333, 91.11328125, -48.09375, -14.125 +Aquamarine, 0.49803921580314636, 1, 0.8313725590705872, 159.84375, 0.5019606947898865, 1, 159.84375, 1, 0.7490196228027344, 0.5639011859893799, 0.807809591293335, 0.7489018440246582, 89.447021484375, -66.78125, 28.40625 +Azure, 0.9411764740943909, 1, 1, 179.9998779296875, 0.058823518455028534, 1, 180, 1, 0.970588207244873, 0.897400975227356, 0.9726435542106628, 1.0862669944763184, 97.747802734375, -10.53125, -3.5625 +Beige, 0.9607843160629272, 0.9607843160629272, 0.8627451062202454, 59.99992370605469, 0.10204079747200012, 0.9607843160629272, 60.000003814697266, 0.5555555820465088, 0.9117647409439087, 0.8322436213493347, 0.8988521099090576, 0.8065600991249084, 91.094970703125, -8, 24.546875 +Bisque, 1, 0.8941176533699036, 0.7686274647712708, 32.5423583984375, 0.23137250542640686, 1, 32.54237365722656, 1, 0.884313702583313, 0.7894670963287354, 0.8073461651802063, 0.6363427639007568, 83.673095703125, 13.46875, 36.5 +Black, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +Blanched Almond, 1, 0.9215686321258545, 0.8039215803146362, 35.99998092651367, 0.19607838988304138, 1, 36, 1, 0.9019607901573181, 0.8196672201156616, 0.8508632779121399, 0.6984653472900391, 87.2802734375, 7.796875, 33.546875 +Blue, 0, 0, 1, 240, 0.9999998807907104, 1, 240, 1, 0.5, 0.18042300641536713, 0.0721689984202385, 0.9502270221710205, 32.293701171875, 79.1875, -107.859375 +Blue Violet, 0.5411764979362488, 0.16862745583057404, 0.886274516582489, 271.1475524902344, 0.8097344040870667, 0.886274516582489, 271.1475524902344, 0.7593361735343933, 0.5274509787559509, 0.25068020820617676, 0.12621363997459412, 0.7304641008377075, 27.099609375, 64.625, -81.25 +Brown, 0.6470588445663452, 0.16470588743686676, 0.16470588743686676, 0, 0.7454544305801392, 0.6470588445663452, 0, 0.5942029356956482, 0.4058823585510254, 0.16764701902866364, 0.09824936091899872, 0.032035328447818756, 18.353271484375, 37.6875, 25.890625 +Burlywood, 0.8705882430076599, 0.7215686440467834, 0.529411792755127, 33.7930908203125, 0.39189180731773376, 0.8705882430076599, 33.7931022644043, 0.5686275362968445, 0.7000000476837158, 0.5163891315460205, 0.5156236290931702, 0.3014764189720154, 56.8359375, 19.359375, 42.4375 +Cadet Blue, 0.37254902720451355, 0.6196078658103943, 0.6274510025978088, 181.84617614746094, 0.4062499403953552, 0.6274510025978088, 181.84616088867188, 0.2549019753932953, 0.5, 0.23288553953170776, 0.2942303717136383, 0.377002090215683, 33.642578125, -17.546875, -7 +Chartreuse, 0.49803921580314636, 1, 0, 90.1176528930664, 0.9999998807907104, 1, 90.11764526367188, 1, 0.5, 0.44511520862579346, 0.7602953314781189, 0.12329627573490143, 88.1103515625, -82.765625, 83.640625 +Chocolate, 0.8235294222831726, 0.4117647111415863, 0.11764705926179886, 24.999996185302734, 0.8571427464485168, 0.8235294222831726, 25.000001907348633, 0.75, 0.47058823704719543, 0.31867295503616333, 0.23902495205402374, 0.04163479432463646, 36.273193359375, 50.359375, 48.09375 +Coral, 1, 0.49803921580314636, 0.3137255012989044, 16.114280700683594, 0.6862744688987732, 1, 16.11428451538086, 1, 0.656862735748291, 0.5028159618377686, 0.3702393174171448, 0.12085746228694916, 56.036376953125, 72.421875, 63.34375 +Cornflower Blue, 0.3921568691730499, 0.5843137502670288, 0.929411768913269, 218.54014587402344, 0.5780590176582336, 0.929411768913269, 218.54014587402344, 0.791907548904419, 0.6607843041419983, 0.31282591819763184, 0.3031572103500366, 0.8430083990097046, 38.653564453125, 37.96875, -74.96875 +Cornsilk, 1, 0.9725490212440491, 0.8627451062202454, 47.99995803833008, 0.13725487887859344, 1, 48, 1, 0.9313725233078003, 0.8772358298301697, 0.9356323480606079, 0.8112900257110596, 94.488525390625, -3, 29.46875 +Crimson, 0.8627451062202454, 0.0784313753247261, 0.23529411852359772, 348, 0.9090908169746399, 0.8627451062202454, 348, 0.8333333730697632, 0.47058823704719543, 0.3058439791202545, 0.1604711264371872, 0.05760817229747772, 37.9150390625, 62.203125, 47.8125 +Cyan, 0, 1, 1, 180, 0.9999998807907104, 1, 180, 1, 0.5, 0.5380030274391174, 0.7873290181159973, 1.0694199800491333, 91.11328125, -48.09375, -14.125 +Dark Blue, 0, 0, 0.545098066329956, 240, 0.9999997615814209, 0.545098066329956, 240, 1, 0.272549033164978, 0.04658212512731552, 0.018632797524333, 0.24533233046531677, 3.533935546875, 24.4375, -38.578125 +Dark Cyan, 0, 0.545098066329956, 0.545098066329956, 179.99998474121094, 0.9999997615814209, 0.545098066329956, 180, 1, 0.272549033164978, 0.13890314102172852, 0.20327484607696533, 0.2761059105396271, 24.49951171875, -18.125, -5.34375 +Dark Goldenrod, 0.7215686440467834, 0.5254902243614197, 0.04313725605607033, 42.65895080566406, 0.9402172565460205, 0.7215686440467834, 42.658958435058594, 0.8871794939041138, 0.38235294818878174, 0.283547043800354, 0.2726714313030243, 0.04086246341466904, 32.8369140625, 23.109375, 43.09375 +Dark Gray, 0.6627451181411743, 0.6627451181411743, 0.6627451181411743, 0, 0, 0.6627451181411743, 0, 0, 0.6627451181411743, 0.37709841132164, 0.3967552185058594, 0.4319688677787781, 42.840576171875, 0.03125, 0.015625 +Dark Green, 0, 0.3921568691730499, 0, 120, 0.9999997019767761, 0.3921568691730499, 120, 1, 0.19607843458652496, 0.04556916654109955, 0.0911383330821991, 0.015189680270850658, 9.423828125, -18.984375, 13.71875 +Dark Khaki, 0.7411764860153198, 0.7176470756530762, 0.41960784792900085, 55.609737396240234, 0.4338623583316803, 0.7411764860153198, 55.60975646972656, 0.38317760825157166, 0.5803921818733215, 0.4057421386241913, 0.4574859142303467, 0.20598962903022766, 49.98779296875, -6.875, 44.703125 +Dark Magenta, 0.545098066329956, 0, 0.545098066329956, 300, 0.9999997615814209, 0.545098066329956, 300, 1, 0.272549033164978, 0.15307042002677917, 0.07354079931974411, 0.2503240406513214, 12.8662109375, 37.171875, -22.96875 +Dark Olive Green, 0.3333333432674408, 0.41960784792900085, 0.18431372940540314, 82.00001525878906, 0.560747504234314, 0.41960784792900085, 82, 0.3896103799343109, 0.3019607961177826, 0.09517066180706024, 0.12651889026165009, 0.046292148530483246, 12.87841796875, -12.5625, 15.859375 +Dark Orange, 1, 0.5490196347236633, 0, 32.9411735534668, 0.9999998807907104, 1, 32.94117736816406, 1, 0.5, 0.5062285661697388, 0.40022218227386475, 0.05059244483709335, 57.330322265625, 68.484375, 68.84375 +Dark Orchid, 0.6000000238418579, 0.19607843458652496, 0.800000011920929, 280.1298522949219, 0.7549018859863281, 0.800000011920929, 280.1298828125, 0.6062992215156555, 0.49803921580314636, 0.25173529982566833, 0.13413403928279877, 0.5837336778640747, 24.517822265625, 55.921875, -61.5 +Dark Red, 0.545098066329956, 0, 0, 0, 0.9999997615814209, 0.545098066329956, 0, 1, 0.272549033164978, 0.10648829489946365, 0.05490800365805626, 0.004991707392036915, 10.174560546875, 30.28125, 16.0625 +Dark Salmon, 0.9137254953384399, 0.5882353186607361, 0.47843137383461, 15.135133743286133, 0.47639480233192444, 0.9137254953384399, 15.135137557983398, 0.7161290049552917, 0.6960784196853638, 0.4802568256855011, 0.4054543673992157, 0.23703771829605103, 50.69580078125, 49.890625, 43.015625 +Dark Sea Green, 0.5607843399047852, 0.7372549176216125, 0.5607843399047852, 120, 0.23936164379119873, 0.7372549176216125, 120, 0.25139662623405457, 0.6490195989608765, 0.3426717221736908, 0.4378833770751953, 0.32625696063041687, 48.47412109375, -31.84375, 25.796875 +Dark Slate Blue, 0.2823529541492462, 0.239215686917305, 0.545098066329956, 248.46153259277344, 0.5611510276794434, 0.545098066329956, 248.4615478515625, 0.39000004529953003, 0.3921568691730499, 0.08999692648649216, 0.06578757613897324, 0.252147376537323, 6.9091796875, 21.65625, -33.015625 +Dark Slate Gray, 0.18431372940540314, 0.30980393290519714, 0.30980393290519714, 179.99993896484375, 0.4050631523132324, 0.30980393290519714, 180, 0.2539682388305664, 0.24705883860588074, 0.053789474070072174, 0.06760461628437042, 0.08416478335857391, 5.46875, -4.3125, -1.515625 +Dark Turquoise, 0, 0.8078431487083435, 0.8196078538894653, 180.8612518310547, 0.9999998807907104, 0.8196078538894653, 180.86123657226562, 1, 0.40980392694473267, 0.3357378840446472, 0.48741617798805237, 0.6794284582138062, 58.85009765625, -31.9375, -12.4375 +Dark Violet, 0.5803921818733215, 0, 0.8274509906768799, 282.0852966308594, 0.9999998807907104, 0.8274509906768799, 282.0852966308594, 1, 0.41372549533843994, 0.23967167735099792, 0.10999131202697754, 0.6247087717056274, 24.49951171875, 59.84375, -68.859375 +Deep Pink, 1, 0.0784313753247261, 0.5764706134796143, 327.574462890625, 0.9215685129165649, 1, 327.574462890625, 1, 0.5392156839370728, 0.46759656071662903, 0.23873063921928406, 0.29741615056991577, 53.814697265625, 81.453125, 34.9375 +Deep Sky Blue, 0, 0.7490196228027344, 1, 195.05882263183594, 0.9999998807907104, 1, 195.05882263183594, 1, 0.5, 0.36672061681747437, 0.4447641968727112, 1.0123260021209717, 55.938720703125, 16, -68.9375 +Dim Gray, 0.4117647111415863, 0.4117647111415863, 0.4117647111415863, 0, 0, 0.4117647111415863, 0, 0, 0.4117647111415863, 0.13426454365253448, 0.14126330614089966, 0.15380096435546875, 14.166259765625, 0.109375, 0.046875 +Dodger Blue, 0.11764705926179886, 0.5647059082984924, 1, 209.60000610351562, 0.8823528289794922, 1, 209.60000610351562, 1, 0.5588235259056091, 0.28550490736961365, 0.27438414096832275, 0.9837203025817871, 40.753173828125, 54.34375, -93.78125 +Firebrick, 0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 0, 0.808988630771637, 0.6980392336845398, 0, 0.6792452931404114, 0.4156862795352936, 0.19223062694072723, 0.10727573186159134, 0.02571427822113037, 22.332763671875, 43.15625, 32.078125 +Floral White, 1, 0.9803921580314636, 0.9411764740943909, 39.99991989135742, 0.058823518455028534, 1, 40, 1, 0.970588207244873, 0.9115046262741089, 0.9592306017875671, 0.9612758755683899, 96.39892578125, 0.359375, 11.703125 +Forest Green, 0.13333334028720856, 0.545098066329956, 0.13333334028720856, 120, 0.7553955316543579, 0.545098066329956, 120, 0.6069364547729492, 0.3392156958580017, 0.10180484503507614, 0.18919843435287476, 0.046282973140478134, 23.333740234375, -31.421875, 29.3125 +Fuchsia, 1, 0, 1, 300, 0.9999998807907104, 1, 300, 1, 0.5, 0.5928760170936584, 0.2848399877548218, 0.9695610404014587, 60.321044921875, 98.234375, -60.828125 +Gainsboro, 0.8627451062202454, 0.8627451062202454, 0.8627451062202454, 0, 0, 0.8627451062202454, 0, 0, 0.8627451062202454, 0.6802351474761963, 0.715693473815918, 0.7792141437530518, 74.15771484375, 0.015625, 0 +Ghost White, 0.9725490212440491, 0.9725490212440491, 1, 240, 0.02745097503066063, 1, 240, 1, 0.9862744808197021, 0.9032419919967651, 0.9431107044219971, 1.0802602767944336, 94.88525390625, 2.90625, -7.65625 +Gold, 1, 0.843137264251709, 0, 50.58823013305664, 0.9999998807907104, 1, 50.588233947753906, 1, 0.5, 0.6554437875747681, 0.6986526250839233, 0.10033071041107178, 76.7333984375, 19.84375, 80.140625 +Goldenrod, 0.8549019694328308, 0.6470588445663452, 0.125490203499794, 42.903221130371094, 0.8532109260559082, 0.8549019694328308, 42.903228759765625, 0.7440000176429749, 0.4901960790157318, 0.426321417093277, 0.4192340672016144, 0.07212784886360168, 49.267578125, 28.4375, 56.875 +Gray, 0.7450980544090271, 0.7450980544090271, 0.7450980544090271, 0, 0, 0.7450980544090271, 0, 0, 0.7450980544090271, 0.4894065856933594, 0.5149176716804504, 0.5606186985969543, 54.7607421875, 0.03125, 0.015625 +Web Gray, 0.501960813999176, 0.501960813999176, 0.501960813999176, 0, 0, 0.501960813999176, 0, 0, 0.501960813999176, 0.20516590774059296, 0.2158605009317398, 0.23501898348331451, 22.979736328125, 0.03125, 0.015625 +Green, 0, 1, 0, 120, 0.9999998807907104, 1, 120, 1, 0.5, 0.3575800061225891, 0.7151600122451782, 0.11919300258159637, 87.738037109375, -86.1875, 83.171875 +Web Green, 0, 0.501960813999176, 0, 120, 0.9999997615814209, 0.501960813999176, 120, 1, 0.250980406999588, 0.07718739658594131, 0.15437479317188263, 0.025729062035679817, 18.84765625, -28.9375, 26.015625 +Green Yellow, 0.6784313917160034, 1, 0.18431372940540314, 83.65385437011719, 0.8156861662864685, 1, 83.65383911132812, 1, 0.5921568870544434, 0.5350667238235474, 0.8060835003852844, 0.1542835831642151, 89.208984375, -73.375, 84.453125 +Honeydew, 0.9411764740943909, 1, 0.9411764740943909, 120, 0.058823518455028534, 1, 120, 1, 0.970588207244873, 0.8741925954818726, 0.9633602499961853, 0.9640365839004517, 96.966552734375, -16.5625, 12.171875 +Hot Pink, 1, 0.4117647111415863, 0.7058823704719543, 330, 0.5882351994514465, 1, 330, 1, 0.7058823704719543, 0.5453130006790161, 0.3466355800628662, 0.46986567974090576, 55.8837890625, 79.96875, 12.65625 +Indian Red, 0.8039215803146362, 0.3607843220233917, 0.3607843220233917, 0, 0.5512194037437439, 0.8039215803146362, 0, 0.5305164456367493, 0.5823529362678528, 0.3093794584274292, 0.21409708261489868, 0.12625597417354584, 33.72802734375, 50.890625, 34.703125 +Indigo, 0.29411765933036804, 0, 0.5098039507865906, 274.6153869628906, 0.9999997615814209, 0.5098039507865906, 274.6153869628906, 1, 0.2549019753932953, 0.06929568946361542, 0.031073689460754395, 0.21347758173942566, 3.8330078125, 23.5, -31.5625 +Ivory, 1, 1, 0.9411764740943909, 59.9998779296875, 0.058823518455028534, 1, 60, 1, 0.970588207244873, 0.9272476434707642, 0.9907166957855225, 0.9665235280990601, 99.249267578125, -5.390625, 15.75 +Khaki, 0.9411764740943909, 0.9019607901573181, 0.5490196347236633, 53.99998474121094, 0.41666659712791443, 0.9411764740943909, 54.000003814697266, 0.7692306637763977, 0.7450980544090271, 0.6896663904190063, 0.7701455354690552, 0.36036184430122375, 80.77392578125, -7.578125, 66.546875 +Lavender, 0.9019607901573181, 0.9019607901573181, 0.9803921580314636, 240, 0.07999998331069946, 0.9803921580314636, 240, 0.6666669845581055, 0.9411764740943909, 0.7818050980567932, 0.8031823635101318, 1.0180078744888306, 82.452392578125, 8.34375, -20.53125 +Lavender Blush, 1, 0.9411764740943909, 0.9607843160629272, 340.0000305175781, 0.058823518455028534, 1, 340, 1, 0.970588207244873, 0.8887804746627808, 0.9017353057861328, 0.9908458590507507, 91.4306640625, 13.015625, -0.921875 +Lawn Green, 0.48627451062202454, 0.9882352948188782, 0, 90.4761962890625, 0.9999998807907104, 0.9882352948188782, 90.47618865966797, 1, 0.4941176474094391, 0.4312170445919037, 0.7390342950820923, 0.11992476135492325, 85.9375, -81.125, 81.890625 +Lemon Chiffon, 1, 0.9803921580314636, 0.8039215803146362, 53.999969482421875, 0.19607838988304138, 1, 54.000003814697266, 1, 0.9019607901573181, 0.864437460899353, 0.9404037594795227, 0.7133886814117432, 95.196533203125, -8.34375, 43.84375 +Light Blue, 0.6784313917160034, 0.8470588326454163, 0.9019607901573181, 194.7368621826172, 0.24782603979110718, 0.9019607901573181, 194.73684692382812, 0.5327103734016418, 0.7901960611343384, 0.5606712102890015, 0.637069046497345, 0.8418401479721069, 67.95654296875, -14.46875, -20.40625 +Light Coral, 0.9411764740943909, 0.501960813999176, 0.501960813999176, 0, 0.466666579246521, 0.9411764740943909, 0, 0.7887322902679443, 0.7215686440467834, 0.47553157806396484, 0.3552677631378174, 0.2476925551891327, 50.152587890625, 63.46875, 40.25 +Light Cyan, 0.8784313797950745, 1, 1, 179.99993896484375, 0.12156860530376434, 1, 180, 1, 0.9392156600952148, 0.8454471826553345, 0.9458548426628113, 1.0838316679000854, 95.855712890625, -20.125, -6.578125 +Light Goldenrod, 0.9803921580314636, 0.9803921580314636, 0.8235294222831726, 59.99995422363281, 0.15999996662139893, 0.9803921580314636, 60, 0.8000002503395081, 0.9019607901573181, 0.8524099588394165, 0.9334931969642639, 0.7448301315307617, 94.427490234375, -11.75, 38.5 +Light Gray, 0.8274509906768799, 0.8274509906768799, 0.8274509906768799, 0, 0, 0.8274509906768799, 0, 0, 0.8274509906768799, 0.619132399559021, 0.6514056324958801, 0.7092204689979553, 68.06640625, 0.015625, 0 +Light Green, 0.5647059082984924, 0.9333333373069763, 0.5647059082984924, 120, 0.39495790004730225, 0.9333333373069763, 120, 0.7343750596046448, 0.7490196228027344, 0.47107797861099243, 0.69089674949646, 0.3723141551017761, 77.1240234375, -65.984375, 58.671875 +Light Pink, 1, 0.7137255072593689, 0.7568627595901489, 350.95892333984375, 0.2862744629383087, 1, 350.9588928222656, 1, 0.8568627834320068, 0.6759384274482727, 0.5856972932815552, 0.5818241834640503, 66.705322265625, 53.203125, 16.15625 +Light Salmon, 1, 0.6274510025978088, 0.47843137383461, 17.142854690551758, 0.5215685963630676, 1, 17.142858505249023, 1, 0.7392156720161438, 0.5732675194740295, 0.4781184494495392, 0.24616536498069763, 60.565185546875, 60.953125, 55.46875 +Light Sea Green, 0.125490203499794, 0.6980392336845398, 0.6666666865348816, 176.71231079101562, 0.8202245831489563, 0.6980392336845398, 176.7123260498047, 0.6952381134033203, 0.4117647111415863, 0.23767849802970886, 0.35047221183776855, 0.43531426787376404, 42.46826171875, -29.65625, -1.625 +Light Sky Blue, 0.529411792755127, 0.8078431487083435, 0.9803921580314636, 202.95652770996094, 0.4599999189376831, 0.9803921580314636, 202.95651245117188, 0.9200001358985901, 0.7549020051956177, 0.49310988187789917, 0.5619192719459534, 0.9866426587104797, 63.153076171875, 0.625, -51.046875 +Light Slate Gray, 0.46666666865348816, 0.5333333611488342, 0.6000000238418579, 210.00001525878906, 0.2222222089767456, 0.6000000238418579, 210, 0.14285717904567719, 0.5333333611488342, 0.2215970903635025, 0.23829501867294312, 0.33560386300086975, 25.946044921875, -0.921875, -13.140625 +Light Steel Blue, 0.6901960968971252, 0.7686274647712708, 0.8705882430076599, 213.91305541992188, 0.20720715820789337, 0.8705882430076599, 213.9130401611328, 0.410714328289032, 0.7803921699523926, 0.5082481503486633, 0.5398250222206116, 0.7682933807373047, 57.84912109375, 1.390625, -27.25 +Light Yellow, 1, 1, 0.8784313797950745, 59.999942779541016, 0.12156860530376434, 1, 60, 1, 0.9392156600952148, 0.9045210480690002, 0.9816260933876038, 0.8468302488327026, 98.626708984375, -9.96875, 31.296875 +Lime, 0, 1, 0, 120, 0.9999998807907104, 1, 120, 1, 0.5, 0.3575800061225891, 0.7151600122451782, 0.11919300258159637, 87.738037109375, -86.1875, 83.171875 +Lime Green, 0.19607843458652496, 0.8039215803146362, 0.19607843458652496, 120, 0.7560974955558777, 0.8039215803146362, 120, 0.6078431606292725, 0.5, 0.2372114062309265, 0.44568729400634766, 0.1036919504404068, 55.72509765625, -58.8125, 56.28125 +Linen, 0.9803921580314636, 0.9411764740943909, 0.9019607901573181, 29.999954223632812, 0.07999998331069946, 0.9803921580314636, 30, 0.6666669845581055, 0.9411764740943909, 0.8486459255218506, 0.8835818767547607, 0.8742563128471375, 89.73388671875, 4.125, 12.6875 +Magenta, 1, 0, 1, 300, 0.9999998807907104, 1, 300, 1, 0.5, 0.5928760170936584, 0.2848399877548218, 0.9695610404014587, 60.321044921875, 98.234375, -60.828125 +Maroon, 0.6901960968971252, 0.1882352977991104, 0.3764705955982208, 337.5, 0.727272629737854, 0.6901960968971252, 337.5, 0.5714285969734192, 0.43921568989753723, 0.2107411026954651, 0.12191140651702881, 0.12306557595729828, 22.320556640625, 42.5, 17.859375 +Web Maroon, 0.501960813999176, 0, 0, 0, 0.9999997615814209, 0.501960813999176, 0, 1, 0.250980406999588, 0.08903230726718903, 0.045907266438007355, 0.004173446912318468, 7.2998046875, 26.734375, 11.546875 +Medium Aquamarine, 0.4000000059604645, 0.8039215803146362, 0.6666666865348816, 159.61163330078125, 0.5024389624595642, 0.8039215803146362, 159.6116485595703, 0.5073891878128052, 0.6019607782363892, 0.3456289768218994, 0.4938696026802063, 0.45730581879615784, 56.951904296875, -45.140625, 18.890625 +Medium Blue, 0, 0, 0.8039215803146362, 240, 0.9999998807907104, 0.8039215803146362, 240, 1, 0.4019607901573181, 0.11014744639396667, 0.04405885189771652, 0.5801093578338623, 17.3583984375, 54.703125, -74.5 +Medium Orchid, 0.729411780834198, 0.3333333432674408, 0.8274509906768799, 288.09521484375, 0.597156286239624, 0.8274509906768799, 288.0952453613281, 0.5887851119041443, 0.5803921818733215, 0.35253477096557617, 0.2164035439491272, 0.6393042802810669, 33.404541015625, 61.03125, -53.953125 +Medium Purple, 0.5764706134796143, 0.43921568989753723, 0.8588235378265381, 259.62615966796875, 0.48858439922332764, 0.8588235378265381, 259.62615966796875, 0.597765326499939, 0.6490195989608765, 0.3060874342918396, 0.22905084490776062, 0.6980715990066528, 30.28564453125, 50.640625, -67.96875 +Medium Sea Green, 0.23529411852359772, 0.7019608020782471, 0.4431372582912445, 146.72268676757812, 0.6648043990135193, 0.7019608020782471, 146.72268676757812, 0.49790799617767334, 0.4686274528503418, 0.2096228152513504, 0.34391117095947266, 0.21151721477508545, 41.845703125, -43.359375, 32.015625 +Medium Slate Blue, 0.48235294222831726, 0.40784314274787903, 0.9333333373069763, 248.50746154785156, 0.5630251169204712, 0.9333333373069763, 248.50746154785156, 0.7976190447807312, 0.6705882549285889, 0.28545498847961426, 0.2028283178806305, 0.8327666521072388, 31.658935546875, 62.015625, -87.640625 +Medium Spring Green, 0, 0.9803921580314636, 0.6039215922355652, 156.95999145507812, 0.9999998807907104, 0.9803921580314636, 156.9600067138672, 1, 0.4901960790157318, 0.4001394212245941, 0.7069948315620422, 0.4210047423839569, 84.503173828125, -79.15625, 61.90625 +Medium Turquoise, 0.2823529541492462, 0.8196078538894653, 0.800000011920929, 177.81019592285156, 0.6555023193359375, 0.8196078538894653, 177.81021118164062, 0.5982533097267151, 0.5509803891181946, 0.36366453766822815, 0.5133431553840637, 0.6510230898857117, 60.260009765625, -36.171875, -5.53125 +Medium Violet Red, 0.7803921699523926, 0.08235294371843338, 0.5215686559677124, 322.2471923828125, 0.8944722414016724, 0.7803921699523926, 322.2471923828125, 0.8090909123420715, 0.4313725531101227, 0.28056198358535767, 0.14375197887420654, 0.23481225967407227, 30.4443359375, 54.640625, 9.328125 +Midnight Blue, 0.09803921729326248, 0.09803921729326248, 0.43921568989753723, 240, 0.7767854928970337, 0.43921568989753723, 240, 0.6350364685058594, 0.26862746477127075, 0.036719486117362976, 0.020713143050670624, 0.15531133115291595, 1.947021484375, 9.859375, -22.6875 +Mint Cream, 0.9607843160629272, 1, 0.9803921580314636, 149.99990844726562, 0.039215680211782455, 1, 150, 1, 0.9803921580314636, 0.9066698551177979, 0.978341281414032, 1.0452386140823364, 98.150634765625, -9.359375, 2.984375 +Misty Rose, 1, 0.8941176533699036, 0.8823529481887817, 5.999993801116943, 0.11764703691005707, 1, 6, 1, 0.9411764740943909, 0.8257196545600891, 0.821847140789032, 0.8272725939750671, 84.649658203125, 19.546875, 10.859375 +Moccasin, 1, 0.8941176533699036, 0.7098039388656616, 38.10809326171875, 0.290196031332016, 1, 38.10810852050781, 1, 0.8549019694328308, 0.773240864276886, 0.8008556365966797, 0.5508846044540405, 83.349609375, 11.421875, 47.65625 +Navajo White, 1, 0.8705882430076599, 0.6784313917160034, 35.85364532470703, 0.3215685784816742, 1, 35.85365676879883, 1, 0.8392156958580017, 0.7490472197532654, 0.7652256488800049, 0.5034854412078857, 80.487060546875, 16.515625, 49.828125 +Navy Blue, 0, 0, 0.501960813999176, 240, 0.9999997615814209, 0.501960813999176, 240, 1, 0.250980406999588, 0.03894620016217232, 0.015578435733914375, 0.20511648058891296, 2.47802734375, 17.421875, -32.34375 +Old Lace, 0.9921568632125854, 0.9607843160629272, 0.9019607901573181, 39.1303825378418, 0.09090907126665115, 0.9921568632125854, 39.13043212890625, 0.8518518805503845, 0.9470587968826294, 0.8744063377380371, 0.9190149903297424, 0.879738450050354, 92.852783203125, 1.09375, 17.171875 +Olive, 0.501960813999176, 0.501960813999176, 0, 59.99998474121094, 0.9999997615814209, 0.501960813999176, 60, 1, 0.250980406999588, 0.16621971130371094, 0.20028206706047058, 0.029902508482336998, 22.015380859375, -7.21875, 30.421875 +Olive Drab, 0.41960784792900085, 0.5568627715110779, 0.13725490868091583, 79.62619018554688, 0.7535209655761719, 0.5568627715110779, 79.62617492675781, 0.604519784450531, 0.34705883264541626, 0.16039887070655823, 0.2259306162595749, 0.05105489119887352, 25.665283203125, -23.078125, 31.984375 +Orange, 1, 0.6470588445663452, 0, 38.823524475097656, 0.9999998807907104, 1, 38.82353210449219, 1, 0.5, 0.5469968318939209, 0.48175865411758423, 0.06418181210756302, 61.279296875, 57.75, 70.78125 +Orange Red, 1, 0.2705882489681244, 0, 16.235292434692383, 0.9999998807907104, 1, 16.235294342041016, 1, 0.5, 0.4337330162525177, 0.2552310526371002, 0.02642732299864292, 53.61328125, 79.015625, 67.328125 +Orchid, 0.8549019694328308, 0.43921568989753723, 0.8392156958580017, 302.26416015625, 0.4862384796142578, 0.8549019694328308, 302.26416015625, 0.588888943195343, 0.6470588445663452, 0.46843424439430237, 0.313510537147522, 0.6718415021896362, 44.287109375, 67.21875, -39.28125 +Pale Goldenrod, 0.9333333373069763, 0.9098039269447327, 0.6666666865348816, 54.70585632324219, 0.2857142388820648, 0.9333333373069763, 54.70588302612305, 0.6666667461395264, 0.800000011920929, 0.7137202620506287, 0.7879424691200256, 0.4946836233139038, 81.73828125, -9.171875, 52.34375 +Pale Green, 0.5960784554481506, 0.9843137264251709, 0.5960784554481506, 120, 0.3944222331047058, 0.9843137264251709, 120, 0.9252336621284485, 0.7901960611343384, 0.5311088562011719, 0.7793415784835815, 0.41941505670547485, 85.955810546875, -72.625, 64.703125 +Pale Turquoise, 0.686274528503418, 0.9333333373069763, 0.9333333373069763, 179.99996948242188, 0.2647058367729187, 0.9333333373069763, 180, 0.6494846343994141, 0.8098039627075195, 0.6368032693862915, 0.7643305063247681, 0.9226345419883728, 80.8837890625, -31.0625, -9.59375 +Pale Violet Red, 0.8588235378265381, 0.43921568989753723, 0.5764706134796143, 340.37384033203125, 0.48858439922332764, 0.8588235378265381, 340.37384033203125, 0.597765326499939, 0.6490195989608765, 0.4027522802352905, 0.28758469223976135, 0.31025686860084534, 40.93017578125, 56.546875, 15.46875 +Papaya Whip, 1, 0.9372549057006836, 0.8352941274642944, 37.14282989501953, 0.16470585763454437, 1, 37.14285659790039, 1, 0.9176470637321472, 0.8411519527435303, 0.8779867887496948, 0.7544852495193481, 89.459228515625, 5.296875, 29.296875 +Peach Puff, 1, 0.8549019694328308, 0.7254902124404907, 28.285701751708984, 0.2745097577571869, 1, 28.285715103149414, 1, 0.8627451062202454, 0.7506852746009827, 0.7490838170051575, 0.5639030337333679, 78.857421875, 22.0625, 39.046875 +Peru, 0.8039215803146362, 0.5215686559677124, 0.24705882370471954, 29.57746124267578, 0.6926828622817993, 0.8039215803146362, 29.577468872070312, 0.5867769122123718, 0.5254902243614197, 0.34463950991630554, 0.3011631667613983, 0.08699263632297516, 38.299560546875, 37.796875, 45.1875 +Pink, 1, 0.7529411911964417, 0.7960784435272217, 349.5238037109375, 0.24705877900123596, 1, 349.5238037109375, 1, 0.8764705657958984, 0.7086877226829529, 0.6327421069145203, 0.6496396660804749, 69.879150390625, 47.5625, 11.625 +Plum, 0.8666666746139526, 0.6274510025978088, 0.8666666746139526, 300.0000305175781, 0.27601805329322815, 0.8666666746139526, 300, 0.4728682041168213, 0.7470588684082031, 0.5543830394744873, 0.4573570787906647, 0.7429462671279907, 52.9052734375, 51.359375, -33.234375 +Powder Blue, 0.6901960968971252, 0.8784313797950745, 0.9019607901573181, 186.6667022705078, 0.23478256165981293, 0.9019607901573181, 186.6666717529297, 0.519230842590332, 0.7960784435272217, 0.5883779525756836, 0.6825222969055176, 0.8491535186767578, 72.44873046875, -21.171875, -13.609375 +Purple, 0.6274510025978088, 0.125490203499794, 0.9411764740943909, 276.9230651855469, 0.8666665554046631, 0.9411764740943909, 276.923095703125, 0.8739495277404785, 0.5333333611488342, 0.3073701858520508, 0.1479761302471161, 0.8365147113800049, 32.94677734375, 73.046875, -87.984375 +Web Purple, 0.501960813999176, 0, 0.501960813999176, 300, 0.9999997615814209, 0.501960813999176, 300, 1, 0.250980406999588, 0.12797850370407104, 0.06148570030927658, 0.20928992331027985, 9.649658203125, 33.015625, -20.421875 +Rebecca Purple, 0.4000000059604645, 0.20000000298023224, 0.6000000238418579, 270, 0.666666567325592, 0.6000000238418579, 270, 0.5000000596046448, 0.4000000059604645, 0.12411271035671234, 0.07492164522409439, 0.309206485748291, 9.87548828125, 31.78125, -38.859375 +Red, 1, 0, 0, 0, 0.9999998807907104, 1, 0, 1, 0.5, 0.4124529957771301, 0.21267099678516388, 0.01933399960398674, 53.240966796875, 80.09375, 67.203125 +Rosy Brown, 0.7372549176216125, 0.5607843399047852, 0.5607843399047852, 0, 0.23936164379119873, 0.7372549176216125, 0, 0.25139662623405457, 0.6490195989608765, 0.35519424080848694, 0.32321077585220337, 0.3034681975841522, 36.712646484375, 24.875, 11.0625 +Royal Blue, 0.2549019753932953, 0.4117647111415863, 0.8823529481887817, 225, 0.7111109495162964, 0.8823529481887817, 225, 0.7272727489471436, 0.5686274766921997, 0.2081635594367981, 0.1666068732738495, 0.7333256006240845, 27.1484375, 52.328125, -80.0625 +Saddle Brown, 0.545098066329956, 0.2705882489681244, 0.07450980693101883, 24.9999942779541, 0.8633092045783997, 0.545098066329956, 25, 0.7594937086105347, 0.30980393290519714, 0.12894324958324432, 0.09793803095817566, 0.018272994086146355, 12.58544921875, 23.65625, 18.765625 +Salmon, 0.9803921580314636, 0.501960813999176, 0.4470588266849518, 6.1764726638793945, 0.5439999103546143, 0.9803921580314636, 6.176474094390869, 0.931506872177124, 0.7137255072593689, 0.501841127872467, 0.36982640624046326, 0.20410597324371338, 54.083251953125, 69.3125, 51.78125 +Sandy Brown, 0.95686274766922, 0.6431372761726379, 0.3764705955982208, 27.56756591796875, 0.6065572500228882, 0.95686274766922, 27.56757164001465, 0.8705880641937256, 0.6666666865348816, 0.5269815921783447, 0.46633121371269226, 0.17288833856582642, 57.208251953125, 49.90625, 59.21875 +Sea Green, 0.18039216101169586, 0.545098066329956, 0.34117648005485535, 146.45159912109375, 0.6690646409988403, 0.545098066329956, 146.4516143798828, 0.5027027726173401, 0.36274510622024536, 0.12078526616096497, 0.19733065366744995, 0.12186554074287415, 23.590087890625, -28.5, 20.1875 +Seashell, 1, 0.9607843160629272, 0.9333333373069763, 24.705839157104492, 0.06666665524244308, 1, 24.705883026123047, 1, 0.9666666984558105, 0.8932191729545593, 0.9273865818977356, 0.9406061172485352, 93.603515625, 5.25, 10.03125 +Sienna, 0.6274510025978088, 0.32156863808631897, 0.1764705926179886, 19.304340362548828, 0.7187498807907104, 0.6274510025978088, 19.30434799194336, 0.5609756708145142, 0.4019607901573181, 0.17989644408226013, 0.13699708878993988, 0.04178870469331741, 18.93310546875, 30.296875, 25.828125 +Silver, 0.7529411911964417, 0.7529411911964417, 0.7529411911964417, 0, 0, 0.7529411911964417, 0, 0, 0.7529411911964417, 0.5009996891021729, 0.5271151065826416, 0.5738986730575562, 55.926513671875, 0.015625, 0 +Sky Blue, 0.529411792755127, 0.8078431487083435, 0.9215686321258545, 197.40000915527344, 0.4255318343639374, 0.9215686321258545, 197.39999389648438, 0.7142857313156128, 0.7254902124404907, 0.4705203175544739, 0.5528834462165833, 0.8676709532737732, 61.65771484375, -10.375, -35.78125 +Slate Blue, 0.4156862795352936, 0.3529411852359772, 0.8039215803146362, 248.3478240966797, 0.5609755516052246, 0.8039215803146362, 248.3478240966797, 0.5348837375640869, 0.5784313678741455, 0.2061532735824585, 0.14783000946044922, 0.5950824618339539, 21.966552734375, 46.59375, -66.78125 +Slate Gray, 0.43921568989753723, 0.501960813999176, 0.5647059082984924, 210.00003051757812, 0.2222222089767456, 0.5647059082984924, 210, 0.12598426640033722, 0.501960813999176, 0.19433583319187164, 0.20896126329898834, 0.2938746213912964, 22.42431640625, -0.921875, -11.65625 +Snow, 1, 0.9803921580314636, 0.9803921580314636, 0, 0.019607840105891228, 1, 0, 1, 0.9901961088180542, 0.9267695546150208, 0.9653365612030029, 1.0416710376739502, 96.8994140625, 3.84375, 1.375 +Spring Green, 0, 1, 0.49803921580314636, 149.88235473632812, 0.9999998807907104, 1, 149.88235473632812, 1, 0.5, 0.3958713114261627, 0.7304764986038208, 0.3208603858947754, 87.8662109375, -84.515625, 74.84375 +Steel Blue, 0.27450981736183167, 0.5098039507865906, 0.7058823704719543, 207.27273559570312, 0.6111109852790833, 0.7058823704719543, 207.27272033691406, 0.4399999678134918, 0.4901961088180542, 0.1874300241470337, 0.20560768246650696, 0.46148544549942017, 24.560546875, 10.1875, -37.984375 +Tan, 0.8235294222831726, 0.7058823704719543, 0.5490196347236633, 34.285701751708984, 0.33333325386047363, 0.8235294222831726, 34.28571319580078, 0.4375000298023224, 0.686274528503418, 0.47633710503578186, 0.4823954105377197, 0.31605905294418335, 52.72216796875, 13.3125, 34.375 +Teal, 0, 0.501960813999176, 0.501960813999176, 179.99998474121094, 0.9999997615814209, 0.501960813999176, 180, 1, 0.250980406999588, 0.11613360047340393, 0.16995322704315186, 0.23084554076194763, 19.989013671875, -16.125, -4.75 +Thistle, 0.8470588326454163, 0.7490196228027344, 0.8470588326454163, 300.00006103515625, 0.11574071645736694, 0.8470588326454163, 300, 0.2427184134721756, 0.7980391979217529, 0.5934168100357056, 0.5681906342506409, 0.7278823256492615, 60.626220703125, 23.546875, -16.015625 +Tomato, 1, 0.38823530077934265, 0.27843138575553894, 9.130434036254883, 0.7215685248374939, 1, 9.130434036254883, 1, 0.6392157077789307, 0.4684373736381531, 0.3064501881599426, 0.09407974779605865, 54.351806640625, 77.125, 63.765625 +Turquoise, 0.250980406999588, 0.8784313797950745, 0.8156862854957581, 173.99998474121094, 0.7142855525016785, 0.8784313797950745, 174, 0.720720648765564, 0.5647059082984924, 0.4014909863471985, 0.5895079374313354, 0.6892006993293762, 69.00634765625, -46.453125, 3.671875 +Violet, 0.9333333373069763, 0.5098039507865906, 0.9333333373069763, 300, 0.4537814259529114, 0.9333333373069763, 300, 0.7605634331703186, 0.7215686440467834, 0.5867264270782471, 0.403179794549942, 0.8555747270584106, 54.876708984375, 77.96875, -48.9375 +Wheat, 0.9607843160629272, 0.8705882430076599, 0.7019608020782471, 39.09088897705078, 0.26938769221305847, 0.9607843160629272, 39.09090805053711, 0.7674418687820435, 0.8313725590705872, 0.7191405892372131, 0.7491186857223511, 0.5330684781074524, 78.302001953125, 8.078125, 42.4375 +White, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0.9504560232162476, 1, 1.0887540578842163, 100, 0, 0 +White Smoke, 0.9607843160629272, 0.9607843160629272, 0.9607843160629272, 0, 0, 0.9607843160629272, 0, 0, 0.9607843160629272, 0.8678600788116455, 0.9130986332893372, 0.9941397905349731, 92.205810546875, 0.015625, 0 +Yellow, 1, 1, 0, 59.99999237060547, 0.9999998807907104, 1, 60, 1, 0.5, 0.7700330018997192, 0.9278309941291809, 0.1385270059108734, 97.137451171875, -21.546875, 94.46875 +Yellow Green, 0.6039215922355652, 0.8039215803146362, 0.19607843458652496, 79.741943359375, 0.7560974955558777, 0.8039215803146362, 79.74193572998047, 0.6078431606292725, 0.5, 0.35733717679977417, 0.5076271295547485, 0.1093229204416275, 57.45849609375, -44.4375, 58.421875 diff --git a/Unicolour.Tests/Utils/OpenCvUtils.cs b/Unicolour.Tests/Utils/OpenCvUtils.cs deleted file mode 100644 index 4d16e443..00000000 --- a/Unicolour.Tests/Utils/OpenCvUtils.cs +++ /dev/null @@ -1,102 +0,0 @@ -namespace Wacton.Unicolour.Tests.Utils; - -using System.Collections.Generic; -using System.Drawing; -using System.Globalization; -using System.IO; -using OpenCvSharp; -using Wacton.Unicolour.Tests.Lookups; - -// https://docs.opencv.org/4.5.5/de/d25/imgproc_color_conversions.html -internal static class OpenCvUtils -{ - public static TestColour FromStored(string name) => TestColours.GetStoredOpenCvColour(name); - - public static TestColour FromRgb255(int r255, int g255, int b255) => FromRgb255(r255, g255, b255, $"{r255:000} {g255:000} {b255:000}"); - private static TestColour FromRgb255(int r255, int g255, int b255, string name) - { - var r = r255 / 255.0; - var g = g255 / 255.0; - var b = b255 / 255.0; - return FromRgb(r, g, b, name); - } - - public static TestColour FromRgb(double r, double g, double b) => FromRgb(r, g, b, $"{r:F2} {g:F2} {b:F2}"); - private static TestColour FromRgb(double r, double g, double b, string name) - { - var rLinear = Companding.InverseStandardRgb(r); - var gLinear = Companding.InverseStandardRgb(g); - var bLinear = Companding.InverseStandardRgb(b); - - // it appears that OpenCV's RGB -> XYZ and RGB -> LAB conversions - // expect to receive RGB values that have already undergone linear correction... - var rgb = GetInputVec((r, g, b)); - var hsb = GetConvertedVec((r, g, b), ColorConversionCodes.RGB2HSV); - var xyz = GetConvertedVec((rLinear, gLinear, bLinear), ColorConversionCodes.RGB2XYZ); - var lab = GetConvertedVec((rLinear, gLinear, bLinear), ColorConversionCodes.RGB2Lab); - - return new TestColour - { - Name = name, - Rgb = (rgb.Item0, rgb.Item1, rgb.Item2), - Hsb = (hsb.Item0, hsb.Item1, hsb.Item2), - Xyz = (xyz.Item0, xyz.Item1, xyz.Item2), - Lab = (lab.Item0, lab.Item1, lab.Item2) - }; - } - - public static TestColour FromHsb(double h, double s, double b) => FromHsb(h, s, b, $"{h:F2} {s:F2} {b:F2}"); - public static TestColour FromHsb(double h, double s, double b, string name) - { - var rgb = GetConvertedVec((h, s, b), ColorConversionCodes.HSV2RGB); - var hsb = GetInputVec((h, s, b)); - - return new TestColour - { - Name = name, - Rgb = (rgb.Item0, rgb.Item1, rgb.Item2), - Hsb = (hsb.Item0, hsb.Item1, hsb.Item2), - }; - } - - private static Vec3f GetInputVec((double, double, double) input) - { - var (i1, i2, i3) = input; - var mat = new Mat(1, 1, MatType.CV_32FC3, new Scalar(i1, i2, i3)); - return mat.Get(0, 0); - } - - private static Vec3f GetConvertedVec((double, double, double) input, ColorConversionCodes conversionCode) - { - var (i1, i2, i3) = input; - var matIn = new Mat(1, 1, MatType.CV_32FC3, new Scalar(i1, i2, i3)); - var matOut = new Mat(1, 1, MatType.CV_32FC3); - Cv2.CvtColor(matIn, matOut, conversionCode); - return matOut.Get(0, 0); - } - - public static void GenerateOpenCvColoursFile() - { - var rows = new List(); - foreach (var namedColour in TestColours.NamedColours) - { - var systemColour = ColorTranslator.FromHtml(namedColour.Hex!); - var (r, g, b) = (systemColour.R, systemColour.G, systemColour.B); - var testColour = FromRgb255(r, g, b, namedColour.Name!); - - string Stringify(double value) => value.ToString(CultureInfo.InvariantCulture); - var row = new List - { - testColour.Name!, - Stringify(testColour.Rgb.Value.r), Stringify(testColour.Rgb.Value.g), Stringify(testColour.Rgb.Value.b), - Stringify(testColour.Hsb.Value.h), Stringify(testColour.Hsb.Value.s), Stringify(testColour.Hsb.Value.b), - Stringify(testColour.Xyz.Value.x), Stringify(testColour.Xyz.Value.y), Stringify(testColour.Xyz.Value.z), - Stringify(testColour.Lab.Value.l), Stringify(testColour.Lab.Value.a), Stringify(testColour.Lab.Value.b) - }; - - rows.Add(string.Join(", ", row)); - } - - File.WriteAllLines("OpenCvColours.csv", rows); - } -} \ No newline at end of file diff --git a/Unicolour.Tests/Utils/SixLaborsUtils.cs b/Unicolour.Tests/Utils/SixLaborsUtils.cs deleted file mode 100644 index dcaa9649..00000000 --- a/Unicolour.Tests/Utils/SixLaborsUtils.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace Wacton.Unicolour.Tests.Utils; - -using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Wacton.Unicolour.Tests.Lookups; -using SixLaborsRgb = SixLabors.ImageSharp.ColorSpaces.Rgb; -using SixLaborsRgbLinear = SixLabors.ImageSharp.ColorSpaces.LinearRgb; -using SixLaborsHsb = SixLabors.ImageSharp.ColorSpaces.Hsv; -using SixLaborsXyz = SixLabors.ImageSharp.ColorSpaces.CieXyz; -using SixLaborsLab = SixLabors.ImageSharp.ColorSpaces.CieLab; -using SixLaborsIlluminants = SixLabors.ImageSharp.ColorSpaces.Illuminants; - -internal static class SixLaborsUtils -{ - public static TestColour FromRgb255(int r255, int g255, int b255) => FromRgb255(r255, g255, b255, $"{r255:000} {g255:000} {b255:000}"); - private static TestColour FromRgb255(int r255, int g255, int b255, string name) - { - var r = r255 / 255.0; - var g = g255 / 255.0; - var b = b255 / 255.0; - return FromRgb(r, g, b, name); - } - - public static TestColour FromRgb(double r, double g, double b) => FromRgb(r, g, b, $"{r:F2} {g:F2} {b:F2}"); - private static TestColour FromRgb(double r, double g, double b, string name) - { - var converter = new ColorSpaceConverter(new ColorSpaceConverterOptions - { - TargetLabWhitePoint = SixLaborsIlluminants.D65 - }); - - var rgb = new SixLaborsRgb((float) r, (float) g, (float) b, RgbWorkingSpaces.SRgb); - var rgbLinear = converter.ToLinearRgb(rgb); - var hsb = converter.ToHsv(rgb); - var xyz = converter.ToCieXyz(rgb); - var lab = converter.ToCieLab(rgb); - - return new TestColour - { - Name = name, - Rgb = (rgb.R, rgb.G, rgb.B), - RgbLinear = (rgbLinear.R, rgbLinear.G, rgbLinear.B), - Hsb = (hsb.H, hsb.S, hsb.V), - Xyz = (xyz.X, xyz.Y, xyz.Z), - Lab = (lab.L, lab.A, lab.B) - }; - } - - public static TestColour FromHsb(double h, double s, double b) => FromHsb(h, s, b, $"{h:F2} {s:F2} {b:F2}"); - public static TestColour FromHsb(double h, double s, double b, string name) - { - var converter = new ColorSpaceConverter(new ColorSpaceConverterOptions - { - TargetLabWhitePoint = SixLaborsIlluminants.D65 - }); - - var hsb = new SixLaborsHsb((float) h, (float) s, (float) b); - var rgb = converter.ToRgb(hsb); - var rgbLinear = converter.ToLinearRgb(hsb); - var xyz = converter.ToCieXyz(hsb); - var lab = converter.ToCieLab(hsb); - - return new TestColour - { - Name = name, - Rgb = (rgb.R, rgb.G, rgb.B), - RgbLinear = (rgbLinear.R, rgbLinear.G, rgbLinear.B), - Xyz = (xyz.X, xyz.Y, xyz.Z), - Lab = (lab.L, lab.A, lab.B) - }; - } -} \ No newline at end of file diff --git a/Unicolour.Tests/Utils/TestColour.cs b/Unicolour.Tests/Utils/TestColour.cs new file mode 100644 index 00000000..8d9782c6 --- /dev/null +++ b/Unicolour.Tests/Utils/TestColour.cs @@ -0,0 +1,36 @@ +namespace Wacton.Unicolour.Tests.Utils; + +using System.Collections.Generic; +using System.Linq; + +internal class TestColour +{ + public string? Name { get; init; } + public string? Hex { get; init; } + public ColourTuple? Rgb { get; init; } + public ColourTuple? RgbLinear { get; init; } + public ColourTuple? Hsl { get; init; } + public ColourTuple? Hsb { get; init; } + public ColourTuple? Xyz { get; init; } + public ColourTuple? Lab { get; init; } + public Tolerances? Tolerances { get; init; } + + /* + * both ColorMine and SixLabors behave strangely with HSL + * from what I can tell, they almost certainly convert HSB -> RGB -> HSL + * and as a result they have some oddities when it comes to HSL: + * 1) they set hue to 0 if there is no HSB saturation (since the intermediate RGB is greyscale, the hue gets lost in conversion) + * 2) SixLabors sets saturation to 0 if RGB chroma (max - min) is < 0.001 + * 3) ColorMine truncates intermediate RGB 255 values during conversion, resulting in full saturation if any RGB 255 value is < 1 (truncated to 0) + * --- no point in trying to compensate for their lossy conversions, just ignore them in the test + */ + public bool ExcludeFromHueBasedTest => ExcludeFromHueBasedTestReasons.Any(); + public List ExcludeFromHueBasedTestReasons { get; init; } = new(); + + public override string ToString() => $"Name:[{Name}] · Hex[{Hex}] · Rgb[{Rgb}] · Hsb[{Hsb}]"; +} + +internal record Tolerances +{ + public double Rgb, RgbLinear, Hsb, Hsl, Xyz, Lab; +} diff --git a/Unicolour.Tests/Utils/TestColours.cs b/Unicolour.Tests/Utils/TestColours.cs new file mode 100644 index 00000000..4136ebec --- /dev/null +++ b/Unicolour.Tests/Utils/TestColours.cs @@ -0,0 +1,66 @@ +namespace Wacton.Unicolour.Tests.Utils; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +// NamedColours.csv is a list of 145 colours taken from https://en.wikipedia.org/wiki/X11_color_names +internal static class TestColours +{ + private static readonly Random Random = new(); + + public static readonly List NamedColours; + public static readonly List RandomRgb255Colours = new(); + public static readonly List RandomRgbColours = new(); + public static readonly List RandomHsbColours = new(); + public static readonly List RandomHslColours = new(); + public static readonly List RandomHexColours = new(); + + static TestColours() + { + NamedColours = File.ReadAllLines(Path.Combine("Utils", "NamedColours.csv")) + .Skip(1).Select(CreateNamedColour).ToList(); + + for (var i = 0; i < 1000; i++) + { + RandomRgb255Colours.Add(new(Random.Next(256), Random.Next(256), Random.Next(256))); + RandomRgbColours.Add(new(Random.NextDouble(), Random.NextDouble(), Random.NextDouble())); + RandomHsbColours.Add(new(Random.NextDouble(), Random.NextDouble(), Random.NextDouble())); + RandomHslColours.Add(new(Random.NextDouble(), Random.NextDouble(), Random.NextDouble())); + RandomHexColours.Add(GenerateRandomHex()); + } + } + + private static TestColour CreateNamedColour(string csvRow) + { + var items = csvRow.Split(",", StringSplitOptions.TrimEntries); + + return new TestColour + { + Name = items[0], + Hex = items[1], + Rgb = new(FromPercentage(items[2]), FromPercentage(items[3]), FromPercentage(items[4])), + Hsl = new(FromDegrees(items[5]), FromPercentage(items[6]), FromPercentage(items[7])), + Hsb = new(FromDegrees(items[5]), FromPercentage(items[8]), FromPercentage(items[9])) + }; + } + + private static double FromPercentage(string text) => double.Parse(text.TrimEnd('%')) / 100.0; + private static double FromDegrees(string text) => double.Parse(text.TrimEnd('°')); + + private static string GenerateRandomHex() + { + const string hexChars = "0123456789abcdefABCDEF"; + var useHash = Random.Next(0, 2) == 0; + var length = Random.Next(0, 2) == 0 ? 6 : 8; + + var hex = useHash ? "#" : string.Empty; + for (var i = 0; i < length; i++) + { + hex += hexChars[Random.Next(hexChars.Length)]; + } + + return hex; + } +} \ No newline at end of file diff --git a/Unicolour/Alpha.cs b/Unicolour/Alpha.cs index ba02d8a4..4b8e7ff4 100644 --- a/Unicolour/Alpha.cs +++ b/Unicolour/Alpha.cs @@ -1,8 +1,6 @@ namespace Wacton.Unicolour; -using System; - -public class Alpha : IEquatable +public record Alpha { public double A { get; } public int A255 => (int) Math.Round(A * 255); @@ -15,26 +13,4 @@ public Alpha(double a) } public override string ToString() => $"{A}"; - - // --- autogenerated --- - - public bool Equals(Alpha? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return A.Equals(other.A); - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Alpha) obj); - } - - public override int GetHashCode() - { - return A.GetHashCode(); - } } \ No newline at end of file diff --git a/Unicolour/ColourTuple.cs b/Unicolour/ColourTuple.cs new file mode 100644 index 00000000..696ab29e --- /dev/null +++ b/Unicolour/ColourTuple.cs @@ -0,0 +1,8 @@ +namespace Wacton.Unicolour; + +public record ColourTuple(double First, double Second, double Third) +{ + public double First { get; } = First; + public double Second { get; } = Second; + public double Third { get; } = Third; +} \ No newline at end of file diff --git a/Unicolour/Configuration.cs b/Unicolour/Configuration.cs index e20a77e7..228083d0 100644 --- a/Unicolour/Configuration.cs +++ b/Unicolour/Configuration.cs @@ -16,21 +16,20 @@ public class Configuration public static readonly Configuration Default = new( Chromaticity.StandardRgbR, Chromaticity.StandardRgbG, - Chromaticity.StandardRgbB, - WhitePoint.From(Illuminant.D65), - WhitePoint.From(Illuminant.D65), - Companding.InverseStandardRgb); + Chromaticity.StandardRgbB, + Companding.InverseStandardRgb, + WhitePoint.From(Illuminant.D65), + WhitePoint.From(Illuminant.D65)); - public Configuration( - Chromaticity chromaticityR, Chromaticity chromaticityG, Chromaticity chromaticityB, - WhitePoint rgbWhitePoint, WhitePoint xyzWhitePoint, Func inverseCompanding) + public Configuration(Chromaticity chromaticityR, Chromaticity chromaticityG, Chromaticity chromaticityB, + Func inverseCompanding, WhitePoint rgbWhitePoint, WhitePoint xyzWhitePoint) { ChromaticityR = chromaticityR; ChromaticityG = chromaticityG; ChromaticityB = chromaticityB; + InverseCompanding = inverseCompanding; RgbWhitePoint = rgbWhitePoint; XyzWhitePoint = xyzWhitePoint; - InverseCompanding = inverseCompanding; RgbToXyzMatrix = Matrices.RgbToXyzMatrix(this); } diff --git a/Unicolour/Conversion.cs b/Unicolour/Conversion.cs index c656eeaa..b76f496d 100644 --- a/Unicolour/Conversion.cs +++ b/Unicolour/Conversion.cs @@ -26,6 +26,33 @@ public static Hsb RgbToHsb(Rgb rgb) return new Hsb(hue, saturation, brightness, false); } + // https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB + public static Hsl RgbToHsl(Rgb rgb) + { + var r = rgb.R; + var g = rgb.G; + var b = rgb.B; + + var components = new[] {r, g, b}; + var xMax = components.Max(); + var xMin = components.Min(); + var chroma = xMax - xMin; + var lightness = (xMax + xMin) / 2.0; + + double hue; + if (chroma == 0.0) hue = 0; + else if (xMax == r) hue = 60 * (0 + ((g - b) / chroma)); + else if (xMax == g) hue = 60 * (2 + ((b - r) / chroma)); + else if (xMax == b) hue = 60 * (4 + ((r - g) / chroma)); + else throw new InvalidOperationException(); + hue = hue < 0 ? 360 + hue : hue; + var saturation = lightness > 0.0 && lightness < 1.0 + ? (xMax - lightness) / Math.Min(lightness, 1 - lightness) + : 0.0; + + return new Hsl(hue, saturation, lightness, false); + } + // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB public static Rgb HsbToRgb(Hsb hsb, Configuration config) { @@ -52,6 +79,31 @@ public static Rgb HsbToRgb(Hsb hsb, Configuration config) var (red, green, blue) = (r + m, g + m, b + m); return new Rgb(red, green, blue, config); } + + // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL + public static Hsl HsbToHsl(Hsb hsb) + { + var hue = hsb.H; + var lightness = hsb.B * (1 - hsb.S / 2); + var saturation = lightness is > 0.0 and < 1.0 + ? (hsb.B - lightness) / Math.Min(lightness, 1 - lightness) + : 0; + + return new Hsl(hue, saturation, lightness, hsb.HasHue); + } + + // https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_HSV + public static Hsb HslToHsb(Hsl hsl) + { + var hue = hsl.H; + var lightness = hsl.L; + var brightness = lightness + hsl.S * Math.Min(lightness, 1 - lightness); + var saturation = brightness > 0.0 + ? 2 * (1 - lightness / brightness) + : 0; + + return new Hsb(hue, saturation, brightness); + } // https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ public static Xyz RgbToXyz(Rgb rgb, Configuration config) diff --git a/Unicolour/Formulas/HSB to HSL.png b/Unicolour/Formulas/HSB to HSL.png new file mode 100644 index 0000000000000000000000000000000000000000..96c27f2e1ca7955632ff79ee456597181908e29b GIT binary patch literal 14097 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 literal 0 HcmV?d00001 diff --git a/Unicolour/Formulas/HSL to HSB.png b/Unicolour/Formulas/HSL to HSB.png new file mode 100644 index 0000000000000000000000000000000000000000..3fd6afe8c92919aa76685d0ca3c495b1b377d3fc GIT binary patch literal 13888 zcmb`uWmH>V@HSecffk42?rrhn5L{cVlol!OP`p@hcb6gsLIr6H6e|htQoOi?;4UE~ zxZd!4@BefXkI5BQ&4VwO~S6B9-Q0DWU&M!}&B&Q#hA8z)H%_~0Zq9s@(Nfq(a208mf{?}Cm zZU*K9R%I-5P0SEU`cPf)(fv11SUNcFr|O3OZ%_EADA=+I8O~X;8=Bn=>B;xdSsFl5 zB6dhJRIp%+C7U-R*ETAKt*hMj>72I9IXlNZ$dw7#hkgwwj)i+U`89K=Vk%cgxAxAt zpYvuQlh8_i$jjR;@FxTedf~WVbrn=oE)%>5Svqcp-A3PC^m?38i!=IG&KRt_Pq#>9 z6}4=Ia1Z|XxFt$VT*{L~uGTLM?7ufzN?CEoem)jziqkp#1%speO_t|i@GbH&ubxgy zgfTC@^ylXjPKa%qJ7^>G^$DfrOKFqZ-NS|2yzHisj3M95rJMPntrF@5osjGy@thsk z#p}o`w=&gqzk9{mjL~)+uju+BjqdJXIt?iwL=DP<*1?xzJYtRCD@++AyR9Crh<3Tu zt~Iy1jN^z9RgOF{lY6h@hAxx6%UpRU5C$A&@=`l0GHsZw`1@SXx~&*-S!E$#?=yKb zD4+)S?k~UJu=!Hq@SOok#QR&hfTk-~QbB`y7+Aq;asj8nxtdI4M*J!t+;-rZ3%B`n zWcwo^)m6QF?gSCWl26Jj3f=@?A3oW?b3$+bJcJa;yP7eWXAQMmFOx8PRRf>%nC*uU`Je{ z9xcX=4Yxe_IaQ`oy+0qve(LU8yKAM3G~m!SD@&-3rgqqG`=g!kyRPJgjn|1NP`$N# z2IljVzh^H?ETCYhb~Bs*Ad9DupwKAgt~lT#d+FodYH7Bbc9YQ^5@u6Otwevj&9rD{ ze%A3W#D|2-iR;4GwJWS_L42X@;}uEZ@J-(+S3=ai*?uPO5%i0z{kbXYFLmxf4)%qb zQ+gL`W&eP=xhUnE98O1z6#5=Q_(l>N4fdy$&!-f4dpk!XSI~%M9{Yt{1LEkMQa72F z>B=4A%DWV~Mz;lYlI3U z;su$Ij}rOSS}BKwG$Wx5?V5;WUGPK&lTzI(x9f}TMl$QnZZ?-&1@auYteCTWe%mOg zRry@RBySPqcG$&?$tPv+w#$BDl=;p)=_qp-Yq|gY)r{E+lTV1(-N}}HSEE05!M$Ni zX~kIOtg;-u#2<0J<7*%!8TBTqpLZJm8MwETV&bBQ#}@m$d6*G+^Y%uo0kHH@rvL(7 z=WdY<+4h;Xg)TM3)HbfmO2dNC=w{Ar z!Vk|d?i%}Q=m%L9`{iTypu6>@+XC3>RBnIzBI+tztm*K!(0HNN={BfF`5qdD7y~r4 z?=M!ZRKgwQK~8sP(n!9BUz2T|XYiY~{^on}YhGvP-;c^zsQ+U?`u_>V+iOjVpnAgn zH}&i333rmKYx;{R?9}`w_;>b0Ib*`2tv5%45K zCg8NbSR=c!3DH>gtgr&KRp7hu+q7w6q(C|#{jTQX#q90mPd(Q&1RU`8>H4_ zY|Hd3e))CiqF33C^vbj}HAPVS^n>#eB(jE*cUz-{m44Nf$vQ3WLn6HFZbjbP|Gv(j zpMz>ji2E#VW@iomvZDch_BBAnY&GWhF&jJvzlSspo+#Z|QrHdVs|9;%UzkrZNj=)~ z8vCZA$I!f?Yh;u&EeRg4Bp>V=n7|9jSfdX;NLZDuBxoy z!MRFKPL95dtG>=%6&4`@pf5xF^e}QkfesN9_uus)?%EA_ABiWn>gaPS zx*a#>>~pWHwB}_ed>kCL`(!n|`&+7mzvxx`@Oh53Cv)2h%kMf~7}9DpP*Ft~7?a68UJ&gbVp1OngtK3}QA4LcOkuWW9b` z;n|J0dT2nN;`Mc?in%UqX9<`yaXBa7nF!mqPge;oJ>}E@B

jBj&o?p+O5F^}b>7 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 +public record Hsb { private readonly bool explicitHue; public double H { get; } public double S { get; } public double B { get; } - public (double h, double s, double b) Tuple => (H, S, B); + public ColourTuple Tuple => new(H, S, B); // RGB(0,0,0) is black, but has no explicit hue (and don't want to assume red) // HSB(0,0,0) is black, but want to acknowledge the explicit red hue of 0 @@ -30,32 +28,4 @@ internal Hsb(double h, double s, double b, bool explicitHue) } public override string ToString() => $"{(HasHue ? Math.Round(H, 1) : "—")}° {Math.Round(S * 100, 1)}% {Math.Round(B * 100, 1)}%"; - - // --- autogenerated --- - - public bool Equals(Hsb? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return H.Equals(other.H) && S.Equals(other.S) && B.Equals(other.B); - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Hsb) obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = H.GetHashCode(); - hashCode = (hashCode * 397) ^ S.GetHashCode(); - hashCode = (hashCode * 397) ^ B.GetHashCode(); - return hashCode; - } - } } \ No newline at end of file diff --git a/Unicolour/Hsl.cs b/Unicolour/Hsl.cs new file mode 100644 index 00000000..b761a54b --- /dev/null +++ b/Unicolour/Hsl.cs @@ -0,0 +1,28 @@ +namespace Wacton.Unicolour; + +public record Hsl +{ + private readonly bool explicitHue; + public double H { get; } + public double S { get; } + public double L { get; } + public ColourTuple Tuple => new(H, S, L); + + public bool HasHue => explicitHue || S > 0.0 && L is > 0.0 and < 1.0; + + public Hsl(double h, double s, double l) : this(h, s, l, true) {} + + internal Hsl(double h, double s, double l, bool explicitHue) + { + h.Guard(0.0, 360.0, "Hue"); + s.Guard(0.0, 1.0, "Saturation"); + l.Guard(0.0, 1.0, "Lightness"); + + H = h; + S = s; + L = l; + this.explicitHue = explicitHue; + } + + public override string ToString() => $"{(HasHue ? Math.Round(H, 1) : "—")}° {Math.Round(S * 100, 1)}% {Math.Round(L * 100, 1)}%"; +} \ No newline at end of file diff --git a/Unicolour/Interpolation.cs b/Unicolour/Interpolation.cs index 325c5585..0f69cb75 100644 --- a/Unicolour/Interpolation.cs +++ b/Unicolour/Interpolation.cs @@ -10,6 +10,22 @@ private static void GuardConfiguration(Unicolour colour1, Unicolour colour2) } } + public static Unicolour InterpolateRgb(this Unicolour startColour, Unicolour endColour, double distance) + { + GuardConfiguration(startColour, endColour); + + var startRgb = startColour.Rgb; + var endRgb = endColour.Rgb; + var startAlpha = startColour.Alpha; + var endAlpha = endColour.Alpha; + + var r = Interpolate(startRgb.R, endRgb.R, distance); + var g = Interpolate(startRgb.G, endRgb.G, distance); + var b = Interpolate(startRgb.B, endRgb.B, distance); + var a = Interpolate(startAlpha.A, endAlpha.A, distance); + return Unicolour.FromRgb(startColour.Config, r, g, b, a); + } + public static Unicolour InterpolateHsb(this Unicolour startColour, Unicolour endColour, double distance) { GuardConfiguration(startColour, endColour); @@ -19,43 +35,50 @@ public static Unicolour InterpolateHsb(this Unicolour startColour, Unicolour end var startAlpha = startColour.Alpha; var endAlpha = endColour.Alpha; - // don't use hue if one colour is monochrome (e.g. black n/a° to green 120° should always stay at hue 120°) - var noHue = !startHsb.HasHue && !endHsb.HasHue; - var startHue = noHue || startHsb.HasHue ? startHsb.H : endHsb.H; - var endHue = noHue || endHsb.HasHue ? endHsb.H : startHsb.H; - - var forwardStart = startHue; - var forwardEnd = endHue; - var backwardStart = Math.Min(startHue, endHue) + 360; - var backwardEnd = Math.Max(startHue, endHue); - - var interpolateForward = Math.Abs(forwardStart - forwardEnd) <= Math.Abs(backwardStart - backwardEnd); - startHue = interpolateForward ? forwardStart : backwardStart; - endHue = interpolateForward ? forwardEnd : backwardEnd; - + var (startHue, endHue) = GetHuePoints((startHsb.HasHue, startHsb.H), (endHsb.HasHue, endHsb.H)); var h = Interpolate(startHue, endHue, distance); var s = Interpolate(startHsb.S, endHsb.S, distance); var b = Interpolate(startHsb.B, endHsb.B, distance); var a = Interpolate(startAlpha.A, endAlpha.A, distance); return Unicolour.FromHsb(startColour.Config, h.Modulo(360), s, b, a); } - - public static Unicolour InterpolateRgb(this Unicolour startColour, Unicolour endColour, double distance) + + public static Unicolour InterpolateHsl(this Unicolour startColour, Unicolour endColour, double distance) { GuardConfiguration(startColour, endColour); - - var startRgb = startColour.Rgb; - var endRgb = endColour.Rgb; + + var startHsl = startColour.Hsl; + var endHsl = endColour.Hsl; var startAlpha = startColour.Alpha; var endAlpha = endColour.Alpha; - var r = Interpolate(startRgb.R, endRgb.R, distance); - var g = Interpolate(startRgb.G, endRgb.G, distance); - var b = Interpolate(startRgb.B, endRgb.B, distance); + var (startHue, endHue) = GetHuePoints((startHsl.HasHue, startHsl.H), (endHsl.HasHue, endHsl.H)); + var h = Interpolate(startHue, endHue, distance); + var s = Interpolate(startHsl.S, endHsl.S, distance); + var l = Interpolate(startHsl.L, endHsl.L, distance); var a = Interpolate(startAlpha.A, endAlpha.A, distance); - return Unicolour.FromRgb(startColour.Config, r, g, b, a); + return Unicolour.FromHsl(startColour.Config, h.Modulo(360), s, l, a); } + private static (double startHue, double endHue) GetHuePoints((bool hasHue, double hueValue) start, (bool hasHue, double hueValue) end) + { + // don't use hue if one colour is monochrome (e.g. black n/a° to green 120° should always stay at hue 120°) + var noHue = !start.hasHue && !end.hasHue; + var startHue = noHue || start.hasHue ? start.hueValue : end.hueValue; + var endHue = noHue || end.hasHue ? end.hueValue : start.hueValue; + + var forwardStart = startHue; + var forwardEnd = endHue; + var backwardStart = Math.Min(startHue, endHue) + 360; + var backwardEnd = Math.Max(startHue, endHue); + + var interpolateForward = Math.Abs(forwardStart - forwardEnd) <= Math.Abs(backwardStart - backwardEnd); + startHue = interpolateForward ? forwardStart : backwardStart; + endHue = interpolateForward ? forwardEnd : backwardEnd; + + return (startHue, endHue); + } + private static double Interpolate(double startValue, double endValue, double distance) { var difference = endValue - startValue; diff --git a/Unicolour/Lab.cs b/Unicolour/Lab.cs index bfbc8bf4..003867f9 100644 --- a/Unicolour/Lab.cs +++ b/Unicolour/Lab.cs @@ -1,11 +1,11 @@ namespace Wacton.Unicolour; -public class Lab : IEquatable +public record Lab { public double L { get; } public double A { get; } public double B { get; } - public (double l, double a, double b) Tuple => (L, A, B); + public ColourTuple Tuple => new(L, A, B); public Lab(double l, double a, double b) { @@ -20,32 +20,4 @@ public override string ToString() var prefixB = B > 0 ? "+" : string.Empty; return $"{Math.Round(L, 2)} {prefixA}{Math.Round(A, 2)} {prefixB}{Math.Round(B, 2)}"; } - - // --- autogenerated --- - - public bool Equals(Lab? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return L.Equals(other.L) && A.Equals(other.A) && B.Equals(other.B); - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Lab) obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = L.GetHashCode(); - hashCode = (hashCode * 397) ^ A.GetHashCode(); - hashCode = (hashCode * 397) ^ B.GetHashCode(); - return hashCode; - } - } } \ No newline at end of file diff --git a/Unicolour/Rgb.cs b/Unicolour/Rgb.cs index b32d3878..a1b6c046 100644 --- a/Unicolour/Rgb.cs +++ b/Unicolour/Rgb.cs @@ -1,24 +1,22 @@ namespace Wacton.Unicolour; -using System; - -public class Rgb : IEquatable +public record Rgb { public double R { get; } public double G { get; } public double B { get; } - public (double r, double g, double b) Tuple => (R, G, B); + public ColourTuple Tuple => new(R, G, B); public int R255 => (int) Math.Round(R * 255); public int G255 => (int) Math.Round(G * 255); public int B255 => (int) Math.Round(B * 255); - public (double r255, double g255, double b255) Tuple255 => (R255, G255, B255); + public ColourTuple Tuple255 => new(R255, G255, B255); private readonly Func inverseCompanding; public double RLinear => inverseCompanding(R); public double GLinear => inverseCompanding(G); public double BLinear => inverseCompanding(B); - public (double rLinear, double gLinear, double bLinear) TupleLinear => (RLinear, GLinear, BLinear); + public ColourTuple TupleLinear => new(RLinear, GLinear, BLinear); public string Hex => $"#{R255:X2}{G255:X2}{B255:X2}"; @@ -35,32 +33,4 @@ public Rgb(double r, double g, double b, Configuration config) } public override string ToString() => $"{R255} {G255} {B255}"; - - // --- autogenerated --- - - public bool Equals(Rgb? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return R.Equals(other.R) && G.Equals(other.G) && B.Equals(other.B); - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Rgb) obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = R.GetHashCode(); - hashCode = (hashCode * 397) ^ G.GetHashCode(); - hashCode = (hashCode * 397) ^ B.GetHashCode(); - return hashCode; - } - } } \ No newline at end of file diff --git a/Unicolour/Unicolour.cs b/Unicolour/Unicolour.cs index ba410075..7bd6d1df 100644 --- a/Unicolour/Unicolour.cs +++ b/Unicolour/Unicolour.cs @@ -1,17 +1,17 @@ namespace Wacton.Unicolour; -using System; - public class Unicolour : IEquatable { - // TODO: track initial colour space for equality and defining process - private Hsb? hsb; + private readonly ColourSpace initialSpace; private Rgb? rgb; + private Hsb? hsb; + private Hsl? hsl; private Xyz? xyz; private Lab? lab; - public Hsb Hsb => hsb ??= Conversion.RgbToHsb(Rgb); public Rgb Rgb => rgb ??= Conversion.HsbToRgb(Hsb, Config); + public Hsb Hsb => hsb ??= rgb != null ? Conversion.RgbToHsb(Rgb) : Conversion.HslToHsb(Hsl); + public Hsl Hsl => hsl ??= Conversion.HsbToHsl(Hsb); public Xyz Xyz => xyz ??= Conversion.RgbToXyz(Rgb, Config); public Lab Lab => lab ??= Conversion.XyzToLab(Xyz, Config); public Alpha Alpha { get; } @@ -19,18 +19,28 @@ public class Unicolour : IEquatable public double Luminance => this.Luminance(); + private Unicolour(Configuration config, Rgb rgb, Alpha alpha) + { + this.rgb = rgb; + Alpha = alpha; + Config = config; + initialSpace = ColourSpace.Rgb; + } + private Unicolour(Configuration config, Hsb hsb, Alpha alpha) { this.hsb = hsb; Alpha = alpha; Config = config; + initialSpace = ColourSpace.Hsb; } - private Unicolour(Configuration config, Rgb rgb, Alpha alpha) + private Unicolour(Configuration config, Hsl hsl, Alpha alpha) { - this.rgb = rgb; + this.hsl = hsl; Alpha = alpha; Config = config; + initialSpace = ColourSpace.Hsl; } public static Unicolour FromHex(string hex) => FromHex(Configuration.Default, hex); @@ -46,16 +56,33 @@ public static Unicolour FromHex(Configuration config, string hex) public static Unicolour FromRgb(Configuration config, double r, double g, double b, double a = 1.0) => new(config, new Rgb(r, g, b, config), new Alpha(a)); public static Unicolour FromHsb(double h, double s, double b, double a = 1.0) => FromHsb(Configuration.Default, h, s, b, a); public static Unicolour FromHsb(Configuration config, double h, double s, double b, double a = 1.0) => new(config, new Hsb(h, s, b), new Alpha(a)); + public static Unicolour FromHsl(double h, double s, double l, double a = 1.0) => FromHsl(Configuration.Default, h, s, l, a); + public static Unicolour FromHsl(Configuration config, double h, double s, double l, double a = 1.0) => new(config, new Hsl(h, s, l), new Alpha(a)); - public override string ToString() => $"HSB:[{Hsb}] RGB:[{Rgb}] Hex:{Rgb.Hex} A:{Alpha.A}"; + public override string ToString() => $"RGB:[{Rgb}] Hex:{Rgb.Hex} HSB:[{Hsb}] A:{Alpha.A}"; - // --- autogenerated --- + // ----- the following is based on auto-generated code ----- public bool Equals(Unicolour? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Hsb.Equals(other.Hsb) && Alpha.Equals(other.Alpha); + return ColourSpaceEquals(other) && Alpha.Equals(other.Alpha); + } + + private bool ColourSpaceEquals(Unicolour other) + { + switch (initialSpace) + { + case ColourSpace.Rgb: return Rgb.Equals(other.Rgb); + case ColourSpace.Hsb: return Hsb.Equals(other.Hsb); + case ColourSpace.Hsl: return Hsl.Equals(other.Hsl); + case ColourSpace.Xyz: + case ColourSpace.Lab: + throw new NotImplementedException(); + default: + throw new ArgumentOutOfRangeException(); + } } public override bool Equals(object? obj) @@ -70,7 +97,28 @@ public override int GetHashCode() { unchecked { - return (Hsb.GetHashCode() * 397) ^ Alpha.GetHashCode(); + int colourSpaceHashCode; + switch (initialSpace) + { + case ColourSpace.Rgb: + colourSpaceHashCode = Rgb.GetHashCode() * 397; + break; + case ColourSpace.Hsb: + colourSpaceHashCode = Hsb.GetHashCode() * 397; + break; + case ColourSpace.Hsl: + colourSpaceHashCode = Hsl.GetHashCode() * 397; + break; + case ColourSpace.Xyz: + case ColourSpace.Lab: + throw new NotImplementedException(); + default: + throw new ArgumentOutOfRangeException(); + } + + return colourSpaceHashCode ^ Alpha.GetHashCode(); } } + + private enum ColourSpace { Rgb, Hsb, Hsl, Xyz, Lab } } \ No newline at end of file diff --git a/Unicolour/Unicolour.csproj b/Unicolour/Unicolour.csproj index 90564f39..66790d20 100644 --- a/Unicolour/Unicolour.csproj +++ b/Unicolour/Unicolour.csproj @@ -15,9 +15,9 @@ netstandard2.0 True Resources\Unicolour.png - 1.0.0 - colour color converter RGB HSB HSV LAB XYZ color-spaces colour-spaces interpolation comparison contrast luminance deltaE - Initial release + 1.1.0 + colour color converter RGB HSB HSV HSL LAB XYZ color-spaces colour-spaces interpolation comparison contrast luminance deltaE + Add HSL support Resources\Unicolour.ico LICENSE diff --git a/Unicolour/WhitePoint.cs b/Unicolour/WhitePoint.cs index 0e4f3d5d..857225b3 100644 --- a/Unicolour/WhitePoint.cs +++ b/Unicolour/WhitePoint.cs @@ -5,6 +5,7 @@ public record WhitePoint(double X, double Y, double Z) public double X { get; } = X; public double Y { get; } = Y; public double Z { get; } = Z; + public override string ToString() => $"({X}, {Y}, {Z})"; public static WhitePoint From(Illuminant illuminant, Observer observer = Observer.Standard2) diff --git a/Unicolour/Xyz.cs b/Unicolour/Xyz.cs index 7ef802e5..49dc31dd 100644 --- a/Unicolour/Xyz.cs +++ b/Unicolour/Xyz.cs @@ -1,13 +1,11 @@ namespace Wacton.Unicolour; -using System; - -public class Xyz : IEquatable +public record Xyz { public double X { get; } public double Y { get; } public double Z { get; } - public (double x, double y, double z) Tuple => (X, Y, Z); + public ColourTuple Tuple => new(X, Y, Z); public Xyz(double x, double y, double z) { @@ -17,32 +15,4 @@ public Xyz(double x, double y, double z) } public override string ToString() => $"{Math.Round(X, 2)} {Math.Round(Y, 2)} {Math.Round(Z, 2)}"; - - // --- autogenerated --- - - public bool Equals(Xyz? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z); - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Xyz) obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = X.GetHashCode(); - hashCode = (hashCode * 397) ^ Y.GetHashCode(); - hashCode = (hashCode * 397) ^ Z.GetHashCode(); - return hashCode; - } - } } \ No newline at end of file