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