blob: 2a86753b3eb831ca5cb0f9ca49d1f582f55d5476 [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.com4241bab2018-07-30 13:00:30 +000016#if defined(__ARM_NEON)
17 #include <arm_neon.h>
18#elif defined(__SSE__)
19 #include <immintrin.h>
skia-autorollba6087c2019-04-09 13:57:02 +000020
21 #if defined(__clang__)
22 // That #include <immintrin.h> is usually enough, but Clang's headers
23 // "helpfully" skip including the whole kitchen sink when _MSC_VER is
24 // defined, because lots of programs on Windows would include that and
25 // it'd be a lot slower. But we want all those headers included so we
26 // can use their features after runtime checks later.
27 #include <smmintrin.h>
28 #include <avxintrin.h>
29 #include <avx2intrin.h>
30 #include <avx512fintrin.h>
31 #include <avx512dqintrin.h>
32 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +000033#endif
34
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000035// sizeof(x) will return size_t, which is 32-bit on some machines and 64-bit on others.
36// We have better testing on 64-bit machines, so force 32-bit machines to behave like 64-bit.
37//
38// Please do not use sizeof() directly, and size_t only when required.
39// (We have no way of enforcing these requests...)
40#define SAFE_SIZEOF(x) ((uint64_t)sizeof(x))
41
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +000042// Same sort of thing for _Layout structs with a variable sized array at the end (named "variable").
43#define SAFE_FIXED_SIZE(type) ((uint64_t)offsetof(type, variable))
44
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000045static const union {
46 uint32_t bits;
47 float f;
48} inf_ = { 0x7f800000 };
49#define INFINITY_ inf_.f
50
51static float fmaxf_(float x, float y) { return x > y ? x : y; }
52static float fminf_(float x, float y) { return x < y ? x : y; }
53
54static bool isfinitef_(float x) { return 0 == x*0; }
55
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000056static float minus_1_ulp(float x) {
57 int32_t bits;
58 memcpy(&bits, &x, sizeof(bits));
59 bits = bits - 1;
60 memcpy(&x, &bits, sizeof(bits));
61 return x;
62}
63
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000064static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000065 if (curve->table_entries == 0) {
66 return skcms_TransferFunction_eval(&curve->parametric, x);
67 }
68
69 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 +000070 int lo = (int) ix ,
71 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000072 float t = ix - (float)lo;
73
74 float l, h;
75 if (curve->table_8) {
76 l = curve->table_8[lo] * (1/255.0f);
77 h = curve->table_8[hi] * (1/255.0f);
78 } else {
79 uint16_t be_l, be_h;
80 memcpy(&be_l, curve->table_16 + 2*lo, 2);
81 memcpy(&be_h, curve->table_16 + 2*hi, 2);
82 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
83 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
84 l = le_l * (1/65535.0f);
85 h = le_h * (1/65535.0f);
86 }
87 return l + (h-l)*t;
88}
89
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000090static 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 +000091 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
92 const float dx = 1.0f / (N - 1);
93 float err = 0;
94 for (uint32_t i = 0; i < N; i++) {
95 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000096 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000097 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
98 }
99 return err;
100}
101
102bool 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 +0000103 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 +0000104}
105
106// Additional ICC signature values that are only used internally
107enum {
108 // File signature
109 skcms_Signature_acsp = 0x61637370,
110
111 // Tag signatures
112 skcms_Signature_rTRC = 0x72545243,
113 skcms_Signature_gTRC = 0x67545243,
114 skcms_Signature_bTRC = 0x62545243,
115 skcms_Signature_kTRC = 0x6B545243,
116
117 skcms_Signature_rXYZ = 0x7258595A,
118 skcms_Signature_gXYZ = 0x6758595A,
119 skcms_Signature_bXYZ = 0x6258595A,
120
121 skcms_Signature_A2B0 = 0x41324230,
122 skcms_Signature_A2B1 = 0x41324231,
123 skcms_Signature_mAB = 0x6D414220,
124
125 skcms_Signature_CHAD = 0x63686164,
126
127 // Type signatures
128 skcms_Signature_curv = 0x63757276,
129 skcms_Signature_mft1 = 0x6D667431,
130 skcms_Signature_mft2 = 0x6D667432,
131 skcms_Signature_para = 0x70617261,
132 skcms_Signature_sf32 = 0x73663332,
133 // XYZ is also a PCS signature, so it's defined in skcms.h
134 // skcms_Signature_XYZ = 0x58595A20,
135};
136
137static uint16_t read_big_u16(const uint8_t* ptr) {
138 uint16_t be;
139 memcpy(&be, ptr, sizeof(be));
140#if defined(_MSC_VER)
141 return _byteswap_ushort(be);
142#else
143 return __builtin_bswap16(be);
144#endif
145}
146
147static uint32_t read_big_u32(const uint8_t* ptr) {
148 uint32_t be;
149 memcpy(&be, ptr, sizeof(be));
150#if defined(_MSC_VER)
151 return _byteswap_ulong(be);
152#else
153 return __builtin_bswap32(be);
154#endif
155}
156
157static int32_t read_big_i32(const uint8_t* ptr) {
158 return (int32_t)read_big_u32(ptr);
159}
160
161static float read_big_fixed(const uint8_t* ptr) {
162 return read_big_i32(ptr) * (1.0f / 65536.0f);
163}
164
165// Maps to an in-memory profile so that fields line up to the locations specified
166// in ICC.1:2010, section 7.2
167typedef struct {
168 uint8_t size [ 4];
169 uint8_t cmm_type [ 4];
170 uint8_t version [ 4];
171 uint8_t profile_class [ 4];
172 uint8_t data_color_space [ 4];
173 uint8_t pcs [ 4];
174 uint8_t creation_date_time [12];
175 uint8_t signature [ 4];
176 uint8_t platform [ 4];
177 uint8_t flags [ 4];
178 uint8_t device_manufacturer [ 4];
179 uint8_t device_model [ 4];
180 uint8_t device_attributes [ 8];
181 uint8_t rendering_intent [ 4];
182 uint8_t illuminant_X [ 4];
183 uint8_t illuminant_Y [ 4];
184 uint8_t illuminant_Z [ 4];
185 uint8_t creator [ 4];
186 uint8_t profile_id [16];
187 uint8_t reserved [28];
188 uint8_t tag_count [ 4]; // Technically not part of header, but required
189} header_Layout;
190
191typedef struct {
192 uint8_t signature [4];
193 uint8_t offset [4];
194 uint8_t size [4];
195} tag_Layout;
196
197static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
198 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
199}
200
201// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
202// use of the type is for the CHAD tag that stores exactly nine values.
203typedef struct {
204 uint8_t type [ 4];
205 uint8_t reserved [ 4];
206 uint8_t values [36];
207} sf32_Layout;
208
209bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
210 skcms_ICCTag tag;
211 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
212 return false;
213 }
214
215 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
216 return false;
217 }
218
219 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
220 const uint8_t* values = sf32Tag->values;
221 for (int r = 0; r < 3; ++r)
222 for (int c = 0; c < 3; ++c, values += 4) {
223 m->vals[r][c] = read_big_fixed(values);
224 }
225 return true;
226}
227
228// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
229// the type are for tags/data that store exactly one triple.
230typedef struct {
231 uint8_t type [4];
232 uint8_t reserved [4];
233 uint8_t X [4];
234 uint8_t Y [4];
235 uint8_t Z [4];
236} XYZ_Layout;
237
238static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
239 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
240 return false;
241 }
242
243 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
244
245 *x = read_big_fixed(xyzTag->X);
246 *y = read_big_fixed(xyzTag->Y);
247 *z = read_big_fixed(xyzTag->Z);
248 return true;
249}
250
251static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
252 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
253 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
254 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
255 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
256}
257
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000258static bool tf_is_valid(const skcms_TransferFunction* tf) {
259 // Reject obviously malformed inputs
260 if (!isfinitef_(tf->a + tf->b + tf->c + tf->d + tf->e + tf->f + tf->g)) {
261 return false;
262 }
263
264 // All of these parameters should be non-negative
265 if (tf->a < 0 || tf->c < 0 || tf->d < 0 || tf->g < 0) {
266 return false;
267 }
268
skia-autorollfe16a332019-08-20 19:44:54 +0000269 // It's rather _complex_ to raise a negative number to a fractional power tf->g.
270 if (tf->a * tf->d + tf->b < 0) {
271 return false;
272 }
273
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000274 return true;
275}
276
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000277typedef struct {
278 uint8_t type [4];
279 uint8_t reserved_a [4];
280 uint8_t function_type [2];
281 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000282 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 +0000283} para_Layout;
284
285static bool read_curve_para(const uint8_t* buf, uint32_t size,
286 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000287 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000288 return false;
289 }
290
291 const para_Layout* paraTag = (const para_Layout*)buf;
292
293 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
294 uint16_t function_type = read_big_u16(paraTag->function_type);
295 if (function_type > kGABCDEF) {
296 return false;
297 }
298
299 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 +0000300 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 +0000301 return false;
302 }
303
304 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000305 *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 +0000306 }
307
308 curve->table_entries = 0;
309 curve->parametric.a = 1.0f;
310 curve->parametric.b = 0.0f;
311 curve->parametric.c = 0.0f;
312 curve->parametric.d = 0.0f;
313 curve->parametric.e = 0.0f;
314 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000315 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000316
317 switch (function_type) {
318 case kGAB:
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);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000321 if (curve->parametric.a == 0) {
322 return false;
323 }
324 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
325 break;
326 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000327 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
328 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
329 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000330 if (curve->parametric.a == 0) {
331 return false;
332 }
333 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
334 curve->parametric.f = curve->parametric.e;
335 break;
336 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000337 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
338 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
339 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
340 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000341 break;
342 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000343 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
344 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
345 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
346 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
347 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
348 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000349 break;
350 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000351 return tf_is_valid(&curve->parametric);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000352}
353
354typedef struct {
355 uint8_t type [4];
356 uint8_t reserved [4];
357 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000358 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 +0000359} curv_Layout;
360
361static bool read_curve_curv(const uint8_t* buf, uint32_t size,
362 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000363 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000364 return false;
365 }
366
367 const curv_Layout* curvTag = (const curv_Layout*)buf;
368
369 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 +0000370 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 +0000371 return false;
372 }
373
374 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000375 *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 +0000376 }
377
378 if (value_count < 2) {
379 curve->table_entries = 0;
380 curve->parametric.a = 1.0f;
381 curve->parametric.b = 0.0f;
382 curve->parametric.c = 0.0f;
383 curve->parametric.d = 0.0f;
384 curve->parametric.e = 0.0f;
385 curve->parametric.f = 0.0f;
386 if (value_count == 0) {
387 // Empty tables are a shorthand for an identity curve
388 curve->parametric.g = 1.0f;
389 } else {
390 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000391 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 +0000392 }
393 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000394 curve->table_8 = nullptr;
395 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000396 curve->table_entries = value_count;
397 }
398
399 return true;
400}
401
402// 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 +0000403// 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 +0000404static bool read_curve(const uint8_t* buf, uint32_t size,
405 skcms_Curve* curve, uint32_t* curve_size) {
406 if (!buf || size < 4 || !curve) {
407 return false;
408 }
409
410 uint32_t type = read_big_u32(buf);
411 if (type == skcms_Signature_para) {
412 return read_curve_para(buf, size, curve, curve_size);
413 } else if (type == skcms_Signature_curv) {
414 return read_curve_curv(buf, size, curve, curve_size);
415 }
416
417 return false;
418}
419
420// mft1 and mft2 share a large chunk of data
421typedef struct {
422 uint8_t type [ 4];
423 uint8_t reserved_a [ 4];
424 uint8_t input_channels [ 1];
425 uint8_t output_channels [ 1];
426 uint8_t grid_points [ 1];
427 uint8_t reserved_b [ 1];
428 uint8_t matrix [36];
429} mft_CommonLayout;
430
431typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000432 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000433
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000434 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000435} mft1_Layout;
436
437typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000438 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000439
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000440 uint8_t input_table_entries [2];
441 uint8_t output_table_entries [2];
442 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000443} mft2_Layout;
444
445static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
446 // MFT matrices are applied before the first set of curves, but must be identity unless the
447 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
448 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
449 // field/flag.
450 a2b->matrix_channels = 0;
451
452 a2b->input_channels = mftTag->input_channels[0];
453 a2b->output_channels = mftTag->output_channels[0];
454
455 // We require exactly three (ie XYZ/Lab/RGB) output channels
456 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
457 return false;
458 }
459 // We require at least one, and no more than four (ie CMYK) input channels
460 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
461 return false;
462 }
463
464 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
465 a2b->grid_points[i] = mftTag->grid_points[0];
466 }
467 // The grid only makes sense with at least two points along each axis
468 if (a2b->grid_points[0] < 2) {
469 return false;
470 }
471
472 return true;
473}
474
475static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
476 uint32_t input_table_entries, uint32_t output_table_entries,
477 skcms_A2B* a2b) {
478 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
479 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
480 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
481
482 // [input|output]_channels are <= 4, so still no overflow
483 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
484 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
485
486 uint64_t grid_size = a2b->output_channels * byte_width;
487 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
488 grid_size *= a2b->grid_points[axis];
489 }
490
491 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
492 return false;
493 }
494
495 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
496 a2b->input_curves[i].table_entries = input_table_entries;
497 if (byte_width == 1) {
498 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 +0000499 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000500 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000501 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000502 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
503 }
504 }
505
506 if (byte_width == 1) {
507 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 +0000508 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000509 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000510 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000511 a2b->grid_16 = table_base + byte_len_all_input_tables;
512 }
513
514 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
515 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
516 a2b->output_curves[i].table_entries = output_table_entries;
517 if (byte_width == 1) {
518 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 +0000519 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000520 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000521 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000522 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
523 }
524 }
525
526 return true;
527}
528
529static 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 +0000530 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000531 return false;
532 }
533
534 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
535 if (!read_mft_common(mftTag->common, a2b)) {
536 return false;
537 }
538
539 uint32_t input_table_entries = 256;
540 uint32_t output_table_entries = 256;
541
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000542 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 +0000543 input_table_entries, output_table_entries, a2b);
544}
545
546static 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 +0000547 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000548 return false;
549 }
550
551 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
552 if (!read_mft_common(mftTag->common, a2b)) {
553 return false;
554 }
555
556 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
557 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
558
559 // ICC spec mandates that 2 <= table_entries <= 4096
560 if (input_table_entries < 2 || input_table_entries > 4096 ||
561 output_table_entries < 2 || output_table_entries > 4096) {
562 return false;
563 }
564
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000565 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 +0000566 input_table_entries, output_table_entries, a2b);
567}
568
569static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
570 uint32_t num_curves, skcms_Curve* curves) {
571 for (uint32_t i = 0; i < num_curves; ++i) {
572 if (curve_offset > size) {
573 return false;
574 }
575
576 uint32_t curve_bytes;
577 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
578 return false;
579 }
580
581 if (curve_bytes > UINT32_MAX - 3) {
582 return false;
583 }
584 curve_bytes = (curve_bytes + 3) & ~3U;
585
586 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
587 curve_offset = (uint32_t)new_offset_64;
588 if (new_offset_64 != curve_offset) {
589 return false;
590 }
591 }
592
593 return true;
594}
595
596typedef struct {
597 uint8_t type [ 4];
598 uint8_t reserved_a [ 4];
599 uint8_t input_channels [ 1];
600 uint8_t output_channels [ 1];
601 uint8_t reserved_b [ 2];
602 uint8_t b_curve_offset [ 4];
603 uint8_t matrix_offset [ 4];
604 uint8_t m_curve_offset [ 4];
605 uint8_t clut_offset [ 4];
606 uint8_t a_curve_offset [ 4];
607} mAB_Layout;
608
609typedef struct {
610 uint8_t grid_points [16];
611 uint8_t grid_byte_width [ 1];
612 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000613 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000614} mABCLUT_Layout;
615
616static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
617 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
618 return false;
619 }
620
621 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
622
623 a2b->input_channels = mABTag->input_channels[0];
624 a2b->output_channels = mABTag->output_channels[0];
625
626 // We require exactly three (ie XYZ/Lab/RGB) output channels
627 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
628 return false;
629 }
630 // We require no more than four (ie CMYK) input channels
631 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
632 return false;
633 }
634
635 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
636 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
637 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
638 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
639 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
640
641 // "B" curves must be present
642 if (0 == b_curve_offset) {
643 return false;
644 }
645
646 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
647 a2b->output_curves)) {
648 return false;
649 }
650
651 // "M" curves and Matrix must be used together
652 if (0 != m_curve_offset) {
653 if (0 == matrix_offset) {
654 return false;
655 }
656 a2b->matrix_channels = a2b->output_channels;
657 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
658 a2b->matrix_curves)) {
659 return false;
660 }
661
662 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
663 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
664 return false;
665 }
666 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
667 const uint8_t* mtx_buf = tag->buf + matrix_offset;
668 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
669 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
670 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
671 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
672 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
673 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
674 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
675 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
676 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
677 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
678 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
679 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
680 } else {
681 if (0 != matrix_offset) {
682 return false;
683 }
684 a2b->matrix_channels = 0;
685 }
686
687 // "A" curves and CLUT must be used together
688 if (0 != a_curve_offset) {
689 if (0 == clut_offset) {
690 return false;
691 }
692 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
693 a2b->input_curves)) {
694 return false;
695 }
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)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000698 return false;
699 }
700 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
701
702 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000703 a2b->grid_8 = clut->variable;
704 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000705 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000706 a2b->grid_8 = nullptr;
707 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000708 } else {
709 return false;
710 }
711
712 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
713 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
714 a2b->grid_points[i] = clut->grid_points[i];
715 // The grid only makes sense with at least two points along each axis
716 if (a2b->grid_points[i] < 2) {
717 return false;
718 }
719 grid_size *= a2b->grid_points[i];
720 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000721 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 +0000722 return false;
723 }
724 } else {
725 if (0 != clut_offset) {
726 return false;
727 }
728
729 // If there is no CLUT, the number of input and output channels must match
730 if (a2b->input_channels != a2b->output_channels) {
731 return false;
732 }
733
734 // Zero out the number of input channels to signal that we're skipping this stage
735 a2b->input_channels = 0;
736 }
737
738 return true;
739}
740
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000741// If you pass f, we'll fit a possibly-non-zero value for *f.
742// If you pass nullptr, we'll assume you want *f to be treated as zero.
743static int fit_linear(const skcms_Curve* curve, int N, float tol,
744 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000745 assert(N > 1);
746 // We iteratively fit the first points to the TF's linear piece.
747 // We want the cx + f line to pass through the first and last points we fit exactly.
748 //
749 // As we walk along the points we find the minimum and maximum slope of the line before the
750 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
751 // emtpy, when we definitely can't add any more points.
752 //
753 // Some points' error intervals may intersect the running interval but not lie fully
754 // within it. So we keep track of the last point we saw that is a valid end point candidate,
755 // and once the search is done, back up to build the line through *that* point.
756 const float dx = 1.0f / (N - 1);
757
758 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000759
760 float f_zero = 0.0f;
761 if (f) {
762 *f = eval_curve(curve, 0);
763 } else {
764 f = &f_zero;
765 }
766
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000767
768 float slope_min = -INFINITY_;
769 float slope_max = +INFINITY_;
770 for (int i = 1; i < N; ++i) {
771 float x = i * dx;
772 float y = eval_curve(curve, x);
773
774 float slope_max_i = (y + tol - *f) / x,
775 slope_min_i = (y - tol - *f) / x;
776 if (slope_max_i < slope_min || slope_max < slope_min_i) {
777 // Slope intervals would no longer overlap.
778 break;
779 }
780 slope_max = fminf_(slope_max, slope_max_i);
781 slope_min = fmaxf_(slope_min, slope_min_i);
782
783 float cur_slope = (y - *f) / x;
784 if (slope_min <= cur_slope && cur_slope <= slope_max) {
785 lin_points = i + 1;
786 *c = cur_slope;
787 }
788 }
789
790 // Set D to the last point that met our tolerance.
791 *d = (lin_points - 1) * dx;
792 return lin_points;
793}
794
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000795static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
796 bool ok = false;
797 if (tag->type == skcms_Signature_mft1) {
798 ok = read_tag_mft1(tag, a2b);
799 } else if (tag->type == skcms_Signature_mft2) {
800 ok = read_tag_mft2(tag, a2b);
801 } else if (tag->type == skcms_Signature_mAB) {
802 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
803 }
804 if (!ok) {
805 return false;
806 }
807
808 // Detect and canonicalize identity tables.
809 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000810 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
811 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
812 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
813 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
814 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
815 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
816 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
817 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
818 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
819 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000820 };
821
822 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
823 skcms_Curve* curve = curves[i];
824
825 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
826 int N = (int)curve->table_entries;
827
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000828 float c = 0.0f, d = 0.0f, f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000829 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 +0000830 && c == 1.0f
831 && f == 0.0f) {
832 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000833 curve->table_8 = nullptr;
834 curve->table_16 = nullptr;
835 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 +0000836 }
837 }
838 }
839
840 return true;
841}
842
843void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
844 if (!profile || !profile->buffer || !tag) { return; }
845 if (idx > profile->tag_count) { return; }
846 const tag_Layout* tags = get_tag_table(profile);
847 tag->signature = read_big_u32(tags[idx].signature);
848 tag->size = read_big_u32(tags[idx].size);
849 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
850 tag->type = read_big_u32(tag->buf);
851}
852
853bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
854 if (!profile || !profile->buffer || !tag) { return false; }
855 const tag_Layout* tags = get_tag_table(profile);
856 for (uint32_t i = 0; i < profile->tag_count; ++i) {
857 if (read_big_u32(tags[i].signature) == sig) {
858 tag->signature = sig;
859 tag->size = read_big_u32(tags[i].size);
860 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
861 tag->type = read_big_u32(tag->buf);
862 return true;
863 }
864 }
865 return false;
866}
867
868static bool usable_as_src(const skcms_ICCProfile* profile) {
869 return profile->has_A2B
870 || (profile->has_trc && profile->has_toXYZD50);
871}
872
873bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
874 assert(SAFE_SIZEOF(header_Layout) == 132);
875
876 if (!profile) {
877 return false;
878 }
879 memset(profile, 0, SAFE_SIZEOF(*profile));
880
881 if (len < SAFE_SIZEOF(header_Layout)) {
882 return false;
883 }
884
885 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000886 const header_Layout* header = (const header_Layout*)buf;
887 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000888 profile->size = read_big_u32(header->size);
889 uint32_t version = read_big_u32(header->version);
890 profile->data_color_space = read_big_u32(header->data_color_space);
891 profile->pcs = read_big_u32(header->pcs);
892 uint32_t signature = read_big_u32(header->signature);
893 float illuminant_X = read_big_fixed(header->illuminant_X);
894 float illuminant_Y = read_big_fixed(header->illuminant_Y);
895 float illuminant_Z = read_big_fixed(header->illuminant_Z);
896 profile->tag_count = read_big_u32(header->tag_count);
897
898 // Validate signature, size (smaller than buffer, large enough to hold tag table),
899 // and major version
900 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
901 if (signature != skcms_Signature_acsp ||
902 profile->size > len ||
903 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
904 (version >> 24) > 4) {
905 return false;
906 }
907
908 // Validate that illuminant is D50 white
909 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
910 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
911 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
912 return false;
913 }
914
915 // Validate that all tag entries have sane offset + size
916 const tag_Layout* tags = get_tag_table(profile);
917 for (uint32_t i = 0; i < profile->tag_count; ++i) {
918 uint32_t tag_offset = read_big_u32(tags[i].offset);
919 uint32_t tag_size = read_big_u32(tags[i].size);
920 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
921 if (tag_size < 4 || tag_end > profile->size) {
922 return false;
923 }
924 }
925
926 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
927 return false;
928 }
929
930 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
931
932 // Pre-parse commonly used tags.
933 skcms_ICCTag kTRC;
934 if (profile->data_color_space == skcms_Signature_Gray &&
935 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000936 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 +0000937 // Malformed tag
938 return false;
939 }
940 profile->trc[1] = profile->trc[0];
941 profile->trc[2] = profile->trc[0];
942 profile->has_trc = true;
943
944 if (pcs_is_xyz) {
945 profile->toXYZD50.vals[0][0] = illuminant_X;
946 profile->toXYZD50.vals[1][1] = illuminant_Y;
947 profile->toXYZD50.vals[2][2] = illuminant_Z;
948 profile->has_toXYZD50 = true;
949 }
950 } else {
951 skcms_ICCTag rTRC, gTRC, bTRC;
952 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
953 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
954 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000955 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
956 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
957 !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 +0000958 // Malformed TRC tags
959 return false;
960 }
961 profile->has_trc = true;
962 }
963
964 skcms_ICCTag rXYZ, gXYZ, bXYZ;
965 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
966 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
967 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
968 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
969 // Malformed XYZ tags
970 return false;
971 }
972 profile->has_toXYZD50 = true;
973 }
974 }
975
976 skcms_ICCTag a2b_tag;
977
978 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
979 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
980 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
981 // and all our known users are thinking exclusively in terms of relative colormetric.
982 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
983 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
984 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
985 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
986 // Malformed A2B tag
987 return false;
988 }
989 profile->has_A2B = true;
990 break;
991 }
992 }
993
994 return usable_as_src(profile);
995}
996
997
998const skcms_ICCProfile* skcms_sRGB_profile() {
999 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001000 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001001
1002 0, // size, moot here
1003 skcms_Signature_RGB, // data_color_space
1004 skcms_Signature_XYZ, // pcs
1005 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001006
1007 // We choose to represent sRGB with its canonical transfer function,
1008 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001009 true, // has_trc, followed by the 3 trc curves
1010 {
1011 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1012 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1013 {{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 +00001014 },
1015
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001016 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1017 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001018 { 0.436065674f, 0.385147095f, 0.143066406f },
1019 { 0.222488403f, 0.716873169f, 0.060607910f },
1020 { 0.013916016f, 0.097076416f, 0.714096069f },
1021 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001022
1023 false, // has_A2B, followed by a2b itself which we don't care about.
1024 {
1025 0,
1026 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001027 {{0, {0,0, 0,0,0,0,0}}},
1028 {{0, {0,0, 0,0,0,0,0}}},
1029 {{0, {0,0, 0,0,0,0,0}}},
1030 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001031 },
1032 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001033 nullptr,
1034 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001035
1036 0,
1037 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001038 {{0, {0,0, 0,0,0,0,0}}},
1039 {{0, {0,0, 0,0,0,0,0}}},
1040 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001041 },
1042 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001043 { 0,0,0,0 },
1044 { 0,0,0,0 },
1045 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001046 }},
1047
1048 0,
1049 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001050 {{0, {0,0, 0,0,0,0,0}}},
1051 {{0, {0,0, 0,0,0,0,0}}},
1052 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001053 },
1054 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001055 };
1056 return &sRGB_profile;
1057}
1058
1059const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001060 // 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 +00001061 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001062 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001063
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001064 0, // size, moot here
1065 skcms_Signature_RGB, // data_color_space
1066 skcms_Signature_XYZ, // pcs
1067 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001068
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001069 true, // has_trc, followed by the 3 trc curves
1070 {
1071 {{0, {1,1, 0,0,0,0,0}}},
1072 {{0, {1,1, 0,0,0,0,0}}},
1073 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001074 },
1075
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001076 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1077 {{
1078 { 1,0,0 },
1079 { 0,1,0 },
1080 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001081 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001082
1083 false, // has_A2B, followed by a2b itself which we don't care about.
1084 {
1085 0,
1086 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001087 {{0, {0,0, 0,0,0,0,0}}},
1088 {{0, {0,0, 0,0,0,0,0}}},
1089 {{0, {0,0, 0,0,0,0,0}}},
1090 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001091 },
1092 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001093 nullptr,
1094 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001095
1096 0,
1097 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001098 {{0, {0,0, 0,0,0,0,0}}},
1099 {{0, {0,0, 0,0,0,0,0}}},
1100 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001101 },
1102 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001103 { 0,0,0,0 },
1104 { 0,0,0,0 },
1105 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001106 }},
1107
1108 0,
1109 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001110 {{0, {0,0, 0,0,0,0,0}}},
1111 {{0, {0,0, 0,0,0,0,0}}},
1112 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001113 },
1114 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001115 };
1116
1117 return &XYZD50_profile;
1118}
1119
1120const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1121 return &skcms_sRGB_profile()->trc[0].parametric;
1122}
1123
1124const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1125 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001126 {0.416666657f, 1.137283325f, -0.0f, 12.920000076f, 0.003130805f, -0.054969788f, -0.0f};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001127 return &sRGB_inv;
1128}
1129
1130const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1131 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1132 return &identity;
1133}
1134
1135const uint8_t skcms_252_random_bytes[] = {
1136 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1137 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1138 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1139 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1140 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1141 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1142 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1143 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1144 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1145 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1146 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1147 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1148 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1149 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1150 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1151 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1152};
1153
1154bool skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile* A, const skcms_ICCProfile* B) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001155 // Test for exactly equal profiles first.
1156 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1157 return true;
1158 }
1159
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001160 // For now this is the essentially the same strategy we use in test_only.c
1161 // for our skcms_Transform() smoke tests:
1162 // 1) transform A to XYZD50
1163 // 2) transform B to XYZD50
1164 // 3) return true if they're similar enough
1165 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1166
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001167 // skcms_252_random_bytes are 252 of a random shuffle of all possible bytes.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001168 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1169
1170 if (A->data_color_space != B->data_color_space) {
1171 return false;
1172 }
1173
1174 // Interpret as RGB_888 if data color space is RGB or GRAY, RGBA_8888 if CMYK.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001175 // TODO: working with RGBA_8888 either way is probably fastest.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001176 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1177 size_t npixels = 84;
1178 if (A->data_color_space == skcms_Signature_CMYK) {
1179 fmt = skcms_PixelFormat_RGBA_8888;
1180 npixels = 63;
1181 }
1182
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001183 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1184 // use pre-canned results and skip that skcms_Transform() call?
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001185 uint8_t dstA[252],
1186 dstB[252];
1187 if (!skcms_Transform(
1188 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1189 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1190 npixels)) {
1191 return false;
1192 }
1193 if (!skcms_Transform(
1194 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1195 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1196 npixels)) {
1197 return false;
1198 }
1199
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001200 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001201 for (size_t i = 0; i < 252; i++) {
1202 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1203 return false;
1204 }
1205 }
1206 return true;
1207}
1208
1209bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1210 const skcms_TransferFunction* inv_tf) {
1211 if (!profile || !profile->has_trc) {
1212 return false;
1213 }
1214
1215 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1216 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1217 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1218}
1219
1220static bool is_zero_to_one(float x) {
1221 return 0 <= x && x <= 1;
1222}
1223
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001224typedef struct { float vals[3]; } skcms_Vector3;
1225
1226static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1227 skcms_Vector3 dst = {{0,0,0}};
1228 for (int row = 0; row < 3; ++row) {
1229 dst.vals[row] = m->vals[row][0] * v->vals[0]
1230 + m->vals[row][1] * v->vals[1]
1231 + m->vals[row][2] * v->vals[2];
1232 }
1233 return dst;
1234}
1235
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001236bool skcms_PrimariesToXYZD50(float rx, float ry,
1237 float gx, float gy,
1238 float bx, float by,
1239 float wx, float wy,
1240 skcms_Matrix3x3* toXYZD50) {
1241 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1242 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1243 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1244 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1245 !toXYZD50) {
1246 return false;
1247 }
1248
1249 // First, we need to convert xy values (primaries) to XYZ.
1250 skcms_Matrix3x3 primaries = {{
1251 { rx, gx, bx },
1252 { ry, gy, by },
1253 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1254 }};
1255 skcms_Matrix3x3 primaries_inv;
1256 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1257 return false;
1258 }
1259
1260 // Assumes that Y is 1.0f.
1261 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 +00001262 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001263
1264 skcms_Matrix3x3 toXYZ = {{
1265 { XYZ.vals[0], 0, 0 },
1266 { 0, XYZ.vals[1], 0 },
1267 { 0, 0, XYZ.vals[2] },
1268 }};
1269 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1270
1271 // Now convert toXYZ matrix to toXYZD50.
1272 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1273
1274 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1275 // the matrices below. The Bradford method is used by Adobe and is widely considered
1276 // to be the best.
1277 skcms_Matrix3x3 xyz_to_lms = {{
1278 { 0.8951f, 0.2664f, -0.1614f },
1279 { -0.7502f, 1.7135f, 0.0367f },
1280 { 0.0389f, -0.0685f, 1.0296f },
1281 }};
1282 skcms_Matrix3x3 lms_to_xyz = {{
1283 { 0.9869929f, -0.1470543f, 0.1599627f },
1284 { 0.4323053f, 0.5183603f, 0.0492912f },
1285 { -0.0085287f, 0.0400428f, 0.9684867f },
1286 }};
1287
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001288 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1289 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001290
1291 skcms_Matrix3x3 DXtoD50 = {{
1292 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1293 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1294 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1295 }};
1296 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1297 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1298
1299 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1300 return true;
1301}
1302
1303
1304bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1305 double a00 = src->vals[0][0],
1306 a01 = src->vals[1][0],
1307 a02 = src->vals[2][0],
1308 a10 = src->vals[0][1],
1309 a11 = src->vals[1][1],
1310 a12 = src->vals[2][1],
1311 a20 = src->vals[0][2],
1312 a21 = src->vals[1][2],
1313 a22 = src->vals[2][2];
1314
1315 double b0 = a00*a11 - a01*a10,
1316 b1 = a00*a12 - a02*a10,
1317 b2 = a01*a12 - a02*a11,
1318 b3 = a20,
1319 b4 = a21,
1320 b5 = a22;
1321
1322 double determinant = b0*b5
1323 - b1*b4
1324 + b2*b3;
1325
1326 if (determinant == 0) {
1327 return false;
1328 }
1329
1330 double invdet = 1.0 / determinant;
1331 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1332 return false;
1333 }
1334
1335 b0 *= invdet;
1336 b1 *= invdet;
1337 b2 *= invdet;
1338 b3 *= invdet;
1339 b4 *= invdet;
1340 b5 *= invdet;
1341
1342 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1343 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1344 dst->vals[2][0] = (float)( + b2 );
1345 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1346 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1347 dst->vals[2][1] = (float)( - b1 );
1348 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1349 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1350 dst->vals[2][2] = (float)( + b0 );
1351
1352 for (int r = 0; r < 3; ++r)
1353 for (int c = 0; c < 3; ++c) {
1354 if (!isfinitef_(dst->vals[r][c])) {
1355 return false;
1356 }
1357 }
1358 return true;
1359}
1360
1361skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1362 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1363 for (int r = 0; r < 3; r++)
1364 for (int c = 0; c < 3; c++) {
1365 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1366 + A->vals[r][1] * B->vals[1][c]
1367 + A->vals[r][2] * B->vals[2][c];
1368 }
1369 return m;
1370}
1371
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001372#if defined(__clang__) || defined(__GNUC__)
1373 #define small_memcpy __builtin_memcpy
1374#else
1375 #define small_memcpy memcpy
1376#endif
1377
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001378static float log2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001379 // The first approximation of log2(x) is its exponent 'e', minus 127.
1380 int32_t bits;
1381 small_memcpy(&bits, &x, sizeof(bits));
1382
1383 float e = (float)bits * (1.0f / (1<<23));
1384
1385 // If we use the mantissa too we can refine the error signficantly.
1386 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
1387 float m;
1388 small_memcpy(&m, &m_bits, sizeof(m));
1389
1390 return (e - 124.225514990f
1391 - 1.498030302f*m
1392 - 1.725879990f/(0.3520887068f + m));
1393}
1394
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001395static float exp2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001396 float fract = x - floorf_(x);
1397
1398 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
1399 - 1.490129070f*fract
1400 + 27.728023300f/(4.84252568f - fract));
skia-autoroll03fccf42019-08-26 19:38:13 +00001401
1402 // Before we cast fbits to int32_t, check for out of range values to pacify UBSAN.
1403 // INT_MAX is not exactly representable as a float, so exclude it as effectively infinite.
1404 // INT_MIN is a power of 2 and exactly representable as a float, so it's fine.
1405 if (fbits >= (float)INT_MAX) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001406 return INFINITY_;
skia-autoroll03fccf42019-08-26 19:38:13 +00001407 } else if (fbits < (float)INT_MIN) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001408 return -INFINITY_;
1409 }
skia-autoroll03fccf42019-08-26 19:38:13 +00001410
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001411 int32_t bits = (int32_t)fbits;
1412 small_memcpy(&x, &bits, sizeof(x));
1413 return x;
1414}
1415
1416float powf_(float x, float y) {
skia-autorollfe16a332019-08-20 19:44:54 +00001417 assert (x >= 0);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001418 return (x == 0) || (x == 1) ? x
1419 : exp2f_(log2f_(x) * y);
1420}
1421
1422float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
1423 float sign = x < 0 ? -1.0f : 1.0f;
1424 x *= sign;
1425
1426 return sign * (x < tf->d ? tf->c * x + tf->f
1427 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
1428}
1429
skia-autorollacd6e012019-01-08 14:10:52 +00001430#if defined(__clang__)
1431 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by tf_is_valid() on the way out.
1432#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001433bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001434 if (!tf_is_valid(src)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001435 return false;
1436 }
1437
skia-autorolld0b577f2019-01-07 19:46:57 +00001438 // We're inverting this function, solving for x in terms of y.
1439 // y = (cx + f) x < d
1440 // (ax + b)^g + e x ≥ d
1441 // The inverse of this function can be expressed in the same piecewise form.
1442 skcms_TransferFunction inv = {0,0,0,0,0,0,0};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001443
skia-autorolld0b577f2019-01-07 19:46:57 +00001444 // We'll start by finding the new threshold inv.d.
1445 // In principle we should be able to find that by solving for y at x=d from either side.
1446 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1447 float d_l = src->c * src->d + src->f,
1448 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1449 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001450 return false;
1451 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001452 inv.d = d_l; // TODO(mtklein): better in practice to choose d_r?
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001453
skia-autorolld0b577f2019-01-07 19:46:57 +00001454 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1455 if (inv.d > 0) {
1456 // Inverting the linear section is pretty straightfoward:
1457 // y = cx + f
1458 // y - f = cx
1459 // (1/c)y - f/c = x
1460 inv.c = 1.0f/src->c;
1461 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001462 }
1463
skia-autorolld0b577f2019-01-07 19:46:57 +00001464 // The interesting part is inverting the nonlinear section:
1465 // y = (ax + b)^g + e.
1466 // y - e = (ax + b)^g
1467 // (y - e)^1/g = ax + b
1468 // (y - e)^1/g - b = ax
1469 // (1/a)(y - e)^1/g - b/a = x
1470 //
1471 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1472 // let k = (1/a)^g
1473 // (1/a)( y - e)^1/g - b/a = x
1474 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001475
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001476 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001477 inv.g = 1.0f / src->g;
1478 inv.a = k;
1479 inv.b = -k * src->e;
1480 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001481
skia-autorollfe16a332019-08-20 19:44:54 +00001482 // We need to enforce the same constraints here that we do when fitting a curve,
1483 // a >= 0 and ad+b >= 0. These constraints are checked by tf_is_valid(), so they're true
skia-autorollbf388232019-08-21 14:17:54 +00001484 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001485
skia-autorollbf388232019-08-21 14:17:54 +00001486 // Just like when fitting the curve, there's really no way to rescue a < 0.
1487 if (inv.a < 0) {
1488 return false;
1489 }
1490 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001491 if (inv.a * inv.d + inv.b < 0) {
1492 inv.b = -inv.a * inv.d;
1493 }
skia-autorollbf388232019-08-21 14:17:54 +00001494
skia-autorolla0ed0702019-08-23 16:49:54 +00001495 // That should usually make tf_is_valid(&inv) true, but there are a couple situations
1496 // where we might still fail here, like non-finite parameter values.
1497 if (!tf_is_valid(&inv)) {
1498 return false;
1499 }
1500
skia-autorollbf388232019-08-21 14:17:54 +00001501 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001502 assert (inv.a * inv.d + inv.b >= 0);
1503
skia-autorolla7b28742019-01-09 18:35:46 +00001504 // Now in principle we're done.
skia-autorollad10df62019-08-21 15:14:54 +00001505 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
1506 // e or f of the inverse, depending on which segment contains src(1.0f).
1507 float s = skcms_TransferFunction_eval(src, 1.0f);
skia-autorolla0ed0702019-08-23 16:49:54 +00001508 if (!isfinitef_(s)) {
1509 return false;
1510 }
1511
skia-autorollad10df62019-08-21 15:14:54 +00001512 float sign = s < 0 ? -1.0f : 1.0f;
1513 s *= sign;
1514 if (s < inv.d) {
1515 inv.f = 1.0f - sign * inv.c * s;
1516 } else {
1517 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
1518 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001519
skia-autorolld0b577f2019-01-07 19:46:57 +00001520 *dst = inv;
1521 return tf_is_valid(dst);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001522}
1523
1524// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1525
1526// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1527//
1528// tf(x) = cx + f x < d
1529// tf(x) = (ax + b)^g + e x ≥ d
1530//
1531// When fitting, we add the additional constraint that both pieces meet at d:
1532//
1533// cd + f = (ad + b)^g + e
1534//
1535// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1536//
1537// tf(x) = cx + f x < d
1538// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1539//
1540// Our overall strategy is then:
1541// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001542// - 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 +00001543// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001544// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1545// (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 +00001546// Return the parameters with least maximum error.
1547//
1548// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1549// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1550//
1551// let y = Table(x)
1552// r(x) = x - f_inv(y)
1553//
1554// ∂r/∂g = ln(ay + b)*(ay + b)^g
1555// - ln(ad + b)*(ad + b)^g
1556// ∂r/∂a = yg(ay + b)^(g-1)
1557// - dg(ad + b)^(g-1)
1558// ∂r/∂b = g(ay + b)^(g-1)
1559// - g(ad + b)^(g-1)
1560
1561// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1562// and fill out the gradient of the residual into dfdP.
1563static float rg_nonlinear(float x,
1564 const skcms_Curve* curve,
1565 const skcms_TransferFunction* tf,
1566 const float P[3],
1567 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001568 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001569
1570 const float g = P[0], a = P[1], b = P[2],
1571 c = tf->c, d = tf->d, f = tf->f;
1572
1573 const float Y = fmaxf_(a*y + b, 0.0f),
1574 D = a*d + b;
1575 assert (D >= 0);
1576
1577 // The gradient.
1578 dfdP[0] = 0.69314718f*log2f_(Y)*powf_(Y, g)
1579 - 0.69314718f*log2f_(D)*powf_(D, g);
1580 dfdP[1] = y*g*powf_(Y, g-1)
1581 - d*g*powf_(D, g-1);
1582 dfdP[2] = g*powf_(Y, g-1)
1583 - g*powf_(D, g-1);
1584
1585 // The residual.
1586 const float f_inv = powf_(Y, g)
1587 - powf_(D, g)
1588 + c*d + f;
1589 return x - f_inv;
1590}
1591
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001592static bool gauss_newton_step(const skcms_Curve* curve,
1593 const skcms_TransferFunction* tf,
1594 float P[3],
1595 float x0, float dx, int N) {
1596 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1597 //
1598 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1599 // where r(P) is the residual vector
1600 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1601 //
1602 // Let's review the shape of each of these expressions:
1603 // r(P) is [N x 1], a column vector with one entry per value of x tested
1604 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1605 // Jf^T is [3 x N], the transpose of Jf
1606 //
1607 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1608 // and so is its inverse (Jf^T Jf)^-1
1609 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1610 //
1611 // Our implementation strategy to get to the final ∆P is
1612 // 1) evaluate Jf^T Jf, call that lhs
1613 // 2) evaluate Jf^T r(P), call that rhs
1614 // 3) invert lhs
1615 // 4) multiply inverse lhs by rhs
1616 //
1617 // This is a friendly implementation strategy because we don't have to have any
1618 // buffers that scale with N, and equally nice don't have to perform any matrix
1619 // operations that are variable size.
1620 //
1621 // Other implementation strategies could trade this off, e.g. evaluating the
1622 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1623 // the residuals. That would probably require implementing singular value
1624 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1625 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1626 // possibility of this gauss_newton_step() function ever failing.
1627
1628 // 0) start off with lhs and rhs safely zeroed.
1629 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1630 skcms_Vector3 rhs = { {0,0,0} };
1631
1632 // 1,2) evaluate lhs and evaluate rhs
1633 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1634 // so we'll have to update lhs and rhs at the same time.
1635 for (int i = 0; i < N; i++) {
1636 float x = x0 + i*dx;
1637
1638 float dfdP[3] = {0,0,0};
1639 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1640
1641 for (int r = 0; r < 3; r++) {
1642 for (int c = 0; c < 3; c++) {
1643 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1644 }
1645 rhs.vals[r] += dfdP[r] * resid;
1646 }
1647 }
1648
1649 // If any of the 3 P parameters are unused, this matrix will be singular.
1650 // Detect those cases and fix them up to indentity instead, so we can invert.
1651 for (int k = 0; k < 3; k++) {
1652 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1653 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1654 lhs.vals[k][k] = 1;
1655 }
1656 }
1657
1658 // 3) invert lhs
1659 skcms_Matrix3x3 lhs_inv;
1660 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1661 return false;
1662 }
1663
1664 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001665 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001666 P[0] += dP.vals[0];
1667 P[1] += dP.vals[1];
1668 P[2] += dP.vals[2];
1669 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1670}
1671
1672
1673// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1674static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1675 float P[3] = { tf->g, tf->a, tf->b };
1676
1677 // No matter where we start, dx should always represent N even steps from 0 to 1.
1678 const float dx = 1.0f / (N-1);
1679
skia-autorolld9718822019-08-23 18:16:54 +00001680 // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
1681 for (int j = 0; j < 2; j++) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001682 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1683 // We don't really know how to fix up a if it goes negative.
1684 if (P[1] < 0) {
1685 return false;
1686 }
1687 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1688 if (P[1] * tf->d + P[2] < 0) {
1689 P[2] = -P[1] * tf->d;
1690 }
1691 assert (P[1] >= 0 &&
1692 P[1] * tf->d + P[2] >= 0);
1693
1694 if (!gauss_newton_step(curve, tf,
1695 P,
1696 L*dx, dx, N-L)) {
1697 return false;
1698 }
1699 }
1700
1701 // We need to apply our fixups one last time
1702 if (P[1] < 0) {
1703 return false;
1704 }
1705 if (P[1] * tf->d + P[2] < 0) {
1706 P[2] = -P[1] * tf->d;
1707 }
1708
skia-autorollfe16a332019-08-20 19:44:54 +00001709 assert (P[1] >= 0 &&
1710 P[1] * tf->d + P[2] >= 0);
1711
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001712 tf->g = P[0];
1713 tf->a = P[1];
1714 tf->b = P[2];
1715 tf->e = tf->c*tf->d + tf->f
1716 - powf_(tf->a*tf->d + tf->b, tf->g);
1717 return true;
1718}
1719
1720bool skcms_ApproximateCurve(const skcms_Curve* curve,
1721 skcms_TransferFunction* approx,
1722 float* max_error) {
1723 if (!curve || !approx || !max_error) {
1724 return false;
1725 }
1726
1727 if (curve->table_entries == 0) {
1728 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1729 return false;
1730 }
1731
1732 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1733 // We need at least two points, and must put some reasonable cap on the maximum number.
1734 return false;
1735 }
1736
1737 int N = (int)curve->table_entries;
1738 const float dx = 1.0f / (N - 1);
1739
1740 *max_error = INFINITY_;
1741 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1742 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1743 skcms_TransferFunction tf,
1744 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001745
1746 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1747 tf.f = 0.0f;
1748 int L = fit_linear(curve, N, kTolerances[t], &tf.c, &tf.d);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001749
1750 if (L == N) {
1751 // If the entire data set was linear, move the coefficients to the nonlinear portion
1752 // with G == 1. This lets use a canonical representation with d == 0.
1753 tf.g = 1;
1754 tf.a = tf.c;
1755 tf.b = tf.f;
1756 tf.c = tf.d = tf.e = tf.f = 0;
1757 } else if (L == N - 1) {
1758 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1759 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001760 tf.a = (eval_curve(curve, (N-1)*dx) -
1761 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001762 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001763 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001764 - tf.a * (N-2)*dx;
1765 tf.e = 0;
1766 } else {
1767 // Start by guessing a gamma-only curve through the midpoint.
1768 int mid = (L + N) / 2;
1769 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001770 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001771 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001772 tf.a = 1;
1773 tf.b = 0;
1774 tf.e = tf.c*tf.d + tf.f
1775 - powf_(tf.a*tf.d + tf.b, tf.g);
1776
1777
1778 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1779 !fit_nonlinear(curve, L,N, &tf_inv)) {
1780 continue;
1781 }
1782
1783 // We fit tf_inv, so calculate tf to keep in sync.
1784 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1785 continue;
1786 }
1787 }
1788
1789 // We find our error by roundtripping the table through tf_inv.
1790 //
1791 // (The most likely use case for this approximation is to be inverted and
1792 // used as the transfer function for a destination color space.)
1793 //
1794 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1795 // invertible, so re-verify that here (and use the new inverse for testing).
1796 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1797 continue;
1798 }
1799
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001800 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001801 if (*max_error > err) {
1802 *max_error = err;
1803 *approx = tf;
1804 }
1805 }
1806 return isfinitef_(*max_error);
1807}
1808
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001809// ~~~~ Impl. of skcms_Transform() ~~~~
1810
1811typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001812 Op_load_a8,
1813 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001814 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001815 Op_load_4444,
1816 Op_load_565,
1817 Op_load_888,
1818 Op_load_8888,
1819 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001820 Op_load_161616LE,
1821 Op_load_16161616LE,
1822 Op_load_161616BE,
1823 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001824 Op_load_hhh,
1825 Op_load_hhhh,
1826 Op_load_fff,
1827 Op_load_ffff,
1828
1829 Op_swap_rb,
1830 Op_clamp,
1831 Op_invert,
1832 Op_force_opaque,
1833 Op_premul,
1834 Op_unpremul,
1835 Op_matrix_3x3,
1836 Op_matrix_3x4,
1837 Op_lab_to_xyz,
1838
1839 Op_tf_r,
1840 Op_tf_g,
1841 Op_tf_b,
1842 Op_tf_a,
1843
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001844 Op_table_r,
1845 Op_table_g,
1846 Op_table_b,
1847 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001848
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001849 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001850
1851 Op_store_a8,
1852 Op_store_g8,
1853 Op_store_4444,
1854 Op_store_565,
1855 Op_store_888,
1856 Op_store_8888,
1857 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001858 Op_store_161616LE,
1859 Op_store_16161616LE,
1860 Op_store_161616BE,
1861 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001862 Op_store_hhh,
1863 Op_store_hhhh,
1864 Op_store_fff,
1865 Op_store_ffff,
1866} Op;
1867
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001868#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001869 template <int N, typename T> using Vec = T __attribute__((ext_vector_type(N)));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001870#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001871 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1872 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1873 template <int N, typename T>
1874 struct VecHelper { typedef T __attribute__((vector_size(N*sizeof(T)))) V; };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001875
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001876 template <int N, typename T> using Vec = typename VecHelper<N,T>::V;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001877#endif
1878
1879// First, instantiate our default exec_ops() implementation using the default compiliation target.
1880
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001881namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00001882#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
1883 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001884 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00001885 template <typename T> using V = T;
1886 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001887#elif defined(__AVX512F__)
1888 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001889 template <typename T> using V = Vec<N,T>;
1890 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001891#elif defined(__AVX__)
1892 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001893 template <typename T> using V = Vec<N,T>;
1894 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00001895#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
1896 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001897 template <typename T> using V = Vec<N,T>;
1898 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001899#else
1900 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00001901 template <typename T> using V = Vec<N,T>;
1902 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001903#endif
1904
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001905 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001906 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001907}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001908
1909// Now, instantiate any other versions of run_program() we may want for runtime detection.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00001910#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00001911 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00001912 (( defined(__clang__) && __clang_major__ >= 5) || \
1913 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00001914 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001915
skia-autorollba6087c2019-04-09 13:57:02 +00001916 #if !defined(__AVX2__)
1917 #if defined(__clang__)
1918 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
1919 #elif defined(__GNUC__)
1920 #pragma GCC push_options
1921 #pragma GCC target("avx2,f16c")
1922 #endif
1923
1924 namespace hsw {
1925 #define USING_AVX
1926 #define USING_AVX_F16C
1927 #define USING_AVX2
1928 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001929 template <typename T> using V = Vec<N,T>;
1930 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00001931
1932 #include "src/Transform_inl.h"
1933
1934 // src/Transform_inl.h will undefine USING_* for us.
1935 #undef N
1936 }
1937
1938 #if defined(__clang__)
1939 #pragma clang attribute pop
1940 #elif defined(__GNUC__)
1941 #pragma GCC pop_options
1942 #endif
1943
1944 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001945 #endif
1946
skia-autorollba6087c2019-04-09 13:57:02 +00001947 #if !defined(__AVX512F__)
1948 #if defined(__clang__)
1949 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
1950 #elif defined(__GNUC__)
1951 #pragma GCC push_options
1952 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
1953 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001954
skia-autorollba6087c2019-04-09 13:57:02 +00001955 namespace skx {
1956 #define USING_AVX512F
1957 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001958 template <typename T> using V = Vec<N,T>;
1959 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001960
skia-autorollba6087c2019-04-09 13:57:02 +00001961 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001962
skia-autorollba6087c2019-04-09 13:57:02 +00001963 // src/Transform_inl.h will undefine USING_* for us.
1964 #undef N
1965 }
1966
1967 #if defined(__clang__)
1968 #pragma clang attribute pop
1969 #elif defined(__GNUC__)
1970 #pragma GCC pop_options
1971 #endif
1972
1973 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001974 #endif
1975
skia-autorollba6087c2019-04-09 13:57:02 +00001976 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
1977 enum class CpuType { None, HSW, SKX };
1978 static CpuType cpu_type() {
1979 static const CpuType type = []{
1980 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001981
skia-autorollba6087c2019-04-09 13:57:02 +00001982 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
1983 uint32_t eax, ebx, ecx, edx;
1984 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1985 : "0"(1), "2"(0));
1986 if ((edx & (1u<<25)) && // SSE
1987 (edx & (1u<<26)) && // SSE2
1988 (ecx & (1u<< 0)) && // SSE3
1989 (ecx & (1u<< 9)) && // SSSE3
1990 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
1991 (ecx & (1u<<19)) && // SSE4.1
1992 (ecx & (1u<<20)) && // SSE4.2
1993 (ecx & (1u<<26)) && // XSAVE
1994 (ecx & (1u<<27)) && // OSXSAVE
1995 (ecx & (1u<<28)) && // AVX
1996 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001997
skia-autorollba6087c2019-04-09 13:57:02 +00001998 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
1999 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2000 : "0"(7), "2"(0));
2001 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2002 uint32_t xcr0, dont_need_edx;
2003 __asm__ __volatile__("xgetbv" : "=a"(xcr0), "=d"(dont_need_edx) : "c"(0));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002004
skia-autorollba6087c2019-04-09 13:57:02 +00002005 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2006 (xcr0 & (1u<<2)) && // YMM register state saved?
2007 (ebx & (1u<<5))) { // AVX2
2008 // At this point we're at least HSW. Continue checking for SKX.
2009 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2010 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2011 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2012 (ebx & (1u<<16)) && // AVX512F
2013 (ebx & (1u<<17)) && // AVX512DQ
2014 (ebx & (1u<<28)) && // AVX512CD
2015 (ebx & (1u<<30)) && // AVX512BW
2016 (ebx & (1u<<31))) { // AVX512VL
2017 return CpuType::SKX;
2018 }
2019 return CpuType::HSW;
2020 }
2021 }
2022 return CpuType::None;
2023 }();
2024 return type;
2025 }
2026 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002027
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002028#endif
2029
2030static bool is_identity_tf(const skcms_TransferFunction* tf) {
2031 return tf->g == 1 && tf->a == 1
2032 && tf->b == 0 && tf->c == 0 && tf->d == 0 && tf->e == 0 && tf->f == 0;
2033}
2034
2035typedef struct {
2036 Op op;
2037 const void* arg;
2038} OpAndArg;
2039
2040static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002041 static const struct { Op parametric, table; } ops[] = {
2042 { Op_tf_r, Op_table_r },
2043 { Op_tf_g, Op_table_g },
2044 { Op_tf_b, Op_table_b },
2045 { Op_tf_a, Op_table_a },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002046 };
2047
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002048 const OpAndArg noop = { Op_load_a8/*doesn't matter*/, nullptr };
2049
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002050 if (curve->table_entries == 0) {
2051 return is_identity_tf(&curve->parametric)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002052 ? noop
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002053 : OpAndArg{ ops[channel].parametric, &curve->parametric };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002054 }
2055
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002056 return OpAndArg{ ops[channel].table, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002057}
2058
2059static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2060 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002061 case skcms_PixelFormat_A_8 >> 1: return 1;
2062 case skcms_PixelFormat_G_8 >> 1: return 1;
2063 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2064 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2065 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2066 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2067 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2068 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2069 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2070 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2071 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2072 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002073 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2074 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002075 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2076 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2077 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2078 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002079 }
2080 assert(false);
2081 return 0;
2082}
2083
2084static bool prep_for_destination(const skcms_ICCProfile* profile,
2085 skcms_Matrix3x3* fromXYZD50,
2086 skcms_TransferFunction* invR,
2087 skcms_TransferFunction* invG,
2088 skcms_TransferFunction* invB) {
2089 // We only support destinations with parametric transfer functions
2090 // and with gamuts that can be transformed from XYZD50.
2091 return profile->has_trc
2092 && profile->has_toXYZD50
2093 && profile->trc[0].table_entries == 0
2094 && profile->trc[1].table_entries == 0
2095 && profile->trc[2].table_entries == 0
2096 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2097 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2098 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2099 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2100}
2101
2102bool skcms_Transform(const void* src,
2103 skcms_PixelFormat srcFmt,
2104 skcms_AlphaFormat srcAlpha,
2105 const skcms_ICCProfile* srcProfile,
2106 void* dst,
2107 skcms_PixelFormat dstFmt,
2108 skcms_AlphaFormat dstAlpha,
2109 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002110 size_t npixels) {
2111 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2112 dst, dstFmt, dstAlpha, dstProfile,
2113 npixels, nullptr);
2114}
2115
2116bool skcms_TransformWithPalette(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 void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002126 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2127 src_bpp = bytes_per_pixel(srcFmt);
2128 // Let's just refuse if the request is absurdly big.
2129 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2130 return false;
2131 }
2132 int n = (int)nz;
2133
2134 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2135 if (!srcProfile) {
2136 srcProfile = skcms_sRGB_profile();
2137 }
2138 if (!dstProfile) {
2139 dstProfile = skcms_sRGB_profile();
2140 }
2141
2142 // We can't transform in place unless the PixelFormats are the same size.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com03a47062018-08-31 12:50:38 +00002143 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002144 return false;
2145 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002146 // TODO: more careful alias rejection (like, dst == src + 1)?
2147
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002148 if (needs_palette(srcFmt) && !palette) {
2149 return false;
2150 }
2151
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002152 Op program [32];
2153 const void* arguments[32];
2154
2155 Op* ops = program;
2156 const void** args = arguments;
2157
2158 skcms_TransferFunction inv_dst_tf_r, inv_dst_tf_g, inv_dst_tf_b;
2159 skcms_Matrix3x3 from_xyz;
2160
2161 switch (srcFmt >> 1) {
2162 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002163 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2164 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2165 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2166 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2167 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2168 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2169 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2170 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2171 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2172 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2173 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002174 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2175 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: *ops++ = Op_load_hhhh; break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002176 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2177 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2178 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2179 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002180
2181 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2182 *args++ = palette;
2183 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002184 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002185 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2186 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2187 *ops++ = Op_clamp;
2188 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002189 if (srcFmt & 1) {
2190 *ops++ = Op_swap_rb;
2191 }
2192 skcms_ICCProfile gray_dst_profile;
2193 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2194 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2195 // luminance (Y) by the destination transfer function.
2196 gray_dst_profile = *dstProfile;
2197 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2198 dstProfile = &gray_dst_profile;
2199 }
2200
2201 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2202 // Photoshop creates CMYK images as inverse CMYK.
2203 // These happen to be the only ones we've _ever_ seen.
2204 *ops++ = Op_invert;
2205 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2206 srcAlpha = skcms_AlphaFormat_Unpremul;
2207 }
2208
2209 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2210 *ops++ = Op_force_opaque;
2211 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2212 *ops++ = Op_unpremul;
2213 }
2214
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002215 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002216
2217 if (!prep_for_destination(dstProfile,
2218 &from_xyz, &inv_dst_tf_r, &inv_dst_tf_b, &inv_dst_tf_g)) {
2219 return false;
2220 }
2221
2222 if (srcProfile->has_A2B) {
2223 if (srcProfile->A2B.input_channels) {
2224 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2225 OpAndArg oa = select_curve_op(&srcProfile->A2B.input_curves[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002226 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002227 *ops++ = oa.op;
2228 *args++ = oa.arg;
2229 }
2230 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002231 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002232 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002233 *args++ = &srcProfile->A2B;
2234 }
2235
2236 if (srcProfile->A2B.matrix_channels == 3) {
2237 for (int i = 0; i < 3; i++) {
2238 OpAndArg oa = select_curve_op(&srcProfile->A2B.matrix_curves[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002239 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002240 *ops++ = oa.op;
2241 *args++ = oa.arg;
2242 }
2243 }
2244
2245 static const skcms_Matrix3x4 I = {{
2246 {1,0,0,0},
2247 {0,1,0,0},
2248 {0,0,1,0},
2249 }};
2250 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2251 *ops++ = Op_matrix_3x4;
2252 *args++ = &srcProfile->A2B.matrix;
2253 }
2254 }
2255
2256 if (srcProfile->A2B.output_channels == 3) {
2257 for (int i = 0; i < 3; i++) {
2258 OpAndArg oa = select_curve_op(&srcProfile->A2B.output_curves[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002259 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002260 *ops++ = oa.op;
2261 *args++ = oa.arg;
2262 }
2263 }
2264 }
2265
2266 if (srcProfile->pcs == skcms_Signature_Lab) {
2267 *ops++ = Op_lab_to_xyz;
2268 }
2269
2270 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2271 for (int i = 0; i < 3; i++) {
2272 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002273 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002274 *ops++ = oa.op;
2275 *args++ = oa.arg;
2276 }
2277 }
2278 } else {
2279 return false;
2280 }
2281
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002282 // A2B sources should already be in XYZD50 at this point.
2283 // Others still need to be transformed using their toXYZD50 matrix.
2284 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2285 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2286 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2287 static const skcms_Matrix3x3 I = {{
2288 { 1.0f, 0.0f, 0.0f },
2289 { 0.0f, 1.0f, 0.0f },
2290 { 0.0f, 0.0f, 1.0f },
2291 }};
2292 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2293
2294 // There's a chance the source and destination gamuts are identical,
2295 // in which case we can skip the gamut transform.
2296 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2297 // Concat the entire gamut transform into from_xyz,
2298 // now slightly misnamed but it's a handy spot to stash the result.
2299 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2300 *ops++ = Op_matrix_3x3;
2301 *args++ = &from_xyz;
2302 }
2303
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002304 // Encode back to dst RGB using its parametric transfer functions.
2305 if (!is_identity_tf(&inv_dst_tf_r)) { *ops++ = Op_tf_r; *args++ = &inv_dst_tf_r; }
2306 if (!is_identity_tf(&inv_dst_tf_g)) { *ops++ = Op_tf_g; *args++ = &inv_dst_tf_g; }
2307 if (!is_identity_tf(&inv_dst_tf_b)) { *ops++ = Op_tf_b; *args++ = &inv_dst_tf_b; }
2308 }
2309
skia-autoroll2e4fa242019-03-11 21:14:18 +00002310 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2311 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002312 //
2313 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2314 // but would be carrying r > 1, which is really unexpected for downstream consumers.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002315 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2316 *ops++ = Op_clamp;
2317 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002318 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2319 *ops++ = Op_force_opaque;
2320 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2321 *ops++ = Op_premul;
2322 }
2323 if (dstFmt & 1) {
2324 *ops++ = Op_swap_rb;
2325 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002326 switch (dstFmt >> 1) {
2327 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002328 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2329 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2330 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2331 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2332 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2333 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2334 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2335 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2336 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2337 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2338 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002339 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2340 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: *ops++ = Op_store_hhhh; break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002341 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2342 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2343 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2344 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_store_ffff; break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002345 }
2346
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002347 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002348#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002349 switch (cpu_type()) {
2350 case CpuType::None: break;
2351 case CpuType::HSW: run = hsw::run_program; break;
2352 case CpuType::SKX: run = hsw::run_program; break;
2353 }
2354#endif
2355#if defined(TEST_FOR_SKX)
2356 switch (cpu_type()) {
2357 case CpuType::None: break;
2358 case CpuType::HSW: break;
2359 case CpuType::SKX: run = skx::run_program; break;
2360 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002361#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002362 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 +00002363 return true;
2364}
2365
2366static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2367#if defined(NDEBUG)
2368 (void)profile;
2369#else
2370 skcms_Matrix3x3 fromXYZD50;
2371 skcms_TransferFunction invR, invG, invB;
2372 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2373#endif
2374}
2375
2376bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2377 skcms_Matrix3x3 fromXYZD50;
2378 if (!profile->has_trc || !profile->has_toXYZD50
2379 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2380 return false;
2381 }
2382
2383 skcms_TransferFunction tf[3];
2384 for (int i = 0; i < 3; i++) {
2385 skcms_TransferFunction inv;
2386 if (profile->trc[i].table_entries == 0
2387 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2388 tf[i] = profile->trc[i].parametric;
2389 continue;
2390 }
2391
2392 float max_error;
2393 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2394 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2395 return false;
2396 }
2397 }
2398
2399 for (int i = 0; i < 3; ++i) {
2400 profile->trc[i].table_entries = 0;
2401 profile->trc[i].parametric = tf[i];
2402 }
2403
2404 assert_usable_as_destination(profile);
2405 return true;
2406}
2407
2408bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2409 // Operate on a copy of profile, so we can choose the best TF for the original curves
2410 skcms_ICCProfile result = *profile;
2411 if (!skcms_MakeUsableAsDestination(&result)) {
2412 return false;
2413 }
2414
2415 int best_tf = 0;
2416 float min_max_error = INFINITY_;
2417 for (int i = 0; i < 3; i++) {
2418 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002419 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2420 return false;
2421 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002422
2423 float err = 0;
2424 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002425 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 +00002426 }
2427 if (min_max_error > err) {
2428 min_max_error = err;
2429 best_tf = i;
2430 }
2431 }
2432
2433 for (int i = 0; i < 3; i++) {
2434 result.trc[i].parametric = result.trc[best_tf].parametric;
2435 }
2436
2437 *profile = result;
2438 assert_usable_as_destination(profile);
2439 return true;
2440}