1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2012-2014 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU Lesser General Public License Version 2.1
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library 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 GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
20  */
21 
22 /**
23  * SECTION:cd-it8
24  * @short_description: Read and write IT8 color sample exchange files
25  *
26  * This object represents .ti1 and .ti3 files which can contain raw
27  * or normalized sample data.
28  */
29 
30 #include "config.h"
31 
32 #include <glib.h>
33 #include <lcms2.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #include "cd-it8.h"
38 #include "cd-color.h"
39 #include "cd-context-lcms.h"
40 
41 static void	cd_it8_class_init	(CdIt8Class	*klass);
42 static void	cd_it8_init		(CdIt8		*it8);
43 static void	cd_it8_finalize		(GObject	*object);
44 
45 #define GET_PRIVATE(o) (cd_it8_get_instance_private (o))
46 
47 /**
48  * CdIt8Private:
49  *
50  * Private #CdIt8 data
51  **/
52 typedef struct
53 {
54 	CdIt8Kind		 kind;
55 	cmsContext		 context_lcms;
56 	CdMat3x3		 matrix;
57 	gboolean		 normalized;
58 	gboolean		 spectral;
59 	gboolean		 enable_created;
60 	gchar			*instrument;
61 	gchar			*reference;
62 	gchar			*originator;
63 	gchar			*title;
64 	GPtrArray		*array_spectra;
65 	GPtrArray		*array_rgb;
66 	GPtrArray		*array_xyz;
67 	GPtrArray		*options;
68 } CdIt8Private;
69 
70 enum {
71 	PROP_0,
72 	PROP_KIND,
73 	PROP_INSTRUMENT,
74 	PROP_REFERENCE,
75 	PROP_NORMALIZED,
76 	PROP_ORIGINATOR,
77 	PROP_TITLE,
78 	PROP_SPECTRAL,
79 	PROP_LAST
80 };
81 
G_DEFINE_TYPE_WITH_PRIVATE(CdIt8,cd_it8,G_TYPE_OBJECT)82 G_DEFINE_TYPE_WITH_PRIVATE (CdIt8, cd_it8, G_TYPE_OBJECT)
83 
84 /**
85  * cd_it8_error_quark:
86  *
87  * Return value: An error quark.
88  *
89  * Since: 0.1.0
90  **/
91 GQuark
92 cd_it8_error_quark (void)
93 {
94 	static GQuark quark = 0;
95 	if (!quark) {
96 		quark = g_quark_from_static_string ("cd_it8_error");
97 	}
98 	return quark;
99 }
100 
101 /**
102  * _cmsIT8GetPropertyDbl:
103  *
104  * This gets a property ensuring the decimal point is '.' rather than what is
105  * specified in LC_NUMERIC
106  **/
107 static gdouble
_cmsIT8GetPropertyDbl(cmsHANDLE it8_lcms,const gchar * key)108 _cmsIT8GetPropertyDbl (cmsHANDLE it8_lcms, const gchar *key)
109 {
110 	const gchar *value;
111 	value = cmsIT8GetProperty (it8_lcms, key);
112 	if (value == NULL)
113 		return -1;
114 	return g_ascii_strtod (value, NULL);
115 }
116 
117 /**
118  * _cmsIT8GetPropertyInt:
119  **/
120 static guint
_cmsIT8GetPropertyInt(cmsHANDLE it8_lcms,const gchar * key)121 _cmsIT8GetPropertyInt (cmsHANDLE it8_lcms, const gchar *key)
122 {
123 	const gchar *value;
124 	guint64 tmp;
125 
126 	value = cmsIT8GetProperty (it8_lcms, key);
127 	if (value == NULL)
128 		return 0;
129 	tmp = g_ascii_strtoull (value, NULL, 10);
130 	if (tmp > G_MAXUINT)
131 		return 0;
132 
133 	return tmp;
134 }
135 
136 /**
137  * _cmsIT8GetDataRowColDbl:
138  *
139  * This gets a data value ensuring the decimal point is '.' rather than what is
140  * specified in LC_NUMERIC
141  **/
142 static gdouble
_cmsIT8GetDataRowColDbl(cmsHANDLE it8_lcms,gint row,gint col)143 _cmsIT8GetDataRowColDbl (cmsHANDLE it8_lcms, gint row, gint col)
144 {
145 	const char *value;
146 	value = cmsIT8GetDataRowCol (it8_lcms, row, col);
147 	if (value == NULL)
148 		return -1;
149 	return g_ascii_strtod (value, NULL);
150 }
151 
152 /**
153  * _cmsIT8WriteFloat:
154  *
155  * We can't use g_ascii_dtostr() as this produces very inefficient
156  * strings for storage. Instead write the string with 13 decimal places
157  * and then manually remove trailing zeros more than one as ArgyllCMS
158  * doesn't support integers as floats (!)
159  **/
160 static void
_cmsIT8WriteFloat(gchar * buffer,gsize buffer_size,gdouble value)161 _cmsIT8WriteFloat (gchar *buffer, gsize buffer_size, gdouble value)
162 {
163 	guint i;
164 	memset (buffer, '\0', buffer_size);
165 	g_ascii_formatd (buffer, buffer_size, "%.12f", value);
166 	for (i = G_ASCII_DTOSTR_BUF_SIZE - 1; i > 2; i--) {
167 		if (buffer[i] == '\0')
168 			continue;
169 		if (buffer[i] != '0')
170 			break;
171 		if (buffer[i-1] == '.')
172 			break;
173 		buffer[i] = '\0';
174 	}
175 }
176 
177 /**
178  * _cmsIT8SetPropertyDbl:
179  *
180  * This sets a property ensuring the decimal point is '.' rather than what is
181  * specified in LC_NUMERIC
182  **/
183 static void
_cmsIT8SetPropertyDbl(cmsHANDLE it8_lcms,const gchar * key,gdouble value)184 _cmsIT8SetPropertyDbl (cmsHANDLE it8_lcms, const gchar *key, gdouble value)
185 {
186 	gchar buffer[G_ASCII_DTOSTR_BUF_SIZE];
187 	_cmsIT8WriteFloat (buffer, G_ASCII_DTOSTR_BUF_SIZE, value);
188 	cmsIT8SetPropertyUncooked (it8_lcms, key, buffer);
189 }
190 
191 /**
192  * _cmsIT8SetPropertyInt:
193  **/
194 static void
_cmsIT8SetPropertyInt(cmsHANDLE it8_lcms,const gchar * key,gint value)195 _cmsIT8SetPropertyInt (cmsHANDLE it8_lcms, const gchar *key, gint value)
196 {
197 	gchar buffer[G_ASCII_DTOSTR_BUF_SIZE];
198 	g_ascii_formatd (buffer, G_ASCII_DTOSTR_BUF_SIZE, "%.0f", value);
199 	cmsIT8SetPropertyUncooked (it8_lcms, key, buffer);
200 }
201 
202 /**
203  * _cmsIT8SetDataRowColDbl:
204  *
205  * This sets a data value ensuring the decimal point is '.' rather than what is
206  * specified in LC_NUMERIC
207  **/
208 static void
_cmsIT8SetDataRowColDbl(cmsHANDLE it8_lcms,gint row,gint col,gdouble value)209 _cmsIT8SetDataRowColDbl (cmsHANDLE it8_lcms, gint row, gint col, gdouble value)
210 {
211 	gchar buffer[G_ASCII_DTOSTR_BUF_SIZE];
212 	_cmsIT8WriteFloat (buffer, G_ASCII_DTOSTR_BUF_SIZE, value);
213 	cmsIT8SetDataRowCol (it8_lcms, row, col, buffer);
214 }
215 
216 /**
217  * cd_it8_set_matrix:
218  * @it8: a #CdIt8 instance.
219  * @matrix: a #CdMat3x3.
220  *
221  * Set the calibration matrix in the it8 file.
222  *
223  * Since: 0.1.20
224  **/
225 void
cd_it8_set_matrix(CdIt8 * it8,const CdMat3x3 * matrix)226 cd_it8_set_matrix (CdIt8 *it8, const CdMat3x3 *matrix)
227 {
228 	CdIt8Private *priv = GET_PRIVATE (it8);
229 	g_return_if_fail (CD_IS_IT8 (it8));
230 	cd_mat33_copy (matrix, &priv->matrix);
231 }
232 
233 /**
234  * cd_it8_get_matrix:
235  * @it8: a #CdIt8 instance.
236  *
237  * Gets the calibration matrix in the it8 file.
238  *
239  * Return value: a #CdMat3x3.
240  *
241  * Since: 0.1.20
242  **/
243 const CdMat3x3 *
cd_it8_get_matrix(CdIt8 * it8)244 cd_it8_get_matrix (CdIt8 *it8)
245 {
246 	CdIt8Private *priv = GET_PRIVATE (it8);
247 	g_return_val_if_fail (CD_IS_IT8 (it8), NULL);
248 	return &priv->matrix;
249 }
250 
251 /**
252  * cd_it8_set_kind:
253  * @it8: a #CdIt8 instance.
254  * @kind: a #CdIt8Kind, e.g %CD_IT8_KIND_TI3.
255  *
256  * Set the kind of IT8 file.
257  *
258  * Since: 0.1.20
259  **/
260 void
cd_it8_set_kind(CdIt8 * it8,CdIt8Kind kind)261 cd_it8_set_kind (CdIt8 *it8, CdIt8Kind kind)
262 {
263 	CdIt8Private *priv = GET_PRIVATE (it8);
264 	g_return_if_fail (CD_IS_IT8 (it8));
265 	priv->kind = kind;
266 }
267 
268 /**
269  * cd_it8_get_kind:
270  * @it8: a #CdIt8 instance.
271  *
272  * Gets the kind of IT8 file.
273  *
274  * Return value: a #CdIt8Kind, e.g %CD_IT8_KIND_TI3.
275  *
276  * Since: 0.1.20
277  **/
278 CdIt8Kind
cd_it8_get_kind(CdIt8 * it8)279 cd_it8_get_kind (CdIt8 *it8)
280 {
281 	CdIt8Private *priv = GET_PRIVATE (it8);
282 	g_return_val_if_fail (CD_IS_IT8 (it8), 0);
283 	return priv->kind;
284 }
285 
286 /**
287  * cd_it8_parse_luminance:
288  **/
289 static gboolean
cd_it8_parse_luminance(const gchar * text,CdColorXYZ * xyz,GError ** error)290 cd_it8_parse_luminance (const gchar *text, CdColorXYZ *xyz, GError **error)
291 {
292 	g_auto(GStrv) split = NULL;
293 
294 	split = g_strsplit (text, " ", -1);
295 	if (g_strv_length (split) != 3) {
296 		g_set_error (error,
297 			     CD_IT8_ERROR,
298 			     CD_IT8_ERROR_INVALID_FORMAT,
299 			     "LUMINANCE_XYZ_CDM2 format invalid: %s",
300 			     text);
301 		return FALSE;
302 	}
303 
304 	xyz->X = g_ascii_strtod (split[0], NULL);
305 	xyz->Y = g_ascii_strtod (split[1], NULL);
306 	xyz->Z = g_ascii_strtod (split[2], NULL);
307 	return TRUE;
308 }
309 
310 /**
311  * cd_it8_get_originator:
312  * @it8: a #CdIt8 instance.
313  *
314  * Gets the file orginator.
315  *
316  * Return value: The originator, or %NULL if unset
317  *
318  * Since: 0.1.20
319  **/
320 const gchar *
cd_it8_get_originator(CdIt8 * it8)321 cd_it8_get_originator (CdIt8 *it8)
322 {
323 	CdIt8Private *priv = GET_PRIVATE (it8);
324 	g_return_val_if_fail (CD_IS_IT8 (it8), NULL);
325 	return priv->originator;
326 }
327 
328 /**
329  * cd_it8_get_title:
330  * @it8: a #CdIt8 instance.
331  *
332  * Gets the file title.
333  *
334  * Return value: The title, or %NULL if unset
335  *
336  * Since: 0.1.20
337  **/
338 const gchar *
cd_it8_get_title(CdIt8 * it8)339 cd_it8_get_title (CdIt8 *it8)
340 {
341 	CdIt8Private *priv = GET_PRIVATE (it8);
342 	g_return_val_if_fail (CD_IS_IT8 (it8), NULL);
343 	return priv->title;
344 }
345 
346 /**
347  * cd_it8_get_instrument:
348  * @it8: a #CdIt8 instance.
349  *
350  * Gets the instrument the file was created by.
351  *
352  * Return value: The instrument, or %NULL if unset
353  *
354  * Since: 0.1.20
355  **/
356 const gchar *
cd_it8_get_instrument(CdIt8 * it8)357 cd_it8_get_instrument (CdIt8 *it8)
358 {
359 	CdIt8Private *priv = GET_PRIVATE (it8);
360 	g_return_val_if_fail (CD_IS_IT8 (it8), NULL);
361 	return priv->instrument;
362 }
363 
364 /**
365  * cd_it8_get_reference:
366  * @it8: a #CdIt8 instance.
367  *
368  * Gets the reference the file was created against.
369  *
370  * Return value: The reference, or %NULL if unset
371  *
372  * Since: 0.1.20
373  **/
374 const gchar *
cd_it8_get_reference(CdIt8 * it8)375 cd_it8_get_reference (CdIt8 *it8)
376 {
377 	CdIt8Private *priv = GET_PRIVATE (it8);
378 	g_return_val_if_fail (CD_IS_IT8 (it8), NULL);
379 	return priv->reference;
380 }
381 
382 /**
383  * cd_it8_get_enable_created:
384  * @it8: a #CdIt8 instance.
385  *
386  * Gets if the 'CREATED' attribute will be written. This is typically only
387  * set in the self test programs.
388  *
389  * Return value: The reference, or %NULL if unset
390  *
391  * Since: 0.1.33
392  **/
393 gboolean
cd_it8_get_enable_created(CdIt8 * it8)394 cd_it8_get_enable_created (CdIt8 *it8)
395 {
396 	CdIt8Private *priv = GET_PRIVATE (it8);
397 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
398 	return priv->enable_created;
399 }
400 
401 /**
402  * cd_it8_get_normalized:
403  * @it8: a #CdIt8 instance.
404  *
405  * Gets if the data should be written normlaised to y=100.
406  *
407  * Return value: %TRUE if the data should be normalised.
408  *
409  * Since: 0.1.20
410  **/
411 gboolean
cd_it8_get_normalized(CdIt8 * it8)412 cd_it8_get_normalized (CdIt8 *it8)
413 {
414 	CdIt8Private *priv = GET_PRIVATE (it8);
415 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
416 	return priv->normalized;
417 }
418 
419 /**
420  * cd_it8_get_spectral:
421  * @it8: a #CdIt8 instance.
422  *
423  * Gets if the data is spectral or XYZ.
424  *
425  * Return value: %TRUE if the data is in spectral bands.
426  *
427  * Since: 0.1.20
428  **/
429 gboolean
cd_it8_get_spectral(CdIt8 * it8)430 cd_it8_get_spectral (CdIt8 *it8)
431 {
432 	CdIt8Private *priv = GET_PRIVATE (it8);
433 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
434 	return priv->spectral;
435 }
436 
437 /**
438  * cd_it8_load_ti1_cal:
439  **/
440 static gboolean
cd_it8_load_ti1_cal(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)441 cd_it8_load_ti1_cal (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
442 {
443 	CdIt8Private *priv = GET_PRIVATE (it8);
444 	CdColorRGB *rgb;
445 	CdColorXYZ *xyz;
446 	const gchar *tmp;
447 	guint i;
448 	guint number_of_sets = 0;
449 
450 	tmp = cmsIT8GetProperty (it8_lcms, "COLOR_REP");
451 	if (g_strcmp0 (tmp, "RGB") != 0) {
452 		g_set_error (error,
453 			     CD_IT8_ERROR,
454 			     CD_IT8_ERROR_INVALID_FORMAT,
455 			     "Invalid data format: %s", tmp);
456 		return FALSE;
457 	}
458 
459 	/* copy out data entries */
460 	number_of_sets = _cmsIT8GetPropertyInt (it8_lcms, "NUMBER_OF_SETS");
461 	if (number_of_sets == 0) {
462 		g_set_error_literal (error,
463 				     CD_IT8_ERROR,
464 				     CD_IT8_ERROR_INVALID_FORMAT,
465 				     "Invalid format, NUMBER_OF_SETS required");
466 		return FALSE;
467 	}
468 
469 	for (i = 0; i < number_of_sets; i++) {
470 		rgb = cd_color_rgb_new ();
471 		rgb->R = _cmsIT8GetDataRowColDbl (it8_lcms, i, 1);
472 		rgb->G = _cmsIT8GetDataRowColDbl (it8_lcms, i, 2);
473 		rgb->B = _cmsIT8GetDataRowColDbl (it8_lcms, i, 3);
474 
475 		/* ti1 files don't have NORMALIZED_TO_Y_100 so guess on
476 		 * the asumption the first patch isn't black */
477 		if (rgb->R > 1.0 || rgb->G > 1.0 || rgb->B > 1.0)
478 			priv->normalized = TRUE;
479 		if (priv->normalized) {
480 			rgb->R /= 100.0f;
481 			rgb->G /= 100.0f;
482 			rgb->B /= 100.0f;
483 		}
484 		g_ptr_array_add (priv->array_rgb, rgb);
485 		xyz = cd_color_xyz_new ();
486 		cd_color_xyz_set (xyz, 0.0, 0.0, 0.0);
487 		g_ptr_array_add (priv->array_xyz, xyz);
488 	}
489 	return TRUE;
490 }
491 
492 /**
493  * cd_it8_load_ti3:
494  **/
495 static gboolean
cd_it8_load_ti3(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)496 cd_it8_load_ti3 (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
497 {
498 	CdIt8Private *priv = GET_PRIVATE (it8);
499 	CdColorRGB *rgb;
500 	CdColorXYZ luminance;
501 	CdColorXYZ *xyz;
502 	const gchar *tmp;
503 	gboolean scaled_to_y100 = FALSE;
504 	guint i;
505 	guint number_of_sets = 0;
506 
507 	tmp = cmsIT8GetProperty (it8_lcms, "COLOR_REP");
508 	if (g_strcmp0 (tmp, "RGB_XYZ") != 0) {
509 		g_set_error (error,
510 			     CD_IT8_ERROR,
511 			     CD_IT8_ERROR_INVALID_FORMAT,
512 			     "Invalid data format: %s", tmp);
513 		return FALSE;
514 	}
515 
516 	/* if normalized, then scale back up */
517 	tmp = cmsIT8GetProperty (it8_lcms, "NORMALIZED_TO_Y_100");
518 	if (g_strcmp0 (tmp, "YES") == 0) {
519 		scaled_to_y100 = TRUE;
520 		tmp = cmsIT8GetProperty (it8_lcms, "LUMINANCE_XYZ_CDM2");
521 		if (!cd_it8_parse_luminance (tmp, &luminance, error))
522 			return FALSE;
523 	} else {
524 		cd_color_xyz_set (&luminance, 1.f, 1.f, 1.f);
525 	}
526 
527 	/* set spectral flag */
528 	tmp = cmsIT8GetProperty (it8_lcms, "INSTRUMENT_TYPE_SPECTRAL");
529 	cd_it8_set_spectral (it8, g_strcmp0 (tmp, "YES") == 0);
530 
531 	/* set instrument */
532 	cd_it8_set_instrument (it8, cmsIT8GetProperty (it8_lcms, "TARGET_INSTRUMENT"));
533 
534 	/* copy out data entries */
535 	number_of_sets = _cmsIT8GetPropertyInt (it8_lcms, "NUMBER_OF_SETS");
536 	if (number_of_sets == 0) {
537 		g_set_error_literal (error,
538 				     CD_IT8_ERROR,
539 				     CD_IT8_ERROR_INVALID_FORMAT,
540 				     "Invalid format, NUMBER_OF_SETS required");
541 		return FALSE;
542 	}
543 	for (i = 0; i < number_of_sets; i++) {
544 		rgb = cd_color_rgb_new ();
545 		rgb->R = _cmsIT8GetDataRowColDbl (it8_lcms, i, 1);
546 		rgb->G = _cmsIT8GetDataRowColDbl (it8_lcms, i, 2);
547 		rgb->B = _cmsIT8GetDataRowColDbl (it8_lcms, i, 3);
548 		if (scaled_to_y100) {
549 			rgb->R /= 100.0f;
550 			rgb->G /= 100.0f;
551 			rgb->B /= 100.0f;
552 		}
553 		g_ptr_array_add (priv->array_rgb, rgb);
554 		xyz = cd_color_xyz_new ();
555 		xyz->X = _cmsIT8GetDataRowColDbl (it8_lcms, i, 4);
556 		xyz->Y = _cmsIT8GetDataRowColDbl (it8_lcms, i, 5);
557 		xyz->Z = _cmsIT8GetDataRowColDbl (it8_lcms, i, 6);
558 		if (scaled_to_y100) {
559 			xyz->X /= 100.0f;
560 			xyz->Y /= 100.0f;
561 			xyz->Z /= 100.0f;
562 			xyz->X *= luminance.X;
563 			xyz->Y *= luminance.Y;
564 			xyz->Z *= luminance.Z;
565 		}
566 		g_ptr_array_add (priv->array_xyz, xyz);
567 	}
568 	return TRUE;
569 }
570 
571 /**
572  * cd_it8_load_ccmx:
573  **/
574 static gboolean
cd_it8_load_ccmx(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)575 cd_it8_load_ccmx (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
576 {
577 	CdIt8Private *priv = GET_PRIVATE (it8);
578 	const gchar *tmp;
579 
580 	/* check color format */
581 	tmp = cmsIT8GetProperty (it8_lcms, "COLOR_REP");
582 	if (g_strcmp0 (tmp, "XYZ") != 0) {
583 		g_set_error (error,
584 			     CD_IT8_ERROR,
585 			     CD_IT8_ERROR_INVALID_FORMAT,
586 			     "Invalid CCMX data format: %s", tmp);
587 		return FALSE;
588 	}
589 
590 	/* set instrument */
591 	cd_it8_set_instrument (it8, cmsIT8GetProperty (it8_lcms, "INSTRUMENT"));
592 
593 	/* just load the matrix */
594 	priv->matrix.m00 = _cmsIT8GetDataRowColDbl (it8_lcms, 0, 0);
595 	priv->matrix.m01 = _cmsIT8GetDataRowColDbl (it8_lcms, 0, 1);
596 	priv->matrix.m02 = _cmsIT8GetDataRowColDbl (it8_lcms, 0, 2);
597 	priv->matrix.m10 = _cmsIT8GetDataRowColDbl (it8_lcms, 1, 0);
598 	priv->matrix.m11 = _cmsIT8GetDataRowColDbl (it8_lcms, 1, 1);
599 	priv->matrix.m12 = _cmsIT8GetDataRowColDbl (it8_lcms, 1, 2);
600 	priv->matrix.m20 = _cmsIT8GetDataRowColDbl (it8_lcms, 2, 0);
601 	priv->matrix.m21 = _cmsIT8GetDataRowColDbl (it8_lcms, 2, 1);
602 	priv->matrix.m22 = _cmsIT8GetDataRowColDbl (it8_lcms, 2, 2);
603 	return TRUE;
604 }
605 
606 /**
607  * cd_it8_load_ccss_spect:
608  **/
609 static gboolean
cd_it8_load_ccss_spect(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)610 cd_it8_load_ccss_spect (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
611 {
612 	const gchar *tmp;
613 	gboolean has_index;
614 	gdouble spectral_end;
615 	gdouble spectral_norm;
616 	gdouble spectral_start;
617 	guint i;
618 	guint j;
619 	guint number_of_fields;
620 	guint number_of_sets;
621 	guint spectral_bands;
622 
623 	/* get spectra endpoints */
624 	tmp = cmsIT8GetProperty (it8_lcms, "SPECTRAL_START_NM");
625 	if (tmp == NULL) {
626 		g_set_error_literal (error,
627 				     CD_IT8_ERROR,
628 				     CD_IT8_ERROR_INVALID_FORMAT,
629 				     "Invalid format, SPECTRAL_START_NM required");
630 		return FALSE;
631 	}
632 	spectral_start = _cmsIT8GetPropertyDbl (it8_lcms, "SPECTRAL_START_NM");
633 	spectral_end = _cmsIT8GetPropertyDbl (it8_lcms, "SPECTRAL_END_NM");
634 	if (spectral_end == 0) {
635 		g_set_error_literal (error,
636 				     CD_IT8_ERROR,
637 				     CD_IT8_ERROR_INVALID_FORMAT,
638 				     "Invalid format, SPECTRAL_END_NM required");
639 		return FALSE;
640 	}
641 
642 	/* get number of bands */
643 	spectral_bands = _cmsIT8GetPropertyInt (it8_lcms, "SPECTRAL_BANDS");
644 	if (spectral_bands == 0) {
645 		g_set_error_literal (error,
646 				     CD_IT8_ERROR,
647 				     CD_IT8_ERROR_INVALID_FORMAT,
648 				     "Invalid format: SPECTRAL_BANDS required");
649 		return FALSE;
650 	}
651 	if (spectral_bands < 2 || spectral_bands > 16384) {
652 		g_set_error (error,
653 			     CD_IT8_ERROR,
654 			     CD_IT8_ERROR_INVALID_FORMAT,
655 			     "Invalid CCSS spectral size: %u", spectral_bands);
656 		return FALSE;
657 	}
658 
659 	/* get spectral norm */
660 	spectral_norm = _cmsIT8GetPropertyDbl (it8_lcms, "SPECTRAL_NORM");
661 	if (spectral_norm < 0.f)
662 		spectral_norm = 1.f;
663 
664 	/* ArgyllCMS seems to support an index in the CCSS file, and not in the
665 	 * SPECT or CMF but like any good library support each mode */
666 	number_of_fields = _cmsIT8GetPropertyInt (it8_lcms, "NUMBER_OF_FIELDS");
667 	if (number_of_fields == 0) {
668 		g_set_error_literal (error,
669 				     CD_IT8_ERROR,
670 				     CD_IT8_ERROR_INVALID_FORMAT,
671 				     "Invalid format, NUMBER_OF_FIELDS required");
672 		return FALSE;
673 	}
674 	if (spectral_bands == number_of_fields) {
675 		has_index = FALSE;
676 	} else if (spectral_bands + 1 == number_of_fields) {
677 		has_index = TRUE;
678 	} else {
679 		g_set_error (error,
680 			     CD_IT8_ERROR,
681 			     CD_IT8_ERROR_INVALID_FORMAT,
682 			     "Invalid CCSS: bands = %u, fields = %u",
683 			     spectral_bands, number_of_fields);
684 		return FALSE;
685 	}
686 
687 	/* read out the arrays of data */
688 	number_of_sets = _cmsIT8GetPropertyInt (it8_lcms, "NUMBER_OF_SETS");
689 	if (number_of_sets == 0) {
690 		g_set_error_literal (error,
691 				     CD_IT8_ERROR,
692 				     CD_IT8_ERROR_INVALID_FORMAT,
693 				     "Invalid format, NUMBER_OF_SETS required");
694 		return FALSE;
695 	}
696 	for (j = 0; j < (guint) number_of_sets; j++) {
697 		g_autoptr(CdSpectrum) spectrum = NULL;
698 		spectrum = cd_spectrum_sized_new (spectral_bands);
699 		if (has_index) {
700 			cd_spectrum_set_id (spectrum,
701 					    cmsIT8GetDataRowCol(it8_lcms, j, 0));
702 		} else {
703 			g_autofree gchar *label = NULL;
704 			label = g_strdup_printf ("%u", j + 1);
705 			cd_spectrum_set_id (spectrum, label);
706 		}
707 		for (i = has_index; i < number_of_fields; i++) {
708 			cd_spectrum_add_value (spectrum,
709 					      _cmsIT8GetDataRowColDbl (it8_lcms, j, i));
710 		}
711 		cd_spectrum_set_start (spectrum, spectral_start);
712 		cd_spectrum_set_end (spectrum, spectral_end);
713 		cd_spectrum_set_norm (spectrum, spectral_norm);
714 		cd_it8_add_spectrum (it8, spectrum);
715 	}
716 	return TRUE;
717 }
718 
719 /**
720  * cd_it8_load_cmf:
721  **/
722 static gboolean
cd_it8_load_cmf(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)723 cd_it8_load_cmf (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
724 {
725 	gdouble spectral_end;
726 	gdouble spectral_norm;
727 	gdouble spectral_start;
728 	guint i;
729 	guint j;
730 	guint number_of_fields;
731 	guint number_of_sets;
732 	guint spectral_bands;
733 
734 	/* get spectra endpoints */
735 	spectral_start = _cmsIT8GetPropertyDbl (it8_lcms, "SPECTRAL_START_NM");
736 	if (spectral_start == 0) {
737 		g_set_error_literal (error,
738 				     CD_IT8_ERROR,
739 				     CD_IT8_ERROR_INVALID_FORMAT,
740 				     "Invalid format, SPECTRAL_START_NM required");
741 		return FALSE;
742 	}
743 	spectral_end = _cmsIT8GetPropertyDbl (it8_lcms, "SPECTRAL_END_NM");
744 	if (spectral_end == 0) {
745 		g_set_error_literal (error,
746 				     CD_IT8_ERROR,
747 				     CD_IT8_ERROR_INVALID_FORMAT,
748 				     "Invalid format, SPECTRAL_END_NM required");
749 		return FALSE;
750 	}
751 
752 	/* get number of bands */
753 	spectral_bands = _cmsIT8GetPropertyInt (it8_lcms, "SPECTRAL_BANDS");
754 	if (spectral_bands == 0) {
755 		g_set_error_literal (error,
756 				     CD_IT8_ERROR,
757 				     CD_IT8_ERROR_INVALID_FORMAT,
758 				     "Invalid format, SPECTRAL_BANDS required");
759 		return FALSE;
760 	}
761 	if (spectral_bands < 2 || spectral_bands > 16384) {
762 		g_set_error (error,
763 			     CD_IT8_ERROR,
764 			     CD_IT8_ERROR_INVALID_FORMAT,
765 			     "Invalid CCSS spectral size: %u", spectral_bands);
766 		return FALSE;
767 	}
768 
769 	/* get spectral norm */
770 	spectral_norm = _cmsIT8GetPropertyDbl (it8_lcms, "SPECTRAL_NORM");
771 	if (spectral_norm < 0.f)
772 		spectral_norm = 1.f;
773 
774 	/* CMF files are un-indexed and implicitly XYZ */
775 	number_of_fields = _cmsIT8GetPropertyInt (it8_lcms, "NUMBER_OF_FIELDS");
776 	if (number_of_fields == 0) {
777 		g_set_error_literal (error,
778 				     CD_IT8_ERROR,
779 				     CD_IT8_ERROR_INVALID_FORMAT,
780 				     "Invalid format, NUMBER_OF_FIELDS required");
781 		return FALSE;
782 	}
783 	if (spectral_bands != number_of_fields) {
784 		g_set_error (error,
785 			     CD_IT8_ERROR,
786 			     CD_IT8_ERROR_INVALID_FORMAT,
787 			     "Invalid CMF: bands != fields (%u, %u)",
788 			     spectral_bands, number_of_fields);
789 		return FALSE;
790 	}
791 
792 	/* read out the arrays of data */
793 	number_of_sets = _cmsIT8GetPropertyInt (it8_lcms, "NUMBER_OF_SETS");
794 	if (number_of_sets != 3) {
795 		g_set_error (error,
796 			     CD_IT8_ERROR,
797 			     CD_IT8_ERROR_INVALID_FORMAT,
798 			     "Invalid CMF: sets != 3 (%u)",
799 			     number_of_sets);
800 		return FALSE;
801 	}
802 	for (j = 0; j < (guint) number_of_sets; j++) {
803 		g_autoptr(CdSpectrum) spectrum = NULL;
804 		spectrum = cd_spectrum_sized_new (spectral_bands);
805 		if (j == 0)
806 			cd_spectrum_set_id (spectrum, "X");
807 		else if (j == 1)
808 			cd_spectrum_set_id (spectrum, "Y");
809 		else
810 			cd_spectrum_set_id (spectrum, "Z");
811 		for (i = 0; i < number_of_fields; i++) {
812 			cd_spectrum_add_value (spectrum,
813 					      _cmsIT8GetDataRowColDbl (it8_lcms, j, i));
814 		}
815 		cd_spectrum_set_start (spectrum, spectral_start);
816 		cd_spectrum_set_end (spectrum, spectral_end);
817 		cd_spectrum_set_norm (spectrum, spectral_norm);
818 		cd_it8_add_spectrum (it8, spectrum);
819 	}
820 	return TRUE;
821 }
822 
823 /**
824  * cd_it8_has_option:
825  * @it8: a #CdIt8 instance.
826  * @option: a option, e.g. "TYPE_CRT"
827  *
828  * Finds an option in the file.
829  *
830  * Return value: %TRUE if the option is set
831  *
832  * Since: 0.1.20
833  **/
834 gboolean
cd_it8_has_option(CdIt8 * it8,const gchar * option)835 cd_it8_has_option (CdIt8 *it8, const gchar *option)
836 {
837 	CdIt8Private *priv = GET_PRIVATE (it8);
838 	const gchar *tmp;
839 	guint i;
840 
841 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
842 	g_return_val_if_fail (option != NULL, FALSE);
843 
844 	for (i = 0; i < priv->options->len; i++) {
845 		tmp = g_ptr_array_index (priv->options, i);
846 		if (g_strcmp0 (tmp, option) == 0)
847 			return TRUE;
848 	}
849 	return FALSE;
850 }
851 
852 /**
853  * cd_it8_load_from_data:
854  * @it8: a #CdIt8 instance.
855  * @data: text data
856  * @size: the size of text data
857  * @error: a #GError, or %NULL
858  *
859  * Loads a it8 file from data.
860  *
861  * Return value: %TRUE if a valid it8 file was read.
862  *
863  * Since: 0.1.20
864  **/
865 gboolean
cd_it8_load_from_data(CdIt8 * it8,const gchar * data,gsize size,GError ** error)866 cd_it8_load_from_data (CdIt8 *it8,
867 		       const gchar *data,
868 		       gsize size,
869 		       GError **error)
870 {
871 	CdIt8Private *priv = GET_PRIVATE (it8);
872 	cmsHANDLE it8_lcms = NULL;
873 	const gchar *tmp;
874 	gboolean ret = TRUE;
875 	gchar **props = NULL;
876 	guint i;
877 	g_autoptr(GError) error_local = NULL;
878 
879 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
880 	g_return_val_if_fail (data != NULL, FALSE);
881 	g_return_val_if_fail (size > 0, FALSE);
882 
883 	/* clear old data */
884 	g_ptr_array_set_size (priv->array_rgb, 0);
885 	g_ptr_array_set_size (priv->array_xyz, 0);
886 	g_ptr_array_set_size (priv->options, 0);
887 	cd_mat33_clear (&priv->matrix);
888 
889 	/* load the it8 data */
890 	it8_lcms = cmsIT8LoadFromMem (priv->context_lcms, (void *) data, size);
891 	if (it8_lcms == NULL) {
892 		ret = cd_context_lcms_error_check (priv->context_lcms, &error_local);
893 		if (!ret) {
894 			g_set_error_literal (error,
895 					     CD_IT8_ERROR,
896 					     CD_IT8_ERROR_FAILED,
897 					     error_local->message);
898 			goto out;
899 		}
900 		ret = FALSE;
901 		g_set_error_literal (error,
902 				     CD_IT8_ERROR,
903 				     CD_IT8_ERROR_FAILED,
904 				     "Failed to load but no error set");
905 		goto out;
906 	}
907 
908 	/* add options */
909 	cmsIT8EnumProperties (it8_lcms, &props);
910 	for (i = 0; props[i] != NULL; i++) {
911 		if (g_str_has_prefix (props[i], "TYPE_"))
912 			cd_it8_add_option (it8, props[i]);
913 	}
914 
915 	/* get sheet type */
916 	tmp = cmsIT8GetSheetType (it8_lcms);
917 	if (g_str_has_prefix (tmp, "CTI1")) {
918 		cd_it8_set_kind (it8, CD_IT8_KIND_TI1);
919 	} else if (g_str_has_prefix (tmp, "CTI3")) {
920 		cd_it8_set_kind (it8, CD_IT8_KIND_TI3);
921 	} else if (g_str_has_prefix (tmp, "CCMX")) {
922 		cd_it8_set_kind (it8, CD_IT8_KIND_CCMX);
923 	} else if (g_str_has_prefix (tmp, "CCSS")) {
924 		cd_it8_set_kind (it8, CD_IT8_KIND_CCSS);
925 	} else if (g_str_has_prefix (tmp, "CMF")) {
926 		cd_it8_set_kind (it8, CD_IT8_KIND_CMF);
927 	} else if (g_str_has_prefix (tmp, "SPECT")) {
928 		cd_it8_set_kind (it8, CD_IT8_KIND_SPECT);
929 	} else if (g_str_has_prefix (tmp, "CAL")) {
930 		cd_it8_set_kind (it8, CD_IT8_KIND_CAL);
931 	} else {
932 		ret = FALSE;
933 		g_set_error (error,
934 			     CD_IT8_ERROR,
935 			     CD_IT8_ERROR_UNKNOWN_KIND,
936 			     "Unknown sheet type: %s", tmp);
937 		goto out;
938 	}
939 
940 	/* get ti1 and ti3 specific data */
941 	switch (priv->kind) {
942 	case CD_IT8_KIND_TI1:
943 	case CD_IT8_KIND_CAL:
944 		ret = cd_it8_load_ti1_cal (it8, it8_lcms, error);
945 		if (!ret)
946 			goto out;
947 		break;
948 	case CD_IT8_KIND_TI3:
949 		ret = cd_it8_load_ti3 (it8, it8_lcms, error);
950 		if (!ret)
951 			goto out;
952 		break;
953 	case CD_IT8_KIND_CCMX:
954 		ret = cd_it8_load_ccmx (it8, it8_lcms, error);
955 		if (!ret)
956 			goto out;
957 		break;
958 	case CD_IT8_KIND_CCSS:
959 	case CD_IT8_KIND_SPECT:
960 		ret = cd_it8_load_ccss_spect (it8, it8_lcms, error);
961 		if (!ret)
962 			goto out;
963 		break;
964 	case CD_IT8_KIND_CMF:
965 		ret = cd_it8_load_cmf (it8, it8_lcms, error);
966 		if (!ret)
967 			goto out;
968 		break;
969 	default:
970 		break;
971 	}
972 
973 	/* set common bits */
974 	cd_it8_set_title (it8, cmsIT8GetProperty (it8_lcms, "DISPLAY"));
975 	cd_it8_set_originator (it8, cmsIT8GetProperty (it8_lcms, "ORIGINATOR"));
976 	cd_it8_set_reference (it8, cmsIT8GetProperty (it8_lcms, "REFERENCE"));
977 out:
978 	if (it8_lcms != NULL)
979 		cmsIT8Free (it8_lcms);
980 	return ret;
981 }
982 
983 /**
984  * cd_it8_load_from_file:
985  * @it8: a #CdIt8 instance.
986  * @file: a #GFile
987  * @error: a #GError, or %NULL
988  *
989  * Loads a it8 file from disk.
990  *
991  * Return value: %TRUE if a valid it8 file was read.
992  *
993  * Since: 0.1.20
994  **/
995 gboolean
cd_it8_load_from_file(CdIt8 * it8,GFile * file,GError ** error)996 cd_it8_load_from_file (CdIt8 *it8, GFile *file, GError **error)
997 {
998 	gsize size = 0;
999 	g_autofree gchar *data = NULL;
1000 
1001 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
1002 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
1003 
1004 	/* load file */
1005 	if (!g_file_load_contents (file, NULL, &data, &size, NULL, error))
1006 		return FALSE;
1007 
1008 	/* load data */
1009 	if (!cd_it8_load_from_data (it8, data, size, error))
1010 		return FALSE;
1011 	return TRUE;
1012 }
1013 
1014 /**
1015  * cd_it8_color_match:
1016  **/
1017 static gboolean
cd_it8_color_match(CdColorRGB * rgb,gdouble r,gdouble g,gdouble b)1018 cd_it8_color_match (CdColorRGB *rgb, gdouble r, gdouble g, gdouble b)
1019 {
1020 	if (ABS (rgb->R - r) > 0.01f)
1021 		return FALSE;
1022 	if (ABS (rgb->G - g) > 0.01f)
1023 		return FALSE;
1024 	if (ABS (rgb->B - b) > 0.01f)
1025 		return FALSE;
1026 	return TRUE;
1027 }
1028 
1029 /**
1030  * cd_it8_convert_xyz_to_string:
1031  **/
1032 static gchar *
cd_it8_convert_xyz_to_string(CdColorXYZ * src)1033 cd_it8_convert_xyz_to_string (CdColorXYZ *src)
1034 {
1035 	gchar buffer[3][G_ASCII_DTOSTR_BUF_SIZE];
1036 	g_ascii_dtostr (buffer[0], G_ASCII_DTOSTR_BUF_SIZE, src->X);
1037 	g_ascii_dtostr (buffer[1], G_ASCII_DTOSTR_BUF_SIZE, src->Y);
1038 	g_ascii_dtostr (buffer[2], G_ASCII_DTOSTR_BUF_SIZE, src->Z);
1039 	return g_strdup_printf ("%s %s %s", buffer[0], buffer[1], buffer[2]);
1040 }
1041 
1042 /**
1043  * cd_it8_save_to_file_ti1_ti3:
1044  **/
1045 static gboolean
cd_it8_save_to_file_ti1_ti3(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)1046 cd_it8_save_to_file_ti1_ti3 (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
1047 {
1048 	CdIt8Private *priv = GET_PRIVATE (it8);
1049 	CdColorRGB *rgb_tmp;
1050 	CdColorXYZ lumi_xyz;
1051 	CdColorXYZ *xyz_tmp;
1052 	gboolean is_white;
1053 	gdouble normalize = 0.0f;
1054 	guint i;
1055 	guint luminance_samples = 0;
1056 	g_autofree gchar *lumi_str = NULL;
1057 
1058 	/* calculate the absolute XYZ in candelas per meter squared */
1059 	cd_color_xyz_clear (&lumi_xyz);
1060 	if (priv->normalized) {
1061 		for (i = 0; i < priv->array_rgb->len; i++) {
1062 			rgb_tmp = g_ptr_array_index (priv->array_rgb, i);
1063 
1064 			/* is this 100% white? */
1065 			is_white = cd_it8_color_match (rgb_tmp, 1.0f, 1.0f, 1.0f);
1066 			if (!is_white)
1067 				continue;
1068 			luminance_samples++;
1069 			xyz_tmp = g_ptr_array_index (priv->array_xyz, i);
1070 			lumi_xyz.X += xyz_tmp->X;
1071 			lumi_xyz.Y += xyz_tmp->Y;
1072 			lumi_xyz.Z += xyz_tmp->Z;
1073 			if (xyz_tmp->Y > normalize)
1074 				normalize = xyz_tmp->Y;
1075 		}
1076 		if (luminance_samples == 0) {
1077 			g_set_error_literal (error,
1078 					     CD_IT8_ERROR,
1079 					     CD_IT8_ERROR_FAILED,
1080 					     "Failed to find any white samples");
1081 			return FALSE;
1082 		}
1083 		lumi_xyz.X /= luminance_samples;
1084 		lumi_xyz.Y /= luminance_samples;
1085 		lumi_xyz.Z /= luminance_samples;
1086 
1087 		/* scale all the readings to 100 */
1088 		normalize = 100.0f / normalize;
1089 	}
1090 	lumi_str = cd_it8_convert_xyz_to_string (&lumi_xyz);
1091 
1092 	/* write data */
1093 	if (priv->kind == CD_IT8_KIND_TI1) {
1094 		cmsIT8SetSheetType (it8_lcms, "CTI1   ");
1095 		cmsIT8SetPropertyStr (it8_lcms, "DESCRIPTOR",
1096 				      "Calibration Target chart information 1");
1097 	} else if (priv->kind == CD_IT8_KIND_TI3) {
1098 		cmsIT8SetSheetType (it8_lcms, "CTI3   ");
1099 		cmsIT8SetPropertyStr (it8_lcms, "DESCRIPTOR",
1100 				      "Calibration Target chart information 3");
1101 	}
1102 	if (priv->kind == CD_IT8_KIND_TI3) {
1103 		cmsIT8SetPropertyStr (it8_lcms, "DEVICE_CLASS",
1104 				      "DISPLAY");
1105 	}
1106 	cmsIT8SetPropertyStr (it8_lcms, "COLOR_REP", "RGB_XYZ");
1107 	if (priv->instrument != NULL) {
1108 		cmsIT8SetPropertyStr (it8_lcms, "TARGET_INSTRUMENT",
1109 				      priv->instrument);
1110 	}
1111 	cmsIT8SetPropertyStr (it8_lcms, "INSTRUMENT_TYPE_SPECTRAL",
1112 			      priv->spectral ? "YES" : "NO");
1113 	if (priv->normalized) {
1114 		cmsIT8SetPropertyStr (it8_lcms, "NORMALIZED_TO_Y_100", "YES");
1115 		cmsIT8SetPropertyStr (it8_lcms, "LUMINANCE_XYZ_CDM2", lumi_str);
1116 	} else {
1117 		cmsIT8SetPropertyStr (it8_lcms, "NORMALIZED_TO_Y_100", "NO");
1118 	}
1119 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_FIELDS", 7);
1120 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_SETS", priv->array_rgb->len);
1121 	cmsIT8SetDataFormat (it8_lcms, 0, "SAMPLE_ID");
1122 	cmsIT8SetDataFormat (it8_lcms, 1, "RGB_R");
1123 	cmsIT8SetDataFormat (it8_lcms, 2, "RGB_G");
1124 	cmsIT8SetDataFormat (it8_lcms, 3, "RGB_B");
1125 	cmsIT8SetDataFormat (it8_lcms, 4, "XYZ_X");
1126 	cmsIT8SetDataFormat (it8_lcms, 5, "XYZ_Y");
1127 	cmsIT8SetDataFormat (it8_lcms, 6, "XYZ_Z");
1128 
1129 	/* write to the it8 file */
1130 	for (i = 0; i < priv->array_rgb->len; i++) {
1131 		rgb_tmp = g_ptr_array_index (priv->array_rgb, i);
1132 		xyz_tmp = g_ptr_array_index (priv->array_xyz, i);
1133 
1134 		_cmsIT8SetDataRowColDbl(it8_lcms, i, 0, i + 1);
1135 		if (priv->normalized) {
1136 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 1, rgb_tmp->R * 100.0f);
1137 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 2, rgb_tmp->G * 100.0f);
1138 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 3, rgb_tmp->B * 100.0f);
1139 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 4, xyz_tmp->X * normalize);
1140 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 5, xyz_tmp->Y * normalize);
1141 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 6, xyz_tmp->Z * normalize);
1142 		} else {
1143 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 1, rgb_tmp->R);
1144 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 2, rgb_tmp->G);
1145 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 3, rgb_tmp->B);
1146 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 4, xyz_tmp->X);
1147 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 5, xyz_tmp->Y);
1148 			_cmsIT8SetDataRowColDbl(it8_lcms, i, 6, xyz_tmp->Z);
1149 		}
1150 	}
1151 	return TRUE;
1152 }
1153 
1154 /**
1155  * cd_it8_save_to_file_cal:
1156  **/
1157 static gboolean
cd_it8_save_to_file_cal(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)1158 cd_it8_save_to_file_cal (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
1159 {
1160 	CdIt8Private *priv = GET_PRIVATE (it8);
1161 	CdColorRGB *rgb_tmp;
1162 	gboolean ret = TRUE;
1163 	guint i;
1164 
1165 	/* write data */
1166 	cmsIT8SetSheetType (it8_lcms, "CAL    ");
1167 	cmsIT8SetPropertyStr (it8_lcms, "DESCRIPTOR",
1168 			      "Device Calibration Curves");
1169 	cmsIT8SetPropertyStr (it8_lcms, "DEVICE_CLASS", "DISPLAY");
1170 	cmsIT8SetPropertyStr (it8_lcms, "COLOR_REP", "RGB");
1171 	if (priv->instrument != NULL) {
1172 		cmsIT8SetPropertyStr (it8_lcms, "TARGET_INSTRUMENT",
1173 				      priv->instrument);
1174 	}
1175 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_FIELDS", 4);
1176 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_SETS", priv->array_rgb->len);
1177 	cmsIT8SetDataFormat (it8_lcms, 0, "RGB_I");
1178 	cmsIT8SetDataFormat (it8_lcms, 1, "RGB_R");
1179 	cmsIT8SetDataFormat (it8_lcms, 2, "RGB_G");
1180 	cmsIT8SetDataFormat (it8_lcms, 3, "RGB_B");
1181 
1182 	/* write to the it8 file */
1183 	for (i = 0; i < priv->array_rgb->len; i++) {
1184 		rgb_tmp = g_ptr_array_index (priv->array_rgb, i);
1185 		_cmsIT8SetDataRowColDbl(it8_lcms, i, 0, 1.0f / (gdouble) (priv->array_rgb->len - 1) * (gdouble) i);
1186 		_cmsIT8SetDataRowColDbl(it8_lcms, i, 1, rgb_tmp->R);
1187 		_cmsIT8SetDataRowColDbl(it8_lcms, i, 2, rgb_tmp->G);
1188 		_cmsIT8SetDataRowColDbl(it8_lcms, i, 3, rgb_tmp->B);
1189 	}
1190 
1191 	return ret;
1192 }
1193 
1194 /**
1195  * cd_it8_save_to_file_ccmx:
1196  **/
1197 static gboolean
cd_it8_save_to_file_ccmx(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)1198 cd_it8_save_to_file_ccmx (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
1199 {
1200 	CdIt8Private *priv = GET_PRIVATE (it8);
1201 	gboolean ret = TRUE;
1202 
1203 	cmsIT8SetSheetType (it8_lcms, "CCMX   ");
1204 	cmsIT8SetPropertyStr (it8_lcms, "DESCRIPTOR",
1205 			      "Device Correction Matrix");
1206 
1207 	cmsIT8SetPropertyStr (it8_lcms, "COLOR_REP", "XYZ");
1208 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_FIELDS", 3);
1209 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_SETS", 3);
1210 	cmsIT8SetDataFormat (it8_lcms, 0, "XYZ_X");
1211 	cmsIT8SetDataFormat (it8_lcms, 1, "XYZ_Y");
1212 	cmsIT8SetDataFormat (it8_lcms, 2, "XYZ_Z");
1213 
1214 	/* save instrument */
1215 	if (priv->instrument != NULL) {
1216 		cmsIT8SetPropertyStr (it8_lcms, "INSTRUMENT",
1217 				      priv->instrument);
1218 	}
1219 
1220 	/* just save the matrix */
1221 	_cmsIT8SetDataRowColDbl (it8_lcms, 0, 0, priv->matrix.m00);
1222 	_cmsIT8SetDataRowColDbl (it8_lcms, 0, 1, priv->matrix.m01);
1223 	_cmsIT8SetDataRowColDbl (it8_lcms, 0, 2, priv->matrix.m02);
1224 	_cmsIT8SetDataRowColDbl (it8_lcms, 1, 0, priv->matrix.m10);
1225 	_cmsIT8SetDataRowColDbl (it8_lcms, 1, 1, priv->matrix.m11);
1226 	_cmsIT8SetDataRowColDbl (it8_lcms, 1, 2, priv->matrix.m12);
1227 	_cmsIT8SetDataRowColDbl (it8_lcms, 2, 0, priv->matrix.m20);
1228 	_cmsIT8SetDataRowColDbl (it8_lcms, 2, 1, priv->matrix.m21);
1229 	_cmsIT8SetDataRowColDbl (it8_lcms, 2, 2, priv->matrix.m22);
1230 
1231 	return ret;
1232 }
1233 
1234 /**
1235  * cd_it8_save_to_file_cmf:
1236  **/
1237 static gboolean
cd_it8_save_to_file_cmf(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)1238 cd_it8_save_to_file_cmf (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
1239 {
1240 	CdIt8Private *priv = GET_PRIVATE (it8);
1241 	CdSpectrum *spectrum;
1242 	guint i;
1243 	guint j;
1244 	guint number_of_sets;
1245 	guint spectral_bands;
1246 
1247 	cmsIT8SetSheetType (it8_lcms, "CMF    ");
1248 	cmsIT8SetPropertyStr (it8_lcms, "DESCRIPTOR",
1249 			      "Color Match Function");
1250 
1251 	/* check data is valid */
1252 	number_of_sets = priv->array_spectra->len;
1253 	if (number_of_sets != 3) {
1254 		g_set_error_literal (error,
1255 				     CD_IT8_ERROR,
1256 				     CD_IT8_ERROR_INVALID_FORMAT,
1257 				     "Cannot write CMF: XYZ data required");
1258 		return FALSE;
1259 	}
1260 
1261 	/* all the arrays have to have the same length */
1262 	spectrum = g_ptr_array_index (priv->array_spectra, 0);
1263 	spectral_bands = cd_spectrum_get_size (spectrum);
1264 	_cmsIT8SetPropertyDbl (it8_lcms, "SPECTRAL_START_NM", cd_spectrum_get_start (spectrum));
1265 	_cmsIT8SetPropertyDbl (it8_lcms, "SPECTRAL_END_NM", cd_spectrum_get_end (spectrum));
1266 	_cmsIT8SetPropertyInt (it8_lcms, "SPECTRAL_BANDS", spectral_bands);
1267 	_cmsIT8SetPropertyDbl (it8_lcms, "SPECTRAL_NORM", cd_spectrum_get_norm (spectrum));
1268 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_FIELDS", spectral_bands);
1269 
1270 	/* set DATA_FORMAT (using an ID if there are more than one spectra */
1271 	spectrum = g_ptr_array_index (priv->array_spectra, 0);
1272 	for (i = 0; i < spectral_bands; i++) {
1273 		g_autofree gchar *label = NULL;
1274 		label = g_strdup_printf ("SPEC_%.0f",
1275 					 cd_spectrum_get_wavelength (spectrum, i));
1276 		cmsIT8SetDataFormat (it8_lcms, i, label);
1277 	}
1278 
1279 	/* set DATA */
1280 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_SETS", number_of_sets);
1281 	for (j = 0; j < number_of_sets; j++) {
1282 		spectrum = g_ptr_array_index (priv->array_spectra, j);
1283 		for (i = 0; i < spectral_bands; i++) {
1284 			if (!priv->normalized) {
1285 				_cmsIT8SetDataRowColDbl (it8_lcms, j, i,
1286 							 cd_spectrum_get_value (spectrum, i));
1287 			} else {
1288 				_cmsIT8SetDataRowColDbl (it8_lcms, j, i,
1289 							 cd_spectrum_get_value_raw (spectrum, i));
1290 			}
1291 		}
1292 	}
1293 	return TRUE;
1294 }
1295 
1296 /**
1297  * cd_it8_save_to_file_ccss_sp:
1298  **/
1299 static gboolean
cd_it8_save_to_file_ccss_sp(CdIt8 * it8,cmsHANDLE it8_lcms,GError ** error)1300 cd_it8_save_to_file_ccss_sp (CdIt8 *it8, cmsHANDLE it8_lcms, GError **error)
1301 {
1302 	CdIt8Private *priv = GET_PRIVATE (it8);
1303 	CdSpectrum *spectrum;
1304 	gboolean has_index = FALSE;
1305 	guint i;
1306 	guint j;
1307 	guint number_of_sets;
1308 	guint spectral_bands;
1309 
1310 	switch (priv->kind) {
1311 	case CD_IT8_KIND_CCSS:
1312 		cmsIT8SetSheetType (it8_lcms, "CCSS   ");
1313 		cmsIT8SetPropertyStr (it8_lcms, "DESCRIPTOR",
1314 				      "Colorimeter Calibration Spectral Set");
1315 		break;
1316 	case CD_IT8_KIND_CMF:
1317 		cmsIT8SetSheetType (it8_lcms, "CMF    ");
1318 		cmsIT8SetPropertyStr (it8_lcms, "DESCRIPTOR",
1319 				      "Color Match Function");
1320 		break;
1321 	case CD_IT8_KIND_SPECT:
1322 		cmsIT8SetSheetType (it8_lcms, "SPECT  ");
1323 		cmsIT8SetPropertyStr (it8_lcms, "DESCRIPTOR",
1324 				      "Spectral Power");
1325 		break;
1326 	default:
1327 		break;
1328 	}
1329 
1330 	/* check data is valid */
1331 	number_of_sets = priv->array_spectra->len;
1332 	if (number_of_sets == 0) {
1333 		g_set_error_literal (error,
1334 				     CD_IT8_ERROR,
1335 				     CD_IT8_ERROR_INVALID_FORMAT,
1336 				     "Cannot write CCSS: spectral data required");
1337 		return FALSE;
1338 	}
1339 	if (number_of_sets > 1)
1340 		has_index = TRUE;
1341 
1342 	/* all the arrays have to have the same length */
1343 	spectrum = g_ptr_array_index (priv->array_spectra, 0);
1344 	spectral_bands = cd_spectrum_get_size (spectrum);
1345 	_cmsIT8SetPropertyDbl (it8_lcms, "SPECTRAL_START_NM", cd_spectrum_get_start (spectrum));
1346 	_cmsIT8SetPropertyDbl (it8_lcms, "SPECTRAL_END_NM", cd_spectrum_get_end (spectrum));
1347 	_cmsIT8SetPropertyInt (it8_lcms, "SPECTRAL_BANDS", spectral_bands);
1348 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_FIELDS", spectral_bands + has_index);
1349 	if (priv->normalized)
1350 		_cmsIT8SetPropertyDbl (it8_lcms, "SPECTRAL_NORM", cd_spectrum_get_norm (spectrum));
1351 
1352 	/* set DATA_FORMAT (using an ID if there are more than one spectra */
1353 	if (has_index)
1354 		cmsIT8SetDataFormat (it8_lcms, 0, "SAMPLE_ID");
1355 	spectrum = g_ptr_array_index (priv->array_spectra, 0);
1356 	for (i = 0; i < spectral_bands; i++) {
1357 		g_autofree gchar *label = NULL;
1358 		/* there are more spectral bands than integers between the
1359 		 * start and stop wavelengths */
1360 		if ((cd_spectrum_get_end (spectrum) -
1361 		     cd_spectrum_get_start (spectrum)) < spectral_bands) {
1362 			label = g_strdup_printf ("SPEC_%.0f",
1363 						 cd_spectrum_get_wavelength (spectrum, i) * 1000.f);
1364 		} else {
1365 			label = g_strdup_printf ("SPEC_%.0f",
1366 						 cd_spectrum_get_wavelength (spectrum, i));
1367 		}
1368 		cmsIT8SetDataFormat (it8_lcms, i + has_index, label);
1369 	}
1370 
1371 	/* set DATA */
1372 	_cmsIT8SetPropertyInt (it8_lcms, "NUMBER_OF_SETS", number_of_sets);
1373 	for (j = 0; j < number_of_sets; j++) {
1374 		spectrum = g_ptr_array_index (priv->array_spectra, j);
1375 		if (has_index) {
1376 			cmsIT8SetDataRowCol (it8_lcms, j, 0,
1377 					     cd_spectrum_get_id (spectrum));
1378 		}
1379 		for (i = 0; i < spectral_bands; i++) {
1380 			if (!priv->normalized) {
1381 				_cmsIT8SetDataRowColDbl (it8_lcms, j, i + has_index,
1382 							 cd_spectrum_get_value (spectrum, i));
1383 			} else {
1384 				_cmsIT8SetDataRowColDbl (it8_lcms, j, i + has_index,
1385 							 cd_spectrum_get_value_raw (spectrum, i));
1386 			}
1387 		}
1388 	}
1389 	return TRUE;
1390 }
1391 
1392 /**
1393  * cd_it8_save_to_data:
1394  * @it8: a #CdIt8 instance.
1395  * @data: a pointer to returned data
1396  * @size: size of @data
1397  * @error: a #GError, or %NULL
1398  *
1399  * Saves a it8 file to an area of memory.
1400  *
1401  * Return value: %TRUE if it8 file was saved.
1402  *
1403  * Since: 0.1.26
1404  **/
1405 gboolean
cd_it8_save_to_data(CdIt8 * it8,gchar ** data,gsize * size,GError ** error)1406 cd_it8_save_to_data (CdIt8 *it8,
1407 		     gchar **data,
1408 		     gsize *size,
1409 		     GError **error)
1410 {
1411 	CdIt8Private *priv = GET_PRIVATE (it8);
1412 	cmsHANDLE it8_lcms = NULL;
1413 	const gchar *tmp;
1414 	gboolean ret;
1415 	GDateTime *datetime = NULL;
1416 	cmsUInt32Number size_tmp = 0;
1417 	guint i;
1418 	g_autofree gchar *data_tmp = NULL;
1419 	g_autofree gchar *date_str = NULL;
1420 
1421 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
1422 
1423 	/* set common data */
1424 	it8_lcms = cmsIT8Alloc (priv->context_lcms);
1425 	if (priv->title != NULL) {
1426 		cmsIT8SetPropertyStr (it8_lcms, "DISPLAY",
1427 				      priv->title);
1428 	}
1429 	if (priv->originator != NULL) {
1430 		cmsIT8SetPropertyStr (it8_lcms, "ORIGINATOR",
1431 				      priv->originator);
1432 	}
1433 	if (priv->reference != NULL) {
1434 		cmsIT8SetPropertyStr (it8_lcms, "REFERENCE",
1435 				      priv->reference);
1436 	}
1437 
1438 	/* set time and date in crazy ArgllCMS format, e.g.
1439 	 * 'Wed Dec 19 18:47:57 2012' */
1440 	if (priv->enable_created) {
1441 		datetime = g_date_time_new_now_local ();
1442 		date_str = g_date_time_format (datetime, "%a %b %d %H:%M:%S %Y");
1443 		cmsIT8SetPropertyStr (it8_lcms, "CREATED", date_str);
1444 	}
1445 
1446 	/* set ti1 and ti3 specific data */
1447 	switch (priv->kind) {
1448 	case CD_IT8_KIND_TI1:
1449 	case CD_IT8_KIND_TI3:
1450 		ret = cd_it8_save_to_file_ti1_ti3 (it8, it8_lcms, error);
1451 		if (!ret)
1452 			goto out;
1453 		break;
1454 	case CD_IT8_KIND_CAL:
1455 		ret = cd_it8_save_to_file_cal (it8, it8_lcms, error);
1456 		if (!ret)
1457 			goto out;
1458 		break;
1459 	case CD_IT8_KIND_CCMX:
1460 		ret = cd_it8_save_to_file_ccmx (it8, it8_lcms, error);
1461 		if (!ret)
1462 			goto out;
1463 		break;
1464 	case CD_IT8_KIND_CMF:
1465 		ret = cd_it8_save_to_file_cmf (it8, it8_lcms, error);
1466 		if (!ret)
1467 			goto out;
1468 		break;
1469 	case CD_IT8_KIND_CCSS:
1470 	case CD_IT8_KIND_SPECT:
1471 		ret = cd_it8_save_to_file_ccss_sp (it8, it8_lcms, error);
1472 		if (!ret)
1473 			goto out;
1474 		break;
1475 	default:
1476 		break;
1477 	}
1478 
1479 	/* save any options */
1480 	for (i = 0; i < priv->options->len; i++) {
1481 		tmp = g_ptr_array_index (priv->options, i);
1482 		cmsIT8SetPropertyStr (it8_lcms, tmp, "YES");
1483 	}
1484 
1485 	/* write the file */
1486 	ret = cmsIT8SaveToMem (it8_lcms, NULL, &size_tmp);
1487 	g_assert (ret);
1488 	data_tmp = g_malloc (size_tmp);
1489 	ret = cmsIT8SaveToMem (it8_lcms, data_tmp, &size_tmp);
1490 	g_assert (ret);
1491 
1492 	/* save for caller */
1493 	if (data != NULL)
1494 		*data = g_strdup (data_tmp);
1495 
1496 	/* LCMS alocates an extra byte for the '\0' byte */
1497 	if (size != NULL)
1498 		*size = size_tmp - 1;
1499 out:
1500 	if (it8_lcms != NULL)
1501 		cmsIT8Free (it8_lcms);
1502 	if (datetime != NULL)
1503 		g_date_time_unref (datetime);
1504 	return ret;
1505 }
1506 
1507 /**
1508  * cd_it8_save_to_file:
1509  * @it8: a #CdIt8 instance.
1510  * @file: a #GFile
1511  * @error: a #GError, or %NULL
1512  *
1513  * Saves a it8 file to disk
1514  *
1515  * Return value: %TRUE if it8 file was saved.
1516  *
1517  * Since: 0.1.20
1518  **/
1519 gboolean
cd_it8_save_to_file(CdIt8 * it8,GFile * file,GError ** error)1520 cd_it8_save_to_file (CdIt8 *it8, GFile *file, GError **error)
1521 {
1522 	gsize size = 0;
1523 	g_autofree gchar *data = NULL;
1524 
1525 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
1526 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
1527 
1528 	/* get data */
1529 	if (!cd_it8_save_to_data (it8, &data, &size, error))
1530 		return FALSE;
1531 
1532 	/* save file */
1533 	return g_file_replace_contents (file, data, size, NULL,
1534 					FALSE, G_FILE_CREATE_NONE,
1535 					NULL, NULL, error);
1536 }
1537 
1538 /**
1539  * cd_it8_add_option:
1540  * @it8: a #CdIt8 instance.
1541  * @option: A IT8 option, e.g. "TYPE_LCD"
1542  *
1543  * Sets any extra options that have to be set in the CCMX file
1544  *
1545  * Since: 0.1.20
1546  **/
1547 void
cd_it8_add_option(CdIt8 * it8,const gchar * option)1548 cd_it8_add_option (CdIt8 *it8, const gchar *option)
1549 {
1550 	CdIt8Private *priv = GET_PRIVATE (it8);
1551 	g_return_if_fail (CD_IS_IT8 (it8));
1552 	g_ptr_array_add (priv->options, g_strdup (option));
1553 }
1554 
1555 /**
1556  * cd_it8_set_normalized:
1557  * @it8: a #CdIt8 instance.
1558  * @normalized: If the data is normalized
1559  *
1560  * Sets if normalized data should be written to the .it8 file.
1561  *
1562  * Since: 0.1.20
1563  **/
1564 void
cd_it8_set_normalized(CdIt8 * it8,gboolean normalized)1565 cd_it8_set_normalized (CdIt8 *it8, gboolean normalized)
1566 {
1567 	CdIt8Private *priv = GET_PRIVATE (it8);
1568 	g_return_if_fail (CD_IS_IT8 (it8));
1569 	priv->normalized = normalized;
1570 }
1571 
1572 /**
1573  * cd_it8_set_spectral:
1574  * @it8: a #CdIt8 instance.
1575  * @spectral: If the data is spectral
1576  *
1577  * Sets if spectral data should be written to the .it8 file.
1578  *
1579  * Since: 0.1.20
1580  **/
1581 void
cd_it8_set_spectral(CdIt8 * it8,gboolean spectral)1582 cd_it8_set_spectral (CdIt8 *it8, gboolean spectral)
1583 {
1584 	CdIt8Private *priv = GET_PRIVATE (it8);
1585 	g_return_if_fail (CD_IS_IT8 (it8));
1586 	priv->spectral = spectral;
1587 }
1588 
1589 /**
1590  * cd_it8_set_originator:
1591  * @it8: a #CdIt8 instance.
1592  * @originator: the program name, e.g. "gcm-calibrate"
1593  *
1594  * Sets the program name that created the .it8 file
1595  *
1596  * Since: 0.1.20
1597  **/
1598 void
cd_it8_set_originator(CdIt8 * it8,const gchar * originator)1599 cd_it8_set_originator (CdIt8 *it8, const gchar *originator)
1600 {
1601 	CdIt8Private *priv = GET_PRIVATE (it8);
1602 	g_return_if_fail (CD_IS_IT8 (it8));
1603 
1604 	g_free (priv->originator);
1605 	priv->originator = g_strdup (originator);
1606 }
1607 
1608 /**
1609  * cd_it8_set_title:
1610  * @it8: a #CdIt8 instance.
1611  * @title: the title name, e.g. "Factory calibration"
1612  *
1613  * Sets the display name for the file.
1614  *
1615  * Since: 0.1.20
1616  **/
1617 void
cd_it8_set_title(CdIt8 * it8,const gchar * title)1618 cd_it8_set_title (CdIt8 *it8, const gchar *title)
1619 {
1620 	CdIt8Private *priv = GET_PRIVATE (it8);
1621 	g_return_if_fail (CD_IS_IT8 (it8));
1622 
1623 	g_free (priv->title);
1624 	priv->title = g_strdup (title);
1625 }
1626 
1627 /**
1628  * cd_it8_set_instrument:
1629  * @it8: a #CdIt8 instance.
1630  * @instrument: the instruemnt name, e.g. "huey"
1631  *
1632  * Sets the measuring instrument that created the .it8 file
1633  *
1634  * Since: 0.1.20
1635  **/
1636 void
cd_it8_set_instrument(CdIt8 * it8,const gchar * instrument)1637 cd_it8_set_instrument (CdIt8 *it8, const gchar *instrument)
1638 {
1639 	CdIt8Private *priv = GET_PRIVATE (it8);
1640 	g_return_if_fail (CD_IS_IT8 (it8));
1641 
1642 	g_free (priv->instrument);
1643 	priv->instrument = g_strdup (instrument);
1644 }
1645 
1646 /**
1647  * cd_it8_set_reference:
1648  * @it8: a #CdIt8 instance.
1649  * @reference: the instruemnt name, e.g. "colormunki"
1650  *
1651  * Sets the reference that as used to create the .it8 reference
1652  *
1653  * Since: 0.1.20
1654  **/
1655 void
cd_it8_set_reference(CdIt8 * it8,const gchar * reference)1656 cd_it8_set_reference (CdIt8 *it8, const gchar *reference)
1657 {
1658 	CdIt8Private *priv = GET_PRIVATE (it8);
1659 	g_return_if_fail (CD_IS_IT8 (it8));
1660 
1661 	g_free (priv->reference);
1662 	priv->reference = g_strdup (reference);
1663 }
1664 
1665 /**
1666  * cd_it8_set_enable_created:
1667  * @it8: a #CdIt8 instance.
1668  * @enable_created: Is 'CREATED' should be written
1669  *
1670  * Sets if the 'CREATED' attribute should be written. This is mainly useful
1671  * in the self test programs where we want to string compare the output data
1672  * with a known reference.
1673  *
1674  * Since: 0.1.33
1675  **/
1676 void
cd_it8_set_enable_created(CdIt8 * it8,gboolean enable_created)1677 cd_it8_set_enable_created (CdIt8 *it8, gboolean enable_created)
1678 {
1679 	CdIt8Private *priv = GET_PRIVATE (it8);
1680 	g_return_if_fail (CD_IS_IT8 (it8));
1681 	priv->enable_created = enable_created;
1682 }
1683 
1684 /**
1685  * cd_it8_add_data:
1686  * @it8: a #CdIt8 instance.
1687  * @rgb: a #CdColorRGB, or %NULL
1688  * @xyz: a #CdColorXYZ, or %NULL
1689  *
1690  * Adds a reading to this object. If either of @rgb or @xyz is NULL then
1691  * a black reading (0.0, 0.0, 0.0) is added instead.
1692  *
1693  * Since: 0.1.20
1694  **/
1695 void
cd_it8_add_data(CdIt8 * it8,const CdColorRGB * rgb,const CdColorXYZ * xyz)1696 cd_it8_add_data (CdIt8 *it8, const CdColorRGB *rgb, const CdColorXYZ *xyz)
1697 {
1698 	CdIt8Private *priv = GET_PRIVATE (it8);
1699 	CdColorRGB *rgb_tmp;
1700 	CdColorXYZ *xyz_tmp;
1701 
1702 	g_return_if_fail (CD_IS_IT8 (it8));
1703 
1704 	/* add RGB */
1705 	if (rgb != NULL) {
1706 		rgb_tmp = cd_color_rgb_dup (rgb);
1707 	} else {
1708 		rgb_tmp = cd_color_rgb_new ();
1709 		cd_color_rgb_set (rgb_tmp, 0.0f, 0.0f, 0.0f);
1710 	}
1711 	g_ptr_array_add (priv->array_rgb, rgb_tmp);
1712 
1713 	/* add XYZ */
1714 	if (xyz != NULL) {
1715 		xyz_tmp = cd_color_xyz_dup (xyz);
1716 	} else {
1717 		xyz_tmp = cd_color_xyz_new ();
1718 		cd_color_xyz_set (xyz_tmp, 0.0f, 0.0f, 0.0f);
1719 	}
1720 	g_ptr_array_add (priv->array_xyz, xyz_tmp);
1721 }
1722 
1723 /**
1724  * cd_it8_get_data_size:
1725  * @it8: a #CdIt8 instance.
1726  *
1727  * Gets the data size.
1728  *
1729  * Return value: The number of RGB-XYZ readings in this object.
1730  *
1731  * Since: 0.1.20
1732  **/
1733 guint
cd_it8_get_data_size(CdIt8 * it8)1734 cd_it8_get_data_size (CdIt8 *it8)
1735 {
1736 	CdIt8Private *priv = GET_PRIVATE (it8);
1737 	g_return_val_if_fail (CD_IS_IT8 (it8), G_MAXUINT);
1738 	return priv->array_xyz->len;
1739 }
1740 
1741 /**
1742  * cd_it8_get_data_item:
1743  * @it8: a #CdIt8 instance.
1744  * @idx: the item index
1745  * @rgb: the returned RGB value
1746  * @xyz: the returned XYZ value
1747  *
1748  * Gets a specific bit of data from this object.
1749  * The returned data are absolute readings and are not normalised.
1750  *
1751  * Return value: %TRUE if the index existed.
1752  *
1753  * Since: 0.1.20
1754  **/
1755 gboolean
cd_it8_get_data_item(CdIt8 * it8,guint idx,CdColorRGB * rgb,CdColorXYZ * xyz)1756 cd_it8_get_data_item (CdIt8 *it8, guint idx, CdColorRGB *rgb, CdColorXYZ *xyz)
1757 {
1758 	CdIt8Private *priv = GET_PRIVATE (it8);
1759 	const CdColorRGB *rgb_tmp;
1760 	const CdColorXYZ *xyz_tmp;
1761 
1762 	g_return_val_if_fail (CD_IS_IT8 (it8), FALSE);
1763 
1764 	if (idx > priv->array_xyz->len)
1765 		return FALSE;
1766 	if (rgb != NULL) {
1767 		rgb_tmp = g_ptr_array_index (priv->array_rgb, idx);
1768 		cd_color_rgb_copy (rgb_tmp, rgb);
1769 	}
1770 	if (xyz != NULL) {
1771 		xyz_tmp = g_ptr_array_index (priv->array_xyz, idx);
1772 		cd_color_xyz_copy (xyz_tmp, xyz);
1773 	}
1774 	return TRUE;
1775 }
1776 
1777 /**
1778  * cd_it8_get_xyz_for_rgb:
1779  * @it8: a #CdIt8 instance.
1780  * @R: the red value
1781  * @G: the green value
1782  * @B: the blue value
1783  * @delta: the smallest difference between colors, e.g. 0.01f
1784  *
1785  * Gets the XYZ value for a specific RGB value.
1786  *
1787  * Return value: (transfer none): A CdColorXYZ, or %NULL if the sample does not exist.
1788  *
1789  * Since: 1.2.6
1790  **/
1791 CdColorXYZ *
cd_it8_get_xyz_for_rgb(CdIt8 * it8,gdouble R,gdouble G,gdouble B,gdouble delta)1792 cd_it8_get_xyz_for_rgb (CdIt8 *it8, gdouble R, gdouble G, gdouble B, gdouble delta)
1793 {
1794 	CdIt8Private *priv = GET_PRIVATE (it8);
1795 	CdColorXYZ *xyz_tmp;
1796 	guint i;
1797 	const CdColorRGB *rgb_tmp;
1798 
1799 	g_return_val_if_fail (CD_IS_IT8 (it8), NULL);
1800 
1801 	for (i = 0; i < priv->array_xyz->len; i++) {
1802 		rgb_tmp = g_ptr_array_index (priv->array_rgb, i);
1803 		if (ABS (rgb_tmp->R - R) > delta)
1804 			continue;
1805 		if (ABS (rgb_tmp->G - G) > delta)
1806 			continue;
1807 		if (ABS (rgb_tmp->B - B) > delta)
1808 			continue;
1809 		xyz_tmp = g_ptr_array_index (priv->array_xyz, i);
1810 		return xyz_tmp;
1811 	}
1812 	return NULL;
1813 }
1814 
1815 /**
1816  * cd_it8_set_spectrum_array:
1817  * @it8: a #CdIt8 instance.
1818  * @data: (transfer container) (element-type CdSpectrum): the spectral data
1819  *
1820  * Set the spectral data
1821  *
1822  * Since: 1.1.6
1823  **/
1824 void
cd_it8_set_spectrum_array(CdIt8 * it8,GPtrArray * data)1825 cd_it8_set_spectrum_array (CdIt8 *it8, GPtrArray *data)
1826 {
1827 	CdIt8Private *priv = GET_PRIVATE (it8);
1828 	g_return_if_fail (CD_IS_IT8 (it8));
1829 	g_ptr_array_unref (priv->array_spectra);
1830 	priv->array_spectra = g_ptr_array_ref (data);
1831 }
1832 
1833 /**
1834  * cd_it8_add_spectrum:
1835  * @it8: a #CdIt8 instance.
1836  * @spectrum: the spectral data
1837  *
1838  * Adds a spectrum to the spectral array.
1839  *
1840  * Since: 1.1.6
1841  **/
1842 void
cd_it8_add_spectrum(CdIt8 * it8,CdSpectrum * spectrum)1843 cd_it8_add_spectrum (CdIt8 *it8, CdSpectrum *spectrum)
1844 {
1845 	CdIt8Private *priv = GET_PRIVATE (it8);
1846 	const gchar *id;
1847 	CdSpectrum *tmp;
1848 
1849 	g_return_if_fail (CD_IS_IT8 (it8));
1850 
1851 	/* remove any existing spectra with this same ID */
1852 	id = cd_spectrum_get_id (spectrum);
1853 	if (id != NULL) {
1854 		tmp = cd_it8_get_spectrum_by_id (it8, id);
1855 		if (tmp != NULL)
1856 			g_ptr_array_remove (priv->array_spectra, tmp);
1857 	}
1858 
1859 	/* add this */
1860 	g_ptr_array_add (priv->array_spectra, cd_spectrum_dup (spectrum));
1861 }
1862 
1863 /**
1864  * cd_it8_get_spectrum_array:
1865  * @it8: a #CdIt8 instance.
1866  *
1867  * Gets the spectral data of IT8 file.
1868  *
1869  * Return value: (transfer container) (element-type CdSpectrum): spectral data
1870  *
1871  * Since: 1.1.6
1872  **/
1873 GPtrArray *
cd_it8_get_spectrum_array(CdIt8 * it8)1874 cd_it8_get_spectrum_array (CdIt8 *it8)
1875 {
1876 	CdIt8Private *priv = GET_PRIVATE (it8);
1877 	g_return_val_if_fail (CD_IS_IT8 (it8), NULL);
1878 	return g_ptr_array_ref (priv->array_spectra);
1879 }
1880 
1881 /**
1882  * cd_it8_get_spectrum_by_id:
1883  * @it8: a #CdIt8 instance.
1884  * @id: the spectrum ID value
1885  *
1886  * Gets a specific spectrum in an IT8 file.
1887  *
1888  * Return value: (transfer none): spectrum, or %NULL
1889  *
1890  * Since: 1.1.6
1891  **/
1892 CdSpectrum *
cd_it8_get_spectrum_by_id(CdIt8 * it8,const gchar * id)1893 cd_it8_get_spectrum_by_id (CdIt8 *it8, const gchar *id)
1894 {
1895 	CdIt8Private *priv = GET_PRIVATE (it8);
1896 	CdSpectrum *tmp;
1897 	guint i;
1898 
1899 	g_return_val_if_fail (CD_IS_IT8 (it8), NULL);
1900 	g_return_val_if_fail (id != NULL, NULL);
1901 
1902 	for (i = 0; i < priv->array_spectra->len; i++) {
1903 		tmp = g_ptr_array_index (priv->array_spectra, i);
1904 		if (g_strcmp0 (cd_spectrum_get_id (tmp), id) == 0)
1905 			return tmp;
1906 	}
1907 	return NULL;
1908 }
1909 
1910 /**********************************************************************/
1911 
1912 /*
1913  * cd_it8_get_property:
1914  */
1915 static void
cd_it8_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1916 cd_it8_get_property (GObject *object,
1917 		     guint prop_id,
1918 		     GValue *value,
1919 		     GParamSpec *pspec)
1920 {
1921 	CdIt8 *it8 = CD_IT8 (object);
1922 	CdIt8Private *priv = GET_PRIVATE (it8);
1923 
1924 	switch (prop_id) {
1925 	case PROP_KIND:
1926 		g_value_set_uint (value, priv->kind);
1927 		break;
1928 	case PROP_NORMALIZED:
1929 		g_value_set_boolean (value, priv->normalized);
1930 		break;
1931 	case PROP_ORIGINATOR:
1932 		g_value_set_string (value, priv->originator);
1933 		break;
1934 	case PROP_TITLE:
1935 		g_value_set_string (value, priv->title);
1936 		break;
1937 	case PROP_INSTRUMENT:
1938 		g_value_set_string (value, priv->instrument);
1939 		break;
1940 	case PROP_REFERENCE:
1941 		g_value_set_string (value, priv->reference);
1942 		break;
1943 	case PROP_SPECTRAL:
1944 		g_value_set_boolean (value, priv->spectral);
1945 		break;
1946 	default:
1947 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1948 		break;
1949 	}
1950 }
1951 
1952 /**
1953  * cd_it8_set_property:
1954  **/
1955 static void
cd_it8_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1956 cd_it8_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
1957 {
1958 	CdIt8 *it8 = CD_IT8 (object);
1959 	CdIt8Private *priv = GET_PRIVATE (it8);
1960 
1961 	switch (prop_id) {
1962 	case PROP_KIND:
1963 		priv->kind = g_value_get_uint (value);
1964 		break;
1965 	default:
1966 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1967 		break;
1968 	}
1969 }
1970 
1971 /*
1972  * cd_it8_class_init:
1973  */
1974 static void
cd_it8_class_init(CdIt8Class * klass)1975 cd_it8_class_init (CdIt8Class *klass)
1976 {
1977 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
1978 
1979 	object_class->get_property = cd_it8_get_property;
1980 	object_class->set_property = cd_it8_set_property;
1981 	object_class->finalize = cd_it8_finalize;
1982 
1983 	/**
1984 	 * CdIt8:kind:
1985 	 *
1986 	 * The kind of IT8 file.
1987 	 *
1988 	 * Since: 0.1.20
1989 	 **/
1990 	g_object_class_install_property (object_class,
1991 					 PROP_KIND,
1992 					 g_param_spec_uint ("kind",
1993 							    NULL, NULL,
1994 							    0, G_MAXUINT, 0,
1995 							    G_PARAM_READWRITE));
1996 
1997 	/**
1998 	 * CdIt8:normalized:
1999 	 *
2000 	 * If the results file is normalized.
2001 	 *
2002 	 * Since: 0.1.20
2003 	 **/
2004 	g_object_class_install_property (object_class,
2005 					 PROP_NORMALIZED,
2006 					 g_param_spec_boolean ("normalized",
2007 							       NULL, NULL,
2008 							       FALSE,
2009 							       G_PARAM_READABLE));
2010 
2011 	/**
2012 	 * CdIt8:originator:
2013 	 *
2014 	 * The framework that created the results, e.g. "cd-self-test"
2015 	 *
2016 	 * Since: 0.1.20
2017 	 **/
2018 	g_object_class_install_property (object_class,
2019 					 PROP_ORIGINATOR,
2020 					 g_param_spec_string ("originator",
2021 							      NULL, NULL,
2022 							      NULL,
2023 							      G_PARAM_READABLE));
2024 
2025 	/**
2026 	 * CdIt8:title:
2027 	 *
2028 	 * The file title, e.g. "Factor calibration".
2029 	 *
2030 	 * Since: 0.1.20
2031 	 **/
2032 	g_object_class_install_property (object_class,
2033 					 PROP_TITLE,
2034 					 g_param_spec_string ("title",
2035 							      NULL, NULL,
2036 							      NULL,
2037 							      G_PARAM_READABLE));
2038 
2039 	/**
2040 	 * CdIt8:instrument:
2041 	 *
2042 	 * The instrument that created the results, e.g. "huey"
2043 	 *
2044 	 * Since: 0.1.20
2045 	 **/
2046 	g_object_class_install_property (object_class,
2047 					 PROP_INSTRUMENT,
2048 					 g_param_spec_string ("instrument",
2049 							      NULL, NULL,
2050 							      NULL,
2051 							      G_PARAM_READABLE));
2052 
2053 	/**
2054 	 * CdIt8:reference:
2055 	 *
2056 	 * The reference that created the results, e.g. "colormunki"
2057 	 *
2058 	 * Since: 0.1.20
2059 	 **/
2060 	g_object_class_install_property (object_class,
2061 					 PROP_REFERENCE,
2062 					 g_param_spec_string ("reference",
2063 							      NULL, NULL,
2064 							      NULL,
2065 							      G_PARAM_READABLE));
2066 
2067 	/**
2068 	 * CdIt8:spectral:
2069 	 *
2070 	 * If the results file is spectral.
2071 	 *
2072 	 * Since: 0.1.20
2073 	 **/
2074 	g_object_class_install_property (object_class,
2075 					 PROP_SPECTRAL,
2076 					 g_param_spec_boolean ("spectral",
2077 							       NULL, NULL,
2078 							       FALSE,
2079 							       G_PARAM_READABLE));
2080 }
2081 
2082 /*
2083  * cd_it8_init:
2084  */
2085 static void
cd_it8_init(CdIt8 * it8)2086 cd_it8_init (CdIt8 *it8)
2087 {
2088 	CdIt8Private *priv = GET_PRIVATE (it8);
2089 	priv->context_lcms = cd_context_lcms_new ();
2090 
2091 	cd_mat33_clear (&priv->matrix);
2092 	priv->array_rgb = g_ptr_array_new_with_free_func ((GDestroyNotify) cd_color_rgb_free);
2093 	priv->array_xyz = g_ptr_array_new_with_free_func ((GDestroyNotify) cd_color_xyz_free);
2094 	priv->array_spectra = g_ptr_array_new_with_free_func ((GDestroyNotify) cd_spectrum_free);
2095 	priv->options = g_ptr_array_new_with_free_func (g_free);
2096 	priv->enable_created = TRUE;
2097 
2098 	/* ensure the remote errors are registered */
2099 	cd_it8_error_quark ();
2100 }
2101 
2102 /*
2103  * cd_it8_finalize:
2104  */
2105 static void
cd_it8_finalize(GObject * object)2106 cd_it8_finalize (GObject *object)
2107 {
2108 	CdIt8 *it8 = CD_IT8 (object);
2109 	CdIt8Private *priv = GET_PRIVATE (it8);
2110 
2111 	g_return_if_fail (CD_IS_IT8 (object));
2112 
2113 	cd_context_lcms_free (priv->context_lcms);
2114 	g_ptr_array_unref (priv->array_spectra);
2115 	g_ptr_array_unref (priv->array_rgb);
2116 	g_ptr_array_unref (priv->array_xyz);
2117 	g_ptr_array_unref (priv->options);
2118 	g_free (priv->originator);
2119 	g_free (priv->title);
2120 	g_free (priv->instrument);
2121 	g_free (priv->reference);
2122 
2123 	G_OBJECT_CLASS (cd_it8_parent_class)->finalize (object);
2124 }
2125 
2126 /**
2127  * cd_it8_new:
2128  *
2129  * Creates a new #CdIt8 object.
2130  *
2131  * Return value: a new CdIt8 object.
2132  *
2133  * Since: 0.1.20
2134  **/
2135 CdIt8 *
cd_it8_new(void)2136 cd_it8_new (void)
2137 {
2138 	CdIt8 *it8;
2139 	it8 = g_object_new (CD_TYPE_IT8, NULL);
2140 	return CD_IT8 (it8);
2141 }
2142 
2143 /**
2144  * cd_it8_new_with_kind:
2145  * @kind: a #CdIt8Kind, e.g %CD_IT8_KIND_TI3.
2146  *
2147  * Creates a new #CdIt8 object.
2148  *
2149  * Return value: a new CdIt8 object.
2150  *
2151  * Since: 0.1.20
2152  **/
2153 CdIt8 *
cd_it8_new_with_kind(CdIt8Kind kind)2154 cd_it8_new_with_kind (CdIt8Kind kind)
2155 {
2156 	CdIt8 *it8;
2157 	it8 = g_object_new (CD_TYPE_IT8,
2158 			    "kind", kind,
2159 			    NULL);
2160 	return CD_IT8 (it8);
2161 }
2162 
2163