1 #include "stdafx.h"
2 #include "PhilipsHue.h"
3 #include "../../main/Logger.h"
4 #include <cmath>
5
6 /*
7 * Helper funtions to convert from Philips XY color to RGB
8 * Based on: https://github.com/benknight/hue-python-rgb-converter/blob/master/rgbxy
9 * And also: https://developers.meethue.com/documentation/color-conversions-rgb-xy (login needed)
10 * https://github.com/Q42Philips/hue-color-converter/blob/master/index.js
11 */
12
13 struct point {
14 double x;
15 double y;
pointpoint16 point() {
17 x = 0.0;
18 y = 0.0;
19 }
pointpoint20 point(double x, double y) {
21 this->x = x;
22 this->y = y;
23 }
24 };
25
26 static const point colorPointsGamut_A[3] = {point(0.703, 0.296),
27 point(0.214, 0.709),
28 point(0.139, 0.081)};
29
30 static const point colorPointsGamut_B[3] = {point(0.674, 0.322),
31 point(0.408, 0.517),
32 point(0.168, 0.041)};
33
34 static const point colorPointsGamut_C[3] = {point(0.692, 0.308),
35 point(0.17, 0.7),
36 point(0.153, 0.048)};
37
38 static const point colorPointsGamut_Default[3] = {point(1.0, 0.0),
39 point(0.0, 1.0),
40 point(0.0, 0.0)};
41
42 static const char* GAMUT_A_BULBS_LIST_v[] = {"LLC001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", "LLC014", "LLC013", "LST001"};
43 static const char* GAMUT_B_BULBS_LIST_v[] = {"LCT001", "LCT002", "LCT003", "LCT004", "LLM001", "LCT005", "LCT006", "LCT007"};
44 static const char* GAMUT_C_BULBS_LIST_v[] = {"LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LLC020", "LST002", "LCS001", "LCG002"};
45 static const char* MULTI_SOURCE_LUMINAIRES_v[] = {"HBL001", "HBL002", "HBL003", "HIL001", "HIL002", "HEL001", "HEL002"};
46 static std::vector<std::string> GAMUT_A_BULBS_LIST(GAMUT_A_BULBS_LIST_v, GAMUT_A_BULBS_LIST_v + sizeof(GAMUT_A_BULBS_LIST_v) / sizeof(GAMUT_A_BULBS_LIST_v[0]));
47 static std::vector<std::string> GAMUT_B_BULBS_LIST(GAMUT_B_BULBS_LIST_v, GAMUT_B_BULBS_LIST_v + sizeof(GAMUT_B_BULBS_LIST_v) / sizeof(GAMUT_B_BULBS_LIST_v[0]));
48 static std::vector<std::string> GAMUT_C_BULBS_LIST(GAMUT_C_BULBS_LIST_v, GAMUT_C_BULBS_LIST_v + sizeof(GAMUT_C_BULBS_LIST_v) / sizeof(GAMUT_C_BULBS_LIST_v[0]));
49 static std::vector<std::string> MULTI_SOURCE_LUMINAIRES(MULTI_SOURCE_LUMINAIRES_v, MULTI_SOURCE_LUMINAIRES_v + sizeof(MULTI_SOURCE_LUMINAIRES_v) / sizeof(MULTI_SOURCE_LUMINAIRES_v[0]));;
50
get_light_gamut(const std::string & modelid)51 static const point* get_light_gamut(const std::string &modelid)
52 {
53 if (std::find(GAMUT_A_BULBS_LIST.begin(), GAMUT_A_BULBS_LIST.end(), modelid) != GAMUT_A_BULBS_LIST.end())
54 return colorPointsGamut_A;
55 else if (std::find(GAMUT_B_BULBS_LIST.begin(), GAMUT_B_BULBS_LIST.end(), modelid) != GAMUT_B_BULBS_LIST.end())
56 return colorPointsGamut_B;
57 else if (std::find(MULTI_SOURCE_LUMINAIRES.begin(), MULTI_SOURCE_LUMINAIRES.end(), modelid) != MULTI_SOURCE_LUMINAIRES.end())
58 return colorPointsGamut_B;
59 else if (std::find(GAMUT_C_BULBS_LIST.begin(), GAMUT_C_BULBS_LIST.end(), modelid) != GAMUT_C_BULBS_LIST.end())
60 return colorPointsGamut_C;
61 else return colorPointsGamut_Default;
62 }
63
cross_product(point p1,point p2)64 static double cross_product(point p1, point p2)
65 {
66 // Returns the cross product of two points
67 // (This is really the determinant, not the cross product)
68 return p1.x * p2.y - p1.y * p2.x;
69 }
70
check_point_in_lamps_reach(point p,const std::string & modelid)71 static bool check_point_in_lamps_reach(point p, const std::string &modelid)
72 {
73 const point* gamut = get_light_gamut(modelid);
74 point Red = gamut[0];
75 point Lime = gamut[1];
76 point Blue = gamut[2];
77
78 // Check if the provided XYPoint can be recreated by a Hue lamp
79 point v1 = point(Lime.x - Red.x, Lime.y - Red.y);
80 point v2 = point(Blue.x - Red.x, Blue.y - Red.y);
81
82 point q = point(p.x - Red.x, p.y - Red.y);
83 double s = cross_product(q, v2) / cross_product(v1, v2);
84 double t = cross_product(v1, q) / cross_product(v1, v2);
85
86 return (s >= 0.0) && (t >= 0.0) && (s + t <= 1.0);
87 }
88
get_closest_point_to_line(point A,point B,point P)89 static point get_closest_point_to_line(point A, point B, point P)
90 {
91 // Find the closest point on a line. This point will be reproducible by a Hue lamp.
92 point AP = point(P.x - A.x, P.y - A.y);
93 point AB = point(B.x - A.x, B.y - A.y);
94 double ab2 = AB.x * AB.x + AB.y * AB.y;
95 double ap_ab = AP.x * AB.x + AP.y * AB.y;
96 double t = ap_ab / ab2;
97
98 if (t < 0.0)
99 t = 0.0;
100 else if (t > 1.0)
101 t = 1.0;
102
103 return point(A.x + AB.x * t, A.y + AB.y * t);
104 }
105
get_distance_between_two_points(point p1,point p2)106 static double get_distance_between_two_points(point p1, point p2)
107 {
108 // Returns the distance between two points.
109 double dx = p1.x - p2.x;
110 double dy = p1.y - p2.y;
111 return sqrt(dx * dx + dy * dy);
112 }
113
get_closest_point_to_point(point xy_point,const std::string & modelid)114 static point get_closest_point_to_point(point xy_point, const std::string &modelid)
115 {
116 const point* gamut = get_light_gamut(modelid);
117 point Red = gamut[0];
118 point Lime = gamut[1];
119 point Blue = gamut[2];
120
121 // Color is unreproducible, find the closest point on each line in the CIE 1931 'triangle'.
122 point pAB = get_closest_point_to_line(Red, Lime, xy_point);
123 point pAC = get_closest_point_to_line(Blue, Red, xy_point);
124 point pBC = get_closest_point_to_line(Lime, Blue, xy_point);
125
126 // Get the distances per point and see which point is closer to our Point.
127 double dAB = get_distance_between_two_points(xy_point, pAB);
128 double dAC = get_distance_between_two_points(xy_point, pAC);
129 double dBC = get_distance_between_two_points(xy_point, pBC);
130
131 double lowest = dAB;
132 point closest_point = pAB;
133
134 if (dAC < lowest) {
135 lowest = dAC;
136 closest_point = pAC;
137 }
138
139 if (dBC < lowest) {
140 lowest = dBC;
141 closest_point = pBC;
142 }
143
144 // Change the xy value to a value which is within the reach of the lamp.
145 double cx = closest_point.x;
146 double cy = closest_point.y;
147
148 return point(cx, cy);
149 }
150
RgbFromXY(const double x,const double y,const double bri,const std::string & modelid,uint8_t & r8,uint8_t & g8,uint8_t & b8)151 void CPhilipsHue::RgbFromXY(const double x, const double y, const double bri, const std::string &modelid, uint8_t &r8, uint8_t &g8, uint8_t &b8)
152 {
153 /* Returns (r, g, b) for given x, y values.
154 Implementation of the instructions found on the Philips Hue iOS SDK docs: http://goo.gl/kWKXKl
155
156 Check if the xy value is within the color gamut of the lamp.
157 If not continue with step 2, otherwise step 3.
158 We do this to calculate the most accurate color the given light can actually do.*/
159
160 point xy_point = point(x, y);
161
162 // TODO: Maybe thsi can be skipped when converting xy-point reported to a light to rgb - why would a light report an unreachable point?
163 if (!check_point_in_lamps_reach(xy_point, modelid)) {
164 // Calculate the closest point on the color gamut triangle and use that as xy value
165 xy_point = get_closest_point_to_point(xy_point, modelid);
166 }
167
168 // Calculate XYZ values Convert using the following formulas:
169 double Y = bri;
170 double X = (Y / xy_point.y) * xy_point.x;
171 double Z = (Y / xy_point.y) * (1 - xy_point.x - xy_point.y);
172
173 // Convert to RGB using Wide RGB D65 conversion
174 double r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
175 double g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
176 double b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
177
178 // Scale before gamma correction
179 if (r > b && r > g && r > 1.0) {
180 // red is too big
181 g = g / r;
182 b = b / r;
183 r = 1.0;
184 }
185 else if (g > b && g > r && g > 1.0) {
186 // green is too big
187 r = r / g;
188 b = b / g;
189 g = 1.0;
190 }
191 else if (b > r && b > g && b > 1.0) {
192 // blue is too big
193 r = r / b;
194 g = g / b;
195 b = 1.0;
196 }
197
198 // Apply gamma correction
199 r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * pow(r, (1.0 / 2.4)) - 0.055;
200 g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * pow(g, (1.0 / 2.4)) - 0.055;
201 b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * pow(b, (1.0 / 2.4)) - 0.055;
202
203 // Bring all negative components to zero
204 r = std::max(0.0,r);
205 g = std::max(0.0,g);
206 b = std::max(0.0,b);
207
208 // If one component is greater than 1, weight components by that value.
209 // TODO: Rewrite with C++11 lambda functions:
210 //max_component = max(r, g, b)
211 // if max_component > 1:
212 // r, g, b = map(lambda x: x / max_component, [r, g, b])
213 if (r > b && r > g) {
214 // red is biggest
215 if (r > 1.0) {
216 g = g / r;
217 b = b / r;
218 r = 1.0;
219 }
220 }
221 else if (g > b && g > r) {
222 // green is biggest
223 if (g > 1.0) {
224 r = r / g;
225 b = b / g;
226 g = 1.0;
227 }
228 }
229 else if (b > r && b > g) {
230 // blue is biggest
231 if (b > 1.0) {
232 r = r / b;
233 g = g / b;
234 b = 1.0;
235 }
236 }
237
238 // Convert the RGB values to your color object The rgb values from the above formulas are between 0.0 and 1.0.
239 r8 = uint8_t(r * 255.0);
240 g8 = uint8_t(g * 255.0);
241 b8 = uint8_t(b * 255.0);
242 }
243
StatesSimilar(const _tHueLightState & s1,const _tHueLightState & s2)244 bool CPhilipsHue::StatesSimilar(const _tHueLightState &s1, const _tHueLightState &s2)
245 {
246 bool res = false;
247 if (s1.on == s2.on &&
248 s1.mode == s2.mode &&
249 abs(s1.level - s2.level) <= 1)
250 {
251 switch (s1.mode)
252 {
253 case HLMODE_NONE:
254 res = true;
255 break;
256 case HLMODE_CT:
257 res = abs(s1.ct - s2.ct) <= 3; // 3 is 1% of 255
258 break;
259 case HLMODE_HS:
260 {
261 uint16_t h1 = (uint16_t) s1.hue;
262 uint16_t h2 = (uint16_t) s2.hue;
263 res = abs(int16_t(h1-h2)) < 655 && // 655 is 1% of 65535, the range of hue
264 abs(s1.sat - s2.sat) <= 3; // 3 is 1% of 255, the range of sat
265 break;
266 }
267 case HLMODE_XY:
268 res = abs(s1.x - s2.x) < 0.01 && // 655 is 1% of 65535, the range of hue
269 abs(s1.y - s2.y) < 0.01; // 3 is 1% of 255, the range of sat
270 break;
271 }
272 }
273 return res;
274 }
275