blob: bace176b94a4d8147cf5bf394e8cf5f8e6a6b7f4 [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
skia-autorollc093cc82019-10-08 19:22:13 +0000163bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
164 float A, float B, float C,
165 float D, float E, float F) {
166 *tf = { TFKind_marker(PQish), A,B,C,D,E,F };
167 assert(classify(*tf) == PQish);
168 return true;
169}
170
skia-autoroll5f8588c2019-10-14 13:54:14 +0000171bool skcms_TransferFunction_makeHLGish(skcms_TransferFunction* tf,
172 float R, float G,
173 float a, float b, float c) {
skia-autorollf6d50642019-10-14 15:31:13 +0000174 *tf = { TFKind_marker(HLGish), R,G, a,b,c, 0 };
skia-autoroll5f8588c2019-10-14 13:54:14 +0000175 assert(classify(*tf) == HLGish);
176 return true;
177}
178
skia-autorollc093cc82019-10-08 19:22:13 +0000179float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
180 float sign = x < 0 ? -1.0f : 1.0f;
181 x *= sign;
182
skia-autoroll5f8588c2019-10-14 13:54:14 +0000183 TF_PQish pq;
184 TF_HLGish hlg;
185 switch (classify(*tf, &pq, &hlg)) {
skia-autorollc093cc82019-10-08 19:22:13 +0000186 case Bad: break;
skia-autoroll5f8588c2019-10-14 13:54:14 +0000187
skia-autoroll5f8588c2019-10-14 13:54:14 +0000188 case HLGish: return sign * (x*hlg.R <= 1 ? powf_(x*hlg.R, hlg.G)
189 : expf_((x-hlg.c)*hlg.a) + hlg.b);
190
skia-autorollf6d50642019-10-14 15:31:13 +0000191 // skcms_TransferFunction_invert() inverts R, G, and a for HLGinvish so this math is fast.
skia-autoroll5f8588c2019-10-14 13:54:14 +0000192 case HLGinvish: return sign * (x <= 1 ? hlg.R * powf_(x, hlg.G)
193 : hlg.a * logf_(x - hlg.b) + hlg.c);
194
skia-autorollc093cc82019-10-08 19:22:13 +0000195
196 case sRGBish: return sign * (x < tf->d ? tf->c * x + tf->f
197 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
198
199 case PQish: return sign * powf_(fmaxf_(pq.A + pq.B * powf_(x, pq.C), 0)
200 / (pq.D + pq.E * powf_(x, pq.C)),
201 pq.F);
202 }
203 return 0;
204}
205
206
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000207static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000208 if (curve->table_entries == 0) {
209 return skcms_TransferFunction_eval(&curve->parametric, x);
210 }
211
212 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 +0000213 int lo = (int) ix ,
214 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000215 float t = ix - (float)lo;
216
217 float l, h;
218 if (curve->table_8) {
219 l = curve->table_8[lo] * (1/255.0f);
220 h = curve->table_8[hi] * (1/255.0f);
221 } else {
222 uint16_t be_l, be_h;
223 memcpy(&be_l, curve->table_16 + 2*lo, 2);
224 memcpy(&be_h, curve->table_16 + 2*hi, 2);
225 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
226 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
227 l = le_l * (1/65535.0f);
228 h = le_h * (1/65535.0f);
229 }
230 return l + (h-l)*t;
231}
232
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000233static 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 +0000234 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
235 const float dx = 1.0f / (N - 1);
236 float err = 0;
237 for (uint32_t i = 0; i < N; i++) {
238 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000239 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000240 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
241 }
242 return err;
243}
244
245bool 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 +0000246 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 +0000247}
248
249// Additional ICC signature values that are only used internally
250enum {
251 // File signature
252 skcms_Signature_acsp = 0x61637370,
253
254 // Tag signatures
255 skcms_Signature_rTRC = 0x72545243,
256 skcms_Signature_gTRC = 0x67545243,
257 skcms_Signature_bTRC = 0x62545243,
258 skcms_Signature_kTRC = 0x6B545243,
259
260 skcms_Signature_rXYZ = 0x7258595A,
261 skcms_Signature_gXYZ = 0x6758595A,
262 skcms_Signature_bXYZ = 0x6258595A,
263
264 skcms_Signature_A2B0 = 0x41324230,
265 skcms_Signature_A2B1 = 0x41324231,
266 skcms_Signature_mAB = 0x6D414220,
267
268 skcms_Signature_CHAD = 0x63686164,
269
270 // Type signatures
271 skcms_Signature_curv = 0x63757276,
272 skcms_Signature_mft1 = 0x6D667431,
273 skcms_Signature_mft2 = 0x6D667432,
274 skcms_Signature_para = 0x70617261,
275 skcms_Signature_sf32 = 0x73663332,
276 // XYZ is also a PCS signature, so it's defined in skcms.h
277 // skcms_Signature_XYZ = 0x58595A20,
278};
279
280static uint16_t read_big_u16(const uint8_t* ptr) {
281 uint16_t be;
282 memcpy(&be, ptr, sizeof(be));
283#if defined(_MSC_VER)
284 return _byteswap_ushort(be);
285#else
286 return __builtin_bswap16(be);
287#endif
288}
289
290static uint32_t read_big_u32(const uint8_t* ptr) {
291 uint32_t be;
292 memcpy(&be, ptr, sizeof(be));
293#if defined(_MSC_VER)
294 return _byteswap_ulong(be);
295#else
296 return __builtin_bswap32(be);
297#endif
298}
299
300static int32_t read_big_i32(const uint8_t* ptr) {
301 return (int32_t)read_big_u32(ptr);
302}
303
304static float read_big_fixed(const uint8_t* ptr) {
305 return read_big_i32(ptr) * (1.0f / 65536.0f);
306}
307
308// Maps to an in-memory profile so that fields line up to the locations specified
309// in ICC.1:2010, section 7.2
310typedef struct {
311 uint8_t size [ 4];
312 uint8_t cmm_type [ 4];
313 uint8_t version [ 4];
314 uint8_t profile_class [ 4];
315 uint8_t data_color_space [ 4];
316 uint8_t pcs [ 4];
317 uint8_t creation_date_time [12];
318 uint8_t signature [ 4];
319 uint8_t platform [ 4];
320 uint8_t flags [ 4];
321 uint8_t device_manufacturer [ 4];
322 uint8_t device_model [ 4];
323 uint8_t device_attributes [ 8];
324 uint8_t rendering_intent [ 4];
325 uint8_t illuminant_X [ 4];
326 uint8_t illuminant_Y [ 4];
327 uint8_t illuminant_Z [ 4];
328 uint8_t creator [ 4];
329 uint8_t profile_id [16];
330 uint8_t reserved [28];
331 uint8_t tag_count [ 4]; // Technically not part of header, but required
332} header_Layout;
333
334typedef struct {
335 uint8_t signature [4];
336 uint8_t offset [4];
337 uint8_t size [4];
338} tag_Layout;
339
340static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
341 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
342}
343
344// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
345// use of the type is for the CHAD tag that stores exactly nine values.
346typedef struct {
347 uint8_t type [ 4];
348 uint8_t reserved [ 4];
349 uint8_t values [36];
350} sf32_Layout;
351
352bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
353 skcms_ICCTag tag;
354 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
355 return false;
356 }
357
358 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
359 return false;
360 }
361
362 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
363 const uint8_t* values = sf32Tag->values;
364 for (int r = 0; r < 3; ++r)
365 for (int c = 0; c < 3; ++c, values += 4) {
366 m->vals[r][c] = read_big_fixed(values);
367 }
368 return true;
369}
370
371// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
372// the type are for tags/data that store exactly one triple.
373typedef struct {
374 uint8_t type [4];
375 uint8_t reserved [4];
376 uint8_t X [4];
377 uint8_t Y [4];
378 uint8_t Z [4];
379} XYZ_Layout;
380
381static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
382 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
383 return false;
384 }
385
386 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
387
388 *x = read_big_fixed(xyzTag->X);
389 *y = read_big_fixed(xyzTag->Y);
390 *z = read_big_fixed(xyzTag->Z);
391 return true;
392}
393
394static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
395 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
396 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
397 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
398 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
399}
400
401typedef struct {
402 uint8_t type [4];
403 uint8_t reserved_a [4];
404 uint8_t function_type [2];
405 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000406 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 +0000407} para_Layout;
408
409static bool read_curve_para(const uint8_t* buf, uint32_t size,
410 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000411 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000412 return false;
413 }
414
415 const para_Layout* paraTag = (const para_Layout*)buf;
416
417 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
418 uint16_t function_type = read_big_u16(paraTag->function_type);
419 if (function_type > kGABCDEF) {
420 return false;
421 }
422
423 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 +0000424 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 +0000425 return false;
426 }
427
428 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000429 *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 +0000430 }
431
432 curve->table_entries = 0;
433 curve->parametric.a = 1.0f;
434 curve->parametric.b = 0.0f;
435 curve->parametric.c = 0.0f;
436 curve->parametric.d = 0.0f;
437 curve->parametric.e = 0.0f;
438 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000439 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000440
441 switch (function_type) {
442 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000443 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
444 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000445 if (curve->parametric.a == 0) {
446 return false;
447 }
448 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
449 break;
450 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000451 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
452 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
453 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000454 if (curve->parametric.a == 0) {
455 return false;
456 }
457 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
458 curve->parametric.f = curve->parametric.e;
459 break;
460 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000461 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
462 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
463 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
464 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000465 break;
466 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000467 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
468 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
469 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
470 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
471 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
472 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000473 break;
474 }
skia-autorollf6d50642019-10-14 15:31:13 +0000475 return classify(curve->parametric) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000476}
477
478typedef struct {
479 uint8_t type [4];
480 uint8_t reserved [4];
481 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000482 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 +0000483} curv_Layout;
484
485static bool read_curve_curv(const uint8_t* buf, uint32_t size,
486 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000487 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000488 return false;
489 }
490
491 const curv_Layout* curvTag = (const curv_Layout*)buf;
492
493 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 +0000494 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 +0000495 return false;
496 }
497
498 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000499 *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 +0000500 }
501
502 if (value_count < 2) {
503 curve->table_entries = 0;
504 curve->parametric.a = 1.0f;
505 curve->parametric.b = 0.0f;
506 curve->parametric.c = 0.0f;
507 curve->parametric.d = 0.0f;
508 curve->parametric.e = 0.0f;
509 curve->parametric.f = 0.0f;
510 if (value_count == 0) {
511 // Empty tables are a shorthand for an identity curve
512 curve->parametric.g = 1.0f;
513 } else {
514 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000515 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 +0000516 }
517 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000518 curve->table_8 = nullptr;
519 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000520 curve->table_entries = value_count;
521 }
522
523 return true;
524}
525
526// 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 +0000527// 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 +0000528static bool read_curve(const uint8_t* buf, uint32_t size,
529 skcms_Curve* curve, uint32_t* curve_size) {
530 if (!buf || size < 4 || !curve) {
531 return false;
532 }
533
534 uint32_t type = read_big_u32(buf);
535 if (type == skcms_Signature_para) {
536 return read_curve_para(buf, size, curve, curve_size);
537 } else if (type == skcms_Signature_curv) {
538 return read_curve_curv(buf, size, curve, curve_size);
539 }
540
541 return false;
542}
543
544// mft1 and mft2 share a large chunk of data
545typedef struct {
546 uint8_t type [ 4];
547 uint8_t reserved_a [ 4];
548 uint8_t input_channels [ 1];
549 uint8_t output_channels [ 1];
550 uint8_t grid_points [ 1];
551 uint8_t reserved_b [ 1];
552 uint8_t matrix [36];
553} mft_CommonLayout;
554
555typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000556 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000557
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000558 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000559} mft1_Layout;
560
561typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000562 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000563
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000564 uint8_t input_table_entries [2];
565 uint8_t output_table_entries [2];
566 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000567} mft2_Layout;
568
569static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
570 // MFT matrices are applied before the first set of curves, but must be identity unless the
571 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
572 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
573 // field/flag.
574 a2b->matrix_channels = 0;
575
576 a2b->input_channels = mftTag->input_channels[0];
577 a2b->output_channels = mftTag->output_channels[0];
578
579 // We require exactly three (ie XYZ/Lab/RGB) output channels
580 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
581 return false;
582 }
583 // We require at least one, and no more than four (ie CMYK) input channels
584 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
585 return false;
586 }
587
588 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
589 a2b->grid_points[i] = mftTag->grid_points[0];
590 }
591 // The grid only makes sense with at least two points along each axis
592 if (a2b->grid_points[0] < 2) {
593 return false;
594 }
595
596 return true;
597}
598
599static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
600 uint32_t input_table_entries, uint32_t output_table_entries,
601 skcms_A2B* a2b) {
602 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
603 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
604 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
605
606 // [input|output]_channels are <= 4, so still no overflow
607 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
608 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
609
610 uint64_t grid_size = a2b->output_channels * byte_width;
611 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
612 grid_size *= a2b->grid_points[axis];
613 }
614
615 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
616 return false;
617 }
618
619 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
620 a2b->input_curves[i].table_entries = input_table_entries;
621 if (byte_width == 1) {
622 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 +0000623 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000624 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000625 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000626 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
627 }
628 }
629
630 if (byte_width == 1) {
631 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 +0000632 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000633 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000634 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000635 a2b->grid_16 = table_base + byte_len_all_input_tables;
636 }
637
638 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
639 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
640 a2b->output_curves[i].table_entries = output_table_entries;
641 if (byte_width == 1) {
642 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 +0000643 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000644 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000645 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000646 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
647 }
648 }
649
650 return true;
651}
652
653static 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 +0000654 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000655 return false;
656 }
657
658 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
659 if (!read_mft_common(mftTag->common, a2b)) {
660 return false;
661 }
662
663 uint32_t input_table_entries = 256;
664 uint32_t output_table_entries = 256;
665
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000666 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 +0000667 input_table_entries, output_table_entries, a2b);
668}
669
670static 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 +0000671 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000672 return false;
673 }
674
675 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
676 if (!read_mft_common(mftTag->common, a2b)) {
677 return false;
678 }
679
680 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
681 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
682
683 // ICC spec mandates that 2 <= table_entries <= 4096
684 if (input_table_entries < 2 || input_table_entries > 4096 ||
685 output_table_entries < 2 || output_table_entries > 4096) {
686 return false;
687 }
688
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000689 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 +0000690 input_table_entries, output_table_entries, a2b);
691}
692
693static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
694 uint32_t num_curves, skcms_Curve* curves) {
695 for (uint32_t i = 0; i < num_curves; ++i) {
696 if (curve_offset > size) {
697 return false;
698 }
699
700 uint32_t curve_bytes;
701 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
702 return false;
703 }
704
705 if (curve_bytes > UINT32_MAX - 3) {
706 return false;
707 }
708 curve_bytes = (curve_bytes + 3) & ~3U;
709
710 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
711 curve_offset = (uint32_t)new_offset_64;
712 if (new_offset_64 != curve_offset) {
713 return false;
714 }
715 }
716
717 return true;
718}
719
720typedef struct {
721 uint8_t type [ 4];
722 uint8_t reserved_a [ 4];
723 uint8_t input_channels [ 1];
724 uint8_t output_channels [ 1];
725 uint8_t reserved_b [ 2];
726 uint8_t b_curve_offset [ 4];
727 uint8_t matrix_offset [ 4];
728 uint8_t m_curve_offset [ 4];
729 uint8_t clut_offset [ 4];
730 uint8_t a_curve_offset [ 4];
731} mAB_Layout;
732
733typedef struct {
734 uint8_t grid_points [16];
735 uint8_t grid_byte_width [ 1];
736 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000737 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000738} mABCLUT_Layout;
739
740static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
741 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
742 return false;
743 }
744
745 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
746
747 a2b->input_channels = mABTag->input_channels[0];
748 a2b->output_channels = mABTag->output_channels[0];
749
750 // We require exactly three (ie XYZ/Lab/RGB) output channels
751 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
752 return false;
753 }
754 // We require no more than four (ie CMYK) input channels
755 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
756 return false;
757 }
758
759 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
760 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
761 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
762 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
763 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
764
765 // "B" curves must be present
766 if (0 == b_curve_offset) {
767 return false;
768 }
769
770 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
771 a2b->output_curves)) {
772 return false;
773 }
774
775 // "M" curves and Matrix must be used together
776 if (0 != m_curve_offset) {
777 if (0 == matrix_offset) {
778 return false;
779 }
780 a2b->matrix_channels = a2b->output_channels;
781 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
782 a2b->matrix_curves)) {
783 return false;
784 }
785
786 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
787 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
788 return false;
789 }
790 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
791 const uint8_t* mtx_buf = tag->buf + matrix_offset;
792 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
793 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
794 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
795 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
796 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
797 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
798 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
799 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
800 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
801 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
802 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
803 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
804 } else {
805 if (0 != matrix_offset) {
806 return false;
807 }
808 a2b->matrix_channels = 0;
809 }
810
811 // "A" curves and CLUT must be used together
812 if (0 != a_curve_offset) {
813 if (0 == clut_offset) {
814 return false;
815 }
816 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
817 a2b->input_curves)) {
818 return false;
819 }
820
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000821 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 +0000822 return false;
823 }
824 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
825
826 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000827 a2b->grid_8 = clut->variable;
828 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000829 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000830 a2b->grid_8 = nullptr;
831 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000832 } else {
833 return false;
834 }
835
836 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
837 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
838 a2b->grid_points[i] = clut->grid_points[i];
839 // The grid only makes sense with at least two points along each axis
840 if (a2b->grid_points[i] < 2) {
841 return false;
842 }
843 grid_size *= a2b->grid_points[i];
844 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000845 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 +0000846 return false;
847 }
848 } else {
849 if (0 != clut_offset) {
850 return false;
851 }
852
853 // If there is no CLUT, the number of input and output channels must match
854 if (a2b->input_channels != a2b->output_channels) {
855 return false;
856 }
857
858 // Zero out the number of input channels to signal that we're skipping this stage
859 a2b->input_channels = 0;
860 }
861
862 return true;
863}
864
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000865// If you pass f, we'll fit a possibly-non-zero value for *f.
866// If you pass nullptr, we'll assume you want *f to be treated as zero.
867static int fit_linear(const skcms_Curve* curve, int N, float tol,
868 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000869 assert(N > 1);
870 // We iteratively fit the first points to the TF's linear piece.
871 // We want the cx + f line to pass through the first and last points we fit exactly.
872 //
873 // As we walk along the points we find the minimum and maximum slope of the line before the
874 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
875 // emtpy, when we definitely can't add any more points.
876 //
877 // Some points' error intervals may intersect the running interval but not lie fully
878 // within it. So we keep track of the last point we saw that is a valid end point candidate,
879 // and once the search is done, back up to build the line through *that* point.
880 const float dx = 1.0f / (N - 1);
881
882 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000883
884 float f_zero = 0.0f;
885 if (f) {
886 *f = eval_curve(curve, 0);
887 } else {
888 f = &f_zero;
889 }
890
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000891
892 float slope_min = -INFINITY_;
893 float slope_max = +INFINITY_;
894 for (int i = 1; i < N; ++i) {
895 float x = i * dx;
896 float y = eval_curve(curve, x);
897
898 float slope_max_i = (y + tol - *f) / x,
899 slope_min_i = (y - tol - *f) / x;
900 if (slope_max_i < slope_min || slope_max < slope_min_i) {
901 // Slope intervals would no longer overlap.
902 break;
903 }
904 slope_max = fminf_(slope_max, slope_max_i);
905 slope_min = fmaxf_(slope_min, slope_min_i);
906
907 float cur_slope = (y - *f) / x;
908 if (slope_min <= cur_slope && cur_slope <= slope_max) {
909 lin_points = i + 1;
910 *c = cur_slope;
911 }
912 }
913
914 // Set D to the last point that met our tolerance.
915 *d = (lin_points - 1) * dx;
916 return lin_points;
917}
918
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000919static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
920 bool ok = false;
921 if (tag->type == skcms_Signature_mft1) {
922 ok = read_tag_mft1(tag, a2b);
923 } else if (tag->type == skcms_Signature_mft2) {
924 ok = read_tag_mft2(tag, a2b);
925 } else if (tag->type == skcms_Signature_mAB) {
926 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
927 }
928 if (!ok) {
929 return false;
930 }
931
932 // Detect and canonicalize identity tables.
933 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000934 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
935 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
936 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
937 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
938 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
939 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
940 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
941 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
942 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
943 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000944 };
945
946 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
947 skcms_Curve* curve = curves[i];
948
949 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
950 int N = (int)curve->table_entries;
951
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000952 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 +0000953 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 +0000954 && c == 1.0f
955 && f == 0.0f) {
956 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000957 curve->table_8 = nullptr;
958 curve->table_16 = nullptr;
959 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 +0000960 }
961 }
962 }
963
964 return true;
965}
966
967void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
968 if (!profile || !profile->buffer || !tag) { return; }
969 if (idx > profile->tag_count) { return; }
970 const tag_Layout* tags = get_tag_table(profile);
971 tag->signature = read_big_u32(tags[idx].signature);
972 tag->size = read_big_u32(tags[idx].size);
973 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
974 tag->type = read_big_u32(tag->buf);
975}
976
977bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
978 if (!profile || !profile->buffer || !tag) { return false; }
979 const tag_Layout* tags = get_tag_table(profile);
980 for (uint32_t i = 0; i < profile->tag_count; ++i) {
981 if (read_big_u32(tags[i].signature) == sig) {
982 tag->signature = sig;
983 tag->size = read_big_u32(tags[i].size);
984 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
985 tag->type = read_big_u32(tag->buf);
986 return true;
987 }
988 }
989 return false;
990}
991
992static bool usable_as_src(const skcms_ICCProfile* profile) {
993 return profile->has_A2B
994 || (profile->has_trc && profile->has_toXYZD50);
995}
996
997bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
998 assert(SAFE_SIZEOF(header_Layout) == 132);
999
1000 if (!profile) {
1001 return false;
1002 }
1003 memset(profile, 0, SAFE_SIZEOF(*profile));
1004
1005 if (len < SAFE_SIZEOF(header_Layout)) {
1006 return false;
1007 }
1008
1009 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001010 const header_Layout* header = (const header_Layout*)buf;
1011 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001012 profile->size = read_big_u32(header->size);
1013 uint32_t version = read_big_u32(header->version);
1014 profile->data_color_space = read_big_u32(header->data_color_space);
1015 profile->pcs = read_big_u32(header->pcs);
1016 uint32_t signature = read_big_u32(header->signature);
1017 float illuminant_X = read_big_fixed(header->illuminant_X);
1018 float illuminant_Y = read_big_fixed(header->illuminant_Y);
1019 float illuminant_Z = read_big_fixed(header->illuminant_Z);
1020 profile->tag_count = read_big_u32(header->tag_count);
1021
1022 // Validate signature, size (smaller than buffer, large enough to hold tag table),
1023 // and major version
1024 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1025 if (signature != skcms_Signature_acsp ||
1026 profile->size > len ||
1027 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1028 (version >> 24) > 4) {
1029 return false;
1030 }
1031
1032 // Validate that illuminant is D50 white
1033 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1034 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1035 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1036 return false;
1037 }
1038
1039 // Validate that all tag entries have sane offset + size
1040 const tag_Layout* tags = get_tag_table(profile);
1041 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1042 uint32_t tag_offset = read_big_u32(tags[i].offset);
1043 uint32_t tag_size = read_big_u32(tags[i].size);
1044 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
1045 if (tag_size < 4 || tag_end > profile->size) {
1046 return false;
1047 }
1048 }
1049
1050 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1051 return false;
1052 }
1053
1054 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1055
1056 // Pre-parse commonly used tags.
1057 skcms_ICCTag kTRC;
1058 if (profile->data_color_space == skcms_Signature_Gray &&
1059 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001060 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 +00001061 // Malformed tag
1062 return false;
1063 }
1064 profile->trc[1] = profile->trc[0];
1065 profile->trc[2] = profile->trc[0];
1066 profile->has_trc = true;
1067
1068 if (pcs_is_xyz) {
1069 profile->toXYZD50.vals[0][0] = illuminant_X;
1070 profile->toXYZD50.vals[1][1] = illuminant_Y;
1071 profile->toXYZD50.vals[2][2] = illuminant_Z;
1072 profile->has_toXYZD50 = true;
1073 }
1074 } else {
1075 skcms_ICCTag rTRC, gTRC, bTRC;
1076 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1077 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1078 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001079 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1080 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1081 !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 +00001082 // Malformed TRC tags
1083 return false;
1084 }
1085 profile->has_trc = true;
1086 }
1087
1088 skcms_ICCTag rXYZ, gXYZ, bXYZ;
1089 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1090 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1091 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1092 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1093 // Malformed XYZ tags
1094 return false;
1095 }
1096 profile->has_toXYZD50 = true;
1097 }
1098 }
1099
1100 skcms_ICCTag a2b_tag;
1101
1102 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
1103 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
1104 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
1105 // and all our known users are thinking exclusively in terms of relative colormetric.
1106 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
1107 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
1108 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
1109 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
1110 // Malformed A2B tag
1111 return false;
1112 }
1113 profile->has_A2B = true;
1114 break;
1115 }
1116 }
1117
1118 return usable_as_src(profile);
1119}
1120
1121
1122const skcms_ICCProfile* skcms_sRGB_profile() {
1123 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001124 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001125
1126 0, // size, moot here
1127 skcms_Signature_RGB, // data_color_space
1128 skcms_Signature_XYZ, // pcs
1129 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001130
1131 // We choose to represent sRGB with its canonical transfer function,
1132 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001133 true, // has_trc, followed by the 3 trc curves
1134 {
1135 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1136 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1137 {{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 +00001138 },
1139
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001140 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1141 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001142 { 0.436065674f, 0.385147095f, 0.143066406f },
1143 { 0.222488403f, 0.716873169f, 0.060607910f },
1144 { 0.013916016f, 0.097076416f, 0.714096069f },
1145 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001146
1147 false, // has_A2B, followed by a2b itself which we don't care about.
1148 {
1149 0,
1150 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001151 {{0, {0,0, 0,0,0,0,0}}},
1152 {{0, {0,0, 0,0,0,0,0}}},
1153 {{0, {0,0, 0,0,0,0,0}}},
1154 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001155 },
1156 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001157 nullptr,
1158 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001159
1160 0,
1161 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001162 {{0, {0,0, 0,0,0,0,0}}},
1163 {{0, {0,0, 0,0,0,0,0}}},
1164 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001165 },
1166 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001167 { 0,0,0,0 },
1168 { 0,0,0,0 },
1169 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001170 }},
1171
1172 0,
1173 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001174 {{0, {0,0, 0,0,0,0,0}}},
1175 {{0, {0,0, 0,0,0,0,0}}},
1176 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001177 },
1178 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001179 };
1180 return &sRGB_profile;
1181}
1182
1183const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001184 // 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 +00001185 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001186 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001187
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001188 0, // size, moot here
1189 skcms_Signature_RGB, // data_color_space
1190 skcms_Signature_XYZ, // pcs
1191 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001192
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001193 true, // has_trc, followed by the 3 trc curves
1194 {
1195 {{0, {1,1, 0,0,0,0,0}}},
1196 {{0, {1,1, 0,0,0,0,0}}},
1197 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001198 },
1199
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001200 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1201 {{
1202 { 1,0,0 },
1203 { 0,1,0 },
1204 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001205 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001206
1207 false, // has_A2B, followed by a2b itself which we don't care about.
1208 {
1209 0,
1210 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001211 {{0, {0,0, 0,0,0,0,0}}},
1212 {{0, {0,0, 0,0,0,0,0}}},
1213 {{0, {0,0, 0,0,0,0,0}}},
1214 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001215 },
1216 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001217 nullptr,
1218 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001219
1220 0,
1221 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001222 {{0, {0,0, 0,0,0,0,0}}},
1223 {{0, {0,0, 0,0,0,0,0}}},
1224 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001225 },
1226 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001227 { 0,0,0,0 },
1228 { 0,0,0,0 },
1229 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001230 }},
1231
1232 0,
1233 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001234 {{0, {0,0, 0,0,0,0,0}}},
1235 {{0, {0,0, 0,0,0,0,0}}},
1236 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001237 },
1238 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001239 };
1240
1241 return &XYZD50_profile;
1242}
1243
1244const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1245 return &skcms_sRGB_profile()->trc[0].parametric;
1246}
1247
1248const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1249 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001250 {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 +00001251 return &sRGB_inv;
1252}
1253
1254const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1255 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1256 return &identity;
1257}
1258
1259const uint8_t skcms_252_random_bytes[] = {
1260 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1261 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1262 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1263 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1264 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1265 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1266 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1267 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1268 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1269 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1270 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1271 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1272 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1273 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1274 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1275 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1276};
1277
1278bool 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 +00001279 // Test for exactly equal profiles first.
1280 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1281 return true;
1282 }
1283
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001284 // For now this is the essentially the same strategy we use in test_only.c
1285 // for our skcms_Transform() smoke tests:
1286 // 1) transform A to XYZD50
1287 // 2) transform B to XYZD50
1288 // 3) return true if they're similar enough
1289 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1290
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001291 // 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 +00001292 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1293
1294 if (A->data_color_space != B->data_color_space) {
1295 return false;
1296 }
1297
1298 // 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 +00001299 // 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 +00001300 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1301 size_t npixels = 84;
1302 if (A->data_color_space == skcms_Signature_CMYK) {
1303 fmt = skcms_PixelFormat_RGBA_8888;
1304 npixels = 63;
1305 }
1306
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001307 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1308 // 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 +00001309 uint8_t dstA[252],
1310 dstB[252];
1311 if (!skcms_Transform(
1312 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1313 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1314 npixels)) {
1315 return false;
1316 }
1317 if (!skcms_Transform(
1318 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1319 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1320 npixels)) {
1321 return false;
1322 }
1323
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001324 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001325 for (size_t i = 0; i < 252; i++) {
1326 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1327 return false;
1328 }
1329 }
1330 return true;
1331}
1332
1333bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1334 const skcms_TransferFunction* inv_tf) {
1335 if (!profile || !profile->has_trc) {
1336 return false;
1337 }
1338
1339 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1340 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1341 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1342}
1343
1344static bool is_zero_to_one(float x) {
1345 return 0 <= x && x <= 1;
1346}
1347
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001348typedef struct { float vals[3]; } skcms_Vector3;
1349
1350static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1351 skcms_Vector3 dst = {{0,0,0}};
1352 for (int row = 0; row < 3; ++row) {
1353 dst.vals[row] = m->vals[row][0] * v->vals[0]
1354 + m->vals[row][1] * v->vals[1]
1355 + m->vals[row][2] * v->vals[2];
1356 }
1357 return dst;
1358}
1359
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001360bool skcms_PrimariesToXYZD50(float rx, float ry,
1361 float gx, float gy,
1362 float bx, float by,
1363 float wx, float wy,
1364 skcms_Matrix3x3* toXYZD50) {
1365 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1366 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1367 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1368 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1369 !toXYZD50) {
1370 return false;
1371 }
1372
1373 // First, we need to convert xy values (primaries) to XYZ.
1374 skcms_Matrix3x3 primaries = {{
1375 { rx, gx, bx },
1376 { ry, gy, by },
1377 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1378 }};
1379 skcms_Matrix3x3 primaries_inv;
1380 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1381 return false;
1382 }
1383
1384 // Assumes that Y is 1.0f.
1385 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 +00001386 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001387
1388 skcms_Matrix3x3 toXYZ = {{
1389 { XYZ.vals[0], 0, 0 },
1390 { 0, XYZ.vals[1], 0 },
1391 { 0, 0, XYZ.vals[2] },
1392 }};
1393 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1394
1395 // Now convert toXYZ matrix to toXYZD50.
1396 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1397
1398 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1399 // the matrices below. The Bradford method is used by Adobe and is widely considered
1400 // to be the best.
1401 skcms_Matrix3x3 xyz_to_lms = {{
1402 { 0.8951f, 0.2664f, -0.1614f },
1403 { -0.7502f, 1.7135f, 0.0367f },
1404 { 0.0389f, -0.0685f, 1.0296f },
1405 }};
1406 skcms_Matrix3x3 lms_to_xyz = {{
1407 { 0.9869929f, -0.1470543f, 0.1599627f },
1408 { 0.4323053f, 0.5183603f, 0.0492912f },
1409 { -0.0085287f, 0.0400428f, 0.9684867f },
1410 }};
1411
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001412 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1413 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001414
1415 skcms_Matrix3x3 DXtoD50 = {{
1416 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1417 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1418 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1419 }};
1420 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1421 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1422
1423 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1424 return true;
1425}
1426
1427
1428bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1429 double a00 = src->vals[0][0],
1430 a01 = src->vals[1][0],
1431 a02 = src->vals[2][0],
1432 a10 = src->vals[0][1],
1433 a11 = src->vals[1][1],
1434 a12 = src->vals[2][1],
1435 a20 = src->vals[0][2],
1436 a21 = src->vals[1][2],
1437 a22 = src->vals[2][2];
1438
1439 double b0 = a00*a11 - a01*a10,
1440 b1 = a00*a12 - a02*a10,
1441 b2 = a01*a12 - a02*a11,
1442 b3 = a20,
1443 b4 = a21,
1444 b5 = a22;
1445
1446 double determinant = b0*b5
1447 - b1*b4
1448 + b2*b3;
1449
1450 if (determinant == 0) {
1451 return false;
1452 }
1453
1454 double invdet = 1.0 / determinant;
1455 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1456 return false;
1457 }
1458
1459 b0 *= invdet;
1460 b1 *= invdet;
1461 b2 *= invdet;
1462 b3 *= invdet;
1463 b4 *= invdet;
1464 b5 *= invdet;
1465
1466 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1467 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1468 dst->vals[2][0] = (float)( + b2 );
1469 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1470 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1471 dst->vals[2][1] = (float)( - b1 );
1472 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1473 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1474 dst->vals[2][2] = (float)( + b0 );
1475
1476 for (int r = 0; r < 3; ++r)
1477 for (int c = 0; c < 3; ++c) {
1478 if (!isfinitef_(dst->vals[r][c])) {
1479 return false;
1480 }
1481 }
1482 return true;
1483}
1484
1485skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1486 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1487 for (int r = 0; r < 3; r++)
1488 for (int c = 0; c < 3; c++) {
1489 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1490 + A->vals[r][1] * B->vals[1][c]
1491 + A->vals[r][2] * B->vals[2][c];
1492 }
1493 return m;
1494}
1495
skia-autorollacd6e012019-01-08 14:10:52 +00001496#if defined(__clang__)
skia-autorollf6d50642019-10-14 15:31:13 +00001497 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by classify() on the way out.
skia-autorollacd6e012019-01-08 14:10:52 +00001498#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001499bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
skia-autorollf6d50642019-10-14 15:31:13 +00001500 TF_PQish pq;
1501 TF_HLGish hlg;
1502 switch (classify(*src, &pq, &hlg)) {
1503 case Bad: return false;
1504 case sRGBish: break; // handled below
1505
1506 case PQish:
1507 *dst = { TFKind_marker(PQish), -pq.A, pq.D, 1.0f/pq.F
1508 , pq.B, -pq.E, 1.0f/pq.C};
1509 return true;
1510
1511 case HLGish:
1512 *dst = { TFKind_marker(HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
1513 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1514 return true;
1515
1516 case HLGinvish:
1517 *dst = { TFKind_marker(HLGish), 1.0f/hlg.R, 1.0f/hlg.G
1518 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1519 return true;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001520 }
1521
skia-autorollf6d50642019-10-14 15:31:13 +00001522 assert (classify(*src) == sRGBish);
1523
skia-autorolld0b577f2019-01-07 19:46:57 +00001524 // We're inverting this function, solving for x in terms of y.
1525 // y = (cx + f) x < d
1526 // (ax + b)^g + e x ≥ d
1527 // The inverse of this function can be expressed in the same piecewise form.
1528 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 +00001529
skia-autorolld0b577f2019-01-07 19:46:57 +00001530 // We'll start by finding the new threshold inv.d.
1531 // In principle we should be able to find that by solving for y at x=d from either side.
1532 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1533 float d_l = src->c * src->d + src->f,
1534 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1535 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001536 return false;
1537 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001538 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 +00001539
skia-autorolld0b577f2019-01-07 19:46:57 +00001540 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1541 if (inv.d > 0) {
1542 // Inverting the linear section is pretty straightfoward:
1543 // y = cx + f
1544 // y - f = cx
1545 // (1/c)y - f/c = x
1546 inv.c = 1.0f/src->c;
1547 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001548 }
1549
skia-autorolld0b577f2019-01-07 19:46:57 +00001550 // The interesting part is inverting the nonlinear section:
1551 // y = (ax + b)^g + e.
1552 // y - e = (ax + b)^g
1553 // (y - e)^1/g = ax + b
1554 // (y - e)^1/g - b = ax
1555 // (1/a)(y - e)^1/g - b/a = x
1556 //
1557 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1558 // let k = (1/a)^g
1559 // (1/a)( y - e)^1/g - b/a = x
1560 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001561
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001562 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001563 inv.g = 1.0f / src->g;
1564 inv.a = k;
1565 inv.b = -k * src->e;
1566 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001567
skia-autorollfe16a332019-08-20 19:44:54 +00001568 // We need to enforce the same constraints here that we do when fitting a curve,
skia-autorollf6d50642019-10-14 15:31:13 +00001569 // a >= 0 and ad+b >= 0. These constraints are checked by classify(), so they're true
skia-autorollbf388232019-08-21 14:17:54 +00001570 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001571
skia-autorollbf388232019-08-21 14:17:54 +00001572 // Just like when fitting the curve, there's really no way to rescue a < 0.
1573 if (inv.a < 0) {
1574 return false;
1575 }
1576 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001577 if (inv.a * inv.d + inv.b < 0) {
1578 inv.b = -inv.a * inv.d;
1579 }
skia-autorollbf388232019-08-21 14:17:54 +00001580
skia-autorollf6d50642019-10-14 15:31:13 +00001581 // That should usually make classify(inv) == sRGBish true, but there are a couple situations
skia-autorolla0ed0702019-08-23 16:49:54 +00001582 // where we might still fail here, like non-finite parameter values.
skia-autorollf6d50642019-10-14 15:31:13 +00001583 if (classify(inv) != sRGBish) {
skia-autorolla0ed0702019-08-23 16:49:54 +00001584 return false;
1585 }
1586
skia-autorollbf388232019-08-21 14:17:54 +00001587 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001588 assert (inv.a * inv.d + inv.b >= 0);
1589
skia-autorolla7b28742019-01-09 18:35:46 +00001590 // Now in principle we're done.
skia-autorollad10df62019-08-21 15:14:54 +00001591 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
1592 // e or f of the inverse, depending on which segment contains src(1.0f).
1593 float s = skcms_TransferFunction_eval(src, 1.0f);
skia-autorolla0ed0702019-08-23 16:49:54 +00001594 if (!isfinitef_(s)) {
1595 return false;
1596 }
1597
skia-autorollad10df62019-08-21 15:14:54 +00001598 float sign = s < 0 ? -1.0f : 1.0f;
1599 s *= sign;
1600 if (s < inv.d) {
1601 inv.f = 1.0f - sign * inv.c * s;
1602 } else {
1603 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
1604 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001605
skia-autorolld0b577f2019-01-07 19:46:57 +00001606 *dst = inv;
skia-autorollf6d50642019-10-14 15:31:13 +00001607 return classify(*dst) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001608}
1609
1610// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1611
1612// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1613//
1614// tf(x) = cx + f x < d
1615// tf(x) = (ax + b)^g + e x ≥ d
1616//
1617// When fitting, we add the additional constraint that both pieces meet at d:
1618//
1619// cd + f = (ad + b)^g + e
1620//
1621// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1622//
1623// tf(x) = cx + f x < d
1624// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1625//
1626// Our overall strategy is then:
1627// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001628// - 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 +00001629// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001630// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1631// (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 +00001632// Return the parameters with least maximum error.
1633//
1634// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1635// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1636//
1637// let y = Table(x)
1638// r(x) = x - f_inv(y)
1639//
1640// ∂r/∂g = ln(ay + b)*(ay + b)^g
1641// - ln(ad + b)*(ad + b)^g
1642// ∂r/∂a = yg(ay + b)^(g-1)
1643// - dg(ad + b)^(g-1)
1644// ∂r/∂b = g(ay + b)^(g-1)
1645// - g(ad + b)^(g-1)
1646
1647// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1648// and fill out the gradient of the residual into dfdP.
1649static float rg_nonlinear(float x,
1650 const skcms_Curve* curve,
1651 const skcms_TransferFunction* tf,
1652 const float P[3],
1653 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001654 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001655
1656 const float g = P[0], a = P[1], b = P[2],
1657 c = tf->c, d = tf->d, f = tf->f;
1658
1659 const float Y = fmaxf_(a*y + b, 0.0f),
1660 D = a*d + b;
1661 assert (D >= 0);
1662
1663 // The gradient.
skia-autoroll5f8588c2019-10-14 13:54:14 +00001664 dfdP[0] = logf_(Y)*powf_(Y, g)
1665 - logf_(D)*powf_(D, g);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001666 dfdP[1] = y*g*powf_(Y, g-1)
1667 - d*g*powf_(D, g-1);
1668 dfdP[2] = g*powf_(Y, g-1)
1669 - g*powf_(D, g-1);
1670
1671 // The residual.
1672 const float f_inv = powf_(Y, g)
1673 - powf_(D, g)
1674 + c*d + f;
1675 return x - f_inv;
1676}
1677
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001678static bool gauss_newton_step(const skcms_Curve* curve,
1679 const skcms_TransferFunction* tf,
1680 float P[3],
1681 float x0, float dx, int N) {
1682 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1683 //
1684 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1685 // where r(P) is the residual vector
1686 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1687 //
1688 // Let's review the shape of each of these expressions:
1689 // r(P) is [N x 1], a column vector with one entry per value of x tested
1690 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1691 // Jf^T is [3 x N], the transpose of Jf
1692 //
1693 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1694 // and so is its inverse (Jf^T Jf)^-1
1695 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1696 //
1697 // Our implementation strategy to get to the final ∆P is
1698 // 1) evaluate Jf^T Jf, call that lhs
1699 // 2) evaluate Jf^T r(P), call that rhs
1700 // 3) invert lhs
1701 // 4) multiply inverse lhs by rhs
1702 //
1703 // This is a friendly implementation strategy because we don't have to have any
1704 // buffers that scale with N, and equally nice don't have to perform any matrix
1705 // operations that are variable size.
1706 //
1707 // Other implementation strategies could trade this off, e.g. evaluating the
1708 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1709 // the residuals. That would probably require implementing singular value
1710 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1711 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1712 // possibility of this gauss_newton_step() function ever failing.
1713
1714 // 0) start off with lhs and rhs safely zeroed.
1715 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1716 skcms_Vector3 rhs = { {0,0,0} };
1717
1718 // 1,2) evaluate lhs and evaluate rhs
1719 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1720 // so we'll have to update lhs and rhs at the same time.
1721 for (int i = 0; i < N; i++) {
1722 float x = x0 + i*dx;
1723
1724 float dfdP[3] = {0,0,0};
1725 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1726
1727 for (int r = 0; r < 3; r++) {
1728 for (int c = 0; c < 3; c++) {
1729 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1730 }
1731 rhs.vals[r] += dfdP[r] * resid;
1732 }
1733 }
1734
1735 // If any of the 3 P parameters are unused, this matrix will be singular.
1736 // Detect those cases and fix them up to indentity instead, so we can invert.
1737 for (int k = 0; k < 3; k++) {
1738 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1739 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1740 lhs.vals[k][k] = 1;
1741 }
1742 }
1743
1744 // 3) invert lhs
1745 skcms_Matrix3x3 lhs_inv;
1746 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1747 return false;
1748 }
1749
1750 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001751 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001752 P[0] += dP.vals[0];
1753 P[1] += dP.vals[1];
1754 P[2] += dP.vals[2];
1755 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1756}
1757
1758
1759// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1760static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1761 float P[3] = { tf->g, tf->a, tf->b };
1762
1763 // No matter where we start, dx should always represent N even steps from 0 to 1.
1764 const float dx = 1.0f / (N-1);
1765
skia-autorolld9718822019-08-23 18:16:54 +00001766 // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
1767 for (int j = 0; j < 2; j++) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001768 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1769 // We don't really know how to fix up a if it goes negative.
1770 if (P[1] < 0) {
1771 return false;
1772 }
1773 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1774 if (P[1] * tf->d + P[2] < 0) {
1775 P[2] = -P[1] * tf->d;
1776 }
1777 assert (P[1] >= 0 &&
1778 P[1] * tf->d + P[2] >= 0);
1779
1780 if (!gauss_newton_step(curve, tf,
1781 P,
1782 L*dx, dx, N-L)) {
1783 return false;
1784 }
1785 }
1786
1787 // We need to apply our fixups one last time
1788 if (P[1] < 0) {
1789 return false;
1790 }
1791 if (P[1] * tf->d + P[2] < 0) {
1792 P[2] = -P[1] * tf->d;
1793 }
1794
skia-autorollfe16a332019-08-20 19:44:54 +00001795 assert (P[1] >= 0 &&
1796 P[1] * tf->d + P[2] >= 0);
1797
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001798 tf->g = P[0];
1799 tf->a = P[1];
1800 tf->b = P[2];
1801 tf->e = tf->c*tf->d + tf->f
1802 - powf_(tf->a*tf->d + tf->b, tf->g);
1803 return true;
1804}
1805
1806bool skcms_ApproximateCurve(const skcms_Curve* curve,
1807 skcms_TransferFunction* approx,
1808 float* max_error) {
1809 if (!curve || !approx || !max_error) {
1810 return false;
1811 }
1812
1813 if (curve->table_entries == 0) {
1814 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1815 return false;
1816 }
1817
1818 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1819 // We need at least two points, and must put some reasonable cap on the maximum number.
1820 return false;
1821 }
1822
1823 int N = (int)curve->table_entries;
1824 const float dx = 1.0f / (N - 1);
1825
1826 *max_error = INFINITY_;
1827 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1828 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1829 skcms_TransferFunction tf,
1830 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001831
1832 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1833 tf.f = 0.0f;
1834 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 +00001835
1836 if (L == N) {
1837 // If the entire data set was linear, move the coefficients to the nonlinear portion
1838 // with G == 1. This lets use a canonical representation with d == 0.
1839 tf.g = 1;
1840 tf.a = tf.c;
1841 tf.b = tf.f;
1842 tf.c = tf.d = tf.e = tf.f = 0;
1843 } else if (L == N - 1) {
1844 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1845 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001846 tf.a = (eval_curve(curve, (N-1)*dx) -
1847 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001848 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001849 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001850 - tf.a * (N-2)*dx;
1851 tf.e = 0;
1852 } else {
1853 // Start by guessing a gamma-only curve through the midpoint.
1854 int mid = (L + N) / 2;
1855 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001856 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001857 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001858 tf.a = 1;
1859 tf.b = 0;
1860 tf.e = tf.c*tf.d + tf.f
1861 - powf_(tf.a*tf.d + tf.b, tf.g);
1862
1863
1864 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1865 !fit_nonlinear(curve, L,N, &tf_inv)) {
1866 continue;
1867 }
1868
1869 // We fit tf_inv, so calculate tf to keep in sync.
1870 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1871 continue;
1872 }
1873 }
1874
1875 // We find our error by roundtripping the table through tf_inv.
1876 //
1877 // (The most likely use case for this approximation is to be inverted and
1878 // used as the transfer function for a destination color space.)
1879 //
1880 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1881 // invertible, so re-verify that here (and use the new inverse for testing).
1882 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1883 continue;
1884 }
1885
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001886 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001887 if (*max_error > err) {
1888 *max_error = err;
1889 *approx = tf;
1890 }
1891 }
1892 return isfinitef_(*max_error);
1893}
1894
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001895// ~~~~ Impl. of skcms_Transform() ~~~~
1896
1897typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001898 Op_load_a8,
1899 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001900 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001901 Op_load_4444,
1902 Op_load_565,
1903 Op_load_888,
1904 Op_load_8888,
1905 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001906 Op_load_161616LE,
1907 Op_load_16161616LE,
1908 Op_load_161616BE,
1909 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001910 Op_load_hhh,
1911 Op_load_hhhh,
1912 Op_load_fff,
1913 Op_load_ffff,
1914
1915 Op_swap_rb,
1916 Op_clamp,
1917 Op_invert,
1918 Op_force_opaque,
1919 Op_premul,
1920 Op_unpremul,
1921 Op_matrix_3x3,
1922 Op_matrix_3x4,
1923 Op_lab_to_xyz,
1924
1925 Op_tf_r,
1926 Op_tf_g,
1927 Op_tf_b,
1928 Op_tf_a,
1929
skia-autorollf6d50642019-10-14 15:31:13 +00001930 Op_pq_r,
1931 Op_pq_g,
1932 Op_pq_b,
1933 Op_pq_a,
1934
1935 Op_hlg_r,
1936 Op_hlg_g,
1937 Op_hlg_b,
1938 Op_hlg_a,
1939
1940 Op_hlginv_r,
1941 Op_hlginv_g,
1942 Op_hlginv_b,
1943 Op_hlginv_a,
1944
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001945 Op_table_r,
1946 Op_table_g,
1947 Op_table_b,
1948 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001949
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001950 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001951
1952 Op_store_a8,
1953 Op_store_g8,
1954 Op_store_4444,
1955 Op_store_565,
1956 Op_store_888,
1957 Op_store_8888,
1958 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001959 Op_store_161616LE,
1960 Op_store_16161616LE,
1961 Op_store_161616BE,
1962 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001963 Op_store_hhh,
1964 Op_store_hhhh,
1965 Op_store_fff,
1966 Op_store_ffff,
1967} Op;
1968
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001969#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001970 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 +00001971#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001972 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1973 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1974 template <int N, typename T>
1975 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 +00001976
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001977 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 +00001978#endif
1979
1980// First, instantiate our default exec_ops() implementation using the default compiliation target.
1981
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001982namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00001983#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
1984 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001985 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00001986 template <typename T> using V = T;
1987 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001988#elif defined(__AVX512F__)
1989 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001990 template <typename T> using V = Vec<N,T>;
1991 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001992#elif defined(__AVX__)
1993 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001994 template <typename T> using V = Vec<N,T>;
1995 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00001996#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
1997 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001998 template <typename T> using V = Vec<N,T>;
1999 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002000#else
2001 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00002002 template <typename T> using V = Vec<N,T>;
2003 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002004#endif
2005
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002006 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002007 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002008}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002009
2010// 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 +00002011#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00002012 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00002013 (( defined(__clang__) && __clang_major__ >= 5) || \
2014 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00002015 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002016
skia-autorollba6087c2019-04-09 13:57:02 +00002017 #if !defined(__AVX2__)
2018 #if defined(__clang__)
2019 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
2020 #elif defined(__GNUC__)
2021 #pragma GCC push_options
2022 #pragma GCC target("avx2,f16c")
2023 #endif
2024
2025 namespace hsw {
2026 #define USING_AVX
2027 #define USING_AVX_F16C
2028 #define USING_AVX2
2029 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002030 template <typename T> using V = Vec<N,T>;
2031 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00002032
2033 #include "src/Transform_inl.h"
2034
2035 // src/Transform_inl.h will undefine USING_* for us.
2036 #undef N
2037 }
2038
2039 #if defined(__clang__)
2040 #pragma clang attribute pop
2041 #elif defined(__GNUC__)
2042 #pragma GCC pop_options
2043 #endif
2044
2045 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002046 #endif
2047
skia-autorollba6087c2019-04-09 13:57:02 +00002048 #if !defined(__AVX512F__)
2049 #if defined(__clang__)
2050 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
2051 #elif defined(__GNUC__)
2052 #pragma GCC push_options
2053 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
2054 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002055
skia-autorollba6087c2019-04-09 13:57:02 +00002056 namespace skx {
2057 #define USING_AVX512F
2058 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002059 template <typename T> using V = Vec<N,T>;
2060 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002061
skia-autorollba6087c2019-04-09 13:57:02 +00002062 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002063
skia-autorollba6087c2019-04-09 13:57:02 +00002064 // src/Transform_inl.h will undefine USING_* for us.
2065 #undef N
2066 }
2067
2068 #if defined(__clang__)
2069 #pragma clang attribute pop
2070 #elif defined(__GNUC__)
2071 #pragma GCC pop_options
2072 #endif
2073
2074 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002075 #endif
2076
skia-autorollba6087c2019-04-09 13:57:02 +00002077 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
2078 enum class CpuType { None, HSW, SKX };
2079 static CpuType cpu_type() {
2080 static const CpuType type = []{
2081 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002082
skia-autorollba6087c2019-04-09 13:57:02 +00002083 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2084 uint32_t eax, ebx, ecx, edx;
2085 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2086 : "0"(1), "2"(0));
2087 if ((edx & (1u<<25)) && // SSE
2088 (edx & (1u<<26)) && // SSE2
2089 (ecx & (1u<< 0)) && // SSE3
2090 (ecx & (1u<< 9)) && // SSSE3
2091 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
2092 (ecx & (1u<<19)) && // SSE4.1
2093 (ecx & (1u<<20)) && // SSE4.2
2094 (ecx & (1u<<26)) && // XSAVE
2095 (ecx & (1u<<27)) && // OSXSAVE
2096 (ecx & (1u<<28)) && // AVX
2097 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002098
skia-autorollba6087c2019-04-09 13:57:02 +00002099 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2100 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2101 : "0"(7), "2"(0));
2102 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2103 uint32_t xcr0, dont_need_edx;
2104 __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 +00002105
skia-autorollba6087c2019-04-09 13:57:02 +00002106 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2107 (xcr0 & (1u<<2)) && // YMM register state saved?
2108 (ebx & (1u<<5))) { // AVX2
2109 // At this point we're at least HSW. Continue checking for SKX.
2110 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2111 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2112 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2113 (ebx & (1u<<16)) && // AVX512F
2114 (ebx & (1u<<17)) && // AVX512DQ
2115 (ebx & (1u<<28)) && // AVX512CD
2116 (ebx & (1u<<30)) && // AVX512BW
2117 (ebx & (1u<<31))) { // AVX512VL
2118 return CpuType::SKX;
2119 }
2120 return CpuType::HSW;
2121 }
2122 }
2123 return CpuType::None;
2124 }();
2125 return type;
2126 }
2127 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002128
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002129#endif
2130
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002131typedef struct {
2132 Op op;
2133 const void* arg;
2134} OpAndArg;
2135
2136static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autorollf6d50642019-10-14 15:31:13 +00002137 static const struct { Op sRGBish, PQish, HLGish, HLGinvish, table; } ops[] = {
2138 { Op_tf_r, Op_pq_r, Op_hlg_r, Op_hlginv_r, Op_table_r },
2139 { Op_tf_g, Op_pq_g, Op_hlg_g, Op_hlginv_g, Op_table_g },
2140 { Op_tf_b, Op_pq_b, Op_hlg_b, Op_hlginv_b, Op_table_b },
2141 { Op_tf_a, Op_pq_a, Op_hlg_a, Op_hlginv_a, Op_table_a },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002142 };
skia-autorollf6d50642019-10-14 15:31:13 +00002143 const auto& op = ops[channel];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002144
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002145 if (curve->table_entries == 0) {
skia-autorollf6d50642019-10-14 15:31:13 +00002146 const OpAndArg noop = { Op_load_a8/*doesn't matter*/, nullptr };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002147
skia-autorollf6d50642019-10-14 15:31:13 +00002148 const skcms_TransferFunction& tf = curve->parametric;
2149
2150 if (tf.g == 1 && tf.a == 1 &&
2151 tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) {
2152 return noop;
2153 }
2154
2155 switch (classify(tf)) {
2156 case Bad: return noop;
2157 case sRGBish: return OpAndArg{op.sRGBish, &tf};
2158 case PQish: return OpAndArg{op.PQish, &tf};
2159 case HLGish: return OpAndArg{op.HLGish, &tf};
2160 case HLGinvish: return OpAndArg{op.HLGinvish, &tf};
2161 }
2162 }
2163 return OpAndArg{op.table, curve};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002164}
2165
2166static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2167 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002168 case skcms_PixelFormat_A_8 >> 1: return 1;
2169 case skcms_PixelFormat_G_8 >> 1: return 1;
2170 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2171 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2172 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2173 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2174 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2175 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2176 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2177 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2178 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2179 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002180 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2181 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002182 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2183 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2184 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2185 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002186 }
2187 assert(false);
2188 return 0;
2189}
2190
2191static bool prep_for_destination(const skcms_ICCProfile* profile,
2192 skcms_Matrix3x3* fromXYZD50,
2193 skcms_TransferFunction* invR,
2194 skcms_TransferFunction* invG,
2195 skcms_TransferFunction* invB) {
2196 // We only support destinations with parametric transfer functions
2197 // and with gamuts that can be transformed from XYZD50.
2198 return profile->has_trc
2199 && profile->has_toXYZD50
2200 && profile->trc[0].table_entries == 0
2201 && profile->trc[1].table_entries == 0
2202 && profile->trc[2].table_entries == 0
2203 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2204 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2205 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2206 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2207}
2208
2209bool skcms_Transform(const void* src,
2210 skcms_PixelFormat srcFmt,
2211 skcms_AlphaFormat srcAlpha,
2212 const skcms_ICCProfile* srcProfile,
2213 void* dst,
2214 skcms_PixelFormat dstFmt,
2215 skcms_AlphaFormat dstAlpha,
2216 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002217 size_t npixels) {
2218 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2219 dst, dstFmt, dstAlpha, dstProfile,
2220 npixels, nullptr);
2221}
2222
2223bool skcms_TransformWithPalette(const void* src,
2224 skcms_PixelFormat srcFmt,
2225 skcms_AlphaFormat srcAlpha,
2226 const skcms_ICCProfile* srcProfile,
2227 void* dst,
2228 skcms_PixelFormat dstFmt,
2229 skcms_AlphaFormat dstAlpha,
2230 const skcms_ICCProfile* dstProfile,
2231 size_t nz,
2232 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002233 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2234 src_bpp = bytes_per_pixel(srcFmt);
2235 // Let's just refuse if the request is absurdly big.
2236 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2237 return false;
2238 }
2239 int n = (int)nz;
2240
2241 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2242 if (!srcProfile) {
2243 srcProfile = skcms_sRGB_profile();
2244 }
2245 if (!dstProfile) {
2246 dstProfile = skcms_sRGB_profile();
2247 }
2248
2249 // 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 +00002250 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002251 return false;
2252 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002253 // TODO: more careful alias rejection (like, dst == src + 1)?
2254
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002255 if (needs_palette(srcFmt) && !palette) {
2256 return false;
2257 }
2258
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002259 Op program [32];
2260 const void* arguments[32];
2261
2262 Op* ops = program;
2263 const void** args = arguments;
2264
skia-autorollf6d50642019-10-14 15:31:13 +00002265 // These are always parametric curves of some sort.
2266 skcms_Curve dst_curves[3];
2267 dst_curves[0].table_entries =
2268 dst_curves[1].table_entries =
2269 dst_curves[2].table_entries = 0;
2270
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002271 skcms_Matrix3x3 from_xyz;
2272
2273 switch (srcFmt >> 1) {
2274 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002275 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2276 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2277 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2278 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2279 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2280 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2281 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2282 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2283 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2284 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2285 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002286 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2287 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 +00002288 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2289 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2290 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2291 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002292
2293 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2294 *args++ = palette;
2295 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002296 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002297 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2298 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2299 *ops++ = Op_clamp;
2300 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002301 if (srcFmt & 1) {
2302 *ops++ = Op_swap_rb;
2303 }
2304 skcms_ICCProfile gray_dst_profile;
2305 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2306 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2307 // luminance (Y) by the destination transfer function.
2308 gray_dst_profile = *dstProfile;
2309 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2310 dstProfile = &gray_dst_profile;
2311 }
2312
2313 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2314 // Photoshop creates CMYK images as inverse CMYK.
2315 // These happen to be the only ones we've _ever_ seen.
2316 *ops++ = Op_invert;
2317 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2318 srcAlpha = skcms_AlphaFormat_Unpremul;
2319 }
2320
2321 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2322 *ops++ = Op_force_opaque;
2323 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2324 *ops++ = Op_unpremul;
2325 }
2326
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002327 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002328
2329 if (!prep_for_destination(dstProfile,
skia-autorollf6d50642019-10-14 15:31:13 +00002330 &from_xyz,
2331 &dst_curves[0].parametric,
2332 &dst_curves[1].parametric,
2333 &dst_curves[2].parametric)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002334 return false;
2335 }
2336
2337 if (srcProfile->has_A2B) {
2338 if (srcProfile->A2B.input_channels) {
2339 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2340 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 +00002341 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002342 *ops++ = oa.op;
2343 *args++ = oa.arg;
2344 }
2345 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002346 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002347 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002348 *args++ = &srcProfile->A2B;
2349 }
2350
2351 if (srcProfile->A2B.matrix_channels == 3) {
2352 for (int i = 0; i < 3; i++) {
2353 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 +00002354 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002355 *ops++ = oa.op;
2356 *args++ = oa.arg;
2357 }
2358 }
2359
2360 static const skcms_Matrix3x4 I = {{
2361 {1,0,0,0},
2362 {0,1,0,0},
2363 {0,0,1,0},
2364 }};
2365 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2366 *ops++ = Op_matrix_3x4;
2367 *args++ = &srcProfile->A2B.matrix;
2368 }
2369 }
2370
2371 if (srcProfile->A2B.output_channels == 3) {
2372 for (int i = 0; i < 3; i++) {
2373 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 +00002374 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002375 *ops++ = oa.op;
2376 *args++ = oa.arg;
2377 }
2378 }
2379 }
2380
2381 if (srcProfile->pcs == skcms_Signature_Lab) {
2382 *ops++ = Op_lab_to_xyz;
2383 }
2384
2385 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2386 for (int i = 0; i < 3; i++) {
2387 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002388 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002389 *ops++ = oa.op;
2390 *args++ = oa.arg;
2391 }
2392 }
2393 } else {
2394 return false;
2395 }
2396
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002397 // A2B sources should already be in XYZD50 at this point.
2398 // Others still need to be transformed using their toXYZD50 matrix.
2399 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2400 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2401 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2402 static const skcms_Matrix3x3 I = {{
2403 { 1.0f, 0.0f, 0.0f },
2404 { 0.0f, 1.0f, 0.0f },
2405 { 0.0f, 0.0f, 1.0f },
2406 }};
2407 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2408
2409 // There's a chance the source and destination gamuts are identical,
2410 // in which case we can skip the gamut transform.
2411 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2412 // Concat the entire gamut transform into from_xyz,
2413 // now slightly misnamed but it's a handy spot to stash the result.
2414 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2415 *ops++ = Op_matrix_3x3;
2416 *args++ = &from_xyz;
2417 }
2418
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002419 // Encode back to dst RGB using its parametric transfer functions.
skia-autorollf6d50642019-10-14 15:31:13 +00002420 for (int i = 0; i < 3; i++) {
2421 OpAndArg oa = select_curve_op(dst_curves+i, i);
2422 if (oa.arg) {
2423 assert (oa.op != Op_table_r &&
2424 oa.op != Op_table_g &&
2425 oa.op != Op_table_b &&
2426 oa.op != Op_table_a);
2427 *ops++ = oa.op;
2428 *args++ = oa.arg;
2429 }
2430 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002431 }
2432
skia-autoroll2e4fa242019-03-11 21:14:18 +00002433 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2434 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002435 //
2436 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2437 // 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 +00002438 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2439 *ops++ = Op_clamp;
2440 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002441 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2442 *ops++ = Op_force_opaque;
2443 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2444 *ops++ = Op_premul;
2445 }
2446 if (dstFmt & 1) {
2447 *ops++ = Op_swap_rb;
2448 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002449 switch (dstFmt >> 1) {
2450 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002451 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2452 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2453 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2454 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2455 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2456 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2457 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2458 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2459 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2460 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2461 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002462 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2463 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 +00002464 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2465 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2466 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2467 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 +00002468 }
2469
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002470 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002471#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002472 switch (cpu_type()) {
2473 case CpuType::None: break;
2474 case CpuType::HSW: run = hsw::run_program; break;
2475 case CpuType::SKX: run = hsw::run_program; break;
2476 }
2477#endif
2478#if defined(TEST_FOR_SKX)
2479 switch (cpu_type()) {
2480 case CpuType::None: break;
2481 case CpuType::HSW: break;
2482 case CpuType::SKX: run = skx::run_program; break;
2483 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002484#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002485 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 +00002486 return true;
2487}
2488
2489static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2490#if defined(NDEBUG)
2491 (void)profile;
2492#else
2493 skcms_Matrix3x3 fromXYZD50;
2494 skcms_TransferFunction invR, invG, invB;
2495 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2496#endif
2497}
2498
2499bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2500 skcms_Matrix3x3 fromXYZD50;
2501 if (!profile->has_trc || !profile->has_toXYZD50
2502 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2503 return false;
2504 }
2505
2506 skcms_TransferFunction tf[3];
2507 for (int i = 0; i < 3; i++) {
2508 skcms_TransferFunction inv;
2509 if (profile->trc[i].table_entries == 0
2510 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2511 tf[i] = profile->trc[i].parametric;
2512 continue;
2513 }
2514
2515 float max_error;
2516 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2517 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2518 return false;
2519 }
2520 }
2521
2522 for (int i = 0; i < 3; ++i) {
2523 profile->trc[i].table_entries = 0;
2524 profile->trc[i].parametric = tf[i];
2525 }
2526
2527 assert_usable_as_destination(profile);
2528 return true;
2529}
2530
2531bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2532 // Operate on a copy of profile, so we can choose the best TF for the original curves
2533 skcms_ICCProfile result = *profile;
2534 if (!skcms_MakeUsableAsDestination(&result)) {
2535 return false;
2536 }
2537
2538 int best_tf = 0;
2539 float min_max_error = INFINITY_;
2540 for (int i = 0; i < 3; i++) {
2541 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002542 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2543 return false;
2544 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002545
2546 float err = 0;
2547 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002548 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 +00002549 }
2550 if (min_max_error > err) {
2551 min_max_error = err;
2552 best_tf = i;
2553 }
2554 }
2555
2556 for (int i = 0; i < 3; i++) {
2557 result.trc[i].parametric = result.trc[best_tf].parametric;
2558 }
2559
2560 *profile = result;
2561 assert_usable_as_destination(profile);
2562 return true;
2563}