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