blob: 4a4fc47cdcce3b9479a71a371ebd8a6cf1286003 [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
skia-autoroll5f8588c2019-10-14 13:54:14 +000051#if defined(__clang__) || defined(__GNUC__)
52 #define small_memcpy __builtin_memcpy
53#else
54 #define small_memcpy memcpy
55#endif
56
57static float log2f_(float x) {
58 // The first approximation of log2(x) is its exponent 'e', minus 127.
59 int32_t bits;
60 small_memcpy(&bits, &x, sizeof(bits));
61
62 float e = (float)bits * (1.0f / (1<<23));
63
64 // If we use the mantissa too we can refine the error signficantly.
65 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
66 float m;
67 small_memcpy(&m, &m_bits, sizeof(m));
68
69 return (e - 124.225514990f
70 - 1.498030302f*m
71 - 1.725879990f/(0.3520887068f + m));
72}
73static float logf_(float x) {
74 const float ln2 = 0.69314718f;
75 return ln2*log2f_(x);
76}
77
78static float exp2f_(float x) {
79 float fract = x - floorf_(x);
80
81 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
82 - 1.490129070f*fract
83 + 27.728023300f/(4.84252568f - fract));
84
85 // Before we cast fbits to int32_t, check for out of range values to pacify UBSAN.
86 // INT_MAX is not exactly representable as a float, so exclude it as effectively infinite.
87 // INT_MIN is a power of 2 and exactly representable as a float, so it's fine.
88 if (fbits >= (float)INT_MAX) {
89 return INFINITY_;
90 } else if (fbits < (float)INT_MIN) {
91 return -INFINITY_;
92 }
93
94 int32_t bits = (int32_t)fbits;
95 small_memcpy(&x, &bits, sizeof(x));
96 return x;
97}
98
99// Not static, as it's used by some test tools.
100float powf_(float x, float y) {
101 assert (x >= 0);
102 return (x == 0) || (x == 1) ? x
103 : exp2f_(log2f_(x) * y);
104}
105
106static float expf_(float x) {
skia-autoroll775981d2019-10-14 14:54:13 +0000107 const float log2_e = 1.4426950408889634074f;
108 return exp2f_(log2_e * x);
skia-autoroll5f8588c2019-10-14 13:54:14 +0000109}
110
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000111static float fmaxf_(float x, float y) { return x > y ? x : y; }
112static float fminf_(float x, float y) { return x < y ? x : y; }
113
114static bool isfinitef_(float x) { return 0 == x*0; }
115
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000116static float minus_1_ulp(float x) {
117 int32_t bits;
118 memcpy(&bits, &x, sizeof(bits));
119 bits = bits - 1;
120 memcpy(&x, &bits, sizeof(bits));
121 return x;
122}
123
skia-autorollc093cc82019-10-08 19:22:13 +0000124// Most transfer functions we work with are sRGBish.
125// For exotic HDR transfer functions, we encode them using a tf.g that makes no sense,
126// and repurpose the other fields to hold the parameters of the HDR functions.
127enum TFKind { Bad, sRGBish, PQish, HLGish, HLGinvish };
128struct TF_PQish { float A,B,C,D,E,F; };
129struct TF_HLGish { float R,G,a,b,c; };
130
131static float TFKind_marker(TFKind kind) {
132 // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM.
133 return -(float)kind;
134}
135
136static TFKind classify(const skcms_TransferFunction& tf, TF_PQish* pq = nullptr
137 , TF_HLGish* hlg = nullptr) {
138 if (tf.g < 0 && (int)tf.g == tf.g) {
139 // TODO: sanity checks for PQ/HLG like we do for sRGBish.
140 switch (-(int)tf.g) {
141 case PQish: if (pq ) { memcpy(pq , &tf.a, sizeof(*pq )); } return PQish;
142 case HLGish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGish;
143 case HLGinvish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGinvish;
144 }
145 return Bad;
146 }
147
148 // Basic sanity checks for sRGBish transfer functions.
149 if (isfinitef_(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g)
150 // a,c,d,g should be non-negative to make any sense.
151 && tf.a >= 0
152 && tf.c >= 0
153 && tf.d >= 0
154 && tf.g >= 0
155 // Raising a negative value to a fractional tf->g produces complex numbers.
156 && tf.a * tf.d + tf.b >= 0) {
157 return sRGBish;
158 }
159
160 return Bad;
161}
162
163// TODO: temporary shim for old call sites
164static bool tf_is_valid(const skcms_TransferFunction* tf) {
165 return classify(*tf) == sRGBish;
166}
167
168
169bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
170 float A, float B, float C,
171 float D, float E, float F) {
172 *tf = { TFKind_marker(PQish), A,B,C,D,E,F };
173 assert(classify(*tf) == PQish);
174 return true;
175}
176
skia-autoroll5f8588c2019-10-14 13:54:14 +0000177bool skcms_TransferFunction_makeHLGinvish(skcms_TransferFunction* tf,
178 float R, float G,
179 float a, float b, float c) {
180 *tf = { TFKind_marker(HLGinvish), R,G, a,b,c, 0 };
181 assert(classify(*tf) == HLGinvish);
182 return true;
183}
184
185bool skcms_TransferFunction_makeHLGish(skcms_TransferFunction* tf,
186 float R, float G,
187 float a, float b, float c) {
188 // The math for HLGish transfer functions is faster if we precompute 1/R, 1/G, 1/a.
189 *tf = { TFKind_marker(HLGish), 1.0f/R,1.0f/G, 1.0f/a,b,c, 0 };
190 assert(classify(*tf) == HLGish);
191 return true;
192}
193
skia-autorollc093cc82019-10-08 19:22:13 +0000194float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
195 float sign = x < 0 ? -1.0f : 1.0f;
196 x *= sign;
197
skia-autoroll5f8588c2019-10-14 13:54:14 +0000198 TF_PQish pq;
199 TF_HLGish hlg;
200 switch (classify(*tf, &pq, &hlg)) {
skia-autorollc093cc82019-10-08 19:22:13 +0000201 case Bad: break;
skia-autoroll5f8588c2019-10-14 13:54:14 +0000202
203 // Remember that hlg.R, hlg.G, and hlg.a are holding each value's reciprocal,
204 // so the math may look a bit funny...
205 case HLGish: return sign * (x*hlg.R <= 1 ? powf_(x*hlg.R, hlg.G)
206 : expf_((x-hlg.c)*hlg.a) + hlg.b);
207
208 // Here all the hlg fields mean what they look like, R,G,a,b,c.
209 case HLGinvish: return sign * (x <= 1 ? hlg.R * powf_(x, hlg.G)
210 : hlg.a * logf_(x - hlg.b) + hlg.c);
211
skia-autorollc093cc82019-10-08 19:22:13 +0000212
213 case sRGBish: return sign * (x < tf->d ? tf->c * x + tf->f
214 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
215
216 case PQish: return sign * powf_(fmaxf_(pq.A + pq.B * powf_(x, pq.C), 0)
217 / (pq.D + pq.E * powf_(x, pq.C)),
218 pq.F);
219 }
220 return 0;
221}
222
223
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000224static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000225 if (curve->table_entries == 0) {
226 return skcms_TransferFunction_eval(&curve->parametric, x);
227 }
228
229 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 +0000230 int lo = (int) ix ,
231 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000232 float t = ix - (float)lo;
233
234 float l, h;
235 if (curve->table_8) {
236 l = curve->table_8[lo] * (1/255.0f);
237 h = curve->table_8[hi] * (1/255.0f);
238 } else {
239 uint16_t be_l, be_h;
240 memcpy(&be_l, curve->table_16 + 2*lo, 2);
241 memcpy(&be_h, curve->table_16 + 2*hi, 2);
242 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
243 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
244 l = le_l * (1/65535.0f);
245 h = le_h * (1/65535.0f);
246 }
247 return l + (h-l)*t;
248}
249
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000250static 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 +0000251 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
252 const float dx = 1.0f / (N - 1);
253 float err = 0;
254 for (uint32_t i = 0; i < N; i++) {
255 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000256 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000257 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
258 }
259 return err;
260}
261
262bool 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 +0000263 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 +0000264}
265
266// Additional ICC signature values that are only used internally
267enum {
268 // File signature
269 skcms_Signature_acsp = 0x61637370,
270
271 // Tag signatures
272 skcms_Signature_rTRC = 0x72545243,
273 skcms_Signature_gTRC = 0x67545243,
274 skcms_Signature_bTRC = 0x62545243,
275 skcms_Signature_kTRC = 0x6B545243,
276
277 skcms_Signature_rXYZ = 0x7258595A,
278 skcms_Signature_gXYZ = 0x6758595A,
279 skcms_Signature_bXYZ = 0x6258595A,
280
281 skcms_Signature_A2B0 = 0x41324230,
282 skcms_Signature_A2B1 = 0x41324231,
283 skcms_Signature_mAB = 0x6D414220,
284
285 skcms_Signature_CHAD = 0x63686164,
286
287 // Type signatures
288 skcms_Signature_curv = 0x63757276,
289 skcms_Signature_mft1 = 0x6D667431,
290 skcms_Signature_mft2 = 0x6D667432,
291 skcms_Signature_para = 0x70617261,
292 skcms_Signature_sf32 = 0x73663332,
293 // XYZ is also a PCS signature, so it's defined in skcms.h
294 // skcms_Signature_XYZ = 0x58595A20,
295};
296
297static uint16_t read_big_u16(const uint8_t* ptr) {
298 uint16_t be;
299 memcpy(&be, ptr, sizeof(be));
300#if defined(_MSC_VER)
301 return _byteswap_ushort(be);
302#else
303 return __builtin_bswap16(be);
304#endif
305}
306
307static uint32_t read_big_u32(const uint8_t* ptr) {
308 uint32_t be;
309 memcpy(&be, ptr, sizeof(be));
310#if defined(_MSC_VER)
311 return _byteswap_ulong(be);
312#else
313 return __builtin_bswap32(be);
314#endif
315}
316
317static int32_t read_big_i32(const uint8_t* ptr) {
318 return (int32_t)read_big_u32(ptr);
319}
320
321static float read_big_fixed(const uint8_t* ptr) {
322 return read_big_i32(ptr) * (1.0f / 65536.0f);
323}
324
325// Maps to an in-memory profile so that fields line up to the locations specified
326// in ICC.1:2010, section 7.2
327typedef struct {
328 uint8_t size [ 4];
329 uint8_t cmm_type [ 4];
330 uint8_t version [ 4];
331 uint8_t profile_class [ 4];
332 uint8_t data_color_space [ 4];
333 uint8_t pcs [ 4];
334 uint8_t creation_date_time [12];
335 uint8_t signature [ 4];
336 uint8_t platform [ 4];
337 uint8_t flags [ 4];
338 uint8_t device_manufacturer [ 4];
339 uint8_t device_model [ 4];
340 uint8_t device_attributes [ 8];
341 uint8_t rendering_intent [ 4];
342 uint8_t illuminant_X [ 4];
343 uint8_t illuminant_Y [ 4];
344 uint8_t illuminant_Z [ 4];
345 uint8_t creator [ 4];
346 uint8_t profile_id [16];
347 uint8_t reserved [28];
348 uint8_t tag_count [ 4]; // Technically not part of header, but required
349} header_Layout;
350
351typedef struct {
352 uint8_t signature [4];
353 uint8_t offset [4];
354 uint8_t size [4];
355} tag_Layout;
356
357static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
358 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
359}
360
361// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
362// use of the type is for the CHAD tag that stores exactly nine values.
363typedef struct {
364 uint8_t type [ 4];
365 uint8_t reserved [ 4];
366 uint8_t values [36];
367} sf32_Layout;
368
369bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
370 skcms_ICCTag tag;
371 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
372 return false;
373 }
374
375 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
376 return false;
377 }
378
379 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
380 const uint8_t* values = sf32Tag->values;
381 for (int r = 0; r < 3; ++r)
382 for (int c = 0; c < 3; ++c, values += 4) {
383 m->vals[r][c] = read_big_fixed(values);
384 }
385 return true;
386}
387
388// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
389// the type are for tags/data that store exactly one triple.
390typedef struct {
391 uint8_t type [4];
392 uint8_t reserved [4];
393 uint8_t X [4];
394 uint8_t Y [4];
395 uint8_t Z [4];
396} XYZ_Layout;
397
398static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
399 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
400 return false;
401 }
402
403 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
404
405 *x = read_big_fixed(xyzTag->X);
406 *y = read_big_fixed(xyzTag->Y);
407 *z = read_big_fixed(xyzTag->Z);
408 return true;
409}
410
411static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
412 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
413 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
414 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
415 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
416}
417
418typedef struct {
419 uint8_t type [4];
420 uint8_t reserved_a [4];
421 uint8_t function_type [2];
422 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000423 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 +0000424} para_Layout;
425
426static bool read_curve_para(const uint8_t* buf, uint32_t size,
427 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000428 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000429 return false;
430 }
431
432 const para_Layout* paraTag = (const para_Layout*)buf;
433
434 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
435 uint16_t function_type = read_big_u16(paraTag->function_type);
436 if (function_type > kGABCDEF) {
437 return false;
438 }
439
440 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 +0000441 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 +0000442 return false;
443 }
444
445 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000446 *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 +0000447 }
448
449 curve->table_entries = 0;
450 curve->parametric.a = 1.0f;
451 curve->parametric.b = 0.0f;
452 curve->parametric.c = 0.0f;
453 curve->parametric.d = 0.0f;
454 curve->parametric.e = 0.0f;
455 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000456 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000457
458 switch (function_type) {
459 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000460 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
461 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000462 if (curve->parametric.a == 0) {
463 return false;
464 }
465 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
466 break;
467 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000468 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
469 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
470 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000471 if (curve->parametric.a == 0) {
472 return false;
473 }
474 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
475 curve->parametric.f = curve->parametric.e;
476 break;
477 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000478 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
479 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
480 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
481 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000482 break;
483 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000484 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
485 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
486 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
487 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
488 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
489 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000490 break;
491 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000492 return tf_is_valid(&curve->parametric);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000493}
494
495typedef struct {
496 uint8_t type [4];
497 uint8_t reserved [4];
498 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000499 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 +0000500} curv_Layout;
501
502static bool read_curve_curv(const uint8_t* buf, uint32_t size,
503 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000504 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000505 return false;
506 }
507
508 const curv_Layout* curvTag = (const curv_Layout*)buf;
509
510 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 +0000511 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 +0000512 return false;
513 }
514
515 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000516 *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 +0000517 }
518
519 if (value_count < 2) {
520 curve->table_entries = 0;
521 curve->parametric.a = 1.0f;
522 curve->parametric.b = 0.0f;
523 curve->parametric.c = 0.0f;
524 curve->parametric.d = 0.0f;
525 curve->parametric.e = 0.0f;
526 curve->parametric.f = 0.0f;
527 if (value_count == 0) {
528 // Empty tables are a shorthand for an identity curve
529 curve->parametric.g = 1.0f;
530 } else {
531 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000532 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 +0000533 }
534 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000535 curve->table_8 = nullptr;
536 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000537 curve->table_entries = value_count;
538 }
539
540 return true;
541}
542
543// 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 +0000544// 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 +0000545static bool read_curve(const uint8_t* buf, uint32_t size,
546 skcms_Curve* curve, uint32_t* curve_size) {
547 if (!buf || size < 4 || !curve) {
548 return false;
549 }
550
551 uint32_t type = read_big_u32(buf);
552 if (type == skcms_Signature_para) {
553 return read_curve_para(buf, size, curve, curve_size);
554 } else if (type == skcms_Signature_curv) {
555 return read_curve_curv(buf, size, curve, curve_size);
556 }
557
558 return false;
559}
560
561// mft1 and mft2 share a large chunk of data
562typedef struct {
563 uint8_t type [ 4];
564 uint8_t reserved_a [ 4];
565 uint8_t input_channels [ 1];
566 uint8_t output_channels [ 1];
567 uint8_t grid_points [ 1];
568 uint8_t reserved_b [ 1];
569 uint8_t matrix [36];
570} mft_CommonLayout;
571
572typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000573 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000574
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000575 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000576} mft1_Layout;
577
578typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000579 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000580
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000581 uint8_t input_table_entries [2];
582 uint8_t output_table_entries [2];
583 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000584} mft2_Layout;
585
586static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
587 // MFT matrices are applied before the first set of curves, but must be identity unless the
588 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
589 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
590 // field/flag.
591 a2b->matrix_channels = 0;
592
593 a2b->input_channels = mftTag->input_channels[0];
594 a2b->output_channels = mftTag->output_channels[0];
595
596 // We require exactly three (ie XYZ/Lab/RGB) output channels
597 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
598 return false;
599 }
600 // We require at least one, and no more than four (ie CMYK) input channels
601 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
602 return false;
603 }
604
605 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
606 a2b->grid_points[i] = mftTag->grid_points[0];
607 }
608 // The grid only makes sense with at least two points along each axis
609 if (a2b->grid_points[0] < 2) {
610 return false;
611 }
612
613 return true;
614}
615
616static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
617 uint32_t input_table_entries, uint32_t output_table_entries,
618 skcms_A2B* a2b) {
619 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
620 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
621 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
622
623 // [input|output]_channels are <= 4, so still no overflow
624 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
625 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
626
627 uint64_t grid_size = a2b->output_channels * byte_width;
628 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
629 grid_size *= a2b->grid_points[axis];
630 }
631
632 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
633 return false;
634 }
635
636 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
637 a2b->input_curves[i].table_entries = input_table_entries;
638 if (byte_width == 1) {
639 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 +0000640 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000641 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000642 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000643 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
644 }
645 }
646
647 if (byte_width == 1) {
648 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 +0000649 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000650 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000651 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000652 a2b->grid_16 = table_base + byte_len_all_input_tables;
653 }
654
655 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
656 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
657 a2b->output_curves[i].table_entries = output_table_entries;
658 if (byte_width == 1) {
659 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 +0000660 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000661 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000662 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000663 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
664 }
665 }
666
667 return true;
668}
669
670static 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 +0000671 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000672 return false;
673 }
674
675 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
676 if (!read_mft_common(mftTag->common, a2b)) {
677 return false;
678 }
679
680 uint32_t input_table_entries = 256;
681 uint32_t output_table_entries = 256;
682
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000683 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 +0000684 input_table_entries, output_table_entries, a2b);
685}
686
687static 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 +0000688 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000689 return false;
690 }
691
692 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
693 if (!read_mft_common(mftTag->common, a2b)) {
694 return false;
695 }
696
697 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
698 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
699
700 // ICC spec mandates that 2 <= table_entries <= 4096
701 if (input_table_entries < 2 || input_table_entries > 4096 ||
702 output_table_entries < 2 || output_table_entries > 4096) {
703 return false;
704 }
705
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000706 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 +0000707 input_table_entries, output_table_entries, a2b);
708}
709
710static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
711 uint32_t num_curves, skcms_Curve* curves) {
712 for (uint32_t i = 0; i < num_curves; ++i) {
713 if (curve_offset > size) {
714 return false;
715 }
716
717 uint32_t curve_bytes;
718 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
719 return false;
720 }
721
722 if (curve_bytes > UINT32_MAX - 3) {
723 return false;
724 }
725 curve_bytes = (curve_bytes + 3) & ~3U;
726
727 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
728 curve_offset = (uint32_t)new_offset_64;
729 if (new_offset_64 != curve_offset) {
730 return false;
731 }
732 }
733
734 return true;
735}
736
737typedef struct {
738 uint8_t type [ 4];
739 uint8_t reserved_a [ 4];
740 uint8_t input_channels [ 1];
741 uint8_t output_channels [ 1];
742 uint8_t reserved_b [ 2];
743 uint8_t b_curve_offset [ 4];
744 uint8_t matrix_offset [ 4];
745 uint8_t m_curve_offset [ 4];
746 uint8_t clut_offset [ 4];
747 uint8_t a_curve_offset [ 4];
748} mAB_Layout;
749
750typedef struct {
751 uint8_t grid_points [16];
752 uint8_t grid_byte_width [ 1];
753 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000754 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000755} mABCLUT_Layout;
756
757static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
758 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
759 return false;
760 }
761
762 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
763
764 a2b->input_channels = mABTag->input_channels[0];
765 a2b->output_channels = mABTag->output_channels[0];
766
767 // We require exactly three (ie XYZ/Lab/RGB) output channels
768 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
769 return false;
770 }
771 // We require no more than four (ie CMYK) input channels
772 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
773 return false;
774 }
775
776 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
777 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
778 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
779 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
780 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
781
782 // "B" curves must be present
783 if (0 == b_curve_offset) {
784 return false;
785 }
786
787 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
788 a2b->output_curves)) {
789 return false;
790 }
791
792 // "M" curves and Matrix must be used together
793 if (0 != m_curve_offset) {
794 if (0 == matrix_offset) {
795 return false;
796 }
797 a2b->matrix_channels = a2b->output_channels;
798 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
799 a2b->matrix_curves)) {
800 return false;
801 }
802
803 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
804 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
805 return false;
806 }
807 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
808 const uint8_t* mtx_buf = tag->buf + matrix_offset;
809 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
810 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
811 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
812 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
813 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
814 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
815 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
816 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
817 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
818 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
819 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
820 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
821 } else {
822 if (0 != matrix_offset) {
823 return false;
824 }
825 a2b->matrix_channels = 0;
826 }
827
828 // "A" curves and CLUT must be used together
829 if (0 != a_curve_offset) {
830 if (0 == clut_offset) {
831 return false;
832 }
833 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
834 a2b->input_curves)) {
835 return false;
836 }
837
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000838 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 +0000839 return false;
840 }
841 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
842
843 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000844 a2b->grid_8 = clut->variable;
845 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000846 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000847 a2b->grid_8 = nullptr;
848 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000849 } else {
850 return false;
851 }
852
853 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
854 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
855 a2b->grid_points[i] = clut->grid_points[i];
856 // The grid only makes sense with at least two points along each axis
857 if (a2b->grid_points[i] < 2) {
858 return false;
859 }
860 grid_size *= a2b->grid_points[i];
861 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000862 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 +0000863 return false;
864 }
865 } else {
866 if (0 != clut_offset) {
867 return false;
868 }
869
870 // If there is no CLUT, the number of input and output channels must match
871 if (a2b->input_channels != a2b->output_channels) {
872 return false;
873 }
874
875 // Zero out the number of input channels to signal that we're skipping this stage
876 a2b->input_channels = 0;
877 }
878
879 return true;
880}
881
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000882// If you pass f, we'll fit a possibly-non-zero value for *f.
883// If you pass nullptr, we'll assume you want *f to be treated as zero.
884static int fit_linear(const skcms_Curve* curve, int N, float tol,
885 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000886 assert(N > 1);
887 // We iteratively fit the first points to the TF's linear piece.
888 // We want the cx + f line to pass through the first and last points we fit exactly.
889 //
890 // As we walk along the points we find the minimum and maximum slope of the line before the
891 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
892 // emtpy, when we definitely can't add any more points.
893 //
894 // Some points' error intervals may intersect the running interval but not lie fully
895 // within it. So we keep track of the last point we saw that is a valid end point candidate,
896 // and once the search is done, back up to build the line through *that* point.
897 const float dx = 1.0f / (N - 1);
898
899 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000900
901 float f_zero = 0.0f;
902 if (f) {
903 *f = eval_curve(curve, 0);
904 } else {
905 f = &f_zero;
906 }
907
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000908
909 float slope_min = -INFINITY_;
910 float slope_max = +INFINITY_;
911 for (int i = 1; i < N; ++i) {
912 float x = i * dx;
913 float y = eval_curve(curve, x);
914
915 float slope_max_i = (y + tol - *f) / x,
916 slope_min_i = (y - tol - *f) / x;
917 if (slope_max_i < slope_min || slope_max < slope_min_i) {
918 // Slope intervals would no longer overlap.
919 break;
920 }
921 slope_max = fminf_(slope_max, slope_max_i);
922 slope_min = fmaxf_(slope_min, slope_min_i);
923
924 float cur_slope = (y - *f) / x;
925 if (slope_min <= cur_slope && cur_slope <= slope_max) {
926 lin_points = i + 1;
927 *c = cur_slope;
928 }
929 }
930
931 // Set D to the last point that met our tolerance.
932 *d = (lin_points - 1) * dx;
933 return lin_points;
934}
935
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000936static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
937 bool ok = false;
938 if (tag->type == skcms_Signature_mft1) {
939 ok = read_tag_mft1(tag, a2b);
940 } else if (tag->type == skcms_Signature_mft2) {
941 ok = read_tag_mft2(tag, a2b);
942 } else if (tag->type == skcms_Signature_mAB) {
943 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
944 }
945 if (!ok) {
946 return false;
947 }
948
949 // Detect and canonicalize identity tables.
950 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000951 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
952 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
953 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
954 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
955 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
956 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
957 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
958 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
959 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
960 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000961 };
962
963 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
964 skcms_Curve* curve = curves[i];
965
966 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
967 int N = (int)curve->table_entries;
968
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000969 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 +0000970 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 +0000971 && c == 1.0f
972 && f == 0.0f) {
973 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000974 curve->table_8 = nullptr;
975 curve->table_16 = nullptr;
976 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 +0000977 }
978 }
979 }
980
981 return true;
982}
983
984void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
985 if (!profile || !profile->buffer || !tag) { return; }
986 if (idx > profile->tag_count) { return; }
987 const tag_Layout* tags = get_tag_table(profile);
988 tag->signature = read_big_u32(tags[idx].signature);
989 tag->size = read_big_u32(tags[idx].size);
990 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
991 tag->type = read_big_u32(tag->buf);
992}
993
994bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
995 if (!profile || !profile->buffer || !tag) { return false; }
996 const tag_Layout* tags = get_tag_table(profile);
997 for (uint32_t i = 0; i < profile->tag_count; ++i) {
998 if (read_big_u32(tags[i].signature) == sig) {
999 tag->signature = sig;
1000 tag->size = read_big_u32(tags[i].size);
1001 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
1002 tag->type = read_big_u32(tag->buf);
1003 return true;
1004 }
1005 }
1006 return false;
1007}
1008
1009static bool usable_as_src(const skcms_ICCProfile* profile) {
1010 return profile->has_A2B
1011 || (profile->has_trc && profile->has_toXYZD50);
1012}
1013
1014bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
1015 assert(SAFE_SIZEOF(header_Layout) == 132);
1016
1017 if (!profile) {
1018 return false;
1019 }
1020 memset(profile, 0, SAFE_SIZEOF(*profile));
1021
1022 if (len < SAFE_SIZEOF(header_Layout)) {
1023 return false;
1024 }
1025
1026 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001027 const header_Layout* header = (const header_Layout*)buf;
1028 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001029 profile->size = read_big_u32(header->size);
1030 uint32_t version = read_big_u32(header->version);
1031 profile->data_color_space = read_big_u32(header->data_color_space);
1032 profile->pcs = read_big_u32(header->pcs);
1033 uint32_t signature = read_big_u32(header->signature);
1034 float illuminant_X = read_big_fixed(header->illuminant_X);
1035 float illuminant_Y = read_big_fixed(header->illuminant_Y);
1036 float illuminant_Z = read_big_fixed(header->illuminant_Z);
1037 profile->tag_count = read_big_u32(header->tag_count);
1038
1039 // Validate signature, size (smaller than buffer, large enough to hold tag table),
1040 // and major version
1041 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1042 if (signature != skcms_Signature_acsp ||
1043 profile->size > len ||
1044 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1045 (version >> 24) > 4) {
1046 return false;
1047 }
1048
1049 // Validate that illuminant is D50 white
1050 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1051 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1052 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1053 return false;
1054 }
1055
1056 // Validate that all tag entries have sane offset + size
1057 const tag_Layout* tags = get_tag_table(profile);
1058 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1059 uint32_t tag_offset = read_big_u32(tags[i].offset);
1060 uint32_t tag_size = read_big_u32(tags[i].size);
1061 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
1062 if (tag_size < 4 || tag_end > profile->size) {
1063 return false;
1064 }
1065 }
1066
1067 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1068 return false;
1069 }
1070
1071 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1072
1073 // Pre-parse commonly used tags.
1074 skcms_ICCTag kTRC;
1075 if (profile->data_color_space == skcms_Signature_Gray &&
1076 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001077 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 +00001078 // Malformed tag
1079 return false;
1080 }
1081 profile->trc[1] = profile->trc[0];
1082 profile->trc[2] = profile->trc[0];
1083 profile->has_trc = true;
1084
1085 if (pcs_is_xyz) {
1086 profile->toXYZD50.vals[0][0] = illuminant_X;
1087 profile->toXYZD50.vals[1][1] = illuminant_Y;
1088 profile->toXYZD50.vals[2][2] = illuminant_Z;
1089 profile->has_toXYZD50 = true;
1090 }
1091 } else {
1092 skcms_ICCTag rTRC, gTRC, bTRC;
1093 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1094 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1095 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001096 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1097 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1098 !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 +00001099 // Malformed TRC tags
1100 return false;
1101 }
1102 profile->has_trc = true;
1103 }
1104
1105 skcms_ICCTag rXYZ, gXYZ, bXYZ;
1106 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1107 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1108 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1109 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1110 // Malformed XYZ tags
1111 return false;
1112 }
1113 profile->has_toXYZD50 = true;
1114 }
1115 }
1116
1117 skcms_ICCTag a2b_tag;
1118
1119 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
1120 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
1121 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
1122 // and all our known users are thinking exclusively in terms of relative colormetric.
1123 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
1124 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
1125 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
1126 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
1127 // Malformed A2B tag
1128 return false;
1129 }
1130 profile->has_A2B = true;
1131 break;
1132 }
1133 }
1134
1135 return usable_as_src(profile);
1136}
1137
1138
1139const skcms_ICCProfile* skcms_sRGB_profile() {
1140 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001141 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001142
1143 0, // size, moot here
1144 skcms_Signature_RGB, // data_color_space
1145 skcms_Signature_XYZ, // pcs
1146 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001147
1148 // We choose to represent sRGB with its canonical transfer function,
1149 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001150 true, // has_trc, followed by the 3 trc curves
1151 {
1152 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1153 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1154 {{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 +00001155 },
1156
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001157 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1158 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001159 { 0.436065674f, 0.385147095f, 0.143066406f },
1160 { 0.222488403f, 0.716873169f, 0.060607910f },
1161 { 0.013916016f, 0.097076416f, 0.714096069f },
1162 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001163
1164 false, // has_A2B, followed by a2b itself which we don't care about.
1165 {
1166 0,
1167 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001168 {{0, {0,0, 0,0,0,0,0}}},
1169 {{0, {0,0, 0,0,0,0,0}}},
1170 {{0, {0,0, 0,0,0,0,0}}},
1171 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001172 },
1173 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001174 nullptr,
1175 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001176
1177 0,
1178 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001179 {{0, {0,0, 0,0,0,0,0}}},
1180 {{0, {0,0, 0,0,0,0,0}}},
1181 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001182 },
1183 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001184 { 0,0,0,0 },
1185 { 0,0,0,0 },
1186 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001187 }},
1188
1189 0,
1190 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001191 {{0, {0,0, 0,0,0,0,0}}},
1192 {{0, {0,0, 0,0,0,0,0}}},
1193 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001194 },
1195 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001196 };
1197 return &sRGB_profile;
1198}
1199
1200const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001201 // 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 +00001202 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001203 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001204
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001205 0, // size, moot here
1206 skcms_Signature_RGB, // data_color_space
1207 skcms_Signature_XYZ, // pcs
1208 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001209
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001210 true, // has_trc, followed by the 3 trc curves
1211 {
1212 {{0, {1,1, 0,0,0,0,0}}},
1213 {{0, {1,1, 0,0,0,0,0}}},
1214 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001215 },
1216
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001217 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1218 {{
1219 { 1,0,0 },
1220 { 0,1,0 },
1221 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001222 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001223
1224 false, // has_A2B, followed by a2b itself which we don't care about.
1225 {
1226 0,
1227 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001228 {{0, {0,0, 0,0,0,0,0}}},
1229 {{0, {0,0, 0,0,0,0,0}}},
1230 {{0, {0,0, 0,0,0,0,0}}},
1231 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001232 },
1233 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001234 nullptr,
1235 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001236
1237 0,
1238 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001239 {{0, {0,0, 0,0,0,0,0}}},
1240 {{0, {0,0, 0,0,0,0,0}}},
1241 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001242 },
1243 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001244 { 0,0,0,0 },
1245 { 0,0,0,0 },
1246 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001247 }},
1248
1249 0,
1250 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001251 {{0, {0,0, 0,0,0,0,0}}},
1252 {{0, {0,0, 0,0,0,0,0}}},
1253 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001254 },
1255 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001256 };
1257
1258 return &XYZD50_profile;
1259}
1260
1261const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1262 return &skcms_sRGB_profile()->trc[0].parametric;
1263}
1264
1265const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1266 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001267 {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 +00001268 return &sRGB_inv;
1269}
1270
1271const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1272 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1273 return &identity;
1274}
1275
1276const uint8_t skcms_252_random_bytes[] = {
1277 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1278 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1279 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1280 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1281 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1282 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1283 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1284 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1285 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1286 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1287 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1288 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1289 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1290 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1291 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1292 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1293};
1294
1295bool 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 +00001296 // Test for exactly equal profiles first.
1297 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1298 return true;
1299 }
1300
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001301 // For now this is the essentially the same strategy we use in test_only.c
1302 // for our skcms_Transform() smoke tests:
1303 // 1) transform A to XYZD50
1304 // 2) transform B to XYZD50
1305 // 3) return true if they're similar enough
1306 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1307
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001308 // 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 +00001309 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1310
1311 if (A->data_color_space != B->data_color_space) {
1312 return false;
1313 }
1314
1315 // 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 +00001316 // 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 +00001317 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1318 size_t npixels = 84;
1319 if (A->data_color_space == skcms_Signature_CMYK) {
1320 fmt = skcms_PixelFormat_RGBA_8888;
1321 npixels = 63;
1322 }
1323
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001324 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1325 // 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 +00001326 uint8_t dstA[252],
1327 dstB[252];
1328 if (!skcms_Transform(
1329 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1330 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1331 npixels)) {
1332 return false;
1333 }
1334 if (!skcms_Transform(
1335 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1336 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1337 npixels)) {
1338 return false;
1339 }
1340
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001341 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001342 for (size_t i = 0; i < 252; i++) {
1343 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1344 return false;
1345 }
1346 }
1347 return true;
1348}
1349
1350bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1351 const skcms_TransferFunction* inv_tf) {
1352 if (!profile || !profile->has_trc) {
1353 return false;
1354 }
1355
1356 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1357 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1358 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1359}
1360
1361static bool is_zero_to_one(float x) {
1362 return 0 <= x && x <= 1;
1363}
1364
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001365typedef struct { float vals[3]; } skcms_Vector3;
1366
1367static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1368 skcms_Vector3 dst = {{0,0,0}};
1369 for (int row = 0; row < 3; ++row) {
1370 dst.vals[row] = m->vals[row][0] * v->vals[0]
1371 + m->vals[row][1] * v->vals[1]
1372 + m->vals[row][2] * v->vals[2];
1373 }
1374 return dst;
1375}
1376
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001377bool skcms_PrimariesToXYZD50(float rx, float ry,
1378 float gx, float gy,
1379 float bx, float by,
1380 float wx, float wy,
1381 skcms_Matrix3x3* toXYZD50) {
1382 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1383 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1384 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1385 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1386 !toXYZD50) {
1387 return false;
1388 }
1389
1390 // First, we need to convert xy values (primaries) to XYZ.
1391 skcms_Matrix3x3 primaries = {{
1392 { rx, gx, bx },
1393 { ry, gy, by },
1394 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1395 }};
1396 skcms_Matrix3x3 primaries_inv;
1397 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1398 return false;
1399 }
1400
1401 // Assumes that Y is 1.0f.
1402 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 +00001403 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001404
1405 skcms_Matrix3x3 toXYZ = {{
1406 { XYZ.vals[0], 0, 0 },
1407 { 0, XYZ.vals[1], 0 },
1408 { 0, 0, XYZ.vals[2] },
1409 }};
1410 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1411
1412 // Now convert toXYZ matrix to toXYZD50.
1413 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1414
1415 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1416 // the matrices below. The Bradford method is used by Adobe and is widely considered
1417 // to be the best.
1418 skcms_Matrix3x3 xyz_to_lms = {{
1419 { 0.8951f, 0.2664f, -0.1614f },
1420 { -0.7502f, 1.7135f, 0.0367f },
1421 { 0.0389f, -0.0685f, 1.0296f },
1422 }};
1423 skcms_Matrix3x3 lms_to_xyz = {{
1424 { 0.9869929f, -0.1470543f, 0.1599627f },
1425 { 0.4323053f, 0.5183603f, 0.0492912f },
1426 { -0.0085287f, 0.0400428f, 0.9684867f },
1427 }};
1428
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001429 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1430 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001431
1432 skcms_Matrix3x3 DXtoD50 = {{
1433 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1434 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1435 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1436 }};
1437 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1438 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1439
1440 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1441 return true;
1442}
1443
1444
1445bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1446 double a00 = src->vals[0][0],
1447 a01 = src->vals[1][0],
1448 a02 = src->vals[2][0],
1449 a10 = src->vals[0][1],
1450 a11 = src->vals[1][1],
1451 a12 = src->vals[2][1],
1452 a20 = src->vals[0][2],
1453 a21 = src->vals[1][2],
1454 a22 = src->vals[2][2];
1455
1456 double b0 = a00*a11 - a01*a10,
1457 b1 = a00*a12 - a02*a10,
1458 b2 = a01*a12 - a02*a11,
1459 b3 = a20,
1460 b4 = a21,
1461 b5 = a22;
1462
1463 double determinant = b0*b5
1464 - b1*b4
1465 + b2*b3;
1466
1467 if (determinant == 0) {
1468 return false;
1469 }
1470
1471 double invdet = 1.0 / determinant;
1472 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1473 return false;
1474 }
1475
1476 b0 *= invdet;
1477 b1 *= invdet;
1478 b2 *= invdet;
1479 b3 *= invdet;
1480 b4 *= invdet;
1481 b5 *= invdet;
1482
1483 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1484 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1485 dst->vals[2][0] = (float)( + b2 );
1486 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1487 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1488 dst->vals[2][1] = (float)( - b1 );
1489 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1490 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1491 dst->vals[2][2] = (float)( + b0 );
1492
1493 for (int r = 0; r < 3; ++r)
1494 for (int c = 0; c < 3; ++c) {
1495 if (!isfinitef_(dst->vals[r][c])) {
1496 return false;
1497 }
1498 }
1499 return true;
1500}
1501
1502skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1503 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1504 for (int r = 0; r < 3; r++)
1505 for (int c = 0; c < 3; c++) {
1506 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1507 + A->vals[r][1] * B->vals[1][c]
1508 + A->vals[r][2] * B->vals[2][c];
1509 }
1510 return m;
1511}
1512
skia-autorollacd6e012019-01-08 14:10:52 +00001513#if defined(__clang__)
1514 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by tf_is_valid() on the way out.
1515#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001516bool 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 +00001517 if (!tf_is_valid(src)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001518 return false;
1519 }
1520
skia-autorolld0b577f2019-01-07 19:46:57 +00001521 // We're inverting this function, solving for x in terms of y.
1522 // y = (cx + f) x < d
1523 // (ax + b)^g + e x ≥ d
1524 // The inverse of this function can be expressed in the same piecewise form.
1525 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 +00001526
skia-autorolld0b577f2019-01-07 19:46:57 +00001527 // We'll start by finding the new threshold inv.d.
1528 // In principle we should be able to find that by solving for y at x=d from either side.
1529 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1530 float d_l = src->c * src->d + src->f,
1531 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1532 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001533 return false;
1534 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001535 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 +00001536
skia-autorolld0b577f2019-01-07 19:46:57 +00001537 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1538 if (inv.d > 0) {
1539 // Inverting the linear section is pretty straightfoward:
1540 // y = cx + f
1541 // y - f = cx
1542 // (1/c)y - f/c = x
1543 inv.c = 1.0f/src->c;
1544 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001545 }
1546
skia-autorolld0b577f2019-01-07 19:46:57 +00001547 // The interesting part is inverting the nonlinear section:
1548 // y = (ax + b)^g + e.
1549 // y - e = (ax + b)^g
1550 // (y - e)^1/g = ax + b
1551 // (y - e)^1/g - b = ax
1552 // (1/a)(y - e)^1/g - b/a = x
1553 //
1554 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1555 // let k = (1/a)^g
1556 // (1/a)( y - e)^1/g - b/a = x
1557 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001558
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001559 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001560 inv.g = 1.0f / src->g;
1561 inv.a = k;
1562 inv.b = -k * src->e;
1563 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001564
skia-autorollfe16a332019-08-20 19:44:54 +00001565 // We need to enforce the same constraints here that we do when fitting a curve,
1566 // 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 +00001567 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001568
skia-autorollbf388232019-08-21 14:17:54 +00001569 // Just like when fitting the curve, there's really no way to rescue a < 0.
1570 if (inv.a < 0) {
1571 return false;
1572 }
1573 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001574 if (inv.a * inv.d + inv.b < 0) {
1575 inv.b = -inv.a * inv.d;
1576 }
skia-autorollbf388232019-08-21 14:17:54 +00001577
skia-autorolla0ed0702019-08-23 16:49:54 +00001578 // That should usually make tf_is_valid(&inv) true, but there are a couple situations
1579 // where we might still fail here, like non-finite parameter values.
1580 if (!tf_is_valid(&inv)) {
1581 return false;
1582 }
1583
skia-autorollbf388232019-08-21 14:17:54 +00001584 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001585 assert (inv.a * inv.d + inv.b >= 0);
1586
skia-autorolla7b28742019-01-09 18:35:46 +00001587 // Now in principle we're done.
skia-autorollad10df62019-08-21 15:14:54 +00001588 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
1589 // e or f of the inverse, depending on which segment contains src(1.0f).
1590 float s = skcms_TransferFunction_eval(src, 1.0f);
skia-autorolla0ed0702019-08-23 16:49:54 +00001591 if (!isfinitef_(s)) {
1592 return false;
1593 }
1594
skia-autorollad10df62019-08-21 15:14:54 +00001595 float sign = s < 0 ? -1.0f : 1.0f;
1596 s *= sign;
1597 if (s < inv.d) {
1598 inv.f = 1.0f - sign * inv.c * s;
1599 } else {
1600 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
1601 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001602
skia-autorolld0b577f2019-01-07 19:46:57 +00001603 *dst = inv;
1604 return tf_is_valid(dst);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001605}
1606
1607// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1608
1609// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1610//
1611// tf(x) = cx + f x < d
1612// tf(x) = (ax + b)^g + e x ≥ d
1613//
1614// When fitting, we add the additional constraint that both pieces meet at d:
1615//
1616// cd + f = (ad + b)^g + e
1617//
1618// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1619//
1620// tf(x) = cx + f x < d
1621// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1622//
1623// Our overall strategy is then:
1624// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001625// - 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 +00001626// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001627// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1628// (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 +00001629// Return the parameters with least maximum error.
1630//
1631// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1632// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1633//
1634// let y = Table(x)
1635// r(x) = x - f_inv(y)
1636//
1637// ∂r/∂g = ln(ay + b)*(ay + b)^g
1638// - ln(ad + b)*(ad + b)^g
1639// ∂r/∂a = yg(ay + b)^(g-1)
1640// - dg(ad + b)^(g-1)
1641// ∂r/∂b = g(ay + b)^(g-1)
1642// - g(ad + b)^(g-1)
1643
1644// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1645// and fill out the gradient of the residual into dfdP.
1646static float rg_nonlinear(float x,
1647 const skcms_Curve* curve,
1648 const skcms_TransferFunction* tf,
1649 const float P[3],
1650 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001651 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001652
1653 const float g = P[0], a = P[1], b = P[2],
1654 c = tf->c, d = tf->d, f = tf->f;
1655
1656 const float Y = fmaxf_(a*y + b, 0.0f),
1657 D = a*d + b;
1658 assert (D >= 0);
1659
1660 // The gradient.
skia-autoroll5f8588c2019-10-14 13:54:14 +00001661 dfdP[0] = logf_(Y)*powf_(Y, g)
1662 - logf_(D)*powf_(D, g);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001663 dfdP[1] = y*g*powf_(Y, g-1)
1664 - d*g*powf_(D, g-1);
1665 dfdP[2] = g*powf_(Y, g-1)
1666 - g*powf_(D, g-1);
1667
1668 // The residual.
1669 const float f_inv = powf_(Y, g)
1670 - powf_(D, g)
1671 + c*d + f;
1672 return x - f_inv;
1673}
1674
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001675static bool gauss_newton_step(const skcms_Curve* curve,
1676 const skcms_TransferFunction* tf,
1677 float P[3],
1678 float x0, float dx, int N) {
1679 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1680 //
1681 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1682 // where r(P) is the residual vector
1683 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1684 //
1685 // Let's review the shape of each of these expressions:
1686 // r(P) is [N x 1], a column vector with one entry per value of x tested
1687 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1688 // Jf^T is [3 x N], the transpose of Jf
1689 //
1690 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1691 // and so is its inverse (Jf^T Jf)^-1
1692 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1693 //
1694 // Our implementation strategy to get to the final ∆P is
1695 // 1) evaluate Jf^T Jf, call that lhs
1696 // 2) evaluate Jf^T r(P), call that rhs
1697 // 3) invert lhs
1698 // 4) multiply inverse lhs by rhs
1699 //
1700 // This is a friendly implementation strategy because we don't have to have any
1701 // buffers that scale with N, and equally nice don't have to perform any matrix
1702 // operations that are variable size.
1703 //
1704 // Other implementation strategies could trade this off, e.g. evaluating the
1705 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1706 // the residuals. That would probably require implementing singular value
1707 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1708 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1709 // possibility of this gauss_newton_step() function ever failing.
1710
1711 // 0) start off with lhs and rhs safely zeroed.
1712 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1713 skcms_Vector3 rhs = { {0,0,0} };
1714
1715 // 1,2) evaluate lhs and evaluate rhs
1716 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1717 // so we'll have to update lhs and rhs at the same time.
1718 for (int i = 0; i < N; i++) {
1719 float x = x0 + i*dx;
1720
1721 float dfdP[3] = {0,0,0};
1722 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1723
1724 for (int r = 0; r < 3; r++) {
1725 for (int c = 0; c < 3; c++) {
1726 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1727 }
1728 rhs.vals[r] += dfdP[r] * resid;
1729 }
1730 }
1731
1732 // If any of the 3 P parameters are unused, this matrix will be singular.
1733 // Detect those cases and fix them up to indentity instead, so we can invert.
1734 for (int k = 0; k < 3; k++) {
1735 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1736 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1737 lhs.vals[k][k] = 1;
1738 }
1739 }
1740
1741 // 3) invert lhs
1742 skcms_Matrix3x3 lhs_inv;
1743 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1744 return false;
1745 }
1746
1747 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001748 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001749 P[0] += dP.vals[0];
1750 P[1] += dP.vals[1];
1751 P[2] += dP.vals[2];
1752 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1753}
1754
1755
1756// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1757static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1758 float P[3] = { tf->g, tf->a, tf->b };
1759
1760 // No matter where we start, dx should always represent N even steps from 0 to 1.
1761 const float dx = 1.0f / (N-1);
1762
skia-autorolld9718822019-08-23 18:16:54 +00001763 // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
1764 for (int j = 0; j < 2; j++) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001765 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1766 // We don't really know how to fix up a if it goes negative.
1767 if (P[1] < 0) {
1768 return false;
1769 }
1770 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1771 if (P[1] * tf->d + P[2] < 0) {
1772 P[2] = -P[1] * tf->d;
1773 }
1774 assert (P[1] >= 0 &&
1775 P[1] * tf->d + P[2] >= 0);
1776
1777 if (!gauss_newton_step(curve, tf,
1778 P,
1779 L*dx, dx, N-L)) {
1780 return false;
1781 }
1782 }
1783
1784 // We need to apply our fixups one last time
1785 if (P[1] < 0) {
1786 return false;
1787 }
1788 if (P[1] * tf->d + P[2] < 0) {
1789 P[2] = -P[1] * tf->d;
1790 }
1791
skia-autorollfe16a332019-08-20 19:44:54 +00001792 assert (P[1] >= 0 &&
1793 P[1] * tf->d + P[2] >= 0);
1794
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001795 tf->g = P[0];
1796 tf->a = P[1];
1797 tf->b = P[2];
1798 tf->e = tf->c*tf->d + tf->f
1799 - powf_(tf->a*tf->d + tf->b, tf->g);
1800 return true;
1801}
1802
1803bool skcms_ApproximateCurve(const skcms_Curve* curve,
1804 skcms_TransferFunction* approx,
1805 float* max_error) {
1806 if (!curve || !approx || !max_error) {
1807 return false;
1808 }
1809
1810 if (curve->table_entries == 0) {
1811 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1812 return false;
1813 }
1814
1815 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1816 // We need at least two points, and must put some reasonable cap on the maximum number.
1817 return false;
1818 }
1819
1820 int N = (int)curve->table_entries;
1821 const float dx = 1.0f / (N - 1);
1822
1823 *max_error = INFINITY_;
1824 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1825 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1826 skcms_TransferFunction tf,
1827 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001828
1829 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1830 tf.f = 0.0f;
1831 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 +00001832
1833 if (L == N) {
1834 // If the entire data set was linear, move the coefficients to the nonlinear portion
1835 // with G == 1. This lets use a canonical representation with d == 0.
1836 tf.g = 1;
1837 tf.a = tf.c;
1838 tf.b = tf.f;
1839 tf.c = tf.d = tf.e = tf.f = 0;
1840 } else if (L == N - 1) {
1841 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1842 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001843 tf.a = (eval_curve(curve, (N-1)*dx) -
1844 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001845 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001846 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001847 - tf.a * (N-2)*dx;
1848 tf.e = 0;
1849 } else {
1850 // Start by guessing a gamma-only curve through the midpoint.
1851 int mid = (L + N) / 2;
1852 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001853 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001854 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001855 tf.a = 1;
1856 tf.b = 0;
1857 tf.e = tf.c*tf.d + tf.f
1858 - powf_(tf.a*tf.d + tf.b, tf.g);
1859
1860
1861 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1862 !fit_nonlinear(curve, L,N, &tf_inv)) {
1863 continue;
1864 }
1865
1866 // We fit tf_inv, so calculate tf to keep in sync.
1867 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1868 continue;
1869 }
1870 }
1871
1872 // We find our error by roundtripping the table through tf_inv.
1873 //
1874 // (The most likely use case for this approximation is to be inverted and
1875 // used as the transfer function for a destination color space.)
1876 //
1877 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1878 // invertible, so re-verify that here (and use the new inverse for testing).
1879 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1880 continue;
1881 }
1882
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001883 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001884 if (*max_error > err) {
1885 *max_error = err;
1886 *approx = tf;
1887 }
1888 }
1889 return isfinitef_(*max_error);
1890}
1891
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001892// ~~~~ Impl. of skcms_Transform() ~~~~
1893
1894typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001895 Op_load_a8,
1896 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001897 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001898 Op_load_4444,
1899 Op_load_565,
1900 Op_load_888,
1901 Op_load_8888,
1902 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001903 Op_load_161616LE,
1904 Op_load_16161616LE,
1905 Op_load_161616BE,
1906 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001907 Op_load_hhh,
1908 Op_load_hhhh,
1909 Op_load_fff,
1910 Op_load_ffff,
1911
1912 Op_swap_rb,
1913 Op_clamp,
1914 Op_invert,
1915 Op_force_opaque,
1916 Op_premul,
1917 Op_unpremul,
1918 Op_matrix_3x3,
1919 Op_matrix_3x4,
1920 Op_lab_to_xyz,
1921
1922 Op_tf_r,
1923 Op_tf_g,
1924 Op_tf_b,
1925 Op_tf_a,
1926
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001927 Op_table_r,
1928 Op_table_g,
1929 Op_table_b,
1930 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001931
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001932 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001933
1934 Op_store_a8,
1935 Op_store_g8,
1936 Op_store_4444,
1937 Op_store_565,
1938 Op_store_888,
1939 Op_store_8888,
1940 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001941 Op_store_161616LE,
1942 Op_store_16161616LE,
1943 Op_store_161616BE,
1944 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001945 Op_store_hhh,
1946 Op_store_hhhh,
1947 Op_store_fff,
1948 Op_store_ffff,
1949} Op;
1950
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001951#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001952 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 +00001953#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001954 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1955 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1956 template <int N, typename T>
1957 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 +00001958
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001959 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 +00001960#endif
1961
1962// First, instantiate our default exec_ops() implementation using the default compiliation target.
1963
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001964namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00001965#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
1966 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001967 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00001968 template <typename T> using V = T;
1969 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001970#elif defined(__AVX512F__)
1971 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001972 template <typename T> using V = Vec<N,T>;
1973 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001974#elif defined(__AVX__)
1975 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001976 template <typename T> using V = Vec<N,T>;
1977 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00001978#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
1979 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001980 template <typename T> using V = Vec<N,T>;
1981 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001982#else
1983 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00001984 template <typename T> using V = Vec<N,T>;
1985 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001986#endif
1987
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001988 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001989 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001990}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001991
1992// 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 +00001993#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00001994 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00001995 (( defined(__clang__) && __clang_major__ >= 5) || \
1996 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00001997 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001998
skia-autorollba6087c2019-04-09 13:57:02 +00001999 #if !defined(__AVX2__)
2000 #if defined(__clang__)
2001 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
2002 #elif defined(__GNUC__)
2003 #pragma GCC push_options
2004 #pragma GCC target("avx2,f16c")
2005 #endif
2006
2007 namespace hsw {
2008 #define USING_AVX
2009 #define USING_AVX_F16C
2010 #define USING_AVX2
2011 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002012 template <typename T> using V = Vec<N,T>;
2013 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00002014
2015 #include "src/Transform_inl.h"
2016
2017 // src/Transform_inl.h will undefine USING_* for us.
2018 #undef N
2019 }
2020
2021 #if defined(__clang__)
2022 #pragma clang attribute pop
2023 #elif defined(__GNUC__)
2024 #pragma GCC pop_options
2025 #endif
2026
2027 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002028 #endif
2029
skia-autorollba6087c2019-04-09 13:57:02 +00002030 #if !defined(__AVX512F__)
2031 #if defined(__clang__)
2032 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
2033 #elif defined(__GNUC__)
2034 #pragma GCC push_options
2035 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
2036 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002037
skia-autorollba6087c2019-04-09 13:57:02 +00002038 namespace skx {
2039 #define USING_AVX512F
2040 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002041 template <typename T> using V = Vec<N,T>;
2042 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002043
skia-autorollba6087c2019-04-09 13:57:02 +00002044 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002045
skia-autorollba6087c2019-04-09 13:57:02 +00002046 // src/Transform_inl.h will undefine USING_* for us.
2047 #undef N
2048 }
2049
2050 #if defined(__clang__)
2051 #pragma clang attribute pop
2052 #elif defined(__GNUC__)
2053 #pragma GCC pop_options
2054 #endif
2055
2056 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002057 #endif
2058
skia-autorollba6087c2019-04-09 13:57:02 +00002059 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
2060 enum class CpuType { None, HSW, SKX };
2061 static CpuType cpu_type() {
2062 static const CpuType type = []{
2063 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002064
skia-autorollba6087c2019-04-09 13:57:02 +00002065 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2066 uint32_t eax, ebx, ecx, edx;
2067 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2068 : "0"(1), "2"(0));
2069 if ((edx & (1u<<25)) && // SSE
2070 (edx & (1u<<26)) && // SSE2
2071 (ecx & (1u<< 0)) && // SSE3
2072 (ecx & (1u<< 9)) && // SSSE3
2073 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
2074 (ecx & (1u<<19)) && // SSE4.1
2075 (ecx & (1u<<20)) && // SSE4.2
2076 (ecx & (1u<<26)) && // XSAVE
2077 (ecx & (1u<<27)) && // OSXSAVE
2078 (ecx & (1u<<28)) && // AVX
2079 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002080
skia-autorollba6087c2019-04-09 13:57:02 +00002081 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2082 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2083 : "0"(7), "2"(0));
2084 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2085 uint32_t xcr0, dont_need_edx;
2086 __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 +00002087
skia-autorollba6087c2019-04-09 13:57:02 +00002088 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2089 (xcr0 & (1u<<2)) && // YMM register state saved?
2090 (ebx & (1u<<5))) { // AVX2
2091 // At this point we're at least HSW. Continue checking for SKX.
2092 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2093 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2094 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2095 (ebx & (1u<<16)) && // AVX512F
2096 (ebx & (1u<<17)) && // AVX512DQ
2097 (ebx & (1u<<28)) && // AVX512CD
2098 (ebx & (1u<<30)) && // AVX512BW
2099 (ebx & (1u<<31))) { // AVX512VL
2100 return CpuType::SKX;
2101 }
2102 return CpuType::HSW;
2103 }
2104 }
2105 return CpuType::None;
2106 }();
2107 return type;
2108 }
2109 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002110
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002111#endif
2112
2113static bool is_identity_tf(const skcms_TransferFunction* tf) {
2114 return tf->g == 1 && tf->a == 1
2115 && tf->b == 0 && tf->c == 0 && tf->d == 0 && tf->e == 0 && tf->f == 0;
2116}
2117
2118typedef struct {
2119 Op op;
2120 const void* arg;
2121} OpAndArg;
2122
2123static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002124 static const struct { Op parametric, table; } ops[] = {
2125 { Op_tf_r, Op_table_r },
2126 { Op_tf_g, Op_table_g },
2127 { Op_tf_b, Op_table_b },
2128 { Op_tf_a, Op_table_a },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002129 };
2130
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002131 const OpAndArg noop = { Op_load_a8/*doesn't matter*/, nullptr };
2132
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002133 if (curve->table_entries == 0) {
2134 return is_identity_tf(&curve->parametric)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002135 ? noop
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002136 : OpAndArg{ ops[channel].parametric, &curve->parametric };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002137 }
2138
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002139 return OpAndArg{ ops[channel].table, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002140}
2141
2142static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2143 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002144 case skcms_PixelFormat_A_8 >> 1: return 1;
2145 case skcms_PixelFormat_G_8 >> 1: return 1;
2146 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2147 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2148 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2149 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2150 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2151 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2152 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2153 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2154 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2155 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002156 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2157 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002158 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2159 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2160 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2161 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002162 }
2163 assert(false);
2164 return 0;
2165}
2166
2167static bool prep_for_destination(const skcms_ICCProfile* profile,
2168 skcms_Matrix3x3* fromXYZD50,
2169 skcms_TransferFunction* invR,
2170 skcms_TransferFunction* invG,
2171 skcms_TransferFunction* invB) {
2172 // We only support destinations with parametric transfer functions
2173 // and with gamuts that can be transformed from XYZD50.
2174 return profile->has_trc
2175 && profile->has_toXYZD50
2176 && profile->trc[0].table_entries == 0
2177 && profile->trc[1].table_entries == 0
2178 && profile->trc[2].table_entries == 0
2179 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2180 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2181 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2182 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2183}
2184
2185bool skcms_Transform(const void* src,
2186 skcms_PixelFormat srcFmt,
2187 skcms_AlphaFormat srcAlpha,
2188 const skcms_ICCProfile* srcProfile,
2189 void* dst,
2190 skcms_PixelFormat dstFmt,
2191 skcms_AlphaFormat dstAlpha,
2192 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002193 size_t npixels) {
2194 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2195 dst, dstFmt, dstAlpha, dstProfile,
2196 npixels, nullptr);
2197}
2198
2199bool skcms_TransformWithPalette(const void* src,
2200 skcms_PixelFormat srcFmt,
2201 skcms_AlphaFormat srcAlpha,
2202 const skcms_ICCProfile* srcProfile,
2203 void* dst,
2204 skcms_PixelFormat dstFmt,
2205 skcms_AlphaFormat dstAlpha,
2206 const skcms_ICCProfile* dstProfile,
2207 size_t nz,
2208 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002209 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2210 src_bpp = bytes_per_pixel(srcFmt);
2211 // Let's just refuse if the request is absurdly big.
2212 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2213 return false;
2214 }
2215 int n = (int)nz;
2216
2217 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2218 if (!srcProfile) {
2219 srcProfile = skcms_sRGB_profile();
2220 }
2221 if (!dstProfile) {
2222 dstProfile = skcms_sRGB_profile();
2223 }
2224
2225 // 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 +00002226 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002227 return false;
2228 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002229 // TODO: more careful alias rejection (like, dst == src + 1)?
2230
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002231 if (needs_palette(srcFmt) && !palette) {
2232 return false;
2233 }
2234
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002235 Op program [32];
2236 const void* arguments[32];
2237
2238 Op* ops = program;
2239 const void** args = arguments;
2240
2241 skcms_TransferFunction inv_dst_tf_r, inv_dst_tf_g, inv_dst_tf_b;
2242 skcms_Matrix3x3 from_xyz;
2243
2244 switch (srcFmt >> 1) {
2245 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002246 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2247 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2248 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2249 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2250 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2251 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2252 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2253 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2254 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2255 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2256 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002257 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2258 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 +00002259 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2260 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2261 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2262 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002263
2264 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2265 *args++ = palette;
2266 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002267 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002268 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2269 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2270 *ops++ = Op_clamp;
2271 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002272 if (srcFmt & 1) {
2273 *ops++ = Op_swap_rb;
2274 }
2275 skcms_ICCProfile gray_dst_profile;
2276 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2277 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2278 // luminance (Y) by the destination transfer function.
2279 gray_dst_profile = *dstProfile;
2280 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2281 dstProfile = &gray_dst_profile;
2282 }
2283
2284 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2285 // Photoshop creates CMYK images as inverse CMYK.
2286 // These happen to be the only ones we've _ever_ seen.
2287 *ops++ = Op_invert;
2288 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2289 srcAlpha = skcms_AlphaFormat_Unpremul;
2290 }
2291
2292 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2293 *ops++ = Op_force_opaque;
2294 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2295 *ops++ = Op_unpremul;
2296 }
2297
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002298 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002299
2300 if (!prep_for_destination(dstProfile,
2301 &from_xyz, &inv_dst_tf_r, &inv_dst_tf_b, &inv_dst_tf_g)) {
2302 return false;
2303 }
2304
2305 if (srcProfile->has_A2B) {
2306 if (srcProfile->A2B.input_channels) {
2307 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2308 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 +00002309 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002310 *ops++ = oa.op;
2311 *args++ = oa.arg;
2312 }
2313 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002314 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002315 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002316 *args++ = &srcProfile->A2B;
2317 }
2318
2319 if (srcProfile->A2B.matrix_channels == 3) {
2320 for (int i = 0; i < 3; i++) {
2321 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 +00002322 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002323 *ops++ = oa.op;
2324 *args++ = oa.arg;
2325 }
2326 }
2327
2328 static const skcms_Matrix3x4 I = {{
2329 {1,0,0,0},
2330 {0,1,0,0},
2331 {0,0,1,0},
2332 }};
2333 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2334 *ops++ = Op_matrix_3x4;
2335 *args++ = &srcProfile->A2B.matrix;
2336 }
2337 }
2338
2339 if (srcProfile->A2B.output_channels == 3) {
2340 for (int i = 0; i < 3; i++) {
2341 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 +00002342 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002343 *ops++ = oa.op;
2344 *args++ = oa.arg;
2345 }
2346 }
2347 }
2348
2349 if (srcProfile->pcs == skcms_Signature_Lab) {
2350 *ops++ = Op_lab_to_xyz;
2351 }
2352
2353 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2354 for (int i = 0; i < 3; i++) {
2355 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002356 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002357 *ops++ = oa.op;
2358 *args++ = oa.arg;
2359 }
2360 }
2361 } else {
2362 return false;
2363 }
2364
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002365 // A2B sources should already be in XYZD50 at this point.
2366 // Others still need to be transformed using their toXYZD50 matrix.
2367 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2368 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2369 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2370 static const skcms_Matrix3x3 I = {{
2371 { 1.0f, 0.0f, 0.0f },
2372 { 0.0f, 1.0f, 0.0f },
2373 { 0.0f, 0.0f, 1.0f },
2374 }};
2375 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2376
2377 // There's a chance the source and destination gamuts are identical,
2378 // in which case we can skip the gamut transform.
2379 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2380 // Concat the entire gamut transform into from_xyz,
2381 // now slightly misnamed but it's a handy spot to stash the result.
2382 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2383 *ops++ = Op_matrix_3x3;
2384 *args++ = &from_xyz;
2385 }
2386
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002387 // Encode back to dst RGB using its parametric transfer functions.
2388 if (!is_identity_tf(&inv_dst_tf_r)) { *ops++ = Op_tf_r; *args++ = &inv_dst_tf_r; }
2389 if (!is_identity_tf(&inv_dst_tf_g)) { *ops++ = Op_tf_g; *args++ = &inv_dst_tf_g; }
2390 if (!is_identity_tf(&inv_dst_tf_b)) { *ops++ = Op_tf_b; *args++ = &inv_dst_tf_b; }
2391 }
2392
skia-autoroll2e4fa242019-03-11 21:14:18 +00002393 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2394 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002395 //
2396 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2397 // 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 +00002398 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2399 *ops++ = Op_clamp;
2400 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002401 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2402 *ops++ = Op_force_opaque;
2403 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2404 *ops++ = Op_premul;
2405 }
2406 if (dstFmt & 1) {
2407 *ops++ = Op_swap_rb;
2408 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002409 switch (dstFmt >> 1) {
2410 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002411 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2412 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2413 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2414 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2415 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2416 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2417 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2418 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2419 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2420 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2421 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002422 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2423 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 +00002424 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2425 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2426 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2427 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 +00002428 }
2429
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002430 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002431#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002432 switch (cpu_type()) {
2433 case CpuType::None: break;
2434 case CpuType::HSW: run = hsw::run_program; break;
2435 case CpuType::SKX: run = hsw::run_program; break;
2436 }
2437#endif
2438#if defined(TEST_FOR_SKX)
2439 switch (cpu_type()) {
2440 case CpuType::None: break;
2441 case CpuType::HSW: break;
2442 case CpuType::SKX: run = skx::run_program; break;
2443 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002444#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002445 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 +00002446 return true;
2447}
2448
2449static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2450#if defined(NDEBUG)
2451 (void)profile;
2452#else
2453 skcms_Matrix3x3 fromXYZD50;
2454 skcms_TransferFunction invR, invG, invB;
2455 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2456#endif
2457}
2458
2459bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2460 skcms_Matrix3x3 fromXYZD50;
2461 if (!profile->has_trc || !profile->has_toXYZD50
2462 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2463 return false;
2464 }
2465
2466 skcms_TransferFunction tf[3];
2467 for (int i = 0; i < 3; i++) {
2468 skcms_TransferFunction inv;
2469 if (profile->trc[i].table_entries == 0
2470 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2471 tf[i] = profile->trc[i].parametric;
2472 continue;
2473 }
2474
2475 float max_error;
2476 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2477 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2478 return false;
2479 }
2480 }
2481
2482 for (int i = 0; i < 3; ++i) {
2483 profile->trc[i].table_entries = 0;
2484 profile->trc[i].parametric = tf[i];
2485 }
2486
2487 assert_usable_as_destination(profile);
2488 return true;
2489}
2490
2491bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2492 // Operate on a copy of profile, so we can choose the best TF for the original curves
2493 skcms_ICCProfile result = *profile;
2494 if (!skcms_MakeUsableAsDestination(&result)) {
2495 return false;
2496 }
2497
2498 int best_tf = 0;
2499 float min_max_error = INFINITY_;
2500 for (int i = 0; i < 3; i++) {
2501 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002502 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2503 return false;
2504 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002505
2506 float err = 0;
2507 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002508 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 +00002509 }
2510 if (min_max_error > err) {
2511 min_max_error = err;
2512 best_tf = i;
2513 }
2514 }
2515
2516 for (int i = 0; i < 3; i++) {
2517 result.trc[i].parametric = result.trc[best_tf].parametric;
2518 }
2519
2520 *profile = result;
2521 assert_usable_as_destination(profile);
2522 return true;
2523}