/src/mozilla-central/gfx/qcms/iccread.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* vim: set ts=8 sw=8 noexpandtab: */ |
2 | | // qcms |
3 | | // Copyright (C) 2009 Mozilla Foundation |
4 | | // Copyright (C) 1998-2007 Marti Maria |
5 | | // |
6 | | // Permission is hereby granted, free of charge, to any person obtaining |
7 | | // a copy of this software and associated documentation files (the "Software"), |
8 | | // to deal in the Software without restriction, including without limitation |
9 | | // the rights to use, copy, modify, merge, publish, distribute, sublicense, |
10 | | // and/or sell copies of the Software, and to permit persons to whom the Software |
11 | | // is furnished to do so, subject to the following conditions: |
12 | | // |
13 | | // The above copyright notice and this permission notice shall be included in |
14 | | // all copies or substantial portions of the Software. |
15 | | // |
16 | | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
17 | | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO |
18 | | // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
19 | | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
20 | | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
21 | | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
22 | | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
23 | | |
24 | | #include <math.h> |
25 | | #include <assert.h> |
26 | | #include <stdlib.h> |
27 | | #include <string.h> //memset |
28 | | #include "qcmsint.h" |
29 | | |
30 | | /* It might be worth having a unified limit on content controlled |
31 | | * allocation per profile. This would remove the need for many |
32 | | * of the arbitrary limits that we used */ |
33 | | |
34 | | typedef uint32_t be32; |
35 | | typedef uint16_t be16; |
36 | | |
37 | | static be32 cpu_to_be32(uint32_t v) |
38 | 0 | { |
39 | 0 | #ifdef IS_LITTLE_ENDIAN |
40 | 0 | return ((v & 0xff) << 24) | ((v & 0xff00) << 8) | ((v & 0xff0000) >> 8) | ((v & 0xff000000) >> 24); |
41 | | #else |
42 | | return v; |
43 | | #endif |
44 | | } |
45 | | |
46 | | static be16 cpu_to_be16(uint16_t v) |
47 | 0 | { |
48 | 0 | #ifdef IS_LITTLE_ENDIAN |
49 | 0 | return ((v & 0xff) << 8) | ((v & 0xff00) >> 8); |
50 | | #else |
51 | | return v; |
52 | | #endif |
53 | | } |
54 | | |
55 | | static uint32_t be32_to_cpu(be32 v) |
56 | 0 | { |
57 | 0 | #ifdef IS_LITTLE_ENDIAN |
58 | 0 | return ((v & 0xff) << 24) | ((v & 0xff00) << 8) | ((v & 0xff0000) >> 8) | ((v & 0xff000000) >> 24); |
59 | 0 | //return __builtin_bswap32(v); |
60 | | #else |
61 | | return v; |
62 | | #endif |
63 | | } |
64 | | |
65 | | static uint16_t be16_to_cpu(be16 v) |
66 | 0 | { |
67 | 0 | #ifdef IS_LITTLE_ENDIAN |
68 | 0 | return ((v & 0xff) << 8) | ((v & 0xff00) >> 8); |
69 | | #else |
70 | | return v; |
71 | | #endif |
72 | | } |
73 | | |
74 | | /* a wrapper around the memory that we are going to parse |
75 | | * into a qcms_profile */ |
76 | | struct mem_source |
77 | | { |
78 | | const unsigned char *buf; |
79 | | size_t size; |
80 | | qcms_bool valid; |
81 | | const char *invalid_reason; |
82 | | }; |
83 | | |
84 | | static void invalid_source(struct mem_source *mem, const char *reason) |
85 | 0 | { |
86 | 0 | mem->valid = false; |
87 | 0 | mem->invalid_reason = reason; |
88 | 0 | } |
89 | | |
90 | | static uint32_t read_u32(struct mem_source *mem, size_t offset) |
91 | 0 | { |
92 | 0 | /* Subtract from mem->size instead of the more intuitive adding to offset. |
93 | 0 | * This avoids overflowing offset. The subtraction is safe because |
94 | 0 | * mem->size is guaranteed to be > 4 */ |
95 | 0 | if (offset > mem->size - 4) { |
96 | 0 | invalid_source(mem, "Invalid offset"); |
97 | 0 | return 0; |
98 | 0 | } else { |
99 | 0 | be32 k; |
100 | 0 | memcpy(&k, mem->buf + offset, sizeof(k)); |
101 | 0 | return be32_to_cpu(k); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | static uint16_t read_u16(struct mem_source *mem, size_t offset) |
106 | 0 | { |
107 | 0 | if (offset > mem->size - 2) { |
108 | 0 | invalid_source(mem, "Invalid offset"); |
109 | 0 | return 0; |
110 | 0 | } else { |
111 | 0 | be16 k; |
112 | 0 | memcpy(&k, mem->buf + offset, sizeof(k)); |
113 | 0 | return be16_to_cpu(k); |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | | static uint8_t read_u8(struct mem_source *mem, size_t offset) |
118 | 0 | { |
119 | 0 | if (offset > mem->size - 1) { |
120 | 0 | invalid_source(mem, "Invalid offset"); |
121 | 0 | return 0; |
122 | 0 | } else { |
123 | 0 | return *(uint8_t*)(mem->buf + offset); |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | | static s15Fixed16Number read_s15Fixed16Number(struct mem_source *mem, size_t offset) |
128 | 0 | { |
129 | 0 | return read_u32(mem, offset); |
130 | 0 | } |
131 | | |
132 | | static uInt8Number read_uInt8Number(struct mem_source *mem, size_t offset) |
133 | 0 | { |
134 | 0 | return read_u8(mem, offset); |
135 | 0 | } |
136 | | |
137 | | static uInt16Number read_uInt16Number(struct mem_source *mem, size_t offset) |
138 | 0 | { |
139 | 0 | return read_u16(mem, offset); |
140 | 0 | } |
141 | | |
142 | | static void write_u32(void *mem, size_t offset, uint32_t value) |
143 | 0 | { |
144 | 0 | *((uint32_t *)((unsigned char*)mem + offset)) = cpu_to_be32(value); |
145 | 0 | } |
146 | | |
147 | | static void write_u16(void *mem, size_t offset, uint16_t value) |
148 | 0 | { |
149 | 0 | *((uint16_t *)((unsigned char*)mem + offset)) = cpu_to_be16(value); |
150 | 0 | } |
151 | | |
152 | | #define BAD_VALUE_PROFILE NULL |
153 | 0 | #define INVALID_PROFILE NULL |
154 | 0 | #define NO_MEM_PROFILE NULL |
155 | | |
156 | | /* An arbitrary 4MB limit on profile size */ |
157 | 0 | #define MAX_PROFILE_SIZE 1024*1024*4 |
158 | 0 | #define MAX_TAG_COUNT 1024 |
159 | | |
160 | | static void check_CMM_type_signature(struct mem_source *src) |
161 | 0 | { |
162 | 0 | //uint32_t CMM_type_signature = read_u32(src, 4); |
163 | 0 | //TODO: do the check? |
164 | 0 |
|
165 | 0 | } |
166 | | |
167 | | static void check_profile_version(struct mem_source *src) |
168 | 0 | { |
169 | 0 |
|
170 | 0 | /* |
171 | 0 | uint8_t major_revision = read_u8(src, 8 + 0); |
172 | 0 | uint8_t minor_revision = read_u8(src, 8 + 1); |
173 | 0 | */ |
174 | 0 | uint8_t reserved1 = read_u8(src, 8 + 2); |
175 | 0 | uint8_t reserved2 = read_u8(src, 8 + 3); |
176 | 0 | /* Checking the version doesn't buy us anything |
177 | 0 | if (major_revision != 0x4) { |
178 | 0 | if (major_revision > 0x2) |
179 | 0 | invalid_source(src, "Unsupported major revision"); |
180 | 0 | if (minor_revision > 0x40) |
181 | 0 | invalid_source(src, "Unsupported minor revision"); |
182 | 0 | } |
183 | 0 | */ |
184 | 0 | if (reserved1 != 0 || reserved2 != 0) |
185 | 0 | invalid_source(src, "Invalid reserved bytes"); |
186 | 0 | } |
187 | | |
188 | 0 | #define INPUT_DEVICE_PROFILE 0x73636e72 // 'scnr' |
189 | 0 | #define DISPLAY_DEVICE_PROFILE 0x6d6e7472 // 'mntr' |
190 | 0 | #define OUTPUT_DEVICE_PROFILE 0x70727472 // 'prtr' |
191 | | #define DEVICE_LINK_PROFILE 0x6c696e6b // 'link' |
192 | 0 | #define COLOR_SPACE_PROFILE 0x73706163 // 'spac' |
193 | | #define ABSTRACT_PROFILE 0x61627374 // 'abst' |
194 | | #define NAMED_COLOR_PROFILE 0x6e6d636c // 'nmcl' |
195 | | |
196 | | static void read_class_signature(qcms_profile *profile, struct mem_source *mem) |
197 | 0 | { |
198 | 0 | profile->class = read_u32(mem, 12); |
199 | 0 | switch (profile->class) { |
200 | 0 | case DISPLAY_DEVICE_PROFILE: |
201 | 0 | case INPUT_DEVICE_PROFILE: |
202 | 0 | case OUTPUT_DEVICE_PROFILE: |
203 | 0 | case COLOR_SPACE_PROFILE: |
204 | 0 | break; |
205 | 0 | default: |
206 | 0 | invalid_source(mem, "Invalid Profile/Device Class signature"); |
207 | 0 | } |
208 | 0 | } |
209 | | |
210 | | static void read_color_space(qcms_profile *profile, struct mem_source *mem) |
211 | 0 | { |
212 | 0 | profile->color_space = read_u32(mem, 16); |
213 | 0 | switch (profile->color_space) { |
214 | 0 | case RGB_SIGNATURE: |
215 | 0 | case GRAY_SIGNATURE: |
216 | 0 | break; |
217 | 0 | default: |
218 | 0 | invalid_source(mem, "Unsupported colorspace"); |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | | static void read_pcs(qcms_profile *profile, struct mem_source *mem) |
223 | 0 | { |
224 | 0 | profile->pcs = read_u32(mem, 20); |
225 | 0 | switch (profile->pcs) { |
226 | 0 | case XYZ_SIGNATURE: |
227 | 0 | case LAB_SIGNATURE: |
228 | 0 | break; |
229 | 0 | default: |
230 | 0 | invalid_source(mem, "Unsupported pcs"); |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | struct tag |
235 | | { |
236 | | uint32_t signature; |
237 | | uint32_t offset; |
238 | | uint32_t size; |
239 | | }; |
240 | | |
241 | | struct tag_index { |
242 | | uint32_t count; |
243 | | struct tag *tags; |
244 | | }; |
245 | | |
246 | | static struct tag_index read_tag_table(qcms_profile *profile, struct mem_source *mem) |
247 | 0 | { |
248 | 0 | struct tag_index index = {0, NULL}; |
249 | 0 | unsigned int i; |
250 | 0 |
|
251 | 0 | index.count = read_u32(mem, 128); |
252 | 0 | if (index.count > MAX_TAG_COUNT) { |
253 | 0 | invalid_source(mem, "max number of tags exceeded"); |
254 | 0 | return index; |
255 | 0 | } |
256 | 0 | |
257 | 0 | index.tags = malloc(sizeof(struct tag)*index.count); |
258 | 0 | if (index.tags) { |
259 | 0 | for (i = 0; i < index.count; i++) { |
260 | 0 | index.tags[i].signature = read_u32(mem, 128 + 4 + 4*i*3); |
261 | 0 | index.tags[i].offset = read_u32(mem, 128 + 4 + 4*i*3 + 4); |
262 | 0 | index.tags[i].size = read_u32(mem, 128 + 4 + 4*i*3 + 8); |
263 | 0 | } |
264 | 0 | } |
265 | 0 |
|
266 | 0 | return index; |
267 | 0 | } |
268 | | |
269 | | // Checks a profile for obvious inconsistencies and returns |
270 | | // true if the profile looks bogus and should probably be |
271 | | // ignored. |
272 | | qcms_bool qcms_profile_is_bogus(qcms_profile *profile) |
273 | 0 | { |
274 | 0 | float sum[3], target[3], tolerance[3]; |
275 | 0 | float rX, rY, rZ, gX, gY, gZ, bX, bY, bZ; |
276 | 0 | bool negative; |
277 | 0 | unsigned i; |
278 | 0 |
|
279 | 0 | // We currently only check the bogosity of RGB profiles |
280 | 0 | if (profile->color_space != RGB_SIGNATURE) |
281 | 0 | return false; |
282 | 0 | |
283 | 0 | if (profile->A2B0 || profile->B2A0) |
284 | 0 | return false; |
285 | 0 | |
286 | 0 | rX = s15Fixed16Number_to_float(profile->redColorant.X); |
287 | 0 | rY = s15Fixed16Number_to_float(profile->redColorant.Y); |
288 | 0 | rZ = s15Fixed16Number_to_float(profile->redColorant.Z); |
289 | 0 |
|
290 | 0 | gX = s15Fixed16Number_to_float(profile->greenColorant.X); |
291 | 0 | gY = s15Fixed16Number_to_float(profile->greenColorant.Y); |
292 | 0 | gZ = s15Fixed16Number_to_float(profile->greenColorant.Z); |
293 | 0 |
|
294 | 0 | bX = s15Fixed16Number_to_float(profile->blueColorant.X); |
295 | 0 | bY = s15Fixed16Number_to_float(profile->blueColorant.Y); |
296 | 0 | bZ = s15Fixed16Number_to_float(profile->blueColorant.Z); |
297 | 0 |
|
298 | 0 | // Sum the values; they should add up to something close to white |
299 | 0 | sum[0] = rX + gX + bX; |
300 | 0 | sum[1] = rY + gY + bY; |
301 | 0 | sum[2] = rZ + gZ + bZ; |
302 | 0 |
|
303 | 0 | // Build our target vector (see mozilla bug 460629) |
304 | 0 | target[0] = 0.96420f; |
305 | 0 | target[1] = 1.00000f; |
306 | 0 | target[2] = 0.82491f; |
307 | 0 |
|
308 | 0 | // Our tolerance vector - Recommended by Chris Murphy based on |
309 | 0 | // conversion from the LAB space criterion of no more than 3 in any one |
310 | 0 | // channel. This is similar to, but slightly more tolerant than Adobe's |
311 | 0 | // criterion. |
312 | 0 | tolerance[0] = 0.02f; |
313 | 0 | tolerance[1] = 0.02f; |
314 | 0 | tolerance[2] = 0.04f; |
315 | 0 |
|
316 | 0 | // Compare with our tolerance |
317 | 0 | for (i = 0; i < 3; ++i) { |
318 | 0 | if (!(((sum[i] - tolerance[i]) <= target[i]) && |
319 | 0 | ((sum[i] + tolerance[i]) >= target[i]))) |
320 | 0 | return true; |
321 | 0 | } |
322 | 0 |
|
323 | 0 | #ifndef __APPLE__ |
324 | 0 | // Check if any of the XYZ values are negative (see mozilla bug 498245) |
325 | 0 | // CIEXYZ tristimulus values cannot be negative according to the spec. |
326 | 0 |
|
327 | 0 | negative = |
328 | 0 | (rX < 0) || (rY < 0) || (rZ < 0) || |
329 | 0 | (gX < 0) || (gY < 0) || (gZ < 0) || |
330 | 0 | (bX < 0) || (bY < 0) || (bZ < 0); |
331 | 0 |
|
332 | | #else |
333 | | // Chromatic adaption to D50 can result in negative XYZ, but the white |
334 | | // point D50 tolerance test has passed. Accept negative values herein. |
335 | | // See https://bugzilla.mozilla.org/show_bug.cgi?id=498245#c18 onwards |
336 | | // for discussion about whether profile XYZ can or cannot be negative, |
337 | | // per the spec. Also the https://bugzil.la/450923 user report. |
338 | | |
339 | | // FIXME: allow this relaxation on all ports? |
340 | | negative = false; |
341 | | #endif |
342 | 0 | if (negative) |
343 | 0 | return true; // bogus |
344 | 0 | |
345 | 0 | // All Good |
346 | 0 | return false; |
347 | 0 | } |
348 | | |
349 | 0 | #define TAG_bXYZ 0x6258595a |
350 | 0 | #define TAG_gXYZ 0x6758595a |
351 | 0 | #define TAG_rXYZ 0x7258595a |
352 | 0 | #define TAG_rTRC 0x72545243 |
353 | 0 | #define TAG_bTRC 0x62545243 |
354 | 0 | #define TAG_gTRC 0x67545243 |
355 | 0 | #define TAG_kTRC 0x6b545243 |
356 | 0 | #define TAG_A2B0 0x41324230 |
357 | 0 | #define TAG_B2A0 0x42324130 |
358 | 0 | #define TAG_CHAD 0x63686164 |
359 | | |
360 | | static struct tag *find_tag(struct tag_index index, uint32_t tag_id) |
361 | 0 | { |
362 | 0 | unsigned int i; |
363 | 0 | struct tag *tag = NULL; |
364 | 0 | for (i = 0; i < index.count; i++) { |
365 | 0 | if (index.tags[i].signature == tag_id) { |
366 | 0 | return &index.tags[i]; |
367 | 0 | } |
368 | 0 | } |
369 | 0 | return tag; |
370 | 0 | } |
371 | | |
372 | 0 | #define XYZ_TYPE 0x58595a20 // 'XYZ ' |
373 | 0 | #define CURVE_TYPE 0x63757276 // 'curv' |
374 | 0 | #define PARAMETRIC_CURVE_TYPE 0x70617261 // 'para' |
375 | 0 | #define LUT16_TYPE 0x6d667432 // 'mft2' |
376 | 0 | #define LUT8_TYPE 0x6d667431 // 'mft1' |
377 | 0 | #define LUT_MAB_TYPE 0x6d414220 // 'mAB ' |
378 | 0 | #define LUT_MBA_TYPE 0x6d424120 // 'mBA ' |
379 | 0 | #define CHROMATIC_TYPE 0x73663332 // 'sf32' |
380 | | |
381 | | static struct matrix read_tag_s15Fixed16ArrayType(struct mem_source *src, struct tag_index index, uint32_t tag_id) |
382 | 0 | { |
383 | 0 | struct tag *tag = find_tag(index, tag_id); |
384 | 0 | struct matrix matrix; |
385 | 0 | if (tag) { |
386 | 0 | uint8_t i; |
387 | 0 | uint32_t offset = tag->offset; |
388 | 0 | uint32_t type = read_u32(src, offset); |
389 | 0 |
|
390 | 0 | // Check mandatory type signature for s16Fixed16ArrayType |
391 | 0 | if (type != CHROMATIC_TYPE) { |
392 | 0 | invalid_source(src, "unexpected type, expected 'sf32'"); |
393 | 0 | } |
394 | 0 |
|
395 | 0 | for (i = 0; i < 9; i++) { |
396 | 0 | matrix.m[i/3][i%3] = s15Fixed16Number_to_float(read_s15Fixed16Number(src, offset+8+i*4)); |
397 | 0 | } |
398 | 0 | matrix.invalid = false; |
399 | 0 | } else { |
400 | 0 | matrix.invalid = true; |
401 | 0 | invalid_source(src, "missing sf32tag"); |
402 | 0 | } |
403 | 0 | return matrix; |
404 | 0 | } |
405 | | |
406 | | static struct XYZNumber read_tag_XYZType(struct mem_source *src, struct tag_index index, uint32_t tag_id) |
407 | 0 | { |
408 | 0 | struct XYZNumber num = {0, 0, 0}; |
409 | 0 | struct tag *tag = find_tag(index, tag_id); |
410 | 0 | if (tag) { |
411 | 0 | uint32_t offset = tag->offset; |
412 | 0 |
|
413 | 0 | uint32_t type = read_u32(src, offset); |
414 | 0 | if (type != XYZ_TYPE) |
415 | 0 | invalid_source(src, "unexpected type, expected XYZ"); |
416 | 0 | num.X = read_s15Fixed16Number(src, offset+8); |
417 | 0 | num.Y = read_s15Fixed16Number(src, offset+12); |
418 | 0 | num.Z = read_s15Fixed16Number(src, offset+16); |
419 | 0 | } else { |
420 | 0 | invalid_source(src, "missing xyztag"); |
421 | 0 | } |
422 | 0 | return num; |
423 | 0 | } |
424 | | |
425 | | // Read the tag at a given offset rather then the tag_index. |
426 | | // This method is used when reading mAB tags where nested curveType are |
427 | | // present that are not part of the tag_index. |
428 | | static struct curveType *read_curveType(struct mem_source *src, uint32_t offset, uint32_t *len) |
429 | 0 | { |
430 | 0 | static const uint32_t COUNT_TO_LENGTH[5] = {1, 3, 4, 5, 7}; |
431 | 0 | struct curveType *curve = NULL; |
432 | 0 | uint32_t type = read_u32(src, offset); |
433 | 0 | uint32_t count; |
434 | 0 | uint32_t i; |
435 | 0 |
|
436 | 0 | if (type != CURVE_TYPE && type != PARAMETRIC_CURVE_TYPE) { |
437 | 0 | invalid_source(src, "unexpected type, expected CURV or PARA"); |
438 | 0 | return NULL; |
439 | 0 | } |
440 | 0 | |
441 | 0 | if (type == CURVE_TYPE) { |
442 | 0 | count = read_u32(src, offset+8); |
443 | 0 |
|
444 | 0 | #define MAX_CURVE_ENTRIES 40000 //arbitrary |
445 | 0 | if (count > MAX_CURVE_ENTRIES) { |
446 | 0 | invalid_source(src, "curve size too large"); |
447 | 0 | return NULL; |
448 | 0 | } |
449 | 0 | curve = malloc(sizeof(struct curveType) + sizeof(uInt16Number)*count); |
450 | 0 | if (!curve) |
451 | 0 | return NULL; |
452 | 0 | |
453 | 0 | curve->count = count; |
454 | 0 | curve->type = CURVE_TYPE; |
455 | 0 |
|
456 | 0 | for (i=0; i<count; i++) { |
457 | 0 | curve->data[i] = read_u16(src, offset + 12 + i*2); |
458 | 0 | } |
459 | 0 | *len = 12 + count * 2; |
460 | 0 | } else { //PARAMETRIC_CURVE_TYPE |
461 | 0 | count = read_u16(src, offset+8); |
462 | 0 |
|
463 | 0 | if (count > 4) { |
464 | 0 | invalid_source(src, "parametric function type not supported."); |
465 | 0 | return NULL; |
466 | 0 | } |
467 | 0 | |
468 | 0 | curve = malloc(sizeof(struct curveType)); |
469 | 0 | if (!curve) |
470 | 0 | return NULL; |
471 | 0 | |
472 | 0 | curve->count = count; |
473 | 0 | curve->type = PARAMETRIC_CURVE_TYPE; |
474 | 0 |
|
475 | 0 | for (i=0; i < COUNT_TO_LENGTH[count]; i++) { |
476 | 0 | curve->parameter[i] = s15Fixed16Number_to_float(read_s15Fixed16Number(src, offset + 12 + i*4)); |
477 | 0 | } |
478 | 0 | *len = 12 + COUNT_TO_LENGTH[count] * 4; |
479 | 0 |
|
480 | 0 | if ((count == 1 || count == 2)) { |
481 | 0 | /* we have a type 1 or type 2 function that has a division by 'a' */ |
482 | 0 | float a = curve->parameter[1]; |
483 | 0 | if (a == 0.f) |
484 | 0 | invalid_source(src, "parametricCurve definition causes division by zero."); |
485 | 0 | } |
486 | 0 | } |
487 | 0 |
|
488 | 0 | return curve; |
489 | 0 | } |
490 | | |
491 | | static struct curveType *read_tag_curveType(struct mem_source *src, struct tag_index index, uint32_t tag_id) |
492 | 0 | { |
493 | 0 | struct tag *tag = find_tag(index, tag_id); |
494 | 0 | struct curveType *curve = NULL; |
495 | 0 | if (tag) { |
496 | 0 | uint32_t len; |
497 | 0 | return read_curveType(src, tag->offset, &len); |
498 | 0 | } else { |
499 | 0 | invalid_source(src, "missing curvetag"); |
500 | 0 | } |
501 | 0 |
|
502 | 0 | return curve; |
503 | 0 | } |
504 | | |
505 | 0 | #define MAX_CLUT_SIZE 500000 // arbitrary |
506 | 0 | #define MAX_CHANNELS 10 // arbitrary |
507 | | static void read_nested_curveType(struct mem_source *src, struct curveType *(*curveArray)[MAX_CHANNELS], uint8_t num_channels, uint32_t curve_offset) |
508 | 0 | { |
509 | 0 | uint32_t channel_offset = 0; |
510 | 0 | int i; |
511 | 0 | for (i = 0; i < num_channels; i++) { |
512 | 0 | uint32_t tag_len; |
513 | 0 |
|
514 | 0 | (*curveArray)[i] = read_curveType(src, curve_offset + channel_offset, &tag_len); |
515 | 0 | if (!(*curveArray)[i]) { |
516 | 0 | invalid_source(src, "invalid nested curveType curve"); |
517 | 0 | break; |
518 | 0 | } |
519 | 0 | |
520 | 0 | channel_offset += tag_len; |
521 | 0 | // 4 byte aligned |
522 | 0 | if ((tag_len % 4) != 0) |
523 | 0 | channel_offset += 4 - (tag_len % 4); |
524 | 0 | } |
525 | 0 |
|
526 | 0 | } |
527 | | |
528 | | static void mAB_release(struct lutmABType *lut) |
529 | 0 | { |
530 | 0 | uint8_t i; |
531 | 0 |
|
532 | 0 | for (i = 0; i < lut->num_in_channels; i++){ |
533 | 0 | free(lut->a_curves[i]); |
534 | 0 | } |
535 | 0 | for (i = 0; i < lut->num_out_channels; i++){ |
536 | 0 | free(lut->b_curves[i]); |
537 | 0 | free(lut->m_curves[i]); |
538 | 0 | } |
539 | 0 | free(lut); |
540 | 0 | } |
541 | | |
542 | | /* See section 10.10 for specs */ |
543 | | static struct lutmABType *read_tag_lutmABType(struct mem_source *src, struct tag_index index, uint32_t tag_id) |
544 | 0 | { |
545 | 0 | struct tag *tag = find_tag(index, tag_id); |
546 | 0 | uint32_t offset = tag->offset; |
547 | 0 | uint32_t a_curve_offset, b_curve_offset, m_curve_offset; |
548 | 0 | uint32_t matrix_offset; |
549 | 0 | uint32_t clut_offset; |
550 | 0 | uint32_t clut_size = 1; |
551 | 0 | uint8_t clut_precision; |
552 | 0 | uint32_t type = read_u32(src, offset); |
553 | 0 | uint8_t num_in_channels, num_out_channels; |
554 | 0 | struct lutmABType *lut; |
555 | 0 | uint32_t i; |
556 | 0 |
|
557 | 0 | if (type != LUT_MAB_TYPE && type != LUT_MBA_TYPE) { |
558 | 0 | return NULL; |
559 | 0 | } |
560 | 0 | |
561 | 0 | num_in_channels = read_u8(src, offset + 8); |
562 | 0 | num_out_channels = read_u8(src, offset + 9); |
563 | 0 | if (num_in_channels > MAX_CHANNELS || num_out_channels > MAX_CHANNELS) |
564 | 0 | return NULL; |
565 | 0 | |
566 | 0 | // We require 3in/out channels since we only support RGB->XYZ (or RGB->LAB) |
567 | 0 | // XXX: If we remove this restriction make sure that the number of channels |
568 | 0 | // is less or equal to the maximum number of mAB curves in qcmsint.h |
569 | 0 | // also check for clut_size overflow. Also make sure it's != 0 |
570 | 0 | if (num_in_channels != 3 || num_out_channels != 3) |
571 | 0 | return NULL; |
572 | 0 | |
573 | 0 | // some of this data is optional and is denoted by a zero offset |
574 | 0 | // we also use this to track their existance |
575 | 0 | a_curve_offset = read_u32(src, offset + 28); |
576 | 0 | clut_offset = read_u32(src, offset + 24); |
577 | 0 | m_curve_offset = read_u32(src, offset + 20); |
578 | 0 | matrix_offset = read_u32(src, offset + 16); |
579 | 0 | b_curve_offset = read_u32(src, offset + 12); |
580 | 0 |
|
581 | 0 | // Convert offsets relative to the tag to relative to the profile |
582 | 0 | // preserve zero for optional fields |
583 | 0 | if (a_curve_offset) |
584 | 0 | a_curve_offset += offset; |
585 | 0 | if (clut_offset) |
586 | 0 | clut_offset += offset; |
587 | 0 | if (m_curve_offset) |
588 | 0 | m_curve_offset += offset; |
589 | 0 | if (matrix_offset) |
590 | 0 | matrix_offset += offset; |
591 | 0 | if (b_curve_offset) |
592 | 0 | b_curve_offset += offset; |
593 | 0 |
|
594 | 0 | if (clut_offset) { |
595 | 0 | assert (num_in_channels == 3); |
596 | 0 | // clut_size can not overflow since lg(256^num_in_channels) = 24 bits. |
597 | 0 | for (i = 0; i < num_in_channels; i++) { |
598 | 0 | clut_size *= read_u8(src, clut_offset + i); |
599 | 0 | if (clut_size == 0) { |
600 | 0 | invalid_source(src, "bad clut_size"); |
601 | 0 | } |
602 | 0 | } |
603 | 0 | } else { |
604 | 0 | clut_size = 0; |
605 | 0 | } |
606 | 0 |
|
607 | 0 | // 24bits * 3 won't overflow either |
608 | 0 | clut_size = clut_size * num_out_channels; |
609 | 0 |
|
610 | 0 | if (clut_size > MAX_CLUT_SIZE) |
611 | 0 | return NULL; |
612 | 0 | |
613 | 0 | lut = malloc(sizeof(struct lutmABType) + (clut_size) * sizeof(float)); |
614 | 0 | if (!lut) |
615 | 0 | return NULL; |
616 | 0 | // we'll fill in the rest below |
617 | 0 | memset(lut, 0, sizeof(struct lutmABType)); |
618 | 0 | lut->clut_table = &lut->clut_table_data[0]; |
619 | 0 |
|
620 | 0 | if (clut_offset) { |
621 | 0 | for (i = 0; i < num_in_channels; i++) { |
622 | 0 | lut->num_grid_points[i] = read_u8(src, clut_offset + i); |
623 | 0 | if (lut->num_grid_points[i] == 0) { |
624 | 0 | invalid_source(src, "bad grid_points"); |
625 | 0 | } |
626 | 0 | } |
627 | 0 | } |
628 | 0 |
|
629 | 0 | // Reverse the processing of transformation elements for mBA type. |
630 | 0 | lut->reversed = (type == LUT_MBA_TYPE); |
631 | 0 |
|
632 | 0 | lut->num_in_channels = num_in_channels; |
633 | 0 | lut->num_out_channels = num_out_channels; |
634 | 0 |
|
635 | 0 | if (matrix_offset) { |
636 | 0 | // read the matrix if we have it |
637 | 0 | lut->e00 = read_s15Fixed16Number(src, matrix_offset+4*0); |
638 | 0 | lut->e01 = read_s15Fixed16Number(src, matrix_offset+4*1); |
639 | 0 | lut->e02 = read_s15Fixed16Number(src, matrix_offset+4*2); |
640 | 0 | lut->e10 = read_s15Fixed16Number(src, matrix_offset+4*3); |
641 | 0 | lut->e11 = read_s15Fixed16Number(src, matrix_offset+4*4); |
642 | 0 | lut->e12 = read_s15Fixed16Number(src, matrix_offset+4*5); |
643 | 0 | lut->e20 = read_s15Fixed16Number(src, matrix_offset+4*6); |
644 | 0 | lut->e21 = read_s15Fixed16Number(src, matrix_offset+4*7); |
645 | 0 | lut->e22 = read_s15Fixed16Number(src, matrix_offset+4*8); |
646 | 0 | lut->e03 = read_s15Fixed16Number(src, matrix_offset+4*9); |
647 | 0 | lut->e13 = read_s15Fixed16Number(src, matrix_offset+4*10); |
648 | 0 | lut->e23 = read_s15Fixed16Number(src, matrix_offset+4*11); |
649 | 0 | } |
650 | 0 |
|
651 | 0 | if (a_curve_offset) { |
652 | 0 | read_nested_curveType(src, &lut->a_curves, num_in_channels, a_curve_offset); |
653 | 0 | } |
654 | 0 | if (m_curve_offset) { |
655 | 0 | read_nested_curveType(src, &lut->m_curves, num_out_channels, m_curve_offset); |
656 | 0 | } |
657 | 0 | if (b_curve_offset) { |
658 | 0 | read_nested_curveType(src, &lut->b_curves, num_out_channels, b_curve_offset); |
659 | 0 | } else { |
660 | 0 | invalid_source(src, "B curves required"); |
661 | 0 | } |
662 | 0 |
|
663 | 0 | if (clut_offset) { |
664 | 0 | clut_precision = read_u8(src, clut_offset + 16); |
665 | 0 | if (clut_precision == 1) { |
666 | 0 | for (i = 0; i < clut_size; i++) { |
667 | 0 | lut->clut_table[i] = uInt8Number_to_float(read_uInt8Number(src, clut_offset + 20 + i*1)); |
668 | 0 | } |
669 | 0 | } else if (clut_precision == 2) { |
670 | 0 | for (i = 0; i < clut_size; i++) { |
671 | 0 | lut->clut_table[i] = uInt16Number_to_float(read_uInt16Number(src, clut_offset + 20 + i*2)); |
672 | 0 | } |
673 | 0 | } else { |
674 | 0 | invalid_source(src, "Invalid clut precision"); |
675 | 0 | } |
676 | 0 | } |
677 | 0 |
|
678 | 0 | if (!src->valid) { |
679 | 0 | mAB_release(lut); |
680 | 0 | return NULL; |
681 | 0 | } |
682 | 0 | |
683 | 0 | return lut; |
684 | 0 | } |
685 | | |
686 | | static struct lutType *read_tag_lutType(struct mem_source *src, struct tag_index index, uint32_t tag_id) |
687 | 0 | { |
688 | 0 | struct tag *tag = find_tag(index, tag_id); |
689 | 0 | uint32_t offset = tag->offset; |
690 | 0 | uint32_t type = read_u32(src, offset); |
691 | 0 | uint16_t num_input_table_entries; |
692 | 0 | uint16_t num_output_table_entries; |
693 | 0 | uint8_t in_chan, grid_points, out_chan; |
694 | 0 | uint32_t input_offset, clut_offset, output_offset; |
695 | 0 | uint32_t clut_size; |
696 | 0 | size_t entry_size; |
697 | 0 | struct lutType *lut; |
698 | 0 | uint32_t i; |
699 | 0 |
|
700 | 0 | if (type == LUT8_TYPE) { |
701 | 0 | num_input_table_entries = 256; |
702 | 0 | num_output_table_entries = 256; |
703 | 0 | entry_size = 1; |
704 | 0 | input_offset = 48; |
705 | 0 | } else if (type == LUT16_TYPE) { |
706 | 0 | num_input_table_entries = read_u16(src, offset + 48); |
707 | 0 | num_output_table_entries = read_u16(src, offset + 50); |
708 | 0 | if (num_input_table_entries == 0 || num_output_table_entries == 0) { |
709 | 0 | invalid_source(src, "Bad channel count"); |
710 | 0 | return NULL; |
711 | 0 | } |
712 | 0 | entry_size = 2; |
713 | 0 | input_offset = 52; |
714 | 0 | } else { |
715 | 0 | assert(0); // the caller checks that this doesn't happen |
716 | 0 | invalid_source(src, "Unexpected lut type"); |
717 | 0 | return NULL; |
718 | 0 | } |
719 | 0 | |
720 | 0 | in_chan = read_u8(src, offset + 8); |
721 | 0 | out_chan = read_u8(src, offset + 9); |
722 | 0 | grid_points = read_u8(src, offset + 10); |
723 | 0 |
|
724 | 0 | clut_size = pow(grid_points, in_chan); |
725 | 0 | if (clut_size > MAX_CLUT_SIZE) { |
726 | 0 | invalid_source(src, "CLUT too large"); |
727 | 0 | return NULL; |
728 | 0 | } |
729 | 0 | |
730 | 0 | if (clut_size <= 0) { |
731 | 0 | invalid_source(src, "CLUT must not be empty."); |
732 | 0 | return NULL; |
733 | 0 | } |
734 | 0 | |
735 | 0 | if (in_chan != 3 || out_chan != 3) { |
736 | 0 | invalid_source(src, "CLUT only supports RGB"); |
737 | 0 | return NULL; |
738 | 0 | } |
739 | 0 | |
740 | 0 | lut = malloc(sizeof(struct lutType) + (num_input_table_entries * in_chan + clut_size*out_chan + num_output_table_entries * out_chan)*sizeof(float)); |
741 | 0 | if (!lut) { |
742 | 0 | invalid_source(src, "CLUT too large"); |
743 | 0 | return NULL; |
744 | 0 | } |
745 | 0 | |
746 | 0 | /* compute the offsets of tables */ |
747 | 0 | lut->input_table = &lut->table_data[0]; |
748 | 0 | lut->clut_table = &lut->table_data[in_chan*num_input_table_entries]; |
749 | 0 | lut->output_table = &lut->table_data[in_chan*num_input_table_entries + clut_size*out_chan]; |
750 | 0 |
|
751 | 0 | lut->num_input_table_entries = num_input_table_entries; |
752 | 0 | lut->num_output_table_entries = num_output_table_entries; |
753 | 0 | lut->num_input_channels = in_chan; |
754 | 0 | lut->num_output_channels = out_chan; |
755 | 0 | lut->num_clut_grid_points = grid_points; |
756 | 0 | lut->e00 = read_s15Fixed16Number(src, offset+12); |
757 | 0 | lut->e01 = read_s15Fixed16Number(src, offset+16); |
758 | 0 | lut->e02 = read_s15Fixed16Number(src, offset+20); |
759 | 0 | lut->e10 = read_s15Fixed16Number(src, offset+24); |
760 | 0 | lut->e11 = read_s15Fixed16Number(src, offset+28); |
761 | 0 | lut->e12 = read_s15Fixed16Number(src, offset+32); |
762 | 0 | lut->e20 = read_s15Fixed16Number(src, offset+36); |
763 | 0 | lut->e21 = read_s15Fixed16Number(src, offset+40); |
764 | 0 | lut->e22 = read_s15Fixed16Number(src, offset+44); |
765 | 0 |
|
766 | 0 | for (i = 0; i < (uint32_t)(lut->num_input_table_entries * in_chan); i++) { |
767 | 0 | if (type == LUT8_TYPE) { |
768 | 0 | lut->input_table[i] = uInt8Number_to_float(read_uInt8Number(src, offset + input_offset + i * entry_size)); |
769 | 0 | } else { |
770 | 0 | lut->input_table[i] = uInt16Number_to_float(read_uInt16Number(src, offset + input_offset + i * entry_size)); |
771 | 0 | } |
772 | 0 | } |
773 | 0 |
|
774 | 0 | clut_offset = offset + input_offset + lut->num_input_table_entries * in_chan * entry_size; |
775 | 0 | for (i = 0; i < clut_size * out_chan; i+=3) { |
776 | 0 | if (type == LUT8_TYPE) { |
777 | 0 | lut->clut_table[i+0] = uInt8Number_to_float(read_uInt8Number(src, clut_offset + i*entry_size + 0)); |
778 | 0 | lut->clut_table[i+1] = uInt8Number_to_float(read_uInt8Number(src, clut_offset + i*entry_size + 1)); |
779 | 0 | lut->clut_table[i+2] = uInt8Number_to_float(read_uInt8Number(src, clut_offset + i*entry_size + 2)); |
780 | 0 | } else { |
781 | 0 | lut->clut_table[i+0] = uInt16Number_to_float(read_uInt16Number(src, clut_offset + i*entry_size + 0)); |
782 | 0 | lut->clut_table[i+1] = uInt16Number_to_float(read_uInt16Number(src, clut_offset + i*entry_size + 2)); |
783 | 0 | lut->clut_table[i+2] = uInt16Number_to_float(read_uInt16Number(src, clut_offset + i*entry_size + 4)); |
784 | 0 | } |
785 | 0 | } |
786 | 0 |
|
787 | 0 | output_offset = clut_offset + clut_size * out_chan * entry_size; |
788 | 0 | for (i = 0; i < (uint32_t)(lut->num_output_table_entries * out_chan); i++) { |
789 | 0 | if (type == LUT8_TYPE) { |
790 | 0 | lut->output_table[i] = uInt8Number_to_float(read_uInt8Number(src, output_offset + i*entry_size)); |
791 | 0 | } else { |
792 | 0 | lut->output_table[i] = uInt16Number_to_float(read_uInt16Number(src, output_offset + i*entry_size)); |
793 | 0 | } |
794 | 0 | } |
795 | 0 |
|
796 | 0 | return lut; |
797 | 0 | } |
798 | | |
799 | | static void read_rendering_intent(qcms_profile *profile, struct mem_source *src) |
800 | | { |
801 | | profile->rendering_intent = read_u32(src, 64); |
802 | | switch (profile->rendering_intent) { |
803 | | case QCMS_INTENT_PERCEPTUAL: |
804 | | case QCMS_INTENT_SATURATION: |
805 | | case QCMS_INTENT_RELATIVE_COLORIMETRIC: |
806 | | case QCMS_INTENT_ABSOLUTE_COLORIMETRIC: |
807 | | break; |
808 | | default: |
809 | | invalid_source(src, "unknown rendering intent"); |
810 | | } |
811 | | } |
812 | | |
813 | | qcms_profile *qcms_profile_create(void) |
814 | 0 | { |
815 | 0 | return calloc(sizeof(qcms_profile), 1); |
816 | 0 | } |
817 | | |
818 | | |
819 | | |
820 | | /* build sRGB gamma table */ |
821 | | /* based on cmsBuildParametricGamma() */ |
822 | | static uint16_t *build_sRGB_gamma_table(int num_entries) |
823 | 0 | { |
824 | 0 | int i; |
825 | 0 | /* taken from lcms: Build_sRGBGamma() */ |
826 | 0 | double gamma = 2.4; |
827 | 0 | double a = 1./1.055; |
828 | 0 | double b = 0.055/1.055; |
829 | 0 | double c = 1./12.92; |
830 | 0 | double d = 0.04045; |
831 | 0 |
|
832 | 0 | uint16_t *table = malloc(sizeof(uint16_t) * num_entries); |
833 | 0 | if (!table) |
834 | 0 | return NULL; |
835 | 0 | |
836 | 0 | for (i=0; i<num_entries; i++) { |
837 | 0 | double x = (double)i / (num_entries-1); |
838 | 0 | double y, output; |
839 | 0 | // IEC 61966-2.1 (sRGB) |
840 | 0 | // Y = (aX + b)^Gamma | X >= d |
841 | 0 | // Y = cX | X < d |
842 | 0 | if (x >= d) { |
843 | 0 | double e = (a*x + b); |
844 | 0 | if (e > 0) |
845 | 0 | y = pow(e, gamma); |
846 | 0 | else |
847 | 0 | y = 0; |
848 | 0 | } else { |
849 | 0 | y = c*x; |
850 | 0 | } |
851 | 0 |
|
852 | 0 | // Saturate -- this could likely move to a separate function |
853 | 0 | output = y * 65535. + .5; |
854 | 0 | if (output > 65535.) |
855 | 0 | output = 65535; |
856 | 0 | if (output < 0) |
857 | 0 | output = 0; |
858 | 0 | table[i] = (uint16_t)floor(output); |
859 | 0 | } |
860 | 0 | return table; |
861 | 0 | } |
862 | | |
863 | | static struct curveType *curve_from_table(uint16_t *table, int num_entries) |
864 | 0 | { |
865 | 0 | struct curveType *curve; |
866 | 0 | int i; |
867 | 0 | curve = malloc(sizeof(struct curveType) + sizeof(uInt16Number)*num_entries); |
868 | 0 | if (!curve) |
869 | 0 | return NULL; |
870 | 0 | curve->type = CURVE_TYPE; |
871 | 0 | curve->count = num_entries; |
872 | 0 | for (i = 0; i < num_entries; i++) { |
873 | 0 | curve->data[i] = table[i]; |
874 | 0 | } |
875 | 0 | return curve; |
876 | 0 | } |
877 | | |
878 | | static uint16_t float_to_u8Fixed8Number(float a) |
879 | 0 | { |
880 | 0 | if (a > (255.f + 255.f/256)) |
881 | 0 | return 0xffff; |
882 | 0 | else if (a < 0.f) |
883 | 0 | return 0; |
884 | 0 | else |
885 | 0 | return floorf(a*256.f + .5f); |
886 | 0 | } |
887 | | |
888 | | static struct curveType *curve_from_gamma(float gamma) |
889 | 0 | { |
890 | 0 | struct curveType *curve; |
891 | 0 | int num_entries = 1; |
892 | 0 | curve = malloc(sizeof(struct curveType) + sizeof(uInt16Number)*num_entries); |
893 | 0 | if (!curve) |
894 | 0 | return NULL; |
895 | 0 | curve->count = num_entries; |
896 | 0 | curve->data[0] = float_to_u8Fixed8Number(gamma); |
897 | 0 | curve->type = CURVE_TYPE; |
898 | 0 | return curve; |
899 | 0 | } |
900 | | |
901 | | //XXX: it would be nice if we had a way of ensuring |
902 | | // everything in a profile was initialized regardless of how it was created |
903 | | |
904 | | //XXX: should this also be taking a black_point? |
905 | | /* similar to CGColorSpaceCreateCalibratedRGB */ |
906 | | qcms_profile* qcms_profile_create_rgb_with_gamma( |
907 | | qcms_CIE_xyY white_point, |
908 | | qcms_CIE_xyYTRIPLE primaries, |
909 | | float gamma) |
910 | 0 | { |
911 | 0 | qcms_profile* profile = qcms_profile_create(); |
912 | 0 | if (!profile) |
913 | 0 | return NO_MEM_PROFILE; |
914 | 0 | |
915 | 0 | //XXX: should store the whitepoint |
916 | 0 | if (!set_rgb_colorants(profile, white_point, primaries)) { |
917 | 0 | qcms_profile_release(profile); |
918 | 0 | return INVALID_PROFILE; |
919 | 0 | } |
920 | 0 |
|
921 | 0 | profile->redTRC = curve_from_gamma(gamma); |
922 | 0 | profile->blueTRC = curve_from_gamma(gamma); |
923 | 0 | profile->greenTRC = curve_from_gamma(gamma); |
924 | 0 |
|
925 | 0 | if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) { |
926 | 0 | qcms_profile_release(profile); |
927 | 0 | return NO_MEM_PROFILE; |
928 | 0 | } |
929 | 0 | profile->class = DISPLAY_DEVICE_PROFILE; |
930 | 0 | profile->rendering_intent = QCMS_INTENT_PERCEPTUAL; |
931 | 0 | profile->color_space = RGB_SIGNATURE; |
932 | 0 | return profile; |
933 | 0 | } |
934 | | |
935 | | qcms_profile* qcms_profile_create_rgb_with_table( |
936 | | qcms_CIE_xyY white_point, |
937 | | qcms_CIE_xyYTRIPLE primaries, |
938 | | uint16_t *table, int num_entries) |
939 | 0 | { |
940 | 0 | qcms_profile* profile = qcms_profile_create(); |
941 | 0 | if (!profile) |
942 | 0 | return NO_MEM_PROFILE; |
943 | 0 | |
944 | 0 | //XXX: should store the whitepoint |
945 | 0 | if (!set_rgb_colorants(profile, white_point, primaries)) { |
946 | 0 | qcms_profile_release(profile); |
947 | 0 | return INVALID_PROFILE; |
948 | 0 | } |
949 | 0 |
|
950 | 0 | profile->redTRC = curve_from_table(table, num_entries); |
951 | 0 | profile->blueTRC = curve_from_table(table, num_entries); |
952 | 0 | profile->greenTRC = curve_from_table(table, num_entries); |
953 | 0 |
|
954 | 0 | if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) { |
955 | 0 | qcms_profile_release(profile); |
956 | 0 | return NO_MEM_PROFILE; |
957 | 0 | } |
958 | 0 | profile->class = DISPLAY_DEVICE_PROFILE; |
959 | 0 | profile->rendering_intent = QCMS_INTENT_PERCEPTUAL; |
960 | 0 | profile->color_space = RGB_SIGNATURE; |
961 | 0 | return profile; |
962 | 0 | } |
963 | | |
964 | | /* from lcms: cmsWhitePointFromTemp */ |
965 | | /* tempK must be >= 4000. and <= 25000. |
966 | | * Invalid values of tempK will return |
967 | | * (x,y,Y) = (-1.0, -1.0, -1.0) |
968 | | * similar to argyll: icx_DTEMP2XYZ() */ |
969 | | static qcms_CIE_xyY white_point_from_temp(int temp_K) |
970 | 0 | { |
971 | 0 | qcms_CIE_xyY white_point; |
972 | 0 | double x, y; |
973 | 0 | double T, T2, T3; |
974 | 0 | // double M1, M2; |
975 | 0 |
|
976 | 0 | // No optimization provided. |
977 | 0 | T = temp_K; |
978 | 0 | T2 = T*T; // Square |
979 | 0 | T3 = T2*T; // Cube |
980 | 0 |
|
981 | 0 | // For correlated color temperature (T) between 4000K and 7000K: |
982 | 0 | if (T >= 4000. && T <= 7000.) { |
983 | 0 | x = -4.6070*(1E9/T3) + 2.9678*(1E6/T2) + 0.09911*(1E3/T) + 0.244063; |
984 | 0 | } else { |
985 | 0 | // or for correlated color temperature (T) between 7000K and 25000K: |
986 | 0 | if (T > 7000.0 && T <= 25000.0) { |
987 | 0 | x = -2.0064*(1E9/T3) + 1.9018*(1E6/T2) + 0.24748*(1E3/T) + 0.237040; |
988 | 0 | } else { |
989 | 0 | // Invalid tempK |
990 | 0 | white_point.x = -1.0; |
991 | 0 | white_point.y = -1.0; |
992 | 0 | white_point.Y = -1.0; |
993 | 0 |
|
994 | 0 | assert(0 && "invalid temp"); |
995 | 0 |
|
996 | 0 | return white_point; |
997 | 0 | } |
998 | 0 | } |
999 | 0 | |
1000 | 0 | // Obtain y(x) |
1001 | 0 | |
1002 | 0 | y = -3.000*(x*x) + 2.870*x - 0.275; |
1003 | 0 |
|
1004 | 0 | // wave factors (not used, but here for futures extensions) |
1005 | 0 |
|
1006 | 0 | // M1 = (-1.3515 - 1.7703*x + 5.9114 *y)/(0.0241 + 0.2562*x - 0.7341*y); |
1007 | 0 | // M2 = (0.0300 - 31.4424*x + 30.0717*y)/(0.0241 + 0.2562*x - 0.7341*y); |
1008 | 0 |
|
1009 | 0 | // Fill white_point struct |
1010 | 0 | white_point.x = x; |
1011 | 0 | white_point.y = y; |
1012 | 0 | white_point.Y = 1.0; |
1013 | 0 |
|
1014 | 0 | return white_point; |
1015 | 0 | } |
1016 | | |
1017 | | qcms_profile* qcms_profile_sRGB(void) |
1018 | 0 | { |
1019 | 0 | qcms_profile *profile; |
1020 | 0 | uint16_t *table; |
1021 | 0 |
|
1022 | 0 | qcms_CIE_xyYTRIPLE Rec709Primaries = { |
1023 | 0 | {0.6400, 0.3300, 1.0}, |
1024 | 0 | {0.3000, 0.6000, 1.0}, |
1025 | 0 | {0.1500, 0.0600, 1.0} |
1026 | 0 | }; |
1027 | 0 | qcms_CIE_xyY D65; |
1028 | 0 |
|
1029 | 0 | D65 = white_point_from_temp(6504); |
1030 | 0 |
|
1031 | 0 | table = build_sRGB_gamma_table(1024); |
1032 | 0 |
|
1033 | 0 | if (!table) |
1034 | 0 | return NO_MEM_PROFILE; |
1035 | 0 | |
1036 | 0 | profile = qcms_profile_create_rgb_with_table(D65, Rec709Primaries, table, 1024); |
1037 | 0 | free(table); |
1038 | 0 | return profile; |
1039 | 0 | } |
1040 | | |
1041 | | |
1042 | | /* qcms_profile_from_memory does not hold a reference to the memory passed in */ |
1043 | | qcms_profile* qcms_profile_from_memory(const void *mem, size_t size) |
1044 | 0 | { |
1045 | 0 | uint32_t length; |
1046 | 0 | struct mem_source source; |
1047 | 0 | struct mem_source *src = &source; |
1048 | 0 | struct tag_index index; |
1049 | 0 | qcms_profile *profile; |
1050 | 0 |
|
1051 | 0 | source.buf = mem; |
1052 | 0 | source.size = size; |
1053 | 0 | source.valid = true; |
1054 | 0 |
|
1055 | 0 | if (size < 4) |
1056 | 0 | return INVALID_PROFILE; |
1057 | 0 | |
1058 | 0 | length = read_u32(src, 0); |
1059 | 0 | if (length <= size) { |
1060 | 0 | // shrink the area that we can read if appropriate |
1061 | 0 | source.size = length; |
1062 | 0 | } else { |
1063 | 0 | return INVALID_PROFILE; |
1064 | 0 | } |
1065 | 0 |
|
1066 | 0 | /* ensure that the profile size is sane so it's easier to reason about */ |
1067 | 0 | if (source.size <= 64 || source.size >= MAX_PROFILE_SIZE) |
1068 | 0 | return INVALID_PROFILE; |
1069 | 0 | |
1070 | 0 | profile = qcms_profile_create(); |
1071 | 0 | if (!profile) |
1072 | 0 | return NO_MEM_PROFILE; |
1073 | 0 | |
1074 | 0 | check_CMM_type_signature(src); |
1075 | 0 | check_profile_version(src); |
1076 | 0 | read_class_signature(profile, src); |
1077 | 0 | read_rendering_intent(profile, src); |
1078 | 0 | read_color_space(profile, src); |
1079 | 0 | read_pcs(profile, src); |
1080 | 0 | //TODO read rest of profile stuff |
1081 | 0 |
|
1082 | 0 | if (!src->valid) |
1083 | 0 | goto invalid_profile; |
1084 | 0 | |
1085 | 0 | index = read_tag_table(profile, src); |
1086 | 0 | if (!src->valid || !index.tags) |
1087 | 0 | goto invalid_tag_table; |
1088 | 0 | |
1089 | 0 | if (find_tag(index, TAG_CHAD)) { |
1090 | 0 | profile->chromaticAdaption = read_tag_s15Fixed16ArrayType(src, index, TAG_CHAD); |
1091 | 0 | } else { |
1092 | 0 | profile->chromaticAdaption.invalid = true; //Signal the data is not present |
1093 | 0 | } |
1094 | 0 |
|
1095 | 0 | if (profile->class == DISPLAY_DEVICE_PROFILE || profile->class == INPUT_DEVICE_PROFILE || |
1096 | 0 | profile->class == OUTPUT_DEVICE_PROFILE || profile->class == COLOR_SPACE_PROFILE) { |
1097 | 0 | if (profile->color_space == RGB_SIGNATURE) { |
1098 | 0 | if (find_tag(index, TAG_A2B0)) { |
1099 | 0 | if (read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT8_TYPE || |
1100 | 0 | read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT16_TYPE) { |
1101 | 0 | profile->A2B0 = read_tag_lutType(src, index, TAG_A2B0); |
1102 | 0 | } else if (read_u32(src, find_tag(index, TAG_A2B0)->offset) == LUT_MAB_TYPE) { |
1103 | 0 | profile->mAB = read_tag_lutmABType(src, index, TAG_A2B0); |
1104 | 0 | } |
1105 | 0 | } |
1106 | 0 | if (find_tag(index, TAG_B2A0)) { |
1107 | 0 | if (read_u32(src, find_tag(index, TAG_B2A0)->offset) == LUT8_TYPE || |
1108 | 0 | read_u32(src, find_tag(index, TAG_B2A0)->offset) == LUT16_TYPE) { |
1109 | 0 | profile->B2A0 = read_tag_lutType(src, index, TAG_B2A0); |
1110 | 0 | } else if (read_u32(src, find_tag(index, TAG_B2A0)->offset) == LUT_MBA_TYPE) { |
1111 | 0 | profile->mBA = read_tag_lutmABType(src, index, TAG_B2A0); |
1112 | 0 | } |
1113 | 0 | } |
1114 | 0 | if (find_tag(index, TAG_rXYZ) || !qcms_supports_iccv4) { |
1115 | 0 | profile->redColorant = read_tag_XYZType(src, index, TAG_rXYZ); |
1116 | 0 | profile->greenColorant = read_tag_XYZType(src, index, TAG_gXYZ); |
1117 | 0 | profile->blueColorant = read_tag_XYZType(src, index, TAG_bXYZ); |
1118 | 0 | } |
1119 | 0 |
|
1120 | 0 | if (!src->valid) |
1121 | 0 | goto invalid_tag_table; |
1122 | 0 | |
1123 | 0 | if (find_tag(index, TAG_rTRC) || !qcms_supports_iccv4) { |
1124 | 0 | profile->redTRC = read_tag_curveType(src, index, TAG_rTRC); |
1125 | 0 | profile->greenTRC = read_tag_curveType(src, index, TAG_gTRC); |
1126 | 0 | profile->blueTRC = read_tag_curveType(src, index, TAG_bTRC); |
1127 | 0 |
|
1128 | 0 | if (!profile->redTRC || !profile->blueTRC || !profile->greenTRC) |
1129 | 0 | goto invalid_tag_table; |
1130 | 0 | } |
1131 | 0 | } else if (profile->color_space == GRAY_SIGNATURE) { |
1132 | 0 |
|
1133 | 0 | profile->grayTRC = read_tag_curveType(src, index, TAG_kTRC); |
1134 | 0 | if (!profile->grayTRC) |
1135 | 0 | goto invalid_tag_table; |
1136 | 0 | |
1137 | 0 | } else { |
1138 | 0 | assert(0 && "read_color_space protects against entering here"); |
1139 | 0 | goto invalid_tag_table; |
1140 | 0 | } |
1141 | 0 | } else { |
1142 | 0 | goto invalid_tag_table; |
1143 | 0 | } |
1144 | 0 | |
1145 | 0 | if (!src->valid) |
1146 | 0 | goto invalid_tag_table; |
1147 | 0 | |
1148 | 0 | free(index.tags); |
1149 | 0 |
|
1150 | 0 | return profile; |
1151 | 0 | |
1152 | 0 | invalid_tag_table: |
1153 | 0 | free(index.tags); |
1154 | 0 | invalid_profile: |
1155 | 0 | qcms_profile_release(profile); |
1156 | 0 | return INVALID_PROFILE; |
1157 | 0 | } |
1158 | | |
1159 | | qcms_intent qcms_profile_get_rendering_intent(qcms_profile *profile) |
1160 | 0 | { |
1161 | 0 | return profile->rendering_intent; |
1162 | 0 | } |
1163 | | |
1164 | | icColorSpaceSignature |
1165 | | qcms_profile_get_color_space(qcms_profile *profile) |
1166 | 0 | { |
1167 | 0 | return profile->color_space; |
1168 | 0 | } |
1169 | | |
1170 | | static void lut_release(struct lutType *lut) |
1171 | 0 | { |
1172 | 0 | free(lut); |
1173 | 0 | } |
1174 | | |
1175 | | void qcms_profile_release(qcms_profile *profile) |
1176 | 0 | { |
1177 | 0 | if (profile->output_table_r) |
1178 | 0 | precache_release(profile->output_table_r); |
1179 | 0 | if (profile->output_table_g) |
1180 | 0 | precache_release(profile->output_table_g); |
1181 | 0 | if (profile->output_table_b) |
1182 | 0 | precache_release(profile->output_table_b); |
1183 | 0 |
|
1184 | 0 | if (profile->A2B0) |
1185 | 0 | lut_release(profile->A2B0); |
1186 | 0 | if (profile->B2A0) |
1187 | 0 | lut_release(profile->B2A0); |
1188 | 0 |
|
1189 | 0 | if (profile->mAB) |
1190 | 0 | mAB_release(profile->mAB); |
1191 | 0 | if (profile->mBA) |
1192 | 0 | mAB_release(profile->mBA); |
1193 | 0 |
|
1194 | 0 | free(profile->redTRC); |
1195 | 0 | free(profile->blueTRC); |
1196 | 0 | free(profile->greenTRC); |
1197 | 0 | free(profile->grayTRC); |
1198 | 0 | free(profile); |
1199 | 0 | } |
1200 | | |
1201 | | |
1202 | | #include <stdio.h> |
1203 | | static void qcms_data_from_file(FILE *file, void **mem, size_t *size) |
1204 | 0 | { |
1205 | 0 | uint32_t length, remaining_length; |
1206 | 0 | size_t read_length; |
1207 | 0 | be32 length_be; |
1208 | 0 | void *data; |
1209 | 0 |
|
1210 | 0 | *mem = NULL; |
1211 | 0 | *size = 0; |
1212 | 0 |
|
1213 | 0 | if (fread(&length_be, 1, sizeof(length_be), file) != sizeof(length_be)) |
1214 | 0 | return; |
1215 | 0 | |
1216 | 0 | length = be32_to_cpu(length_be); |
1217 | 0 | if (length > MAX_PROFILE_SIZE || length < sizeof(length_be)) |
1218 | 0 | return; |
1219 | 0 | |
1220 | 0 | /* allocate room for the entire profile */ |
1221 | 0 | data = malloc(length); |
1222 | 0 | if (!data) |
1223 | 0 | return; |
1224 | 0 | |
1225 | 0 | /* copy in length to the front so that the buffer will contain the entire profile */ |
1226 | 0 | *((be32*)data) = length_be; |
1227 | 0 | remaining_length = length - sizeof(length_be); |
1228 | 0 |
|
1229 | 0 | /* read the rest profile */ |
1230 | 0 | read_length = fread((unsigned char*)data + sizeof(length_be), 1, remaining_length, file); |
1231 | 0 | if (read_length != remaining_length) { |
1232 | 0 | free(data); |
1233 | 0 | return; |
1234 | 0 | } |
1235 | 0 | |
1236 | 0 | /* successfully get the profile.*/ |
1237 | 0 | *mem = data; |
1238 | 0 | *size = length; |
1239 | 0 | } |
1240 | | |
1241 | | qcms_profile* qcms_profile_from_file(FILE *file) |
1242 | 0 | { |
1243 | 0 | size_t length; |
1244 | 0 | qcms_profile *profile; |
1245 | 0 | void *data; |
1246 | 0 |
|
1247 | 0 | qcms_data_from_file(file, &data, &length); |
1248 | 0 | if ((data == NULL) || (length == 0)) |
1249 | 0 | return INVALID_PROFILE; |
1250 | 0 | |
1251 | 0 | profile = qcms_profile_from_memory(data, length); |
1252 | 0 | free(data); |
1253 | 0 | return profile; |
1254 | 0 | } |
1255 | | |
1256 | | qcms_profile* qcms_profile_from_path(const char *path) |
1257 | 0 | { |
1258 | 0 | qcms_profile *profile = NULL; |
1259 | 0 | FILE *file = fopen(path, "rb"); |
1260 | 0 | if (file) { |
1261 | 0 | profile = qcms_profile_from_file(file); |
1262 | 0 | fclose(file); |
1263 | 0 | } |
1264 | 0 | return profile; |
1265 | 0 | } |
1266 | | |
1267 | | void qcms_data_from_path(const char *path, void **mem, size_t *size) |
1268 | 0 | { |
1269 | 0 | FILE *file = NULL; |
1270 | 0 | *mem = NULL; |
1271 | 0 | *size = 0; |
1272 | 0 | |
1273 | 0 | file = fopen(path, "rb"); |
1274 | 0 | if (file) { |
1275 | 0 | qcms_data_from_file(file, mem, size); |
1276 | 0 | fclose(file); |
1277 | 0 | } |
1278 | 0 | } |
1279 | | |
1280 | | #ifdef _WIN32 |
1281 | | /* Unicode path version */ |
1282 | | qcms_profile* qcms_profile_from_unicode_path(const wchar_t *path) |
1283 | | { |
1284 | | qcms_profile *profile = NULL; |
1285 | | FILE *file = _wfopen(path, L"rb"); |
1286 | | if (file) { |
1287 | | profile = qcms_profile_from_file(file); |
1288 | | fclose(file); |
1289 | | } |
1290 | | return profile; |
1291 | | } |
1292 | | |
1293 | | void qcms_data_from_unicode_path(const wchar_t *path, void **mem, size_t *size) |
1294 | | { |
1295 | | FILE *file = NULL; |
1296 | | *mem = NULL; |
1297 | | *size = 0; |
1298 | | |
1299 | | file = _wfopen(path, L"rb"); |
1300 | | if (file) { |
1301 | | qcms_data_from_file(file, mem, size); |
1302 | | fclose(file); |
1303 | | } |
1304 | | } |
1305 | | #endif |
1306 | | |
1307 | | /* |
1308 | | * This function constructs an ICC profile memory with given header and tag data, |
1309 | | * which can be read via qcms_profile_from_memory(). that means, we must satisfy |
1310 | | * the profiler header type check (which seems not complete till now) and proper |
1311 | | * information to read data from the tag table and tag data elements memory. |
1312 | | * |
1313 | | * To construct a valid ICC profile, its divided into three steps : |
1314 | | * (1) construct the r/g/bXYZ part |
1315 | | * (2) construct the r/g/bTRC part |
1316 | | * (3) construct the profile header |
1317 | | * this is a hardcode step just for "create_rgb_with_gamma", it is the only |
1318 | | * requirement till now, maybe we can make this method more general in future, |
1319 | | * |
1320 | | * NOTE : some of the parameters below are hardcode, please refer to the ICC documentation. |
1321 | | */ |
1322 | 0 | #define ICC_PROFILE_HEADER_LENGTH 128 |
1323 | | void qcms_data_create_rgb_with_gamma(qcms_CIE_xyY white_point, qcms_CIE_xyYTRIPLE primaries, float gamma, void **mem, size_t *size) |
1324 | 0 | { |
1325 | 0 | uint32_t length, index, xyz_count, trc_count; |
1326 | 0 | size_t tag_table_offset, tag_data_offset; |
1327 | 0 | void *data; |
1328 | 0 | struct matrix colorants; |
1329 | 0 |
|
1330 | 0 | uint32_t TAG_XYZ[3] = {TAG_rXYZ, TAG_gXYZ, TAG_bXYZ}; |
1331 | 0 | uint32_t TAG_TRC[3] = {TAG_rTRC, TAG_gTRC, TAG_bTRC}; |
1332 | 0 |
|
1333 | 0 | if ((mem == NULL) || (size == NULL)) |
1334 | 0 | return; |
1335 | 0 | |
1336 | 0 | *mem = NULL; |
1337 | 0 | *size = 0; |
1338 | 0 |
|
1339 | 0 | /* |
1340 | 0 | * total length = icc profile header(128) + tag count(4) + |
1341 | 0 | * (tag table item (12) * total tag (6 = 3 rTRC + 3 rXYZ)) + rTRC elements data (3 * 20) |
1342 | 0 | * + rXYZ elements data (3*16), and all tag data elements must start at the 4-byte boundary. |
1343 | 0 | */ |
1344 | 0 | xyz_count = 3; // rXYZ, gXYZ, bXYZ |
1345 | 0 | trc_count = 3; // rTRC, gTRC, bTRC |
1346 | 0 | length = ICC_PROFILE_HEADER_LENGTH + 4 + (12 * (xyz_count + trc_count)) + (xyz_count * 20) + (trc_count * 16); |
1347 | 0 | |
1348 | 0 | // reserve the total memory. |
1349 | 0 | data = malloc(length); |
1350 | 0 | if (!data) |
1351 | 0 | return; |
1352 | 0 | memset(data, 0, length); |
1353 | 0 |
|
1354 | 0 | // Part1 : write rXYZ, gXYZ and bXYZ |
1355 | 0 | if (!get_rgb_colorants(&colorants, white_point, primaries)) { |
1356 | 0 | free(data); |
1357 | 0 | return; |
1358 | 0 | } |
1359 | 0 | |
1360 | 0 | // the position of first tag's signature in tag table |
1361 | 0 | tag_table_offset = ICC_PROFILE_HEADER_LENGTH + 4; |
1362 | 0 | tag_data_offset = ICC_PROFILE_HEADER_LENGTH + 4 + |
1363 | 0 | (12 * (xyz_count + trc_count)); // the start of tag data elements. |
1364 | 0 |
|
1365 | 0 | for (index = 0; index < xyz_count; ++index) { |
1366 | 0 | // tag table |
1367 | 0 | write_u32(data, tag_table_offset, TAG_XYZ[index]); |
1368 | 0 | write_u32(data, tag_table_offset+4, tag_data_offset); |
1369 | 0 | write_u32(data, tag_table_offset+8, 20); // 20 bytes per TAG_(r/g/b)XYZ tag element |
1370 | 0 |
|
1371 | 0 | // tag data element |
1372 | 0 | write_u32(data, tag_data_offset, XYZ_TYPE); |
1373 | 0 | // reserved 4 bytes. |
1374 | 0 | write_u32(data, tag_data_offset+8, double_to_s15Fixed16Number(colorants.m[0][index])); |
1375 | 0 | write_u32(data, tag_data_offset+12, double_to_s15Fixed16Number(colorants.m[1][index])); |
1376 | 0 | write_u32(data, tag_data_offset+16, double_to_s15Fixed16Number(colorants.m[2][index])); |
1377 | 0 |
|
1378 | 0 | tag_table_offset += 12; |
1379 | 0 | tag_data_offset += 20; |
1380 | 0 | } |
1381 | 0 |
|
1382 | 0 | // Part2 : write rTRC, gTRC and bTRC |
1383 | 0 | for (index = 0; index < trc_count; ++index) { |
1384 | 0 | // tag table |
1385 | 0 | write_u32(data, tag_table_offset, TAG_TRC[index]); |
1386 | 0 | write_u32(data, tag_table_offset+4, tag_data_offset); |
1387 | 0 | write_u32(data, tag_table_offset+8, 14); // 14 bytes per TAG_(r/g/b)TRC element |
1388 | 0 |
|
1389 | 0 | // tag data element |
1390 | 0 | write_u32(data, tag_data_offset, CURVE_TYPE); |
1391 | 0 | // reserved 4 bytes. |
1392 | 0 | write_u32(data, tag_data_offset+8, 1); // count |
1393 | 0 | write_u16(data, tag_data_offset+12, float_to_u8Fixed8Number(gamma)); |
1394 | 0 |
|
1395 | 0 | tag_table_offset += 12; |
1396 | 0 | tag_data_offset += 16; |
1397 | 0 | } |
1398 | 0 |
|
1399 | 0 | /* Part3 : write profile header |
1400 | 0 | * |
1401 | 0 | * Important header fields are left empty. This generates a profile for internal use only. |
1402 | 0 | * We should be generating: Profile version (04300000h), Profile signature (acsp), |
1403 | 0 | * PCS illumiant field. Likewise mandatory profile tags are omitted. |
1404 | 0 | */ |
1405 | 0 | write_u32(data, 0, length); // the total length of this memory |
1406 | 0 | write_u32(data, 12, DISPLAY_DEVICE_PROFILE); // profile->class |
1407 | 0 | write_u32(data, 16, RGB_SIGNATURE); // profile->color_space |
1408 | 0 | write_u32(data, 20, XYZ_SIGNATURE); // profile->pcs |
1409 | 0 | write_u32(data, 64, QCMS_INTENT_PERCEPTUAL); // profile->rendering_intent |
1410 | 0 |
|
1411 | 0 | write_u32(data, ICC_PROFILE_HEADER_LENGTH, 6); // total tag count |
1412 | 0 |
|
1413 | 0 | // prepare the result |
1414 | 0 | *mem = data; |
1415 | 0 | *size = length; |
1416 | 0 | } |