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 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-icc
24  * @short_description: An object to read and write a binary ICC profile
25  */
26 
27 #include "config.h"
28 
29 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include <lcms2.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <math.h>
35 
36 #include "cd-context-lcms.h"
37 #include "cd-icc.h"
38 
39 static void	cd_icc_class_init	(CdIccClass	*klass);
40 static void	cd_icc_init		(CdIcc		*icc);
41 static gboolean	cd_icc_load_named_colors (CdIcc		*icc, GError **error);
42 static void	cd_icc_finalize		(GObject	*object);
43 
44 #define GET_PRIVATE(o) (cd_icc_get_instance_private (o))
45 
46 typedef enum {
47 	CD_MLUC_DESCRIPTION,
48 	CD_MLUC_COPYRIGHT,
49 	CD_MLUC_MANUFACTURER,
50 	CD_MLUC_MODEL,
51 	CD_MLUC_LAST
52 } CdIccMluc;
53 
54 /**
55  * CdIccPrivate:
56  *
57  * Private #CdIcc data
58  **/
59 typedef struct
60 {
61 	CdColorspace		 colorspace;
62 	CdProfileKind		 kind;
63 	cmsContext		 context_lcms;
64 	cmsHPROFILE		 lcms_profile;
65 	gboolean		 can_delete;
66 	gchar			*checksum;
67 	gchar			*filename;
68 	gchar			*characterization_data;
69 	gdouble			 version;
70 	GHashTable		*mluc_data[CD_MLUC_LAST]; /* key is 'en_GB' or '' for default */
71 	GHashTable		*metadata;
72 	guint32			 size;
73 	GPtrArray		*named_colors;
74 	guint			 temperature;
75 	CdColorXYZ		 white;
76 	CdColorXYZ		 red;
77 	CdColorXYZ		 green;
78 	CdColorXYZ		 blue;
79 } CdIccPrivate;
80 
81 G_DEFINE_TYPE_WITH_PRIVATE (CdIcc, cd_icc, G_TYPE_OBJECT)
82 
83 enum {
84 	PROP_0,
85 	PROP_SIZE,
86 	PROP_FILENAME,
87 	PROP_VERSION,
88 	PROP_KIND,
89 	PROP_COLORSPACE,
90 	PROP_CAN_DELETE,
91 	PROP_CHECKSUM,
92 	PROP_RED,
93 	PROP_GREEN,
94 	PROP_BLUE,
95 	PROP_WHITE,
96 	PROP_TEMPERATURE,
97 	PROP_LAST
98 };
99 
100 /**
101  * cd_icc_error_quark:
102  *
103  * Return value: An error quark.
104  *
105  * Since: 0.1.32
106  **/
107 GQuark
cd_icc_error_quark(void)108 cd_icc_error_quark (void)
109 {
110 	static GQuark quark = 0;
111 	if (!quark)
112 		quark = g_quark_from_static_string ("cd_icc_error");
113 	return quark;
114 }
115 
116 /**
117  * cd_icc_fix_utf8_string:
118  *
119  * NC entries are supposed to be 7-bit ASCII, although some profile vendors
120  * try to be clever which breaks handling them as UTF-8.
121  **/
122 static gboolean
cd_icc_fix_utf8_string(GString * string)123 cd_icc_fix_utf8_string (GString *string)
124 {
125 	guint i;
126 	guchar tmp;
127 
128 	/* replace clever characters */
129 	for (i = 0; i < string->len; i++) {
130 		tmp = (guchar) string->str[i];
131 
132 		/* (R) */
133 		if (tmp == 0xae) {
134 			string->str[i] = 0xc2;
135 			g_string_insert_c (string, i + 1, tmp);
136 			i += 1;
137 		}
138 
139 		/* unknown */
140 		if (tmp == 0x86)
141 			g_string_erase (string, i, 1);
142 	}
143 
144 	/* check if we repaired it okay */
145 	return g_utf8_validate (string->str, string->len, NULL);
146 }
147 
148 /**
149  * cd_icc_uint32_to_str:
150  **/
151 static void
cd_icc_uint32_to_str(guint32 id,gchar * str)152 cd_icc_uint32_to_str (guint32 id, gchar *str)
153 {
154 	/* this is a hack */
155 	memcpy (str, &id, 4);
156 	str[4] = '\0';
157 }
158 
159 /**
160  * cd_icc_read_tag:
161  **/
162 static gpointer
cd_icc_read_tag(CdIcc * icc,cmsTagSignature sig,GError ** error)163 cd_icc_read_tag (CdIcc *icc, cmsTagSignature sig, GError **error)
164 {
165 	CdIccPrivate *priv = GET_PRIVATE (icc);
166 	gchar sig_string[5];
167 	gpointer tmp;
168 
169 	/* ensure context error is not present to aid debugging */
170 	cd_context_lcms_error_clear (priv->context_lcms);
171 
172 	/* read raw value */
173 	tmp = cmsReadTag (priv->lcms_profile, sig);
174 	if (tmp != NULL)
175 		return tmp;
176 
177 	/* any context error? */
178 	if (!cd_context_lcms_error_check (priv->context_lcms, error))
179 		return NULL;
180 
181 	/* missing value */
182 	cd_icc_uint32_to_str (GINT32_FROM_BE (sig), sig_string);
183 	g_set_error (error,
184 		     CD_ICC_ERROR,
185 		     CD_ICC_ERROR_NO_DATA,
186 		     "No data for tag %s [0x%04x]", sig_string, sig);
187 	return NULL;
188 }
189 
190 /**
191  * cd_icc_write_tag:
192  **/
193 static gboolean
cd_icc_write_tag(CdIcc * icc,cmsTagSignature sig,gpointer data,GError ** error)194 cd_icc_write_tag (CdIcc *icc, cmsTagSignature sig, gpointer data, GError **error)
195 {
196 	CdIccPrivate *priv = GET_PRIVATE (icc);
197 	gchar sig_string[5];
198 
199 	/* ensure context error is not present to aid debugging */
200 	cd_context_lcms_error_clear (priv->context_lcms);
201 
202 	/* write raw value */
203 	if (cmsWriteTag (priv->lcms_profile, sig, data))
204 		return TRUE;
205 
206 	/* due to a bug in lcms2, writing with data==NULL returns FALSE
207 	 * with no conext error set */
208 	if (data == NULL)
209 		return TRUE;
210 
211 	/* any context error? */
212 	if (!cd_context_lcms_error_check (priv->context_lcms, error))
213 		return FALSE;
214 
215 	/* missing value */
216 	cd_icc_uint32_to_str (GINT32_FROM_BE (sig), sig_string);
217 	g_set_error (error,
218 		     CD_ICC_ERROR,
219 		     CD_ICC_ERROR_NO_DATA,
220 		     "Failed to write tag %s [0x%04x]", sig_string, sig);
221 	return FALSE;
222 }
223 
224 /**
225  * cd_icc_to_string:
226  * @icc: a #CdIcc instance.
227  *
228  * Returns a string representation of the ICC profile.
229  *
230  * Return value: an allocated string
231  *
232  * Since: 0.1.32
233  **/
234 gchar *
cd_icc_to_string(CdIcc * icc)235 cd_icc_to_string (CdIcc *icc)
236 {
237 	CdIccPrivate *priv = GET_PRIVATE (icc);
238 	cmsInt32Number tag_size;
239 	cmsTagSignature sig;
240 	cmsTagSignature sig_link;
241 	cmsTagTypeSignature tag_type;
242 	gboolean ret;
243 	gchar tag_str[5] = "    ";
244 	GDateTime *created;
245 	GError *error_local = NULL;
246 	GString *str;
247 	guint32 i;
248 	guint32 number_tags;
249 	guint32 tmp;
250 	guint64 header_flags;
251 	guint8 profile_id[4];
252 
253 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
254 
255 	/* setup error handler */
256 
257 	/* print header */
258 	str = g_string_new ("icc:\nHeader:\n");
259 
260 	/* print size */
261 	tmp = cd_icc_get_size (icc);
262 	if (tmp > 0)
263 		g_string_append_printf (str, "  Size\t\t= %" G_GUINT32_FORMAT " bytes\n", tmp);
264 
265 	/* version */
266 	g_string_append_printf (str, "  Version\t= %.1f\n",
267 				cd_icc_get_version (icc));
268 
269 	/* device class */
270 	g_string_append_printf (str, "  Profile Kind\t= %s\n",
271 				cd_profile_kind_to_string (cd_icc_get_kind (icc)));
272 
273 	/* colorspace */
274 	g_string_append_printf (str, "  Colorspace\t= %s\n",
275 				cd_colorspace_to_string (cd_icc_get_colorspace (icc)));
276 
277 	/* PCS */
278 	g_string_append (str, "  Conn. Space\t= ");
279 	switch (cmsGetPCS (priv->lcms_profile)) {
280 	case cmsSigXYZData:
281 		g_string_append (str, "xyz\n");
282 		break;
283 	case cmsSigLabData:
284 		g_string_append (str, "lab\n");
285 		break;
286 	default:
287 		g_string_append (str, "unknown\n");
288 		break;
289 	}
290 
291 	/* date and time */
292 	created = cd_icc_get_created (icc);
293 	if (created != NULL) {
294 		g_autofree gchar *created_str = NULL;
295 		created_str = g_date_time_format (created, "%F, %T");
296 		g_string_append_printf (str, "  Date, Time\t= %s\n", created_str);
297 		g_date_time_unref (created);
298 	}
299 
300 	/* profile use flags */
301 	g_string_append (str, "  Flags\t\t= ");
302 	tmp = cmsGetHeaderFlags (priv->lcms_profile);
303 	g_string_append (str, (tmp & cmsEmbeddedProfileTrue) > 0 ?
304 				"Embedded profile" : "Not embedded profile");
305 	g_string_append (str, ", ");
306 	g_string_append (str, (tmp & cmsUseWithEmbeddedDataOnly) > 0 ?
307 				"Use with embedded data only" : "Use anywhere");
308 	g_string_append (str, "\n");
309 
310 	/* header attributes */
311 	cmsGetHeaderAttributes (priv->lcms_profile, &header_flags);
312 	g_string_append (str, "  Dev. Attrbts\t= ");
313 	g_string_append (str, (header_flags & cmsTransparency) > 0 ?
314 				"transparency" : "reflective");
315 	g_string_append (str, ", ");
316 	g_string_append (str, (header_flags & cmsMatte) > 0 ?
317 				"matte" : "glossy");
318 	g_string_append (str, "\n");
319 
320 	/* rendering intent */
321 	g_string_append (str, "  Rndrng Intnt\t= ");
322 	switch (cmsGetHeaderRenderingIntent (priv->lcms_profile)) {
323 	case INTENT_PERCEPTUAL:
324 		g_string_append (str, "perceptual\n");
325 		break;
326 	case INTENT_RELATIVE_COLORIMETRIC:
327 		g_string_append (str, "relative-colorimetric\n");
328 		break;
329 	case INTENT_SATURATION:
330 		g_string_append (str, "saturation\n");
331 		break;
332 	case INTENT_ABSOLUTE_COLORIMETRIC:
333 		g_string_append (str, "absolute-colorimetric\n");
334 		break;
335 	default:
336 		g_string_append (str, "unknown\n");
337 		break;
338 	}
339 
340 	/* creator */
341 	tmp = cmsGetHeaderCreator (priv->lcms_profile);
342 	cd_icc_uint32_to_str (GUINT32_FROM_BE (tmp), tag_str);
343 	g_string_append_printf (str, "  Creator\t= %s\n", tag_str);
344 
345 	/* profile ID */
346 	cmsGetHeaderProfileID (priv->lcms_profile, profile_id);
347 	g_string_append_printf (str, "  Profile ID\t= 0x%02x%02x%02x%02x\n",
348 				profile_id[0],
349 				profile_id[1],
350 				profile_id[2],
351 				profile_id[3]);
352 
353 	/* print tags */
354 	g_string_append (str, "\n");
355 	number_tags = cmsGetTagCount (priv->lcms_profile);
356 	for (i = 0; i < number_tags; i++) {
357 		sig = cmsGetTagSignature (priv->lcms_profile, i);
358 
359 		/* convert to text */
360 		cd_icc_uint32_to_str (GUINT32_FROM_BE (sig), tag_str);
361 
362 		/* print header */
363 		g_string_append_printf (str, "tag %02u:\n", (guint) i);
364 		g_string_append_printf (str, "  sig\t'%s' [0x%x]\n",
365 					tag_str, (guint) sig);
366 
367 		/* is this linked to another data area? */
368 		sig_link = cmsTagLinkedTo (priv->lcms_profile, sig);
369 		if (sig_link != 0) {
370 			cd_icc_uint32_to_str (GUINT32_FROM_BE (sig_link), tag_str);
371 			g_string_append_printf (str, "  link\t'%s' [0x%x]\n", tag_str, sig_link);
372 			continue;
373 		}
374 
375 		/* get the tag type */
376 		tag_size = cmsReadRawTag (priv->lcms_profile, sig, NULL, 0);
377 		if (tag_size == 0 || tag_size > 16 * 1024 * 1024) {
378 			g_string_append_printf (str, "WARNING: Tag size impossible %i", tag_size);
379 			continue;
380 		}
381 		g_string_append_printf (str, "  size\t%i\n", tag_size);
382 		cmsReadRawTag (priv->lcms_profile, sig, &tmp, 4);
383 
384 		cd_icc_uint32_to_str (tmp, tag_str);
385 		tag_type = GUINT32_FROM_BE (tmp);
386 		g_string_append_printf (str, "  type\t'%s' [0x%x]\n", tag_str, tag_type);
387 
388 		/* print tag details */
389 		switch (tag_type) {
390 		case cmsSigTextType:
391 		case cmsSigTextDescriptionType:
392 		case cmsSigMultiLocalizedUnicodeType:
393 		{
394 			cmsMLU *mlu;
395 			guint32 j;
396 			guint32 mlu_size;
397 
398 			g_string_append_printf (str, "Text:\n");
399 			mlu = cd_icc_read_tag (icc, sig, &error_local);
400 			if (mlu == NULL) {
401 				g_string_append_printf (str, "WARNING: %s",
402 							error_local->message);
403 				g_clear_error (&error_local);
404 				break;
405 			}
406 			mlu_size = cmsMLUtranslationsCount (mlu);
407 			if (mlu_size == 0)
408 				g_string_append_printf (str, "  Info:\t\tMLU empty!\n");
409 			for (j = 0; j < mlu_size; j++) {
410 				gchar country_code[3] = "\0\0\0";
411 				gchar language_code[3] = "\0\0\0";
412 				guint32 text_size;
413 				g_autofree gchar *text_buffer = NULL;
414 				g_autofree gunichar *wtext = NULL;
415 				g_autoptr(GError) error = NULL;
416 
417 				ret = cmsMLUtranslationsCodes (mlu,
418 							       j,
419 							       language_code,
420 							       country_code);
421 				if (!ret)
422 					continue;
423 				text_size = cmsMLUgetWide (mlu,
424 							   language_code,
425 							   country_code,
426 							   NULL, 0);
427 				if (text_size == 0)
428 					continue;
429 				wtext = g_malloc (text_size);
430 				cmsMLUgetWide (mlu,
431 					       language_code,
432 					       country_code,
433 					       (wchar_t *) wtext,
434 					       text_size);
435 				text_buffer = g_ucs4_to_utf8 ((gunichar *) wtext, -1,
436 							      NULL, NULL, &error);
437 				if (text_buffer == NULL) {
438 					g_string_append_printf (str, "  %s_%s:\tInvalid: '%s'\n",
439 								language_code[0] != '\0' ? language_code : "en",
440 								country_code[0] != '\0' ? country_code : "US",
441 								error->message);
442 					continue;
443 				}
444 				g_string_append_printf (str, "  %s_%s:\t%s [%" G_GUINT32_FORMAT " bytes]\n",
445 							language_code[0] != '\0' ? language_code : "**",
446 							country_code[0] != '\0' ? country_code : "**",
447 							text_buffer,
448 							text_size);
449 			}
450 			break;
451 		}
452 		case cmsSigXYZType:
453 		{
454 			cmsCIEXYZ *xyz;
455 			xyz = cd_icc_read_tag (icc, sig, &error_local);
456 			if (xyz == NULL) {
457 				g_string_append_printf (str, "WARNING: %s",
458 							error_local->message);
459 				g_clear_error (&error_local);
460 				break;
461 			}
462 			g_string_append_printf (str, "XYZ:\n");
463 			g_string_append_printf (str, "  X:%f Y:%f Z:%f\n",
464 						xyz->X, xyz->Y, xyz->Z);
465 			break;
466 		}
467 		case cmsSigCurveType:
468 		{
469 			cmsToneCurve *curve;
470 			gdouble estimated_gamma;
471 			g_string_append_printf (str, "Curve:\n");
472 			curve = cd_icc_read_tag (icc, sig, &error_local);
473 			if (curve == NULL) {
474 				g_string_append_printf (str, "WARNING: %s",
475 							error_local->message);
476 				g_clear_error (&error_local);
477 				break;
478 			}
479 			estimated_gamma = cmsEstimateGamma (curve, 0.01);
480 			if (estimated_gamma > 0) {
481 				g_string_append_printf (str,
482 							"  Curve is gamma of %f\n",
483 							estimated_gamma);
484 			}
485 			break;
486 		}
487 		case cmsSigViewingConditionsType:
488 		{
489 			cmsICCViewingConditions *v;
490 			v = cmsReadTag(priv->lcms_profile, sig);
491 			if (v == NULL) {
492 				g_warning ("cannot read view tag");
493 				continue;
494 			}
495 			g_string_append_printf (str, "ViewingCondition:\n");
496 			g_string_append (str, "  Illuminant Type: ");
497 			switch (v->IlluminantType) {
498 			case cmsILLUMINANT_TYPE_D50:
499 				g_string_append (str, "D50\n");
500 				break;
501 			case cmsILLUMINANT_TYPE_D65:
502 				g_string_append (str, "D65\n");
503 				break;
504 			case cmsILLUMINANT_TYPE_D93:
505 				g_string_append (str, "D93\n");
506 				break;
507 			case cmsILLUMINANT_TYPE_F2:
508 				g_string_append (str, "F2\n");
509 				break;
510 			case cmsILLUMINANT_TYPE_D55:
511 				g_string_append (str, "D55\n");
512 				break;
513 			case cmsILLUMINANT_TYPE_A:
514 				g_string_append (str, "A\n");
515 				break;
516 			case cmsILLUMINANT_TYPE_E:
517 				g_string_append (str, "E\n");
518 				break;
519 			case cmsILLUMINANT_TYPE_F8:
520 				g_string_append (str, "F8\n");
521 				break;
522 			default:
523 				g_string_append (str, "Unknown\n");
524 			}
525 			g_string_append_printf (str, "  Illuminant: X:%f Y:%f Z:%f\n",
526 						v->IlluminantXYZ.X,
527 						v->IlluminantXYZ.Y,
528 						v->IlluminantXYZ.Z);
529 			g_string_append_printf (str, "  Surround: X:%f Y:%f Z:%f\n",
530 						v->SurroundXYZ.X,
531 						v->SurroundXYZ.Y,
532 						v->SurroundXYZ.Z);
533 			break;
534 		}
535 		case cmsSigDictType:
536 		{
537 			cmsHANDLE dict;
538 			const cmsDICTentry *entry;
539 
540 			g_string_append_printf (str, "Dictionary:\n");
541 			dict = cd_icc_read_tag (icc, sig, &error_local);
542 			if (dict == NULL) {
543 				g_string_append_printf (str, "WARNING: %s",
544 							error_local->message);
545 				g_clear_error (&error_local);
546 				break;
547 			}
548 			for (entry = cmsDictGetEntryList (dict);
549 			     entry != NULL;
550 			     entry = cmsDictNextEntry (entry)) {
551 				g_autofree gchar *ascii_name = NULL;
552 				g_autofree gchar *ascii_value = NULL;
553 
554 				/* convert from wchar_t to UTF-8 */
555 				ascii_name = g_ucs4_to_utf8 ((gunichar *) entry->Name, -1,
556 							     NULL, NULL, NULL);
557 				ascii_value = g_ucs4_to_utf8 ((gunichar *) entry->Value, -1,
558 							      NULL, NULL, NULL);
559 				g_string_append_printf (str, "  %s\t->\t%s\n",
560 							ascii_name != NULL ? ascii_name : "Invalid UCS4",
561 							ascii_value != NULL ? ascii_value : "Invalid UCS4");
562 			}
563 			break;
564 		}
565 		case cmsSigVcgtType:
566 		{
567 			const cmsToneCurve **vcgt;
568 			g_string_append (str, "VideoCardGammaTable:\n");
569 			vcgt = cd_icc_read_tag (icc, sig, &error_local);
570 			if (vcgt == NULL) {
571 				g_string_append_printf (str, "WARNING: %s",
572 							error_local->message);
573 				g_clear_error (&error_local);
574 				break;
575 			}
576 			g_string_append_printf (str, "  channels\t = %i\n", 3);
577 			g_string_append_printf (str, "  entries\t = %" G_GUINT32_FORMAT "\n",
578 						cmsGetToneCurveEstimatedTableEntries (vcgt[0]));
579 			break;
580 		}
581 		case cmsSigNamedColor2Type:
582 		{
583 			CdColorLab lab;
584 			cmsNAMEDCOLORLIST *nc2;
585 			cmsUInt16Number pcs[3];
586 			gchar name[cmsMAX_PATH];
587 			gchar prefix[33];
588 			gchar suffix[33];
589 			guint j;
590 
591 			g_string_append_printf (str, "Named colors:\n");
592 			nc2 = cd_icc_read_tag (icc, sig, &error_local);
593 			if (nc2 == NULL) {
594 				g_string_append_printf (str, "WARNING: %s",
595 							error_local->message);
596 				g_clear_error (&error_local);
597 				break;
598 			}
599 
600 			/* get the number of NCs */
601 			tmp = cmsNamedColorCount (nc2);
602 			if (tmp == 0) {
603 				g_string_append_printf (str, "  Info:\t\tNo NC's!\n");
604 				continue;
605 			}
606 			for (j = 0; j < tmp; j++) {
607 				g_autoptr(GString) string = NULL;
608 
609 				/* parse title */
610 				string = g_string_new ("");
611 				ret = cmsNamedColorInfo (nc2, j,
612 							 name,
613 							 prefix,
614 							 suffix,
615 							 (cmsUInt16Number *)&pcs,
616 							 NULL);
617 				if (!ret) {
618 					g_string_append_printf (str, "  Info:\t\tFailed to get NC #%" G_GUINT32_FORMAT, j);
619 					continue;
620 				}
621 				if (prefix[0] != '\0')
622 					g_string_append_printf (string, "%s ", prefix);
623 				g_string_append (string, name);
624 				if (suffix[0] != '\0')
625 					g_string_append_printf (string, " %s", suffix);
626 
627 				/* check is valid */
628 				ret = g_utf8_validate (string->str, string->len, NULL);
629 				if (!ret) {
630 					g_string_append (str, "  Info:\t\tInvalid 7 bit ASCII / UTF8\n");
631 					ret = cd_icc_fix_utf8_string (string);
632 					if (!ret) {
633 						g_string_append (str, "  Info:\t\tIFailed to fix: skipping entry\n");
634 						continue;
635 					}
636 				}
637 
638 				/* get color */
639 				cmsLabEncoded2Float ((cmsCIELab *) &lab, pcs);
640 				g_string_append_printf (str, "  %03u:\t %s\tL:%.2f a:%.3f b:%.3f\n",
641 							(guint) j,
642 							string->str,
643 							lab.L, lab.a, lab.b);
644 			}
645 			break;
646 		}
647 		default:
648 			break;
649 		}
650 
651 		/* done! */
652 		g_string_append_printf (str, "\n");
653 	}
654 
655 	/* remove trailing newline */
656 	if (str->len > 0)
657 		g_string_truncate (str, str->len - 1);
658 
659 	return g_string_free (str, FALSE);
660 }
661 
662 /**
663  * cd_icc_get_tags:
664  * @icc: a #CdIcc instance.
665  * @error: A #GError or %NULL
666  *
667  * Returns the internal tag table. Most users do not need to do this.
668  *
669  * Return value: (transfer full): the tag tables as an array of strings
670  *
671  * Since: 1.1.6
672  **/
673 gchar **
cd_icc_get_tags(CdIcc * icc,GError ** error)674 cd_icc_get_tags (CdIcc *icc, GError **error)
675 {
676 	CdIccPrivate *priv = GET_PRIVATE (icc);
677 	GPtrArray *tags;
678 	cmsTagSignature sig;
679 	gchar *tmp;
680 	guint32 i;
681 	guint32 number_tags;
682 
683 	tags = g_ptr_array_new ();
684 	number_tags = cmsGetTagCount (priv->lcms_profile);
685 	for (i = 0; i < number_tags; i++) {
686 		sig = cmsGetTagSignature (priv->lcms_profile, i);
687 		tmp = g_new0 (gchar, 5);
688 		cd_icc_uint32_to_str (GUINT32_FROM_BE (sig), tmp);
689 		g_ptr_array_add (tags, tmp);
690 	}
691 	g_ptr_array_add (tags, NULL);
692 	return (gchar **) g_ptr_array_free (tags, FALSE);
693 }
694 
695 /**
696  * cd_icc_str_to_tag:
697  **/
698 static cmsTagSignature
cd_icc_str_to_tag(const gchar * tag)699 cd_icc_str_to_tag (const gchar *tag)
700 {
701 	guint32 id;
702 	if (strlen (tag) != 4)
703 		return 0;
704 	memcpy (&id, tag, 4);
705 	return GUINT32_TO_BE (id);
706 }
707 
708 /**
709  * cd_icc_get_tag_data:
710  * @icc: a #CdIcc instance.
711  * @tag: a 4 bytes tag description, e.g. "cprt" or "vcgt"
712  * @error: A #GError or %NULL
713  *
714  * Returns the raw data for the specific tag.
715  * Most users do not need to do this.
716  *
717  * Return value: (transfer full): the data for the tag
718  *
719  * Since: 1.1.6
720  **/
721 GBytes *
cd_icc_get_tag_data(CdIcc * icc,const gchar * tag,GError ** error)722 cd_icc_get_tag_data (CdIcc *icc, const gchar *tag, GError **error)
723 {
724 	CdIccPrivate *priv = GET_PRIVATE (icc);
725 	cmsInt32Number tag_size;
726 	cmsTagSignature sig;
727 	gchar *tmp;
728 
729 	/* read tag */
730 	sig = cd_icc_str_to_tag (tag);
731 	if (sig == 0) {
732 		g_set_error (error,
733 			     CD_ICC_ERROR,
734 			     CD_ICC_ERROR_FAILED_TO_PARSE,
735 			     "Tag '%s' was not valid", tag);
736 		return NULL;
737 	}
738 	tag_size = cmsReadRawTag (priv->lcms_profile, sig, NULL, 0);
739 	if (tag_size == 0 || tag_size > 16 * 1024 * 1024) {
740 		g_set_error (error,
741 			     CD_ICC_ERROR,
742 			     CD_ICC_ERROR_NO_DATA,
743 			     "Tag size %i was not valid", tag_size);
744 		return NULL;
745 	}
746 
747 	/* return data */
748 	tmp = g_new0 (gchar, tag_size);
749 	cmsReadRawTag (priv->lcms_profile, sig, tmp, tag_size);
750 	return g_bytes_new_with_free_func (tmp, tag_size, g_free, tmp);
751 }
752 
753 /**
754  * cd_icc_set_tag_data:
755  * @icc: a #CdIcc instance.
756  * @tag: a 4 bytes tag description, e.g. "cprt" or "vcgt"
757  * @data: a variable sized data entry
758  * @error: A #GError or %NULL
759  *
760  * Sets the raw data for the specific tag.
761  * Most users do not need to do this.
762  *
763  * Since: 1.1.6
764  **/
765 gboolean
cd_icc_set_tag_data(CdIcc * icc,const gchar * tag,GBytes * data,GError ** error)766 cd_icc_set_tag_data (CdIcc *icc, const gchar *tag, GBytes *data, GError **error)
767 {
768 	CdIccPrivate *priv = GET_PRIVATE (icc);
769 	cmsTagSignature sig;
770 	gboolean ret;
771 
772 	/* work around an LCMS API quirk in that you can't do cmsWriteRawTag()
773 	 * if the tag already exists. Use the undocumented usage of
774 	 * cmsWriteTag() to delete the tag first */
775 	sig = cd_icc_str_to_tag (tag);
776 	if (sig == 0) {
777 		g_set_error (error,
778 			     CD_ICC_ERROR,
779 			     CD_ICC_ERROR_FAILED_TO_PARSE,
780 			     "Tag '%s' was not valid", tag);
781 		return FALSE;
782 	}
783 	cmsWriteTag (priv->lcms_profile, sig, NULL);
784 	ret = cmsWriteRawTag (priv->lcms_profile,
785 			      sig,
786 			      g_bytes_get_data (data, NULL),
787 			      g_bytes_get_size (data));
788 	if (!ret) {
789 		g_set_error (error,
790 			     CD_ICC_ERROR,
791 			     CD_ICC_ERROR_FAILED_TO_SAVE,
792 			     "Failed to write %" G_GSIZE_FORMAT " bytes",
793 			     g_bytes_get_size (data));
794 	}
795 	return ret;
796 }
797 
798 /* map lcms profile class to colord type */
799 const struct {
800 	cmsProfileClassSignature	lcms;
801 	CdProfileKind			colord;
802 } map_profile_kind[] = {
803 	{ cmsSigInputClass,		CD_PROFILE_KIND_INPUT_DEVICE },
804 	{ cmsSigDisplayClass,		CD_PROFILE_KIND_DISPLAY_DEVICE },
805 	{ cmsSigOutputClass,		CD_PROFILE_KIND_OUTPUT_DEVICE },
806 	{ cmsSigLinkClass,		CD_PROFILE_KIND_DEVICELINK },
807 	{ cmsSigColorSpaceClass,	CD_PROFILE_KIND_COLORSPACE_CONVERSION },
808 	{ cmsSigAbstractClass,		CD_PROFILE_KIND_ABSTRACT },
809 	{ cmsSigNamedColorClass,	CD_PROFILE_KIND_NAMED_COLOR },
810 	{ 0,				CD_PROFILE_KIND_LAST }
811 };
812 
813 /* map lcms colorspace to colord type */
814 const struct {
815 	cmsColorSpaceSignature		lcms;
816 	CdColorspace			colord;
817 } map_colorspace[] = {
818 	{ cmsSigXYZData,		CD_COLORSPACE_XYZ },
819 	{ cmsSigLabData,		CD_COLORSPACE_LAB },
820 	{ cmsSigLuvData,		CD_COLORSPACE_LUV },
821 	{ cmsSigYCbCrData,		CD_COLORSPACE_YCBCR },
822 	{ cmsSigYxyData,		CD_COLORSPACE_YXY },
823 	{ cmsSigRgbData,		CD_COLORSPACE_RGB },
824 	{ cmsSigGrayData,		CD_COLORSPACE_GRAY },
825 	{ cmsSigHsvData,		CD_COLORSPACE_HSV },
826 	{ cmsSigCmykData,		CD_COLORSPACE_CMYK },
827 	{ cmsSigCmyData,		CD_COLORSPACE_CMY },
828 	{ 0,				CD_COLORSPACE_LAST }
829 };
830 
831 /**
832  * cd_icc_get_precooked_md5:
833  **/
834 static gchar *
cd_icc_get_precooked_md5(cmsHPROFILE lcms_profile)835 cd_icc_get_precooked_md5 (cmsHPROFILE lcms_profile)
836 {
837 	cmsUInt8Number icc_id[16];
838 	gboolean md5_precooked = FALSE;
839 	gchar *md5 = NULL;
840 	guint i;
841 
842 	/* check to see if we have a pre-cooked MD5 */
843 	cmsGetHeaderProfileID (lcms_profile, icc_id);
844 	for (i = 0; i < 16; i++) {
845 		if (icc_id[i] != 0) {
846 			md5_precooked = TRUE;
847 			break;
848 		}
849 	}
850 	if (md5_precooked == FALSE)
851 		return NULL;
852 
853 	/* convert to a hex string */
854 	md5 = g_new0 (gchar, 32 + 1);
855 	for (i = 0; i < 16; i++)
856 		g_snprintf (md5 + i * 2, 3, "%02x", icc_id[i]);
857 	return md5;
858 }
859 
860 /**
861  * cd_icc_calc_whitepoint:
862  **/
863 static gboolean
cd_icc_calc_whitepoint(CdIcc * icc,GError ** error)864 cd_icc_calc_whitepoint (CdIcc *icc, GError **error)
865 {
866 	CdIccPrivate *priv = GET_PRIVATE (icc);
867 	cmsBool bpc[2] = { FALSE, FALSE };
868 	cmsCIEXYZ whitepoint;
869 	cmsFloat64Number adaption[2] = { 0, 0 };
870 	cmsHPROFILE profiles[2];
871 	cmsHTRANSFORM transform;
872 	cmsUInt32Number intents[2] = { INTENT_ABSOLUTE_COLORIMETRIC,
873 				       INTENT_ABSOLUTE_COLORIMETRIC };
874 	gboolean ret = TRUE;
875 	gdouble temp_float;
876 	guint8 data[3] = { 255, 255, 255 };
877 
878 	/* do Lab to RGB transform to get primaries */
879 	profiles[0] = priv->lcms_profile;
880 	profiles[1] = cmsCreateXYZProfileTHR (priv->context_lcms);
881 	transform = cmsCreateExtendedTransform (priv->context_lcms,
882 						2,
883 						profiles,
884 						bpc,
885 						intents,
886 						adaption,
887 						NULL, 0, /* gamut ICC */
888 						TYPE_RGB_8,
889 						TYPE_XYZ_DBL,
890 						cmsFLAGS_NOOPTIMIZE);
891 	if (transform == NULL) {
892 		ret = FALSE;
893 		g_set_error_literal (error,
894 				     CD_ICC_ERROR,
895 				     CD_ICC_ERROR_FAILED_TO_PARSE,
896 				     "failed to setup RGB -> XYZ transform");
897 		goto out;
898 	}
899 
900 	/* run white through the transform */
901 	cmsDoTransform (transform, data, &whitepoint, 1);
902 	cd_color_xyz_set (&priv->white,
903 			  whitepoint.X,
904 			  whitepoint.Y,
905 			  whitepoint.Z);
906 
907 	/* get temperature rounded to nearest 100K */
908 	temp_float = cd_color_xyz_to_cct (&priv->white);
909 	if (temp_float > 0)
910 		priv->temperature = (((guint) temp_float) / 100) * 100;
911 out:
912 	if (profiles[1] != NULL)
913 		cmsCloseProfile (profiles[1]);
914 	if (transform != NULL)
915 		cmsDeleteTransform (transform);
916 	return ret;
917 }
918 
919 /**
920  * cd_icc_load_characterization_data:
921  **/
922 static gboolean
cd_icc_load_characterization_data(CdIcc * icc,GError ** error)923 cd_icc_load_characterization_data (CdIcc *icc, GError **error)
924 {
925 	CdIccPrivate *priv = GET_PRIVATE (icc);
926 	cmsMLU *mlu;
927 	gboolean ret = TRUE;
928 	GError *error_local = NULL;
929 	guint32 text_size;
930 
931 	/* this can only be non-localized text */
932 	mlu = cd_icc_read_tag (icc, cmsSigCharTargetTag, &error_local);
933 	if (mlu == NULL) {
934 		/* no data is okay */
935 		if (g_error_matches (error_local,
936 				     CD_ICC_ERROR,
937 				     CD_ICC_ERROR_NO_DATA)) {
938 			g_error_free (error_local);
939 			priv->characterization_data = NULL;
940 			goto out;
941 		}
942 		ret = FALSE;
943 		g_propagate_error (error, error_local);
944 		goto out;
945 	}
946 	text_size = cmsMLUgetASCII (mlu,
947 				    cmsNoLanguage,
948 				    cmsNoCountry,
949 				    NULL, 0);
950 	priv->characterization_data = g_new0 (gchar, text_size + 1);
951 	cmsMLUgetASCII (mlu,
952 			cmsNoLanguage,
953 			cmsNoCountry,
954 			priv->characterization_data,
955 			text_size);
956 out:
957 	return ret;
958 }
959 
960 /**
961  * cd_icc_load_primaries:
962  **/
963 static gboolean
cd_icc_load_primaries(CdIcc * icc,GError ** error)964 cd_icc_load_primaries (CdIcc *icc, GError **error)
965 {
966 	CdIccPrivate *priv = GET_PRIVATE (icc);
967 	cmsCIEXYZ *cie_xyz;
968 	cmsHPROFILE xyz_profile = NULL;
969 	cmsHTRANSFORM transform = NULL;
970 	gboolean ret = TRUE;
971 	gdouble rgb_values[3];
972 
973 	/* get white point */
974 	ret = cd_icc_calc_whitepoint (icc, error);
975 	if (!ret)
976 		goto out;
977 
978 	/* get the illuminants from the primaries */
979 	cie_xyz = cd_icc_read_tag (icc, cmsSigRedMatrixColumnTag, NULL);
980 	if (cie_xyz != NULL) {
981 		cd_color_xyz_copy ((CdColorXYZ *) cie_xyz, &priv->red);
982 		cie_xyz = cd_icc_read_tag (icc, cmsSigGreenMatrixColumnTag, error);
983 		if (cie_xyz == NULL) {
984 			ret = FALSE;
985 			goto out;
986 		}
987 		cd_color_xyz_copy ((CdColorXYZ *) cie_xyz, &priv->green);
988 		cie_xyz = cd_icc_read_tag (icc, cmsSigBlueMatrixColumnTag, error);
989 		if (cie_xyz == NULL) {
990 			ret = FALSE;
991 			goto out;
992 		}
993 		cd_color_xyz_copy ((CdColorXYZ *) cie_xyz, &priv->blue);
994 		goto out;
995 	}
996 
997 	/* get the illuminants by running it through the profile */
998 	xyz_profile = cmsCreateXYZProfileTHR (priv->context_lcms);
999 	transform = cmsCreateTransformTHR (priv->context_lcms,
1000 					   priv->lcms_profile, TYPE_RGB_DBL,
1001 					   xyz_profile, TYPE_XYZ_DBL,
1002 					   INTENT_PERCEPTUAL, 0);
1003 	if (transform == NULL) {
1004 		ret = FALSE;
1005 		g_set_error_literal (error,
1006 				     CD_ICC_ERROR,
1007 				     CD_ICC_ERROR_FAILED_TO_PARSE,
1008 				     "failed to setup RGB -> XYZ transform");
1009 		goto out;
1010 	}
1011 
1012 	/* red */
1013 	rgb_values[0] = 1.0;
1014 	rgb_values[1] = 0.0;
1015 	rgb_values[2] = 0.0;
1016 	cmsDoTransform (transform, rgb_values, &priv->red, 1);
1017 
1018 	/* green */
1019 	rgb_values[0] = 0.0;
1020 	rgb_values[1] = 1.0;
1021 	rgb_values[2] = 0.0;
1022 	cmsDoTransform (transform, rgb_values, &priv->green, 1);
1023 
1024 	/* blue */
1025 	rgb_values[0] = 0.0;
1026 	rgb_values[1] = 0.0;
1027 	rgb_values[2] = 1.0;
1028 	cmsDoTransform (transform, rgb_values, &priv->blue, 1);
1029 out:
1030 	if (transform != NULL)
1031 		cmsDeleteTransform (transform);
1032 	if (xyz_profile != NULL)
1033 		cmsCloseProfile (xyz_profile);
1034 	return ret;
1035 }
1036 
1037 /**
1038  * cd_icc_load_metadata_item:
1039  **/
1040 static gboolean
cd_icc_load_metadata_item(CdIcc * icc,const gunichar * name,const gunichar * value,GError ** error)1041 cd_icc_load_metadata_item (CdIcc *icc,
1042 			   const gunichar *name,
1043 			   const gunichar *value,
1044 			   GError **error)
1045 {
1046 	CdIccPrivate *priv = GET_PRIVATE (icc);
1047 	g_autoptr(GError) error_local = NULL;
1048 	g_autofree gchar *ascii_name = NULL;
1049 	g_autofree gchar *ascii_value = NULL;
1050 
1051 	/* parse name */
1052 	ascii_name = g_ucs4_to_utf8 (name, -1, NULL, NULL, &error_local);
1053 	if (ascii_name == NULL) {
1054 		g_set_error (error,
1055 			     CD_ICC_ERROR,
1056 			     CD_ICC_ERROR_CORRUPTION_DETECTED,
1057 			     "Could not convert name in dict: %s",
1058 			     error_local->message);
1059 		return FALSE;
1060 	}
1061 
1062 	/* parse value */
1063 	ascii_value = g_ucs4_to_utf8 (value, -1, NULL, NULL, &error_local);
1064 	if (ascii_value == NULL) {
1065 		g_set_error (error,
1066 			     CD_ICC_ERROR,
1067 			     CD_ICC_ERROR_CORRUPTION_DETECTED,
1068 			     "Could not convert value in dict: %s",
1069 			     error_local->message);
1070 		return FALSE;
1071 	}
1072 
1073 	/* all okay */
1074 	g_hash_table_insert (priv->metadata,
1075 			     g_strdup (ascii_name),
1076 			     g_strdup (ascii_value));
1077 	return TRUE;
1078 }
1079 
1080 /**
1081  * cd_icc_load_metadata:
1082  **/
1083 static gboolean
cd_icc_load_metadata(CdIcc * icc,GError ** error)1084 cd_icc_load_metadata (CdIcc *icc, GError **error)
1085 {
1086 	cmsHANDLE dict;
1087 	const cmsDICTentry *entry;
1088 	GError *error_local = NULL;
1089 
1090 	/* get dictionary metadata */
1091 	dict = cd_icc_read_tag (icc, cmsSigMetaTag, &error_local);
1092 	if (dict == NULL) {
1093 		/* no data is okay */
1094 		if (g_error_matches (error_local,
1095 				     CD_ICC_ERROR,
1096 				     CD_ICC_ERROR_NO_DATA)) {
1097 			g_error_free (error_local);
1098 			return TRUE;
1099 		}
1100 		g_propagate_error (error, error_local);
1101 		return FALSE;
1102 	}
1103 	for (entry = cmsDictGetEntryList (dict);
1104 	     entry != NULL;
1105 	     entry = cmsDictNextEntry (entry)) {
1106 		if (!cd_icc_load_metadata_item (icc,
1107 						(const gunichar *) entry->Name,
1108 						(const gunichar *) entry->Value,
1109 						error))
1110 			return FALSE;
1111 	}
1112 	return TRUE;
1113 }
1114 
1115 /**
1116  * cd_icc_load:
1117  **/
1118 static gboolean
cd_icc_load(CdIcc * icc,CdIccLoadFlags flags,GError ** error)1119 cd_icc_load (CdIcc *icc, CdIccLoadFlags flags, GError **error)
1120 {
1121 	CdIccPrivate *priv = GET_PRIVATE (icc);
1122 	cmsColorSpaceSignature colorspace;
1123 	cmsProfileClassSignature profile_class;
1124 	guint i;
1125 
1126 	/* get version */
1127 	priv->version = cmsGetProfileVersion (priv->lcms_profile);
1128 
1129 	/* convert profile kind */
1130 	profile_class = cmsGetDeviceClass (priv->lcms_profile);
1131 	for (i = 0; map_profile_kind[i].colord != CD_PROFILE_KIND_LAST; i++) {
1132 		if (map_profile_kind[i].lcms == profile_class) {
1133 			priv->kind = map_profile_kind[i].colord;
1134 			break;
1135 		}
1136 	}
1137 
1138 	/* convert colorspace */
1139 	colorspace = cmsGetColorSpace (priv->lcms_profile);
1140 	for (i = 0; map_colorspace[i].colord != CD_COLORSPACE_LAST; i++) {
1141 		if (map_colorspace[i].lcms == colorspace) {
1142 			priv->colorspace = map_colorspace[i].colord;
1143 			break;
1144 		}
1145 	}
1146 
1147 	/* read optional metadata? */
1148 	if ((flags & CD_ICC_LOAD_FLAGS_METADATA) > 0) {
1149 		if (!cd_icc_load_metadata (icc, error))
1150 			return FALSE;
1151 	}
1152 
1153 	/* get precooked profile ID if one exists */
1154 	priv->checksum = cd_icc_get_precooked_md5 (priv->lcms_profile);
1155 
1156 	/* read default translations */
1157 	cd_icc_get_description (icc, NULL, NULL);
1158 	cd_icc_get_copyright (icc, NULL, NULL);
1159 	cd_icc_get_manufacturer (icc, NULL, NULL);
1160 	cd_icc_get_model (icc, NULL, NULL);
1161 	if ((flags & CD_ICC_LOAD_FLAGS_TRANSLATIONS) > 0) {
1162 		/* FIXME: get the locale list from LCMS */
1163 	}
1164 
1165 	/* read named colors if the client cares */
1166 	if ((flags & CD_ICC_LOAD_FLAGS_NAMED_COLORS) > 0) {
1167 		if (!cd_icc_load_named_colors (icc, error))
1168 			return FALSE;
1169 	}
1170 
1171 	/* read primaries if the client cares */
1172 	if ((flags & CD_ICC_LOAD_FLAGS_PRIMARIES) > 0 &&
1173 	    priv->colorspace == CD_COLORSPACE_RGB) {
1174 		if (!cd_icc_load_primaries (icc, error))
1175 			return FALSE;
1176 	}
1177 
1178 	/* read characterization data if the client cares */
1179 	if ((flags & CD_ICC_LOAD_FLAGS_CHARACTERIZATION) > 0) {
1180 		if (!cd_icc_load_characterization_data (icc, error))
1181 			return FALSE;
1182 	}
1183 	return TRUE;
1184 }
1185 
1186 /**
1187  * cd_icc_load_data:
1188  * @icc: a #CdIcc instance.
1189  * @data: binary data
1190  * @data_len: Length of @data
1191  * @flags: a set of #CdIccLoadFlags
1192  * @error: A #GError or %NULL
1193  *
1194  * Loads an ICC profile from raw byte data.
1195  *
1196  * Since: 0.1.32
1197  **/
1198 gboolean
cd_icc_load_data(CdIcc * icc,const guint8 * data,gsize data_len,CdIccLoadFlags flags,GError ** error)1199 cd_icc_load_data (CdIcc *icc,
1200 		  const guint8 *data,
1201 		  gsize data_len,
1202 		  CdIccLoadFlags flags,
1203 		  GError **error)
1204 {
1205 	CdIccPrivate *priv = GET_PRIVATE (icc);
1206 
1207 	g_return_val_if_fail (CD_IS_ICC (icc), FALSE);
1208 	g_return_val_if_fail (data != NULL, FALSE);
1209 	g_return_val_if_fail (priv->lcms_profile == NULL, FALSE);
1210 
1211 	/* ensure we have the header */
1212 	if (data_len < 0x84) {
1213 		g_set_error_literal (error,
1214 				     CD_ICC_ERROR,
1215 				     CD_ICC_ERROR_FAILED_TO_PARSE,
1216 				     "icc was not valid (file size too small)");
1217 		return FALSE;
1218 	}
1219 
1220 	/* load icc into lcms */
1221 	priv->lcms_profile = cmsOpenProfileFromMemTHR (priv->context_lcms,
1222 						       data, data_len);
1223 	if (priv->lcms_profile == NULL) {
1224 		g_set_error_literal (error,
1225 				     CD_ICC_ERROR,
1226 				     CD_ICC_ERROR_FAILED_TO_PARSE,
1227 				     "failed to load: not an ICC icc");
1228 		return FALSE;
1229 	}
1230 
1231 	/* save length to avoid trusting the profile */
1232 	priv->size = data_len;
1233 
1234 	/* load cached data */
1235 	if (!cd_icc_load (icc, flags, error))
1236 		return FALSE;
1237 
1238 	/* calculate the data MD5 if there was no embedded profile */
1239 	if (priv->checksum == NULL &&
1240 	    (flags & CD_ICC_LOAD_FLAGS_FALLBACK_MD5) > 0) {
1241 		priv->checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5,
1242 							      (const guchar *) data,
1243 							      data_len);
1244 	}
1245 	return TRUE;
1246 }
1247 
1248 /**
1249  * cd_util_write_dict_entry:
1250  **/
1251 static gboolean
cd_util_write_dict_entry(cmsHANDLE dict,const gchar * key,const gchar * value,GError ** error)1252 cd_util_write_dict_entry (cmsHANDLE dict,
1253 			  const gchar *key,
1254 			  const gchar *value,
1255 			  GError **error)
1256 {
1257 	gboolean ret = FALSE;
1258 	g_autofree gunichar *mb_key = NULL;
1259 	g_autofree gunichar *mb_value = NULL;
1260 
1261 	mb_key = g_utf8_to_ucs4 (key, -1, NULL, NULL, error);
1262 	if (mb_key == NULL)
1263 		return FALSE;
1264 	mb_value = g_utf8_to_ucs4 (value, -1, NULL, NULL, error);
1265 	if (mb_value == NULL)
1266 		return FALSE;
1267 	ret = cmsDictAddEntry (dict,
1268 			       (const wchar_t *) mb_key,
1269 			       (const wchar_t *) mb_value,
1270 			       NULL, NULL);
1271 	if (!ret) {
1272 		g_set_error_literal (error,
1273 				     CD_ICC_ERROR,
1274 				     CD_ICC_ERROR_FAILED_TO_SAVE,
1275 				     "Failed to write dict entry");
1276 		return FALSE;
1277 	}
1278 	return TRUE;
1279 }
1280 
1281 typedef struct {
1282 	gchar		*language_code;	/* will always be xx\0 */
1283 	gchar		*country_code;	/* will always be xx\0 */
1284 	gunichar	*wtext;
1285 } CdMluObject;
1286 
1287 /**
1288  * cd_util_mlu_object_free:
1289  **/
1290 static void
cd_util_mlu_object_free(gpointer data)1291 cd_util_mlu_object_free (gpointer data)
1292 {
1293 	CdMluObject *obj = (CdMluObject *) data;
1294 	g_free (obj->language_code);
1295 	g_free (obj->country_code);
1296 	g_free (obj->wtext);
1297 	g_free (obj);
1298 }
1299 
1300 /**
1301  * cd_util_mlu_object_parse:
1302  **/
1303 static CdMluObject *
cd_util_mlu_object_parse(const gchar * locale,const gchar * utf8_text,GError ** error)1304 cd_util_mlu_object_parse (const gchar *locale,
1305 			  const gchar *utf8_text,
1306 			  GError **error)
1307 {
1308 	CdMluObject *obj = NULL;
1309 	guint type;
1310 	gunichar *wtext;
1311 	g_autofree gchar *key = NULL;
1312 	g_auto(GStrv) split = NULL;
1313 
1314 	/* untranslated version */
1315 	if (locale == NULL || locale[0] == '\0') {
1316 		wtext = g_utf8_to_ucs4 (utf8_text, -1, NULL, NULL, error);
1317 		if (wtext == NULL)
1318 			return NULL;
1319 		obj = g_new0 (CdMluObject, 1);
1320 		obj->wtext = wtext;
1321 		return obj;
1322 	}
1323 
1324 	/* ignore ##@latin */
1325 	if (g_strstr_len (locale, -1, "@") != NULL)
1326 		return NULL;
1327 
1328 	key = g_strdup (locale);
1329 	g_strdelimit (key, ".", '\0');
1330 	split = g_strsplit (key, "_", -1);
1331 	if (strlen (split[0]) != 2)
1332 		return NULL;
1333 	type = g_strv_length (split);
1334 	if (type > 2)
1335 		return NULL;
1336 
1337 	/* convert to wchars */
1338 	wtext = g_utf8_to_ucs4 (utf8_text, -1, NULL, NULL, error);
1339 	if (wtext == NULL)
1340 		return NULL;
1341 
1342 	/* lv */
1343 	if (type == 1) {
1344 		obj = g_new0 (CdMluObject, 1);
1345 		obj->language_code = g_strdup (split[0]);
1346 		obj->wtext = wtext;
1347 		return obj;
1348 	}
1349 
1350 	/* en_GB */
1351 	if (strlen (split[1]) != 2)
1352 		return NULL;
1353 	obj = g_new0 (CdMluObject, 1);
1354 	obj->language_code = g_strdup (split[0]);
1355 	obj->country_code = g_strdup (split[1]);
1356 	obj->wtext = wtext;
1357 	return obj;
1358 }
1359 
1360 /**
1361  * cd_util_write_tag_ascii:
1362  **/
1363 static gboolean
cd_util_write_tag_ascii(CdIcc * icc,cmsTagSignature sig,const gchar * value,GError ** error)1364 cd_util_write_tag_ascii (CdIcc *icc,
1365 			 cmsTagSignature sig,
1366 			 const gchar *value,
1367 			 GError **error)
1368 {
1369 	CdIccPrivate *priv = GET_PRIVATE (icc);
1370 	cmsMLU *mlu = NULL;
1371 	gboolean ret = TRUE;
1372 
1373 	/* nothing set */
1374 	if (value == NULL) {
1375 		ret = cd_icc_write_tag (icc, sig, NULL, error);
1376 		goto out;
1377 	}
1378 
1379 	/* set value */
1380 	mlu = cmsMLUalloc (priv->context_lcms, 1);
1381 	ret = cmsMLUsetASCII (mlu, "en", "US", value);
1382 	if (!ret) {
1383 		g_set_error_literal (error,
1384 				     CD_ICC_ERROR,
1385 				     CD_ICC_ERROR_FAILED_TO_SAVE,
1386 				     "cannot write MLU text");
1387 		goto out;
1388 	}
1389 
1390 	/* write tag */
1391 	ret = cd_icc_write_tag (icc, sig, mlu, error);
1392 	if (!ret)
1393 		goto out;
1394 out:
1395 	if (mlu != NULL)
1396 		cmsMLUfree (mlu);
1397 	return ret;
1398 }
1399 
1400 /**
1401  * cd_util_write_tag_ascii_default:
1402  **/
1403 static gboolean
cd_util_write_tag_ascii_default(CdIcc * icc,cmsTagSignature sig,GHashTable * hash,GError ** error)1404 cd_util_write_tag_ascii_default (CdIcc *icc,
1405 				 cmsTagSignature sig,
1406 				 GHashTable *hash,
1407 				 GError **error)
1408 {
1409 	const gchar *value;
1410 	/* get default value */
1411 	value = g_hash_table_lookup (hash, "");
1412 	return cd_util_write_tag_ascii (icc, sig, value, error);
1413 }
1414 
1415 /**
1416  * cd_util_sort_mlu_array_cb:
1417  **/
1418 static gint
cd_util_sort_mlu_array_cb(gconstpointer a,gconstpointer b)1419 cd_util_sort_mlu_array_cb (gconstpointer a, gconstpointer b)
1420 {
1421 	CdMluObject *sa = *((CdMluObject **) a);
1422 	CdMluObject *sb = *((CdMluObject **) b);
1423 	return g_strcmp0 (sa->language_code, sb->language_code);
1424 }
1425 
1426 /**
1427  * cd_util_write_tag_localized:
1428  **/
1429 static gboolean
cd_util_write_tag_localized(CdIcc * icc,cmsTagSignature sig,GHashTable * hash,GError ** error)1430 cd_util_write_tag_localized (CdIcc *icc,
1431 			     cmsTagSignature sig,
1432 			     GHashTable *hash,
1433 			     GError **error)
1434 {
1435 	CdIccPrivate *priv = GET_PRIVATE (icc);
1436 	CdMluObject *obj;
1437 	GError *error_local = NULL;
1438 	GList *l;
1439 	cmsMLU *mlu = NULL;
1440 	const gchar *locale;
1441 	const gchar *value;
1442 	gboolean ret = TRUE;
1443 	guint i;
1444 	g_autoptr(GList) keys = NULL;
1445 	g_autoptr(GPtrArray) array = NULL;
1446 
1447 	/* convert all the hash entries into CdMluObject's */
1448 	keys = g_hash_table_get_keys (hash);
1449 	array = g_ptr_array_new_with_free_func (cd_util_mlu_object_free);
1450 	for (l = keys; l != NULL; l = l->next) {
1451 		locale = l->data;
1452 		value = g_hash_table_lookup (hash, locale);
1453 		if (value == NULL)
1454 			continue;
1455 		obj = cd_util_mlu_object_parse (locale, value, &error_local);
1456 		if (obj == NULL) {
1457 			g_warning ("failed to parse localized text %s[%s]: %s",
1458 				   value, locale, error_local->message);
1459 			g_clear_error (&error_local);
1460 			continue;
1461 		}
1462 		g_ptr_array_add (array, obj);
1463 	}
1464 
1465 	/* delete tag if there is no data */
1466 	if (array->len == 0) {
1467 		ret = cd_icc_write_tag (icc, sig, NULL, error);
1468 		goto out;
1469 	}
1470 
1471 	/* sort the data so we always write the default first */
1472 	g_ptr_array_sort (array, cd_util_sort_mlu_array_cb);
1473 
1474 	/* create MLU object to hold all the translations */
1475 	mlu = cmsMLUalloc (priv->context_lcms, array->len);
1476 	for (i = 0; i < array->len; i++) {
1477 		obj = g_ptr_array_index (array, i);
1478 		if (obj->language_code == NULL &&
1479 		    obj->country_code == NULL) {
1480 			/* the default translation is encoded as en_US rather
1481 			 * than NoLanguage_NoCountry as the latter means
1482 			 * 'the first entry' when reading */
1483 			ret = cmsMLUsetWide (mlu, "en", "US",
1484 					     (const wchar_t *) obj->wtext);
1485 		} else {
1486 			/* casting to wchar_t is okay as gunichar is 4 bytes
1487 			 * on Linux and OS-X, and colord is never going to
1488 			 * be compiled for Windows */
1489 			ret = cmsMLUsetWide (mlu,
1490 					     obj->language_code != NULL ? obj->language_code : cmsNoLanguage,
1491 					     obj->country_code != NULL ? obj->country_code : cmsNoCountry,
1492 					     (const wchar_t *) obj->wtext);
1493 		}
1494 		if (!ret) {
1495 			g_set_error_literal (error,
1496 					     CD_ICC_ERROR,
1497 					     CD_ICC_ERROR_FAILED_TO_SAVE,
1498 					     "cannot write MLU text");
1499 			goto out;
1500 		}
1501 	}
1502 
1503 	/* write tag */
1504 	ret = cd_icc_write_tag (icc, sig, mlu, error);
1505 	if (!ret)
1506 		goto out;
1507 out:
1508 	if (mlu != NULL)
1509 		cmsMLUfree (mlu);
1510 	return ret;
1511 }
1512 
1513 /**
1514  * cd_icc_save_file_mkdir_parents:
1515  **/
1516 static gboolean
cd_icc_save_file_mkdir_parents(GFile * file,GError ** error)1517 cd_icc_save_file_mkdir_parents (GFile *file, GError **error)
1518 {
1519 	g_autoptr(GFile) parent_dir = NULL;
1520 
1521 	/* get parent directory */
1522 	parent_dir = g_file_get_parent (file);
1523 	if (parent_dir == NULL) {
1524 		g_set_error_literal (error,
1525 				     CD_ICC_ERROR,
1526 				     CD_ICC_ERROR_FAILED_TO_CREATE,
1527 				     "could not get parent dir");
1528 		return FALSE;
1529 	}
1530 
1531 	/* ensure desination does not already exist */
1532 	if (g_file_query_exists (parent_dir, NULL))
1533 		return TRUE;
1534 	if (!g_file_make_directory_with_parents (parent_dir, NULL, error))
1535 		return FALSE;
1536 	return TRUE;
1537 }
1538 
1539 /**
1540  * cd_icc_serialize_profile:
1541  **/
1542 static GBytes *
cd_icc_serialize_profile(CdIcc * icc,GError ** error)1543 cd_icc_serialize_profile (CdIcc *icc, GError **error)
1544 {
1545 	CdIccPrivate *priv = GET_PRIVATE (icc);
1546 	cmsUInt32Number length = 0;
1547 	gboolean ret;
1548 	g_autofree gchar *data_tmp = NULL;
1549 
1550 	/* get size of profile */
1551 	ret = cmsSaveProfileToMem (priv->lcms_profile,
1552 				   NULL, &length);
1553 	if (!ret) {
1554 		g_set_error_literal (error,
1555 				     CD_ICC_ERROR,
1556 				     CD_ICC_ERROR_FAILED_TO_SAVE,
1557 				     "failed to dump ICC file");
1558 		return NULL;
1559 	}
1560 
1561 	/* sanity check to 16Mb */
1562 	if (length == 0 || length > 16 * 1024 * 1024) {
1563 		g_set_error (error,
1564 			     CD_ICC_ERROR,
1565 			     CD_ICC_ERROR_FAILED_TO_SAVE,
1566 			     "failed to save ICC file, requested %u "
1567 			     "bytes and limit is 16Mb",
1568 			     length);
1569 		return NULL;
1570 	}
1571 
1572 	/* allocate and get profile data */
1573 	data_tmp = g_new0 (gchar, length);
1574 	ret = cmsSaveProfileToMem (priv->lcms_profile,
1575 				   data_tmp, &length);
1576 	if (!ret) {
1577 		g_set_error_literal (error,
1578 				     CD_ICC_ERROR,
1579 				     CD_ICC_ERROR_FAILED_TO_SAVE,
1580 				     "failed to dump ICC file to memory");
1581 		return NULL;
1582 	}
1583 
1584 	/* success */
1585 	return g_bytes_new (data_tmp, length);
1586 }
1587 
1588 /**
1589  * cd_icc_save_data:
1590  * @icc: a #CdIcc instance.
1591  * @flags: a set of #CdIccSaveFlags
1592  * @error: A #GError or %NULL
1593  *
1594  * Saves an ICC profile to an allocated memory location.
1595  *
1596  * Return vale: A #GBytes structure, or %NULL for error
1597  *
1598  * Since: 1.0.2
1599  **/
1600 GBytes *
cd_icc_save_data(CdIcc * icc,CdIccSaveFlags flags,GError ** error)1601 cd_icc_save_data (CdIcc *icc,
1602 		  CdIccSaveFlags flags,
1603 		  GError **error)
1604 {
1605 	CdIccPrivate *priv = GET_PRIVATE (icc);
1606 	cmsHANDLE dict = NULL;
1607 	const gchar *key;
1608 	const gchar *value;
1609 	gboolean ret = FALSE;
1610 	GBytes *data = NULL;
1611 	GList *l;
1612 	guint i;
1613 	g_autoptr(GList) md_keys = NULL;
1614 
1615 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
1616 
1617 	/* convert profile kind */
1618 	for (i = 0; map_profile_kind[i].colord != CD_PROFILE_KIND_LAST; i++) {
1619 		if (map_profile_kind[i].colord == priv->kind) {
1620 			cmsSetDeviceClass (priv->lcms_profile,
1621 					   map_profile_kind[i].lcms);
1622 			break;
1623 		}
1624 	}
1625 
1626 	/* convert colorspace */
1627 	for (i = 0; map_colorspace[i].colord != CD_COLORSPACE_LAST; i++) {
1628 		if (map_colorspace[i].colord == priv->colorspace) {
1629 			cmsSetColorSpace (priv->lcms_profile,
1630 					  map_colorspace[i].lcms);
1631 			break;
1632 		}
1633 	}
1634 
1635 	/* set version */
1636 	if (priv->version > 0.0)
1637 		cmsSetProfileVersion (priv->lcms_profile, priv->version);
1638 
1639 	/* save metadata */
1640 	if (g_hash_table_size (priv->metadata) != 0) {
1641 		dict = cmsDictAlloc (priv->context_lcms);
1642 		md_keys = g_hash_table_get_keys (priv->metadata);
1643 		if (md_keys != NULL) {
1644 			for (l = md_keys; l != NULL; l = l->next) {
1645 				key = l->data;
1646 				value = g_hash_table_lookup (priv->metadata, key);
1647 				ret = cd_util_write_dict_entry (dict, key,
1648 								value, error);
1649 				if (!ret)
1650 					goto out;
1651 			}
1652 		}
1653 		ret = cd_icc_write_tag (icc, cmsSigMetaTag, dict, error);
1654 		if (!ret)
1655 			goto out;
1656 	} else {
1657 		ret = cd_icc_write_tag (icc, cmsSigMetaTag, NULL, error);
1658 		if (!ret)
1659 			goto out;
1660 	}
1661 
1662 	/* save characterization data */
1663 	if (priv->characterization_data != NULL) {
1664 		ret = cd_util_write_tag_ascii (icc,
1665 					       cmsSigCharTargetTag,
1666 					       priv->characterization_data,
1667 					       error);
1668 		if (!ret)
1669 			goto out;
1670 	} else {
1671 		ret = cd_icc_write_tag (icc, cmsSigCharTargetTag, NULL, error);
1672 		if (!ret)
1673 			goto out;
1674 	}
1675 
1676 	/* save translations */
1677 	if (priv->version < 4.0) {
1678 		/* v2 profiles cannot have a mluc type for cmsSigProfileDescriptionTag
1679 		 * so use the non-standard Apple extension cmsSigProfileDescriptionTagML
1680 		 * and only write a en_US version for the description */
1681 		ret = cd_util_write_tag_ascii_default (icc,
1682 						       cmsSigProfileDescriptionTag,
1683 						       priv->mluc_data[CD_MLUC_DESCRIPTION],
1684 						       error);
1685 		if (!ret)
1686 			goto out;
1687 		ret = cd_util_write_tag_localized (icc,
1688 						   cmsSigProfileDescriptionMLTag,
1689 						   priv->mluc_data[CD_MLUC_DESCRIPTION],
1690 						   error);
1691 		if (!ret)
1692 			goto out;
1693 		ret = cd_util_write_tag_ascii_default (icc,
1694 						       cmsSigCopyrightTag,
1695 						       priv->mluc_data[CD_MLUC_COPYRIGHT],
1696 						       error);
1697 		if (!ret)
1698 			goto out;
1699 		ret = cd_util_write_tag_ascii_default (icc,
1700 						       cmsSigDeviceMfgDescTag,
1701 						       priv->mluc_data[CD_MLUC_MANUFACTURER],
1702 						       error);
1703 		if (!ret)
1704 			goto out;
1705 		ret = cd_util_write_tag_ascii_default (icc,
1706 						       cmsSigDeviceModelDescTag,
1707 						       priv->mluc_data[CD_MLUC_MODEL],
1708 						       error);
1709 		if (!ret)
1710 			goto out;
1711 	} else {
1712 		/* v4 profiles can use mluc types for all fields */
1713 		ret = cd_util_write_tag_localized (icc,
1714 						   cmsSigProfileDescriptionTag,
1715 						   priv->mluc_data[CD_MLUC_DESCRIPTION],
1716 						   error);
1717 		if (!ret)
1718 			goto out;
1719 		ret = cd_util_write_tag_localized (icc,
1720 						   cmsSigCopyrightTag,
1721 						   priv->mluc_data[CD_MLUC_COPYRIGHT],
1722 						   error);
1723 		if (!ret)
1724 			goto out;
1725 		ret = cd_util_write_tag_localized (icc,
1726 						   cmsSigDeviceMfgDescTag,
1727 						   priv->mluc_data[CD_MLUC_MANUFACTURER],
1728 						   error);
1729 		if (!ret)
1730 			goto out;
1731 		ret = cd_util_write_tag_localized (icc,
1732 						   cmsSigDeviceModelDescTag,
1733 						   priv->mluc_data[CD_MLUC_MODEL],
1734 						   error);
1735 		if (!ret)
1736 			goto out;
1737 	}
1738 
1739 	/* write profile id */
1740 	ret = cmsMD5computeID (priv->lcms_profile);
1741 	if (!ret) {
1742 		g_set_error_literal (error,
1743 				     CD_ICC_ERROR,
1744 				     CD_ICC_ERROR_FAILED_TO_SAVE,
1745 				     "failed to compute profile id");
1746 		goto out;
1747 	}
1748 	data = cd_icc_serialize_profile (icc, error);
1749 out:
1750 	if (dict != NULL)
1751 		cmsDictFree (dict);
1752 	return data;
1753 }
1754 
1755 /**
1756  * cd_icc_get_characterization_data:
1757  * @icc: a #CdIcc instance.
1758  *
1759  * Gets any characterization data used to build the profile.
1760  * This function will only return results if the profile was loaded with the
1761  * %CD_ICC_LOAD_FLAGS_CHARACTERIZATION flag.
1762  *
1763  * Return value: TI3 string data
1764  *
1765  * Since: 1.1.1
1766  **/
1767 const gchar *
cd_icc_get_characterization_data(CdIcc * icc)1768 cd_icc_get_characterization_data (CdIcc *icc)
1769 {
1770 	CdIccPrivate *priv = GET_PRIVATE (icc);
1771 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
1772 	return priv->characterization_data;
1773 }
1774 
1775 /**
1776  * cd_icc_set_characterization_data:
1777  * @icc: a #CdIcc instance.
1778  * @data: TI3 string data, or %NULL
1779  *
1780  * Sets the characterization data used to build the profile.
1781  *
1782  * Since: 1.1.1
1783  **/
1784 void
cd_icc_set_characterization_data(CdIcc * icc,const gchar * data)1785 cd_icc_set_characterization_data (CdIcc *icc, const gchar *data)
1786 {
1787 	CdIccPrivate *priv = GET_PRIVATE (icc);
1788 	g_return_if_fail (CD_IS_ICC (icc));
1789 	g_free (priv->characterization_data);
1790 	priv->characterization_data = g_strdup (data);
1791 }
1792 
1793 /**
1794  * cd_icc_save_file:
1795  * @icc: a #CdIcc instance.
1796  * @file: a #GFile
1797  * @flags: a set of #CdIccSaveFlags
1798  * @cancellable: A #GCancellable or %NULL
1799  * @error: A #GError or %NULL
1800  *
1801  * Saves an ICC profile to a local or remote file.
1802  *
1803  * Return vale: %TRUE for success.
1804  *
1805  * Since: 0.1.32
1806  **/
1807 gboolean
cd_icc_save_file(CdIcc * icc,GFile * file,CdIccSaveFlags flags,GCancellable * cancellable,GError ** error)1808 cd_icc_save_file (CdIcc *icc,
1809 		  GFile *file,
1810 		  CdIccSaveFlags flags,
1811 		  GCancellable *cancellable,
1812 		  GError **error)
1813 {
1814 	gboolean ret;
1815 	g_autoptr(GBytes) data = NULL;
1816 	g_autoptr(GError) error_local = NULL;
1817 
1818 	g_return_val_if_fail (CD_IS_ICC (icc), FALSE);
1819 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
1820 
1821 	/* get data */
1822 	data = cd_icc_save_data (icc, flags, error);
1823 	if (data == NULL)
1824 		return FALSE;
1825 
1826 	/* ensure parent directories exist */
1827 	if (!cd_icc_save_file_mkdir_parents (file, error))
1828 		return FALSE;
1829 
1830 	/* actually write file */
1831 	ret = g_file_replace_contents (file,
1832 				       g_bytes_get_data (data, NULL),
1833 				       g_bytes_get_size (data),
1834 				       NULL,
1835 				       FALSE,
1836 				       G_FILE_CREATE_NONE,
1837 				       NULL,
1838 				       cancellable,
1839 				       &error_local);
1840 	if (!ret) {
1841 		g_set_error (error,
1842 			     CD_ICC_ERROR,
1843 			     CD_ICC_ERROR_FAILED_TO_SAVE,
1844 			     "failed to save ICC file: %s",
1845 			     error_local->message);
1846 		return FALSE;
1847 	}
1848 	return TRUE;
1849 }
1850 
1851 /**
1852  * cd_icc_save_default:
1853  * @icc: a #CdIcc instance.
1854  * @flags: a set of #CdIccSaveFlags
1855  * @cancellable: A #GCancellable or %NULL
1856  * @error: A #GError or %NULL
1857  *
1858  * Saves an ICC profile to the default per-user location.
1859  *
1860  * Return vale: %TRUE for success.
1861  *
1862  * Since: 1.1.1
1863  **/
1864 gboolean
cd_icc_save_default(CdIcc * icc,CdIccSaveFlags flags,GCancellable * cancellable,GError ** error)1865 cd_icc_save_default (CdIcc *icc,
1866 		     CdIccSaveFlags flags,
1867 		     GCancellable *cancellable,
1868 		     GError **error)
1869 {
1870 	CdIccPrivate *priv = GET_PRIVATE (icc);
1871 	const gchar *root = "edid"; /* TODO: only for cd_icc_create_from_edid() */
1872 	g_autofree gchar *basename = NULL;
1873 	g_autofree gchar *filename = NULL;
1874 	g_autoptr(GFile) file = NULL;
1875 
1876 	g_return_val_if_fail (CD_IS_ICC (icc), FALSE);
1877 
1878 	/* build a per-user filename */
1879 	basename = g_strdup_printf ("%s-%s.icc", root, priv->checksum);
1880 	filename = g_build_filename (g_get_user_data_dir (), "icc", basename, NULL);
1881 	file = g_file_new_for_path (filename);
1882 	return cd_icc_save_file (icc, file, flags, cancellable, error);
1883 }
1884 
1885 /**
1886  * cd_icc_set_filename:
1887  * @icc: a #CdIcc instance.
1888  * @filename: a filename, or %NULL
1889  *
1890  * Sets the filename, which may be required if the ICC profile has been loaded
1891  * using cd_icc_load_fd() from a disk cache.
1892  *
1893  * Since: 1.1.1
1894  **/
1895 void
cd_icc_set_filename(CdIcc * icc,const gchar * filename)1896 cd_icc_set_filename (CdIcc *icc, const gchar *filename)
1897 {
1898 	CdIccPrivate *priv = GET_PRIVATE (icc);
1899 	g_free (priv->filename);
1900 	priv->filename = g_strdup (filename);
1901 }
1902 
1903 /**
1904  * cd_icc_load_file:
1905  * @icc: a #CdIcc instance.
1906  * @file: a #GFile
1907  * @flags: a set of #CdIccLoadFlags
1908  * @cancellable: A #GCancellable or %NULL
1909  * @error: A #GError or %NULL
1910  *
1911  * Loads an ICC profile from a local or remote file.
1912  *
1913  * Since: 0.1.32
1914  **/
1915 gboolean
cd_icc_load_file(CdIcc * icc,GFile * file,CdIccLoadFlags flags,GCancellable * cancellable,GError ** error)1916 cd_icc_load_file (CdIcc *icc,
1917 		  GFile *file,
1918 		  CdIccLoadFlags flags,
1919 		  GCancellable *cancellable,
1920 		  GError **error)
1921 {
1922 	CdIccPrivate *priv = GET_PRIVATE (icc);
1923 	gboolean ret = FALSE;
1924 	gsize length;
1925 	g_autoptr(GError) error_local = NULL;
1926 	g_autofree gchar *data = NULL;
1927 	g_autoptr(GFileInfo) info = NULL;
1928 
1929 	g_return_val_if_fail (CD_IS_ICC (icc), FALSE);
1930 	g_return_val_if_fail (G_IS_FILE (file), FALSE);
1931 
1932 	/* load files */
1933 	ret = g_file_load_contents (file, cancellable, &data, &length,
1934 				    NULL, &error_local);
1935 	if (!ret) {
1936 		g_set_error (error,
1937 			     CD_ICC_ERROR,
1938 			     CD_ICC_ERROR_FAILED_TO_OPEN,
1939 			     "failed to load file: %s",
1940 			     error_local->message);
1941 		return FALSE;
1942 	}
1943 
1944 	/* parse the data */
1945 	ret = cd_icc_load_data (icc,
1946 				(const guint8 *) data,
1947 				length,
1948 				flags,
1949 				error);
1950 	if (!ret)
1951 		return FALSE;
1952 
1953 	/* find out if the user could delete this profile */
1954 	info = g_file_query_info (file,
1955 				  G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE,
1956 				  G_FILE_QUERY_INFO_NONE,
1957 				  cancellable,
1958 				  &error_local);
1959 	if (info == NULL) {
1960 		g_set_error (error,
1961 			     CD_ICC_ERROR,
1962 			     CD_ICC_ERROR_FAILED_TO_OPEN,
1963 			     "failed to query file: %s",
1964 			     error_local->message);
1965 		return FALSE;
1966 	}
1967 	priv->can_delete = g_file_info_get_attribute_boolean (info,
1968 							      G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE);
1969 
1970 	/* save filename for later */
1971 	priv->filename = g_file_get_path (file);
1972 	return TRUE;
1973 }
1974 
1975 /**
1976  * cd_icc_load_fd:
1977  * @icc: a #CdIcc instance.
1978  * @fd: a file descriptor
1979  * @flags: a set of #CdIccLoadFlags
1980  * @error: A #GError or %NULL
1981  *
1982  * Loads an ICC profile from an open file descriptor.
1983  *
1984  * Since: 0.1.32
1985  **/
1986 gboolean
cd_icc_load_fd(CdIcc * icc,gint fd,CdIccLoadFlags flags,GError ** error)1987 cd_icc_load_fd (CdIcc *icc,
1988 		gint fd,
1989 		CdIccLoadFlags flags,
1990 		GError **error)
1991 {
1992 	CdIccPrivate *priv = GET_PRIVATE (icc);
1993 	FILE *stream = NULL;
1994 
1995 	g_return_val_if_fail (CD_IS_ICC (icc), FALSE);
1996 	g_return_val_if_fail (fd > 0, FALSE);
1997 
1998 	/* convert the file descriptor to a stream */
1999 	stream = fdopen (fd, "r");
2000 	if (stream == NULL) {
2001 		g_set_error (error,
2002 			     CD_ICC_ERROR,
2003 			     CD_ICC_ERROR_FAILED_TO_OPEN,
2004 			     "failed to open stream from fd %i",
2005 			     fd);
2006 		return FALSE;
2007 	}
2008 
2009 	/* parse the ICC file */
2010 	priv->lcms_profile = cmsOpenProfileFromStreamTHR (priv->context_lcms, stream, "r");
2011 	if (priv->lcms_profile == NULL) {
2012 		g_set_error_literal (error,
2013 				     CD_ICC_ERROR,
2014 				     CD_ICC_ERROR_FAILED_TO_OPEN,
2015 				     "failed to open stream");
2016 		return FALSE;
2017 	}
2018 
2019 	/* load cached data */
2020 	return cd_icc_load (icc, flags, error);
2021 }
2022 
2023 /**
2024  * cd_icc_get_handle:
2025  * @icc: a #CdIcc instance.
2026  *
2027  * Return the cmsHPROFILE instance used locally. This may be required if you
2028  * are using the profile in a transform.
2029  *
2030  * Return value: (transfer none): Do not call cmsCloseProfile() on this value!
2031  **/
2032 gpointer
cd_icc_get_handle(CdIcc * icc)2033 cd_icc_get_handle (CdIcc *icc)
2034 {
2035 	CdIccPrivate *priv = GET_PRIVATE (icc);
2036 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2037 	return priv->lcms_profile;
2038 }
2039 
2040 /**
2041  * cd_icc_get_context:
2042  * @icc: a #CdIcc instance.
2043  *
2044  * Return the cmsContext instance used locally. This may be required if you
2045  * are using native LCMS calls and then cd_icc_load_handle().
2046  *
2047  * Return value: (transfer none): Do not call cmsDeleteContext() on this value!
2048  *
2049  * Since: 1.1.7
2050  **/
2051 gpointer
cd_icc_get_context(CdIcc * icc)2052 cd_icc_get_context (CdIcc *icc)
2053 {
2054 	CdIccPrivate *priv = GET_PRIVATE (icc);
2055 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2056 	return priv->context_lcms;
2057 }
2058 
2059 /**
2060  * cd_icc_load_handle:
2061  * @icc: a #CdIcc instance.
2062  * @handle: a cmsHPROFILE instance
2063  * @flags: a set of #CdIccLoadFlags
2064  * @error: A #GError or %NULL
2065  *
2066  * Set the internal cmsHPROFILE instance. This may be required if you create
2067  * the profile using cmsCreateRGBProfileTHR() and then want to use the
2068  * functionality in #CdIcc.
2069  *
2070  * Do not call cmsCloseProfile() on @handle in the caller, this will be done
2071  * when the @icc object is finalized. Treat the profile like it's been adopted
2072  * by this module.
2073  *
2074  * To handle the internal error callback, you should use the thread-safe
2075  * creation function, e.g. cmsCreateNULLProfileTHR(). The @context_id should be
2076  * set as the value of cd_icc_get_context() for this object.
2077  *
2078  * Additionally, this function cannot be called more than once, and also can't
2079  * be called if cd_icc_load_file() has previously been used on the @icc object.
2080  *
2081  * Since: 0.1.33
2082  **/
2083 gboolean
cd_icc_load_handle(CdIcc * icc,gpointer handle,CdIccLoadFlags flags,GError ** error)2084 cd_icc_load_handle (CdIcc *icc,
2085 		    gpointer handle,
2086 		    CdIccLoadFlags flags,
2087 		    GError **error)
2088 {
2089 	CdIccPrivate *priv = GET_PRIVATE (icc);
2090 	cmsContext context;
2091 
2092 	g_return_val_if_fail (CD_IS_ICC (icc), FALSE);
2093 	g_return_val_if_fail (handle != NULL, FALSE);
2094 	g_return_val_if_fail (priv->lcms_profile == NULL, FALSE);
2095 
2096 	/* check the THR version has been correctly set up */
2097 	context = cmsGetProfileContextID (handle);
2098 	if (context == NULL) {
2099 		g_set_error_literal (error,
2100 				     CD_ICC_ERROR,
2101 				     CD_ICC_ERROR_FAILED_TO_CREATE,
2102 				     "lcms2 threadsafe version (THR) not used, "
2103 				     "or context not set");
2104 		return FALSE;
2105 	}
2106 
2107 	/* load profile */
2108 	priv->lcms_profile = handle;
2109 	return cd_icc_load (icc, flags, error);
2110 }
2111 
2112 /**
2113  * cd_icc_get_size:
2114  *
2115  * Gets the ICC profile file size
2116  *
2117  * Return value: The size in bytes, or 0 for unknown.
2118  *
2119  * Since: 0.1.32
2120  **/
2121 guint32
cd_icc_get_size(CdIcc * icc)2122 cd_icc_get_size (CdIcc *icc)
2123 {
2124 	CdIccPrivate *priv = GET_PRIVATE (icc);
2125 	g_return_val_if_fail (CD_IS_ICC (icc), 0);
2126 	return priv->size;
2127 }
2128 
2129 /**
2130  * cd_icc_get_filename:
2131  * @icc: A valid #CdIcc
2132  *
2133  * Gets the filename of the ICC data, if one exists.
2134  *
2135  * Return value: A filename, or %NULL
2136  *
2137  * Since: 0.1.32
2138  **/
2139 const gchar *
cd_icc_get_filename(CdIcc * icc)2140 cd_icc_get_filename (CdIcc *icc)
2141 {
2142 	CdIccPrivate *priv = GET_PRIVATE (icc);
2143 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2144 	return priv->filename;
2145 }
2146 
2147 /**
2148  * cd_icc_get_version:
2149  * @icc: a #CdIcc instance.
2150  *
2151  * Gets the ICC profile version, typically 2.1 or 4.2
2152  *
2153  * Return value: A floating point version number, or 0.0 for unknown
2154  *
2155  * Since: 0.1.32
2156  **/
2157 gdouble
cd_icc_get_version(CdIcc * icc)2158 cd_icc_get_version (CdIcc *icc)
2159 {
2160 	CdIccPrivate *priv = GET_PRIVATE (icc);
2161 	g_return_val_if_fail (CD_IS_ICC (icc), 0.0f);
2162 	return priv->version;
2163 }
2164 
2165 /**
2166  * cd_icc_set_version:
2167  * @icc: a #CdIcc instance.
2168  * @version: the profile version, e.g. 2.1 or 4.0
2169  *
2170  * Sets the profile version.
2171  *
2172  * Since: 0.1.32
2173  **/
2174 void
cd_icc_set_version(CdIcc * icc,gdouble version)2175 cd_icc_set_version (CdIcc *icc, gdouble version)
2176 {
2177 	CdIccPrivate *priv = GET_PRIVATE (icc);
2178 	g_return_if_fail (CD_IS_ICC (icc));
2179 	priv->version = version;
2180 	g_object_notify (G_OBJECT (icc), "version");
2181 }
2182 
2183 /**
2184  * cd_icc_get_kind:
2185  * @icc: a #CdIcc instance.
2186  *
2187  * Gets the profile kind.
2188  *
2189  * Return value: The kind, e.g. %CD_PROFILE_KIND_INPUT
2190  *
2191  * Since: 0.1.32
2192  **/
2193 CdProfileKind
cd_icc_get_kind(CdIcc * icc)2194 cd_icc_get_kind (CdIcc *icc)
2195 {
2196 	CdIccPrivate *priv = GET_PRIVATE (icc);
2197 	g_return_val_if_fail (CD_IS_ICC (icc), CD_PROFILE_KIND_UNKNOWN);
2198 	return priv->kind;
2199 }
2200 
2201 /**
2202  * cd_icc_set_kind:
2203  * @icc: a #CdIcc instance.
2204  * @kind: the profile kind, e.g. %CD_PROFILE_KIND_DISPLAY_DEVICE
2205  *
2206  * Sets the profile kind.
2207  *
2208  * Since: 0.1.32
2209  **/
2210 void
cd_icc_set_kind(CdIcc * icc,CdProfileKind kind)2211 cd_icc_set_kind (CdIcc *icc, CdProfileKind kind)
2212 {
2213 	CdIccPrivate *priv = GET_PRIVATE (icc);
2214 	g_return_if_fail (CD_IS_ICC (icc));
2215 	priv->kind = kind;
2216 	g_object_notify (G_OBJECT (icc), "kind");
2217 }
2218 
2219 /**
2220  * cd_icc_get_colorspace:
2221  * @icc: a #CdIcc instance.
2222  *
2223  * Gets the profile colorspace
2224  *
2225  * Return value: The profile colorspace, e.g. %CD_COLORSPACE_RGB
2226  *
2227  * Since: 0.1.32
2228  **/
2229 CdColorspace
cd_icc_get_colorspace(CdIcc * icc)2230 cd_icc_get_colorspace (CdIcc *icc)
2231 {
2232 	CdIccPrivate *priv = GET_PRIVATE (icc);
2233 	g_return_val_if_fail (CD_IS_ICC (icc), CD_COLORSPACE_UNKNOWN);
2234 	return priv->colorspace;
2235 }
2236 
2237 /**
2238  * cd_icc_set_colorspace:
2239  * @icc: a #CdIcc instance.
2240  * @colorspace: the profile colorspace, e.g. %CD_COLORSPACE_RGB
2241  *
2242  * Sets the colorspace kind.
2243  *
2244  * Since: 0.1.32
2245  **/
2246 void
cd_icc_set_colorspace(CdIcc * icc,CdColorspace colorspace)2247 cd_icc_set_colorspace (CdIcc *icc, CdColorspace colorspace)
2248 {
2249 	CdIccPrivate *priv = GET_PRIVATE (icc);
2250 	g_return_if_fail (CD_IS_ICC (icc));
2251 	priv->colorspace = colorspace;
2252 	g_object_notify (G_OBJECT (icc), "colorspace");
2253 }
2254 
2255 /**
2256  * cd_icc_get_metadata:
2257  * @icc: A valid #CdIcc
2258  *
2259  * Gets all the metadata from the ICC profile.
2260  *
2261  * Return value: (transfer container): The profile metadata
2262  *
2263  * Since: 0.1.32
2264  **/
2265 GHashTable *
cd_icc_get_metadata(CdIcc * icc)2266 cd_icc_get_metadata (CdIcc *icc)
2267 {
2268 	CdIccPrivate *priv = GET_PRIVATE (icc);
2269 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2270 	return g_hash_table_ref (priv->metadata);
2271 }
2272 
2273 /**
2274  * cd_icc_get_metadata_item:
2275  * @icc: A valid #CdIcc
2276  * @key: the dictionary key
2277  *
2278  * Gets an item of data from the ICC metadata store.
2279  *
2280  * Return value: The dictionary data, or %NULL if the key does not exist.
2281  *
2282  * Since: 0.1.32
2283  **/
2284 const gchar *
cd_icc_get_metadata_item(CdIcc * icc,const gchar * key)2285 cd_icc_get_metadata_item (CdIcc *icc, const gchar *key)
2286 {
2287 	CdIccPrivate *priv = GET_PRIVATE (icc);
2288 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2289 	g_return_val_if_fail (key != NULL, NULL);
2290 	return (const gchar *) g_hash_table_lookup (priv->metadata, key);
2291 }
2292 
2293 /**
2294  * cd_icc_add_metadata:
2295  * @icc: A valid #CdIcc
2296  * @key: the metadata key
2297  * @value: the UTF-8 metadata value
2298  *
2299  * Sets an item of data to the profile metadata, overwriting it if
2300  * it already exists.
2301  *
2302  * Since: 0.1.32
2303  **/
2304 void
cd_icc_add_metadata(CdIcc * icc,const gchar * key,const gchar * value)2305 cd_icc_add_metadata (CdIcc *icc, const gchar *key, const gchar *value)
2306 {
2307 	CdIccPrivate *priv = GET_PRIVATE (icc);
2308 	g_return_if_fail (CD_IS_ICC (icc));
2309 	g_return_if_fail (key != NULL);
2310 	g_return_if_fail (g_utf8_validate (key, -1, NULL));
2311 	g_return_if_fail (value != NULL);
2312 	g_return_if_fail (g_utf8_validate (value, -1, NULL));
2313 	g_hash_table_insert (priv->metadata,
2314 			     g_strdup (key),
2315 			     g_strdup (value));
2316 }
2317 
2318 /**
2319  * cd_icc_remove_metadata:
2320  * @icc: A valid #CdIcc
2321  * @key: the metadata key
2322  *
2323  * Removes an item of metadata.
2324  *
2325  * Since: 0.1.32
2326  **/
2327 void
cd_icc_remove_metadata(CdIcc * icc,const gchar * key)2328 cd_icc_remove_metadata (CdIcc *icc, const gchar *key)
2329 {
2330 	CdIccPrivate *priv = GET_PRIVATE (icc);
2331 	g_return_if_fail (CD_IS_ICC (icc));
2332 	g_return_if_fail (key != NULL);
2333 	g_hash_table_remove (priv->metadata, key);
2334 }
2335 
2336 /**
2337  * cd_icc_load_named_colors:
2338  **/
2339 static gboolean
cd_icc_load_named_colors(CdIcc * icc,GError ** error)2340 cd_icc_load_named_colors (CdIcc *icc, GError **error)
2341 {
2342 	CdIccPrivate *priv = GET_PRIVATE (icc);
2343 	CdColorLab lab;
2344 	CdColorSwatch *swatch;
2345 	cmsNAMEDCOLORLIST *nc2;
2346 	cmsUInt16Number pcs[3];
2347 	gboolean ret = TRUE;
2348 	gchar name[cmsMAX_PATH];
2349 	gchar prefix[33];
2350 	gchar suffix[33];
2351 	GError *error_local = NULL;
2352 	GString *string;
2353 	guint j;
2354 	guint size;
2355 
2356 	/* do any named colors exist? */
2357 	nc2 = cd_icc_read_tag (icc, cmsSigNamedColor2Type, &error_local);
2358 	if (nc2 == NULL) {
2359 		/* no data is okay */
2360 		if (g_error_matches (error_local,
2361 				     CD_ICC_ERROR,
2362 				     CD_ICC_ERROR_NO_DATA)) {
2363 			g_error_free (error_local);
2364 			return TRUE;
2365 		}
2366 		g_propagate_error (error, error_local);
2367 		return FALSE;
2368 	}
2369 
2370 	/* get each NC */
2371 	size = cmsNamedColorCount (nc2);
2372 	for (j = 0; j < size; j++) {
2373 
2374 		/* parse title */
2375 		ret = cmsNamedColorInfo (nc2, j,
2376 					 name,
2377 					 prefix,
2378 					 suffix,
2379 					 (cmsUInt16Number *) &pcs,
2380 					 NULL);
2381 		if (!ret)
2382 			continue;
2383 		string = g_string_new ("");
2384 		if (prefix[0] != '\0')
2385 			g_string_append_printf (string, "%s ", prefix);
2386 		g_string_append (string, name);
2387 		if (suffix[0] != '\0')
2388 			g_string_append_printf (string, " %s", suffix);
2389 
2390 		/* check is valid */
2391 		ret = g_utf8_validate (string->str, string->len, NULL);
2392 		if (!ret)
2393 			ret = cd_icc_fix_utf8_string (string);
2394 
2395 		/* save color if valid */
2396 		if (ret) {
2397 			cmsLabEncoded2Float ((cmsCIELab *) &lab, pcs);
2398 			swatch = cd_color_swatch_new ();
2399 			cd_color_swatch_set_name (swatch, string->str);
2400 			cd_color_swatch_set_value (swatch, (const CdColorLab *) &lab);
2401 			g_ptr_array_add (priv->named_colors, swatch);
2402 		}
2403 		g_string_free (string, TRUE);
2404 	}
2405 	return TRUE;
2406 }
2407 
2408 /**
2409  * cd_icc_get_named_colors:
2410  * @icc: a #CdIcc instance.
2411  *
2412  * Gets any named colors in the profile.
2413  * This function will only return results if the profile was loaded with the
2414  * %CD_ICC_LOAD_FLAGS_NAMED_COLORS flag.
2415  *
2416  * Return value: (transfer container) (element-type CdColorSwatch): An array of color swatches
2417  *
2418  * Since: 0.1.32
2419  **/
2420 GPtrArray *
cd_icc_get_named_colors(CdIcc * icc)2421 cd_icc_get_named_colors (CdIcc *icc)
2422 {
2423 	CdIccPrivate *priv = GET_PRIVATE (icc);
2424 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2425 	return g_ptr_array_ref (priv->named_colors);
2426 }
2427 
2428 /**
2429  * cd_icc_get_can_delete:
2430  * @icc: a #CdIcc instance.
2431  *
2432  * Finds out if the profile could be deleted.
2433  * This is only applicable for profiles loaded with cd_icc_load_file() as
2434  * obviously data and fd's cannot be sanely unlinked.
2435  *
2436  * Return value: %TRUE if g_file_delete() would likely work
2437  *
2438  * Since: 0.1.32
2439  **/
2440 gboolean
cd_icc_get_can_delete(CdIcc * icc)2441 cd_icc_get_can_delete (CdIcc *icc)
2442 {
2443 	CdIccPrivate *priv = GET_PRIVATE (icc);
2444 	g_return_val_if_fail (CD_IS_ICC (icc), FALSE);
2445 	return priv->can_delete;
2446 }
2447 
2448 /**
2449  * cd_icc_get_created:
2450  * @icc: A valid #CdIcc
2451  *
2452  * Gets the ICC creation date and time.
2453  *
2454  * Return value: A #GDateTime object, or %NULL for not set
2455  *
2456  * Since: 0.1.32
2457  **/
2458 GDateTime *
cd_icc_get_created(CdIcc * icc)2459 cd_icc_get_created (CdIcc *icc)
2460 {
2461 	CdIccPrivate *priv = GET_PRIVATE (icc);
2462 	struct tm created_tm;
2463 	time_t created_t;
2464 
2465 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2466 
2467 	/* get the profile creation time and date */
2468 	if (!cmsGetHeaderCreationDateTime (priv->lcms_profile, &created_tm))
2469 		return NULL;
2470 
2471 	created_tm.tm_isdst = -1;
2472 
2473 	/* convert to UNIX time */
2474 	created_t = mktime (&created_tm);
2475 	if (created_t == (time_t) -1)
2476 		return NULL;
2477 
2478 	/* instantiate object */
2479 	return g_date_time_new_from_unix_local (created_t);
2480 }
2481 
2482 /**
2483  * cd_icc_get_checksum:
2484  * @icc: A valid #CdIcc
2485  *
2486  * Gets the profile checksum if one exists.
2487  * This will either be the embedded profile ID, or the file checksum if
2488  * the #CdIcc object was loaded using cd_icc_load_data() or cd_icc_load_file()
2489  * and the %CD_ICC_LOAD_FLAGS_FALLBACK_MD5 flag is used.
2490  *
2491  * Return value: An embedded MD5 checksum, or %NULL for not set
2492  *
2493  * Since: 0.1.32
2494  **/
2495 const gchar *
cd_icc_get_checksum(CdIcc * icc)2496 cd_icc_get_checksum (CdIcc *icc)
2497 {
2498 	CdIccPrivate *priv = GET_PRIVATE (icc);
2499 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2500 	return priv->checksum;
2501 }
2502 
2503 /**
2504  * cd_icc_get_locale_key:
2505  **/
2506 static gchar *
cd_icc_get_locale_key(const gchar * locale)2507 cd_icc_get_locale_key (const gchar *locale)
2508 {
2509 	gchar *locale_key;
2510 
2511 	/* en_US is the default locale in an ICC profile */
2512 	if (locale == NULL || g_str_has_prefix (locale, "en_US"))
2513 		return g_strdup ("");
2514 	locale_key = g_strdup (locale);
2515 	g_strdelimit (locale_key, ".(", '\0');
2516 	return locale_key;
2517 }
2518 
2519 /**
2520  * cd_icc_get_mluc_data:
2521  **/
2522 static const gchar *
cd_icc_get_mluc_data(CdIcc * icc,const gchar * locale,CdIccMluc mluc,cmsTagSignature * sigs,GError ** error)2523 cd_icc_get_mluc_data (CdIcc *icc,
2524 		      const gchar *locale,
2525 		      CdIccMluc mluc,
2526 		      cmsTagSignature *sigs,
2527 		      GError **error)
2528 {
2529 	CdIccPrivate *priv = GET_PRIVATE (icc);
2530 	cmsMLU *mlu = NULL;
2531 	const gchar *country_code = "\0\0\0";
2532 	const gchar *language_code = "\0\0\0";
2533 	const gchar *value;
2534 	gchar *tmp;
2535 	guint32 text_size;
2536 	guint i;
2537 	g_autofree gchar *locale_key = NULL;
2538 	g_autofree gchar *text_buffer = NULL;
2539 	g_autofree gunichar *wtext = NULL;
2540 
2541 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2542 
2543 	/* does cache entry exist already? */
2544 	locale_key = cd_icc_get_locale_key (locale);
2545 	value = g_hash_table_lookup (priv->mluc_data[mluc], locale_key);
2546 	if (value != NULL)
2547 		goto out;
2548 
2549 	/* convert the locale into something we can use as a key, in this case
2550 	 * 'en_GB.UTF-8' -> 'en_GB'
2551 	 * 'fr'          -> 'fr' */
2552 	if (locale_key[0] != '\0') {
2553 
2554 		/* decompose it into language and country codes */
2555 		tmp = g_strstr_len (locale_key, -1, "_");
2556 		language_code = locale_key;
2557 		if (tmp != NULL) {
2558 			country_code = tmp + 1;
2559 			*tmp = '\0';
2560 		}
2561 
2562 		/* check the format is correct */
2563 		if (strlen (language_code) != 2) {
2564 			g_set_error (error,
2565 				     CD_ICC_ERROR,
2566 				     CD_ICC_ERROR_INVALID_LOCALE,
2567 				     "invalid locale: %s", locale);
2568 			goto out;
2569 		}
2570 		if (country_code != NULL &&
2571 		    country_code[0] != '\0' &&
2572 		    strlen (country_code) != 2) {
2573 			g_set_error (error,
2574 				     CD_ICC_ERROR,
2575 				     CD_ICC_ERROR_INVALID_LOCALE,
2576 				     "invalid locale: %s", locale);
2577 			goto out;
2578 		}
2579 	} else {
2580 		/* lcms maps this to 'default' */
2581 		language_code = "en";
2582 		country_code = "US";
2583 	}
2584 
2585 	/* read each MLU entry in order of preference */
2586 	for (i = 0; sigs[i] != 0; i++) {
2587 		mlu = cd_icc_read_tag (icc, sigs[i], NULL);
2588 		if (mlu != NULL)
2589 			break;
2590 	}
2591 	if (mlu == NULL) {
2592 		g_set_error_literal (error,
2593 				     CD_ICC_ERROR,
2594 				     CD_ICC_ERROR_NO_DATA,
2595 				     "cmsSigProfile*Tag mising");
2596 		goto out;
2597 	}
2598 
2599 	/* get required size for wide chars */
2600 	text_size = cmsMLUgetWide (mlu,
2601 				   language_code,
2602 				   country_code,
2603 				   NULL,
2604 				   0);
2605 	if (text_size == 0)
2606 		goto out;
2607 
2608 	/* load wide chars */
2609 	wtext = g_new (gunichar, text_size);
2610 	text_size = cmsMLUgetWide (mlu,
2611 				   language_code,
2612 				   country_code,
2613 				   (wchar_t *) wtext,
2614 				   text_size);
2615 	if (text_size == 0)
2616 		goto out;
2617 
2618 	/* insert UTF-8 value into locale cache */
2619 	text_buffer = g_ucs4_to_utf8 (wtext, -1, NULL, NULL, error);
2620 	if (text_buffer == NULL)
2621 		goto out;
2622 	tmp = g_strdup (text_buffer);
2623 	g_hash_table_insert (priv->mluc_data[mluc],
2624 			     g_strdup (locale_key),
2625 			     tmp);
2626 	value = tmp;
2627 out:
2628 	return value;
2629 }
2630 
2631 /**
2632  * cd_icc_get_description:
2633  * @icc: A valid #CdIcc
2634  * @locale: A locale, e.g. "en_GB.UTF-8" or %NULL for the profile default
2635  * @error: A #GError or %NULL
2636  *
2637  * Gets the profile description.
2638  * If the translated text is not available in the selected locale then the
2639  * default untranslated (en_US) text is returned.
2640  *
2641  * Return value: The text as a UTF-8 string, or %NULL of the locale is invalid
2642  *               or the tag does not exist.
2643  *
2644  * Since: 0.1.32
2645  **/
2646 const gchar *
cd_icc_get_description(CdIcc * icc,const gchar * locale,GError ** error)2647 cd_icc_get_description (CdIcc *icc, const gchar *locale, GError **error)
2648 {
2649 	cmsTagSignature sigs[] = { cmsSigProfileDescriptionMLTag,
2650 				   cmsSigProfileDescriptionTag,
2651 				   0 };
2652 	return cd_icc_get_mluc_data (icc,
2653 				     locale,
2654 				     CD_MLUC_DESCRIPTION,
2655 				     sigs,
2656 				     error);
2657 }
2658 
2659 /**
2660  * cd_icc_get_copyright:
2661  * @icc: A valid #CdIcc
2662  * @locale: A locale, e.g. "en_GB.UTF-8" or %NULL for the profile default
2663  * @error: A #GError or %NULL
2664  *
2665  * Gets the profile copyright.
2666  * If the translated text is not available in the selected locale then the
2667  * default untranslated (en_US) text is returned.
2668  *
2669  * Return value: The text as a UTF-8 string, or %NULL of the locale is invalid
2670  *               or the tag does not exist.
2671  *
2672  * Since: 0.1.32
2673  **/
2674 const gchar *
cd_icc_get_copyright(CdIcc * icc,const gchar * locale,GError ** error)2675 cd_icc_get_copyright (CdIcc *icc, const gchar *locale, GError **error)
2676 {
2677 	cmsTagSignature sigs[] = { cmsSigCopyrightTag, 0 };
2678 	return cd_icc_get_mluc_data (icc,
2679 				     locale,
2680 				     CD_MLUC_COPYRIGHT,
2681 				     sigs,
2682 				     error);
2683 }
2684 
2685 /**
2686  * cd_icc_get_manufacturer:
2687  * @icc: A valid #CdIcc
2688  * @locale: A locale, e.g. "en_GB.UTF-8" or %NULL for the profile default
2689  * @error: A #GError or %NULL
2690  *
2691  * Gets the profile manufacturer.
2692  * If the translated text is not available in the selected locale then the
2693  * default untranslated (en_US) text is returned.
2694  *
2695  * Return value: The text as a UTF-8 string, or %NULL of the locale is invalid
2696  *               or the tag does not exist.
2697  *
2698  * Since: 0.1.32
2699  **/
2700 const gchar *
cd_icc_get_manufacturer(CdIcc * icc,const gchar * locale,GError ** error)2701 cd_icc_get_manufacturer (CdIcc *icc, const gchar *locale, GError **error)
2702 {
2703 	cmsTagSignature sigs[] = { cmsSigDeviceMfgDescTag, 0 };
2704 	return cd_icc_get_mluc_data (icc,
2705 				     locale,
2706 				     CD_MLUC_MANUFACTURER,
2707 				     sigs,
2708 				     error);
2709 }
2710 
2711 /**
2712  * cd_icc_get_model:
2713  * @icc: A valid #CdIcc
2714  * @locale: A locale, e.g. "en_GB.UTF-8" or %NULL for the profile default
2715  * @error: A #GError or %NULL
2716  *
2717  * Gets the profile model.
2718  * If the translated text is not available in the selected locale then the
2719  * default untranslated (en_US) text is returned.
2720  *
2721  * Return value: The text as a UTF-8 string, or %NULL of the locale is invalid
2722  *               or the tag does not exist.
2723  *
2724  * Since: 0.1.32
2725  **/
2726 const gchar *
cd_icc_get_model(CdIcc * icc,const gchar * locale,GError ** error)2727 cd_icc_get_model (CdIcc *icc, const gchar *locale, GError **error)
2728 {
2729 	cmsTagSignature sigs[] = { cmsSigDeviceModelDescTag, 0 };
2730 	return cd_icc_get_mluc_data (icc,
2731 				     locale,
2732 				     CD_MLUC_MODEL,
2733 				     sigs,
2734 				     error);
2735 }
2736 
2737 /**
2738  * cd_icc_set_description:
2739  * @icc: A valid #CdIcc
2740  * @locale: A locale, e.g. "en_GB.UTF-8" or %NULL for the profile default
2741  * @value: (allow-none): New UTF-8 string value
2742  *
2743  * Sets the profile description for a specific locale.
2744  *
2745  * Since: 0.1.32
2746  **/
2747 void
cd_icc_set_description(CdIcc * icc,const gchar * locale,const gchar * value)2748 cd_icc_set_description (CdIcc *icc, const gchar *locale, const gchar *value)
2749 {
2750 	CdIccPrivate *priv = GET_PRIVATE (icc);
2751 	g_return_if_fail (value == NULL || g_utf8_validate (value, -1, NULL));
2752 	g_hash_table_insert (priv->mluc_data[CD_MLUC_DESCRIPTION],
2753 			     cd_icc_get_locale_key (locale),
2754 			     g_strdup (value));
2755 }
2756 
2757 /**
2758  * cd_icc_set_description_items:
2759  * @icc: A valid #CdIcc
2760  * @values: New translated values, with the key being the locale.
2761  *
2762  * Sets the profile descriptions for specific locales.
2763  *
2764  * Since: 0.1.32
2765  **/
2766 void
cd_icc_set_description_items(CdIcc * icc,GHashTable * values)2767 cd_icc_set_description_items (CdIcc *icc, GHashTable *values)
2768 {
2769 	GList *l;
2770 	const gchar *key;
2771 	const gchar *value;
2772 	g_autoptr(GList) keys = NULL;
2773 
2774 	g_return_if_fail (CD_IS_ICC (icc));
2775 
2776 	/* add each translation */
2777 	keys = g_hash_table_get_keys (values);
2778 	for (l = keys; l != NULL; l = l->next) {
2779 		key = l->data;
2780 		value = g_hash_table_lookup (values, key);
2781 		cd_icc_set_description (icc, key, value);
2782 	}
2783 }
2784 
2785 /**
2786  * cd_icc_set_copyright:
2787  * @icc: A valid #CdIcc
2788  * @locale: A locale, e.g. "en_GB.UTF-8" or %NULL for the profile default
2789  * @value: (allow-none): New UTF-8 string value
2790  *
2791  * Sets the profile _copyright for a specific locale.
2792  *
2793  * Since: 0.1.32
2794  **/
2795 void
cd_icc_set_copyright(CdIcc * icc,const gchar * locale,const gchar * value)2796 cd_icc_set_copyright (CdIcc *icc, const gchar *locale, const gchar *value)
2797 {
2798 	CdIccPrivate *priv = GET_PRIVATE (icc);
2799 	g_return_if_fail (value == NULL || g_utf8_validate (value, -1, NULL));
2800 	g_hash_table_insert (priv->mluc_data[CD_MLUC_COPYRIGHT],
2801 			     cd_icc_get_locale_key (locale),
2802 			     g_strdup (value));
2803 }
2804 
2805 /**
2806  * cd_icc_set_copyright_items:
2807  * @icc: A valid #CdIcc
2808  * @values: New translated values, with the key being the locale.
2809  *
2810  * Sets the profile copyrights for specific locales.
2811  *
2812  * Since: 0.1.32
2813  **/
2814 void
cd_icc_set_copyright_items(CdIcc * icc,GHashTable * values)2815 cd_icc_set_copyright_items (CdIcc *icc, GHashTable *values)
2816 {
2817 	const gchar *key;
2818 	const gchar *value;
2819 	GList *l;
2820 	g_autoptr(GList) keys = NULL;
2821 
2822 	g_return_if_fail (CD_IS_ICC (icc));
2823 
2824 	/* add each translation */
2825 	keys = g_hash_table_get_keys (values);
2826 	for (l = keys; l != NULL; l = l->next) {
2827 		key = l->data;
2828 		value = g_hash_table_lookup (values, key);
2829 		cd_icc_set_copyright (icc, key, value);
2830 	}
2831 }
2832 
2833 /**
2834  * cd_icc_set_manufacturer:
2835  * @icc: A valid #CdIcc
2836  * @locale: A locale, e.g. "en_GB.UTF-8" or %NULL for the profile default
2837  * @value: (allow-none): New UTF-8 string value
2838  *
2839  * Sets the profile manufacturer for a specific locale.
2840  *
2841  * Since: 0.1.32
2842  **/
2843 void
cd_icc_set_manufacturer(CdIcc * icc,const gchar * locale,const gchar * value)2844 cd_icc_set_manufacturer (CdIcc *icc, const gchar *locale, const gchar *value)
2845 {
2846 	CdIccPrivate *priv = GET_PRIVATE (icc);
2847 	g_return_if_fail (value == NULL || g_utf8_validate (value, -1, NULL));
2848 	g_hash_table_insert (priv->mluc_data[CD_MLUC_MANUFACTURER],
2849 			     cd_icc_get_locale_key (locale),
2850 			     g_strdup (value));
2851 }
2852 
2853 /**
2854  * cd_icc_set_manufacturer_items:
2855  * @icc: A valid #CdIcc
2856  * @values: New translated values, with the key being the locale.
2857  *
2858  * Sets the profile manufacturers for specific locales.
2859  *
2860  * Since: 0.1.32
2861  **/
2862 void
cd_icc_set_manufacturer_items(CdIcc * icc,GHashTable * values)2863 cd_icc_set_manufacturer_items (CdIcc *icc, GHashTable *values)
2864 {
2865 	const gchar *key;
2866 	const gchar *value;
2867 	GList *l;
2868 	g_autoptr(GList) keys = NULL;
2869 
2870 	g_return_if_fail (CD_IS_ICC (icc));
2871 
2872 	/* add each translation */
2873 	keys = g_hash_table_get_keys (values);
2874 	for (l = keys; l != NULL; l = l->next) {
2875 		key = l->data;
2876 		value = g_hash_table_lookup (values, key);
2877 		cd_icc_set_manufacturer (icc, key, value);
2878 	}
2879 }
2880 
2881 /**
2882  * cd_icc_set_model:
2883  * @icc: A valid #CdIcc
2884  * @locale: A locale, e.g. "en_GB.UTF-8" or %NULL for the profile default
2885  * @value: (allow-none): New UTF-8 string value
2886  *
2887  * Sets the profile model for a specific locale.
2888  *
2889  * Since: 0.1.32
2890  **/
2891 void
cd_icc_set_model(CdIcc * icc,const gchar * locale,const gchar * value)2892 cd_icc_set_model (CdIcc *icc, const gchar *locale, const gchar *value)
2893 {
2894 	CdIccPrivate *priv = GET_PRIVATE (icc);
2895 	g_return_if_fail (value == NULL || g_utf8_validate (value, -1, NULL));
2896 	g_hash_table_insert (priv->mluc_data[CD_MLUC_MODEL],
2897 			     cd_icc_get_locale_key (locale),
2898 			     g_strdup (value));
2899 }
2900 
2901 /**
2902  * cd_icc_set_model_items:
2903  * @icc: A valid #CdIcc
2904  * @values: New translated values, with the key being the locale.
2905  *
2906  * Sets the profile models for specific locales.
2907  *
2908  * Since: 0.1.32
2909  **/
2910 void
cd_icc_set_model_items(CdIcc * icc,GHashTable * values)2911 cd_icc_set_model_items (CdIcc *icc, GHashTable *values)
2912 {
2913 	const gchar *key;
2914 	const gchar *value;
2915 	GList *l;
2916 	g_autoptr(GList) keys = NULL;
2917 
2918 	g_return_if_fail (CD_IS_ICC (icc));
2919 
2920 	/* add each translation */
2921 	keys = g_hash_table_get_keys (values);
2922 	for (l = keys; l != NULL; l = l->next) {
2923 		key = l->data;
2924 		value = g_hash_table_lookup (values, key);
2925 		cd_icc_set_model (icc, key, value);
2926 	}
2927 }
2928 
2929 /**
2930  * cd_icc_get_temperature:
2931  * @icc: A valid #CdIcc
2932  *
2933  * Gets the ICC color temperature, rounded to the nearest 100K.
2934  * This function will only return results if the profile was loaded with the
2935  * %CD_ICC_LOAD_FLAGS_PRIMARIES flag.
2936  *
2937  * Return value: The color temperature in Kelvin, or 0 for error.
2938  *
2939  * Since: 0.1.32
2940  **/
2941 guint
cd_icc_get_temperature(CdIcc * icc)2942 cd_icc_get_temperature (CdIcc *icc)
2943 {
2944 	CdIccPrivate *priv = GET_PRIVATE (icc);
2945 	g_return_val_if_fail (CD_IS_ICC (icc), 0);
2946 	return priv->temperature;
2947 }
2948 
2949 /**
2950  * cd_icc_get_red:
2951  * @icc: a valid #CdIcc instance
2952  *
2953  * Gets the profile red chromaticity value.
2954  * This function will only return results if the profile was loaded with the
2955  * %CD_ICC_LOAD_FLAGS_PRIMARIES flag.
2956  *
2957  * Return value: the #CdColorXYZ value
2958  *
2959  * Since: 0.1.32
2960  **/
2961 const CdColorXYZ *
cd_icc_get_red(CdIcc * icc)2962 cd_icc_get_red (CdIcc *icc)
2963 {
2964 	CdIccPrivate *priv = GET_PRIVATE (icc);
2965 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2966 	return &priv->red;
2967 }
2968 
2969 /**
2970  * cd_icc_get_green:
2971  * @icc: a valid #CdIcc instance
2972  *
2973  * Gets the profile green chromaticity value.
2974  * This function will only return results if the profile was loaded with the
2975  * %CD_ICC_LOAD_FLAGS_PRIMARIES flag.
2976  *
2977  * Return value: the #CdColorXYZ value
2978  *
2979  * Since: 0.1.32
2980  **/
2981 const CdColorXYZ *
cd_icc_get_green(CdIcc * icc)2982 cd_icc_get_green (CdIcc *icc)
2983 {
2984 	CdIccPrivate *priv = GET_PRIVATE (icc);
2985 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
2986 	return &priv->green;
2987 }
2988 
2989 /**
2990  * cd_icc_get_blue:
2991  * @icc: a valid #CdIcc instance
2992  *
2993  * Gets the profile red chromaticity value.
2994  * This function will only return results if the profile was loaded with the
2995  * %CD_ICC_LOAD_FLAGS_PRIMARIES flag.
2996  *
2997  * Return value: the #CdColorXYZ value
2998  *
2999  * Since: 0.1.32
3000  **/
3001 const CdColorXYZ *
cd_icc_get_blue(CdIcc * icc)3002 cd_icc_get_blue (CdIcc *icc)
3003 {
3004 	CdIccPrivate *priv = GET_PRIVATE (icc);
3005 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
3006 	return &priv->blue;
3007 }
3008 
3009 /**
3010  * cd_icc_get_white:
3011  * @icc: a valid #CdIcc instance
3012  *
3013  * Gets the profile white point.
3014  * This function will only return results if the profile was loaded with the
3015  * %CD_ICC_LOAD_FLAGS_PRIMARIES flag.
3016  *
3017  * Return value: the #CdColorXYZ value
3018  *
3019  * Since: 0.1.32
3020  **/
3021 const CdColorXYZ *
cd_icc_get_white(CdIcc * icc)3022 cd_icc_get_white (CdIcc *icc)
3023 {
3024 	CdIccPrivate *priv = GET_PRIVATE (icc);
3025 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
3026 	return &priv->white;
3027 }
3028 
3029 /**
3030  * cd_icc_create_default:
3031  * @icc: A valid #CdIcc
3032  * @error: A #GError, or %NULL
3033  *
3034  * Creates a default sRGB ICC profile.
3035  *
3036  * Return value: %TRUE for success
3037  *
3038  * Since: 1.1.2
3039  **/
3040 gboolean
cd_icc_create_default(CdIcc * icc,GError ** error)3041 cd_icc_create_default (CdIcc *icc, GError **error)
3042 {
3043 	CdIccPrivate *priv = GET_PRIVATE (icc);
3044 	gboolean ret = TRUE;
3045 
3046 	/* not loaded */
3047 	if (priv->lcms_profile != NULL) {
3048 		ret = FALSE;
3049 		g_set_error_literal (error,
3050 				     CD_ICC_ERROR,
3051 				     CD_ICC_ERROR_FAILED_TO_CREATE,
3052 				     "already loaded or generated");
3053 		goto out;
3054 	}
3055 
3056 	/* create our generated ICC */
3057 	priv->lcms_profile = cmsCreate_sRGBProfileTHR (priv->context_lcms);
3058 	if (priv->lcms_profile == NULL) {
3059 		ret = FALSE;
3060 		g_set_error (error,
3061 			     CD_ICC_ERROR,
3062 			     CD_ICC_ERROR_FAILED_TO_CREATE,
3063 			     "failed to create sRGB profile");
3064 		goto out;
3065 	}
3066 
3067 	/* get defaults from profile */
3068 	ret = cd_icc_load (icc, 0, error);
3069 	if (!ret)
3070 		goto out;
3071 
3072 	/* set any extra profile metadata */
3073 	cd_icc_add_metadata (icc,
3074 			     CD_PROFILE_METADATA_DATA_SOURCE,
3075 			     CD_PROFILE_METADATA_DATA_SOURCE_STANDARD);
3076 	cd_icc_add_metadata (icc,
3077 			     CD_PROFILE_METADATA_STANDARD_SPACE,
3078 			     cd_standard_space_to_string (CD_STANDARD_SPACE_SRGB));
3079 out:
3080 	return ret;
3081 }
3082 
3083 /**
3084  * cd_icc_create_from_edid_data:
3085  * @icc: A valid #CdIcc
3086  * @edid: EDID data
3087  * @error: A #GError, or %NULL
3088  *
3089  * Creates an ICC profile from EDID data.
3090  *
3091  * Return value: %TRUE for success
3092  *
3093  * Since: 1.1.2
3094  **/
3095 gboolean
cd_icc_create_from_edid_data(CdIcc * icc,CdEdid * edid,GError ** error)3096 cd_icc_create_from_edid_data (CdIcc *icc, CdEdid *edid, GError **error)
3097 {
3098 	CdIccPrivate *priv = GET_PRIVATE (icc);
3099 	const gchar *data;
3100 
3101 	/* not loaded */
3102 	if (priv->lcms_profile != NULL) {
3103 		g_set_error_literal (error,
3104 				     CD_ICC_ERROR,
3105 				     CD_ICC_ERROR_FAILED_TO_CREATE,
3106 				     "already loaded or generated");
3107 		return FALSE;
3108 	}
3109 
3110 	/* create from parsed object */
3111 	if (!cd_icc_create_from_edid (icc,
3112 				      cd_edid_get_gamma (edid),
3113 				      cd_edid_get_red (edid),
3114 				      cd_edid_get_green (edid),
3115 				      cd_edid_get_blue (edid),
3116 				      cd_edid_get_white (edid),
3117 				      error)) {
3118 		return FALSE;
3119 	}
3120 
3121 	/* set copyright */
3122 	cd_icc_set_copyright (icc, NULL,
3123 			      /* deliberately not translated */
3124 			      "This profile is free of known copyright restrictions.");
3125 
3126 	/* set 'ICC meta Tag for Monitor Profiles' data */
3127 	data = cd_edid_get_checksum (edid);
3128 	if (data != NULL)
3129 		cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MD5, data);
3130 	data = cd_edid_get_monitor_name (edid);
3131 	if (data != NULL)
3132 		cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MODEL, data);
3133 	data = cd_edid_get_serial_number (edid);
3134 	if (data != NULL)
3135 		cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_SERIAL, data);
3136 	data = cd_edid_get_pnp_id (edid);
3137 	if (data != NULL)
3138 		cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MNFT, data);
3139 	data = cd_edid_get_vendor_name (edid);
3140 	if (data != NULL)
3141 		cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_VENDOR, data);
3142 	return TRUE;
3143 }
3144 
3145 /**
3146  * cd_icc_create_from_edid:
3147  * @icc: A valid #CdIcc
3148  * @gamma_value: approximate device gamma
3149  * @red: primary color value
3150  * @green: primary color value
3151  * @blue: primary color value
3152  * @white: whitepoint value
3153  * @error: A #GError, or %NULL
3154  *
3155  * Creates an ICC profile from EDID data.
3156  *
3157  * Return value: %TRUE for success
3158  *
3159  * Since: 0.1.32
3160  **/
3161 gboolean
cd_icc_create_from_edid(CdIcc * icc,gdouble gamma_value,const CdColorYxy * red,const CdColorYxy * green,const CdColorYxy * blue,const CdColorYxy * white,GError ** error)3162 cd_icc_create_from_edid (CdIcc *icc,
3163 			 gdouble gamma_value,
3164 			 const CdColorYxy *red,
3165 			 const CdColorYxy *green,
3166 			 const CdColorYxy *blue,
3167 			 const CdColorYxy *white,
3168 			 GError **error)
3169 {
3170 	CdIccPrivate *priv = GET_PRIVATE (icc);
3171 	cmsCIExyYTRIPLE chroma;
3172 	cmsCIExyY white_point;
3173 	cmsToneCurve *transfer_curve[3] = { NULL, NULL, NULL };
3174 	gboolean ret = FALSE;
3175 
3176 	/* not loaded */
3177 	if (priv->lcms_profile != NULL) {
3178 		g_set_error_literal (error,
3179 				     CD_ICC_ERROR,
3180 				     CD_ICC_ERROR_FAILED_TO_CREATE,
3181 				     "already loaded or generated");
3182 		goto out;
3183 	}
3184 
3185 	/* copy data from our structures (which are the wrong packing
3186 	 * size for lcms2) */
3187 	chroma.Red.x = red->x;
3188 	chroma.Red.y = red->y;
3189 	chroma.Green.x = green->x;
3190 	chroma.Green.y = green->y;
3191 	chroma.Blue.x = blue->x;
3192 	chroma.Blue.y = blue->y;
3193 	white_point.x = white->x;
3194 	white_point.y = white->y;
3195 	white_point.Y = 1.0;
3196 
3197 	/* estimate the transfer function for the gamma */
3198 	transfer_curve[0] = cmsBuildGamma (NULL, gamma_value);
3199 	transfer_curve[1] = transfer_curve[0];
3200 	transfer_curve[2] = transfer_curve[0];
3201 
3202 	/* create our generated ICC */
3203 	priv->lcms_profile = cmsCreateRGBProfileTHR (priv->context_lcms,
3204 						     &white_point,
3205 						     &chroma,
3206 						     transfer_curve);
3207 	if (priv->lcms_profile == NULL) {
3208 		g_set_error (error,
3209 			     CD_ICC_ERROR,
3210 			     CD_ICC_ERROR_FAILED_TO_CREATE,
3211 			     "failed to create profile with chroma and gamma");
3212 		goto out;
3213 	}
3214 
3215 	/* set header options */
3216 	cmsSetHeaderRenderingIntent (priv->lcms_profile, INTENT_PERCEPTUAL);
3217 	cmsSetDeviceClass (priv->lcms_profile, cmsSigDisplayClass);
3218 
3219 	/* copy any important parts out of the lcms-generated profile */
3220 	ret = cd_icc_load (icc, CD_ICC_LOAD_FLAGS_NONE, error);
3221 	if (!ret)
3222 		goto out;
3223 
3224 	/* set the data source so we don't ever prompt the user to
3225 	* recalibrate (as the EDID data won't have changed) */
3226 	cd_icc_add_metadata (icc,
3227 			     CD_PROFILE_METADATA_DATA_SOURCE,
3228 			     CD_PROFILE_METADATA_DATA_SOURCE_EDID);
3229 
3230 	/* success */
3231 	ret = TRUE;
3232 out:
3233 	if (transfer_curve[0] != NULL)
3234 		cmsFreeToneCurve (transfer_curve[0]);
3235 	return ret;
3236 }
3237 
3238 /**
3239  * cd_icc_get_vcgt:
3240  * @icc: A valid #CdIcc
3241  * @size: the desired size of the table data
3242  * @error: A #GError or %NULL
3243  *
3244  * Gets the video card calibration data from the profile.
3245  *
3246  * Return value: (transfer container) (element-type CdColorRGB): VCGT data, or %NULL for error
3247  *
3248  * Since: 0.1.34
3249  **/
3250 GPtrArray *
cd_icc_get_vcgt(CdIcc * icc,guint size,GError ** error)3251 cd_icc_get_vcgt (CdIcc *icc, guint size, GError **error)
3252 {
3253 	CdIccPrivate *priv = GET_PRIVATE (icc);
3254 	CdColorRGB *tmp;
3255 	cmsFloat32Number in;
3256 	const cmsToneCurve **vcgt;
3257 	GPtrArray *array = NULL;
3258 	guint i;
3259 
3260 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
3261 	g_return_val_if_fail (priv->lcms_profile != NULL, NULL);
3262 
3263 	/* get tone curves from icc */
3264 	vcgt = cmsReadTag (priv->lcms_profile, cmsSigVcgtType);
3265 	if (vcgt == NULL || vcgt[0] == NULL) {
3266 		g_set_error_literal (error,
3267 				     CD_ICC_ERROR,
3268 				     CD_ICC_ERROR_NO_DATA,
3269 				     "icc does not have any VCGT data");
3270 		goto out;
3271 	}
3272 
3273 	/* create array */
3274 	array = g_ptr_array_new_with_free_func ((GDestroyNotify) cd_color_rgb_free);
3275 	for (i = 0; i < size; i++) {
3276 		in = (gdouble) i / (gdouble) (size - 1);
3277 		tmp = cd_color_rgb_new ();
3278 		cd_color_rgb_set (tmp,
3279 				  cmsEvalToneCurveFloat(vcgt[0], in),
3280 				  cmsEvalToneCurveFloat(vcgt[1], in),
3281 				  cmsEvalToneCurveFloat(vcgt[2], in));
3282 		g_ptr_array_add (array, tmp);
3283 	}
3284 out:
3285 	return array;
3286 }
3287 
3288 /**
3289  * cd_icc_get_response:
3290  * @icc: A valid #CdIcc
3291  * @size: the size of the curve to generate
3292  * @error: a valid #GError, or %NULL
3293  *
3294  * Generates a response curve of a specified size.
3295  *
3296  * Return value: (transfer container) (element-type CdColorRGB): response data, or %NULL for error
3297  *
3298  * Since: 0.1.34
3299  **/
3300 GPtrArray *
cd_icc_get_response(CdIcc * icc,guint size,GError ** error)3301 cd_icc_get_response (CdIcc *icc, guint size, GError **error)
3302 {
3303 	CdIccPrivate *priv = GET_PRIVATE (icc);
3304 	CdColorRGB *data;
3305 	CdColorspace colorspace;
3306 	cmsHPROFILE srgb_profile = NULL;
3307 	cmsHTRANSFORM transform = NULL;
3308 	const guint component_width = 3;
3309 	gdouble tmp;
3310 	gfloat divadd;
3311 	gfloat divamount;
3312 	GPtrArray *array = NULL;
3313 	guint i;
3314 	g_autofree gdouble *values_in = NULL;
3315 	g_autofree gdouble *values_out = NULL;
3316 
3317 	/* run through the icc */
3318 	colorspace = cd_icc_get_colorspace (icc);
3319 	if (colorspace != CD_COLORSPACE_RGB) {
3320 		g_set_error_literal (error,
3321 				     CD_ICC_ERROR,
3322 				     CD_ICC_ERROR_INVALID_COLORSPACE,
3323 				     "Only RGB colorspaces are supported");
3324 		goto out;
3325 	}
3326 
3327 	/* create input array */
3328 	values_in = g_new0 (gdouble, size * 3 * component_width);
3329 	divamount = 1.0f / (gfloat) (size - 1);
3330 	for (i = 0; i < size; i++) {
3331 		divadd = divamount * (gfloat) i;
3332 
3333 		/* red */
3334 		values_in[(i * 3 * component_width) + 0] = divadd;
3335 		values_in[(i * 3 * component_width) + 1] = 0.0f;
3336 		values_in[(i * 3 * component_width) + 2] = 0.0f;
3337 
3338 		/* green */
3339 		values_in[(i * 3 * component_width) + 3] = 0.0f;
3340 		values_in[(i * 3 * component_width) + 4] = divadd;
3341 		values_in[(i * 3 * component_width) + 5] = 0.0f;
3342 
3343 		/* blue */
3344 		values_in[(i * 3 * component_width) + 6] = 0.0f;
3345 		values_in[(i * 3 * component_width) + 7] = 0.0f;
3346 		values_in[(i * 3 * component_width) + 8] = divadd;
3347 	}
3348 
3349 	/* create a transform from icc to sRGB */
3350 	values_out = g_new0 (gdouble, size * 3 * component_width);
3351 	srgb_profile = cmsCreate_sRGBProfileTHR (priv->context_lcms);
3352 	transform = cmsCreateTransformTHR (priv->context_lcms,
3353 					   priv->lcms_profile, TYPE_RGB_DBL,
3354 					   srgb_profile, TYPE_RGB_DBL,
3355 					   INTENT_PERCEPTUAL, 0);
3356 	if (transform == NULL) {
3357 		g_set_error_literal (error,
3358 				     CD_ICC_ERROR,
3359 				     CD_ICC_ERROR_NO_DATA,
3360 				     "Failed to setup transform");
3361 		goto out;
3362 	}
3363 	cmsDoTransform (transform, values_in, values_out, size * 3);
3364 
3365 	/* create output array */
3366 	array = cd_color_rgb_array_new ();
3367 	for (i = 0; i < size; i++) {
3368 		data = cd_color_rgb_new ();
3369 		cd_color_rgb_set (data, 0.0f, 0.0f, 0.0f);
3370 
3371 		/* only save curve data if it is positive */
3372 		tmp = values_out[(i * 3 * component_width) + 0];
3373 		if (tmp > 0.0f)
3374 			data->R = tmp;
3375 		tmp = values_out[(i * 3 * component_width) + 4];
3376 		if (tmp > 0.0f)
3377 			data->G = tmp;
3378 		tmp = values_out[(i * 3 * component_width) + 8];
3379 		if (tmp > 0.0f)
3380 			data->B = tmp;
3381 		g_ptr_array_add (array, data);
3382 	}
3383 out:
3384 	if (transform != NULL)
3385 		cmsDeleteTransform (transform);
3386 	if (srgb_profile != NULL)
3387 		cmsCloseProfile (srgb_profile);
3388 	return array;
3389 }
3390 
3391 /**
3392  * cd_icc_set_vcgt:
3393  * @icc: A valid #CdIcc
3394  * @vcgt: (element-type CdColorRGB): video card calibration data
3395  * @error: A #GError or %NULL
3396  *
3397  * Sets the Video Card Gamma Table from the profile.
3398  *
3399  * Return vale: %TRUE for success.
3400  *
3401  * Since: 0.1.34
3402  **/
3403 gboolean
cd_icc_set_vcgt(CdIcc * icc,GPtrArray * vcgt,GError ** error)3404 cd_icc_set_vcgt (CdIcc *icc, GPtrArray *vcgt, GError **error)
3405 {
3406 	CdIccPrivate *priv = GET_PRIVATE (icc);
3407 	CdColorRGB *tmp;
3408 	cmsToneCurve *curve[3];
3409 	gboolean ret;
3410 	guint i;
3411 	g_autofree guint16 *blue = NULL;
3412 	g_autofree guint16 *green = NULL;
3413 	g_autofree guint16 *red = NULL;
3414 
3415 	g_return_val_if_fail (CD_IS_ICC (icc), FALSE);
3416 	g_return_val_if_fail (priv->lcms_profile != NULL, FALSE);
3417 
3418 	/* unwrap data */
3419 	red = g_new0 (guint16, vcgt->len);
3420 	green = g_new0 (guint16, vcgt->len);
3421 	blue = g_new0 (guint16, vcgt->len);
3422 	for (i = 0; i < vcgt->len; i++) {
3423 		tmp = g_ptr_array_index (vcgt, i);
3424 		red[i]   = tmp->R * (gdouble) 0xffff;
3425 		green[i] = tmp->G * (gdouble) 0xffff;
3426 		blue[i]  = tmp->B * (gdouble) 0xffff;
3427 	}
3428 
3429 	/* build tone curve */
3430 	curve[0] = cmsBuildTabulatedToneCurve16 (NULL, vcgt->len, red);
3431 	curve[1] = cmsBuildTabulatedToneCurve16 (NULL, vcgt->len, green);
3432 	curve[2] = cmsBuildTabulatedToneCurve16 (NULL, vcgt->len, blue);
3433 
3434 	/* smooth it */
3435 	for (i = 0; i < 3; i++)
3436 		cmsSmoothToneCurve (curve[i], 5);
3437 
3438 	/* write the tag */
3439 	ret = cmsWriteTag (priv->lcms_profile, cmsSigVcgtType, curve);
3440 	if (!ret) {
3441 		g_set_error_literal (error,
3442 				     CD_ICC_ERROR,
3443 				     CD_ICC_ERROR_NO_DATA,
3444 				     "failed to write VCGT data");
3445 		goto out;
3446 	}
3447 out:
3448 	for (i = 0; i < 3; i++)
3449 		cmsFreeToneCurve (curve[i]);
3450 	return ret;
3451 }
3452 
3453 /**
3454  * cd_icc_check_whitepoint:
3455  **/
3456 static CdProfileWarning
cd_icc_check_whitepoint(CdIcc * icc)3457 cd_icc_check_whitepoint (CdIcc *icc)
3458 {
3459 	CdIccPrivate *priv = GET_PRIVATE (icc);
3460 	guint temp = priv->temperature;
3461 
3462 	/* not set */
3463 	if (temp == 0)
3464 		return CD_PROFILE_WARNING_NONE;
3465 
3466 	/* hardcoded sanity check */
3467 	if (temp < 3000 || temp > 10000)
3468 		return CD_PROFILE_WARNING_WHITEPOINT_UNLIKELY;
3469 
3470 	return CD_PROFILE_WARNING_NONE;
3471 }
3472 
3473 /**
3474  * cd_icc_check_vcgt:
3475  **/
3476 static CdProfileWarning
cd_icc_check_vcgt(CdIcc * icc)3477 cd_icc_check_vcgt (CdIcc *icc)
3478 {
3479 	CdIccPrivate *priv = GET_PRIVATE (icc);
3480 	cmsFloat32Number in;
3481 	cmsFloat32Number now[3];
3482 	cmsFloat32Number previous[3] = { -1, -1, -1};
3483 	const cmsToneCurve **vcgt;
3484 	const guint size = 32;
3485 	guint i;
3486 
3487 	/* does profile have monotonic VCGT */
3488 	vcgt = cmsReadTag (priv->lcms_profile, cmsSigVcgtTag);
3489 	if (vcgt == NULL)
3490 		return CD_PROFILE_WARNING_NONE;
3491 	for (i = 0; i < size; i++) {
3492 		in = (gdouble) i / (gdouble) (size - 1);
3493 		now[0] = cmsEvalToneCurveFloat(vcgt[0], in);
3494 		now[1] = cmsEvalToneCurveFloat(vcgt[1], in);
3495 		now[2] = cmsEvalToneCurveFloat(vcgt[2], in);
3496 
3497 		/* check VCGT is increasing */
3498 		if (i > 0) {
3499 			if (now[0] < previous[0] ||
3500 			    now[1] < previous[1] ||
3501 			    now[2] < previous[2]) {
3502 				return CD_PROFILE_WARNING_VCGT_NON_MONOTONIC;
3503 			}
3504 		}
3505 		previous[0] = now[0];
3506 		previous[1] = now[1];
3507 		previous[2] = now[2];
3508 	}
3509 	return CD_PROFILE_WARNING_NONE;
3510 }
3511 
3512 /**
3513  * cd_profile_check_scum_dot:
3514  **/
3515 static CdProfileWarning
cd_profile_check_scum_dot(CdIcc * icc)3516 cd_profile_check_scum_dot (CdIcc *icc)
3517 {
3518 	CdIccPrivate *priv = GET_PRIVATE (icc);
3519 	CdProfileWarning warning = CD_PROFILE_WARNING_NONE;
3520 	cmsCIELab white;
3521 	cmsHPROFILE profile_lab;
3522 	cmsHTRANSFORM transform;
3523 	guint8 rgb[3] = { 0, 0, 0 };
3524 
3525 	/* do Lab to RGB transform of 100,0,0 */
3526 	profile_lab = cmsCreateLab2ProfileTHR (priv->context_lcms, cmsD50_xyY ());
3527 	transform = cmsCreateTransformTHR (priv->context_lcms,
3528 					   profile_lab, TYPE_Lab_DBL,
3529 					   priv->lcms_profile, TYPE_RGB_8,
3530 					   INTENT_RELATIVE_COLORIMETRIC,
3531 					   cmsFLAGS_NOOPTIMIZE);
3532 	if (transform == NULL) {
3533 		g_warning ("failed to setup Lab -> RGB transform");
3534 		goto out;
3535 	}
3536 	white.L = 100.0;
3537 	white.a = 0.0;
3538 	white.b = 0.0;
3539 	cmsDoTransform (transform, &white, rgb, 1);
3540 	if (rgb[0] != 255 || rgb[1] != 255 || rgb[2] != 255) {
3541 		warning = CD_PROFILE_WARNING_SCUM_DOT;
3542 		goto out;
3543 	}
3544 out:
3545 	if (profile_lab != NULL)
3546 		cmsCloseProfile (profile_lab);
3547 	if (transform != NULL)
3548 		cmsDeleteTransform (transform);
3549 	return warning;
3550 }
3551 
3552 /**
3553  * cd_icc_check_primaries:
3554  **/
3555 static CdProfileWarning
cd_icc_check_primaries(CdIcc * icc)3556 cd_icc_check_primaries (CdIcc *icc)
3557 {
3558 	CdIccPrivate *priv = GET_PRIVATE (icc);
3559 	cmsCIEXYZ *tmp;
3560 
3561 	/* The values used to check are based on the following ultra-wide
3562 	 * gamut profile XYZ values:
3563 	 *
3564 	 * CIERGB:
3565 	 * Red:		0.486893 0.174667 -0.001251
3566 	 * Green:	0.306320 0.824768 0.016998
3567 	 * Blue:	0.170990 0.000565 0.809158
3568 
3569 	 * ProPhoto RGB:
3570 	 * Red:		0.797546 0.288025 0.000000
3571 	 * Green:	0.135315 0.711899 -0.000015
3572 	 * Blue:	0.031342 0.000076 0.824921
3573 	 */
3574 
3575 	/* check red */
3576 	tmp = cmsReadTag (priv->lcms_profile, cmsSigRedColorantTag);
3577 	if (tmp == NULL)
3578 		return CD_PROFILE_WARNING_NONE;
3579 	if (tmp->X > 0.85f || tmp->Y < 0.15f || tmp->Z < -0.01)
3580 		return CD_PROFILE_WARNING_PRIMARIES_INVALID;
3581 
3582 	/* check green */
3583 	tmp = cmsReadTag (priv->lcms_profile, cmsSigGreenColorantTag);
3584 	if (tmp == NULL)
3585 		return CD_PROFILE_WARNING_NONE;
3586 	if (tmp->X < 0.10f || tmp->Y > 0.85f || tmp->Z < -0.01f)
3587 		return CD_PROFILE_WARNING_PRIMARIES_INVALID;
3588 
3589 	/* check blue */
3590 	tmp = cmsReadTag (priv->lcms_profile, cmsSigBlueColorantTag);
3591 	if (tmp == NULL)
3592 		return CD_PROFILE_WARNING_NONE;
3593 	if (tmp->X < 0.01f || tmp->Y < 0.0f || tmp->Z > 0.87f)
3594 		return CD_PROFILE_WARNING_PRIMARIES_INVALID;
3595 
3596 	return CD_PROFILE_WARNING_NONE;
3597 }
3598 
3599 /**
3600  * cd_icc_check_gray_axis:
3601  **/
3602 static CdProfileWarning
cd_icc_check_gray_axis(CdIcc * icc)3603 cd_icc_check_gray_axis (CdIcc *icc)
3604 {
3605 	CdIccPrivate *priv = GET_PRIVATE (icc);
3606 	CdProfileWarning warning = CD_PROFILE_WARNING_NONE;
3607 	cmsCIELab gray[16];
3608 	cmsHPROFILE profile_lab = NULL;
3609 	cmsHTRANSFORM transform = NULL;
3610 	const gdouble gray_error = 5.0f;
3611 	gdouble last_l = -1;
3612 	guint8 rgb[3*16];
3613 	guint8 tmp;
3614 	guint i;
3615 
3616 	/* only do this for display profiles */
3617 	if (cmsGetDeviceClass (priv->lcms_profile) != cmsSigDisplayClass)
3618 		goto out;
3619 
3620 	/* do Lab to RGB transform of 100,0,0 */
3621 	profile_lab = cmsCreateLab2ProfileTHR (priv->context_lcms, cmsD50_xyY ());
3622 	transform = cmsCreateTransformTHR (priv->context_lcms,
3623 					   priv->lcms_profile, TYPE_RGB_8,
3624 					   profile_lab, TYPE_Lab_DBL,
3625 					   INTENT_RELATIVE_COLORIMETRIC,
3626 					   cmsFLAGS_NOOPTIMIZE);
3627 	if (transform == NULL) {
3628 		g_warning ("failed to setup RGB -> Lab transform");
3629 		goto out;
3630 	}
3631 
3632 	/* run a 16 item gray ramp through the transform */
3633 	for (i = 0; i < 16; i++) {
3634 		tmp = (255.0f / (16.0f - 1)) * i;
3635 		rgb[(i * 3) + 0] = tmp;
3636 		rgb[(i * 3) + 1] = tmp;
3637 		rgb[(i * 3) + 2] = tmp;
3638 	}
3639 	cmsDoTransform (transform, rgb, gray, 16);
3640 
3641 	/* check a/b is small */
3642 	for (i = 0; i < 16; i++) {
3643 		if (gray[i].a > gray_error ||
3644 		    gray[i].b > gray_error) {
3645 			warning = CD_PROFILE_WARNING_GRAY_AXIS_INVALID;
3646 			goto out;
3647 		}
3648 	}
3649 
3650 	/* check it's monotonic */
3651 	for (i = 0; i < 16; i++) {
3652 		if (last_l > 0 && gray[i].L < last_l) {
3653 			warning = CD_PROFILE_WARNING_GRAY_AXIS_NON_MONOTONIC;
3654 			goto out;
3655 		}
3656 		last_l = gray[i].L;
3657 	}
3658 out:
3659 	if (profile_lab != NULL)
3660 		cmsCloseProfile (profile_lab);
3661 	if (transform != NULL)
3662 		cmsDeleteTransform (transform);
3663 	return warning;
3664 }
3665 
3666 /**
3667  * cd_icc_check_d50_whitepoint:
3668  **/
3669 static CdProfileWarning
cd_icc_check_d50_whitepoint(CdIcc * icc)3670 cd_icc_check_d50_whitepoint (CdIcc *icc)
3671 {
3672 	CdIccPrivate *priv = GET_PRIVATE (icc);
3673 	CdProfileWarning warning = CD_PROFILE_WARNING_NONE;
3674 	cmsCIExyY tmp;
3675 	cmsCIEXYZ additive;
3676 	cmsCIEXYZ primaries[4];
3677 	cmsHPROFILE profile_lab;
3678 	cmsHTRANSFORM transform;
3679 	const cmsCIEXYZ *d50;
3680 	const gdouble rgb_error = 0.05;
3681 	const gdouble additive_error = 0.1f;
3682 	const gdouble white_error = 0.05;
3683 	guint8 rgb[3*4];
3684 	guint i;
3685 
3686 	/* do Lab to RGB transform to get primaries */
3687 	profile_lab = cmsCreateXYZProfileTHR (priv->context_lcms);
3688 	transform = cmsCreateTransformTHR (priv->context_lcms,
3689 					   priv->lcms_profile, TYPE_RGB_8,
3690 					   profile_lab, TYPE_XYZ_DBL,
3691 					   INTENT_RELATIVE_COLORIMETRIC,
3692 					   cmsFLAGS_NOOPTIMIZE);
3693 	if (transform == NULL) {
3694 		g_warning ("failed to setup RGB -> XYZ transform");
3695 		goto out;
3696 	}
3697 
3698 	/* Run RGBW through the transform */
3699 	rgb[0 + 0] = 255;
3700 	rgb[0 + 1] = 0;
3701 	rgb[0 + 2] = 0;
3702 	rgb[3 + 0] = 0;
3703 	rgb[3 + 1] = 255;
3704 	rgb[3 + 2] = 0;
3705 	rgb[6 + 0] = 0;
3706 	rgb[6 + 1] = 0;
3707 	rgb[6 + 2] = 255;
3708 	rgb[9 + 0] = 255;
3709 	rgb[9 + 1] = 255;
3710 	rgb[9 + 2] = 255;
3711 	cmsDoTransform (transform, rgb, primaries, 4);
3712 
3713 	/* check red is in gamut */
3714 	cmsXYZ2xyY (&tmp, &primaries[0]);
3715 	if (tmp.x - 0.735 > rgb_error || 0.265 - tmp.y > rgb_error) {
3716 		warning = CD_PROFILE_WARNING_PRIMARIES_UNLIKELY;
3717 		goto out;
3718 	}
3719 
3720 	/* check green is in gamut */
3721 	cmsXYZ2xyY (&tmp, &primaries[1]);
3722 	if (0.160 - tmp.x > rgb_error || tmp.y - 0.840 > rgb_error) {
3723 		warning = CD_PROFILE_WARNING_PRIMARIES_UNLIKELY;
3724 		goto out;
3725 	}
3726 
3727 	/* check blue is in gamut */
3728 	cmsXYZ2xyY (&tmp, &primaries[2]);
3729 	if (0.037 - tmp.x > rgb_error || tmp.y - 0.358 > rgb_error) {
3730 		warning = CD_PROFILE_WARNING_PRIMARIES_UNLIKELY;
3731 		goto out;
3732 	}
3733 
3734 	/* only do the rest for display profiles */
3735 	if (cmsGetDeviceClass (priv->lcms_profile) != cmsSigDisplayClass)
3736 		goto out;
3737 
3738 	/* check white is D50 */
3739 	d50 = cmsD50_XYZ();
3740 	if (fabs (primaries[3].X - d50->X) > white_error ||
3741 	    fabs (primaries[3].Y - d50->Y) > white_error ||
3742 	    fabs (primaries[3].Z - d50->Z) > white_error) {
3743 		warning = CD_PROFILE_WARNING_WHITEPOINT_INVALID;
3744 		goto out;
3745 	}
3746 
3747 	/* check primaries add up to D50 */
3748 	additive.X = 0;
3749 	additive.Y = 0;
3750 	additive.Z = 0;
3751 	for (i = 0; i < 3; i++) {
3752 		additive.X += primaries[i].X;
3753 		additive.Y += primaries[i].Y;
3754 		additive.Z += primaries[i].Z;
3755 	}
3756 	if (fabs (additive.X - d50->X) > additive_error ||
3757 	    fabs (additive.Y - d50->Y) > additive_error ||
3758 	    fabs (additive.Z - d50->Z) > additive_error) {
3759 		warning = CD_PROFILE_WARNING_PRIMARIES_NON_ADDITIVE;
3760 		goto out;
3761 	}
3762 out:
3763 	if (profile_lab != NULL)
3764 		cmsCloseProfile (profile_lab);
3765 	if (transform != NULL)
3766 		cmsDeleteTransform (transform);
3767 	return warning;
3768 }
3769 
3770 /**
3771  * cd_icc_get_warnings:
3772  * @icc: a #CdIcc instance.
3773  *
3774  * Returns any warnings with profiles
3775  *
3776  * Return value: (transfer container) (element-type CdProfileWarning): An array of warning values
3777  *
3778  * Since: 0.1.34
3779  **/
3780 GArray *
cd_icc_get_warnings(CdIcc * icc)3781 cd_icc_get_warnings (CdIcc *icc)
3782 {
3783 	CdIccPrivate *priv = GET_PRIVATE (icc);
3784 	GArray *flags;
3785 	gboolean ret;
3786 	gchar ascii_name[1024];
3787 	CdProfileWarning warning;
3788 
3789 	g_return_val_if_fail (CD_IS_ICC (icc), NULL);
3790 	g_return_val_if_fail (priv->lcms_profile != NULL, NULL);
3791 
3792 	flags = g_array_new (FALSE, FALSE, sizeof (CdProfileWarning));
3793 
3794 	/* check that the profile has a description and a copyright */
3795 	ret = cmsGetProfileInfoASCII (priv->lcms_profile,
3796 				      cmsInfoDescription, "en", "US",
3797 				      ascii_name, 1024);
3798 	if (!ret || ascii_name[0] == '\0') {
3799 		warning = CD_PROFILE_WARNING_DESCRIPTION_MISSING;
3800 		g_array_append_val (flags, warning);
3801 	}
3802 	ret = cmsGetProfileInfoASCII (priv->lcms_profile,
3803 				      cmsInfoCopyright, "en", "US",
3804 				      ascii_name, 1024);
3805 	if (!ret || ascii_name[0] == '\0') {
3806 		warning = CD_PROFILE_WARNING_COPYRIGHT_MISSING;
3807 		g_array_append_val (flags, warning);
3808 	}
3809 
3810 	/* not a RGB space */
3811 	if (cmsGetColorSpace (priv->lcms_profile) != cmsSigRgbData)
3812 		goto out;
3813 
3814 	/* does profile have an unlikely whitepoint */
3815 	warning = cd_icc_check_whitepoint (icc);
3816 	if (warning != CD_PROFILE_WARNING_NONE)
3817 		g_array_append_val (flags, warning);
3818 
3819 	/* does profile have monotonic VCGT */
3820 	warning = cd_icc_check_vcgt (icc);
3821 	if (warning != CD_PROFILE_WARNING_NONE)
3822 		g_array_append_val (flags, warning);
3823 
3824 	/* if Lab 100,0,0 does not map to RGB 255,255,255 for relative
3825 	 * colorimetric then white it will not work on printers */
3826 	warning = cd_profile_check_scum_dot (icc);
3827 	if (warning != CD_PROFILE_WARNING_NONE)
3828 		g_array_append_val (flags, warning);
3829 
3830 	/* gray should give low a/b and should be monotonic */
3831 	warning = cd_icc_check_gray_axis (icc);
3832 	if (warning != CD_PROFILE_WARNING_NONE)
3833 		g_array_append_val (flags, warning);
3834 
3835 	/* tristimulus values cannot be negative */
3836 	warning = cd_icc_check_primaries (icc);
3837 	if (warning != CD_PROFILE_WARNING_NONE)
3838 		g_array_append_val (flags, warning);
3839 
3840 	/* check whitepoint works out to D50 */
3841 	warning = cd_icc_check_d50_whitepoint (icc);
3842 	if (warning != CD_PROFILE_WARNING_NONE)
3843 		g_array_append_val (flags, warning);
3844 out:
3845 	return flags;
3846 }
3847 
3848 /**
3849  * cd_icc_get_property:
3850  **/
3851 static void
cd_icc_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)3852 cd_icc_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
3853 {
3854 	CdIcc *icc = CD_ICC (object);
3855 	CdIccPrivate *priv = GET_PRIVATE (icc);
3856 
3857 	switch (prop_id) {
3858 	case PROP_SIZE:
3859 		g_value_set_uint (value, priv->size);
3860 		break;
3861 	case PROP_FILENAME:
3862 		g_value_set_string (value, priv->filename);
3863 		break;
3864 	case PROP_VERSION:
3865 		g_value_set_double (value, priv->version);
3866 		break;
3867 	case PROP_KIND:
3868 		g_value_set_uint (value, priv->kind);
3869 		break;
3870 	case PROP_COLORSPACE:
3871 		g_value_set_uint (value, priv->colorspace);
3872 		break;
3873 	case PROP_CAN_DELETE:
3874 		g_value_set_boolean (value, priv->can_delete);
3875 		break;
3876 	case PROP_CHECKSUM:
3877 		g_value_set_string (value, priv->checksum);
3878 		break;
3879 	case PROP_WHITE:
3880 		g_value_set_boxed (value, g_boxed_copy (CD_TYPE_COLOR_XYZ, &priv->white));
3881 		break;
3882 	case PROP_RED:
3883 		g_value_set_boxed (value, g_boxed_copy (CD_TYPE_COLOR_XYZ, &priv->red));
3884 		break;
3885 	case PROP_GREEN:
3886 		g_value_set_boxed (value, g_boxed_copy (CD_TYPE_COLOR_XYZ, &priv->green));
3887 		break;
3888 	case PROP_BLUE:
3889 		g_value_set_boxed (value, g_boxed_copy (CD_TYPE_COLOR_XYZ, &priv->blue));
3890 		break;
3891 	case PROP_TEMPERATURE:
3892 		g_value_set_uint (value, priv->temperature);
3893 		break;
3894 	default:
3895 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3896 		break;
3897 	}
3898 }
3899 
3900 /**
3901  * cd_icc_set_property:
3902  **/
3903 static void
cd_icc_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)3904 cd_icc_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
3905 {
3906 	CdIcc *icc = CD_ICC (object);
3907 	switch (prop_id) {
3908 	case PROP_KIND:
3909 		cd_icc_set_kind (icc, g_value_get_uint (value));
3910 		break;
3911 	case PROP_COLORSPACE:
3912 		cd_icc_set_colorspace (icc, g_value_get_uint (value));
3913 		break;
3914 	case PROP_VERSION:
3915 		cd_icc_set_version (icc, g_value_get_double (value));
3916 		break;
3917 	default:
3918 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3919 		break;
3920 	}
3921 }
3922 
3923 /**
3924  * cd_icc_class_init:
3925  */
3926 static void
cd_icc_class_init(CdIccClass * klass)3927 cd_icc_class_init (CdIccClass *klass)
3928 {
3929 	GParamSpec *pspec;
3930 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
3931 
3932 	object_class->finalize = cd_icc_finalize;
3933 	object_class->get_property = cd_icc_get_property;
3934 	object_class->set_property = cd_icc_set_property;
3935 
3936 	/**
3937 	 * CdIcc:size:
3938 	 */
3939 	pspec = g_param_spec_uint ("size", NULL, NULL,
3940 				   0, G_MAXUINT, 0,
3941 				   G_PARAM_READABLE);
3942 	g_object_class_install_property (object_class, PROP_SIZE, pspec);
3943 
3944 	/**
3945 	 * CdIcc:filename:
3946 	 */
3947 	pspec = g_param_spec_string ("filename", NULL, NULL,
3948 				     NULL,
3949 				     G_PARAM_READABLE);
3950 	g_object_class_install_property (object_class, PROP_FILENAME, pspec);
3951 
3952 	/**
3953 	 * CdIcc:version:
3954 	 */
3955 	pspec = g_param_spec_double ("version", NULL, NULL,
3956 				     0, G_MAXFLOAT, 0,
3957 				     G_PARAM_READWRITE);
3958 	g_object_class_install_property (object_class, PROP_VERSION, pspec);
3959 
3960 	/**
3961 	 * CdIcc:kind:
3962 	 */
3963 	pspec = g_param_spec_uint ("kind", NULL, NULL,
3964 				   0, G_MAXUINT, 0,
3965 				   G_PARAM_READWRITE);
3966 	g_object_class_install_property (object_class, PROP_KIND, pspec);
3967 
3968 	/**
3969 	 * CdIcc:colorspace:
3970 	 */
3971 	pspec = g_param_spec_uint ("colorspace", NULL, NULL,
3972 				   0, G_MAXUINT, 0,
3973 				   G_PARAM_READWRITE);
3974 	g_object_class_install_property (object_class, PROP_COLORSPACE, pspec);
3975 
3976 	/**
3977 	 * CdIcc:can-delete:
3978 	 */
3979 	pspec = g_param_spec_boolean ("can-delete", NULL, NULL,
3980 				      FALSE,
3981 				      G_PARAM_READABLE);
3982 	g_object_class_install_property (object_class, PROP_CAN_DELETE, pspec);
3983 
3984 	/**
3985 	 * CdIcc:checksum:
3986 	 */
3987 	pspec = g_param_spec_string ("checksum", NULL, NULL,
3988 				     NULL,
3989 				     G_PARAM_READABLE);
3990 	g_object_class_install_property (object_class, PROP_CHECKSUM, pspec);
3991 
3992 	/**
3993 	 * CdIcc:white:
3994 	 */
3995 	pspec = g_param_spec_boxed ("white", NULL, NULL,
3996 				    CD_TYPE_COLOR_XYZ,
3997 				    G_PARAM_READABLE);
3998 	g_object_class_install_property (object_class, PROP_WHITE, pspec);
3999 
4000 	/**
4001 	 * CdIcc:red:
4002 	 */
4003 	pspec = g_param_spec_boxed ("red", NULL, NULL,
4004 				    CD_TYPE_COLOR_XYZ,
4005 				    G_PARAM_READABLE);
4006 	g_object_class_install_property (object_class, PROP_RED, pspec);
4007 
4008 	/**
4009 	 * CdIcc:green:
4010 	 */
4011 	pspec = g_param_spec_boxed ("green", NULL, NULL,
4012 				    CD_TYPE_COLOR_XYZ,
4013 				    G_PARAM_READABLE);
4014 	g_object_class_install_property (object_class, PROP_GREEN, pspec);
4015 
4016 	/**
4017 	 * CdIcc:blue:
4018 	 */
4019 	pspec = g_param_spec_boxed ("blue", NULL, NULL,
4020 				    CD_TYPE_COLOR_XYZ,
4021 				    G_PARAM_READABLE);
4022 	g_object_class_install_property (object_class, PROP_BLUE, pspec);
4023 
4024 	/**
4025 	 * CdIcc:temperature:
4026 	 */
4027 	pspec = g_param_spec_uint ("temperature", NULL, NULL,
4028 				   0, G_MAXUINT, 0,
4029 				   G_PARAM_READABLE);
4030 	g_object_class_install_property (object_class, PROP_TEMPERATURE, pspec);
4031 }
4032 
4033 /**
4034  * cd_icc_init:
4035  */
4036 static void
cd_icc_init(CdIcc * icc)4037 cd_icc_init (CdIcc *icc)
4038 {
4039 	guint i;
4040 	CdIccPrivate *priv = GET_PRIVATE (icc);
4041 
4042 	priv->context_lcms = cd_context_lcms_new ();
4043 	priv->kind = CD_PROFILE_KIND_UNKNOWN;
4044 	priv->colorspace = CD_COLORSPACE_UNKNOWN;
4045 	priv->named_colors = g_ptr_array_new_with_free_func ((GDestroyNotify) cd_color_swatch_free);
4046 	priv->metadata = g_hash_table_new_full (g_str_hash,
4047 						     g_str_equal,
4048 						     g_free,
4049 						     g_free);
4050 	for (i = 0; i < CD_MLUC_LAST; i++) {
4051 		priv->mluc_data[i] = g_hash_table_new_full (g_str_hash,
4052 								 g_str_equal,
4053 								 g_free,
4054 								 g_free);
4055 	}
4056 	cd_color_xyz_clear (&priv->white);
4057 	cd_color_xyz_clear (&priv->red);
4058 	cd_color_xyz_clear (&priv->green);
4059 	cd_color_xyz_clear (&priv->blue);
4060 }
4061 
4062 /**
4063  * cd_icc_finalize:
4064  */
4065 static void
cd_icc_finalize(GObject * object)4066 cd_icc_finalize (GObject *object)
4067 {
4068 	CdIcc *icc = CD_ICC (object);
4069 	CdIccPrivate *priv = GET_PRIVATE (icc);
4070 	guint i;
4071 
4072 	g_free (priv->filename);
4073 	g_free (priv->checksum);
4074 	g_free (priv->characterization_data);
4075 	g_ptr_array_unref (priv->named_colors);
4076 	g_hash_table_destroy (priv->metadata);
4077 	for (i = 0; i < CD_MLUC_LAST; i++)
4078 		g_hash_table_destroy (priv->mluc_data[i]);
4079 	if (priv->lcms_profile != NULL)
4080 		cmsCloseProfile (priv->lcms_profile);
4081 	cd_context_lcms_free (priv->context_lcms);
4082 
4083 	G_OBJECT_CLASS (cd_icc_parent_class)->finalize (object);
4084 }
4085 
4086 /**
4087  * cd_icc_new:
4088  *
4089  * Creates a new #CdIcc object.
4090  *
4091  * Return value: a new CdIcc object.
4092  *
4093  * Since: 0.1.32
4094  **/
4095 CdIcc *
cd_icc_new(void)4096 cd_icc_new (void)
4097 {
4098 	CdIcc *icc;
4099 	icc = g_object_new (CD_TYPE_ICC, NULL);
4100 	return CD_ICC (icc);
4101 }
4102