blob: 6b8c25df066763bb80497176ef416526928aa57e [file] [log] [blame]
Mike Kleinded7a552018-04-10 10:05:31 -04001/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "../skcms.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comb5d1f242018-05-09 18:44:08 +00009#include "LinearAlgebra.h"
Mike Kleinded7a552018-04-10 10:05:31 -040010#include "Macros.h"
11#include "PortableMath.h"
Mike Klein3101f652018-04-17 11:20:08 -040012#include "RandomBytes.h"
Mike Kleinded7a552018-04-10 10:05:31 -040013#include "TransferFunction.h"
14#include <assert.h>
15#include <limits.h>
16#include <stdlib.h>
17#include <string.h>
18
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +000019// Additional ICC signature values that are only used internally
20enum {
21 // File signature
22 skcms_Signature_acsp = 0x61637370,
23
24 // Tag signatures
25 skcms_Signature_rTRC = 0x72545243,
26 skcms_Signature_gTRC = 0x67545243,
27 skcms_Signature_bTRC = 0x62545243,
28 skcms_Signature_kTRC = 0x6B545243,
29
30 skcms_Signature_rXYZ = 0x7258595A,
31 skcms_Signature_gXYZ = 0x6758595A,
32 skcms_Signature_bXYZ = 0x6258595A,
33
34 skcms_Signature_A2B0 = 0x41324230,
35 skcms_Signature_A2B1 = 0x41324231,
36 skcms_Signature_mAB = 0x6D414220,
37
38 // Type signatures
39 skcms_Signature_curv = 0x63757276,
40 skcms_Signature_mft1 = 0x6D667431,
41 skcms_Signature_mft2 = 0x6D667432,
42 skcms_Signature_para = 0x70617261,
43 // XYZ is also a PCS signature, so it's defined in skcms.h
44 // skcms_Signature_XYZ = 0x58595A20,
45};
Mike Kleinded7a552018-04-10 10:05:31 -040046
47static uint16_t read_big_u16(const uint8_t* ptr) {
48 uint16_t be;
49 memcpy(&be, ptr, sizeof(be));
50#if defined(_MSC_VER)
51 return _byteswap_ushort(be);
52#else
53 return __builtin_bswap16(be);
54#endif
55}
56
57static uint32_t read_big_u32(const uint8_t* ptr) {
58 uint32_t be;
59 memcpy(&be, ptr, sizeof(be));
60#if defined(_MSC_VER)
61 return _byteswap_ulong(be);
62#else
63 return __builtin_bswap32(be);
64#endif
65}
66
67static int32_t read_big_i32(const uint8_t* ptr) {
68 return (int32_t)read_big_u32(ptr);
69}
70
71static float read_big_fixed(const uint8_t* ptr) {
72 return read_big_i32(ptr) * (1.0f / 65536.0f);
73}
74
75// Maps to an in-memory profile so that fields line up to the locations specified
76// in ICC.1:2010, section 7.2
77typedef struct {
78 uint8_t size [ 4];
79 uint8_t cmm_type [ 4];
80 uint8_t version [ 4];
81 uint8_t profile_class [ 4];
82 uint8_t data_color_space [ 4];
83 uint8_t pcs [ 4];
84 uint8_t creation_date_time [12];
85 uint8_t signature [ 4];
86 uint8_t platform [ 4];
87 uint8_t flags [ 4];
88 uint8_t device_manufacturer [ 4];
89 uint8_t device_model [ 4];
90 uint8_t device_attributes [ 8];
91 uint8_t rendering_intent [ 4];
92 uint8_t illuminant_X [ 4];
93 uint8_t illuminant_Y [ 4];
94 uint8_t illuminant_Z [ 4];
95 uint8_t creator [ 4];
96 uint8_t profile_id [16];
97 uint8_t reserved [28];
98 uint8_t tag_count [ 4]; // Technically not part of header, but required
99} header_Layout;
100
101typedef struct {
102 uint8_t signature [4];
103 uint8_t offset [4];
104 uint8_t size [4];
105} tag_Layout;
106
107static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
108 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
109}
110
111// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
112// the type are for tags/data that store exactly one triple.
113typedef struct {
114 uint8_t type [4];
115 uint8_t reserved [4];
116 uint8_t X [4];
117 uint8_t Y [4];
118 uint8_t Z [4];
119} XYZ_Layout;
120
121static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000122 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
Mike Kleinded7a552018-04-10 10:05:31 -0400123 return false;
124 }
125
126 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
127
128 *x = read_big_fixed(xyzTag->X);
129 *y = read_big_fixed(xyzTag->Y);
130 *z = read_big_fixed(xyzTag->Z);
131 return true;
132}
133
134static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
135 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
136 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
137 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
138 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
139}
140
141typedef struct {
142 uint8_t type [4];
143 uint8_t reserved_a [4];
144 uint8_t function_type [2];
145 uint8_t reserved_b [2];
146 uint8_t parameters [ ]; // 1, 3, 4, 5, or 7 s15.16 parameters, depending on function_type
147} para_Layout;
148
149static bool read_curve_para(const uint8_t* buf, uint32_t size,
150 skcms_Curve* curve, uint32_t* curve_size) {
151 if (size < SAFE_SIZEOF(para_Layout)) {
152 return false;
153 }
154
155 const para_Layout* paraTag = (const para_Layout*)buf;
156
157 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
158 uint16_t function_type = read_big_u16(paraTag->function_type);
159 if (function_type > kGABCDEF) {
160 return false;
161 }
162
163 static const uint32_t curve_bytes[] = { 4, 12, 16, 20, 28 };
164 if (size < SAFE_SIZEOF(para_Layout) + curve_bytes[function_type]) {
165 return false;
166 }
167
168 if (curve_size) {
169 *curve_size = SAFE_SIZEOF(para_Layout) + curve_bytes[function_type];
170 }
171
172 curve->table_entries = 0;
173 curve->parametric.a = 1.0f;
174 curve->parametric.b = 0.0f;
175 curve->parametric.c = 0.0f;
176 curve->parametric.d = 0.0f;
177 curve->parametric.e = 0.0f;
178 curve->parametric.f = 0.0f;
179 curve->parametric.g = read_big_fixed(paraTag->parameters);
180
181 switch (function_type) {
182 case kGAB:
183 curve->parametric.a = read_big_fixed(paraTag->parameters + 4);
184 curve->parametric.b = read_big_fixed(paraTag->parameters + 8);
185 if (curve->parametric.a == 0) {
186 return false;
187 }
188 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
189 break;
190 case kGABC:
191 curve->parametric.a = read_big_fixed(paraTag->parameters + 4);
192 curve->parametric.b = read_big_fixed(paraTag->parameters + 8);
193 curve->parametric.e = read_big_fixed(paraTag->parameters + 12);
194 if (curve->parametric.a == 0) {
195 return false;
196 }
197 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
198 curve->parametric.f = curve->parametric.e;
199 break;
200 case kGABCD:
201 curve->parametric.a = read_big_fixed(paraTag->parameters + 4);
202 curve->parametric.b = read_big_fixed(paraTag->parameters + 8);
203 curve->parametric.c = read_big_fixed(paraTag->parameters + 12);
204 curve->parametric.d = read_big_fixed(paraTag->parameters + 16);
205 break;
206 case kGABCDEF:
207 curve->parametric.a = read_big_fixed(paraTag->parameters + 4);
208 curve->parametric.b = read_big_fixed(paraTag->parameters + 8);
209 curve->parametric.c = read_big_fixed(paraTag->parameters + 12);
210 curve->parametric.d = read_big_fixed(paraTag->parameters + 16);
211 curve->parametric.e = read_big_fixed(paraTag->parameters + 20);
212 curve->parametric.f = read_big_fixed(paraTag->parameters + 24);
213 break;
214 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com7c09a312018-04-25 14:16:32 +0000215 return skcms_TransferFunction_isValid(&curve->parametric);
Mike Kleinded7a552018-04-10 10:05:31 -0400216}
217
218typedef struct {
219 uint8_t type [4];
220 uint8_t reserved [4];
221 uint8_t value_count [4];
222 uint8_t parameters [ ]; // value_count parameters (8.8 if 1, uint16 (n*65535) if > 1)
223} curv_Layout;
224
225static bool read_curve_curv(const uint8_t* buf, uint32_t size,
226 skcms_Curve* curve, uint32_t* curve_size) {
227 if (size < SAFE_SIZEOF(curv_Layout)) {
228 return false;
229 }
230
231 const curv_Layout* curvTag = (const curv_Layout*)buf;
232
233 uint32_t value_count = read_big_u32(curvTag->value_count);
234 if (size < SAFE_SIZEOF(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t)) {
235 return false;
236 }
237
238 if (curve_size) {
239 *curve_size = SAFE_SIZEOF(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t);
240 }
241
242 if (value_count < 2) {
243 curve->table_entries = 0;
244 curve->parametric.a = 1.0f;
245 curve->parametric.b = 0.0f;
246 curve->parametric.c = 0.0f;
247 curve->parametric.d = 0.0f;
248 curve->parametric.e = 0.0f;
249 curve->parametric.f = 0.0f;
250 if (value_count == 0) {
Mike Klein73297192018-04-11 10:26:09 -0400251 // Empty tables are a shorthand for an identity curve
Mike Kleinded7a552018-04-10 10:05:31 -0400252 curve->parametric.g = 1.0f;
253 } else {
254 // Single entry tables are a shorthand for simple gamma
255 curve->parametric.g = read_big_u16(curvTag->parameters) * (1.0f / 256.0f);
256 }
257 } else {
258 curve->table_8 = NULL;
259 curve->table_16 = curvTag->parameters;
260 curve->table_entries = value_count;
261 }
262
263 return true;
264}
265
266// Parses both curveType and parametricCurveType data. Ensures that at most 'size' bytes are read.
267// If curve_size is not NULL, writes the number of bytes used by the curve in (*curve_size).
268static bool read_curve(const uint8_t* buf, uint32_t size,
269 skcms_Curve* curve, uint32_t* curve_size) {
270 if (!buf || size < 4 || !curve) {
271 return false;
272 }
273
274 uint32_t type = read_big_u32(buf);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000275 if (type == skcms_Signature_para) {
Mike Kleinded7a552018-04-10 10:05:31 -0400276 return read_curve_para(buf, size, curve, curve_size);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000277 } else if (type == skcms_Signature_curv) {
Mike Kleinded7a552018-04-10 10:05:31 -0400278 return read_curve_curv(buf, size, curve, curve_size);
279 }
280
281 return false;
282}
283
Mike Kleinded7a552018-04-10 10:05:31 -0400284// mft1 and mft2 share a large chunk of data
285typedef struct {
286 uint8_t type [ 4];
287 uint8_t reserved_a [ 4];
288 uint8_t input_channels [ 1];
289 uint8_t output_channels [ 1];
290 uint8_t grid_points [ 1];
291 uint8_t reserved_b [ 1];
292 uint8_t matrix [36];
293} mft_CommonLayout;
294
295typedef struct {
296 mft_CommonLayout common [ 1];
297
298 uint8_t tables [ ];
299} mft1_Layout;
300
301typedef struct {
302 mft_CommonLayout common [ 1];
303
304 uint8_t input_table_entries [ 2];
305 uint8_t output_table_entries [ 2];
306 uint8_t tables [ ];
307} mft2_Layout;
308
309static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
310 // MFT matrices are applied before the first set of curves, but must be identity unless the
311 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
312 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
313 // field/flag.
314 a2b->matrix_channels = 0;
315
316 a2b->input_channels = mftTag->input_channels[0];
317 a2b->output_channels = mftTag->output_channels[0];
318
319 // We require exactly three (ie XYZ/Lab/RGB) output channels
320 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
321 return false;
322 }
323 // We require at least one, and no more than four (ie CMYK) input channels
324 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
325 return false;
326 }
327
328 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
329 a2b->grid_points[i] = mftTag->grid_points[0];
330 }
331 // The grid only makes sense with at least two points along each axis
332 if (a2b->grid_points[0] < 2) {
333 return false;
334 }
335
336 return true;
337}
338
339static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
340 uint32_t input_table_entries, uint32_t output_table_entries,
341 skcms_A2B* a2b) {
342 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
343 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
344 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
345
346 // [input|output]_channels are <= 4, so still no overflow
347 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
348 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
349
350 uint64_t grid_size = a2b->output_channels * byte_width;
351 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
352 grid_size *= a2b->grid_points[axis];
353 }
354
355 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
356 return false;
357 }
358
359 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
360 a2b->input_curves[i].table_entries = input_table_entries;
361 if (byte_width == 1) {
362 a2b->input_curves[i].table_8 = table_base + i * byte_len_per_input_table;
363 a2b->input_curves[i].table_16 = NULL;
364 } else {
365 a2b->input_curves[i].table_8 = NULL;
366 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
367 }
368 }
369
370 if (byte_width == 1) {
371 a2b->grid_8 = table_base + byte_len_all_input_tables;
372 a2b->grid_16 = NULL;
373 } else {
374 a2b->grid_8 = NULL;
375 a2b->grid_16 = table_base + byte_len_all_input_tables;
376 }
377
378 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
379 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
380 a2b->output_curves[i].table_entries = output_table_entries;
381 if (byte_width == 1) {
382 a2b->output_curves[i].table_8 = output_table_base + i * byte_len_per_output_table;
383 a2b->output_curves[i].table_16 = NULL;
384 } else {
385 a2b->output_curves[i].table_8 = NULL;
386 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
387 }
388 }
389
390 return true;
391}
392
393static bool read_tag_mft1(const skcms_ICCTag* tag, skcms_A2B* a2b) {
394 if (tag->size < SAFE_SIZEOF(mft1_Layout)) {
395 return false;
396 }
397
398 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
399 if (!read_mft_common(mftTag->common, a2b)) {
400 return false;
401 }
402
403 uint32_t input_table_entries = 256;
404 uint32_t output_table_entries = 256;
405
406 return init_a2b_tables(mftTag->tables, tag->size - SAFE_SIZEOF(mft1_Layout), 1,
407 input_table_entries, output_table_entries, a2b);
408}
409
410static bool read_tag_mft2(const skcms_ICCTag* tag, skcms_A2B* a2b) {
411 if (tag->size < SAFE_SIZEOF(mft2_Layout)) {
412 return false;
413 }
414
415 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
416 if (!read_mft_common(mftTag->common, a2b)) {
417 return false;
418 }
419
420 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
421 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
422
423 // ICC spec mandates that 2 <= table_entries <= 4096
424 if (input_table_entries < 2 || input_table_entries > 4096 ||
425 output_table_entries < 2 || output_table_entries > 4096) {
426 return false;
427 }
428
429 return init_a2b_tables(mftTag->tables, tag->size - SAFE_SIZEOF(mft2_Layout), 2,
430 input_table_entries, output_table_entries, a2b);
431}
432
433static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
434 uint32_t num_curves, skcms_Curve* curves) {
435 for (uint32_t i = 0; i < num_curves; ++i) {
436 if (curve_offset > size) {
437 return false;
438 }
439
440 uint32_t curve_bytes;
441 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
442 return false;
443 }
444
445 if (curve_bytes > UINT32_MAX - 3) {
446 return false;
447 }
448 curve_bytes = (curve_bytes + 3) & ~3U;
449
450 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
451 curve_offset = (uint32_t)new_offset_64;
452 if (new_offset_64 != curve_offset) {
453 return false;
454 }
455 }
456
457 return true;
458}
459
460typedef struct {
461 uint8_t type [ 4];
462 uint8_t reserved_a [ 4];
463 uint8_t input_channels [ 1];
464 uint8_t output_channels [ 1];
465 uint8_t reserved_b [ 2];
466 uint8_t b_curve_offset [ 4];
467 uint8_t matrix_offset [ 4];
468 uint8_t m_curve_offset [ 4];
469 uint8_t clut_offset [ 4];
470 uint8_t a_curve_offset [ 4];
471} mAB_Layout;
472
473typedef struct {
474 uint8_t grid_points [16];
475 uint8_t grid_byte_width [ 1];
476 uint8_t reserved [ 3];
477 uint8_t data [ ];
478} mABCLUT_Layout;
479
480static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
481 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
482 return false;
483 }
484
485 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
486
487 a2b->input_channels = mABTag->input_channels[0];
488 a2b->output_channels = mABTag->output_channels[0];
489
490 // We require exactly three (ie XYZ/Lab/RGB) output channels
491 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
492 return false;
493 }
494 // We require no more than four (ie CMYK) input channels
495 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
496 return false;
497 }
498
499 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
500 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
501 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
502 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
503 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
504
505 // "B" curves must be present
506 if (0 == b_curve_offset) {
507 return false;
508 }
509
510 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
511 a2b->output_curves)) {
512 return false;
513 }
514
515 // "M" curves and Matrix must be used together
516 if (0 != m_curve_offset) {
517 if (0 == matrix_offset) {
518 return false;
519 }
520 a2b->matrix_channels = a2b->output_channels;
521 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
522 a2b->matrix_curves)) {
523 return false;
524 }
525
526 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
527 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
528 return false;
529 }
530 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
531 const uint8_t* mtx_buf = tag->buf + matrix_offset;
532 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
533 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
534 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
535 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
536 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
537 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
538 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
539 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
540 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
541 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
542 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
543 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
544 } else {
545 if (0 != matrix_offset) {
546 return false;
547 }
548 a2b->matrix_channels = 0;
549 }
550
551 // "A" curves and CLUT must be used together
552 if (0 != a_curve_offset) {
553 if (0 == clut_offset) {
554 return false;
555 }
556 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
557 a2b->input_curves)) {
558 return false;
559 }
560
561 if (tag->size < clut_offset + SAFE_SIZEOF(mABCLUT_Layout)) {
562 return false;
563 }
564 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
565
566 if (clut->grid_byte_width[0] == 1) {
567 a2b->grid_8 = clut->data;
568 a2b->grid_16 = NULL;
569 } else if (clut->grid_byte_width[0] == 2) {
570 a2b->grid_8 = NULL;
571 a2b->grid_16 = clut->data;
572 } else {
573 return false;
574 }
575
576 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
577 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
578 a2b->grid_points[i] = clut->grid_points[i];
579 // The grid only makes sense with at least two points along each axis
580 if (a2b->grid_points[i] < 2) {
581 return false;
582 }
583 grid_size *= a2b->grid_points[i];
584 }
585 if (tag->size < clut_offset + SAFE_SIZEOF(mABCLUT_Layout) + grid_size) {
586 return false;
587 }
588 } else {
589 if (0 != clut_offset) {
590 return false;
591 }
592
593 // If there is no CLUT, the number of input and output channels must match
594 if (a2b->input_channels != a2b->output_channels) {
595 return false;
596 }
597
598 // Zero out the number of input channels to signal that we're skipping this stage
599 a2b->input_channels = 0;
600 }
601
602 return true;
603}
604
605static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
Mike Klein73297192018-04-11 10:26:09 -0400606 bool ok = false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000607 if (tag->type == skcms_Signature_mft1) {
Mike Klein73297192018-04-11 10:26:09 -0400608 ok = read_tag_mft1(tag, a2b);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000609 } else if (tag->type == skcms_Signature_mft2) {
Mike Klein73297192018-04-11 10:26:09 -0400610 ok = read_tag_mft2(tag, a2b);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000611 } else if (tag->type == skcms_Signature_mAB) {
Mike Klein73297192018-04-11 10:26:09 -0400612 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
613 }
614 if (!ok) {
615 return false;
Mike Kleinded7a552018-04-10 10:05:31 -0400616 }
617
Mike Klein73297192018-04-11 10:26:09 -0400618 // Detect and canonicalize identity tables.
619 skcms_Curve* curves[] = {
620 a2b->input_channels > 0 ? a2b->input_curves + 0 : NULL,
621 a2b->input_channels > 1 ? a2b->input_curves + 1 : NULL,
622 a2b->input_channels > 2 ? a2b->input_curves + 2 : NULL,
623 a2b->input_channels > 3 ? a2b->input_curves + 3 : NULL,
624 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : NULL,
625 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : NULL,
626 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : NULL,
627 a2b->output_channels > 0 ? a2b->output_curves + 0 : NULL,
628 a2b->output_channels > 1 ? a2b->output_curves + 1 : NULL,
629 a2b->output_channels > 2 ? a2b->output_curves + 2 : NULL,
630 };
631
632 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
633 skcms_Curve* curve = curves[i];
634
635 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
636 int N = (int)curve->table_entries;
637
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma11f7d02018-04-24 19:40:32 +0000638 float c,d,f;
639 if (N == skcms_fit_linear(curve, N, 1.0f/(2*N), &c,&d,&f)
640 && c == 1.0f
641 && f == 0.0f) {
Mike Klein73297192018-04-11 10:26:09 -0400642 curve->table_entries = 0;
643 curve->table_8 = NULL;
644 curve->table_16 = NULL;
645 curve->parametric = (skcms_TransferFunction){1,1,0,0,0,0,0};
646 }
647 }
648 }
649
650 return true;
Mike Kleinded7a552018-04-10 10:05:31 -0400651}
652
653void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
654 if (!profile || !profile->buffer || !tag) { return; }
655 if (idx > profile->tag_count) { return; }
656 const tag_Layout* tags = get_tag_table(profile);
657 tag->signature = read_big_u32(tags[idx].signature);
658 tag->size = read_big_u32(tags[idx].size);
659 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
660 tag->type = read_big_u32(tag->buf);
661}
662
663bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
664 if (!profile || !profile->buffer || !tag) { return false; }
665 const tag_Layout* tags = get_tag_table(profile);
666 for (uint32_t i = 0; i < profile->tag_count; ++i) {
667 if (read_big_u32(tags[i].signature) == sig) {
668 tag->signature = sig;
669 tag->size = read_big_u32(tags[i].size);
670 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
671 tag->type = read_big_u32(tag->buf);
672 return true;
673 }
674 }
675 return false;
676}
677
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com72308612018-04-23 14:41:01 +0000678static bool usable_as_src(const skcms_ICCProfile* profile) {
679 return profile->has_A2B
680 || (profile->has_trc && profile->has_toXYZD50);
681}
682
Mike Kleinded7a552018-04-10 10:05:31 -0400683bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
684 assert(SAFE_SIZEOF(header_Layout) == 132);
685
686 if (!profile) {
687 return false;
688 }
689 memset(profile, 0, SAFE_SIZEOF(*profile));
690
691 if (len < SAFE_SIZEOF(header_Layout)) {
692 return false;
693 }
694
695 // Byte-swap all header fields
696 const header_Layout* header = buf;
697 profile->buffer = buf;
698 profile->size = read_big_u32(header->size);
699 uint32_t version = read_big_u32(header->version);
700 profile->data_color_space = read_big_u32(header->data_color_space);
701 profile->pcs = read_big_u32(header->pcs);
702 uint32_t signature = read_big_u32(header->signature);
703 float illuminant_X = read_big_fixed(header->illuminant_X);
704 float illuminant_Y = read_big_fixed(header->illuminant_Y);
705 float illuminant_Z = read_big_fixed(header->illuminant_Z);
706 profile->tag_count = read_big_u32(header->tag_count);
707
708 // Validate signature, size (smaller than buffer, large enough to hold tag table),
709 // and major version
710 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000711 if (signature != skcms_Signature_acsp ||
Mike Kleinded7a552018-04-10 10:05:31 -0400712 profile->size > len ||
713 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
714 (version >> 24) > 4) {
715 return false;
716 }
717
718 // Validate that illuminant is D50 white
719 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
720 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
721 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
722 return false;
723 }
724
725 // Validate that all tag entries have sane offset + size
726 const tag_Layout* tags = get_tag_table(profile);
727 for (uint32_t i = 0; i < profile->tag_count; ++i) {
728 uint32_t tag_offset = read_big_u32(tags[i].offset);
729 uint32_t tag_size = read_big_u32(tags[i].size);
730 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
731 if (tag_size < 4 || tag_end > profile->size) {
732 return false;
733 }
734 }
735
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000736 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
Mike Kleinac78c7f2018-04-11 13:53:52 -0400737 return false;
738 }
739
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000740 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
Mike Kleinded7a552018-04-10 10:05:31 -0400741
742 // Pre-parse commonly used tags.
743 skcms_ICCTag kTRC;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000744 if (profile->data_color_space == skcms_Signature_Gray &&
745 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
Mike Kleinded7a552018-04-10 10:05:31 -0400746 if (!read_curve(kTRC.buf, kTRC.size, &profile->trc[0], NULL)) {
747 // Malformed tag
748 return false;
749 }
750 profile->trc[1] = profile->trc[0];
751 profile->trc[2] = profile->trc[0];
752 profile->has_trc = true;
753
754 if (pcs_is_xyz) {
755 profile->toXYZD50.vals[0][0] = illuminant_X;
756 profile->toXYZD50.vals[1][1] = illuminant_Y;
757 profile->toXYZD50.vals[2][2] = illuminant_Z;
758 profile->has_toXYZD50 = true;
759 }
760 } else {
761 skcms_ICCTag rTRC, gTRC, bTRC;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000762 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
763 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
764 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
Mike Kleinded7a552018-04-10 10:05:31 -0400765 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], NULL) ||
766 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], NULL) ||
767 !read_curve(bTRC.buf, bTRC.size, &profile->trc[2], NULL)) {
768 // Malformed TRC tags
769 return false;
770 }
771 profile->has_trc = true;
772 }
773
774 skcms_ICCTag rXYZ, gXYZ, bXYZ;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000775 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
776 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
777 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
Mike Kleinded7a552018-04-10 10:05:31 -0400778 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
779 // Malformed XYZ tags
780 return false;
781 }
782 profile->has_toXYZD50 = true;
783 }
784 }
785
786 skcms_ICCTag a2b_tag;
787
788 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
789 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
790 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
791 // and all our known users are thinking exclusively in terms of relative colormetric.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000792 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
Mike Kleinded7a552018-04-10 10:05:31 -0400793 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
794 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
795 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
796 // Malformed A2B tag
797 return false;
798 }
799 profile->has_A2B = true;
800 break;
801 }
802 }
803
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com72308612018-04-23 14:41:01 +0000804 return usable_as_src(profile);
Mike Kleinded7a552018-04-10 10:05:31 -0400805}
Mike Klein3462eb02018-04-11 13:53:40 -0400806
Mike Klein3462eb02018-04-11 13:53:40 -0400807
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400808const skcms_ICCProfile* skcms_sRGB_profile() {
809 static const skcms_ICCProfile sRGB_profile = {
810 // These fields are moot when not a skcms_Parse()'d profile.
811 .buffer = NULL,
812 .size = 0,
813 .tag_count = 0,
Mike Klein3462eb02018-04-11 13:53:40 -0400814
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400815 // We choose to represent sRGB with its canonical transfer function,
816 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000817 .data_color_space = skcms_Signature_RGB,
818 .pcs = skcms_Signature_XYZ,
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400819 .has_trc = true,
820 .has_toXYZD50 = true,
821 .has_A2B = false,
Mike Klein3462eb02018-04-11 13:53:40 -0400822
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400823 .trc = {
824 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0 }}},
825 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0 }}},
826 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0 }}},
827 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc4afb062018-04-26 20:01:32 +0000828
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400829 .toXYZD50 = {{
830 { 0.436065674f, 0.385147095f, 0.143066406f },
831 { 0.222488403f, 0.716873169f, 0.060607910f },
832 { 0.013916016f, 0.097076416f, 0.714096069f },
833 }},
Mike Klein3462eb02018-04-11 13:53:40 -0400834
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400835 .has_poly_tf = { true, true, true },
836 .poly_tf = {
837 {0.293833881617f, 0.704207003117f, (float)(1/12.92), 0.04045f},
838 {0.293833881617f, 0.704207003117f, (float)(1/12.92), 0.04045f},
839 {0.293833881617f, 0.704207003117f, (float)(1/12.92), 0.04045f},
840 },
841 };
842 return &sRGB_profile;
843}
Mike Klein3101f652018-04-17 11:20:08 -0400844
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400845const skcms_ICCProfile* skcms_XYZD50_profile() {
846 static const skcms_ICCProfile XYZD50_profile = {
847 .buffer = NULL,
848 .size = 0,
849 .tag_count = 0,
Mike Klein3101f652018-04-17 11:20:08 -0400850
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000851 .data_color_space = skcms_Signature_RGB,
852 .pcs = skcms_Signature_XYZ,
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400853 .has_trc = true,
854 .has_toXYZD50 = true,
855 .has_A2B = false,
Mike Klein3101f652018-04-17 11:20:08 -0400856
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400857 .trc = {
858 {{0, {1,1,0,0,0,0,0}}},
859 {{0, {1,1,0,0,0,0,0}}},
860 {{0, {1,1,0,0,0,0,0}}},
861 },
862
863 .toXYZD50 = {{
864 {1,0,0},
865 {0,1,0},
866 {0,0,1},
867 }},
868 };
869
870 return &XYZD50_profile;
871}
Mike Klein3101f652018-04-17 11:20:08 -0400872
873const uint8_t skcms_252_random_bytes[] = {
874 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
875 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
876 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
877 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
878 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
879 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
880 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
881 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
882 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
883 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
884 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
885 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
886 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
887 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
888 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
889 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
890};
891
Mike Klein3462eb02018-04-11 13:53:40 -0400892bool skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile* A, const skcms_ICCProfile* B) {
893 // For now this is the essentially the same strategy we use in test_only.c
894 // for our skcms_Transform() smoke tests:
895 // 1) transform A to XYZD50
896 // 2) transform B to XYZD50
897 // 3) return true if they're similar enough
898 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
899
900 // Here are 252 of a random shuffle of all possible bytes.
901 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
Mike Klein3462eb02018-04-11 13:53:40 -0400902
903 if (A->data_color_space != B->data_color_space) {
904 return false;
905 }
906
907 // Interpret as RGB_888 if data color space is RGB or GRAY, RGBA_8888 if CMYK.
908 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
909 size_t npixels = 84;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.coma724ea52018-05-07 16:57:26 +0000910 if (A->data_color_space == skcms_Signature_CMYK) {
Mike Klein3462eb02018-04-11 13:53:40 -0400911 fmt = skcms_PixelFormat_RGBA_8888;
912 npixels = 63;
913 }
914
915 uint8_t dstA[252],
916 dstB[252];
Mike Klein3101f652018-04-17 11:20:08 -0400917 if (!skcms_Transform(
918 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400919 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
Mike Klein3101f652018-04-17 11:20:08 -0400920 npixels)) {
Mike Klein3462eb02018-04-11 13:53:40 -0400921 return false;
922 }
Mike Klein3101f652018-04-17 11:20:08 -0400923 if (!skcms_Transform(
924 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
Brian Osman0ab0f1c2018-05-01 13:09:53 -0400925 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
Mike Klein3101f652018-04-17 11:20:08 -0400926 npixels)) {
Mike Klein3462eb02018-04-11 13:53:40 -0400927 return false;
928 }
929
930 for (size_t i = 0; i < 252; i++) {
931 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
932 return false;
933 }
934 }
935 return true;
936}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comb5d1f242018-05-09 18:44:08 +0000937
938static bool is_zero_to_one(float x) {
939 return 0 <= x && x <= 1;
940}
941
942bool skcms_PrimariesToXYZD50(float rx, float ry,
943 float gx, float gy,
944 float bx, float by,
945 float wx, float wy,
946 skcms_Matrix3x3* toXYZD50) {
947 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
948 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
949 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
950 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
951 !toXYZD50) {
952 return false;
953 }
954
955 // First, we need to convert xy values (primaries) to XYZ.
956 skcms_Matrix3x3 primaries = {{
957 { rx, gx, bx },
958 { ry, gy, by },
959 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
960 }};
961 skcms_Matrix3x3 primaries_inv;
962 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
963 return false;
964 }
965
966 // Assumes that Y is 1.0f.
967 skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
968 skcms_Vector3 XYZ = skcms_MV_mul(&primaries_inv, &wXYZ);
969
970 skcms_Matrix3x3 toXYZ = {{
971 { XYZ.vals[0], 0, 0 },
972 { 0, XYZ.vals[1], 0 },
973 { 0, 0, XYZ.vals[2] },
974 }};
975 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
976
977 // Now convert toXYZ matrix to toXYZD50.
978 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
979
980 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
981 // the matrices below. The Bradford method is used by Adobe and is widely considered
982 // to be the best.
983 skcms_Matrix3x3 xyz_to_lms = {{
984 { 0.8951f, 0.2664f, -0.1614f },
985 { -0.7502f, 1.7135f, 0.0367f },
986 { 0.0389f, -0.0685f, 1.0296f },
987 }};
988 skcms_Matrix3x3 lms_to_xyz = {{
989 { 0.9869929f, -0.1470543f, 0.1599627f },
990 { 0.4323053f, 0.5183603f, 0.0492912f },
991 { -0.0085287f, 0.0400428f, 0.9684867f },
992 }};
993
994 skcms_Vector3 srcCone = skcms_MV_mul(&xyz_to_lms, &wXYZ);
995 skcms_Vector3 dstCone = skcms_MV_mul(&xyz_to_lms, &wXYZD50);
996
997 skcms_Matrix3x3 DXtoD50 = {{
998 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
999 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1000 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1001 }};
1002 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1003 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1004
1005 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1006 return true;
1007}