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