blob: 41a67ff0a21b61226f05771c0d7b66ef09d608bf [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
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00008#include "skcms.h"
9#include "skcms_internal.h"
10#include <assert.h>
11#include <float.h>
12#include <limits.h>
13#include <stdlib.h>
14#include <string.h>
Mike Kleinded7a552018-04-10 10:05:31 -040015
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000016// sizeof(x) will return size_t, which is 32-bit on some machines and 64-bit on others.
17// We have better testing on 64-bit machines, so force 32-bit machines to behave like 64-bit.
18//
19// Please do not use sizeof() directly, and size_t only when required.
20// (We have no way of enforcing these requests...)
21#define SAFE_SIZEOF(x) ((uint64_t)sizeof(x))
22
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +000023// Same sort of thing for _Layout structs with a variable sized array at the end (named "variable").
24#define SAFE_FIXED_SIZE(type) ((uint64_t)offsetof(type, variable))
25
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000026static const union {
27 uint32_t bits;
28 float f;
29} inf_ = { 0x7f800000 };
30#define INFINITY_ inf_.f
31
32static float fmaxf_(float x, float y) { return x > y ? x : y; }
33static float fminf_(float x, float y) { return x < y ? x : y; }
34
35static bool isfinitef_(float x) { return 0 == x*0; }
36
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000037static float minus_1_ulp(float x) {
38 int32_t bits;
39 memcpy(&bits, &x, sizeof(bits));
40 bits = bits - 1;
41 memcpy(&x, &bits, sizeof(bits));
42 return x;
43}
44
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000045static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000046 if (curve->table_entries == 0) {
47 return skcms_TransferFunction_eval(&curve->parametric, x);
48 }
49
50 float ix = fmaxf_(0, fminf_(x, 1)) * (curve->table_entries - 1);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com8cefce12018-07-03 18:14:31 +000051 int lo = (int) ix ,
52 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000053 float t = ix - (float)lo;
54
55 float l, h;
56 if (curve->table_8) {
57 l = curve->table_8[lo] * (1/255.0f);
58 h = curve->table_8[hi] * (1/255.0f);
59 } else {
60 uint16_t be_l, be_h;
61 memcpy(&be_l, curve->table_16 + 2*lo, 2);
62 memcpy(&be_h, curve->table_16 + 2*hi, 2);
63 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
64 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
65 l = le_l * (1/65535.0f);
66 h = le_h * (1/65535.0f);
67 }
68 return l + (h-l)*t;
69}
70
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000071static float max_roundtrip_error(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000072 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
73 const float dx = 1.0f / (N - 1);
74 float err = 0;
75 for (uint32_t i = 0; i < N; i++) {
76 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000077 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000078 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
79 }
80 return err;
81}
82
83bool skcms_AreApproximateInverses(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000084 return max_roundtrip_error(curve, inv_tf) < (1/512.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000085}
86
87// Additional ICC signature values that are only used internally
88enum {
89 // File signature
90 skcms_Signature_acsp = 0x61637370,
91
92 // Tag signatures
93 skcms_Signature_rTRC = 0x72545243,
94 skcms_Signature_gTRC = 0x67545243,
95 skcms_Signature_bTRC = 0x62545243,
96 skcms_Signature_kTRC = 0x6B545243,
97
98 skcms_Signature_rXYZ = 0x7258595A,
99 skcms_Signature_gXYZ = 0x6758595A,
100 skcms_Signature_bXYZ = 0x6258595A,
101
102 skcms_Signature_A2B0 = 0x41324230,
103 skcms_Signature_A2B1 = 0x41324231,
104 skcms_Signature_mAB = 0x6D414220,
105
106 skcms_Signature_CHAD = 0x63686164,
107
108 // Type signatures
109 skcms_Signature_curv = 0x63757276,
110 skcms_Signature_mft1 = 0x6D667431,
111 skcms_Signature_mft2 = 0x6D667432,
112 skcms_Signature_para = 0x70617261,
113 skcms_Signature_sf32 = 0x73663332,
114 // XYZ is also a PCS signature, so it's defined in skcms.h
115 // skcms_Signature_XYZ = 0x58595A20,
116};
117
118static uint16_t read_big_u16(const uint8_t* ptr) {
119 uint16_t be;
120 memcpy(&be, ptr, sizeof(be));
121#if defined(_MSC_VER)
122 return _byteswap_ushort(be);
123#else
124 return __builtin_bswap16(be);
125#endif
126}
127
128static uint32_t read_big_u32(const uint8_t* ptr) {
129 uint32_t be;
130 memcpy(&be, ptr, sizeof(be));
131#if defined(_MSC_VER)
132 return _byteswap_ulong(be);
133#else
134 return __builtin_bswap32(be);
135#endif
136}
137
138static int32_t read_big_i32(const uint8_t* ptr) {
139 return (int32_t)read_big_u32(ptr);
140}
141
142static float read_big_fixed(const uint8_t* ptr) {
143 return read_big_i32(ptr) * (1.0f / 65536.0f);
144}
145
146// Maps to an in-memory profile so that fields line up to the locations specified
147// in ICC.1:2010, section 7.2
148typedef struct {
149 uint8_t size [ 4];
150 uint8_t cmm_type [ 4];
151 uint8_t version [ 4];
152 uint8_t profile_class [ 4];
153 uint8_t data_color_space [ 4];
154 uint8_t pcs [ 4];
155 uint8_t creation_date_time [12];
156 uint8_t signature [ 4];
157 uint8_t platform [ 4];
158 uint8_t flags [ 4];
159 uint8_t device_manufacturer [ 4];
160 uint8_t device_model [ 4];
161 uint8_t device_attributes [ 8];
162 uint8_t rendering_intent [ 4];
163 uint8_t illuminant_X [ 4];
164 uint8_t illuminant_Y [ 4];
165 uint8_t illuminant_Z [ 4];
166 uint8_t creator [ 4];
167 uint8_t profile_id [16];
168 uint8_t reserved [28];
169 uint8_t tag_count [ 4]; // Technically not part of header, but required
170} header_Layout;
171
172typedef struct {
173 uint8_t signature [4];
174 uint8_t offset [4];
175 uint8_t size [4];
176} tag_Layout;
177
178static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
179 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
180}
181
182// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
183// use of the type is for the CHAD tag that stores exactly nine values.
184typedef struct {
185 uint8_t type [ 4];
186 uint8_t reserved [ 4];
187 uint8_t values [36];
188} sf32_Layout;
189
190bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
191 skcms_ICCTag tag;
192 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
193 return false;
194 }
195
196 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
197 return false;
198 }
199
200 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
201 const uint8_t* values = sf32Tag->values;
202 for (int r = 0; r < 3; ++r)
203 for (int c = 0; c < 3; ++c, values += 4) {
204 m->vals[r][c] = read_big_fixed(values);
205 }
206 return true;
207}
208
209// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
210// the type are for tags/data that store exactly one triple.
211typedef struct {
212 uint8_t type [4];
213 uint8_t reserved [4];
214 uint8_t X [4];
215 uint8_t Y [4];
216 uint8_t Z [4];
217} XYZ_Layout;
218
219static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
220 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
221 return false;
222 }
223
224 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
225
226 *x = read_big_fixed(xyzTag->X);
227 *y = read_big_fixed(xyzTag->Y);
228 *z = read_big_fixed(xyzTag->Z);
229 return true;
230}
231
232static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
233 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
234 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
235 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
236 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
237}
238
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000239static bool tf_is_valid(const skcms_TransferFunction* tf) {
240 // Reject obviously malformed inputs
241 if (!isfinitef_(tf->a + tf->b + tf->c + tf->d + tf->e + tf->f + tf->g)) {
242 return false;
243 }
244
245 // All of these parameters should be non-negative
246 if (tf->a < 0 || tf->c < 0 || tf->d < 0 || tf->g < 0) {
247 return false;
248 }
249
250 return true;
251}
252
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000253typedef struct {
254 uint8_t type [4];
255 uint8_t reserved_a [4];
256 uint8_t function_type [2];
257 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000258 uint8_t variable [1/*variable*/]; // 1, 3, 4, 5, or 7 s15.16, depending on function_type
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000259} para_Layout;
260
261static bool read_curve_para(const uint8_t* buf, uint32_t size,
262 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000263 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000264 return false;
265 }
266
267 const para_Layout* paraTag = (const para_Layout*)buf;
268
269 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
270 uint16_t function_type = read_big_u16(paraTag->function_type);
271 if (function_type > kGABCDEF) {
272 return false;
273 }
274
275 static const uint32_t curve_bytes[] = { 4, 12, 16, 20, 28 };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000276 if (size < SAFE_FIXED_SIZE(para_Layout) + curve_bytes[function_type]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000277 return false;
278 }
279
280 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000281 *curve_size = SAFE_FIXED_SIZE(para_Layout) + curve_bytes[function_type];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000282 }
283
284 curve->table_entries = 0;
285 curve->parametric.a = 1.0f;
286 curve->parametric.b = 0.0f;
287 curve->parametric.c = 0.0f;
288 curve->parametric.d = 0.0f;
289 curve->parametric.e = 0.0f;
290 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000291 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000292
293 switch (function_type) {
294 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000295 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
296 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000297 if (curve->parametric.a == 0) {
298 return false;
299 }
300 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
301 break;
302 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000303 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
304 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
305 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000306 if (curve->parametric.a == 0) {
307 return false;
308 }
309 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
310 curve->parametric.f = curve->parametric.e;
311 break;
312 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000313 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
314 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
315 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
316 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000317 break;
318 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000319 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
320 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
321 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
322 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
323 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
324 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000325 break;
326 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000327 return tf_is_valid(&curve->parametric);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000328}
329
330typedef struct {
331 uint8_t type [4];
332 uint8_t reserved [4];
333 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000334 uint8_t variable [1/*variable*/]; // value_count, 8.8 if 1, uint16 (n*65535) if > 1
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000335} curv_Layout;
336
337static bool read_curve_curv(const uint8_t* buf, uint32_t size,
338 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000339 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000340 return false;
341 }
342
343 const curv_Layout* curvTag = (const curv_Layout*)buf;
344
345 uint32_t value_count = read_big_u32(curvTag->value_count);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000346 if (size < SAFE_FIXED_SIZE(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000347 return false;
348 }
349
350 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000351 *curve_size = SAFE_FIXED_SIZE(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000352 }
353
354 if (value_count < 2) {
355 curve->table_entries = 0;
356 curve->parametric.a = 1.0f;
357 curve->parametric.b = 0.0f;
358 curve->parametric.c = 0.0f;
359 curve->parametric.d = 0.0f;
360 curve->parametric.e = 0.0f;
361 curve->parametric.f = 0.0f;
362 if (value_count == 0) {
363 // Empty tables are a shorthand for an identity curve
364 curve->parametric.g = 1.0f;
365 } else {
366 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000367 curve->parametric.g = read_big_u16(curvTag->variable) * (1.0f / 256.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000368 }
369 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000370 curve->table_8 = nullptr;
371 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000372 curve->table_entries = value_count;
373 }
374
375 return true;
376}
377
378// Parses both curveType and parametricCurveType data. Ensures that at most 'size' bytes are read.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000379// If curve_size is not nullptr, writes the number of bytes used by the curve in (*curve_size).
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000380static bool read_curve(const uint8_t* buf, uint32_t size,
381 skcms_Curve* curve, uint32_t* curve_size) {
382 if (!buf || size < 4 || !curve) {
383 return false;
384 }
385
386 uint32_t type = read_big_u32(buf);
387 if (type == skcms_Signature_para) {
388 return read_curve_para(buf, size, curve, curve_size);
389 } else if (type == skcms_Signature_curv) {
390 return read_curve_curv(buf, size, curve, curve_size);
391 }
392
393 return false;
394}
395
396// mft1 and mft2 share a large chunk of data
397typedef struct {
398 uint8_t type [ 4];
399 uint8_t reserved_a [ 4];
400 uint8_t input_channels [ 1];
401 uint8_t output_channels [ 1];
402 uint8_t grid_points [ 1];
403 uint8_t reserved_b [ 1];
404 uint8_t matrix [36];
405} mft_CommonLayout;
406
407typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000408 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000409
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000410 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000411} mft1_Layout;
412
413typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000414 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000415
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000416 uint8_t input_table_entries [2];
417 uint8_t output_table_entries [2];
418 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000419} mft2_Layout;
420
421static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
422 // MFT matrices are applied before the first set of curves, but must be identity unless the
423 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
424 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
425 // field/flag.
426 a2b->matrix_channels = 0;
427
428 a2b->input_channels = mftTag->input_channels[0];
429 a2b->output_channels = mftTag->output_channels[0];
430
431 // We require exactly three (ie XYZ/Lab/RGB) output channels
432 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
433 return false;
434 }
435 // We require at least one, and no more than four (ie CMYK) input channels
436 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
437 return false;
438 }
439
440 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
441 a2b->grid_points[i] = mftTag->grid_points[0];
442 }
443 // The grid only makes sense with at least two points along each axis
444 if (a2b->grid_points[0] < 2) {
445 return false;
446 }
447
448 return true;
449}
450
451static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
452 uint32_t input_table_entries, uint32_t output_table_entries,
453 skcms_A2B* a2b) {
454 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
455 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
456 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
457
458 // [input|output]_channels are <= 4, so still no overflow
459 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
460 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
461
462 uint64_t grid_size = a2b->output_channels * byte_width;
463 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
464 grid_size *= a2b->grid_points[axis];
465 }
466
467 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
468 return false;
469 }
470
471 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
472 a2b->input_curves[i].table_entries = input_table_entries;
473 if (byte_width == 1) {
474 a2b->input_curves[i].table_8 = table_base + i * byte_len_per_input_table;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000475 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000476 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000477 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000478 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
479 }
480 }
481
482 if (byte_width == 1) {
483 a2b->grid_8 = table_base + byte_len_all_input_tables;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000484 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000485 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000486 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000487 a2b->grid_16 = table_base + byte_len_all_input_tables;
488 }
489
490 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
491 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
492 a2b->output_curves[i].table_entries = output_table_entries;
493 if (byte_width == 1) {
494 a2b->output_curves[i].table_8 = output_table_base + i * byte_len_per_output_table;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000495 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000496 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000497 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000498 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
499 }
500 }
501
502 return true;
503}
504
505static bool read_tag_mft1(const skcms_ICCTag* tag, skcms_A2B* a2b) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000506 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000507 return false;
508 }
509
510 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
511 if (!read_mft_common(mftTag->common, a2b)) {
512 return false;
513 }
514
515 uint32_t input_table_entries = 256;
516 uint32_t output_table_entries = 256;
517
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000518 return init_a2b_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000519 input_table_entries, output_table_entries, a2b);
520}
521
522static bool read_tag_mft2(const skcms_ICCTag* tag, skcms_A2B* a2b) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000523 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000524 return false;
525 }
526
527 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
528 if (!read_mft_common(mftTag->common, a2b)) {
529 return false;
530 }
531
532 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
533 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
534
535 // ICC spec mandates that 2 <= table_entries <= 4096
536 if (input_table_entries < 2 || input_table_entries > 4096 ||
537 output_table_entries < 2 || output_table_entries > 4096) {
538 return false;
539 }
540
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000541 return init_a2b_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout), 2,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000542 input_table_entries, output_table_entries, a2b);
543}
544
545static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
546 uint32_t num_curves, skcms_Curve* curves) {
547 for (uint32_t i = 0; i < num_curves; ++i) {
548 if (curve_offset > size) {
549 return false;
550 }
551
552 uint32_t curve_bytes;
553 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
554 return false;
555 }
556
557 if (curve_bytes > UINT32_MAX - 3) {
558 return false;
559 }
560 curve_bytes = (curve_bytes + 3) & ~3U;
561
562 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
563 curve_offset = (uint32_t)new_offset_64;
564 if (new_offset_64 != curve_offset) {
565 return false;
566 }
567 }
568
569 return true;
570}
571
572typedef struct {
573 uint8_t type [ 4];
574 uint8_t reserved_a [ 4];
575 uint8_t input_channels [ 1];
576 uint8_t output_channels [ 1];
577 uint8_t reserved_b [ 2];
578 uint8_t b_curve_offset [ 4];
579 uint8_t matrix_offset [ 4];
580 uint8_t m_curve_offset [ 4];
581 uint8_t clut_offset [ 4];
582 uint8_t a_curve_offset [ 4];
583} mAB_Layout;
584
585typedef struct {
586 uint8_t grid_points [16];
587 uint8_t grid_byte_width [ 1];
588 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000589 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000590} mABCLUT_Layout;
591
592static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
593 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
594 return false;
595 }
596
597 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
598
599 a2b->input_channels = mABTag->input_channels[0];
600 a2b->output_channels = mABTag->output_channels[0];
601
602 // We require exactly three (ie XYZ/Lab/RGB) output channels
603 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
604 return false;
605 }
606 // We require no more than four (ie CMYK) input channels
607 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
608 return false;
609 }
610
611 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
612 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
613 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
614 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
615 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
616
617 // "B" curves must be present
618 if (0 == b_curve_offset) {
619 return false;
620 }
621
622 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
623 a2b->output_curves)) {
624 return false;
625 }
626
627 // "M" curves and Matrix must be used together
628 if (0 != m_curve_offset) {
629 if (0 == matrix_offset) {
630 return false;
631 }
632 a2b->matrix_channels = a2b->output_channels;
633 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
634 a2b->matrix_curves)) {
635 return false;
636 }
637
638 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
639 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
640 return false;
641 }
642 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
643 const uint8_t* mtx_buf = tag->buf + matrix_offset;
644 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
645 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
646 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
647 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
648 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
649 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
650 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
651 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
652 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
653 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
654 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
655 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
656 } else {
657 if (0 != matrix_offset) {
658 return false;
659 }
660 a2b->matrix_channels = 0;
661 }
662
663 // "A" curves and CLUT must be used together
664 if (0 != a_curve_offset) {
665 if (0 == clut_offset) {
666 return false;
667 }
668 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
669 a2b->input_curves)) {
670 return false;
671 }
672
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000673 if (tag->size < clut_offset + SAFE_FIXED_SIZE(mABCLUT_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000674 return false;
675 }
676 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
677
678 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000679 a2b->grid_8 = clut->variable;
680 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000681 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000682 a2b->grid_8 = nullptr;
683 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000684 } else {
685 return false;
686 }
687
688 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
689 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
690 a2b->grid_points[i] = clut->grid_points[i];
691 // The grid only makes sense with at least two points along each axis
692 if (a2b->grid_points[i] < 2) {
693 return false;
694 }
695 grid_size *= a2b->grid_points[i];
696 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000697 if (tag->size < clut_offset + SAFE_FIXED_SIZE(mABCLUT_Layout) + grid_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000698 return false;
699 }
700 } else {
701 if (0 != clut_offset) {
702 return false;
703 }
704
705 // If there is no CLUT, the number of input and output channels must match
706 if (a2b->input_channels != a2b->output_channels) {
707 return false;
708 }
709
710 // Zero out the number of input channels to signal that we're skipping this stage
711 a2b->input_channels = 0;
712 }
713
714 return true;
715}
716
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000717static int fit_linear(const skcms_Curve* curve, int N, float tol, float* c, float* d, float* f) {
718 assert(N > 1);
719 // We iteratively fit the first points to the TF's linear piece.
720 // We want the cx + f line to pass through the first and last points we fit exactly.
721 //
722 // As we walk along the points we find the minimum and maximum slope of the line before the
723 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
724 // emtpy, when we definitely can't add any more points.
725 //
726 // Some points' error intervals may intersect the running interval but not lie fully
727 // within it. So we keep track of the last point we saw that is a valid end point candidate,
728 // and once the search is done, back up to build the line through *that* point.
729 const float dx = 1.0f / (N - 1);
730
731 int lin_points = 1;
732 *f = eval_curve(curve, 0);
733
734 float slope_min = -INFINITY_;
735 float slope_max = +INFINITY_;
736 for (int i = 1; i < N; ++i) {
737 float x = i * dx;
738 float y = eval_curve(curve, x);
739
740 float slope_max_i = (y + tol - *f) / x,
741 slope_min_i = (y - tol - *f) / x;
742 if (slope_max_i < slope_min || slope_max < slope_min_i) {
743 // Slope intervals would no longer overlap.
744 break;
745 }
746 slope_max = fminf_(slope_max, slope_max_i);
747 slope_min = fmaxf_(slope_min, slope_min_i);
748
749 float cur_slope = (y - *f) / x;
750 if (slope_min <= cur_slope && cur_slope <= slope_max) {
751 lin_points = i + 1;
752 *c = cur_slope;
753 }
754 }
755
756 // Set D to the last point that met our tolerance.
757 *d = (lin_points - 1) * dx;
758 return lin_points;
759}
760
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000761static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
762 bool ok = false;
763 if (tag->type == skcms_Signature_mft1) {
764 ok = read_tag_mft1(tag, a2b);
765 } else if (tag->type == skcms_Signature_mft2) {
766 ok = read_tag_mft2(tag, a2b);
767 } else if (tag->type == skcms_Signature_mAB) {
768 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
769 }
770 if (!ok) {
771 return false;
772 }
773
774 // Detect and canonicalize identity tables.
775 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000776 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
777 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
778 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
779 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
780 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
781 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
782 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
783 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
784 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
785 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000786 };
787
788 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
789 skcms_Curve* curve = curves[i];
790
791 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
792 int N = (int)curve->table_entries;
793
794 float c,d,f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000795 if (N == fit_linear(curve, N, 1.0f/(2*N), &c,&d,&f)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000796 && c == 1.0f
797 && f == 0.0f) {
798 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000799 curve->table_8 = nullptr;
800 curve->table_16 = nullptr;
801 curve->parametric = skcms_TransferFunction{1,1,0,0,0,0,0};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000802 }
803 }
804 }
805
806 return true;
807}
808
809void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
810 if (!profile || !profile->buffer || !tag) { return; }
811 if (idx > profile->tag_count) { return; }
812 const tag_Layout* tags = get_tag_table(profile);
813 tag->signature = read_big_u32(tags[idx].signature);
814 tag->size = read_big_u32(tags[idx].size);
815 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
816 tag->type = read_big_u32(tag->buf);
817}
818
819bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
820 if (!profile || !profile->buffer || !tag) { return false; }
821 const tag_Layout* tags = get_tag_table(profile);
822 for (uint32_t i = 0; i < profile->tag_count; ++i) {
823 if (read_big_u32(tags[i].signature) == sig) {
824 tag->signature = sig;
825 tag->size = read_big_u32(tags[i].size);
826 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
827 tag->type = read_big_u32(tag->buf);
828 return true;
829 }
830 }
831 return false;
832}
833
834static bool usable_as_src(const skcms_ICCProfile* profile) {
835 return profile->has_A2B
836 || (profile->has_trc && profile->has_toXYZD50);
837}
838
839bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
840 assert(SAFE_SIZEOF(header_Layout) == 132);
841
842 if (!profile) {
843 return false;
844 }
845 memset(profile, 0, SAFE_SIZEOF(*profile));
846
847 if (len < SAFE_SIZEOF(header_Layout)) {
848 return false;
849 }
850
851 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000852 const header_Layout* header = (const header_Layout*)buf;
853 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000854 profile->size = read_big_u32(header->size);
855 uint32_t version = read_big_u32(header->version);
856 profile->data_color_space = read_big_u32(header->data_color_space);
857 profile->pcs = read_big_u32(header->pcs);
858 uint32_t signature = read_big_u32(header->signature);
859 float illuminant_X = read_big_fixed(header->illuminant_X);
860 float illuminant_Y = read_big_fixed(header->illuminant_Y);
861 float illuminant_Z = read_big_fixed(header->illuminant_Z);
862 profile->tag_count = read_big_u32(header->tag_count);
863
864 // Validate signature, size (smaller than buffer, large enough to hold tag table),
865 // and major version
866 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
867 if (signature != skcms_Signature_acsp ||
868 profile->size > len ||
869 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
870 (version >> 24) > 4) {
871 return false;
872 }
873
874 // Validate that illuminant is D50 white
875 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
876 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
877 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
878 return false;
879 }
880
881 // Validate that all tag entries have sane offset + size
882 const tag_Layout* tags = get_tag_table(profile);
883 for (uint32_t i = 0; i < profile->tag_count; ++i) {
884 uint32_t tag_offset = read_big_u32(tags[i].offset);
885 uint32_t tag_size = read_big_u32(tags[i].size);
886 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
887 if (tag_size < 4 || tag_end > profile->size) {
888 return false;
889 }
890 }
891
892 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
893 return false;
894 }
895
896 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
897
898 // Pre-parse commonly used tags.
899 skcms_ICCTag kTRC;
900 if (profile->data_color_space == skcms_Signature_Gray &&
901 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000902 if (!read_curve(kTRC.buf, kTRC.size, &profile->trc[0], nullptr)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000903 // Malformed tag
904 return false;
905 }
906 profile->trc[1] = profile->trc[0];
907 profile->trc[2] = profile->trc[0];
908 profile->has_trc = true;
909
910 if (pcs_is_xyz) {
911 profile->toXYZD50.vals[0][0] = illuminant_X;
912 profile->toXYZD50.vals[1][1] = illuminant_Y;
913 profile->toXYZD50.vals[2][2] = illuminant_Z;
914 profile->has_toXYZD50 = true;
915 }
916 } else {
917 skcms_ICCTag rTRC, gTRC, bTRC;
918 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
919 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
920 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000921 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
922 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
923 !read_curve(bTRC.buf, bTRC.size, &profile->trc[2], nullptr)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000924 // Malformed TRC tags
925 return false;
926 }
927 profile->has_trc = true;
928 }
929
930 skcms_ICCTag rXYZ, gXYZ, bXYZ;
931 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
932 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
933 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
934 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
935 // Malformed XYZ tags
936 return false;
937 }
938 profile->has_toXYZD50 = true;
939 }
940 }
941
942 skcms_ICCTag a2b_tag;
943
944 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
945 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
946 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
947 // and all our known users are thinking exclusively in terms of relative colormetric.
948 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
949 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
950 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
951 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
952 // Malformed A2B tag
953 return false;
954 }
955 profile->has_A2B = true;
956 break;
957 }
958 }
959
960 return usable_as_src(profile);
961}
962
963
964const skcms_ICCProfile* skcms_sRGB_profile() {
965 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000966 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000967
968 0, // size, moot here
969 skcms_Signature_RGB, // data_color_space
970 skcms_Signature_XYZ, // pcs
971 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000972
973 // We choose to represent sRGB with its canonical transfer function,
974 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000975 true, // has_trc, followed by the 3 trc curves
976 {
977 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
978 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
979 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000980 },
981
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000982 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
983 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000984 { 0.436065674f, 0.385147095f, 0.143066406f },
985 { 0.222488403f, 0.716873169f, 0.060607910f },
986 { 0.013916016f, 0.097076416f, 0.714096069f },
987 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000988
989 false, // has_A2B, followed by a2b itself which we don't care about.
990 {
991 0,
992 {
993 {{0, {1,1, 0,0,0,0,0}}},
994 {{0, {1,1, 0,0,0,0,0}}},
995 {{0, {1,1, 0,0,0,0,0}}},
996 {{0, {1,1, 0,0,0,0,0}}},
997 },
998 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000999 nullptr,
1000 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001001
1002 0,
1003 {
1004 {{0, {1,1, 0,0,0,0,0}}},
1005 {{0, {1,1, 0,0,0,0,0}}},
1006 {{0, {1,1, 0,0,0,0,0}}},
1007 },
1008 {{
1009 { 1,0,0,0 },
1010 { 0,1,0,0 },
1011 { 0,0,1,0 },
1012 }},
1013
1014 0,
1015 {
1016 {{0, {1,1, 0,0,0,0,0}}},
1017 {{0, {1,1, 0,0,0,0,0}}},
1018 {{0, {1,1, 0,0,0,0,0}}},
1019 },
1020 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001021 };
1022 return &sRGB_profile;
1023}
1024
1025const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001026 // Just like sRGB above, but with identity transfer functions and toXYZD50 matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001027 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001028 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001029
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001030 0, // size, moot here
1031 skcms_Signature_RGB, // data_color_space
1032 skcms_Signature_XYZ, // pcs
1033 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001034
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001035 true, // has_trc, followed by the 3 trc curves
1036 {
1037 {{0, {1,1, 0,0,0,0,0}}},
1038 {{0, {1,1, 0,0,0,0,0}}},
1039 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001040 },
1041
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001042 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1043 {{
1044 { 1,0,0 },
1045 { 0,1,0 },
1046 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001047 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001048
1049 false, // has_A2B, followed by a2b itself which we don't care about.
1050 {
1051 0,
1052 {
1053 {{0, {1,1, 0,0,0,0,0}}},
1054 {{0, {1,1, 0,0,0,0,0}}},
1055 {{0, {1,1, 0,0,0,0,0}}},
1056 {{0, {1,1, 0,0,0,0,0}}},
1057 },
1058 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001059 nullptr,
1060 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001061
1062 0,
1063 {
1064 {{0, {1,1, 0,0,0,0,0}}},
1065 {{0, {1,1, 0,0,0,0,0}}},
1066 {{0, {1,1, 0,0,0,0,0}}},
1067 },
1068 {{
1069 { 1,0,0,0 },
1070 { 0,1,0,0 },
1071 { 0,0,1,0 },
1072 }},
1073
1074 0,
1075 {
1076 {{0, {1,1, 0,0,0,0,0}}},
1077 {{0, {1,1, 0,0,0,0,0}}},
1078 {{0, {1,1, 0,0,0,0,0}}},
1079 },
1080 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001081 };
1082
1083 return &XYZD50_profile;
1084}
1085
1086const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1087 return &skcms_sRGB_profile()->trc[0].parametric;
1088}
1089
1090const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1091 static const skcms_TransferFunction sRGB_inv =
1092 { (float)(1/2.4), 1.137119f, 0, 12.92f, 0.0031308f, -0.055f, 0 };
1093 return &sRGB_inv;
1094}
1095
1096const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1097 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1098 return &identity;
1099}
1100
1101const uint8_t skcms_252_random_bytes[] = {
1102 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1103 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1104 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1105 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1106 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1107 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1108 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1109 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1110 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1111 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1112 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1113 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1114 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1115 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1116 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1117 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1118};
1119
1120bool skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile* A, const skcms_ICCProfile* B) {
1121 // For now this is the essentially the same strategy we use in test_only.c
1122 // for our skcms_Transform() smoke tests:
1123 // 1) transform A to XYZD50
1124 // 2) transform B to XYZD50
1125 // 3) return true if they're similar enough
1126 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1127
1128 // Here are 252 of a random shuffle of all possible bytes.
1129 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1130
1131 if (A->data_color_space != B->data_color_space) {
1132 return false;
1133 }
1134
1135 // Interpret as RGB_888 if data color space is RGB or GRAY, RGBA_8888 if CMYK.
1136 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1137 size_t npixels = 84;
1138 if (A->data_color_space == skcms_Signature_CMYK) {
1139 fmt = skcms_PixelFormat_RGBA_8888;
1140 npixels = 63;
1141 }
1142
1143 uint8_t dstA[252],
1144 dstB[252];
1145 if (!skcms_Transform(
1146 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1147 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1148 npixels)) {
1149 return false;
1150 }
1151 if (!skcms_Transform(
1152 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1153 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1154 npixels)) {
1155 return false;
1156 }
1157
1158 for (size_t i = 0; i < 252; i++) {
1159 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1160 return false;
1161 }
1162 }
1163 return true;
1164}
1165
1166bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1167 const skcms_TransferFunction* inv_tf) {
1168 if (!profile || !profile->has_trc) {
1169 return false;
1170 }
1171
1172 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1173 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1174 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1175}
1176
1177static bool is_zero_to_one(float x) {
1178 return 0 <= x && x <= 1;
1179}
1180
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001181typedef struct { float vals[3]; } skcms_Vector3;
1182
1183static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1184 skcms_Vector3 dst = {{0,0,0}};
1185 for (int row = 0; row < 3; ++row) {
1186 dst.vals[row] = m->vals[row][0] * v->vals[0]
1187 + m->vals[row][1] * v->vals[1]
1188 + m->vals[row][2] * v->vals[2];
1189 }
1190 return dst;
1191}
1192
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001193bool skcms_PrimariesToXYZD50(float rx, float ry,
1194 float gx, float gy,
1195 float bx, float by,
1196 float wx, float wy,
1197 skcms_Matrix3x3* toXYZD50) {
1198 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1199 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1200 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1201 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1202 !toXYZD50) {
1203 return false;
1204 }
1205
1206 // First, we need to convert xy values (primaries) to XYZ.
1207 skcms_Matrix3x3 primaries = {{
1208 { rx, gx, bx },
1209 { ry, gy, by },
1210 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1211 }};
1212 skcms_Matrix3x3 primaries_inv;
1213 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1214 return false;
1215 }
1216
1217 // Assumes that Y is 1.0f.
1218 skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001219 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001220
1221 skcms_Matrix3x3 toXYZ = {{
1222 { XYZ.vals[0], 0, 0 },
1223 { 0, XYZ.vals[1], 0 },
1224 { 0, 0, XYZ.vals[2] },
1225 }};
1226 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1227
1228 // Now convert toXYZ matrix to toXYZD50.
1229 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1230
1231 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1232 // the matrices below. The Bradford method is used by Adobe and is widely considered
1233 // to be the best.
1234 skcms_Matrix3x3 xyz_to_lms = {{
1235 { 0.8951f, 0.2664f, -0.1614f },
1236 { -0.7502f, 1.7135f, 0.0367f },
1237 { 0.0389f, -0.0685f, 1.0296f },
1238 }};
1239 skcms_Matrix3x3 lms_to_xyz = {{
1240 { 0.9869929f, -0.1470543f, 0.1599627f },
1241 { 0.4323053f, 0.5183603f, 0.0492912f },
1242 { -0.0085287f, 0.0400428f, 0.9684867f },
1243 }};
1244
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001245 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1246 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001247
1248 skcms_Matrix3x3 DXtoD50 = {{
1249 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1250 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1251 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1252 }};
1253 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1254 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1255
1256 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1257 return true;
1258}
1259
1260
1261bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1262 double a00 = src->vals[0][0],
1263 a01 = src->vals[1][0],
1264 a02 = src->vals[2][0],
1265 a10 = src->vals[0][1],
1266 a11 = src->vals[1][1],
1267 a12 = src->vals[2][1],
1268 a20 = src->vals[0][2],
1269 a21 = src->vals[1][2],
1270 a22 = src->vals[2][2];
1271
1272 double b0 = a00*a11 - a01*a10,
1273 b1 = a00*a12 - a02*a10,
1274 b2 = a01*a12 - a02*a11,
1275 b3 = a20,
1276 b4 = a21,
1277 b5 = a22;
1278
1279 double determinant = b0*b5
1280 - b1*b4
1281 + b2*b3;
1282
1283 if (determinant == 0) {
1284 return false;
1285 }
1286
1287 double invdet = 1.0 / determinant;
1288 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1289 return false;
1290 }
1291
1292 b0 *= invdet;
1293 b1 *= invdet;
1294 b2 *= invdet;
1295 b3 *= invdet;
1296 b4 *= invdet;
1297 b5 *= invdet;
1298
1299 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1300 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1301 dst->vals[2][0] = (float)( + b2 );
1302 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1303 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1304 dst->vals[2][1] = (float)( - b1 );
1305 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1306 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1307 dst->vals[2][2] = (float)( + b0 );
1308
1309 for (int r = 0; r < 3; ++r)
1310 for (int c = 0; c < 3; ++c) {
1311 if (!isfinitef_(dst->vals[r][c])) {
1312 return false;
1313 }
1314 }
1315 return true;
1316}
1317
1318skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1319 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1320 for (int r = 0; r < 3; r++)
1321 for (int c = 0; c < 3; c++) {
1322 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1323 + A->vals[r][1] * B->vals[1][c]
1324 + A->vals[r][2] * B->vals[2][c];
1325 }
1326 return m;
1327}
1328
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001329#if defined(__clang__) || defined(__GNUC__)
1330 #define small_memcpy __builtin_memcpy
1331#else
1332 #define small_memcpy memcpy
1333#endif
1334
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001335static float log2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001336 // The first approximation of log2(x) is its exponent 'e', minus 127.
1337 int32_t bits;
1338 small_memcpy(&bits, &x, sizeof(bits));
1339
1340 float e = (float)bits * (1.0f / (1<<23));
1341
1342 // If we use the mantissa too we can refine the error signficantly.
1343 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
1344 float m;
1345 small_memcpy(&m, &m_bits, sizeof(m));
1346
1347 return (e - 124.225514990f
1348 - 1.498030302f*m
1349 - 1.725879990f/(0.3520887068f + m));
1350}
1351
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001352static float exp2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001353 float fract = x - floorf_(x);
1354
1355 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
1356 - 1.490129070f*fract
1357 + 27.728023300f/(4.84252568f - fract));
1358 if (fbits > INT_MAX) {
1359 return INFINITY_;
1360 } else if (fbits < INT_MIN) {
1361 return -INFINITY_;
1362 }
1363 int32_t bits = (int32_t)fbits;
1364 small_memcpy(&x, &bits, sizeof(x));
1365 return x;
1366}
1367
1368float powf_(float x, float y) {
1369 return (x == 0) || (x == 1) ? x
1370 : exp2f_(log2f_(x) * y);
1371}
1372
1373float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
1374 float sign = x < 0 ? -1.0f : 1.0f;
1375 x *= sign;
1376
1377 return sign * (x < tf->d ? tf->c * x + tf->f
1378 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
1379}
1380
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001381// TODO: Adjust logic here? This still assumes that purely linear inputs will have D > 1, which
1382// we never generate. It also emits inverted linear using the same formulation. Standardize on
1383// G == 1 here, too?
1384bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
1385 // Original equation is: y = (ax + b)^g + e for x >= d
1386 // y = cx + f otherwise
1387 //
1388 // so 1st inverse is: (y - e)^(1/g) = ax + b
1389 // x = ((y - e)^(1/g) - b) / a
1390 //
1391 // which can be re-written as: x = (1/a)(y - e)^(1/g) - b/a
1392 // x = ((1/a)^g)^(1/g) * (y - e)^(1/g) - b/a
1393 // x = ([(1/a)^g]y + [-((1/a)^g)e]) ^ [1/g] + [-b/a]
1394 //
1395 // and 2nd inverse is: x = (y - f) / c
1396 // which can be re-written as: x = [1/c]y + [-f/c]
1397 //
1398 // and now both can be expressed in terms of the same parametric form as the
1399 // original - parameters are enclosed in square brackets.
1400 skcms_TransferFunction tf_inv = { 0, 0, 0, 0, 0, 0, 0 };
1401
1402 // This rejects obviously malformed inputs, as well as decreasing functions
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001403 if (!tf_is_valid(src)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001404 return false;
1405 }
1406
1407 // There are additional constraints to be invertible
1408 bool has_nonlinear = (src->d <= 1);
1409 bool has_linear = (src->d > 0);
1410
1411 // Is the linear section not invertible?
1412 if (has_linear && src->c == 0) {
1413 return false;
1414 }
1415
1416 // Is the nonlinear section not invertible?
1417 if (has_nonlinear && (src->a == 0 || src->g == 0)) {
1418 return false;
1419 }
1420
1421 // If both segments are present, they need to line up
1422 if (has_linear && has_nonlinear) {
1423 float l_at_d = src->c * src->d + src->f;
1424 float n_at_d = powf_(src->a * src->d + src->b, src->g) + src->e;
1425 if (fabsf_(l_at_d - n_at_d) > (1 / 512.0f)) {
1426 return false;
1427 }
1428 }
1429
1430 // Invert linear segment
1431 if (has_linear) {
1432 tf_inv.c = 1.0f / src->c;
1433 tf_inv.f = -src->f / src->c;
1434 }
1435
1436 // Invert nonlinear segment
1437 if (has_nonlinear) {
1438 tf_inv.g = 1.0f / src->g;
1439 tf_inv.a = powf_(1.0f / src->a, src->g);
1440 tf_inv.b = -tf_inv.a * src->e;
1441 tf_inv.e = -src->b / src->a;
1442 }
1443
1444 if (!has_linear) {
1445 tf_inv.d = 0;
1446 } else if (!has_nonlinear) {
1447 // Any value larger than 1 works
1448 tf_inv.d = 2.0f;
1449 } else {
1450 tf_inv.d = src->c * src->d + src->f;
1451 }
1452
1453 *dst = tf_inv;
1454 return true;
1455}
1456
1457// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1458
1459// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1460//
1461// tf(x) = cx + f x < d
1462// tf(x) = (ax + b)^g + e x ≥ d
1463//
1464// When fitting, we add the additional constraint that both pieces meet at d:
1465//
1466// cd + f = (ad + b)^g + e
1467//
1468// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1469//
1470// tf(x) = cx + f x < d
1471// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1472//
1473// Our overall strategy is then:
1474// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001475// - fit_linear(): fit c,d,f iteratively to as many points as our tolerance allows
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001476// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001477// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1478// (and by constraint, inverted e) to the inverse of the table.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001479// Return the parameters with least maximum error.
1480//
1481// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1482// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1483//
1484// let y = Table(x)
1485// r(x) = x - f_inv(y)
1486//
1487// ∂r/∂g = ln(ay + b)*(ay + b)^g
1488// - ln(ad + b)*(ad + b)^g
1489// ∂r/∂a = yg(ay + b)^(g-1)
1490// - dg(ad + b)^(g-1)
1491// ∂r/∂b = g(ay + b)^(g-1)
1492// - g(ad + b)^(g-1)
1493
1494// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1495// and fill out the gradient of the residual into dfdP.
1496static float rg_nonlinear(float x,
1497 const skcms_Curve* curve,
1498 const skcms_TransferFunction* tf,
1499 const float P[3],
1500 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001501 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001502
1503 const float g = P[0], a = P[1], b = P[2],
1504 c = tf->c, d = tf->d, f = tf->f;
1505
1506 const float Y = fmaxf_(a*y + b, 0.0f),
1507 D = a*d + b;
1508 assert (D >= 0);
1509
1510 // The gradient.
1511 dfdP[0] = 0.69314718f*log2f_(Y)*powf_(Y, g)
1512 - 0.69314718f*log2f_(D)*powf_(D, g);
1513 dfdP[1] = y*g*powf_(Y, g-1)
1514 - d*g*powf_(D, g-1);
1515 dfdP[2] = g*powf_(Y, g-1)
1516 - g*powf_(D, g-1);
1517
1518 // The residual.
1519 const float f_inv = powf_(Y, g)
1520 - powf_(D, g)
1521 + c*d + f;
1522 return x - f_inv;
1523}
1524
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001525static bool gauss_newton_step(const skcms_Curve* curve,
1526 const skcms_TransferFunction* tf,
1527 float P[3],
1528 float x0, float dx, int N) {
1529 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1530 //
1531 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1532 // where r(P) is the residual vector
1533 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1534 //
1535 // Let's review the shape of each of these expressions:
1536 // r(P) is [N x 1], a column vector with one entry per value of x tested
1537 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1538 // Jf^T is [3 x N], the transpose of Jf
1539 //
1540 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1541 // and so is its inverse (Jf^T Jf)^-1
1542 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1543 //
1544 // Our implementation strategy to get to the final ∆P is
1545 // 1) evaluate Jf^T Jf, call that lhs
1546 // 2) evaluate Jf^T r(P), call that rhs
1547 // 3) invert lhs
1548 // 4) multiply inverse lhs by rhs
1549 //
1550 // This is a friendly implementation strategy because we don't have to have any
1551 // buffers that scale with N, and equally nice don't have to perform any matrix
1552 // operations that are variable size.
1553 //
1554 // Other implementation strategies could trade this off, e.g. evaluating the
1555 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1556 // the residuals. That would probably require implementing singular value
1557 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1558 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1559 // possibility of this gauss_newton_step() function ever failing.
1560
1561 // 0) start off with lhs and rhs safely zeroed.
1562 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1563 skcms_Vector3 rhs = { {0,0,0} };
1564
1565 // 1,2) evaluate lhs and evaluate rhs
1566 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1567 // so we'll have to update lhs and rhs at the same time.
1568 for (int i = 0; i < N; i++) {
1569 float x = x0 + i*dx;
1570
1571 float dfdP[3] = {0,0,0};
1572 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1573
1574 for (int r = 0; r < 3; r++) {
1575 for (int c = 0; c < 3; c++) {
1576 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1577 }
1578 rhs.vals[r] += dfdP[r] * resid;
1579 }
1580 }
1581
1582 // If any of the 3 P parameters are unused, this matrix will be singular.
1583 // Detect those cases and fix them up to indentity instead, so we can invert.
1584 for (int k = 0; k < 3; k++) {
1585 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1586 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1587 lhs.vals[k][k] = 1;
1588 }
1589 }
1590
1591 // 3) invert lhs
1592 skcms_Matrix3x3 lhs_inv;
1593 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1594 return false;
1595 }
1596
1597 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001598 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001599 P[0] += dP.vals[0];
1600 P[1] += dP.vals[1];
1601 P[2] += dP.vals[2];
1602 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1603}
1604
1605
1606// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1607static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1608 float P[3] = { tf->g, tf->a, tf->b };
1609
1610 // No matter where we start, dx should always represent N even steps from 0 to 1.
1611 const float dx = 1.0f / (N-1);
1612
1613 for (int j = 0; j < 3/*TODO: tune*/; j++) {
1614 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1615 // We don't really know how to fix up a if it goes negative.
1616 if (P[1] < 0) {
1617 return false;
1618 }
1619 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1620 if (P[1] * tf->d + P[2] < 0) {
1621 P[2] = -P[1] * tf->d;
1622 }
1623 assert (P[1] >= 0 &&
1624 P[1] * tf->d + P[2] >= 0);
1625
1626 if (!gauss_newton_step(curve, tf,
1627 P,
1628 L*dx, dx, N-L)) {
1629 return false;
1630 }
1631 }
1632
1633 // We need to apply our fixups one last time
1634 if (P[1] < 0) {
1635 return false;
1636 }
1637 if (P[1] * tf->d + P[2] < 0) {
1638 P[2] = -P[1] * tf->d;
1639 }
1640
1641 tf->g = P[0];
1642 tf->a = P[1];
1643 tf->b = P[2];
1644 tf->e = tf->c*tf->d + tf->f
1645 - powf_(tf->a*tf->d + tf->b, tf->g);
1646 return true;
1647}
1648
1649bool skcms_ApproximateCurve(const skcms_Curve* curve,
1650 skcms_TransferFunction* approx,
1651 float* max_error) {
1652 if (!curve || !approx || !max_error) {
1653 return false;
1654 }
1655
1656 if (curve->table_entries == 0) {
1657 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1658 return false;
1659 }
1660
1661 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1662 // We need at least two points, and must put some reasonable cap on the maximum number.
1663 return false;
1664 }
1665
1666 int N = (int)curve->table_entries;
1667 const float dx = 1.0f / (N - 1);
1668
1669 *max_error = INFINITY_;
1670 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1671 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1672 skcms_TransferFunction tf,
1673 tf_inv;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001674 int L = fit_linear(curve, N, kTolerances[t], &tf.c, &tf.d, &tf.f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001675
1676 if (L == N) {
1677 // If the entire data set was linear, move the coefficients to the nonlinear portion
1678 // with G == 1. This lets use a canonical representation with d == 0.
1679 tf.g = 1;
1680 tf.a = tf.c;
1681 tf.b = tf.f;
1682 tf.c = tf.d = tf.e = tf.f = 0;
1683 } else if (L == N - 1) {
1684 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1685 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001686 tf.a = (eval_curve(curve, (N-1)*dx) -
1687 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001688 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001689 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001690 - tf.a * (N-2)*dx;
1691 tf.e = 0;
1692 } else {
1693 // Start by guessing a gamma-only curve through the midpoint.
1694 int mid = (L + N) / 2;
1695 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001696 float mid_y = eval_curve(curve, mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001697 tf.g = log2f_(mid_y) / log2f_(mid_x);;
1698 tf.a = 1;
1699 tf.b = 0;
1700 tf.e = tf.c*tf.d + tf.f
1701 - powf_(tf.a*tf.d + tf.b, tf.g);
1702
1703
1704 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1705 !fit_nonlinear(curve, L,N, &tf_inv)) {
1706 continue;
1707 }
1708
1709 // We fit tf_inv, so calculate tf to keep in sync.
1710 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1711 continue;
1712 }
1713 }
1714
1715 // We find our error by roundtripping the table through tf_inv.
1716 //
1717 // (The most likely use case for this approximation is to be inverted and
1718 // used as the transfer function for a destination color space.)
1719 //
1720 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1721 // invertible, so re-verify that here (and use the new inverse for testing).
1722 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1723 continue;
1724 }
1725
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001726 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001727 if (*max_error > err) {
1728 *max_error = err;
1729 *approx = tf;
1730 }
1731 }
1732 return isfinitef_(*max_error);
1733}
1734
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001735// ~~~~ Impl. of skcms_Transform() ~~~~
1736
1737typedef enum {
1738 Op_noop,
1739
1740 Op_load_a8,
1741 Op_load_g8,
1742 Op_load_4444,
1743 Op_load_565,
1744 Op_load_888,
1745 Op_load_8888,
1746 Op_load_1010102,
1747 Op_load_161616,
1748 Op_load_16161616,
1749 Op_load_hhh,
1750 Op_load_hhhh,
1751 Op_load_fff,
1752 Op_load_ffff,
1753
1754 Op_swap_rb,
1755 Op_clamp,
1756 Op_invert,
1757 Op_force_opaque,
1758 Op_premul,
1759 Op_unpremul,
1760 Op_matrix_3x3,
1761 Op_matrix_3x4,
1762 Op_lab_to_xyz,
1763
1764 Op_tf_r,
1765 Op_tf_g,
1766 Op_tf_b,
1767 Op_tf_a,
1768
1769 Op_table_8_r,
1770 Op_table_8_g,
1771 Op_table_8_b,
1772 Op_table_8_a,
1773
1774 Op_table_16_r,
1775 Op_table_16_g,
1776 Op_table_16_b,
1777 Op_table_16_a,
1778
1779 Op_clut_3D_8,
1780 Op_clut_3D_16,
1781 Op_clut_4D_8,
1782 Op_clut_4D_16,
1783
1784 Op_store_a8,
1785 Op_store_g8,
1786 Op_store_4444,
1787 Op_store_565,
1788 Op_store_888,
1789 Op_store_8888,
1790 Op_store_1010102,
1791 Op_store_161616,
1792 Op_store_16161616,
1793 Op_store_hhh,
1794 Op_store_hhhh,
1795 Op_store_fff,
1796 Op_store_ffff,
1797} Op;
1798
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001799// Without this wasm would try to use the N=4 128-bit vector code path,
1800// which while ideal, causes tons of compiler problems. This would be
1801// a good thing to revisit as emcc matures (currently 1.38.5).
1802#if 1 && defined(__EMSCRIPTEN_major__)
1803 #if !defined(SKCMS_PORTABLE)
1804 #define SKCMS_PORTABLE
1805 #endif
1806#endif
1807
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001808#if defined(__clang__)
1809 typedef float __attribute__((ext_vector_type(4))) Fx4;
1810 typedef int32_t __attribute__((ext_vector_type(4))) I32x4;
1811 typedef uint64_t __attribute__((ext_vector_type(4))) U64x4;
1812 typedef uint32_t __attribute__((ext_vector_type(4))) U32x4;
1813 typedef uint16_t __attribute__((ext_vector_type(4))) U16x4;
1814 typedef uint8_t __attribute__((ext_vector_type(4))) U8x4;
1815
1816 typedef float __attribute__((ext_vector_type(8))) Fx8;
1817 typedef int32_t __attribute__((ext_vector_type(8))) I32x8;
1818 typedef uint64_t __attribute__((ext_vector_type(8))) U64x8;
1819 typedef uint32_t __attribute__((ext_vector_type(8))) U32x8;
1820 typedef uint16_t __attribute__((ext_vector_type(8))) U16x8;
1821 typedef uint8_t __attribute__((ext_vector_type(8))) U8x8;
1822
1823 typedef float __attribute__((ext_vector_type(16))) Fx16;
1824 typedef int32_t __attribute__((ext_vector_type(16))) I32x16;
1825 typedef uint64_t __attribute__((ext_vector_type(16))) U64x16;
1826 typedef uint32_t __attribute__((ext_vector_type(16))) U32x16;
1827 typedef uint16_t __attribute__((ext_vector_type(16))) U16x16;
1828 typedef uint8_t __attribute__((ext_vector_type(16))) U8x16;
1829#elif defined(__GNUC__)
1830 typedef float __attribute__((vector_size(16))) Fx4;
1831 typedef int32_t __attribute__((vector_size(16))) I32x4;
1832 typedef uint64_t __attribute__((vector_size(32))) U64x4;
1833 typedef uint32_t __attribute__((vector_size(16))) U32x4;
1834 typedef uint16_t __attribute__((vector_size( 8))) U16x4;
1835 typedef uint8_t __attribute__((vector_size( 4))) U8x4;
1836
1837 typedef float __attribute__((vector_size(32))) Fx8;
1838 typedef int32_t __attribute__((vector_size(32))) I32x8;
1839 typedef uint64_t __attribute__((vector_size(64))) U64x8;
1840 typedef uint32_t __attribute__((vector_size(32))) U32x8;
1841 typedef uint16_t __attribute__((vector_size(16))) U16x8;
1842 typedef uint8_t __attribute__((vector_size( 8))) U8x8;
1843
1844 typedef float __attribute__((vector_size( 64))) Fx16;
1845 typedef int32_t __attribute__((vector_size( 64))) I32x16;
1846 typedef uint64_t __attribute__((vector_size(128))) U64x16;
1847 typedef uint32_t __attribute__((vector_size( 64))) U32x16;
1848 typedef uint16_t __attribute__((vector_size( 32))) U16x16;
1849 typedef uint8_t __attribute__((vector_size( 16))) U8x16;
1850#endif
1851
1852// First, instantiate our default exec_ops() implementation using the default compiliation target.
1853
1854#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__))
1855 #define N 1
1856
1857 #define F float
1858 #define U64 uint64_t
1859 #define U32 uint32_t
1860 #define I32 int32_t
1861 #define U16 uint16_t
1862 #define U8 uint8_t
1863
1864 #define F0 0.0f
1865 #define F1 1.0f
1866
1867#elif defined(__AVX512F__)
1868 #define N 16
1869
1870 #define F Fx16
1871 #define U64 U64x16
1872 #define U32 U32x16
1873 #define I32 I32x16
1874 #define U16 U16x16
1875 #define U8 U8x16
1876
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001877 #define F0 F{0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}
1878 #define F1 F{1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001879#elif defined(__AVX__)
1880 #define N 8
1881
1882 #define F Fx8
1883 #define U64 U64x8
1884 #define U32 U32x8
1885 #define I32 I32x8
1886 #define U16 U16x8
1887 #define U8 U8x8
1888
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001889 #define F0 F{0,0,0,0, 0,0,0,0}
1890 #define F1 F{1,1,1,1, 1,1,1,1}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001891#else
1892 #define N 4
1893
1894 #define F Fx4
1895 #define U64 U64x4
1896 #define U32 U32x4
1897 #define I32 I32x4
1898 #define U16 U16x4
1899 #define U8 U8x4
1900
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001901 #define F0 F{0,0,0,0}
1902 #define F1 F{1,1,1,1}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001903#endif
1904
1905#define NS(id) id
1906#define ATTR
1907 #include "src/Transform_inl.h"
1908#undef N
1909#undef F
1910#undef U64
1911#undef U32
1912#undef I32
1913#undef U16
1914#undef U8
1915#undef F0
1916#undef F1
1917#undef NS
1918#undef ATTR
1919
1920// Now, instantiate any other versions of run_program() we may want for runtime detection.
1921#if !defined(SKCMS_PORTABLE) && (defined(__clang__) || defined(__GNUC__)) \
1922 && defined(__x86_64__) && !defined(__AVX2__)
1923 #define N 8
1924 #define F Fx8
1925 #define U64 U64x8
1926 #define U32 U32x8
1927 #define I32 I32x8
1928 #define U16 U16x8
1929 #define U8 U8x8
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001930 #define F0 F{0,0,0,0, 0,0,0,0}
1931 #define F1 F{1,1,1,1, 1,1,1,1}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001932
1933 #define NS(id) id ## _hsw
1934 #define ATTR __attribute__((target("avx2,f16c")))
1935
1936 // We check these guards to see if we have support for these features.
1937 // They're likely _not_ defined here in our baseline build config.
1938 #ifndef __AVX__
1939 #define __AVX__ 1
1940 #define UNDEF_AVX
1941 #endif
1942 #ifndef __F16C__
1943 #define __F16C__ 1
1944 #define UNDEF_F16C
1945 #endif
1946 #ifndef __AVX2__
1947 #define __AVX2__ 1
1948 #define UNDEF_AVX2
1949 #endif
1950
1951 #include "src/Transform_inl.h"
1952
1953 #undef N
1954 #undef F
1955 #undef U64
1956 #undef U32
1957 #undef I32
1958 #undef U16
1959 #undef U8
1960 #undef F0
1961 #undef F1
1962 #undef NS
1963 #undef ATTR
1964
1965 #ifdef UNDEF_AVX
1966 #undef __AVX__
1967 #undef UNDEF_AVX
1968 #endif
1969 #ifdef UNDEF_F16C
1970 #undef __F16C__
1971 #undef UNDEF_F16C
1972 #endif
1973 #ifdef UNDEF_AVX2
1974 #undef __AVX2__
1975 #undef UNDEF_AVX2
1976 #endif
1977
1978 #define TEST_FOR_HSW
1979
1980 static bool hsw_ok_ = false;
1981 static void check_hsw_ok() {
1982 // See http://www.sandpile.org/x86/cpuid.htm
1983
1984 // First, a basic cpuid(1).
1985 uint32_t eax, ebx, ecx, edx;
1986 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1987 : "0"(1), "2"(0));
1988
1989 // Sanity check for prerequisites.
1990 if ((edx & (1<<25)) != (1<<25)) { return; } // SSE
1991 if ((edx & (1<<26)) != (1<<26)) { return; } // SSE2
1992 if ((ecx & (1<< 0)) != (1<< 0)) { return; } // SSE3
1993 if ((ecx & (1<< 9)) != (1<< 9)) { return; } // SSSE3
1994 if ((ecx & (1<<19)) != (1<<19)) { return; } // SSE4.1
1995 if ((ecx & (1<<20)) != (1<<20)) { return; } // SSE4.2
1996
1997 if ((ecx & (3<<26)) != (3<<26)) { return; } // XSAVE + OSXSAVE
1998
1999 {
2000 uint32_t eax_xgetbv, edx_xgetbv;
2001 __asm__ __volatile__("xgetbv" : "=a"(eax_xgetbv), "=d"(edx_xgetbv) : "c"(0));
2002 if ((eax_xgetbv & (3<<1)) != (3<<1)) { return; } // XMM+YMM state saved?
2003 }
2004
2005 if ((ecx & (1<<28)) != (1<<28)) { return; } // AVX
2006 if ((ecx & (1<<29)) != (1<<29)) { return; } // F16C
2007 if ((ecx & (1<<12)) != (1<<12)) { return; } // FMA (TODO: not currently used)
2008
2009 // Call cpuid(7) to check for our final AVX2 feature bit!
2010 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2011 : "0"(7), "2"(0));
2012 if ((ebx & (1<< 5)) != (1<< 5)) { return; } // AVX2
2013
2014 hsw_ok_ = true;
2015 }
2016
2017 #if defined(_MSC_VER)
2018 #include <Windows.h>
2019 INIT_ONCE check_hsw_ok_once = INIT_ONCE_STATIC_INIT;
2020
2021 static BOOL check_hsw_ok_InitOnce_wrapper(INIT_ONCE* once, void* param, void** ctx) {
2022 (void)once;
2023 (void)param;
2024 (void)ctx;
2025 check_hsw_ok();
2026 return TRUE;
2027 }
2028
2029 static bool hsw_ok() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002030 InitOnceExecuteOnce(&check_hsw_ok_once, check_hsw_ok_InitOnce_wrapper,
2031 nullptr, nullptr);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002032 return hsw_ok_;
2033 }
2034 #else
2035 #include <pthread.h>
2036 static pthread_once_t check_hsw_ok_once = PTHREAD_ONCE_INIT;
2037
2038 static bool hsw_ok() {
2039 pthread_once(&check_hsw_ok_once, check_hsw_ok);
2040 return hsw_ok_;
2041 }
2042 #endif
2043
2044#endif
2045
2046static bool is_identity_tf(const skcms_TransferFunction* tf) {
2047 return tf->g == 1 && tf->a == 1
2048 && tf->b == 0 && tf->c == 0 && tf->d == 0 && tf->e == 0 && tf->f == 0;
2049}
2050
2051typedef struct {
2052 Op op;
2053 const void* arg;
2054} OpAndArg;
2055
2056static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
2057 static const struct { Op parametric, table_8, table_16; } ops[] = {
2058 { Op_tf_r, Op_table_8_r, Op_table_16_r },
2059 { Op_tf_g, Op_table_8_g, Op_table_16_g },
2060 { Op_tf_b, Op_table_8_b, Op_table_16_b },
2061 { Op_tf_a, Op_table_8_a, Op_table_16_a },
2062 };
2063
2064 if (curve->table_entries == 0) {
2065 return is_identity_tf(&curve->parametric)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002066 ? OpAndArg{ Op_noop, nullptr }
2067 : OpAndArg{ ops[channel].parametric, &curve->parametric };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002068 } else if (curve->table_8) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002069 return OpAndArg{ ops[channel].table_8, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002070 } else if (curve->table_16) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002071 return OpAndArg{ ops[channel].table_16, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002072 }
2073
2074 assert(false);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002075 return OpAndArg{Op_noop,nullptr};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002076}
2077
2078static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2079 switch (fmt >> 1) { // ignore rgb/bgr
2080 case skcms_PixelFormat_A_8 >> 1: return 1;
2081 case skcms_PixelFormat_G_8 >> 1: return 1;
2082 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2083 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2084 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2085 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2086 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2087 case skcms_PixelFormat_RGB_161616 >> 1: return 6;
2088 case skcms_PixelFormat_RGBA_16161616 >> 1: return 8;
2089 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2090 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2091 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2092 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
2093 }
2094 assert(false);
2095 return 0;
2096}
2097
2098static bool prep_for_destination(const skcms_ICCProfile* profile,
2099 skcms_Matrix3x3* fromXYZD50,
2100 skcms_TransferFunction* invR,
2101 skcms_TransferFunction* invG,
2102 skcms_TransferFunction* invB) {
2103 // We only support destinations with parametric transfer functions
2104 // and with gamuts that can be transformed from XYZD50.
2105 return profile->has_trc
2106 && profile->has_toXYZD50
2107 && profile->trc[0].table_entries == 0
2108 && profile->trc[1].table_entries == 0
2109 && profile->trc[2].table_entries == 0
2110 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2111 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2112 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2113 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2114}
2115
2116bool skcms_Transform(const void* src,
2117 skcms_PixelFormat srcFmt,
2118 skcms_AlphaFormat srcAlpha,
2119 const skcms_ICCProfile* srcProfile,
2120 void* dst,
2121 skcms_PixelFormat dstFmt,
2122 skcms_AlphaFormat dstAlpha,
2123 const skcms_ICCProfile* dstProfile,
2124 size_t nz) {
2125 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2126 src_bpp = bytes_per_pixel(srcFmt);
2127 // Let's just refuse if the request is absurdly big.
2128 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2129 return false;
2130 }
2131 int n = (int)nz;
2132
2133 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2134 if (!srcProfile) {
2135 srcProfile = skcms_sRGB_profile();
2136 }
2137 if (!dstProfile) {
2138 dstProfile = skcms_sRGB_profile();
2139 }
2140
2141 // We can't transform in place unless the PixelFormats are the same size.
2142 if (dst == src && (dstFmt >> 1) != (srcFmt >> 1)) {
2143 return false;
2144 }
2145 // TODO: this check lazilly disallows U16 <-> F16, but that would actually be fine.
2146 // TODO: more careful alias rejection (like, dst == src + 1)?
2147
2148 Op program [32];
2149 const void* arguments[32];
2150
2151 Op* ops = program;
2152 const void** args = arguments;
2153
2154 skcms_TransferFunction inv_dst_tf_r, inv_dst_tf_g, inv_dst_tf_b;
2155 skcms_Matrix3x3 from_xyz;
2156
2157 switch (srcFmt >> 1) {
2158 default: return false;
2159 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2160 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2161 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2162 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2163 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2164 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2165 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2166 case skcms_PixelFormat_RGB_161616 >> 1: *ops++ = Op_load_161616; break;
2167 case skcms_PixelFormat_RGBA_16161616 >> 1: *ops++ = Op_load_16161616; break;
2168 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2169 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2170 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2171 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
2172 }
2173 if (srcFmt & 1) {
2174 *ops++ = Op_swap_rb;
2175 }
2176 skcms_ICCProfile gray_dst_profile;
2177 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2178 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2179 // luminance (Y) by the destination transfer function.
2180 gray_dst_profile = *dstProfile;
2181 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2182 dstProfile = &gray_dst_profile;
2183 }
2184
2185 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2186 // Photoshop creates CMYK images as inverse CMYK.
2187 // These happen to be the only ones we've _ever_ seen.
2188 *ops++ = Op_invert;
2189 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2190 srcAlpha = skcms_AlphaFormat_Unpremul;
2191 }
2192
2193 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2194 *ops++ = Op_force_opaque;
2195 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2196 *ops++ = Op_unpremul;
2197 }
2198
2199 // TODO: We can skip this work if both srcAlpha and dstAlpha are PremulLinear, and the profiles
2200 // are the same. Also, if dstAlpha is PremulLinear, and SrcAlpha is Opaque.
2201 if (dstProfile != srcProfile ||
2202 srcAlpha == skcms_AlphaFormat_PremulLinear ||
2203 dstAlpha == skcms_AlphaFormat_PremulLinear) {
2204
2205 if (!prep_for_destination(dstProfile,
2206 &from_xyz, &inv_dst_tf_r, &inv_dst_tf_b, &inv_dst_tf_g)) {
2207 return false;
2208 }
2209
2210 if (srcProfile->has_A2B) {
2211 if (srcProfile->A2B.input_channels) {
2212 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2213 OpAndArg oa = select_curve_op(&srcProfile->A2B.input_curves[i], i);
2214 if (oa.op != Op_noop) {
2215 *ops++ = oa.op;
2216 *args++ = oa.arg;
2217 }
2218 }
2219 switch (srcProfile->A2B.input_channels) {
2220 case 3: *ops++ = srcProfile->A2B.grid_8 ? Op_clut_3D_8 : Op_clut_3D_16; break;
2221 case 4: *ops++ = srcProfile->A2B.grid_8 ? Op_clut_4D_8 : Op_clut_4D_16; break;
2222 default: return false;
2223 }
2224 *args++ = &srcProfile->A2B;
2225 }
2226
2227 if (srcProfile->A2B.matrix_channels == 3) {
2228 for (int i = 0; i < 3; i++) {
2229 OpAndArg oa = select_curve_op(&srcProfile->A2B.matrix_curves[i], i);
2230 if (oa.op != Op_noop) {
2231 *ops++ = oa.op;
2232 *args++ = oa.arg;
2233 }
2234 }
2235
2236 static const skcms_Matrix3x4 I = {{
2237 {1,0,0,0},
2238 {0,1,0,0},
2239 {0,0,1,0},
2240 }};
2241 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2242 *ops++ = Op_matrix_3x4;
2243 *args++ = &srcProfile->A2B.matrix;
2244 }
2245 }
2246
2247 if (srcProfile->A2B.output_channels == 3) {
2248 for (int i = 0; i < 3; i++) {
2249 OpAndArg oa = select_curve_op(&srcProfile->A2B.output_curves[i], i);
2250 if (oa.op != Op_noop) {
2251 *ops++ = oa.op;
2252 *args++ = oa.arg;
2253 }
2254 }
2255 }
2256
2257 if (srcProfile->pcs == skcms_Signature_Lab) {
2258 *ops++ = Op_lab_to_xyz;
2259 }
2260
2261 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2262 for (int i = 0; i < 3; i++) {
2263 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
2264 if (oa.op != Op_noop) {
2265 *ops++ = oa.op;
2266 *args++ = oa.arg;
2267 }
2268 }
2269 } else {
2270 return false;
2271 }
2272
2273 // At this point our source colors are linear, either RGB (XYZ-type profiles)
2274 // or XYZ (A2B-type profiles). Unpremul is a linear operation (multiply by a
2275 // constant 1/a), so either way we can do it now if needed.
2276 if (srcAlpha == skcms_AlphaFormat_PremulLinear) {
2277 *ops++ = Op_unpremul;
2278 }
2279
2280 // A2B sources should already be in XYZD50 at this point.
2281 // Others still need to be transformed using their toXYZD50 matrix.
2282 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2283 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2284 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2285 static const skcms_Matrix3x3 I = {{
2286 { 1.0f, 0.0f, 0.0f },
2287 { 0.0f, 1.0f, 0.0f },
2288 { 0.0f, 0.0f, 1.0f },
2289 }};
2290 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2291
2292 // There's a chance the source and destination gamuts are identical,
2293 // in which case we can skip the gamut transform.
2294 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2295 // Concat the entire gamut transform into from_xyz,
2296 // now slightly misnamed but it's a handy spot to stash the result.
2297 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2298 *ops++ = Op_matrix_3x3;
2299 *args++ = &from_xyz;
2300 }
2301
2302 if (dstAlpha == skcms_AlphaFormat_PremulLinear) {
2303 *ops++ = Op_premul;
2304 }
2305
2306 // Encode back to dst RGB using its parametric transfer functions.
2307 if (!is_identity_tf(&inv_dst_tf_r)) { *ops++ = Op_tf_r; *args++ = &inv_dst_tf_r; }
2308 if (!is_identity_tf(&inv_dst_tf_g)) { *ops++ = Op_tf_g; *args++ = &inv_dst_tf_g; }
2309 if (!is_identity_tf(&inv_dst_tf_b)) { *ops++ = Op_tf_b; *args++ = &inv_dst_tf_b; }
2310 }
2311
2312 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2313 *ops++ = Op_force_opaque;
2314 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2315 *ops++ = Op_premul;
2316 }
2317 if (dstFmt & 1) {
2318 *ops++ = Op_swap_rb;
2319 }
2320 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2321 *ops++ = Op_clamp;
2322 }
2323 switch (dstFmt >> 1) {
2324 default: return false;
2325 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2326 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2327 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2328 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2329 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2330 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2331 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2332 case skcms_PixelFormat_RGB_161616 >> 1: *ops++ = Op_store_161616; break;
2333 case skcms_PixelFormat_RGBA_16161616 >> 1: *ops++ = Op_store_16161616; break;
2334 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2335 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2336 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2337 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_store_ffff; break;
2338 }
2339
2340 void (*run)(const Op*, const void**, const char*, char*, int, size_t,size_t) = run_program;
2341#if defined(TEST_FOR_HSW)
2342 if (hsw_ok()) {
2343 run = run_program_hsw;
2344 }
2345#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002346 run(program, arguments, (const char*)src, (char*)dst, n, src_bpp,dst_bpp);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002347 return true;
2348}
2349
2350static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2351#if defined(NDEBUG)
2352 (void)profile;
2353#else
2354 skcms_Matrix3x3 fromXYZD50;
2355 skcms_TransferFunction invR, invG, invB;
2356 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2357#endif
2358}
2359
2360bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2361 skcms_Matrix3x3 fromXYZD50;
2362 if (!profile->has_trc || !profile->has_toXYZD50
2363 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2364 return false;
2365 }
2366
2367 skcms_TransferFunction tf[3];
2368 for (int i = 0; i < 3; i++) {
2369 skcms_TransferFunction inv;
2370 if (profile->trc[i].table_entries == 0
2371 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2372 tf[i] = profile->trc[i].parametric;
2373 continue;
2374 }
2375
2376 float max_error;
2377 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2378 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2379 return false;
2380 }
2381 }
2382
2383 for (int i = 0; i < 3; ++i) {
2384 profile->trc[i].table_entries = 0;
2385 profile->trc[i].parametric = tf[i];
2386 }
2387
2388 assert_usable_as_destination(profile);
2389 return true;
2390}
2391
2392bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2393 // Operate on a copy of profile, so we can choose the best TF for the original curves
2394 skcms_ICCProfile result = *profile;
2395 if (!skcms_MakeUsableAsDestination(&result)) {
2396 return false;
2397 }
2398
2399 int best_tf = 0;
2400 float min_max_error = INFINITY_;
2401 for (int i = 0; i < 3; i++) {
2402 skcms_TransferFunction inv;
2403 skcms_TransferFunction_invert(&result.trc[i].parametric, &inv);
2404
2405 float err = 0;
2406 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002407 err = fmaxf_(err, max_roundtrip_error(&profile->trc[j], &inv));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002408 }
2409 if (min_max_error > err) {
2410 min_max_error = err;
2411 best_tf = i;
2412 }
2413 }
2414
2415 for (int i = 0; i < 3; i++) {
2416 result.trc[i].parametric = result.trc[best_tf].parametric;
2417 }
2418
2419 *profile = result;
2420 assert_usable_as_destination(profile);
2421 return true;
2422}