From 239fa79968db6ac5581511eddd448dc5445b47e1 Mon Sep 17 00:00:00 2001 From: Michiel Post Date: Fri, 22 Mar 2019 15:17:49 +0100 Subject: [PATCH] Replaced OriginalWithModel ColorConverter with Gamut ColorConverter --- .../ColorConverterTests-Arrays.cs | 0 .../ColorConverterTests.cs | 23 +- .../HueColorConverterTests.cs | 5 +- .../Q42.HueApi.ColorConverters.Tests.csproj | 4 +- .../Extensions/LightCommandExtensions.cs | 9 +- .../Gamut/Extensions/LightExtensions.cs | 22 ++ .../Gamut/Extensions/StateExtensions.cs | 22 ++ .../HueColorConverter.cs | 30 ++- .../OriginalWithModel/CIE1931Gamut.cs | 200 ---------------- .../Extensions/LightExtensions.cs | 16 -- .../Extensions/StateExtensions.cs | 21 -- src/Q42.HueApi.Tests/App.config | 8 +- src/Q42.HueApi/Converters/GamutConverter.cs | 74 ++++++ src/Q42.HueApi/Models/Gamut/CIE1931Gamut.cs | 213 ++++++++++++++++++ .../Models/Gamut}/CIE1931Point.cs | 12 +- src/Q42.HueApi/Models/Light.cs | 38 ++++ 16 files changed, 414 insertions(+), 283 deletions(-) rename src/Q42.HueApi.ColorConverters.Tests/{OriginalWithModel => Gamut}/ColorConverterTests-Arrays.cs (100%) rename src/Q42.HueApi.ColorConverters.Tests/{OriginalWithModel => Gamut}/ColorConverterTests.cs (95%) rename src/Q42.HueApi.ColorConverters/{OriginalWithModel => Gamut}/Extensions/LightCommandExtensions.cs (67%) create mode 100644 src/Q42.HueApi.ColorConverters/Gamut/Extensions/LightExtensions.cs create mode 100644 src/Q42.HueApi.ColorConverters/Gamut/Extensions/StateExtensions.cs rename src/Q42.HueApi.ColorConverters/{OriginalWithModel => Gamut}/HueColorConverter.cs (90%) delete mode 100644 src/Q42.HueApi.ColorConverters/OriginalWithModel/CIE1931Gamut.cs delete mode 100644 src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/LightExtensions.cs delete mode 100644 src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/StateExtensions.cs create mode 100644 src/Q42.HueApi/Converters/GamutConverter.cs create mode 100644 src/Q42.HueApi/Models/Gamut/CIE1931Gamut.cs rename src/{Q42.HueApi.ColorConverters/OriginalWithModel => Q42.HueApi/Models/Gamut}/CIE1931Point.cs (80%) diff --git a/src/Q42.HueApi.ColorConverters.Tests/OriginalWithModel/ColorConverterTests-Arrays.cs b/src/Q42.HueApi.ColorConverters.Tests/Gamut/ColorConverterTests-Arrays.cs similarity index 100% rename from src/Q42.HueApi.ColorConverters.Tests/OriginalWithModel/ColorConverterTests-Arrays.cs rename to src/Q42.HueApi.ColorConverters.Tests/Gamut/ColorConverterTests-Arrays.cs diff --git a/src/Q42.HueApi.ColorConverters.Tests/OriginalWithModel/ColorConverterTests.cs b/src/Q42.HueApi.ColorConverters.Tests/Gamut/ColorConverterTests.cs similarity index 95% rename from src/Q42.HueApi.ColorConverters.Tests/OriginalWithModel/ColorConverterTests.cs rename to src/Q42.HueApi.ColorConverters.Tests/Gamut/ColorConverterTests.cs index 685c0d72..7295f346 100644 --- a/src/Q42.HueApi.ColorConverters.Tests/OriginalWithModel/ColorConverterTests.cs +++ b/src/Q42.HueApi.ColorConverters.Tests/Gamut/ColorConverterTests.cs @@ -1,5 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Q42.HueApi.ColorConverters.OriginalWithModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Q42.HueApi.ColorConverters.Gamut; +using Q42.HueApi.Models.Gamut; using System; namespace Q42.HueApi.ColorConverters.Tests @@ -17,12 +18,12 @@ public void ColorConversionWhitePoint() foreach (string model in models) { // Make sure that Philips' white point resolves to #FFFFFF for all lights. - var rgb = HueColorConverter.XYToRgb(CIE1931Point.PhilipsWhite, model); + var rgb = HueColorConverter.XYToRgb(CIE1931Point.PhilipsWhite, CIE1931Gamut.ForModel(model)); Assert.AreEqual(rgb.R, 1.0, 0.0001); Assert.AreEqual(rgb.G, 1.0, 0.0001); Assert.AreEqual(rgb.B, 1.0, 0.0001); - var xy = HueColorConverter.RgbToXY(new RGBColor(1.0, 1.0, 1.0), model); + var xy = HueColorConverter.RgbToXY(new RGBColor(1.0, 1.0, 1.0), CIE1931Gamut.ForModel(model)); AssertAreEqual(CIE1931Point.PhilipsWhite, xy, 0.0001); } } @@ -51,8 +52,8 @@ public void ColorsOutsideGamutAdjustedToInBeInGamutOnConversion() // A color green outside Gamut A. CIE1931Point greenOutsideGamut = new CIE1931Point(0.21, 0.75); - var a = HueColorConverter.XYToRgb(gamutGreen, "LST001"); - var b = HueColorConverter.XYToRgb(greenOutsideGamut, "LST001"); + var a = HueColorConverter.XYToRgb(gamutGreen, CIE1931Gamut.ForModel("LST001")); + var b = HueColorConverter.XYToRgb(greenOutsideGamut, CIE1931Gamut.ForModel("LST001")); // Points should be equal, since the green outside the gamut should // be adjusted the the nearest green in-gamut. @@ -76,7 +77,7 @@ public void CompareColorConversionWithReference() var referenceXy = ReferenceColorConverter.XyFromColor(red, green, blue); // LCT001 uses Gamut B, which is the gamut used in the reference implementation. - var actualXy = HueColorConverter.RgbToXY(new RGBColor(red, green, blue), "LCT001"); + var actualXy = HueColorConverter.RgbToXY(new RGBColor(red, green, blue), CIE1931Gamut.ForModel("LCT001")); AssertAreEqual(referenceXy, actualXy, 0.0001); } @@ -115,8 +116,8 @@ public void ColorConversionRoundtripInsideGamut() || !ReferenceColorConverter.CheckPointInLampsReach(originalXy) || !CIE1931Gamut.PhilipsWideGamut.Contains(originalXy)); - RGBColor rgb = HueColorConverter.XYToRgb(originalXy, "LCT001"); - var xy = HueColorConverter.RgbToXY(rgb, "LCT001"); + RGBColor rgb = HueColorConverter.XYToRgb(originalXy, CIE1931Gamut.ForModel("LCT001")); + var xy = HueColorConverter.RgbToXY(rgb, CIE1931Gamut.ForModel("LCT001")); AssertAreEqual(originalXy, xy, 0.0001); } @@ -139,8 +140,8 @@ public void ColorConversionRoundtripAllPoints() } while (originalXy.x + originalXy.y >= 1.0); - RGBColor rgb = HueColorConverter.XYToRgb(originalXy, "LCT001"); - var xy = HueColorConverter.RgbToXY(rgb, "LCT001"); + RGBColor rgb = HueColorConverter.XYToRgb(originalXy, CIE1931Gamut.ForModel("LCT001")); + var xy = HueColorConverter.RgbToXY(rgb, CIE1931Gamut.ForModel("LCT001")); // We expect a point that is both inside the lamp's gamut and the "wide gamut" // used for XYZ->RGB and RGB->XYZ conversion. diff --git a/src/Q42.HueApi.ColorConverters.Tests/HueColorConverterTests.cs b/src/Q42.HueApi.ColorConverters.Tests/HueColorConverterTests.cs index 178dcae3..75e92dac 100644 --- a/src/Q42.HueApi.ColorConverters.Tests/HueColorConverterTests.cs +++ b/src/Q42.HueApi.ColorConverters.Tests/HueColorConverterTests.cs @@ -5,7 +5,8 @@ using System.Text; using System.Threading.Tasks; using System.Drawing; -using Q42.HueApi.ColorConverters.OriginalWithModel; +using Q42.HueApi.Models.Gamut; +using Q42.HueApi.ColorConverters.Gamut; namespace Q42.HueApi.ColorConverters.Tests { @@ -32,7 +33,7 @@ private Bitmap DrawBitmap(string model) for (int y = 0; y < dimension; y++) { CIE1931Point point = new CIE1931Point(x / (dimension * 1.0), y / (dimension * 1.0)); - var rgb = HueColorConverter.XYToRgb(point, model); + var rgb = HueColorConverter.XYToRgb(point, CIE1931Gamut.ForModel(model)); Color c; if (point.x + point.y > 1.0) diff --git a/src/Q42.HueApi.ColorConverters.Tests/Q42.HueApi.ColorConverters.Tests.csproj b/src/Q42.HueApi.ColorConverters.Tests/Q42.HueApi.ColorConverters.Tests.csproj index 9e37bba3..7dee7a43 100644 --- a/src/Q42.HueApi.ColorConverters.Tests/Q42.HueApi.ColorConverters.Tests.csproj +++ b/src/Q42.HueApi.ColorConverters.Tests/Q42.HueApi.ColorConverters.Tests.csproj @@ -55,8 +55,8 @@ - - + + diff --git a/src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/LightCommandExtensions.cs b/src/Q42.HueApi.ColorConverters/Gamut/Extensions/LightCommandExtensions.cs similarity index 67% rename from src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/LightCommandExtensions.cs rename to src/Q42.HueApi.ColorConverters/Gamut/Extensions/LightCommandExtensions.cs index 891cd789..ffb82db3 100644 --- a/src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/LightCommandExtensions.cs +++ b/src/Q42.HueApi.ColorConverters/Gamut/Extensions/LightCommandExtensions.cs @@ -1,20 +1,21 @@ -using System; +using Q42.HueApi.Models.Gamut; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Q42.HueApi.ColorConverters.OriginalWithModel +namespace Q42.HueApi.ColorConverters.Gamut { public static class LightCommandExtensions { - public static LightCommand SetColor(this LightCommand lightCommand, RGBColor color, string model = "LCT001") + public static LightCommand SetColor(this LightCommand lightCommand, RGBColor color, CIE1931Gamut? gamut) { if (lightCommand == null) throw new ArgumentNullException(nameof(lightCommand)); - var point = HueColorConverter.RgbToXY(color, model); + var point = HueColorConverter.RgbToXY(color, gamut); return lightCommand.SetColor(point.x, point.y); } } diff --git a/src/Q42.HueApi.ColorConverters/Gamut/Extensions/LightExtensions.cs b/src/Q42.HueApi.ColorConverters/Gamut/Extensions/LightExtensions.cs new file mode 100644 index 00000000..5bfd9173 --- /dev/null +++ b/src/Q42.HueApi.ColorConverters/Gamut/Extensions/LightExtensions.cs @@ -0,0 +1,22 @@ +using Q42.HueApi.Models.Gamut; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Q42.HueApi.ColorConverters.Gamut +{ + public static class LightExtensions + { + public static string ToHex(this Light light) + { + return light.ToHex(light.Capabilities?.Control?.ColorGamut); + } + + public static string ToHex(this Light light, CIE1931Gamut? gamut) + { + return HueColorConverter.HexFromState(light.State, gamut); + } + } +} diff --git a/src/Q42.HueApi.ColorConverters/Gamut/Extensions/StateExtensions.cs b/src/Q42.HueApi.ColorConverters/Gamut/Extensions/StateExtensions.cs new file mode 100644 index 00000000..7c7067b9 --- /dev/null +++ b/src/Q42.HueApi.ColorConverters/Gamut/Extensions/StateExtensions.cs @@ -0,0 +1,22 @@ +using Q42.HueApi.Models.Gamut; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Q42.HueApi.ColorConverters.Gamut +{ + public static class StateExtensions + { + public static string ToHex(this State state, CIE1931Gamut? gamut) + { + return HueColorConverter.HexFromState(state, gamut); + } + + public static RGBColor ToRgb(this State state, CIE1931Gamut? gamut) + { + return HueColorConverter.RgbFromState(state, gamut); + } + } +} diff --git a/src/Q42.HueApi.ColorConverters/OriginalWithModel/HueColorConverter.cs b/src/Q42.HueApi.ColorConverters/Gamut/HueColorConverter.cs similarity index 90% rename from src/Q42.HueApi.ColorConverters/OriginalWithModel/HueColorConverter.cs rename to src/Q42.HueApi.ColorConverters/Gamut/HueColorConverter.cs index bd8cf807..067abd0f 100644 --- a/src/Q42.HueApi.ColorConverters/OriginalWithModel/HueColorConverter.cs +++ b/src/Q42.HueApi.ColorConverters/Gamut/HueColorConverter.cs @@ -1,11 +1,12 @@ -using Q42.HueApi.ColorConverters; +using Q42.HueApi.ColorConverters; +using Q42.HueApi.Models.Gamut; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Q42.HueApi.ColorConverters.OriginalWithModel +namespace Q42.HueApi.ColorConverters.Gamut { /// /// Used to convert colors between XY and RGB @@ -16,7 +17,7 @@ namespace Q42.HueApi.ColorConverters.OriginalWithModel /// internal static class HueColorConverter { - public static CIE1931Point RgbToXY(RGBColor color, string model) + public static CIE1931Point RgbToXY(RGBColor color, CIE1931Gamut? gamut) { // Apply gamma correction. Convert non-linear RGB colour components // to linear color intensity levels. @@ -61,13 +62,10 @@ public static CIE1931Point RgbToXY(RGBColor color, string model) xyPoint = new CIE1931Point(X / (X + Y + Z), Y / (X + Y + Z)); } - if (model != null) + if (gamut.HasValue) { - //Check if the given XY value is within the colourreach of our lamps. - CIE1931Gamut gamut = CIE1931Gamut.ForModel(model); - // The point, adjusted it to the nearest point that is within the gamut of the lamp, if neccessary. - return gamut.NearestContainedPoint(xyPoint); + return gamut.Value.NearestContainedPoint(xyPoint); } return xyPoint; } @@ -78,9 +76,9 @@ public static CIE1931Point RgbToXY(RGBColor color, string model) /// /// /// - public static string HexFromState(State state, string model) + public static string HexFromState(State state, CIE1931Gamut? gamut) { - var rgb = RgbFromState(state, model); + var rgb = RgbFromState(state, gamut); return rgb.ToHex(); } @@ -90,7 +88,7 @@ public static string HexFromState(State state, string model) /// /// /// - public static RGBColor RgbFromState(State state, string model) + public static RGBColor RgbFromState(State state, CIE1931Gamut? gamut) { if (state == null) throw new ArgumentNullException(nameof(state)); @@ -98,22 +96,20 @@ public static RGBColor RgbFromState(State state, string model) return new RGBColor(0,0,0); if (state.ColorCoordinates != null && state.ColorCoordinates.Length == 2) //Based on XY value { - var color = XYToRgb(new CIE1931Point(state.ColorCoordinates[0], state.ColorCoordinates[1]), model); + var color = XYToRgb(new CIE1931Point(state.ColorCoordinates[0], state.ColorCoordinates[1]), gamut); return color; } return new RGBColor(1, 1, 1); ; } - public static RGBColor XYToRgb(CIE1931Point point, string model) + public static RGBColor XYToRgb(CIE1931Point point, CIE1931Gamut? gamut) { - if (model != null) + if (gamut.HasValue) { - CIE1931Gamut gamut = CIE1931Gamut.ForModel(model); - // If the color is outside the lamp's gamut, adjust to the nearest color // inside the lamp's gamut. - point = gamut.NearestContainedPoint(point); + point = gamut.Value.NearestContainedPoint(point); } // Also adjust it to be in the Philips "Wide Gamut" if not already. diff --git a/src/Q42.HueApi.ColorConverters/OriginalWithModel/CIE1931Gamut.cs b/src/Q42.HueApi.ColorConverters/OriginalWithModel/CIE1931Gamut.cs deleted file mode 100644 index d3d46927..00000000 --- a/src/Q42.HueApi.ColorConverters/OriginalWithModel/CIE1931Gamut.cs +++ /dev/null @@ -1,200 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Q42.HueApi.ColorConverters.OriginalWithModel -{ - /// - /// Represents a gamut with red, green and blue primaries in CIE1931 color space. - /// - internal struct CIE1931Gamut - { - public readonly CIE1931Point Red; - public readonly CIE1931Point Green; - public readonly CIE1931Point Blue; - - public CIE1931Gamut(CIE1931Point red, CIE1931Point green, CIE1931Point blue) - { - this.Red = red; - this.Green = green; - this.Blue = blue; - } - - public static readonly CIE1931Gamut PhilipsWideGamut = new CIE1931Gamut( - red: new CIE1931Point(0.700607, 0.299301), - green: new CIE1931Point(0.172416, 0.746797), - blue: new CIE1931Point(0.135503, 0.039879) - ); - - public static CIE1931Gamut ForModel(string modelId) - { - // Details from http://www.developers.meethue.com/documentation/supported-lights - - List gamutA = new List() { - "LLC001" /* Monet, Renoir, Mondriaan (gen II) */, - "LLC005" /* Bloom (gen II) */, - "LLC006" /* Iris (gen III) */, - "LLC007" /* Bloom, Aura (gen III) */, - "LLC010" /* Iris */, - "LLC011" /* Hue Bloom */, - "LLC012" /* Hue Bloom */, - "LLC013" /* Storylight */, - "LST001" /* Light Strips */, - "LLC014" /* Bloom, Aura (gen III) */ - }; - - List gamutB = new List() { - "LCT001" /* Hue A19 */, - "LCT007" /* Hue A19 */, - "LCT002" /* Hue BR30 */, - "LCT003" /* Hue GU10 */, - "LLM001" /* Color Light Module */ - }; - - List gamutC = new List() { - "LLC020" /* Hue Go */, - "LST002" /* Hue LightStrips Plus */, - "LCT011" /* Hue BR30 */, - "LCT012" /* Hue color candle */, - "LCT010" /* Hue A19 gen 3 */, - "LCT014" /* Hue A19 gen 3 */, - "LCT015" /* Hue A19 gen 3 */, - "LCT016" /* Hue A19 gen 3 */ - }; - - if (gamutA.Contains(modelId)) - { - return new CIE1931Gamut( - red: new CIE1931Point(0.704, 0.296), - green: new CIE1931Point(0.2151, 0.7106), - blue: new CIE1931Point(0.138, 0.08) - ); - } - else if (gamutB.Contains(modelId)) - { - return new CIE1931Gamut( - red: new CIE1931Point(0.675, 0.322), - green: new CIE1931Point(0.409, 0.518), - blue: new CIE1931Point(0.167, 0.04) - ); - } - else if (gamutC.Contains(modelId)) - { - return new CIE1931Gamut( - red: new CIE1931Point(0.692, 0.308), - green: new CIE1931Point(0.17, 0.7), - blue: new CIE1931Point(0.153, 0.048) - ); - } - else - { - // A gamut containing all colors (and then some!) - return new CIE1931Gamut( - red: new CIE1931Point(1.0F, 0.0F), - green: new CIE1931Point(0.0F, 1.0F), - blue: new CIE1931Point(0.0F, 0.0F) - ); - } - } - - public bool Contains(CIE1931Point point) - { - // Arrangement of points in color space: - // - // ^ G - // y| - // | R - // | B - // .-------------------> - // x - // - return IsBelow(Blue, Green, point) && - IsBelow(Green, Red, point) && - IsAbove(Red, Blue, point); - } - - private static bool IsBelow(CIE1931Point a, CIE1931Point b, CIE1931Point point) - { - double slope = (a.y - b.y) / (a.x - b.x); - double intercept = a.y - slope * a.x; - - double maxY = point.x * slope + intercept; - return point.y <= maxY; - } - - private static bool IsAbove(CIE1931Point blue, CIE1931Point red, CIE1931Point point) - { - double slope = (blue.y - red.y) / (blue.x - red.x); - double intercept = blue.y - slope * blue.x; - - double minY = point.x * slope + intercept; - return point.y >= minY; - } - - public CIE1931Point NearestContainedPoint(CIE1931Point point) - { - if (Contains(point)) - { - // If this gamut already contains the point, then no adjustment is required. - return point; - } - - // Find the closest point on each line in the triangle. - CIE1931Point pAB = GetClosestPointOnLine(Red, Green, point); - CIE1931Point pAC = GetClosestPointOnLine(Red, Blue, point); - CIE1931Point pBC = GetClosestPointOnLine(Green, Blue, point); - - //Get the distances per point and see which point is closer to our Point. - double dAB = GetDistanceBetweenTwoPoints(point, pAB); - double dAC = GetDistanceBetweenTwoPoints(point, pAC); - double dBC = GetDistanceBetweenTwoPoints(point, pBC); - - double lowest = dAB; - CIE1931Point closestPoint = pAB; - - if (dAC < lowest) - { - lowest = dAC; - closestPoint = pAC; - } - if (dBC < lowest) - { - lowest = dBC; - closestPoint = pBC; - } - return closestPoint; - } - - private static CIE1931Point GetClosestPointOnLine(CIE1931Point a, CIE1931Point b, CIE1931Point p) - { - CIE1931Point AP = new CIE1931Point(p.x - a.x, p.y - a.y); - CIE1931Point AB = new CIE1931Point(b.x - a.x, b.y - a.y); - - double ab2 = AB.x * AB.x + AB.y * AB.y; - double ap_ab = AP.x * AB.x + AP.y * AB.y; - - double t = ap_ab / ab2; - - // Bound to ends of line between A and B. - if (t < 0.0f) - { - t = 0.0f; - } - else if (t > 1.0f) - { - t = 1.0f; - } - - return new CIE1931Point(a.x + AB.x * t, a.y + AB.y * t); - } - - private static double GetDistanceBetweenTwoPoints(CIE1931Point one, CIE1931Point two) - { - double dx = one.x - two.x; // horizontal difference - double dy = one.y - two.y; // vertical difference - return Math.Sqrt(dx * dx + dy * dy); - } - } -} diff --git a/src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/LightExtensions.cs b/src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/LightExtensions.cs deleted file mode 100644 index 3b9653fa..00000000 --- a/src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/LightExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Q42.HueApi.ColorConverters.OriginalWithModel -{ - public static class LightExtensions - { - public static string ToHex(this Light light) - { - return HueColorConverter.HexFromState(light.State, light.ModelId); - } - } -} diff --git a/src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/StateExtensions.cs b/src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/StateExtensions.cs deleted file mode 100644 index bc0197a2..00000000 --- a/src/Q42.HueApi.ColorConverters/OriginalWithModel/Extensions/StateExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Q42.HueApi.ColorConverters.OriginalWithModel -{ - public static class StateExtensions - { - public static string ToHex(this State state, string model = "LCT001") - { - return HueColorConverter.HexFromState(state, model); - } - - public static RGBColor ToRgb(this State state, string model = "LCT001") - { - return HueColorConverter.RgbFromState(state, model); - } - } -} diff --git a/src/Q42.HueApi.Tests/App.config b/src/Q42.HueApi.Tests/App.config index 5ed0560a..e48e929f 100644 --- a/src/Q42.HueApi.Tests/App.config +++ b/src/Q42.HueApi.Tests/App.config @@ -1,10 +1,10 @@ - - - + + + diff --git a/src/Q42.HueApi/Converters/GamutConverter.cs b/src/Q42.HueApi/Converters/GamutConverter.cs new file mode 100644 index 00000000..8139baab --- /dev/null +++ b/src/Q42.HueApi/Converters/GamutConverter.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Q42.HueApi.Models.Gamut; + +namespace Q42.HueApi.Converters +{ + internal class GamutConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(CIE1931Gamut?) || objectType == typeof(CIE1931Gamut); + } + + public override bool CanRead + { + get { return true; } + } + + public override bool CanWrite + { + get { return true; } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.StartArray) + { + + JArray array = JArray.Load(reader); + var gammutValues = array.ToObject>>(); + + if (gammutValues.Count == 3) + { + var red = new CIE1931Point(gammutValues[0][0], gammutValues[0][1]); + var green = new CIE1931Point(gammutValues[1][0], gammutValues[1][1]); + var blue = new CIE1931Point(gammutValues[2][0], gammutValues[2][1]); + + return new CIE1931Gamut(red, green, blue); + } + } + + return null; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + CIE1931Gamut gammut; + + if (value == null) + { + writer.WriteNull(); + return; + } + + if (value is CIE1931Gamut?) + gammut = ((CIE1931Gamut?)value).Value; + else + gammut = (CIE1931Gamut)value; + + var red = new List() { gammut.Red.x, gammut.Red.y }; + var green = new List() { gammut.Green.x, gammut.Green.y }; + var blue = new List() { gammut.Blue.x, gammut.Blue.y }; + + var result = new List>() { red, green, blue }; + + writer.WriteValue(result); + } + } +} diff --git a/src/Q42.HueApi/Models/Gamut/CIE1931Gamut.cs b/src/Q42.HueApi/Models/Gamut/CIE1931Gamut.cs new file mode 100644 index 00000000..9d6711a4 --- /dev/null +++ b/src/Q42.HueApi/Models/Gamut/CIE1931Gamut.cs @@ -0,0 +1,213 @@ +using Newtonsoft.Json; +using Q42.HueApi.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Q42.HueApi.Models.Gamut +{ + /// + /// Represents a gamut with red, green and blue primaries in CIE1931 color space. + /// + [JsonConverter(typeof(GamutConverter))] + public struct CIE1931Gamut + { + public readonly CIE1931Point Red; + public readonly CIE1931Point Green; + public readonly CIE1931Point Blue; + + public CIE1931Gamut(CIE1931Point red, CIE1931Point green, CIE1931Point blue) + { + this.Red = red; + this.Green = green; + this.Blue = blue; + } + + public static readonly CIE1931Gamut PhilipsWideGamut = new CIE1931Gamut( + red: new CIE1931Point(0.700607, 0.299301), + green: new CIE1931Point(0.172416, 0.746797), + blue: new CIE1931Point(0.135503, 0.039879) + ); + + public static CIE1931Gamut ModelTypeA => new CIE1931Gamut( + red: new CIE1931Point(0.704, 0.296), + green: new CIE1931Point(0.2151, 0.7106), + blue: new CIE1931Point(0.138, 0.08) + ); + + public static CIE1931Gamut ModelTypeB => new CIE1931Gamut( + red: new CIE1931Point(0.675, 0.322), + green: new CIE1931Point(0.409, 0.518), + blue: new CIE1931Point(0.167, 0.04) + ); + + public static CIE1931Gamut ModelTypeC => new CIE1931Gamut( + red: new CIE1931Point(0.692, 0.308), + green: new CIE1931Point(0.17, 0.7), + blue: new CIE1931Point(0.153, 0.048) + ); + + public static CIE1931Gamut All => new CIE1931Gamut( + red: new CIE1931Point(1.0F, 0.0F), + green: new CIE1931Point(0.0F, 1.0F), + blue: new CIE1931Point(0.0F, 0.0F) + ); + + + [Obsolete("List of Models is no longer updated by Philips. Use The Capabilities.GamutType or Gamut from the Light")] + public static CIE1931Gamut ForModel(string modelId) + { + // Details from http://www.developers.meethue.com/documentation/supported-lights + + List gamutA = new List() { + "LLC001" /* Monet, Renoir, Mondriaan (gen II) */, + "LLC005" /* Bloom (gen II) */, + "LLC006" /* Iris (gen III) */, + "LLC007" /* Bloom, Aura (gen III) */, + "LLC010" /* Iris */, + "LLC011" /* Hue Bloom */, + "LLC012" /* Hue Bloom */, + "LLC013" /* Storylight */, + "LST001" /* Light Strips */, + "LLC014" /* Bloom, Aura (gen III) */ + }; + + List gamutB = new List() { + "LCT001" /* Hue A19 */, + "LCT007" /* Hue A19 */, + "LCT002" /* Hue BR30 */, + "LCT003" /* Hue GU10 */, + "LLM001" /* Color Light Module */ + }; + + List gamutC = new List() { + "LLC020" /* Hue Go */, + "LST002" /* Hue LightStrips Plus */, + "LCT011" /* Hue BR30 */, + "LCT012" /* Hue color candle */, + "LCT010" /* Hue A19 gen 3 */, + "LCT014" /* Hue A19 gen 3 */, + "LCT015" /* Hue A19 gen 3 */, + "LCT016" /* Hue A19 gen 3 */ + }; + + if (gamutA.Contains(modelId)) + { + return CIE1931Gamut.ModelTypeA; + } + else if (gamutB.Contains(modelId)) + { + return CIE1931Gamut.ModelTypeB; + } + else if (gamutC.Contains(modelId)) + { + return CIE1931Gamut.ModelTypeC; + } + else + { + // A gamut containing all colors (and then some!) + return CIE1931Gamut.All; + } + } + + public bool Contains(CIE1931Point point) + { + // Arrangement of points in color space: + // + // ^ G + // y| + // | R + // | B + // .-------------------> + // x + // + return IsBelow(Blue, Green, point) && + IsBelow(Green, Red, point) && + IsAbove(Red, Blue, point); + } + + private static bool IsBelow(CIE1931Point a, CIE1931Point b, CIE1931Point point) + { + double slope = (a.y - b.y) / (a.x - b.x); + double intercept = a.y - slope * a.x; + + double maxY = point.x * slope + intercept; + return point.y <= maxY; + } + + private static bool IsAbove(CIE1931Point blue, CIE1931Point red, CIE1931Point point) + { + double slope = (blue.y - red.y) / (blue.x - red.x); + double intercept = blue.y - slope * blue.x; + + double minY = point.x * slope + intercept; + return point.y >= minY; + } + + public CIE1931Point NearestContainedPoint(CIE1931Point point) + { + if (Contains(point)) + { + // If this gamut already contains the point, then no adjustment is required. + return point; + } + + // Find the closest point on each line in the triangle. + CIE1931Point pAB = GetClosestPointOnLine(Red, Green, point); + CIE1931Point pAC = GetClosestPointOnLine(Red, Blue, point); + CIE1931Point pBC = GetClosestPointOnLine(Green, Blue, point); + + //Get the distances per point and see which point is closer to our Point. + double dAB = GetDistanceBetweenTwoPoints(point, pAB); + double dAC = GetDistanceBetweenTwoPoints(point, pAC); + double dBC = GetDistanceBetweenTwoPoints(point, pBC); + + double lowest = dAB; + CIE1931Point closestPoint = pAB; + + if (dAC < lowest) + { + lowest = dAC; + closestPoint = pAC; + } + if (dBC < lowest) + { + lowest = dBC; + closestPoint = pBC; + } + return closestPoint; + } + + private static CIE1931Point GetClosestPointOnLine(CIE1931Point a, CIE1931Point b, CIE1931Point p) + { + CIE1931Point AP = new CIE1931Point(p.x - a.x, p.y - a.y); + CIE1931Point AB = new CIE1931Point(b.x - a.x, b.y - a.y); + + double ab2 = AB.x * AB.x + AB.y * AB.y; + double ap_ab = AP.x * AB.x + AP.y * AB.y; + + double t = ap_ab / ab2; + + // Bound to ends of line between A and B. + if (t < 0.0f) + { + t = 0.0f; + } + else if (t > 1.0f) + { + t = 1.0f; + } + + return new CIE1931Point(a.x + AB.x * t, a.y + AB.y * t); + } + + private static double GetDistanceBetweenTwoPoints(CIE1931Point one, CIE1931Point two) + { + double dx = one.x - two.x; // horizontal difference + double dy = one.y - two.y; // vertical difference + return Math.Sqrt(dx * dx + dy * dy); + } + } +} diff --git a/src/Q42.HueApi.ColorConverters/OriginalWithModel/CIE1931Point.cs b/src/Q42.HueApi/Models/Gamut/CIE1931Point.cs similarity index 80% rename from src/Q42.HueApi.ColorConverters/OriginalWithModel/CIE1931Point.cs rename to src/Q42.HueApi/Models/Gamut/CIE1931Point.cs index 57efac7b..0852cdf5 100644 --- a/src/Q42.HueApi.ColorConverters/OriginalWithModel/CIE1931Point.cs +++ b/src/Q42.HueApi/Models/Gamut/CIE1931Point.cs @@ -1,15 +1,15 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Q42.HueApi.ColorConverters.OriginalWithModel +namespace Q42.HueApi.Models.Gamut { - /// - /// Represents a point in CIE1931 color space. - /// - internal struct CIE1931Point + /// + /// Represents a point in CIE1931 color space. + /// + public struct CIE1931Point { /// /// The D65 White Point. diff --git a/src/Q42.HueApi/Models/Light.cs b/src/Q42.HueApi/Models/Light.cs index 86b518f8..ed4001d7 100644 --- a/src/Q42.HueApi/Models/Light.cs +++ b/src/Q42.HueApi/Models/Light.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using Q42.HueApi.Models.Gamut; using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -81,10 +82,47 @@ public class StreamingLightCapabilities [DataContract] public class LightCapabilities { + [DataMember(Name = "certified")] + public bool Certified { get; set; } + + [DataMember(Name = "control")] + public Control Control { get; set; } + [DataMember(Name = "streaming")] public StreamingLightCapabilities Streaming { get; set; } } + [DataContract] + public class Control + { + [DataMember(Name = "mindimlevel")] + public int? MinDimLevel { get; set; } + + [DataMember(Name = "maxlumen")] + public int? MaxLumen { get; set; } + + /// + /// A, B or C + /// + [DataMember(Name = "colorgamuttype")] + public string ColorGamutType { get; set; } + + [DataMember(Name = "colorgamut")] + public CIE1931Gamut? ColorGamut { get; set; } + + [DataMember(Name = "ct")] + public ColorTemperature ColorTemperature { get; set; } + } + + [DataContract] + public class ColorTemperature + { + [DataMember(Name = "min")] + public int Min { get; set; } + [DataMember(Name = "max")] + public int Max { get; set; } + } + [DataContract] public class LightConfig {