1 | #include "mupdf/fitz.h" |
2 | #include "icc34.h" |
3 | |
4 | #include <string.h> |
5 | |
6 | #define SAVEICCPROFILE 0 |
7 | #define 128 |
8 | #define ICC_TAG_SIZE 12 |
9 | #define ICC_NUMBER_COMMON_TAGS 2 |
10 | #define ICC_XYZPT_SIZE 12 |
11 | #define ICC_DATATYPE_SIZE 8 |
12 | #define D50_X 0.9642f |
13 | #define D50_Y 1.0f |
14 | #define D50_Z 0.8249f |
15 | static const char copy_right[] = "Copyright Artifex Software 2017" ; |
16 | #if SAVEICCPROFILE |
17 | unsigned int icc_debug_index = 0; |
18 | #endif |
19 | |
20 | typedef struct fz_icc_tag_s fz_icc_tag; |
21 | |
22 | struct fz_icc_tag_s |
23 | { |
24 | icTagSignature sig; |
25 | icUInt32Number offset; |
26 | icUInt32Number size; |
27 | unsigned char byte_padding; |
28 | }; |
29 | |
30 | #if SAVEICCPROFILE |
31 | static void |
32 | save_profile(fz_context *ctx, fz_buffer *buf, const char *name) |
33 | { |
34 | char full_file_name[50]; |
35 | fz_snprintf(full_file_name, sizeof full_file_name, "profile%d-%s.icc" , icc_debug_index, name); |
36 | fz_save_buffer(ctx, buf, full_file_name); |
37 | icc_debug_index++; |
38 | } |
39 | #endif |
40 | |
41 | static void |
42 | fz_append_byte_n(fz_context *ctx, fz_buffer *buf, int c, int n) |
43 | { |
44 | int k; |
45 | for (k = 0; k < n; k++) |
46 | fz_append_byte(ctx, buf, c); |
47 | } |
48 | |
49 | static int |
50 | get_padding(int x) |
51 | { |
52 | return (4 - x % 4) % 4; |
53 | } |
54 | |
55 | static void |
56 | setdatetime(fz_context *ctx, icDateTimeNumber *datetime) |
57 | { |
58 | datetime->day = 0; |
59 | datetime->hours = 0; |
60 | datetime->minutes = 0; |
61 | datetime->month = 0; |
62 | datetime->seconds = 0; |
63 | datetime->year = 0; |
64 | } |
65 | |
66 | static void |
67 | add_gammadata(fz_context *ctx, fz_buffer *buf, unsigned short gamma, icTagTypeSignature curveType) |
68 | { |
69 | fz_append_int32_be(ctx, buf, curveType); |
70 | fz_append_byte_n(ctx, buf, 0, 4); |
71 | |
72 | /* one entry for gamma */ |
73 | fz_append_int32_be(ctx, buf, 1); |
74 | |
75 | /* The encode (8frac8) gamma, with padding */ |
76 | fz_append_int16_be(ctx, buf, gamma); |
77 | |
78 | /* pad two bytes */ |
79 | fz_append_byte_n(ctx, buf, 0, 2); |
80 | } |
81 | |
82 | static unsigned short |
83 | float2u8Fixed8(fz_context *ctx, float number_in) |
84 | { |
85 | return (unsigned short)(number_in * 256); |
86 | } |
87 | |
88 | static void |
89 | add_xyzdata(fz_context *ctx, fz_buffer *buf, icS15Fixed16Number temp_XYZ[]) |
90 | { |
91 | int j; |
92 | |
93 | fz_append_int32_be(ctx, buf, icSigXYZType); |
94 | fz_append_byte_n(ctx, buf, 0, 4); |
95 | |
96 | for (j = 0; j < 3; j++) |
97 | fz_append_int32_be(ctx, buf, temp_XYZ[j]); |
98 | } |
99 | |
100 | static icS15Fixed16Number |
101 | double2XYZtype(fz_context *ctx, float number_in) |
102 | { |
103 | short s; |
104 | unsigned short m; |
105 | |
106 | if (number_in < 0) |
107 | number_in = 0; |
108 | s = (short)number_in; |
109 | m = (unsigned short)((number_in - s) * 65536); |
110 | return (icS15Fixed16Number) ((s << 16) | m); |
111 | } |
112 | |
113 | static void |
114 | get_D50(fz_context *ctx, icS15Fixed16Number XYZ[]) |
115 | { |
116 | XYZ[0] = double2XYZtype(ctx, D50_X); |
117 | XYZ[1] = double2XYZtype(ctx, D50_Y); |
118 | XYZ[2] = double2XYZtype(ctx, D50_Z); |
119 | } |
120 | |
121 | static void |
122 | get_XYZ_doubletr(fz_context *ctx, icS15Fixed16Number XYZ[], float vector[]) |
123 | { |
124 | XYZ[0] = double2XYZtype(ctx, vector[0]); |
125 | XYZ[1] = double2XYZtype(ctx, vector[1]); |
126 | XYZ[2] = double2XYZtype(ctx, vector[2]); |
127 | } |
128 | |
129 | static void |
130 | add_desc_tag(fz_context *ctx, fz_buffer *buf, const char text[], fz_icc_tag tag_list[], int curr_tag) |
131 | { |
132 | int len = strlen(text); |
133 | |
134 | fz_append_int32_be(ctx, buf, icSigTextDescriptionType); |
135 | fz_append_byte_n(ctx, buf, 0, 4); |
136 | fz_append_int32_be(ctx, buf, len + 1); |
137 | fz_append_string(ctx, buf, text); |
138 | /* 1 + 4 + 4 + 2 + 1 + 67 */ |
139 | fz_append_byte_n(ctx, buf, 0, 79); |
140 | fz_append_byte_n(ctx, buf, 0, tag_list[curr_tag].byte_padding); |
141 | } |
142 | |
143 | static void |
144 | add_text_tag(fz_context *ctx, fz_buffer *buf, const char text[], fz_icc_tag tag_list[], int curr_tag) |
145 | { |
146 | fz_append_int32_be(ctx, buf, icSigTextType); |
147 | fz_append_byte_n(ctx, buf, 0, 4); |
148 | fz_append_string(ctx, buf, text); |
149 | fz_append_byte(ctx, buf, 0); |
150 | fz_append_byte_n(ctx, buf, 0, tag_list[curr_tag].byte_padding); |
151 | } |
152 | |
153 | static void |
154 | add_common_tag_data(fz_context *ctx, fz_buffer *buf, fz_icc_tag tag_list[], const char *desc_name) |
155 | { |
156 | add_desc_tag(ctx, buf, desc_name, tag_list, 0); |
157 | add_text_tag(ctx, buf, copy_right, tag_list, 1); |
158 | } |
159 | |
160 | static void |
161 | init_common_tags(fz_context *ctx, fz_icc_tag tag_list[], int num_tags, int *last_tag, const char *desc_name) |
162 | { |
163 | int curr_tag, temp_size; |
164 | |
165 | if (*last_tag < 0) |
166 | curr_tag = 0; |
167 | else |
168 | curr_tag = (*last_tag) + 1; |
169 | |
170 | tag_list[curr_tag].offset = ICC_HEADER_SIZE + num_tags * ICC_TAG_SIZE + 4; |
171 | tag_list[curr_tag].sig = icSigProfileDescriptionTag; |
172 | |
173 | /* temp_size = DATATYPE_SIZE + 4 (zeros) + 4 (len) + strlen(desc_name) + 1 (null) + 4 + 4 + 2 + 1 + 67 + bytepad; */ |
174 | temp_size = strlen(desc_name) + 91; |
175 | |
176 | tag_list[curr_tag].byte_padding = get_padding(temp_size); |
177 | tag_list[curr_tag].size = temp_size + tag_list[curr_tag].byte_padding; |
178 | curr_tag++; |
179 | tag_list[curr_tag].offset = tag_list[curr_tag - 1].offset + tag_list[curr_tag - 1].size; |
180 | tag_list[curr_tag].sig = icSigCopyrightTag; |
181 | |
182 | /* temp_size = DATATYPE_SIZE + 4 (zeros) + strlen(copy_right) + 1 (null); */ |
183 | temp_size = strlen(copy_right) + 9; |
184 | tag_list[curr_tag].byte_padding = get_padding(temp_size); |
185 | tag_list[curr_tag].size = temp_size + tag_list[curr_tag].byte_padding; |
186 | *last_tag = curr_tag; |
187 | } |
188 | |
189 | static void |
190 | (fz_context *ctx, fz_buffer *buffer, icHeader *) |
191 | { |
192 | fz_append_int32_be(ctx, buffer, header->size); |
193 | fz_append_byte_n(ctx, buffer, 0, 4); |
194 | fz_append_int32_be(ctx, buffer, header->version); |
195 | fz_append_int32_be(ctx, buffer, header->deviceClass); |
196 | fz_append_int32_be(ctx, buffer, header->colorSpace); |
197 | fz_append_int32_be(ctx, buffer, header->pcs); |
198 | fz_append_byte_n(ctx, buffer, 0, 12); |
199 | fz_append_int32_be(ctx, buffer, header->magic); |
200 | fz_append_int32_be(ctx, buffer, header->platform); |
201 | fz_append_byte_n(ctx, buffer, 0, 24); |
202 | fz_append_int32_be(ctx, buffer, header->illuminant.X); |
203 | fz_append_int32_be(ctx, buffer, header->illuminant.Y); |
204 | fz_append_int32_be(ctx, buffer, header->illuminant.Z); |
205 | fz_append_byte_n(ctx, buffer, 0, 48); |
206 | } |
207 | |
208 | static void |
209 | (fz_context *ctx, icHeader *) |
210 | { |
211 | header->cmmId = 0; |
212 | header->version = 0x02200000; |
213 | setdatetime(ctx, &(header->date)); |
214 | header->magic = icMagicNumber; |
215 | header->platform = icSigMacintosh; |
216 | header->flags = 0; |
217 | header->manufacturer = 0; |
218 | header->model = 0; |
219 | header->attributes[0] = 0; |
220 | header->attributes[1] = 0; |
221 | header->renderingIntent = 3; |
222 | header->illuminant.X = double2XYZtype(ctx, (float) 0.9642); |
223 | header->illuminant.Y = double2XYZtype(ctx, (float) 1.0); |
224 | header->illuminant.Z = double2XYZtype(ctx, (float) 0.8249); |
225 | header->creator = 0; |
226 | memset(header->reserved, 0, 44); |
227 | } |
228 | |
229 | static void |
230 | copy_tagtable(fz_context *ctx, fz_buffer *buf, fz_icc_tag *tag_list, int num_tags) |
231 | { |
232 | int k; |
233 | |
234 | fz_append_int32_be(ctx, buf, num_tags); |
235 | for (k = 0; k < num_tags; k++) |
236 | { |
237 | fz_append_int32_be(ctx, buf, tag_list[k].sig); |
238 | fz_append_int32_be(ctx, buf, tag_list[k].offset); |
239 | fz_append_int32_be(ctx, buf, tag_list[k].size); |
240 | } |
241 | } |
242 | |
243 | static void |
244 | init_tag(fz_context *ctx, fz_icc_tag tag_list[], int *last_tag, icTagSignature tagsig, int datasize) |
245 | { |
246 | int curr_tag = (*last_tag) + 1; |
247 | |
248 | tag_list[curr_tag].offset = tag_list[curr_tag - 1].offset + tag_list[curr_tag - 1].size; |
249 | tag_list[curr_tag].sig = tagsig; |
250 | tag_list[curr_tag].byte_padding = get_padding(ICC_DATATYPE_SIZE + datasize); |
251 | tag_list[curr_tag].size = ICC_DATATYPE_SIZE + datasize + tag_list[curr_tag].byte_padding; |
252 | *last_tag = curr_tag; |
253 | } |
254 | |
255 | static void |
256 | matrixmult(fz_context *ctx, float leftmatrix[], int nlrow, int nlcol, float rightmatrix[], int nrrow, int nrcol, float result[]) |
257 | { |
258 | float *curr_row; |
259 | int k, l, j, ncols, nrows; |
260 | float sum; |
261 | |
262 | nrows = nlrow; |
263 | ncols = nrcol; |
264 | if (nlcol == nrrow) |
265 | { |
266 | for (k = 0; k < nrows; k++) |
267 | { |
268 | curr_row = &(leftmatrix[k*nlcol]); |
269 | for (l = 0; l < ncols; l++) |
270 | { |
271 | sum = 0.0; |
272 | for (j = 0; j < nlcol; j++) |
273 | sum = sum + curr_row[j] * rightmatrix[j*nrcol + l]; |
274 | result[k*ncols + l] = sum; |
275 | } |
276 | } |
277 | } |
278 | } |
279 | |
280 | static void |
281 | apply_adaption(fz_context *ctx, float matrix[], float in[], float out[]) |
282 | { |
283 | out[0] = matrix[0] * in[0] + matrix[1] * in[1] + matrix[2] * in[2]; |
284 | out[1] = matrix[3] * in[0] + matrix[4] * in[1] + matrix[5] * in[2]; |
285 | out[2] = matrix[6] * in[0] + matrix[7] * in[1] + matrix[8] * in[2]; |
286 | } |
287 | |
288 | /* |
289 | Compute the CAT02 transformation to get us from the Cal White point to the |
290 | D50 white point |
291 | */ |
292 | static void |
293 | gsicc_create_compute_cam(fz_context *ctx, float white_src[], float *cam) |
294 | { |
295 | float cat02matrix[] = { 0.7328f, 0.4296f, -0.1624f, -0.7036f, 1.6975f, 0.0061f, 0.003f, 0.0136f, 0.9834f }; |
296 | float cat02matrixinv[] = { 1.0961f, -0.2789f, 0.1827f, 0.4544f, 0.4735f, 0.0721f, -0.0096f, -0.0057f, 1.0153f }; |
297 | float vonkries_diag[9]; |
298 | float temp_matrix[9]; |
299 | float lms_wp_src[3], lms_wp_des[3]; |
300 | int k; |
301 | float d50[3] = { D50_X, D50_Y, D50_Z }; |
302 | |
303 | matrixmult(ctx, cat02matrix, 3, 3, white_src, 3, 1, lms_wp_src); |
304 | matrixmult(ctx, cat02matrix, 3, 3, d50, 3, 1, lms_wp_des); |
305 | memset(&(vonkries_diag[0]), 0, sizeof(float) * 9); |
306 | |
307 | for (k = 0; k < 3; k++) |
308 | { |
309 | if (lms_wp_src[k] > 0) |
310 | vonkries_diag[k * 3 + k] = lms_wp_des[k] / lms_wp_src[k]; |
311 | else |
312 | vonkries_diag[k * 3 + k] = 1; |
313 | } |
314 | matrixmult(ctx, &(vonkries_diag[0]), 3, 3, cat02matrix, 3, 3, temp_matrix); |
315 | matrixmult(ctx, &(cat02matrixinv[0]), 3, 3, temp_matrix, 3, 3, cam); |
316 | } |
317 | |
318 | /* Create ICC profile from PDF calGray and calRGB definitions */ |
319 | fz_buffer * |
320 | fz_new_icc_data_from_cal(fz_context *ctx, |
321 | float wp[3], |
322 | float bp[3], |
323 | float gamma[3], |
324 | float matrix[9], |
325 | int n) |
326 | { |
327 | fz_icc_tag *tag_list; |
328 | icProfile iccprofile; |
329 | icHeader * = &(iccprofile.header); |
330 | fz_buffer *profile; |
331 | size_t profile_size; |
332 | int k; |
333 | int num_tags; |
334 | unsigned short encode_gamma; |
335 | int last_tag; |
336 | icS15Fixed16Number temp_XYZ[3]; |
337 | int tag_location; |
338 | icTagSignature TRC_Tags[3] = { icSigRedTRCTag, icSigGreenTRCTag, icSigBlueTRCTag }; |
339 | int trc_tag_size; |
340 | float cat02[9]; |
341 | float black_adapt[3]; |
342 | const char *desc_name; |
343 | |
344 | /* common */ |
345 | setheader_common(ctx, header); |
346 | header->pcs = icSigXYZData; |
347 | profile_size = ICC_HEADER_SIZE; |
348 | header->deviceClass = icSigInputClass; |
349 | |
350 | if (n == 3) |
351 | { |
352 | desc_name = "CalRGB" ; |
353 | header->colorSpace = icSigRgbData; |
354 | num_tags = 10; /* common (2) + rXYZ, gXYZ, bXYZ, rTRC, gTRC, bTRC, bkpt, wtpt */ |
355 | } |
356 | else |
357 | { |
358 | desc_name = "CalGray" ; |
359 | header->colorSpace = icSigGrayData; |
360 | num_tags = 5; /* common (2) + GrayTRC, bkpt, wtpt */ |
361 | TRC_Tags[0] = icSigGrayTRCTag; |
362 | } |
363 | |
364 | tag_list = fz_malloc(ctx, sizeof(fz_icc_tag) * num_tags); |
365 | |
366 | /* precompute sizes and offsets */ |
367 | profile_size += ICC_TAG_SIZE * num_tags; |
368 | profile_size += 4; /* number of tags.... */ |
369 | last_tag = -1; |
370 | init_common_tags(ctx, tag_list, num_tags, &last_tag, desc_name); |
371 | if (n == 3) |
372 | { |
373 | init_tag(ctx, tag_list, &last_tag, icSigRedColorantTag, ICC_XYZPT_SIZE); |
374 | init_tag(ctx, tag_list, &last_tag, icSigGreenColorantTag, ICC_XYZPT_SIZE); |
375 | init_tag(ctx, tag_list, &last_tag, icSigBlueColorantTag, ICC_XYZPT_SIZE); |
376 | } |
377 | init_tag(ctx, tag_list, &last_tag, icSigMediaWhitePointTag, ICC_XYZPT_SIZE); |
378 | init_tag(ctx, tag_list, &last_tag, icSigMediaBlackPointTag, ICC_XYZPT_SIZE); |
379 | |
380 | /* 4 for count, 2 for gamma, Extra 2 bytes for 4 byte alignment requirement */ |
381 | trc_tag_size = 8; |
382 | for (k = 0; k < n; k++) |
383 | init_tag(ctx, tag_list, &last_tag, TRC_Tags[k], trc_tag_size); |
384 | for (k = 0; k < num_tags; k++) |
385 | profile_size += tag_list[k].size; |
386 | |
387 | /* Allocate buffer */ |
388 | fz_try(ctx) |
389 | { |
390 | profile = fz_new_buffer(ctx, profile_size); |
391 | } |
392 | fz_catch(ctx) |
393 | { |
394 | fz_free(ctx, tag_list); |
395 | fz_rethrow(ctx); |
396 | } |
397 | |
398 | /* Header */ |
399 | header->size = (icUInt32Number)profile_size; |
400 | copy_header(ctx, profile, header); |
401 | |
402 | /* Tag table */ |
403 | copy_tagtable(ctx, profile, tag_list, num_tags); |
404 | |
405 | /* Common tags */ |
406 | add_common_tag_data(ctx, profile, tag_list, desc_name); |
407 | tag_location = ICC_NUMBER_COMMON_TAGS; |
408 | |
409 | /* Get the cat02 matrix */ |
410 | gsicc_create_compute_cam(ctx, wp, cat02); |
411 | |
412 | /* The matrix */ |
413 | if (n == 3) |
414 | { |
415 | float primary[3]; |
416 | |
417 | for (k = 0; k < 3; k++) |
418 | { |
419 | /* Apply the cat02 matrix to the primaries */ |
420 | apply_adaption(ctx, cat02, &(matrix[k * 3]), &(primary[0])); |
421 | get_XYZ_doubletr(ctx, temp_XYZ, &(primary[0])); |
422 | add_xyzdata(ctx, profile, temp_XYZ); |
423 | tag_location++; |
424 | } |
425 | } |
426 | |
427 | /* White and black points. WP is D50 */ |
428 | get_D50(ctx, temp_XYZ); |
429 | add_xyzdata(ctx, profile, temp_XYZ); |
430 | tag_location++; |
431 | |
432 | /* Black point. Apply cat02*/ |
433 | apply_adaption(ctx, cat02, bp, &(black_adapt[0])); |
434 | get_XYZ_doubletr(ctx, temp_XYZ, &(black_adapt[0])); |
435 | add_xyzdata(ctx, profile, temp_XYZ); |
436 | tag_location++; |
437 | |
438 | /* Gamma */ |
439 | for (k = 0; k < n; k++) |
440 | { |
441 | encode_gamma = float2u8Fixed8(ctx, gamma[k]); |
442 | add_gammadata(ctx, profile, encode_gamma, icSigCurveType); |
443 | tag_location++; |
444 | } |
445 | |
446 | fz_free(ctx, tag_list); |
447 | |
448 | #if SAVEICCPROFILE |
449 | if (n == 3) |
450 | save_profile(ctx, profile, "calRGB" ); |
451 | else |
452 | save_profile(ctx, profile, "calGray" ); |
453 | #endif |
454 | return profile; |
455 | } |
456 | |