iccprofile: Decode 'sf32' data type
[deark.git] / modules / iccprofile.c
bloba08e0cad01ce2b77ca08c29eb3bc3910aa474e86
1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // ICC Profile format
7 #include <deark-config.h>
8 #include <deark-private.h>
9 DE_DECLARE_MODULE(de_module_iccprofile);
11 #define MAX_TAGS_PER_TAGSET 500
12 #define MAX_NESTING_LEVEL 16
14 struct tag_seen_type {
15 i64 offset; // relative to tagset_type::data_area_pos
16 i64 len;
19 struct tagset_type {
20 int nesting_level;
21 i64 data_area_pos;
22 i64 data_area_len;
23 i64 num_tags;
24 struct tag_seen_type *tags_seen;
27 struct XYZ_type {
28 double v[3];
31 typedef struct localctx_struct {
32 unsigned int profile_ver_major;
33 unsigned int profile_ver_minor;
34 unsigned int profile_ver_bugfix;
35 i64 profile_size;
36 char tmpbuf1[80];
37 char tmpbuf2[80];
38 } lctx;
40 struct typedec_params {
41 lctx *d;
42 struct tagset_type *tgs;
43 i64 pos1;
44 i64 len;
45 u32 type_id;
47 typedef void (*datatype_decoder_fn_type)(deark *c, struct typedec_params *p);
49 struct datatypeinfo {
50 u32 id;
51 u32 reserved;
52 const char *name;
53 datatype_decoder_fn_type dtdfn;
56 struct taginfo {
57 u32 id;
59 // 0x1 = ignore if version >= 4.0.0
60 // 0x2 = ignore if version < 4.0.0
61 u32 flags;
63 const char *name;
64 void *reserved2;
67 static double read_s15Fixed16Number(dbuf *f, i64 pos)
69 i64 n, frac;
71 n = dbuf_geti16be(f, pos);
72 frac = dbuf_getu16be(f, pos+2);
73 return (double)n + ((double)frac)/65536.0;
76 static void dbg_timestamp(deark *c, i64 pos1, const char *name)
78 i64 n[6];
79 i64 pos = pos1;
80 i64 i;
81 char timestamp_buf[64];
83 for(i=0; i<6; i++) {
84 n[i] = de_getu16be_p(&pos);
87 if(n[0]!=0) {
88 struct de_timestamp ts;
90 de_make_timestamp(&ts, n[0], n[1], n[2], n[3], n[4], n[5]);
91 ts.tzcode = DE_TZCODE_UTC;
92 de_dbg_timestamp_to_string(c, &ts, timestamp_buf, sizeof(timestamp_buf), 0);
94 else {
95 de_strlcpy(timestamp_buf, "(none)", sizeof(timestamp_buf));
98 de_dbg(c, "%s: %s", name, timestamp_buf);
101 static void read_XYZ(deark *c, i64 pos, struct XYZ_type *xyz)
103 xyz->v[0] = read_s15Fixed16Number(c->infile, pos);
104 xyz->v[1] = read_s15Fixed16Number(c->infile, pos+4);
105 xyz->v[2] = read_s15Fixed16Number(c->infile, pos+8);
108 static void destroy_tagset(deark *c, struct tagset_type *tgs)
110 if(!tgs) return;
111 de_free(c, tgs->tags_seen);
112 de_free(c, tgs);
115 // flag 0x1: Include the hex value
116 // flag 0x2: Interpret 0 as (none)
117 // Returns a copy of the buf pointer.
118 static const char *format_4cc_dbgstr(const struct de_fourcc *tmp4cc,
119 char *buf, size_t buflen, unsigned int flags)
121 char str[40];
123 if((tmp4cc->id==0) && (flags&0x2))
124 de_strlcpy(str, "(none)", sizeof(str));
125 else
126 de_snprintf(str, sizeof(str), "'%s'", tmp4cc->id_dbgstr);
128 if(flags&0x1)
129 de_snprintf(buf, buflen, "0x%08x=%s", (unsigned int)tmp4cc->id, str);
130 else
131 de_strlcpy(buf, str, buflen);
133 return buf;
136 static void typedec_sf32(deark *c, struct typedec_params *p)
138 i64 count;
139 i64 i;
140 i64 pos = p->pos1 + 8;
141 double val;
143 if(p->len<8) return;
144 count = (p->len-8)/4;
145 for(i=0; i<count && i<64; i++) {
146 val = read_s15Fixed16Number(c->infile, pos);
147 pos += 4;
148 de_dbg(c, "arr[%d] = %.5f", (int)i, val);
152 static void typedec_XYZ(deark *c, struct typedec_params *p)
154 i64 xyz_count;
155 i64 k;
157 if(p->len<8) return;
158 xyz_count = (p->len-8)/12;
159 for(k=0; k<xyz_count; k++) {
160 struct XYZ_type xyz;
162 read_XYZ(c, p->pos1+8+12*k, &xyz);
163 de_dbg(c, "XYZ[%d]: %.5f, %.5f, %.5f", (int)k,
164 xyz.v[0], xyz.v[1], xyz.v[2]);
168 static void typedec_text(deark *c, struct typedec_params *p)
170 de_ucstring *s = NULL;
171 i64 textlen = p->len-8;
172 de_ext_encoding enc;
174 if(textlen<0) goto done;
176 if(p->type_id==0x75746638U) {
177 enc = DE_ENCODING_UTF8;
179 else {
180 enc = DE_ENCODING_ASCII;
183 s = ucstring_create(c);
184 dbuf_read_to_ucstring_n(c->infile, p->pos1+8, textlen, DE_DBG_MAX_STRLEN,
185 s, 0, enc);
186 ucstring_truncate_at_NUL(s);
187 de_dbg(c, "text: \"%s\"", ucstring_getpsz(s));
189 done:
190 ucstring_destroy(s);
191 return;
194 static void typedec_desc(deark *c, struct typedec_params *p)
196 de_ucstring *s = NULL;
197 i64 invdesclen, uloclen;
198 i64 langcode;
199 i64 lstrstartpos;
200 i64 bytes_to_read;
201 de_encoding encoding;
202 i64 pos = p->pos1;
204 if(p->len<12) goto done;
206 pos += 8;
208 // ASCII invariant description
209 invdesclen = de_getu32be(pos); // invariant desc. len, including NUL byte
210 pos += 4;
211 s = ucstring_create(c);
212 dbuf_read_to_ucstring_n(c->infile, pos, invdesclen, DE_DBG_MAX_STRLEN,
213 s, 0, DE_ENCODING_ASCII);
214 ucstring_truncate_at_NUL(s);
215 de_dbg(c, "invariant desc.: \"%s\"", ucstring_getpsz(s));
216 pos += invdesclen;
217 if(pos >= p->pos1+p->len) goto done;
219 // Unicode localizable description
220 ucstring_empty(s);
222 langcode = de_getu32be(pos);
223 pos += 4;
225 uloclen = de_getu32be(pos);
226 pos += 4;
228 if(uloclen>0) {
229 // TODO: How to interpret the language code?
230 de_dbg(c, "language code: %d", (int)langcode);
233 lstrstartpos = pos;
234 bytes_to_read = uloclen*2;
236 encoding = DE_ENCODING_UTF16BE;
237 if(uloclen>=1) {
238 i32 firstchar;
239 // Check for a BOM. The spec doesn't say much about the format of
240 // Unicode text in 'desc' tags. It does say that "All profile data must
241 // be encoded as big-endian", so maybe that means UTF-16LE is not
242 // allowed. In practice, some strings begin with a BOM.
243 firstchar = (i32)de_getu16be(lstrstartpos);
244 if(firstchar==0xfeff) { // UTF-16BE BOM
245 lstrstartpos += 2;
246 bytes_to_read -= 2;
248 else if(firstchar==0xffef) { // UTF-16LE BOM
249 lstrstartpos += 2;
250 bytes_to_read -= 2;
251 encoding = DE_ENCODING_UTF16LE;
255 dbuf_read_to_ucstring_n(c->infile, lstrstartpos, bytes_to_read, DE_DBG_MAX_STRLEN*2,
256 s, 0, encoding);
257 ucstring_truncate_at_NUL(s);
258 if(s->len>0) {
259 de_dbg(c, "localizable desc.: \"%s\"", ucstring_getpsz(s));
261 pos += uloclen*2;
262 if(pos >= p->pos1+p->len) goto done;
264 // Macintosh localizable description
265 // (not implemented)
267 done:
268 ucstring_destroy(s);
271 static void do_mluc_record(deark *c, lctx *d, i64 tagstartpos,
272 i64 pos, i64 recsize)
274 de_ucstring *s = NULL;
275 i64 string_len;
276 i64 string_offset;
278 s = ucstring_create(c);
280 dbuf_read_to_ucstring(c->infile, pos, 2, s, 0, DE_ENCODING_ASCII);
281 de_dbg(c, "language code: '%s'", ucstring_getpsz(s));
282 ucstring_empty(s);
284 dbuf_read_to_ucstring(c->infile, pos+2, 2, s, 0, DE_ENCODING_ASCII);
285 de_dbg(c, "country code: '%s'", ucstring_getpsz(s));
286 ucstring_empty(s);
288 string_len = de_getu32be(pos+4);
289 string_offset = de_getu32be(pos+8);
290 de_dbg(c, "string offset=%d+%d, len=%d bytes", (int)tagstartpos,
291 (int)string_offset, (int)string_len);
293 dbuf_read_to_ucstring_n(c->infile, tagstartpos+string_offset, string_len, DE_DBG_MAX_STRLEN*2,
294 s, 0, DE_ENCODING_UTF16BE);
295 ucstring_truncate_at_NUL(s);
296 de_dbg(c, "string: \"%s\"", ucstring_getpsz(s));
298 ucstring_destroy(s);
301 static void typedec_mluc(deark *c, struct typedec_params *p)
303 i64 pos = p->pos1;
304 i64 num_recs;
305 i64 recsize;
306 i64 rec_array_startpos;
307 i64 rec;
309 if(p->len<12) goto done;
310 pos += 8;
312 num_recs = de_getu32be(pos);
313 de_dbg(c, "number of records: %d", (int)num_recs);
314 pos += 4;
316 recsize = de_getu32be(pos);
317 de_dbg(c, "record size: %d", (int)recsize);
318 if(recsize<12) goto done;
319 pos += 4;
321 rec_array_startpos = pos;
323 for(rec=0; rec<num_recs; rec++) {
324 if(rec_array_startpos+rec*recsize > p->pos1+p->len) break;
326 de_dbg(c, "record #%d at %d", (int)rec, (int)pos);
327 de_dbg_indent(c, 1);
328 do_mluc_record(c, p->d, p->pos1, pos, recsize);
329 de_dbg_indent(c, -1);
330 pos += recsize;
333 done:
337 static void do_tag_data(deark *c, lctx *d, struct tagset_type *tgs, i64 tagindex,
338 i64 tagdataoffset, i64 tagdatalen);
340 static void typedec_tagArray_tagStruct(deark *c, struct typedec_params *p)
342 struct de_fourcc ty4cc;
343 struct de_fourcc tmp4cc;
344 lctx *d = p->d;
345 struct tagset_type *tgs = NULL;
346 i64 pos = p->pos1 + 8;
347 i64 endpos = p->pos1 + p->len;
348 int is_struct = (p->type_id == 0x74737472U);
349 i64 array_item_size;
350 i64 i;
351 const char *struct_name;
352 int saved_indent_level;
354 de_dbg_indent_save(c, &saved_indent_level);
355 de_zeromem(&tmp4cc, sizeof(struct de_fourcc));
357 tgs = de_malloc(c, sizeof(struct tagset_type));
358 tgs->data_area_pos = p->pos1;
359 tgs->data_area_len = p->len;
360 tgs->nesting_level = p->tgs->nesting_level+1;
361 if(tgs->nesting_level > MAX_NESTING_LEVEL) goto done;
363 if(is_struct) {
364 array_item_size = 12;
365 struct_name = "struct";
367 else {
368 array_item_size = 8;
369 struct_name = "array";
372 dbuf_read_fourcc(c->infile, pos, &ty4cc, 4, 0x0);
373 pos += 4;
374 de_dbg(c, "%s type: %s", struct_name,
375 format_4cc_dbgstr(&ty4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0));
376 tgs->num_tags = de_getu32be_p(&pos);
377 de_dbg(c, "number of elements: %"I64_FMT, tgs->num_tags);
378 if(tgs->num_tags>MAX_TAGS_PER_TAGSET) {
379 de_err(c, "Invalid or excessive number of elements: %u", (UI)tgs->num_tags);
380 goto done;
383 tgs->tags_seen = de_mallocarray(c, tgs->num_tags, sizeof(struct tag_seen_type));
385 for(i=0; i<tgs->num_tags; i++) {
386 i64 elem_pos_rel, elem_pos_abs;
387 i64 elem_dlen;
388 i64 tpos = pos;
390 if(pos+array_item_size > endpos) break;
392 if(is_struct) {
393 dbuf_read_fourcc(c->infile, pos, &tmp4cc, 4, 0x0);
394 pos += 4;
397 elem_pos_rel = de_getu32be_p(&pos);
398 elem_dlen = de_getu32be_p(&pos);
399 elem_pos_abs = p->pos1 + elem_pos_rel;
401 if(is_struct) {
402 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0);
403 de_snprintf(d->tmpbuf2, sizeof(d->tmpbuf2), " %s", d->tmpbuf1);
405 else {
406 d->tmpbuf2[0] = '\0';
408 de_dbg(c, "elem #%d%s tpos=%"I64_FMT" dpos=%"I64_FMT" (%"I64_FMT"+%"I64_FMT"), dlen=%"I64_FMT,
409 (int)i, d->tmpbuf2, tpos, elem_pos_abs, p->pos1, elem_pos_rel, elem_dlen);
411 de_dbg_indent(c, 1);
412 do_tag_data(c, d, tgs, i, elem_pos_rel, elem_dlen);
413 de_dbg_indent(c, -1);
416 done:
417 destroy_tagset(c, tgs);
418 de_dbg_indent_restore(c, saved_indent_level);
421 static void do_dict_string(deark *c, struct typedec_params *p, i64 itempos, const char *itemname)
423 de_ucstring *s = NULL;
424 i64 dpos_rel, dpos_abs, dlen;
426 dpos_rel = de_getu32be(itempos);
427 dpos_abs = p->pos1 + dpos_rel;
428 dlen = de_getu32be(itempos+4);
429 if(dpos_abs + dlen > p->pos1 + p->len) goto done;
430 s = ucstring_create(c);
431 dbuf_read_to_ucstring_n(c->infile, dpos_abs, dlen, DE_DBG_MAX_STRLEN, s, 0, DE_ENCODING_UTF16BE);
432 de_dbg(c, "%s: dpos=%"I64_FMT", dlen=%"I64_FMT", string=\"%s\"", itemname, dpos_abs, dlen,
433 ucstring_getpsz_d(s));
435 done:
436 ucstring_destroy(s);
439 static void typedec_dict(deark *c, struct typedec_params *p)
441 i64 pos = p->pos1 + 8;
442 i64 nrec;
443 i64 reclen;
444 i64 i;
446 if(p->len<16) goto done;
448 nrec = de_getu32be_p(&pos);
449 de_dbg(c, "num records: %u", (UI)nrec);
450 reclen = de_getu32be_p(&pos);
451 de_dbg(c, "rec len: %u", (UI)reclen);
452 if(reclen!=16 && reclen!=24 && reclen!=32) goto done;
453 if(pos+nrec*reclen > p->pos1 + p->len) goto done;
454 if(nrec>MAX_TAGS_PER_TAGSET) goto done;
456 for(i=0; i<nrec; i++) {
457 do_dict_string(c, p, pos, "name");
458 pos += 8;
459 do_dict_string(c, p, pos, "value");
460 pos += 8;
461 if(reclen>=24) {
462 do_dict_string(c, p, pos, "display name");
463 pos += 8;
465 if(reclen>=32) {
466 do_dict_string(c, p, pos, "display value");
467 pos += 8;
471 done:
475 static void typedec_hexdump(deark *c, struct typedec_params *p)
477 UI rsvd;
479 if(p->len<8) return;
480 rsvd = (UI)de_getu32be(p->pos1+4);
481 de_dbg(c, "reserved/etc.: 0x%08x", rsvd);
482 de_dbg_hexdump(c, c->infile, p->pos1+8, p->len-8, 256, NULL, 0x1);
485 static const struct datatypeinfo datatypeinfo_arr[] = {
486 { 0x58595a20U, 0, "XYZ", typedec_XYZ }, // XYZ
487 { 0x62666420U, 0, "ucrbg", NULL }, // bfd
488 { 0x6368726dU, 0, "chromaticity", NULL }, // chrm
489 { 0x636c726fU, 0, "colorantOrder", NULL }, // clro
490 { 0x636c7274U, 0, "colorantTable", NULL }, // clrt
491 { 0x63726469U, 0, "crdInfo", NULL }, // crdi
492 { 0x63757266U, 0, "segmentedCurve", NULL}, // curf
493 { 0x63757276U, 0, "curve", NULL }, // curv
494 { 0x64657363U, 0, "textDescription", typedec_desc }, // desc
495 { 0x64617461U, 0, "data", NULL }, // data
496 { 0x64657673U, 0, "deviceSettings", NULL }, // devs
497 { 0x64696374U, 0, "dictionary array", typedec_dict }, // dict
498 { 0x6474696dU, 0, "dateTime", NULL }, // dtim
499 { 0x666c3136U, 0, "float16Array", NULL }, // fl16
500 { 0x666c3332U, 0, "float32Array", NULL }, // fl32
501 { 0x666c3634U, 0, "float64Array", NULL }, // fl64
502 { 0x67626420U, 0, "gamutBoundaryDescription", NULL }, // gbd
503 { 0x6d414220U, 0, "lutAToB", NULL }, // mAB
504 { 0x6d424120U, 0, "lutBToA", NULL }, // mBA
505 { 0x6d656173U, 0, "measurement", NULL }, // meas
506 { 0x6d667431U, 0, "lut8", NULL }, // mft1
507 { 0x6d667432U, 0, "lut16", NULL }, // mft2
508 { 0x6d6c7563U, 0, "multiLocalizedUnicode", typedec_mluc }, // mluc
509 { 0x6d706574U, 0, "multiProcessElements", NULL }, // mpet
510 { 0x6e636c32U, 0, "namedColor2", NULL }, // ncl2
511 { 0x6e636f6cU, 0, "namedColor", NULL }, // ncol
512 { 0x70617261U, 0, "parametricCurve", NULL }, // para
513 { 0x70736571U, 0, "profileSequenceDesc", NULL }, // pseq
514 { 0x70736964U, 0, "profileSequenceIdentifier", NULL }, // psid
515 { 0x72637332U, 0, "responseCurveSet16", NULL }, // rcs2
516 { 0x73663332U, 0, "s15Fixed16Array", typedec_sf32 }, // sf32
517 { 0x7363726eU, 0, "screening", NULL }, // scrn
518 { 0x73696720U, 0, "signature", NULL }, // sig
519 { 0x7376636eU, 0, "spectralViewingConditions", NULL }, // svcn
520 { 0x74617279U, 0, "tagArray", typedec_tagArray_tagStruct }, // tary
521 { 0x74657874U, 0, "text", typedec_text }, // text
522 { 0x74737472U, 0, "tagStruct", typedec_tagArray_tagStruct }, // tstr
523 { 0x75663332U, 0, "u16Fixed16Array", NULL }, // uf32
524 { 0x75693038U, 0, "uInt8Array", NULL }, // ui08
525 { 0x75693136U, 0, "uInt16Array", NULL }, // ui16
526 { 0x75693332U, 0, "uInt32Array", NULL }, // ui32
527 { 0x75693634U, 0, "uInt64Array", NULL }, // ui64
528 { 0x75743136U, 0, "utf16", NULL }, // ut16
529 { 0x75746638U, 0, "utf8", typedec_text }, // utf8
530 { 0x76636774U, 0, "Video Card Gamma Type", NULL }, // vcgt (Apple)
531 { 0x76696577U, 0, "viewingConditions", NULL } // view
534 static const struct taginfo taginfo_arr[] = {
535 { 0x41324230U, 0, "AToB0", NULL }, // A2B0
536 { 0x41324231U, 0, "AToB1", NULL }, // A2B1
537 { 0x41324232U, 0, "AToB2", NULL }, // A2B2
538 { 0x41324233U, 0, "AToB3", NULL }, // A2B3
539 { 0x42324130U, 0, "BToA0", NULL }, // B2A0
540 { 0x42324131U, 0, "BToA1", NULL }, // B2A1
541 { 0x42324132U, 0, "BToA2", NULL }, // B2A2
542 { 0x42324133U, 0, "BToA3", NULL }, // B2A3
543 { 0x42324430U, 0, "BToD0", NULL }, // B2D0
544 { 0x42324431U, 0, "BToD1", NULL }, // B2D1
545 { 0x42324432U, 0, "BToD2", NULL }, // B2D2
546 { 0x42324433U, 0, "BToD3", NULL }, // B2D3
547 { 0x44324230U, 0, "DToB0", NULL }, // D2B0
548 { 0x44324231U, 0, "DToB1", NULL }, // D2B1
549 { 0x44324232U, 0, "DToB2", NULL }, // D2B2
550 { 0x44324233U, 0, "DToB3", NULL }, // D2B3
551 { 0x62545243U, 0, "blueTRC", NULL }, // bTRC
552 { 0x6258595aU, 0x1, "blueColorant", NULL }, // bXYZ
553 { 0x6258595aU, 0x2, "blueMatrixColumn", NULL }, // bXYZ
554 { 0x62666420U, 0, "ucrbg", NULL }, // bfd
555 { 0x626b7074U, 0, "mediaBlackPoint", NULL }, // bkpt
556 { 0x63327370U, 0, "customToStandardPcc", NULL }, // c2sp
557 { 0x63616c74U, 0, "calibrationDateTime", NULL }, // calt
558 { 0x63657074U, 0, "colorEncodingParams", NULL }, // cept
559 { 0x63686164U, 0, "chromaticAdaptation", NULL }, // chad
560 { 0x6368726dU, 0, "chromaticity", NULL }, // chrm
561 { 0x63696973U, 0, "colorimetricIntentImageState", NULL }, // ciis
562 { 0x636c6f74U, 0, "colorantTableOut", NULL }, // clot
563 { 0x636c726fU, 0, "colorantOrder", NULL }, // clro
564 { 0x636c7274U, 0, "colorantTable", NULL }, // clrt
565 { 0x63707274U, 0, "copyright", NULL }, // cprt
566 { 0x63726469U, 0, "crdInfo", NULL }, // crdi
567 { 0x63736e6dU, 0, "colorSpaceName", NULL }, // csnm
568 { 0x64657363U, 0, "profileDescription", NULL }, // desc
569 { 0x64657673U, 0, "deviceSettings", NULL }, // devs
570 { 0x646d6464U, 0, "deviceModelDesc", NULL }, // dmdd
571 { 0x646d6e64U, 0, "deviceMfgDesc", NULL }, // dmnd
572 { 0x67616d74U, 0, "gamut", NULL }, // gamt
573 { 0x67626431U, 0, "gamutBoundaryDescription1", NULL }, // gbd1
574 { 0x67545243U, 0, "greenTRC", NULL }, // gTRC
575 { 0x6758595aU, 0x1, "greenColorant", NULL }, // gXYZ
576 { 0x6758595aU, 0x2, "greenMatrixColumn", NULL }, // gXYZ
577 { 0x6b545243U, 0, "grayTRC", NULL }, // kTRC
578 { 0x6c756d69U, 0, "luminance", NULL }, // lumi
579 { 0x6d656173U, 0, "measurement", NULL }, // meas
580 { 0x6e636c32U, 0, "namedColor2", NULL }, // ncl2
581 { 0x6e636f6cU, 0, "namedColor", NULL }, // ncol
582 { 0x70726530U, 0, "preview0", NULL }, // pre0
583 { 0x70726531U, 0, "preview1", NULL }, // pre1
584 { 0x70726532U, 0, "preview2", NULL }, // pre2
585 { 0x70733269U, 0, "ps2RenderingIntent", NULL }, // ps2i
586 { 0x70733273U, 0, "ps2CSA", NULL }, // ps2s
587 { 0x70736430U, 0, "ps2CRD0", NULL }, // psd0
588 { 0x70736431U, 0, "ps2CRD1", NULL }, // psd1
589 { 0x70736432U, 0, "ps2CRD2", NULL }, // psd2
590 { 0x70736433U, 0, "ps2CRD3", NULL }, // psd3
591 { 0x70736571U, 0, "profileSequenceDesc", NULL }, // pseq
592 { 0x70736964U, 0, "profileSequenceIdentifier", NULL }, // psid
593 { 0x72545243U, 0, "redTRC", NULL }, // rTRC
594 { 0x7258595aU, 0x1, "redColorant", NULL }, // rXYZ
595 { 0x7258595aU, 0x2, "redMatrixColumn", NULL }, // rXYZ
596 { 0x72657370U, 0, "outputResponse", NULL }, // resp
597 { 0x72666e6dU, 0, "referenceName", NULL }, // rfnm
598 { 0x72696730U, 0, "perceptualRenderingIntentGamut", NULL }, // rig0
599 { 0x72696732U, 0, "saturationRenderingIntentGamut", NULL }, // rig2
600 { 0x73326370U, 0, "standardToCustomPcc", NULL }, // s2cp
601 { 0x73637264U, 0, "screeningDesc", NULL }, // scrd
602 { 0x7363726eU, 0, "screening", NULL }, // scrn
603 { 0x7376636eU, 0, "spectralViewingConditions", NULL }, // svcn
604 { 0x74617267U, 0, "charTarget", NULL }, // targ
605 { 0x74656368U, 0, "technology", NULL }, // tech
606 { 0x76636774U, 0, "Video Card Gamma Type", NULL }, // vcgt (Apple)
607 { 0x76696577U, 0, "viewingConditions", NULL }, // view
608 { 0x76756564U, 0, "viewingCondDesc", NULL }, // vued
609 { 0x77747074U, 0, "mediaWhitePoint", NULL } // wtpt
612 static void do_read_header(deark *c, lctx *d, i64 pos)
614 u32 profile_ver_raw;
615 i64 x;
616 u64 xu;
617 struct de_fourcc tmp4cc;
618 UI tmpflags;
619 const char *name;
620 struct XYZ_type xyz;
622 de_dbg(c, "header at %d", (int)pos);
623 de_dbg_indent(c, 1);
625 d->profile_size = de_getu32be(pos+0);
626 de_dbg(c, "profile size: %"I64_FMT, d->profile_size);
628 dbuf_read_fourcc(c->infile, pos+4, &tmp4cc, 4, 0x0);
629 de_dbg(c, "preferred CMM type: %s",
630 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
632 profile_ver_raw = (u32)de_getu32be(pos+8);
633 d->profile_ver_major = 10*((profile_ver_raw&0xf0000000U)>>28) +
634 ((profile_ver_raw&0x0f000000U)>>24);
635 d->profile_ver_minor = (profile_ver_raw&0x00f00000U)>>20;
636 d->profile_ver_bugfix = (profile_ver_raw&0x000f0000U)>>16;
637 de_dbg(c, "profile version: %u.%u.%u", d->profile_ver_major,
638 d->profile_ver_minor, d->profile_ver_bugfix);
640 dbuf_read_fourcc(c->infile, pos+12, &tmp4cc, 4, 0x0);
641 de_dbg(c, "profile/device class: %s",
642 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x1));
644 dbuf_read_fourcc(c->infile, pos+16, &tmp4cc, 4, 0x0);
645 tmpflags = 0x1;
646 if(d->profile_ver_major>=5) tmpflags |= 0x2;
647 de_dbg(c, "colour space: %s",
648 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), tmpflags));
650 dbuf_read_fourcc(c->infile, pos+20, &tmp4cc, 4, 0x0);
651 tmpflags = 0x1;
652 if(d->profile_ver_major>=5) tmpflags |= 0x2;
653 de_dbg(c, "PCS: %s",
654 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), tmpflags));
656 dbg_timestamp(c, pos+24, "creation time");
658 dbuf_read_fourcc(c->infile, pos+36, &tmp4cc, 4, 0x0);
659 de_dbg(c, "file signature: %s",
660 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x1));
662 dbuf_read_fourcc(c->infile, pos+40, &tmp4cc, 4, 0x0);
663 de_dbg(c, "primary platform: %s",
664 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
666 // TODO: Decode profile flags
667 x = de_getu32be(pos+44);
668 de_dbg(c, "profile flags: 0x%08x", (UI)x);
670 dbuf_read_fourcc(c->infile, pos+48, &tmp4cc, 4, 0x0);
671 de_dbg(c, "device manufacturer: %s",
672 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
674 dbuf_read_fourcc(c->infile, pos+52, &tmp4cc, 4, 0x0);
675 de_dbg(c, "device model: %s",
676 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
678 // TODO: Decode device attributes
679 xu = dbuf_getu64be(c->infile, pos+56);
680 de_dbg(c, "device attribs: 0x%016"U64_FMTx, xu);
682 x = de_getu32be(pos+64);
683 switch(x) {
684 case 0: name="perceptual"; break;
685 case 1: name="relative colorimetric"; break;
686 case 2: name="saturation"; break;
687 case 3: name="absolute colorimetric"; break;
688 default: name="?"; break;
690 de_dbg(c, "rendering intent: %d (%s)", (int)x, name);
692 read_XYZ(c, pos+68, &xyz);
693 de_dbg(c, "illuminant: %.5f, %.5f, %.5f", xyz.v[0], xyz.v[1], xyz.v[2]);
695 dbuf_read_fourcc(c->infile, pos+80, &tmp4cc, 4, 0x0);
696 de_dbg(c, "profile creator: %s",
697 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
699 // TODO: pos=84-99 Profile ID
701 de_dbg_indent(c, -1);
704 static const struct datatypeinfo *lookup_datatypeinfo(u32 id)
706 size_t k;
707 for(k=0; k<DE_ARRAYCOUNT(datatypeinfo_arr); k++) {
708 if(datatypeinfo_arr[k].id == id) {
709 return &datatypeinfo_arr[k];
712 return NULL;
715 static const struct taginfo *lookup_taginfo(lctx *d, u32 id)
717 size_t k;
718 for(k=0; k<DE_ARRAYCOUNT(taginfo_arr); k++) {
719 if((taginfo_arr[k].flags & 0x1) && d->profile_ver_major>=4) continue;
720 if((taginfo_arr[k].flags & 0x2) && d->profile_ver_major<4) continue;
721 if(taginfo_arr[k].id == id) {
722 return &taginfo_arr[k];
725 return NULL;
728 static int is_duplicate_data(deark *c, lctx *d, struct tagset_type *tgs,
729 i64 tagindex, i64 tagdataoffset, i64 tagdatalen,
730 i64 *idx_of_dup)
732 i64 k;
734 for(k=0; k<tagindex && k<tgs->num_tags; k++) {
735 if(tgs->tags_seen[k].offset==tagdataoffset &&
736 tgs->tags_seen[k].len==tagdatalen)
738 *idx_of_dup = k;
739 return 1;
743 *idx_of_dup = -1;
744 return 0;
747 // tagdataoffset is relative to tgs->data_area_pos
748 static void do_tag_data(deark *c, lctx *d, struct tagset_type *tgs, i64 tagindex,
749 i64 tagdataoffset, i64 tagdatalen)
751 struct de_fourcc tagtype4cc;
752 struct typedec_params tdp;
753 const struct datatypeinfo *dti;
754 const char *dtname;
755 i64 idx_of_dup;
757 if(tagindex >= tgs->num_tags) return;
758 if(tagdatalen<1) goto done;
759 if(tagdataoffset+tagdatalen > tgs->data_area_len) {
760 de_err(c, "Tag #%d data exceeds its bounds", (int)tagindex);
761 goto done;
763 if(is_duplicate_data(c, d, tgs, tagindex, tagdataoffset, tagdatalen, &idx_of_dup)) {
764 de_dbg(c, "[data is a duplicate of tag #%d]", (int)idx_of_dup);
765 goto done;
768 if(tagdatalen<4) goto done;
770 dbuf_read_fourcc(c->infile, tgs->data_area_pos+tagdataoffset, &tagtype4cc, 4, 0x0);
771 dti = lookup_datatypeinfo(tagtype4cc.id);
772 if(dti && dti->name) dtname=dti->name;
773 else dtname="?";
774 de_dbg(c, "data type: %s (%s)",
775 format_4cc_dbgstr(&tagtype4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x0), dtname);
777 de_zeromem(&tdp, sizeof(struct typedec_params));
778 tdp.d = d;
779 tdp.tgs = tgs;
780 tdp.pos1 = tgs->data_area_pos+tagdataoffset;
781 tdp.len = tagdatalen;
782 tdp.type_id = tagtype4cc.id;
784 if(dti && dti->dtdfn) {
785 dti->dtdfn(c, &tdp);
787 else if(c->debug_level>=2) {
788 typedec_hexdump(c, &tdp);
791 done:
792 tgs->tags_seen[tagindex].offset = tagdataoffset;
793 tgs->tags_seen[tagindex].len = tagdatalen;
796 static void do_main_tag(deark *c, lctx *d, struct tagset_type *tgs,
797 i64 tagindex, i64 pos_in_tagtable)
799 struct de_fourcc tag4cc;
800 const struct taginfo *ti;
801 const char *tname;
802 i64 tagdataoffset;
803 i64 tagdatalen;
805 dbuf_read_fourcc(c->infile, pos_in_tagtable, &tag4cc, 4, 0x0);
806 tagdataoffset = de_getu32be(pos_in_tagtable+4);
807 tagdatalen = de_getu32be(pos_in_tagtable+8);
808 ti = lookup_taginfo(d, tag4cc.id);
809 if(ti && ti->name)
810 tname = ti->name;
811 else
812 tname = "?";
813 de_dbg(c, "tag #%d %s (%s) tpos=%"I64_FMT" dpos=%"I64_FMT" dlen=%"I64_FMT, (int)tagindex,
814 format_4cc_dbgstr(&tag4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x0), tname,
815 pos_in_tagtable, tgs->data_area_pos+tagdataoffset, tagdatalen);
817 de_dbg_indent(c, 1);
818 do_tag_data(c, d, tgs, tagindex, tagdataoffset, tagdatalen);
819 de_dbg_indent(c, -1);
822 static void do_read_main_tags(deark *c, lctx *d, i64 pos1)
824 i64 tagindex;
825 struct tagset_type *tgs = NULL;
827 de_dbg(c, "tag table at %"I64_FMT, pos1);
828 de_dbg_indent(c, 1);
830 tgs = de_malloc(c, sizeof(struct tagset_type));
831 tgs->data_area_pos = 0;
832 if(d->profile_size!=0) {
833 tgs->data_area_len = de_min_int(d->profile_size, c->infile->len);
835 else {
836 tgs->data_area_len = c->infile->len;
838 tgs->num_tags = de_getu32be(pos1);
839 de_dbg(c, "number of tags: %d", (int)tgs->num_tags);
840 if(tgs->num_tags>MAX_TAGS_PER_TAGSET) {
841 de_err(c, "Invalid or excessive number of tags: %d", (int)tgs->num_tags);
842 goto done;
844 de_dbg(c, "expected start of data segment: %d", (int)(pos1+4+12*tgs->num_tags));
846 // Make a place to record some information about each tag we encounter in the table.
847 tgs->tags_seen = de_mallocarray(c, tgs->num_tags, sizeof(struct tag_seen_type));
849 for(tagindex=0; tagindex<tgs->num_tags; tagindex++) {
850 do_main_tag(c, d, tgs, tagindex, pos1+4+12*tagindex);
853 done:
854 destroy_tagset(c, tgs);
855 de_dbg_indent(c, -1);
858 static void de_run_iccprofile(deark *c, de_module_params *mparams)
860 lctx *d = NULL;
861 i64 pos;
863 d = de_malloc(c, sizeof(lctx));
865 pos = 0;
866 do_read_header(c, d, pos);
867 pos += 128;
868 do_read_main_tags(c, d, pos);
870 de_free(c, d);
873 static int de_identify_iccprofile(deark *c)
875 if(!dbuf_memcmp(c->infile, 36, "acsp", 4))
876 return 85;
877 return 0;
880 void de_module_iccprofile(deark *c, struct deark_module_info *mi)
882 mi->id = "iccprofile";
883 mi->desc = "ICC profile";
884 mi->run_fn = de_run_iccprofile;
885 mi->identify_fn = de_identify_iccprofile;