1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2013 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU General Public License Version 2
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #include "config.h"
23 
24 #include <glib.h>
25 #include <lcms2.h>
26 #include <stdlib.h>
27 
28 #include "huey-ctx.h"
29 #include "huey-device.h"
30 #include "huey-enum.h"
31 
32 static void	huey_ctx_class_init	(HueyCtxClass	*klass);
33 static void	huey_ctx_init		(HueyCtx	*ctx);
34 static void	huey_ctx_finalize	(GObject	*object);
35 
36 #define GET_PRIVATE(o) (huey_ctx_get_instance_private (o))
37 
38 #define HUEY_CONTROL_MESSAGE_TIMEOUT	50000 /* ms */
39 #define HUEY_MAX_READ_RETRIES		5
40 
41 /* The CY7C63001 is paired with a 6.00Mhz crystal */
42 #define HUEY_CLOCK_FREQUENCY		6e6
43 
44 /* It takes 6 clock pulses to process a single 16bit increment (INC)
45  * instruction and check for the carry so this is the fastest a loop
46  * can be processed. */
47 #define HUEY_POLL_FREQUENCY		1e6
48 
49 /* Picked out of thin air, just to try to match reality...
50  * I have no idea why we need to do this, although it probably
51  * indicates we doing something wrong. */
52 #define HUEY_XYZ_POST_MULTIPLY_FACTOR	3.428
53 
54 /**
55  * HueyCtxPrivate:
56  *
57  * Private #HueyCtx data
58  **/
59 typedef struct
60 {
61 	CdMat3x3		 calibration_crt;
62 	CdMat3x3		 calibration_lcd;
63 	CdVec3			 dark_offset;
64 	gchar			*unlock_string;
65 	gfloat			 calibration_value;
66 	GUsbDevice		*device;
67 } HueyCtxPrivate;
68 
69 enum {
70 	PROP_0,
71 	PROP_DEVICE,
72 	PROP_LAST
73 };
74 
G_DEFINE_TYPE_WITH_PRIVATE(HueyCtx,huey_ctx,G_TYPE_OBJECT)75 G_DEFINE_TYPE_WITH_PRIVATE (HueyCtx, huey_ctx, G_TYPE_OBJECT)
76 
77 /**
78  * huey_ctx_error_quark:
79  *
80  * Return value: An error quark.
81  *
82  * Since: 0.1.0
83  **/
84 GQuark
85 huey_ctx_error_quark (void)
86 {
87 	static GQuark quark = 0;
88 	if (!quark) {
89 		quark = g_quark_from_static_string ("huey_ctx_error");
90 	}
91 	return quark;
92 }
93 
94 
95 /**
96  * huey_ctx_get_device:
97  *
98  * Since: 0.1.29
99  **/
100 GUsbDevice *
huey_ctx_get_device(HueyCtx * ctx)101 huey_ctx_get_device (HueyCtx *ctx)
102 {
103 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
104 	g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL);
105 	return priv->device;
106 }
107 
108 /**
109  * huey_ctx_set_device:
110  *
111  * Since: 0.1.29
112  **/
113 void
huey_ctx_set_device(HueyCtx * ctx,GUsbDevice * device)114 huey_ctx_set_device (HueyCtx *ctx, GUsbDevice *device)
115 {
116 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
117 	g_return_if_fail (HUEY_IS_CTX (ctx));
118 	priv->device = g_object_ref (device);
119 }
120 
121 /**
122  * huey_ctx_setup:
123  *
124  * Since: 0.1.29
125  **/
126 gboolean
huey_ctx_setup(HueyCtx * ctx,GError ** error)127 huey_ctx_setup (HueyCtx *ctx, GError **error)
128 {
129 	gboolean ret;
130 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
131 
132 	g_return_val_if_fail (HUEY_IS_CTX (ctx), FALSE);
133 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
134 
135 	/* get matrix */
136 	cd_mat33_clear (&priv->calibration_lcd);
137 	ret = huey_device_read_register_matrix (priv->device,
138 						HUEY_EEPROM_ADDR_CALIBRATION_DATA_LCD,
139 						&priv->calibration_lcd,
140 						error);
141 	if (!ret)
142 		return FALSE;
143 	g_debug ("device calibration LCD: %s",
144 		 cd_mat33_to_string (&priv->calibration_lcd));
145 
146 	/* get another matrix, although this one is different... */
147 	cd_mat33_clear (&priv->calibration_crt);
148 	ret = huey_device_read_register_matrix (priv->device,
149 						HUEY_EEPROM_ADDR_CALIBRATION_DATA_CRT,
150 						&priv->calibration_crt,
151 						error);
152 	if (!ret)
153 		return FALSE;
154 	g_debug ("device calibration CRT: %s",
155 		 cd_mat33_to_string (&priv->calibration_crt));
156 
157 	/* this number is different on all three hueys */
158 	ret = huey_device_read_register_float (priv->device,
159 					       HUEY_EEPROM_ADDR_AMBIENT_CALIB_VALUE,
160 					       &priv->calibration_value,
161 					       error);
162 	if (!ret)
163 		return FALSE;
164 
165 	/* this vector changes between sensor 1 and 3 */
166 	ret = huey_device_read_register_vector (priv->device,
167 						HUEY_EEPROM_ADDR_DARK_OFFSET,
168 						&priv->dark_offset,
169 						error);
170 	if (!ret)
171 		return FALSE;
172 	return TRUE;
173 }
174 
175 /**
176  * huey_ctx_get_calibration_lcd:
177  *
178  * Since: 0.1.29
179  **/
180 const CdMat3x3 *
huey_ctx_get_calibration_lcd(HueyCtx * ctx)181 huey_ctx_get_calibration_lcd (HueyCtx *ctx)
182 {
183 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
184 	g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL);
185 	return &priv->calibration_lcd;
186 }
187 
188 /**
189  * huey_ctx_get_calibration_crt:
190  *
191  * Since: 0.1.29
192  **/
193 const CdMat3x3 *
huey_ctx_get_calibration_crt(HueyCtx * ctx)194 huey_ctx_get_calibration_crt (HueyCtx *ctx)
195 {
196 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
197 	g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL);
198 	return &priv->calibration_crt;
199 }
200 
201 /**
202  * huey_ctx_get_calibration_value:
203  *
204  * Since: 0.1.29
205  **/
206 gfloat
huey_ctx_get_calibration_value(HueyCtx * ctx)207 huey_ctx_get_calibration_value (HueyCtx *ctx)
208 {
209 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
210 	g_return_val_if_fail (HUEY_IS_CTX (ctx), -1);
211 	return priv->calibration_value;
212 }
213 
214 /**
215  * huey_ctx_get_dark_offset:
216  *
217  * Since: 0.1.29
218  **/
219 const CdVec3 *
huey_ctx_get_dark_offset(HueyCtx * ctx)220 huey_ctx_get_dark_offset (HueyCtx *ctx)
221 {
222 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
223 	g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL);
224 	return &priv->dark_offset;
225 }
226 
227 /**
228  * huey_ctx_get_unlock_string:
229  *
230  * Since: 0.1.29
231  **/
232 const gchar *
huey_ctx_get_unlock_string(HueyCtx * ctx)233 huey_ctx_get_unlock_string (HueyCtx *ctx)
234 {
235 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
236 	g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL);
237 	return priv->unlock_string;
238 }
239 
240 typedef struct {
241 	guint16	R;
242 	guint16	G;
243 	guint16	B;
244 } HueyCtxMultiplier;
245 
246 typedef struct {
247 	guint32	R;
248 	guint32	G;
249 	guint32	B;
250 } HueyCtxDeviceRaw;
251 
252 static gboolean
huey_ctx_sample_for_threshold(HueyCtx * ctx,HueyCtxMultiplier * threshold,HueyCtxDeviceRaw * raw,GError ** error)253 huey_ctx_sample_for_threshold (HueyCtx *ctx,
254 			       HueyCtxMultiplier *threshold,
255 			       HueyCtxDeviceRaw *raw,
256 			       GError **error)
257 {
258 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
259 	guint8 request[] = { HUEY_CMD_SENSOR_MEASURE_RGB,
260 			     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
261 	guint8 reply[8];
262 	gboolean ret;
263 	gsize reply_read;
264 
265 	/* these are 16 bit gain values */
266 	cd_buffer_write_uint16_be (request + 1, threshold->R);
267 	cd_buffer_write_uint16_be (request + 3, threshold->G);
268 	cd_buffer_write_uint16_be (request + 5, threshold->B);
269 
270 	/* measure, and get red */
271 	ret = huey_device_send_data (priv->device,
272 				     request, 8,
273 				     reply, 8,
274 				     &reply_read,
275 				     error);
276 	if (!ret)
277 		return FALSE;
278 
279 	/* get value */
280 	raw->R = cd_buffer_read_uint32_be (reply+2);
281 
282 	/* get green */
283 	request[0] = HUEY_CMD_READ_GREEN;
284 	ret = huey_device_send_data (priv->device,
285 				     request, 8,
286 				     reply, 8,
287 				     &reply_read,
288 				     error);
289 	if (!ret)
290 		return FALSE;
291 
292 	/* get value */
293 	raw->G = cd_buffer_read_uint32_be (reply+2);
294 
295 	/* get blue */
296 	request[0] = HUEY_CMD_READ_BLUE;
297 	ret = huey_device_send_data (priv->device,
298 				     request, 8,
299 				     reply, 8,
300 				     &reply_read,
301 				     error);
302 	if (!ret)
303 		return FALSE;
304 
305 	/* get value */
306 	raw->B = cd_buffer_read_uint32_be (reply+2);
307 	return TRUE;
308 }
309 
310 /**
311  * huey_ctx_convert_device_RGB_to_XYZ:
312  *
313  * / X \   ( / R \    / c a l \ )
314  * | Y | = ( | G |  * | m a t | ) x post_scale
315  * \ Z /   ( \ B /    \ l c d / )
316  *
317  **/
318 static void
huey_ctx_convert_device_RGB_to_XYZ(CdColorRGB * src,CdColorXYZ * dest,CdMat3x3 * calibration,gdouble post_scale)319 huey_ctx_convert_device_RGB_to_XYZ (CdColorRGB *src,
320 				    CdColorXYZ *dest,
321 				    CdMat3x3 *calibration,
322 				    gdouble post_scale)
323 {
324 	CdVec3 *result;
325 
326 	/* convolve */
327 	result = (CdVec3 *) dest;
328 	cd_mat33_vector_multiply (calibration, (CdVec3 *) src, result);
329 
330 	/* post-multiply */
331 	cd_vec3_scalar_multiply (result,
332 				 post_scale,
333 				 result);
334 }
335 
336 
337 /**
338  * huey_ctx_take_sample:
339  *
340  * Since: 0.1.29
341  **/
342 CdColorXYZ *
huey_ctx_take_sample(HueyCtx * ctx,CdSensorCap cap,GError ** error)343 huey_ctx_take_sample (HueyCtx *ctx, CdSensorCap cap, GError **error)
344 {
345 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
346 	CdColorRGB values;
347 	CdColorXYZ color_result;
348 	CdMat3x3 *device_calibration;
349 	CdVec3 *temp;
350 	gboolean ret;
351 	HueyCtxDeviceRaw color_native;
352 	HueyCtxMultiplier multiplier;
353 
354 	g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL);
355 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
356 
357 	/* no hardware support */
358 	if (cap == CD_SENSOR_CAP_PROJECTOR) {
359 		g_set_error_literal (error,
360 				     HUEY_CTX_ERROR,
361 				     HUEY_CTX_ERROR_NO_SUPPORT,
362 				     "Huey cannot measure in projector mode");
363 		return NULL;
364 	}
365 
366 	/* set this to one value for a quick approximate value */
367 	multiplier.R = 1;
368 	multiplier.G = 1;
369 	multiplier.B = 1;
370 	ret = huey_ctx_sample_for_threshold (ctx,
371 					     &multiplier,
372 					     &color_native,
373 					     error);
374 	if (!ret)
375 		return NULL;
376 	g_debug ("initial values: red=%u, green=%u, blue=%u",
377 		 color_native.R, color_native.G, color_native.B);
378 
379 	/* try to fill the 16 bit register for accuracy */
380 	multiplier.R = HUEY_POLL_FREQUENCY / color_native.R;
381 	multiplier.G = HUEY_POLL_FREQUENCY / color_native.G;
382 	multiplier.B = HUEY_POLL_FREQUENCY / color_native.B;
383 
384 	/* don't allow a value of zero */
385 	if (multiplier.R == 0)
386 		multiplier.R = 1;
387 	if (multiplier.G == 0)
388 		multiplier.G = 1;
389 	if (multiplier.B == 0)
390 		multiplier.B = 1;
391 	g_debug ("using multiplier factor: red=%i, green=%i, blue=%i",
392 		 multiplier.R, multiplier.G, multiplier.B);
393 	ret = huey_ctx_sample_for_threshold (ctx,
394 					     &multiplier,
395 					     &color_native,
396 					     error);
397 	if (!ret)
398 		return NULL;
399 	g_debug ("raw values: red=%u, green=%u, blue=%u",
400 		 color_native.R, color_native.G, color_native.B);
401 
402 	/* get DeviceRGB values */
403 	values.R = (gdouble) multiplier.R * 0.5f * HUEY_POLL_FREQUENCY / ((gdouble) color_native.R);
404 	values.G = (gdouble) multiplier.G * 0.5f * HUEY_POLL_FREQUENCY / ((gdouble) color_native.G);
405 	values.B = (gdouble) multiplier.B * 0.5f * HUEY_POLL_FREQUENCY / ((gdouble) color_native.B);
406 	g_debug ("scaled values: red=%0.6lf, green=%0.6lf, blue=%0.6lf",
407 		 values.R, values.G, values.B);
408 
409 	/* remove dark offset */
410 	temp = (CdVec3*) &values;
411 	cd_vec3_subtract (temp,
412 			  &priv->dark_offset,
413 			  temp);
414 
415 	g_debug ("dark offset values: red=%0.6lf, green=%0.6lf, blue=%0.6lf",
416 		 values.R, values.G, values.B);
417 
418 	/* negative values don't make sense (device needs recalibration) */
419 	if (values.R < 0.0f)
420 		values.R = 0.0f;
421 	if (values.G < 0.0f)
422 		values.G = 0.0f;
423 	if (values.B < 0.0f)
424 		values.B = 0.0f;
425 
426 	/* we use different calibration matrices for each output type */
427 	switch (cap) {
428 	case CD_SENSOR_CAP_CRT:
429 	case CD_SENSOR_CAP_PLASMA:
430 		g_debug ("using CRT calibration matrix");
431 		device_calibration = &priv->calibration_crt;
432 		break;
433 	default:
434 		g_debug ("using LCD calibration matrix");
435 		device_calibration = &priv->calibration_lcd;
436 		break;
437 	}
438 
439 	/* convert from device RGB to XYZ */
440 	huey_ctx_convert_device_RGB_to_XYZ (&values,
441 					    &color_result,
442 					    device_calibration,
443 					    HUEY_XYZ_POST_MULTIPLY_FACTOR);
444 	g_debug ("finished values: red=%0.6lf, green=%0.6lf, blue=%0.6lf",
445 		 color_result.X, color_result.Y, color_result.Z);
446 
447 	/* save result */
448 	return cd_color_xyz_dup (&color_result);
449 }
450 
451 /**
452  * huey_ctx_get_property:
453  **/
454 static void
huey_ctx_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)455 huey_ctx_get_property (GObject *object,
456 		       guint prop_id,
457 		       GValue *value,
458 		       GParamSpec *pspec)
459 {
460 	HueyCtx *ctx = HUEY_CTX (object);
461 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
462 
463 	switch (prop_id) {
464 	case PROP_DEVICE:
465 		g_value_set_object (value, priv->device);
466 		break;
467 	default:
468 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
469 		break;
470 	}
471 }
472 
473 
474 /**
475  * huey_ctx_set_property:
476  **/
477 static void
huey_ctx_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)478 huey_ctx_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
479 {
480 	HueyCtx *ctx = HUEY_CTX (object);
481 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
482 
483 	switch (prop_id) {
484 	case PROP_DEVICE:
485 		priv->device = g_value_dup_object (value);
486 		break;
487 	default:
488 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
489 		break;
490 	}
491 }
492 
493 /*
494  * huey_ctx_class_init:
495  */
496 static void
huey_ctx_class_init(HueyCtxClass * klass)497 huey_ctx_class_init (HueyCtxClass *klass)
498 {
499 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
500 
501 	object_class->get_property = huey_ctx_get_property;
502 	object_class->set_property = huey_ctx_set_property;
503 	object_class->finalize = huey_ctx_finalize;
504 
505 	/**
506 	 * HueyCtx:device:
507 	 *
508 	 * Since: 0.1.29
509 	 **/
510 	g_object_class_install_property (object_class,
511 					 PROP_DEVICE,
512 					 g_param_spec_object ("device",
513 							      NULL, NULL,
514 							      G_USB_TYPE_DEVICE,
515 							      G_PARAM_READWRITE));
516 }
517 
518 /*
519  * huey_ctx_init:
520  */
521 static void
huey_ctx_init(HueyCtx * ctx)522 huey_ctx_init (HueyCtx *ctx)
523 {
524 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
525 
526 	cd_mat33_clear (&priv->calibration_lcd);
527 	cd_mat33_clear (&priv->calibration_crt);
528 
529 	/* ensure the remote errors are registered */
530 	huey_ctx_error_quark ();
531 }
532 
533 /**
534  * huey_ctx_finalize:
535  **/
536 static void
huey_ctx_finalize(GObject * object)537 huey_ctx_finalize (GObject *object)
538 {
539 	HueyCtx *ctx = HUEY_CTX (object);
540 	HueyCtxPrivate *priv = GET_PRIVATE (ctx);
541 
542 	g_return_if_fail (HUEY_IS_CTX (object));
543 
544 	g_free (priv->unlock_string);
545 
546 	G_OBJECT_CLASS (huey_ctx_parent_class)->finalize (object);
547 }
548 
549 /**
550  * huey_ctx_new:
551  *
552  * Creates a new #HueyCtx object.
553  *
554  * Return value: a new HueyCtx object.
555  *
556  * Since: 0.1.29
557  **/
558 HueyCtx *
huey_ctx_new(void)559 huey_ctx_new (void)
560 {
561 	HueyCtx *ctx;
562 	ctx = g_object_new (HUEY_TYPE_CTX, NULL);
563 	return HUEY_CTX (ctx);
564 }
565