Refactoring to use de_read_simple_palette
[deark.git] / modules / iccprofile.c
blobab2a87c4cbae0caf942e32f4a21b61f072d6d6bb
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_XYZ(deark *c, struct typedec_params *p)
138 i64 xyz_count;
139 i64 k;
141 if(p->len<8) return;
142 xyz_count = (p->len-8)/12;
143 for(k=0; k<xyz_count; k++) {
144 struct XYZ_type xyz;
146 read_XYZ(c, p->pos1+8+12*k, &xyz);
147 de_dbg(c, "XYZ[%d]: %.5f, %.5f, %.5f", (int)k,
148 xyz.v[0], xyz.v[1], xyz.v[2]);
152 static void typedec_text(deark *c, struct typedec_params *p)
154 de_ucstring *s = NULL;
155 i64 textlen = p->len-8;
156 de_ext_encoding enc;
158 if(textlen<0) goto done;
160 if(p->type_id==0x75746638U) {
161 enc = DE_ENCODING_UTF8;
163 else {
164 enc = DE_ENCODING_ASCII;
167 s = ucstring_create(c);
168 dbuf_read_to_ucstring_n(c->infile, p->pos1+8, textlen, DE_DBG_MAX_STRLEN,
169 s, 0, enc);
170 ucstring_truncate_at_NUL(s);
171 de_dbg(c, "text: \"%s\"", ucstring_getpsz(s));
173 done:
174 ucstring_destroy(s);
175 return;
178 static void typedec_desc(deark *c, struct typedec_params *p)
180 de_ucstring *s = NULL;
181 i64 invdesclen, uloclen;
182 i64 langcode;
183 i64 lstrstartpos;
184 i64 bytes_to_read;
185 de_encoding encoding;
186 i64 pos = p->pos1;
188 if(p->len<12) goto done;
190 pos += 8;
192 // ASCII invariant description
193 invdesclen = de_getu32be(pos); // invariant desc. len, including NUL byte
194 pos += 4;
195 s = ucstring_create(c);
196 dbuf_read_to_ucstring_n(c->infile, pos, invdesclen, DE_DBG_MAX_STRLEN,
197 s, 0, DE_ENCODING_ASCII);
198 ucstring_truncate_at_NUL(s);
199 de_dbg(c, "invariant desc.: \"%s\"", ucstring_getpsz(s));
200 pos += invdesclen;
201 if(pos >= p->pos1+p->len) goto done;
203 // Unicode localizable description
204 ucstring_empty(s);
206 langcode = de_getu32be(pos);
207 pos += 4;
209 uloclen = de_getu32be(pos);
210 pos += 4;
212 if(uloclen>0) {
213 // TODO: How to interpret the language code?
214 de_dbg(c, "language code: %d", (int)langcode);
217 lstrstartpos = pos;
218 bytes_to_read = uloclen*2;
220 encoding = DE_ENCODING_UTF16BE;
221 if(uloclen>=1) {
222 i32 firstchar;
223 // Check for a BOM. The spec doesn't say much about the format of
224 // Unicode text in 'desc' tags. It does say that "All profile data must
225 // be encoded as big-endian", so maybe that means UTF-16LE is not
226 // allowed. In practice, some strings begin with a BOM.
227 firstchar = (i32)de_getu16be(lstrstartpos);
228 if(firstchar==0xfeff) { // UTF-16BE BOM
229 lstrstartpos += 2;
230 bytes_to_read -= 2;
232 else if(firstchar==0xffef) { // UTF-16LE BOM
233 lstrstartpos += 2;
234 bytes_to_read -= 2;
235 encoding = DE_ENCODING_UTF16LE;
239 dbuf_read_to_ucstring_n(c->infile, lstrstartpos, bytes_to_read, DE_DBG_MAX_STRLEN*2,
240 s, 0, encoding);
241 ucstring_truncate_at_NUL(s);
242 if(s->len>0) {
243 de_dbg(c, "localizable desc.: \"%s\"", ucstring_getpsz(s));
245 pos += uloclen*2;
246 if(pos >= p->pos1+p->len) goto done;
248 // Macintosh localizable description
249 // (not implemented)
251 done:
252 ucstring_destroy(s);
255 static void do_mluc_record(deark *c, lctx *d, i64 tagstartpos,
256 i64 pos, i64 recsize)
258 de_ucstring *s = NULL;
259 i64 string_len;
260 i64 string_offset;
262 s = ucstring_create(c);
264 dbuf_read_to_ucstring(c->infile, pos, 2, s, 0, DE_ENCODING_ASCII);
265 de_dbg(c, "language code: '%s'", ucstring_getpsz(s));
266 ucstring_empty(s);
268 dbuf_read_to_ucstring(c->infile, pos+2, 2, s, 0, DE_ENCODING_ASCII);
269 de_dbg(c, "country code: '%s'", ucstring_getpsz(s));
270 ucstring_empty(s);
272 string_len = de_getu32be(pos+4);
273 string_offset = de_getu32be(pos+8);
274 de_dbg(c, "string offset=%d+%d, len=%d bytes", (int)tagstartpos,
275 (int)string_offset, (int)string_len);
277 dbuf_read_to_ucstring_n(c->infile, tagstartpos+string_offset, string_len, DE_DBG_MAX_STRLEN*2,
278 s, 0, DE_ENCODING_UTF16BE);
279 ucstring_truncate_at_NUL(s);
280 de_dbg(c, "string: \"%s\"", ucstring_getpsz(s));
282 ucstring_destroy(s);
285 static void typedec_mluc(deark *c, struct typedec_params *p)
287 i64 pos = p->pos1;
288 i64 num_recs;
289 i64 recsize;
290 i64 rec_array_startpos;
291 i64 rec;
293 if(p->len<12) goto done;
294 pos += 8;
296 num_recs = de_getu32be(pos);
297 de_dbg(c, "number of records: %d", (int)num_recs);
298 pos += 4;
300 recsize = de_getu32be(pos);
301 de_dbg(c, "record size: %d", (int)recsize);
302 if(recsize<12) goto done;
303 pos += 4;
305 rec_array_startpos = pos;
307 for(rec=0; rec<num_recs; rec++) {
308 if(rec_array_startpos+rec*recsize > p->pos1+p->len) break;
310 de_dbg(c, "record #%d at %d", (int)rec, (int)pos);
311 de_dbg_indent(c, 1);
312 do_mluc_record(c, p->d, p->pos1, pos, recsize);
313 de_dbg_indent(c, -1);
314 pos += recsize;
317 done:
321 static void do_tag_data(deark *c, lctx *d, struct tagset_type *tgs, i64 tagindex,
322 i64 tagdataoffset, i64 tagdatalen);
324 static void typedec_tagArray_tagStruct(deark *c, struct typedec_params *p)
326 struct de_fourcc ty4cc;
327 struct de_fourcc tmp4cc;
328 lctx *d = p->d;
329 struct tagset_type *tgs = NULL;
330 i64 pos = p->pos1 + 8;
331 i64 endpos = p->pos1 + p->len;
332 int is_struct = (p->type_id == 0x74737472U);
333 i64 array_item_size;
334 i64 i;
335 const char *struct_name;
336 int saved_indent_level;
338 de_dbg_indent_save(c, &saved_indent_level);
339 de_zeromem(&tmp4cc, sizeof(struct de_fourcc));
341 tgs = de_malloc(c, sizeof(struct tagset_type));
342 tgs->data_area_pos = p->pos1;
343 tgs->data_area_len = p->len;
344 tgs->nesting_level = p->tgs->nesting_level+1;
345 if(tgs->nesting_level > MAX_NESTING_LEVEL) goto done;
347 if(is_struct) {
348 array_item_size = 12;
349 struct_name = "struct";
351 else {
352 array_item_size = 8;
353 struct_name = "array";
356 dbuf_read_fourcc(c->infile, pos, &ty4cc, 4, 0x0);
357 pos += 4;
358 de_dbg(c, "%s type: %s", struct_name,
359 format_4cc_dbgstr(&ty4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0));
360 tgs->num_tags = de_getu32be_p(&pos);
361 de_dbg(c, "number of elements: %"I64_FMT, tgs->num_tags);
362 if(tgs->num_tags>MAX_TAGS_PER_TAGSET) {
363 de_err(c, "Invalid or excessive number of elements: %u", (UI)tgs->num_tags);
364 goto done;
367 tgs->tags_seen = de_mallocarray(c, tgs->num_tags, sizeof(struct tag_seen_type));
369 for(i=0; i<tgs->num_tags; i++) {
370 i64 elem_pos_rel, elem_pos_abs;
371 i64 elem_dlen;
372 i64 tpos = pos;
374 if(pos+array_item_size > endpos) break;
376 if(is_struct) {
377 dbuf_read_fourcc(c->infile, pos, &tmp4cc, 4, 0x0);
378 pos += 4;
381 elem_pos_rel = de_getu32be_p(&pos);
382 elem_dlen = de_getu32be_p(&pos);
383 elem_pos_abs = p->pos1 + elem_pos_rel;
385 if(is_struct) {
386 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0);
387 de_snprintf(d->tmpbuf2, sizeof(d->tmpbuf2), " %s", d->tmpbuf1);
389 else {
390 d->tmpbuf2[0] = '\0';
392 de_dbg(c, "elem #%d%s tpos=%"I64_FMT" dpos=%"I64_FMT" (%"I64_FMT"+%"I64_FMT"), dlen=%"I64_FMT,
393 (int)i, d->tmpbuf2, tpos, elem_pos_abs, p->pos1, elem_pos_rel, elem_dlen);
395 de_dbg_indent(c, 1);
396 do_tag_data(c, d, tgs, i, elem_pos_rel, elem_dlen);
397 de_dbg_indent(c, -1);
400 done:
401 destroy_tagset(c, tgs);
402 de_dbg_indent_restore(c, saved_indent_level);
405 static void do_dict_string(deark *c, struct typedec_params *p, i64 itempos, const char *itemname)
407 de_ucstring *s = NULL;
408 i64 dpos_rel, dpos_abs, dlen;
410 dpos_rel = de_getu32be(itempos);
411 dpos_abs = p->pos1 + dpos_rel;
412 dlen = de_getu32be(itempos+4);
413 if(dpos_abs + dlen > p->pos1 + p->len) goto done;
414 s = ucstring_create(c);
415 dbuf_read_to_ucstring_n(c->infile, dpos_abs, dlen, DE_DBG_MAX_STRLEN, s, 0, DE_ENCODING_UTF16BE);
416 de_dbg(c, "%s: dpos=%"I64_FMT", dlen=%"I64_FMT", string=\"%s\"", itemname, dpos_abs, dlen,
417 ucstring_getpsz_d(s));
419 done:
420 ucstring_destroy(s);
423 static void typedec_dict(deark *c, struct typedec_params *p)
425 i64 pos = p->pos1 + 8;
426 i64 nrec;
427 i64 reclen;
428 i64 i;
430 if(p->len<16) goto done;
432 nrec = de_getu32be_p(&pos);
433 de_dbg(c, "num records: %u", (UI)nrec);
434 reclen = de_getu32be_p(&pos);
435 de_dbg(c, "rec len: %u", (UI)reclen);
436 if(reclen!=16 && reclen!=24 && reclen!=32) goto done;
437 if(pos+nrec*reclen > p->pos1 + p->len) goto done;
438 if(nrec>MAX_TAGS_PER_TAGSET) goto done;
440 for(i=0; i<nrec; i++) {
441 do_dict_string(c, p, pos, "name");
442 pos += 8;
443 do_dict_string(c, p, pos, "value");
444 pos += 8;
445 if(reclen>=24) {
446 do_dict_string(c, p, pos, "display name");
447 pos += 8;
449 if(reclen>=32) {
450 do_dict_string(c, p, pos, "display value");
451 pos += 8;
455 done:
459 static void typedec_hexdump(deark *c, struct typedec_params *p)
461 UI rsvd;
463 if(p->len<8) return;
464 rsvd = (UI)de_getu32be(p->pos1+4);
465 de_dbg(c, "reserved/etc.: 0x%08x", rsvd);
466 de_dbg_hexdump(c, c->infile, p->pos1+8, p->len-8, 256, NULL, 0x1);
469 static const struct datatypeinfo datatypeinfo_arr[] = {
470 { 0x58595a20U, 0, "XYZ", typedec_XYZ }, // XYZ
471 { 0x62666420U, 0, "ucrbg", NULL }, // bfd
472 { 0x6368726dU, 0, "chromaticity", NULL }, // chrm
473 { 0x636c726fU, 0, "colorantOrder", NULL }, // clro
474 { 0x636c7274U, 0, "colorantTable", NULL }, // clrt
475 { 0x63726469U, 0, "crdInfo", NULL }, // crdi
476 { 0x63757266U, 0, "segmentedCurve", NULL}, // curf
477 { 0x63757276U, 0, "curve", NULL }, // curv
478 { 0x64657363U, 0, "textDescription", typedec_desc }, // desc
479 { 0x64617461U, 0, "data", NULL }, // data
480 { 0x64657673U, 0, "deviceSettings", NULL }, // devs
481 { 0x64696374U, 0, "dictionary array", typedec_dict }, // dict
482 { 0x6474696dU, 0, "dateTime", NULL }, // dtim
483 { 0x666c3136U, 0, "float16Array", NULL }, // fl16
484 { 0x666c3332U, 0, "float32Array", NULL }, // fl32
485 { 0x666c3634U, 0, "float64Array", NULL }, // fl64
486 { 0x67626420U, 0, "gamutBoundaryDescription", NULL }, // gbd
487 { 0x6d414220U, 0, "lutAToB", NULL }, // mAB
488 { 0x6d424120U, 0, "lutBToA", NULL }, // mBA
489 { 0x6d656173U, 0, "measurement", NULL }, // meas
490 { 0x6d667431U, 0, "lut8", NULL }, // mft1
491 { 0x6d667432U, 0, "lut16", NULL }, // mft2
492 { 0x6d6c7563U, 0, "multiLocalizedUnicode", typedec_mluc }, // mluc
493 { 0x6d706574U, 0, "multiProcessElements", NULL }, // mpet
494 { 0x6e636c32U, 0, "namedColor2", NULL }, // ncl2
495 { 0x6e636f6cU, 0, "namedColor", NULL }, // ncol
496 { 0x70617261U, 0, "parametricCurve", NULL }, // para
497 { 0x70736571U, 0, "profileSequenceDesc", NULL }, // pseq
498 { 0x70736964U, 0, "profileSequenceIdentifier", NULL }, // psid
499 { 0x72637332U, 0, "responseCurveSet16", NULL }, // rcs2
500 { 0x73663332U, 0, "s15Fixed16Array", NULL }, // sf32
501 { 0x7363726eU, 0, "screening", NULL }, // scrn
502 { 0x73696720U, 0, "signature", NULL }, // sig
503 { 0x7376636eU, 0, "spectralViewingConditions", NULL }, // svcn
504 { 0x74617279U, 0, "tagArray", typedec_tagArray_tagStruct }, // tary
505 { 0x74657874U, 0, "text", typedec_text }, // text
506 { 0x74737472U, 0, "tagStruct", typedec_tagArray_tagStruct }, // tstr
507 { 0x75663332U, 0, "u16Fixed16Array", NULL }, // uf32
508 { 0x75693038U, 0, "uInt8Array", NULL }, // ui08
509 { 0x75693136U, 0, "uInt16Array", NULL }, // ui16
510 { 0x75693332U, 0, "uInt32Array", NULL }, // ui32
511 { 0x75693634U, 0, "uInt64Array", NULL }, // ui64
512 { 0x75743136U, 0, "utf16", NULL }, // ut16
513 { 0x75746638U, 0, "utf8", typedec_text }, // utf8
514 { 0x76636774U, 0, "Video Card Gamma Type", NULL }, // vcgt (Apple)
515 { 0x76696577U, 0, "viewingConditions", NULL } // view
518 static const struct taginfo taginfo_arr[] = {
519 { 0x41324230U, 0, "AToB0", NULL }, // A2B0
520 { 0x41324231U, 0, "AToB1", NULL }, // A2B1
521 { 0x41324232U, 0, "AToB2", NULL }, // A2B2
522 { 0x41324233U, 0, "AToB3", NULL }, // A2B3
523 { 0x42324130U, 0, "BToA0", NULL }, // B2A0
524 { 0x42324131U, 0, "BToA1", NULL }, // B2A1
525 { 0x42324132U, 0, "BToA2", NULL }, // B2A2
526 { 0x42324133U, 0, "BToA3", NULL }, // B2A3
527 { 0x42324430U, 0, "BToD0", NULL }, // B2D0
528 { 0x42324431U, 0, "BToD1", NULL }, // B2D1
529 { 0x42324432U, 0, "BToD2", NULL }, // B2D2
530 { 0x42324433U, 0, "BToD3", NULL }, // B2D3
531 { 0x44324230U, 0, "DToB0", NULL }, // D2B0
532 { 0x44324231U, 0, "DToB1", NULL }, // D2B1
533 { 0x44324232U, 0, "DToB2", NULL }, // D2B2
534 { 0x44324233U, 0, "DToB3", NULL }, // D2B3
535 { 0x62545243U, 0, "blueTRC", NULL }, // bTRC
536 { 0x6258595aU, 0x1, "blueColorant", NULL }, // bXYZ
537 { 0x6258595aU, 0x2, "blueMatrixColumn", NULL }, // bXYZ
538 { 0x62666420U, 0, "ucrbg", NULL }, // bfd
539 { 0x626b7074U, 0, "mediaBlackPoint", NULL }, // bkpt
540 { 0x63327370U, 0, "customToStandardPcc", NULL }, // c2sp
541 { 0x63616c74U, 0, "calibrationDateTime", NULL }, // calt
542 { 0x63657074U, 0, "colorEncodingParams", NULL }, // cept
543 { 0x63686164U, 0, "chromaticAdaptation", NULL }, // chad
544 { 0x6368726dU, 0, "chromaticity", NULL }, // chrm
545 { 0x63696973U, 0, "colorimetricIntentImageState", NULL }, // ciis
546 { 0x636c6f74U, 0, "colorantTableOut", NULL }, // clot
547 { 0x636c726fU, 0, "colorantOrder", NULL }, // clro
548 { 0x636c7274U, 0, "colorantTable", NULL }, // clrt
549 { 0x63707274U, 0, "copyright", NULL }, // cprt
550 { 0x63726469U, 0, "crdInfo", NULL }, // crdi
551 { 0x63736e6dU, 0, "colorSpaceName", NULL }, // csnm
552 { 0x64657363U, 0, "profileDescription", NULL }, // desc
553 { 0x64657673U, 0, "deviceSettings", NULL }, // devs
554 { 0x646d6464U, 0, "deviceModelDesc", NULL }, // dmdd
555 { 0x646d6e64U, 0, "deviceMfgDesc", NULL }, // dmnd
556 { 0x67616d74U, 0, "gamut", NULL }, // gamt
557 { 0x67626431U, 0, "amutBoundaryDescription1", NULL }, // gbd1
558 { 0x67545243U, 0, "greenTRC", NULL }, // gTRC
559 { 0x6758595aU, 0x1, "greenColorant", NULL }, // gXYZ
560 { 0x6758595aU, 0x2, "greenMatrixColumn", NULL }, // gXYZ
561 { 0x6b545243U, 0, "grayTRC", NULL }, // kTRC
562 { 0x6c756d69U, 0, "luminance", NULL }, // lumi
563 { 0x6d656173U, 0, "measurement", NULL }, // meas
564 { 0x6e636c32U, 0, "namedColor2", NULL }, // ncl2
565 { 0x6e636f6cU, 0, "namedColor", NULL }, // ncol
566 { 0x70726530U, 0, "preview0", NULL }, // pre0
567 { 0x70726531U, 0, "preview1", NULL }, // pre1
568 { 0x70726532U, 0, "preview2", NULL }, // pre2
569 { 0x70733269U, 0, "ps2RenderingIntent", NULL }, // ps2i
570 { 0x70733273U, 0, "ps2CSA", NULL }, // ps2s
571 { 0x70736430U, 0, "ps2CRD0", NULL }, // psd0
572 { 0x70736431U, 0, "ps2CRD1", NULL }, // psd1
573 { 0x70736432U, 0, "ps2CRD2", NULL }, // psd2
574 { 0x70736433U, 0, "ps2CRD3", NULL }, // psd3
575 { 0x70736571U, 0, "profileSequenceDesc", NULL }, // pseq
576 { 0x70736964U, 0, "profileSequenceIdentifier", NULL }, // psid
577 { 0x72545243U, 0, "redTRC", NULL }, // rTRC
578 { 0x7258595aU, 0x1, "redColorant", NULL }, // rXYZ
579 { 0x7258595aU, 0x2, "redMatrixColumn", NULL }, // rXYZ
580 { 0x72657370U, 0, "outputResponse", NULL }, // resp
581 { 0x72666e6dU, 0, "referenceName", NULL }, // rfnm
582 { 0x72696730U, 0, "perceptualRenderingIntentGamut", NULL }, // rig0
583 { 0x72696732U, 0, "saturationRenderingIntentGamut", NULL }, // rig2
584 { 0x73326370U, 0, "standardToCustomPcc", NULL }, // s2cp
585 { 0x73637264U, 0, "screeningDesc", NULL }, // scrd
586 { 0x7363726eU, 0, "screening", NULL }, // scrn
587 { 0x7376636eU, 0, "spectralViewingConditions", NULL }, // svcn
588 { 0x74617267U, 0, "charTarget", NULL }, // targ
589 { 0x74656368U, 0, "technology", NULL }, // tech
590 { 0x76636774U, 0, "Video Card Gamma Type", NULL }, // vcgt (Apple)
591 { 0x76696577U, 0, "viewingConditions", NULL }, // view
592 { 0x76756564U, 0, "viewingCondDesc", NULL }, // vued
593 { 0x77747074U, 0, "mediaWhitePoint", NULL } // wtpt
596 static void do_read_header(deark *c, lctx *d, i64 pos)
598 u32 profile_ver_raw;
599 i64 x;
600 u64 xu;
601 struct de_fourcc tmp4cc;
602 UI tmpflags;
603 const char *name;
604 struct XYZ_type xyz;
606 de_dbg(c, "header at %d", (int)pos);
607 de_dbg_indent(c, 1);
609 d->profile_size = de_getu32be(pos+0);
610 de_dbg(c, "profile size: %"I64_FMT, d->profile_size);
612 dbuf_read_fourcc(c->infile, pos+4, &tmp4cc, 4, 0x0);
613 de_dbg(c, "preferred CMM type: %s",
614 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
616 profile_ver_raw = (u32)de_getu32be(pos+8);
617 d->profile_ver_major = 10*((profile_ver_raw&0xf0000000U)>>28) +
618 ((profile_ver_raw&0x0f000000U)>>24);
619 d->profile_ver_minor = (profile_ver_raw&0x00f00000U)>>20;
620 d->profile_ver_bugfix = (profile_ver_raw&0x000f0000U)>>16;
621 de_dbg(c, "profile version: %u.%u.%u", d->profile_ver_major,
622 d->profile_ver_minor, d->profile_ver_bugfix);
624 dbuf_read_fourcc(c->infile, pos+12, &tmp4cc, 4, 0x0);
625 de_dbg(c, "profile/device class: %s",
626 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x1));
628 dbuf_read_fourcc(c->infile, pos+16, &tmp4cc, 4, 0x0);
629 tmpflags = 0x1;
630 if(d->profile_ver_major>=5) tmpflags |= 0x2;
631 de_dbg(c, "colour space: %s",
632 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), tmpflags));
634 dbuf_read_fourcc(c->infile, pos+20, &tmp4cc, 4, 0x0);
635 tmpflags = 0x1;
636 if(d->profile_ver_major>=5) tmpflags |= 0x2;
637 de_dbg(c, "PCS: %s",
638 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), tmpflags));
640 dbg_timestamp(c, pos+24, "creation time");
642 dbuf_read_fourcc(c->infile, pos+36, &tmp4cc, 4, 0x0);
643 de_dbg(c, "file signature: %s",
644 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x1));
646 dbuf_read_fourcc(c->infile, pos+40, &tmp4cc, 4, 0x0);
647 de_dbg(c, "primary platform: %s",
648 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
650 // TODO: Decode profile flags
651 x = de_getu32be(pos+44);
652 de_dbg(c, "profile flags: 0x%08x", (UI)x);
654 dbuf_read_fourcc(c->infile, pos+48, &tmp4cc, 4, 0x0);
655 de_dbg(c, "device manufacturer: %s",
656 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
658 dbuf_read_fourcc(c->infile, pos+52, &tmp4cc, 4, 0x0);
659 de_dbg(c, "device model: %s",
660 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
662 // TODO: Decode device attributes
663 xu = dbuf_getu64be(c->infile, pos+56);
664 de_dbg(c, "device attribs: 0x%016"U64_FMTx, xu);
666 x = de_getu32be(pos+64);
667 switch(x) {
668 case 0: name="perceptual"; break;
669 case 1: name="relative colorimetric"; break;
670 case 2: name="saturation"; break;
671 case 3: name="absolute colorimetric"; break;
672 default: name="?"; break;
674 de_dbg(c, "rendering intent: %d (%s)", (int)x, name);
676 read_XYZ(c, pos+68, &xyz);
677 de_dbg(c, "illuminant: %.5f, %.5f, %.5f", xyz.v[0], xyz.v[1], xyz.v[2]);
679 dbuf_read_fourcc(c->infile, pos+80, &tmp4cc, 4, 0x0);
680 de_dbg(c, "profile creator: %s",
681 format_4cc_dbgstr(&tmp4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x3));
683 // TODO: pos=84-99 Profile ID
685 de_dbg_indent(c, -1);
688 static const struct datatypeinfo *lookup_datatypeinfo(u32 id)
690 size_t k;
691 for(k=0; k<DE_ARRAYCOUNT(datatypeinfo_arr); k++) {
692 if(datatypeinfo_arr[k].id == id) {
693 return &datatypeinfo_arr[k];
696 return NULL;
699 static const struct taginfo *lookup_taginfo(lctx *d, u32 id)
701 size_t k;
702 for(k=0; k<DE_ARRAYCOUNT(taginfo_arr); k++) {
703 if((taginfo_arr[k].flags & 0x1) && d->profile_ver_major>=4) continue;
704 if((taginfo_arr[k].flags & 0x2) && d->profile_ver_major<4) continue;
705 if(taginfo_arr[k].id == id) {
706 return &taginfo_arr[k];
709 return NULL;
712 static int is_duplicate_data(deark *c, lctx *d, struct tagset_type *tgs,
713 i64 tagindex, i64 tagdataoffset, i64 tagdatalen,
714 i64 *idx_of_dup)
716 i64 k;
718 for(k=0; k<tagindex && k<tgs->num_tags; k++) {
719 if(tgs->tags_seen[k].offset==tagdataoffset &&
720 tgs->tags_seen[k].len==tagdatalen)
722 *idx_of_dup = k;
723 return 1;
727 *idx_of_dup = -1;
728 return 0;
731 // tagdataoffset is relative to tgs->data_area_pos
732 static void do_tag_data(deark *c, lctx *d, struct tagset_type *tgs, i64 tagindex,
733 i64 tagdataoffset, i64 tagdatalen)
735 struct de_fourcc tagtype4cc;
736 struct typedec_params tdp;
737 const struct datatypeinfo *dti;
738 const char *dtname;
739 i64 idx_of_dup;
741 if(tagindex >= tgs->num_tags) return;
742 if(tagdatalen<1) goto done;
743 if(tagdataoffset+tagdatalen > tgs->data_area_len) {
744 de_err(c, "Tag #%d data exceeds its bounds", (int)tagindex);
745 goto done;
747 if(is_duplicate_data(c, d, tgs, tagindex, tagdataoffset, tagdatalen, &idx_of_dup)) {
748 de_dbg(c, "[data is a duplicate of tag #%d]", (int)idx_of_dup);
749 goto done;
752 if(tagdatalen<4) goto done;
754 dbuf_read_fourcc(c->infile, tgs->data_area_pos+tagdataoffset, &tagtype4cc, 4, 0x0);
755 dti = lookup_datatypeinfo(tagtype4cc.id);
756 if(dti && dti->name) dtname=dti->name;
757 else dtname="?";
758 de_dbg(c, "data type: %s (%s)",
759 format_4cc_dbgstr(&tagtype4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x0), dtname);
761 de_zeromem(&tdp, sizeof(struct typedec_params));
762 tdp.d = d;
763 tdp.tgs = tgs;
764 tdp.pos1 = tgs->data_area_pos+tagdataoffset;
765 tdp.len = tagdatalen;
766 tdp.type_id = tagtype4cc.id;
768 if(dti && dti->dtdfn) {
769 dti->dtdfn(c, &tdp);
771 else if(c->debug_level>=2) {
772 typedec_hexdump(c, &tdp);
775 done:
776 tgs->tags_seen[tagindex].offset = tagdataoffset;
777 tgs->tags_seen[tagindex].len = tagdatalen;
780 static void do_main_tag(deark *c, lctx *d, struct tagset_type *tgs,
781 i64 tagindex, i64 pos_in_tagtable)
783 struct de_fourcc tag4cc;
784 const struct taginfo *ti;
785 const char *tname;
786 i64 tagdataoffset;
787 i64 tagdatalen;
789 dbuf_read_fourcc(c->infile, pos_in_tagtable, &tag4cc, 4, 0x0);
790 tagdataoffset = de_getu32be(pos_in_tagtable+4);
791 tagdatalen = de_getu32be(pos_in_tagtable+8);
792 ti = lookup_taginfo(d, tag4cc.id);
793 if(ti && ti->name)
794 tname = ti->name;
795 else
796 tname = "?";
797 de_dbg(c, "tag #%d %s (%s) tpos=%"I64_FMT" dpos=%"I64_FMT" dlen=%"I64_FMT, (int)tagindex,
798 format_4cc_dbgstr(&tag4cc, d->tmpbuf1, sizeof(d->tmpbuf1), 0x0), tname,
799 pos_in_tagtable, tgs->data_area_pos+tagdataoffset, tagdatalen);
801 de_dbg_indent(c, 1);
802 do_tag_data(c, d, tgs, tagindex, tagdataoffset, tagdatalen);
803 de_dbg_indent(c, -1);
806 static void do_read_main_tags(deark *c, lctx *d, i64 pos1)
808 i64 tagindex;
809 struct tagset_type *tgs = NULL;
811 de_dbg(c, "tag table at %"I64_FMT, pos1);
812 de_dbg_indent(c, 1);
814 tgs = de_malloc(c, sizeof(struct tagset_type));
815 tgs->data_area_pos = 0;
816 if(d->profile_size!=0) {
817 tgs->data_area_len = de_min_int(d->profile_size, c->infile->len);
819 else {
820 tgs->data_area_len = c->infile->len;
822 tgs->num_tags = de_getu32be(pos1);
823 de_dbg(c, "number of tags: %d", (int)tgs->num_tags);
824 if(tgs->num_tags>MAX_TAGS_PER_TAGSET) {
825 de_err(c, "Invalid or excessive number of tags: %d", (int)tgs->num_tags);
826 goto done;
828 de_dbg(c, "expected start of data segment: %d", (int)(pos1+4+12*tgs->num_tags));
830 // Make a place to record some information about each tag we encounter in the table.
831 tgs->tags_seen = de_mallocarray(c, tgs->num_tags, sizeof(struct tag_seen_type));
833 for(tagindex=0; tagindex<tgs->num_tags; tagindex++) {
834 do_main_tag(c, d, tgs, tagindex, pos1+4+12*tagindex);
837 done:
838 destroy_tagset(c, tgs);
839 de_dbg_indent(c, -1);
842 static void de_run_iccprofile(deark *c, de_module_params *mparams)
844 lctx *d = NULL;
845 i64 pos;
847 d = de_malloc(c, sizeof(lctx));
849 pos = 0;
850 do_read_header(c, d, pos);
851 pos += 128;
852 do_read_main_tags(c, d, pos);
854 de_free(c, d);
857 static int de_identify_iccprofile(deark *c)
859 if(!dbuf_memcmp(c->infile, 36, "acsp", 4))
860 return 85;
861 return 0;
864 void de_module_iccprofile(deark *c, struct deark_module_info *mi)
866 mi->id = "iccprofile";
867 mi->desc = "ICC profile";
868 mi->run_fn = de_run_iccprofile;
869 mi->identify_fn = de_identify_iccprofile;