1#include "mupdf/fitz.h"
2#include "icc34.h"
3
4#include <string.h>
5
6#define SAVEICCPROFILE 0
7#define ICC_HEADER_SIZE 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
15static const char copy_right[] = "Copyright Artifex Software 2017";
16#if SAVEICCPROFILE
17unsigned int icc_debug_index = 0;
18#endif
19
20typedef struct fz_icc_tag_s fz_icc_tag;
21
22struct fz_icc_tag_s
23{
24 icTagSignature sig;
25 icUInt32Number offset;
26 icUInt32Number size;
27 unsigned char byte_padding;
28};
29
30#if SAVEICCPROFILE
31static void
32save_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
41static void
42fz_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
49static int
50get_padding(int x)
51{
52 return (4 - x % 4) % 4;
53}
54
55static void
56setdatetime(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
66static void
67add_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
82static unsigned short
83float2u8Fixed8(fz_context *ctx, float number_in)
84{
85 return (unsigned short)(number_in * 256);
86}
87
88static void
89add_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
100static icS15Fixed16Number
101double2XYZtype(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
113static void
114get_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
121static void
122get_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
129static void
130add_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
143static void
144add_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
153static void
154add_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
160static void
161init_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
189static void
190copy_header(fz_context *ctx, fz_buffer *buffer, icHeader *header)
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
208static void
209setheader_common(fz_context *ctx, icHeader *header)
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
229static void
230copy_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
243static void
244init_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
255static void
256matrixmult(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
280static void
281apply_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*/
292static void
293gsicc_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 */
319fz_buffer *
320fz_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 *header = &(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