blob: c54acb6fe12d92096366edce0823267196f7a074 [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.
skia-autorolle1227722019-10-28 20:17:07 +0000140 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;
skia-autorollc093cc82019-10-08 19:22:13 +0000144 }
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
skia-autorolld95243b2019-11-20 19:40:25 +0000233float skcms_MaxRoundtripError(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) {
skia-autorolld95243b2019-11-20 19:40:25 +0000246 return skcms_MaxRoundtripError(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,
skia-autoroll2565a6d2019-11-11 16:48:43 +0000269 skcms_Signature_WTPT = 0x77747074,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000270
271 // Type signatures
272 skcms_Signature_curv = 0x63757276,
273 skcms_Signature_mft1 = 0x6D667431,
274 skcms_Signature_mft2 = 0x6D667432,
275 skcms_Signature_para = 0x70617261,
276 skcms_Signature_sf32 = 0x73663332,
277 // XYZ is also a PCS signature, so it's defined in skcms.h
278 // skcms_Signature_XYZ = 0x58595A20,
279};
280
281static uint16_t read_big_u16(const uint8_t* ptr) {
282 uint16_t be;
283 memcpy(&be, ptr, sizeof(be));
284#if defined(_MSC_VER)
285 return _byteswap_ushort(be);
286#else
287 return __builtin_bswap16(be);
288#endif
289}
290
291static uint32_t read_big_u32(const uint8_t* ptr) {
292 uint32_t be;
293 memcpy(&be, ptr, sizeof(be));
294#if defined(_MSC_VER)
295 return _byteswap_ulong(be);
296#else
297 return __builtin_bswap32(be);
298#endif
299}
300
301static int32_t read_big_i32(const uint8_t* ptr) {
302 return (int32_t)read_big_u32(ptr);
303}
304
305static float read_big_fixed(const uint8_t* ptr) {
306 return read_big_i32(ptr) * (1.0f / 65536.0f);
307}
308
309// Maps to an in-memory profile so that fields line up to the locations specified
310// in ICC.1:2010, section 7.2
311typedef struct {
312 uint8_t size [ 4];
313 uint8_t cmm_type [ 4];
314 uint8_t version [ 4];
315 uint8_t profile_class [ 4];
316 uint8_t data_color_space [ 4];
317 uint8_t pcs [ 4];
318 uint8_t creation_date_time [12];
319 uint8_t signature [ 4];
320 uint8_t platform [ 4];
321 uint8_t flags [ 4];
322 uint8_t device_manufacturer [ 4];
323 uint8_t device_model [ 4];
324 uint8_t device_attributes [ 8];
325 uint8_t rendering_intent [ 4];
326 uint8_t illuminant_X [ 4];
327 uint8_t illuminant_Y [ 4];
328 uint8_t illuminant_Z [ 4];
329 uint8_t creator [ 4];
330 uint8_t profile_id [16];
331 uint8_t reserved [28];
332 uint8_t tag_count [ 4]; // Technically not part of header, but required
333} header_Layout;
334
335typedef struct {
336 uint8_t signature [4];
337 uint8_t offset [4];
338 uint8_t size [4];
339} tag_Layout;
340
341static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
342 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
343}
344
345// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
346// use of the type is for the CHAD tag that stores exactly nine values.
347typedef struct {
348 uint8_t type [ 4];
349 uint8_t reserved [ 4];
350 uint8_t values [36];
351} sf32_Layout;
352
353bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
354 skcms_ICCTag tag;
355 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
356 return false;
357 }
358
359 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
360 return false;
361 }
362
363 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
364 const uint8_t* values = sf32Tag->values;
365 for (int r = 0; r < 3; ++r)
366 for (int c = 0; c < 3; ++c, values += 4) {
367 m->vals[r][c] = read_big_fixed(values);
368 }
369 return true;
370}
371
372// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
373// the type are for tags/data that store exactly one triple.
374typedef struct {
375 uint8_t type [4];
376 uint8_t reserved [4];
377 uint8_t X [4];
378 uint8_t Y [4];
379 uint8_t Z [4];
380} XYZ_Layout;
381
382static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
383 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
384 return false;
385 }
386
387 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
388
389 *x = read_big_fixed(xyzTag->X);
390 *y = read_big_fixed(xyzTag->Y);
391 *z = read_big_fixed(xyzTag->Z);
392 return true;
393}
394
skia-autoroll2565a6d2019-11-11 16:48:43 +0000395bool skcms_GetWTPT(const skcms_ICCProfile* profile, float xyz[3]) {
396 skcms_ICCTag tag;
397 return skcms_GetTagBySignature(profile, skcms_Signature_WTPT, &tag) &&
398 read_tag_xyz(&tag, &xyz[0], &xyz[1], &xyz[2]);
399}
400
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000401static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
402 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
403 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
404 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
405 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
406}
407
408typedef struct {
409 uint8_t type [4];
410 uint8_t reserved_a [4];
411 uint8_t function_type [2];
412 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000413 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 +0000414} para_Layout;
415
416static bool read_curve_para(const uint8_t* buf, uint32_t size,
417 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000418 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000419 return false;
420 }
421
422 const para_Layout* paraTag = (const para_Layout*)buf;
423
424 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
425 uint16_t function_type = read_big_u16(paraTag->function_type);
426 if (function_type > kGABCDEF) {
427 return false;
428 }
429
430 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 +0000431 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 +0000432 return false;
433 }
434
435 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000436 *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 +0000437 }
438
439 curve->table_entries = 0;
440 curve->parametric.a = 1.0f;
441 curve->parametric.b = 0.0f;
442 curve->parametric.c = 0.0f;
443 curve->parametric.d = 0.0f;
444 curve->parametric.e = 0.0f;
445 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000446 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000447
448 switch (function_type) {
449 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000450 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
451 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000452 if (curve->parametric.a == 0) {
453 return false;
454 }
455 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
456 break;
457 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000458 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
459 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
460 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000461 if (curve->parametric.a == 0) {
462 return false;
463 }
464 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
465 curve->parametric.f = curve->parametric.e;
466 break;
467 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000468 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
469 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
470 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
471 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000472 break;
473 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000474 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
475 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
476 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
477 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
478 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
479 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000480 break;
481 }
skia-autorollf6d50642019-10-14 15:31:13 +0000482 return classify(curve->parametric) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000483}
484
485typedef struct {
486 uint8_t type [4];
487 uint8_t reserved [4];
488 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000489 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 +0000490} curv_Layout;
491
492static bool read_curve_curv(const uint8_t* buf, uint32_t size,
493 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000494 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000495 return false;
496 }
497
498 const curv_Layout* curvTag = (const curv_Layout*)buf;
499
500 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 +0000501 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 +0000502 return false;
503 }
504
505 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000506 *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 +0000507 }
508
509 if (value_count < 2) {
510 curve->table_entries = 0;
511 curve->parametric.a = 1.0f;
512 curve->parametric.b = 0.0f;
513 curve->parametric.c = 0.0f;
514 curve->parametric.d = 0.0f;
515 curve->parametric.e = 0.0f;
516 curve->parametric.f = 0.0f;
517 if (value_count == 0) {
518 // Empty tables are a shorthand for an identity curve
519 curve->parametric.g = 1.0f;
520 } else {
521 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000522 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 +0000523 }
524 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000525 curve->table_8 = nullptr;
526 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000527 curve->table_entries = value_count;
528 }
529
530 return true;
531}
532
533// 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 +0000534// 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 +0000535static bool read_curve(const uint8_t* buf, uint32_t size,
536 skcms_Curve* curve, uint32_t* curve_size) {
537 if (!buf || size < 4 || !curve) {
538 return false;
539 }
540
541 uint32_t type = read_big_u32(buf);
542 if (type == skcms_Signature_para) {
543 return read_curve_para(buf, size, curve, curve_size);
544 } else if (type == skcms_Signature_curv) {
545 return read_curve_curv(buf, size, curve, curve_size);
546 }
547
548 return false;
549}
550
551// mft1 and mft2 share a large chunk of data
552typedef struct {
553 uint8_t type [ 4];
554 uint8_t reserved_a [ 4];
555 uint8_t input_channels [ 1];
556 uint8_t output_channels [ 1];
557 uint8_t grid_points [ 1];
558 uint8_t reserved_b [ 1];
559 uint8_t matrix [36];
560} mft_CommonLayout;
561
562typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000563 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000564
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000565 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000566} mft1_Layout;
567
568typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000569 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000570
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000571 uint8_t input_table_entries [2];
572 uint8_t output_table_entries [2];
573 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000574} mft2_Layout;
575
576static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
577 // MFT matrices are applied before the first set of curves, but must be identity unless the
578 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
579 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
580 // field/flag.
581 a2b->matrix_channels = 0;
582
583 a2b->input_channels = mftTag->input_channels[0];
584 a2b->output_channels = mftTag->output_channels[0];
585
586 // We require exactly three (ie XYZ/Lab/RGB) output channels
587 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
588 return false;
589 }
590 // We require at least one, and no more than four (ie CMYK) input channels
591 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
592 return false;
593 }
594
595 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
596 a2b->grid_points[i] = mftTag->grid_points[0];
597 }
598 // The grid only makes sense with at least two points along each axis
599 if (a2b->grid_points[0] < 2) {
600 return false;
601 }
602
603 return true;
604}
605
606static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
607 uint32_t input_table_entries, uint32_t output_table_entries,
608 skcms_A2B* a2b) {
609 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
610 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
611 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
612
613 // [input|output]_channels are <= 4, so still no overflow
614 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
615 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
616
617 uint64_t grid_size = a2b->output_channels * byte_width;
618 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
619 grid_size *= a2b->grid_points[axis];
620 }
621
622 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
623 return false;
624 }
625
626 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
627 a2b->input_curves[i].table_entries = input_table_entries;
628 if (byte_width == 1) {
629 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 +0000630 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000631 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000632 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000633 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
634 }
635 }
636
637 if (byte_width == 1) {
638 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 +0000639 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000640 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000641 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000642 a2b->grid_16 = table_base + byte_len_all_input_tables;
643 }
644
645 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
646 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
647 a2b->output_curves[i].table_entries = output_table_entries;
648 if (byte_width == 1) {
649 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 +0000650 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000651 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000652 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000653 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
654 }
655 }
656
657 return true;
658}
659
660static 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 +0000661 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000662 return false;
663 }
664
665 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
666 if (!read_mft_common(mftTag->common, a2b)) {
667 return false;
668 }
669
670 uint32_t input_table_entries = 256;
671 uint32_t output_table_entries = 256;
672
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000673 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 +0000674 input_table_entries, output_table_entries, a2b);
675}
676
677static 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 +0000678 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000679 return false;
680 }
681
682 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
683 if (!read_mft_common(mftTag->common, a2b)) {
684 return false;
685 }
686
687 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
688 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
689
690 // ICC spec mandates that 2 <= table_entries <= 4096
691 if (input_table_entries < 2 || input_table_entries > 4096 ||
692 output_table_entries < 2 || output_table_entries > 4096) {
693 return false;
694 }
695
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000696 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 +0000697 input_table_entries, output_table_entries, a2b);
698}
699
700static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
701 uint32_t num_curves, skcms_Curve* curves) {
702 for (uint32_t i = 0; i < num_curves; ++i) {
703 if (curve_offset > size) {
704 return false;
705 }
706
707 uint32_t curve_bytes;
708 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
709 return false;
710 }
711
712 if (curve_bytes > UINT32_MAX - 3) {
713 return false;
714 }
715 curve_bytes = (curve_bytes + 3) & ~3U;
716
717 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
718 curve_offset = (uint32_t)new_offset_64;
719 if (new_offset_64 != curve_offset) {
720 return false;
721 }
722 }
723
724 return true;
725}
726
727typedef struct {
728 uint8_t type [ 4];
729 uint8_t reserved_a [ 4];
730 uint8_t input_channels [ 1];
731 uint8_t output_channels [ 1];
732 uint8_t reserved_b [ 2];
733 uint8_t b_curve_offset [ 4];
734 uint8_t matrix_offset [ 4];
735 uint8_t m_curve_offset [ 4];
736 uint8_t clut_offset [ 4];
737 uint8_t a_curve_offset [ 4];
738} mAB_Layout;
739
740typedef struct {
741 uint8_t grid_points [16];
742 uint8_t grid_byte_width [ 1];
743 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000744 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000745} mABCLUT_Layout;
746
747static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
748 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
749 return false;
750 }
751
752 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
753
754 a2b->input_channels = mABTag->input_channels[0];
755 a2b->output_channels = mABTag->output_channels[0];
756
757 // We require exactly three (ie XYZ/Lab/RGB) output channels
758 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
759 return false;
760 }
761 // We require no more than four (ie CMYK) input channels
762 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
763 return false;
764 }
765
766 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
767 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
768 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
769 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
770 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
771
772 // "B" curves must be present
773 if (0 == b_curve_offset) {
774 return false;
775 }
776
777 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
778 a2b->output_curves)) {
779 return false;
780 }
781
782 // "M" curves and Matrix must be used together
783 if (0 != m_curve_offset) {
784 if (0 == matrix_offset) {
785 return false;
786 }
787 a2b->matrix_channels = a2b->output_channels;
788 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
789 a2b->matrix_curves)) {
790 return false;
791 }
792
793 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
794 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
795 return false;
796 }
797 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
798 const uint8_t* mtx_buf = tag->buf + matrix_offset;
799 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
800 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
801 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
802 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
803 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
804 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
805 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
806 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
807 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
808 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
809 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
810 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
811 } else {
812 if (0 != matrix_offset) {
813 return false;
814 }
815 a2b->matrix_channels = 0;
816 }
817
818 // "A" curves and CLUT must be used together
819 if (0 != a_curve_offset) {
820 if (0 == clut_offset) {
821 return false;
822 }
823 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
824 a2b->input_curves)) {
825 return false;
826 }
827
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000828 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 +0000829 return false;
830 }
831 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
832
833 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000834 a2b->grid_8 = clut->variable;
835 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000836 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000837 a2b->grid_8 = nullptr;
838 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000839 } else {
840 return false;
841 }
842
843 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
844 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
845 a2b->grid_points[i] = clut->grid_points[i];
846 // The grid only makes sense with at least two points along each axis
847 if (a2b->grid_points[i] < 2) {
848 return false;
849 }
850 grid_size *= a2b->grid_points[i];
851 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000852 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 +0000853 return false;
854 }
855 } else {
856 if (0 != clut_offset) {
857 return false;
858 }
859
860 // If there is no CLUT, the number of input and output channels must match
861 if (a2b->input_channels != a2b->output_channels) {
862 return false;
863 }
864
865 // Zero out the number of input channels to signal that we're skipping this stage
866 a2b->input_channels = 0;
867 }
868
869 return true;
870}
871
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000872// If you pass f, we'll fit a possibly-non-zero value for *f.
873// If you pass nullptr, we'll assume you want *f to be treated as zero.
874static int fit_linear(const skcms_Curve* curve, int N, float tol,
875 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000876 assert(N > 1);
877 // We iteratively fit the first points to the TF's linear piece.
878 // We want the cx + f line to pass through the first and last points we fit exactly.
879 //
880 // As we walk along the points we find the minimum and maximum slope of the line before the
881 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
882 // emtpy, when we definitely can't add any more points.
883 //
884 // Some points' error intervals may intersect the running interval but not lie fully
885 // within it. So we keep track of the last point we saw that is a valid end point candidate,
886 // and once the search is done, back up to build the line through *that* point.
887 const float dx = 1.0f / (N - 1);
888
889 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000890
891 float f_zero = 0.0f;
892 if (f) {
893 *f = eval_curve(curve, 0);
894 } else {
895 f = &f_zero;
896 }
897
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000898
899 float slope_min = -INFINITY_;
900 float slope_max = +INFINITY_;
901 for (int i = 1; i < N; ++i) {
902 float x = i * dx;
903 float y = eval_curve(curve, x);
904
905 float slope_max_i = (y + tol - *f) / x,
906 slope_min_i = (y - tol - *f) / x;
907 if (slope_max_i < slope_min || slope_max < slope_min_i) {
908 // Slope intervals would no longer overlap.
909 break;
910 }
911 slope_max = fminf_(slope_max, slope_max_i);
912 slope_min = fmaxf_(slope_min, slope_min_i);
913
914 float cur_slope = (y - *f) / x;
915 if (slope_min <= cur_slope && cur_slope <= slope_max) {
916 lin_points = i + 1;
917 *c = cur_slope;
918 }
919 }
920
921 // Set D to the last point that met our tolerance.
922 *d = (lin_points - 1) * dx;
923 return lin_points;
924}
925
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000926static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
927 bool ok = false;
928 if (tag->type == skcms_Signature_mft1) {
929 ok = read_tag_mft1(tag, a2b);
930 } else if (tag->type == skcms_Signature_mft2) {
931 ok = read_tag_mft2(tag, a2b);
932 } else if (tag->type == skcms_Signature_mAB) {
933 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
934 }
935 if (!ok) {
936 return false;
937 }
938
939 // Detect and canonicalize identity tables.
940 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000941 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
942 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
943 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
944 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
945 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
946 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
947 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
948 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
949 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
950 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000951 };
952
953 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
954 skcms_Curve* curve = curves[i];
955
956 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
957 int N = (int)curve->table_entries;
958
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000959 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 +0000960 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 +0000961 && c == 1.0f
962 && f == 0.0f) {
963 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000964 curve->table_8 = nullptr;
965 curve->table_16 = nullptr;
966 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 +0000967 }
968 }
969 }
970
971 return true;
972}
973
974void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
975 if (!profile || !profile->buffer || !tag) { return; }
976 if (idx > profile->tag_count) { return; }
977 const tag_Layout* tags = get_tag_table(profile);
978 tag->signature = read_big_u32(tags[idx].signature);
979 tag->size = read_big_u32(tags[idx].size);
980 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
981 tag->type = read_big_u32(tag->buf);
982}
983
984bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
985 if (!profile || !profile->buffer || !tag) { return false; }
986 const tag_Layout* tags = get_tag_table(profile);
987 for (uint32_t i = 0; i < profile->tag_count; ++i) {
988 if (read_big_u32(tags[i].signature) == sig) {
989 tag->signature = sig;
990 tag->size = read_big_u32(tags[i].size);
991 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
992 tag->type = read_big_u32(tag->buf);
993 return true;
994 }
995 }
996 return false;
997}
998
999static bool usable_as_src(const skcms_ICCProfile* profile) {
1000 return profile->has_A2B
1001 || (profile->has_trc && profile->has_toXYZD50);
1002}
1003
1004bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
1005 assert(SAFE_SIZEOF(header_Layout) == 132);
1006
1007 if (!profile) {
1008 return false;
1009 }
1010 memset(profile, 0, SAFE_SIZEOF(*profile));
1011
1012 if (len < SAFE_SIZEOF(header_Layout)) {
1013 return false;
1014 }
1015
1016 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001017 const header_Layout* header = (const header_Layout*)buf;
1018 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001019 profile->size = read_big_u32(header->size);
1020 uint32_t version = read_big_u32(header->version);
1021 profile->data_color_space = read_big_u32(header->data_color_space);
1022 profile->pcs = read_big_u32(header->pcs);
1023 uint32_t signature = read_big_u32(header->signature);
1024 float illuminant_X = read_big_fixed(header->illuminant_X);
1025 float illuminant_Y = read_big_fixed(header->illuminant_Y);
1026 float illuminant_Z = read_big_fixed(header->illuminant_Z);
1027 profile->tag_count = read_big_u32(header->tag_count);
1028
1029 // Validate signature, size (smaller than buffer, large enough to hold tag table),
1030 // and major version
1031 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1032 if (signature != skcms_Signature_acsp ||
1033 profile->size > len ||
1034 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1035 (version >> 24) > 4) {
1036 return false;
1037 }
1038
1039 // Validate that illuminant is D50 white
1040 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1041 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1042 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1043 return false;
1044 }
1045
1046 // Validate that all tag entries have sane offset + size
1047 const tag_Layout* tags = get_tag_table(profile);
1048 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1049 uint32_t tag_offset = read_big_u32(tags[i].offset);
1050 uint32_t tag_size = read_big_u32(tags[i].size);
1051 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
1052 if (tag_size < 4 || tag_end > profile->size) {
1053 return false;
1054 }
1055 }
1056
1057 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1058 return false;
1059 }
1060
1061 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1062
1063 // Pre-parse commonly used tags.
1064 skcms_ICCTag kTRC;
1065 if (profile->data_color_space == skcms_Signature_Gray &&
1066 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001067 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 +00001068 // Malformed tag
1069 return false;
1070 }
1071 profile->trc[1] = profile->trc[0];
1072 profile->trc[2] = profile->trc[0];
1073 profile->has_trc = true;
1074
1075 if (pcs_is_xyz) {
1076 profile->toXYZD50.vals[0][0] = illuminant_X;
1077 profile->toXYZD50.vals[1][1] = illuminant_Y;
1078 profile->toXYZD50.vals[2][2] = illuminant_Z;
1079 profile->has_toXYZD50 = true;
1080 }
1081 } else {
1082 skcms_ICCTag rTRC, gTRC, bTRC;
1083 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1084 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1085 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001086 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1087 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1088 !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 +00001089 // Malformed TRC tags
1090 return false;
1091 }
1092 profile->has_trc = true;
1093 }
1094
1095 skcms_ICCTag rXYZ, gXYZ, bXYZ;
1096 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1097 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1098 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1099 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1100 // Malformed XYZ tags
1101 return false;
1102 }
1103 profile->has_toXYZD50 = true;
1104 }
1105 }
1106
1107 skcms_ICCTag a2b_tag;
1108
1109 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
1110 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
1111 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
1112 // and all our known users are thinking exclusively in terms of relative colormetric.
1113 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
1114 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
1115 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
1116 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
1117 // Malformed A2B tag
1118 return false;
1119 }
1120 profile->has_A2B = true;
1121 break;
1122 }
1123 }
1124
1125 return usable_as_src(profile);
1126}
1127
1128
1129const skcms_ICCProfile* skcms_sRGB_profile() {
1130 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001131 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001132
1133 0, // size, moot here
1134 skcms_Signature_RGB, // data_color_space
1135 skcms_Signature_XYZ, // pcs
1136 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001137
1138 // We choose to represent sRGB with its canonical transfer function,
1139 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001140 true, // has_trc, followed by the 3 trc curves
1141 {
1142 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1143 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1144 {{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 +00001145 },
1146
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001147 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1148 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001149 { 0.436065674f, 0.385147095f, 0.143066406f },
1150 { 0.222488403f, 0.716873169f, 0.060607910f },
1151 { 0.013916016f, 0.097076416f, 0.714096069f },
1152 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001153
1154 false, // has_A2B, followed by a2b itself which we don't care about.
1155 {
1156 0,
1157 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001158 {{0, {0,0, 0,0,0,0,0}}},
1159 {{0, {0,0, 0,0,0,0,0}}},
1160 {{0, {0,0, 0,0,0,0,0}}},
1161 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001162 },
1163 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001164 nullptr,
1165 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001166
1167 0,
1168 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001169 {{0, {0,0, 0,0,0,0,0}}},
1170 {{0, {0,0, 0,0,0,0,0}}},
1171 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001172 },
1173 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001174 { 0,0,0,0 },
1175 { 0,0,0,0 },
1176 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001177 }},
1178
1179 0,
1180 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001181 {{0, {0,0, 0,0,0,0,0}}},
1182 {{0, {0,0, 0,0,0,0,0}}},
1183 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001184 },
1185 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001186 };
1187 return &sRGB_profile;
1188}
1189
1190const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001191 // 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 +00001192 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001193 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001194
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001195 0, // size, moot here
1196 skcms_Signature_RGB, // data_color_space
1197 skcms_Signature_XYZ, // pcs
1198 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001199
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001200 true, // has_trc, followed by the 3 trc curves
1201 {
1202 {{0, {1,1, 0,0,0,0,0}}},
1203 {{0, {1,1, 0,0,0,0,0}}},
1204 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001205 },
1206
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001207 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1208 {{
1209 { 1,0,0 },
1210 { 0,1,0 },
1211 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001212 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001213
1214 false, // has_A2B, followed by a2b itself which we don't care about.
1215 {
1216 0,
1217 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001218 {{0, {0,0, 0,0,0,0,0}}},
1219 {{0, {0,0, 0,0,0,0,0}}},
1220 {{0, {0,0, 0,0,0,0,0}}},
1221 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001222 },
1223 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001224 nullptr,
1225 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001226
1227 0,
1228 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001229 {{0, {0,0, 0,0,0,0,0}}},
1230 {{0, {0,0, 0,0,0,0,0}}},
1231 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001232 },
1233 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001234 { 0,0,0,0 },
1235 { 0,0,0,0 },
1236 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001237 }},
1238
1239 0,
1240 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001241 {{0, {0,0, 0,0,0,0,0}}},
1242 {{0, {0,0, 0,0,0,0,0}}},
1243 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001244 },
1245 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001246 };
1247
1248 return &XYZD50_profile;
1249}
1250
1251const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1252 return &skcms_sRGB_profile()->trc[0].parametric;
1253}
1254
1255const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1256 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001257 {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 +00001258 return &sRGB_inv;
1259}
1260
1261const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1262 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1263 return &identity;
1264}
1265
1266const uint8_t skcms_252_random_bytes[] = {
1267 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1268 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1269 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1270 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1271 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1272 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1273 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1274 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1275 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1276 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1277 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1278 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1279 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1280 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1281 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1282 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1283};
1284
1285bool 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 +00001286 // Test for exactly equal profiles first.
1287 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1288 return true;
1289 }
1290
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001291 // For now this is the essentially the same strategy we use in test_only.c
1292 // for our skcms_Transform() smoke tests:
1293 // 1) transform A to XYZD50
1294 // 2) transform B to XYZD50
1295 // 3) return true if they're similar enough
1296 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1297
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001298 // 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 +00001299 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1300
1301 if (A->data_color_space != B->data_color_space) {
1302 return false;
1303 }
1304
1305 // 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 +00001306 // 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 +00001307 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1308 size_t npixels = 84;
1309 if (A->data_color_space == skcms_Signature_CMYK) {
1310 fmt = skcms_PixelFormat_RGBA_8888;
1311 npixels = 63;
1312 }
1313
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001314 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1315 // 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 +00001316 uint8_t dstA[252],
1317 dstB[252];
1318 if (!skcms_Transform(
1319 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1320 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1321 npixels)) {
1322 return false;
1323 }
1324 if (!skcms_Transform(
1325 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1326 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1327 npixels)) {
1328 return false;
1329 }
1330
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001331 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001332 for (size_t i = 0; i < 252; i++) {
1333 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1334 return false;
1335 }
1336 }
1337 return true;
1338}
1339
1340bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1341 const skcms_TransferFunction* inv_tf) {
1342 if (!profile || !profile->has_trc) {
1343 return false;
1344 }
1345
1346 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1347 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1348 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1349}
1350
1351static bool is_zero_to_one(float x) {
1352 return 0 <= x && x <= 1;
1353}
1354
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001355typedef struct { float vals[3]; } skcms_Vector3;
1356
1357static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1358 skcms_Vector3 dst = {{0,0,0}};
1359 for (int row = 0; row < 3; ++row) {
1360 dst.vals[row] = m->vals[row][0] * v->vals[0]
1361 + m->vals[row][1] * v->vals[1]
1362 + m->vals[row][2] * v->vals[2];
1363 }
1364 return dst;
1365}
1366
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001367bool skcms_PrimariesToXYZD50(float rx, float ry,
1368 float gx, float gy,
1369 float bx, float by,
1370 float wx, float wy,
1371 skcms_Matrix3x3* toXYZD50) {
1372 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1373 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1374 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1375 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1376 !toXYZD50) {
1377 return false;
1378 }
1379
1380 // First, we need to convert xy values (primaries) to XYZ.
1381 skcms_Matrix3x3 primaries = {{
1382 { rx, gx, bx },
1383 { ry, gy, by },
1384 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1385 }};
1386 skcms_Matrix3x3 primaries_inv;
1387 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1388 return false;
1389 }
1390
1391 // Assumes that Y is 1.0f.
1392 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 +00001393 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001394
1395 skcms_Matrix3x3 toXYZ = {{
1396 { XYZ.vals[0], 0, 0 },
1397 { 0, XYZ.vals[1], 0 },
1398 { 0, 0, XYZ.vals[2] },
1399 }};
1400 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1401
1402 // Now convert toXYZ matrix to toXYZD50.
1403 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1404
1405 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1406 // the matrices below. The Bradford method is used by Adobe and is widely considered
1407 // to be the best.
1408 skcms_Matrix3x3 xyz_to_lms = {{
1409 { 0.8951f, 0.2664f, -0.1614f },
1410 { -0.7502f, 1.7135f, 0.0367f },
1411 { 0.0389f, -0.0685f, 1.0296f },
1412 }};
1413 skcms_Matrix3x3 lms_to_xyz = {{
1414 { 0.9869929f, -0.1470543f, 0.1599627f },
1415 { 0.4323053f, 0.5183603f, 0.0492912f },
1416 { -0.0085287f, 0.0400428f, 0.9684867f },
1417 }};
1418
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001419 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1420 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001421
1422 skcms_Matrix3x3 DXtoD50 = {{
1423 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1424 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1425 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1426 }};
1427 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1428 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1429
1430 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1431 return true;
1432}
1433
1434
1435bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1436 double a00 = src->vals[0][0],
1437 a01 = src->vals[1][0],
1438 a02 = src->vals[2][0],
1439 a10 = src->vals[0][1],
1440 a11 = src->vals[1][1],
1441 a12 = src->vals[2][1],
1442 a20 = src->vals[0][2],
1443 a21 = src->vals[1][2],
1444 a22 = src->vals[2][2];
1445
1446 double b0 = a00*a11 - a01*a10,
1447 b1 = a00*a12 - a02*a10,
1448 b2 = a01*a12 - a02*a11,
1449 b3 = a20,
1450 b4 = a21,
1451 b5 = a22;
1452
1453 double determinant = b0*b5
1454 - b1*b4
1455 + b2*b3;
1456
1457 if (determinant == 0) {
1458 return false;
1459 }
1460
1461 double invdet = 1.0 / determinant;
1462 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1463 return false;
1464 }
1465
1466 b0 *= invdet;
1467 b1 *= invdet;
1468 b2 *= invdet;
1469 b3 *= invdet;
1470 b4 *= invdet;
1471 b5 *= invdet;
1472
1473 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1474 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1475 dst->vals[2][0] = (float)( + b2 );
1476 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1477 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1478 dst->vals[2][1] = (float)( - b1 );
1479 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1480 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1481 dst->vals[2][2] = (float)( + b0 );
1482
1483 for (int r = 0; r < 3; ++r)
1484 for (int c = 0; c < 3; ++c) {
1485 if (!isfinitef_(dst->vals[r][c])) {
1486 return false;
1487 }
1488 }
1489 return true;
1490}
1491
1492skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1493 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1494 for (int r = 0; r < 3; r++)
1495 for (int c = 0; c < 3; c++) {
1496 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1497 + A->vals[r][1] * B->vals[1][c]
1498 + A->vals[r][2] * B->vals[2][c];
1499 }
1500 return m;
1501}
1502
skia-autorollacd6e012019-01-08 14:10:52 +00001503#if defined(__clang__)
skia-autorollf6d50642019-10-14 15:31:13 +00001504 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by classify() on the way out.
skia-autorollacd6e012019-01-08 14:10:52 +00001505#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001506bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
skia-autorollf6d50642019-10-14 15:31:13 +00001507 TF_PQish pq;
1508 TF_HLGish hlg;
1509 switch (classify(*src, &pq, &hlg)) {
1510 case Bad: return false;
1511 case sRGBish: break; // handled below
1512
1513 case PQish:
1514 *dst = { TFKind_marker(PQish), -pq.A, pq.D, 1.0f/pq.F
1515 , pq.B, -pq.E, 1.0f/pq.C};
1516 return true;
1517
1518 case HLGish:
1519 *dst = { TFKind_marker(HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
1520 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1521 return true;
1522
1523 case HLGinvish:
1524 *dst = { TFKind_marker(HLGish), 1.0f/hlg.R, 1.0f/hlg.G
1525 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1526 return true;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001527 }
1528
skia-autorollf6d50642019-10-14 15:31:13 +00001529 assert (classify(*src) == sRGBish);
1530
skia-autorolld0b577f2019-01-07 19:46:57 +00001531 // We're inverting this function, solving for x in terms of y.
1532 // y = (cx + f) x < d
1533 // (ax + b)^g + e x ≥ d
1534 // The inverse of this function can be expressed in the same piecewise form.
1535 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 +00001536
skia-autorolld0b577f2019-01-07 19:46:57 +00001537 // We'll start by finding the new threshold inv.d.
1538 // In principle we should be able to find that by solving for y at x=d from either side.
1539 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1540 float d_l = src->c * src->d + src->f,
1541 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1542 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001543 return false;
1544 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001545 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 +00001546
skia-autorolld0b577f2019-01-07 19:46:57 +00001547 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1548 if (inv.d > 0) {
1549 // Inverting the linear section is pretty straightfoward:
1550 // y = cx + f
1551 // y - f = cx
1552 // (1/c)y - f/c = x
1553 inv.c = 1.0f/src->c;
1554 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001555 }
1556
skia-autorolld0b577f2019-01-07 19:46:57 +00001557 // The interesting part is inverting the nonlinear section:
1558 // y = (ax + b)^g + e.
1559 // y - e = (ax + b)^g
1560 // (y - e)^1/g = ax + b
1561 // (y - e)^1/g - b = ax
1562 // (1/a)(y - e)^1/g - b/a = x
1563 //
1564 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1565 // let k = (1/a)^g
1566 // (1/a)( y - e)^1/g - b/a = x
1567 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001568
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001569 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001570 inv.g = 1.0f / src->g;
1571 inv.a = k;
1572 inv.b = -k * src->e;
1573 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001574
skia-autorollfe16a332019-08-20 19:44:54 +00001575 // We need to enforce the same constraints here that we do when fitting a curve,
skia-autorollf6d50642019-10-14 15:31:13 +00001576 // a >= 0 and ad+b >= 0. These constraints are checked by classify(), so they're true
skia-autorollbf388232019-08-21 14:17:54 +00001577 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001578
skia-autorollbf388232019-08-21 14:17:54 +00001579 // Just like when fitting the curve, there's really no way to rescue a < 0.
1580 if (inv.a < 0) {
1581 return false;
1582 }
1583 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001584 if (inv.a * inv.d + inv.b < 0) {
1585 inv.b = -inv.a * inv.d;
1586 }
skia-autorollbf388232019-08-21 14:17:54 +00001587
skia-autorollf6d50642019-10-14 15:31:13 +00001588 // That should usually make classify(inv) == sRGBish true, but there are a couple situations
skia-autorolla0ed0702019-08-23 16:49:54 +00001589 // where we might still fail here, like non-finite parameter values.
skia-autorollf6d50642019-10-14 15:31:13 +00001590 if (classify(inv) != sRGBish) {
skia-autorolla0ed0702019-08-23 16:49:54 +00001591 return false;
1592 }
1593
skia-autorollbf388232019-08-21 14:17:54 +00001594 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001595 assert (inv.a * inv.d + inv.b >= 0);
1596
skia-autorolla7b28742019-01-09 18:35:46 +00001597 // Now in principle we're done.
skia-autorollad10df62019-08-21 15:14:54 +00001598 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
1599 // e or f of the inverse, depending on which segment contains src(1.0f).
1600 float s = skcms_TransferFunction_eval(src, 1.0f);
skia-autorolla0ed0702019-08-23 16:49:54 +00001601 if (!isfinitef_(s)) {
1602 return false;
1603 }
1604
skia-autorollad10df62019-08-21 15:14:54 +00001605 float sign = s < 0 ? -1.0f : 1.0f;
1606 s *= sign;
1607 if (s < inv.d) {
1608 inv.f = 1.0f - sign * inv.c * s;
1609 } else {
1610 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
1611 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001612
skia-autorolld0b577f2019-01-07 19:46:57 +00001613 *dst = inv;
skia-autorollf6d50642019-10-14 15:31:13 +00001614 return classify(*dst) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001615}
1616
1617// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1618
1619// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1620//
1621// tf(x) = cx + f x < d
1622// tf(x) = (ax + b)^g + e x ≥ d
1623//
1624// When fitting, we add the additional constraint that both pieces meet at d:
1625//
1626// cd + f = (ad + b)^g + e
1627//
1628// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1629//
1630// tf(x) = cx + f x < d
1631// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1632//
1633// Our overall strategy is then:
1634// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001635// - 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 +00001636// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001637// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1638// (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 +00001639// Return the parameters with least maximum error.
1640//
1641// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1642// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1643//
1644// let y = Table(x)
1645// r(x) = x - f_inv(y)
1646//
1647// ∂r/∂g = ln(ay + b)*(ay + b)^g
1648// - ln(ad + b)*(ad + b)^g
1649// ∂r/∂a = yg(ay + b)^(g-1)
1650// - dg(ad + b)^(g-1)
1651// ∂r/∂b = g(ay + b)^(g-1)
1652// - g(ad + b)^(g-1)
1653
1654// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1655// and fill out the gradient of the residual into dfdP.
1656static float rg_nonlinear(float x,
1657 const skcms_Curve* curve,
1658 const skcms_TransferFunction* tf,
1659 const float P[3],
1660 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001661 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001662
1663 const float g = P[0], a = P[1], b = P[2],
1664 c = tf->c, d = tf->d, f = tf->f;
1665
1666 const float Y = fmaxf_(a*y + b, 0.0f),
1667 D = a*d + b;
1668 assert (D >= 0);
1669
1670 // The gradient.
skia-autoroll5f8588c2019-10-14 13:54:14 +00001671 dfdP[0] = logf_(Y)*powf_(Y, g)
1672 - logf_(D)*powf_(D, g);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001673 dfdP[1] = y*g*powf_(Y, g-1)
1674 - d*g*powf_(D, g-1);
1675 dfdP[2] = g*powf_(Y, g-1)
1676 - g*powf_(D, g-1);
1677
1678 // The residual.
1679 const float f_inv = powf_(Y, g)
1680 - powf_(D, g)
1681 + c*d + f;
1682 return x - f_inv;
1683}
1684
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001685static bool gauss_newton_step(const skcms_Curve* curve,
1686 const skcms_TransferFunction* tf,
1687 float P[3],
1688 float x0, float dx, int N) {
1689 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1690 //
1691 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1692 // where r(P) is the residual vector
1693 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1694 //
1695 // Let's review the shape of each of these expressions:
1696 // r(P) is [N x 1], a column vector with one entry per value of x tested
1697 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1698 // Jf^T is [3 x N], the transpose of Jf
1699 //
1700 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1701 // and so is its inverse (Jf^T Jf)^-1
1702 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1703 //
1704 // Our implementation strategy to get to the final ∆P is
1705 // 1) evaluate Jf^T Jf, call that lhs
1706 // 2) evaluate Jf^T r(P), call that rhs
1707 // 3) invert lhs
1708 // 4) multiply inverse lhs by rhs
1709 //
1710 // This is a friendly implementation strategy because we don't have to have any
1711 // buffers that scale with N, and equally nice don't have to perform any matrix
1712 // operations that are variable size.
1713 //
1714 // Other implementation strategies could trade this off, e.g. evaluating the
1715 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1716 // the residuals. That would probably require implementing singular value
1717 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1718 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1719 // possibility of this gauss_newton_step() function ever failing.
1720
1721 // 0) start off with lhs and rhs safely zeroed.
1722 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1723 skcms_Vector3 rhs = { {0,0,0} };
1724
1725 // 1,2) evaluate lhs and evaluate rhs
1726 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1727 // so we'll have to update lhs and rhs at the same time.
1728 for (int i = 0; i < N; i++) {
1729 float x = x0 + i*dx;
1730
1731 float dfdP[3] = {0,0,0};
1732 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1733
1734 for (int r = 0; r < 3; r++) {
1735 for (int c = 0; c < 3; c++) {
1736 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1737 }
1738 rhs.vals[r] += dfdP[r] * resid;
1739 }
1740 }
1741
1742 // If any of the 3 P parameters are unused, this matrix will be singular.
1743 // Detect those cases and fix them up to indentity instead, so we can invert.
1744 for (int k = 0; k < 3; k++) {
1745 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1746 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1747 lhs.vals[k][k] = 1;
1748 }
1749 }
1750
1751 // 3) invert lhs
1752 skcms_Matrix3x3 lhs_inv;
1753 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1754 return false;
1755 }
1756
1757 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001758 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001759 P[0] += dP.vals[0];
1760 P[1] += dP.vals[1];
1761 P[2] += dP.vals[2];
1762 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1763}
1764
1765
1766// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1767static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1768 float P[3] = { tf->g, tf->a, tf->b };
1769
1770 // No matter where we start, dx should always represent N even steps from 0 to 1.
1771 const float dx = 1.0f / (N-1);
1772
skia-autorolld9718822019-08-23 18:16:54 +00001773 // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
1774 for (int j = 0; j < 2; j++) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001775 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1776 // We don't really know how to fix up a if it goes negative.
1777 if (P[1] < 0) {
1778 return false;
1779 }
1780 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1781 if (P[1] * tf->d + P[2] < 0) {
1782 P[2] = -P[1] * tf->d;
1783 }
1784 assert (P[1] >= 0 &&
1785 P[1] * tf->d + P[2] >= 0);
1786
1787 if (!gauss_newton_step(curve, tf,
1788 P,
1789 L*dx, dx, N-L)) {
1790 return false;
1791 }
1792 }
1793
1794 // We need to apply our fixups one last time
1795 if (P[1] < 0) {
1796 return false;
1797 }
1798 if (P[1] * tf->d + P[2] < 0) {
1799 P[2] = -P[1] * tf->d;
1800 }
1801
skia-autorollfe16a332019-08-20 19:44:54 +00001802 assert (P[1] >= 0 &&
1803 P[1] * tf->d + P[2] >= 0);
1804
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001805 tf->g = P[0];
1806 tf->a = P[1];
1807 tf->b = P[2];
1808 tf->e = tf->c*tf->d + tf->f
1809 - powf_(tf->a*tf->d + tf->b, tf->g);
1810 return true;
1811}
1812
1813bool skcms_ApproximateCurve(const skcms_Curve* curve,
1814 skcms_TransferFunction* approx,
1815 float* max_error) {
1816 if (!curve || !approx || !max_error) {
1817 return false;
1818 }
1819
1820 if (curve->table_entries == 0) {
1821 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1822 return false;
1823 }
1824
1825 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1826 // We need at least two points, and must put some reasonable cap on the maximum number.
1827 return false;
1828 }
1829
1830 int N = (int)curve->table_entries;
1831 const float dx = 1.0f / (N - 1);
1832
1833 *max_error = INFINITY_;
1834 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1835 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1836 skcms_TransferFunction tf,
1837 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001838
1839 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1840 tf.f = 0.0f;
1841 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 +00001842
1843 if (L == N) {
1844 // If the entire data set was linear, move the coefficients to the nonlinear portion
1845 // with G == 1. This lets use a canonical representation with d == 0.
1846 tf.g = 1;
1847 tf.a = tf.c;
1848 tf.b = tf.f;
1849 tf.c = tf.d = tf.e = tf.f = 0;
1850 } else if (L == N - 1) {
1851 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1852 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001853 tf.a = (eval_curve(curve, (N-1)*dx) -
1854 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001855 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001856 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001857 - tf.a * (N-2)*dx;
1858 tf.e = 0;
1859 } else {
1860 // Start by guessing a gamma-only curve through the midpoint.
1861 int mid = (L + N) / 2;
1862 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001863 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001864 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001865 tf.a = 1;
1866 tf.b = 0;
1867 tf.e = tf.c*tf.d + tf.f
1868 - powf_(tf.a*tf.d + tf.b, tf.g);
1869
1870
1871 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1872 !fit_nonlinear(curve, L,N, &tf_inv)) {
1873 continue;
1874 }
1875
1876 // We fit tf_inv, so calculate tf to keep in sync.
1877 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1878 continue;
1879 }
1880 }
1881
skia-autorollc7a4e772019-10-15 19:47:13 +00001882 // We'd better have a sane, sRGB-ish TF by now.
1883 // Other non-Bad TFs would be fine, but we know we've only ever tried to fit sRGBish;
1884 // anything else is just some accident of math and the way we pun tf.g as a type flag.
1885 if (sRGBish != classify(tf)) {
1886 continue;
1887 }
1888
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001889 // We find our error by roundtripping the table through tf_inv.
1890 //
1891 // (The most likely use case for this approximation is to be inverted and
1892 // used as the transfer function for a destination color space.)
1893 //
1894 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1895 // invertible, so re-verify that here (and use the new inverse for testing).
1896 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1897 continue;
1898 }
1899
skia-autorolld95243b2019-11-20 19:40:25 +00001900 float err = skcms_MaxRoundtripError(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001901 if (*max_error > err) {
1902 *max_error = err;
1903 *approx = tf;
1904 }
1905 }
1906 return isfinitef_(*max_error);
1907}
1908
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001909// ~~~~ Impl. of skcms_Transform() ~~~~
1910
1911typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001912 Op_load_a8,
1913 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001914 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001915 Op_load_4444,
1916 Op_load_565,
1917 Op_load_888,
1918 Op_load_8888,
1919 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001920 Op_load_161616LE,
1921 Op_load_16161616LE,
1922 Op_load_161616BE,
1923 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001924 Op_load_hhh,
1925 Op_load_hhhh,
1926 Op_load_fff,
1927 Op_load_ffff,
1928
1929 Op_swap_rb,
1930 Op_clamp,
1931 Op_invert,
1932 Op_force_opaque,
1933 Op_premul,
1934 Op_unpremul,
1935 Op_matrix_3x3,
1936 Op_matrix_3x4,
1937 Op_lab_to_xyz,
1938
1939 Op_tf_r,
1940 Op_tf_g,
1941 Op_tf_b,
1942 Op_tf_a,
1943
skia-autorollf6d50642019-10-14 15:31:13 +00001944 Op_pq_r,
1945 Op_pq_g,
1946 Op_pq_b,
1947 Op_pq_a,
1948
1949 Op_hlg_r,
1950 Op_hlg_g,
1951 Op_hlg_b,
1952 Op_hlg_a,
1953
1954 Op_hlginv_r,
1955 Op_hlginv_g,
1956 Op_hlginv_b,
1957 Op_hlginv_a,
1958
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001959 Op_table_r,
1960 Op_table_g,
1961 Op_table_b,
1962 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001963
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001964 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001965
1966 Op_store_a8,
1967 Op_store_g8,
1968 Op_store_4444,
1969 Op_store_565,
1970 Op_store_888,
1971 Op_store_8888,
1972 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001973 Op_store_161616LE,
1974 Op_store_16161616LE,
1975 Op_store_161616BE,
1976 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001977 Op_store_hhh,
1978 Op_store_hhhh,
1979 Op_store_fff,
1980 Op_store_ffff,
1981} Op;
1982
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001983#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001984 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 +00001985#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001986 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1987 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1988 template <int N, typename T>
1989 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 +00001990
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001991 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 +00001992#endif
1993
1994// First, instantiate our default exec_ops() implementation using the default compiliation target.
1995
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001996namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00001997#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
1998 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001999 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00002000 template <typename T> using V = T;
2001 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002002#elif defined(__AVX512F__)
2003 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002004 template <typename T> using V = Vec<N,T>;
2005 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002006#elif defined(__AVX__)
2007 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002008 template <typename T> using V = Vec<N,T>;
2009 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00002010#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
2011 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002012 template <typename T> using V = Vec<N,T>;
2013 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002014#else
2015 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00002016 template <typename T> using V = Vec<N,T>;
2017 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002018#endif
2019
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002020 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002021 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002022}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002023
2024// 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 +00002025#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00002026 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00002027 (( defined(__clang__) && __clang_major__ >= 5) || \
2028 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00002029 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002030
skia-autorollba6087c2019-04-09 13:57:02 +00002031 #if !defined(__AVX2__)
2032 #if defined(__clang__)
2033 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
2034 #elif defined(__GNUC__)
2035 #pragma GCC push_options
2036 #pragma GCC target("avx2,f16c")
2037 #endif
2038
2039 namespace hsw {
2040 #define USING_AVX
2041 #define USING_AVX_F16C
2042 #define USING_AVX2
2043 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002044 template <typename T> using V = Vec<N,T>;
2045 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00002046
2047 #include "src/Transform_inl.h"
2048
2049 // src/Transform_inl.h will undefine USING_* for us.
2050 #undef N
2051 }
2052
2053 #if defined(__clang__)
2054 #pragma clang attribute pop
2055 #elif defined(__GNUC__)
2056 #pragma GCC pop_options
2057 #endif
2058
2059 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002060 #endif
2061
skia-autorollba6087c2019-04-09 13:57:02 +00002062 #if !defined(__AVX512F__)
2063 #if defined(__clang__)
2064 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
2065 #elif defined(__GNUC__)
2066 #pragma GCC push_options
2067 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
2068 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002069
skia-autorollba6087c2019-04-09 13:57:02 +00002070 namespace skx {
2071 #define USING_AVX512F
2072 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002073 template <typename T> using V = Vec<N,T>;
2074 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002075
skia-autorollba6087c2019-04-09 13:57:02 +00002076 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002077
skia-autorollba6087c2019-04-09 13:57:02 +00002078 // src/Transform_inl.h will undefine USING_* for us.
2079 #undef N
2080 }
2081
2082 #if defined(__clang__)
2083 #pragma clang attribute pop
2084 #elif defined(__GNUC__)
2085 #pragma GCC pop_options
2086 #endif
2087
2088 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002089 #endif
2090
skia-autorollba6087c2019-04-09 13:57:02 +00002091 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
2092 enum class CpuType { None, HSW, SKX };
2093 static CpuType cpu_type() {
2094 static const CpuType type = []{
2095 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002096
skia-autorollba6087c2019-04-09 13:57:02 +00002097 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2098 uint32_t eax, ebx, ecx, edx;
2099 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2100 : "0"(1), "2"(0));
2101 if ((edx & (1u<<25)) && // SSE
2102 (edx & (1u<<26)) && // SSE2
2103 (ecx & (1u<< 0)) && // SSE3
2104 (ecx & (1u<< 9)) && // SSSE3
2105 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
2106 (ecx & (1u<<19)) && // SSE4.1
2107 (ecx & (1u<<20)) && // SSE4.2
2108 (ecx & (1u<<26)) && // XSAVE
2109 (ecx & (1u<<27)) && // OSXSAVE
2110 (ecx & (1u<<28)) && // AVX
2111 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002112
skia-autorollba6087c2019-04-09 13:57:02 +00002113 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2114 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2115 : "0"(7), "2"(0));
2116 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2117 uint32_t xcr0, dont_need_edx;
2118 __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 +00002119
skia-autorollba6087c2019-04-09 13:57:02 +00002120 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2121 (xcr0 & (1u<<2)) && // YMM register state saved?
2122 (ebx & (1u<<5))) { // AVX2
2123 // At this point we're at least HSW. Continue checking for SKX.
2124 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2125 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2126 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2127 (ebx & (1u<<16)) && // AVX512F
2128 (ebx & (1u<<17)) && // AVX512DQ
2129 (ebx & (1u<<28)) && // AVX512CD
2130 (ebx & (1u<<30)) && // AVX512BW
2131 (ebx & (1u<<31))) { // AVX512VL
2132 return CpuType::SKX;
2133 }
2134 return CpuType::HSW;
2135 }
2136 }
2137 return CpuType::None;
2138 }();
2139 return type;
2140 }
2141 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002142
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002143#endif
2144
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002145typedef struct {
2146 Op op;
2147 const void* arg;
2148} OpAndArg;
2149
2150static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autorollf6d50642019-10-14 15:31:13 +00002151 static const struct { Op sRGBish, PQish, HLGish, HLGinvish, table; } ops[] = {
2152 { Op_tf_r, Op_pq_r, Op_hlg_r, Op_hlginv_r, Op_table_r },
2153 { Op_tf_g, Op_pq_g, Op_hlg_g, Op_hlginv_g, Op_table_g },
2154 { Op_tf_b, Op_pq_b, Op_hlg_b, Op_hlginv_b, Op_table_b },
2155 { 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 +00002156 };
skia-autorollf6d50642019-10-14 15:31:13 +00002157 const auto& op = ops[channel];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002158
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002159 if (curve->table_entries == 0) {
skia-autorollf6d50642019-10-14 15:31:13 +00002160 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 +00002161
skia-autorollf6d50642019-10-14 15:31:13 +00002162 const skcms_TransferFunction& tf = curve->parametric;
2163
2164 if (tf.g == 1 && tf.a == 1 &&
2165 tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) {
2166 return noop;
2167 }
2168
2169 switch (classify(tf)) {
2170 case Bad: return noop;
2171 case sRGBish: return OpAndArg{op.sRGBish, &tf};
2172 case PQish: return OpAndArg{op.PQish, &tf};
2173 case HLGish: return OpAndArg{op.HLGish, &tf};
2174 case HLGinvish: return OpAndArg{op.HLGinvish, &tf};
2175 }
2176 }
2177 return OpAndArg{op.table, curve};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002178}
2179
2180static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2181 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002182 case skcms_PixelFormat_A_8 >> 1: return 1;
2183 case skcms_PixelFormat_G_8 >> 1: return 1;
2184 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2185 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2186 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2187 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2188 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2189 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2190 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2191 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2192 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2193 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002194 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2195 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002196 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2197 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2198 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2199 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002200 }
2201 assert(false);
2202 return 0;
2203}
2204
2205static bool prep_for_destination(const skcms_ICCProfile* profile,
2206 skcms_Matrix3x3* fromXYZD50,
2207 skcms_TransferFunction* invR,
2208 skcms_TransferFunction* invG,
2209 skcms_TransferFunction* invB) {
2210 // We only support destinations with parametric transfer functions
2211 // and with gamuts that can be transformed from XYZD50.
2212 return profile->has_trc
2213 && profile->has_toXYZD50
2214 && profile->trc[0].table_entries == 0
2215 && profile->trc[1].table_entries == 0
2216 && profile->trc[2].table_entries == 0
2217 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2218 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2219 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2220 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2221}
2222
2223bool skcms_Transform(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,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002231 size_t npixels) {
2232 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2233 dst, dstFmt, dstAlpha, dstProfile,
2234 npixels, nullptr);
2235}
2236
2237bool skcms_TransformWithPalette(const void* src,
2238 skcms_PixelFormat srcFmt,
2239 skcms_AlphaFormat srcAlpha,
2240 const skcms_ICCProfile* srcProfile,
2241 void* dst,
2242 skcms_PixelFormat dstFmt,
2243 skcms_AlphaFormat dstAlpha,
2244 const skcms_ICCProfile* dstProfile,
2245 size_t nz,
2246 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002247 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2248 src_bpp = bytes_per_pixel(srcFmt);
2249 // Let's just refuse if the request is absurdly big.
2250 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2251 return false;
2252 }
2253 int n = (int)nz;
2254
2255 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2256 if (!srcProfile) {
2257 srcProfile = skcms_sRGB_profile();
2258 }
2259 if (!dstProfile) {
2260 dstProfile = skcms_sRGB_profile();
2261 }
2262
2263 // 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 +00002264 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002265 return false;
2266 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002267 // TODO: more careful alias rejection (like, dst == src + 1)?
2268
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002269 if (needs_palette(srcFmt) && !palette) {
2270 return false;
2271 }
2272
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002273 Op program [32];
2274 const void* arguments[32];
2275
2276 Op* ops = program;
2277 const void** args = arguments;
2278
skia-autorollf6d50642019-10-14 15:31:13 +00002279 // These are always parametric curves of some sort.
2280 skcms_Curve dst_curves[3];
2281 dst_curves[0].table_entries =
2282 dst_curves[1].table_entries =
2283 dst_curves[2].table_entries = 0;
2284
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002285 skcms_Matrix3x3 from_xyz;
2286
2287 switch (srcFmt >> 1) {
2288 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002289 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2290 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2291 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2292 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2293 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2294 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2295 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2296 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2297 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2298 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2299 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002300 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2301 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 +00002302 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2303 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2304 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2305 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002306
2307 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2308 *args++ = palette;
2309 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002310 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002311 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2312 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2313 *ops++ = Op_clamp;
2314 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002315 if (srcFmt & 1) {
2316 *ops++ = Op_swap_rb;
2317 }
2318 skcms_ICCProfile gray_dst_profile;
2319 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2320 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2321 // luminance (Y) by the destination transfer function.
2322 gray_dst_profile = *dstProfile;
2323 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2324 dstProfile = &gray_dst_profile;
2325 }
2326
2327 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2328 // Photoshop creates CMYK images as inverse CMYK.
2329 // These happen to be the only ones we've _ever_ seen.
2330 *ops++ = Op_invert;
2331 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2332 srcAlpha = skcms_AlphaFormat_Unpremul;
2333 }
2334
2335 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2336 *ops++ = Op_force_opaque;
2337 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2338 *ops++ = Op_unpremul;
2339 }
2340
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002341 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002342
2343 if (!prep_for_destination(dstProfile,
skia-autorollf6d50642019-10-14 15:31:13 +00002344 &from_xyz,
2345 &dst_curves[0].parametric,
2346 &dst_curves[1].parametric,
2347 &dst_curves[2].parametric)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002348 return false;
2349 }
2350
2351 if (srcProfile->has_A2B) {
2352 if (srcProfile->A2B.input_channels) {
2353 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2354 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 +00002355 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002356 *ops++ = oa.op;
2357 *args++ = oa.arg;
2358 }
2359 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002360 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002361 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002362 *args++ = &srcProfile->A2B;
2363 }
2364
2365 if (srcProfile->A2B.matrix_channels == 3) {
2366 for (int i = 0; i < 3; i++) {
2367 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 +00002368 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002369 *ops++ = oa.op;
2370 *args++ = oa.arg;
2371 }
2372 }
2373
2374 static const skcms_Matrix3x4 I = {{
2375 {1,0,0,0},
2376 {0,1,0,0},
2377 {0,0,1,0},
2378 }};
2379 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2380 *ops++ = Op_matrix_3x4;
2381 *args++ = &srcProfile->A2B.matrix;
2382 }
2383 }
2384
2385 if (srcProfile->A2B.output_channels == 3) {
2386 for (int i = 0; i < 3; i++) {
2387 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 +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 }
2394
2395 if (srcProfile->pcs == skcms_Signature_Lab) {
2396 *ops++ = Op_lab_to_xyz;
2397 }
2398
2399 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2400 for (int i = 0; i < 3; i++) {
2401 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002402 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002403 *ops++ = oa.op;
2404 *args++ = oa.arg;
2405 }
2406 }
2407 } else {
2408 return false;
2409 }
2410
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002411 // A2B sources should already be in XYZD50 at this point.
2412 // Others still need to be transformed using their toXYZD50 matrix.
2413 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2414 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2415 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2416 static const skcms_Matrix3x3 I = {{
2417 { 1.0f, 0.0f, 0.0f },
2418 { 0.0f, 1.0f, 0.0f },
2419 { 0.0f, 0.0f, 1.0f },
2420 }};
2421 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2422
2423 // There's a chance the source and destination gamuts are identical,
2424 // in which case we can skip the gamut transform.
2425 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2426 // Concat the entire gamut transform into from_xyz,
2427 // now slightly misnamed but it's a handy spot to stash the result.
2428 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2429 *ops++ = Op_matrix_3x3;
2430 *args++ = &from_xyz;
2431 }
2432
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002433 // Encode back to dst RGB using its parametric transfer functions.
skia-autorollf6d50642019-10-14 15:31:13 +00002434 for (int i = 0; i < 3; i++) {
2435 OpAndArg oa = select_curve_op(dst_curves+i, i);
2436 if (oa.arg) {
2437 assert (oa.op != Op_table_r &&
2438 oa.op != Op_table_g &&
2439 oa.op != Op_table_b &&
2440 oa.op != Op_table_a);
2441 *ops++ = oa.op;
2442 *args++ = oa.arg;
2443 }
2444 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002445 }
2446
skia-autoroll2e4fa242019-03-11 21:14:18 +00002447 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2448 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002449 //
2450 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2451 // 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 +00002452 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2453 *ops++ = Op_clamp;
2454 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002455 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2456 *ops++ = Op_force_opaque;
2457 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2458 *ops++ = Op_premul;
2459 }
2460 if (dstFmt & 1) {
2461 *ops++ = Op_swap_rb;
2462 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002463 switch (dstFmt >> 1) {
2464 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002465 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2466 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2467 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2468 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2469 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2470 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2471 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2472 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2473 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2474 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2475 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002476 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2477 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 +00002478 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2479 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2480 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2481 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 +00002482 }
2483
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002484 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002485#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002486 switch (cpu_type()) {
2487 case CpuType::None: break;
2488 case CpuType::HSW: run = hsw::run_program; break;
2489 case CpuType::SKX: run = hsw::run_program; break;
2490 }
2491#endif
2492#if defined(TEST_FOR_SKX)
2493 switch (cpu_type()) {
2494 case CpuType::None: break;
2495 case CpuType::HSW: break;
2496 case CpuType::SKX: run = skx::run_program; break;
2497 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002498#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002499 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 +00002500 return true;
2501}
2502
2503static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2504#if defined(NDEBUG)
2505 (void)profile;
2506#else
2507 skcms_Matrix3x3 fromXYZD50;
2508 skcms_TransferFunction invR, invG, invB;
2509 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2510#endif
2511}
2512
2513bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2514 skcms_Matrix3x3 fromXYZD50;
2515 if (!profile->has_trc || !profile->has_toXYZD50
2516 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2517 return false;
2518 }
2519
2520 skcms_TransferFunction tf[3];
2521 for (int i = 0; i < 3; i++) {
2522 skcms_TransferFunction inv;
2523 if (profile->trc[i].table_entries == 0
2524 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2525 tf[i] = profile->trc[i].parametric;
2526 continue;
2527 }
2528
2529 float max_error;
2530 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2531 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2532 return false;
2533 }
2534 }
2535
2536 for (int i = 0; i < 3; ++i) {
2537 profile->trc[i].table_entries = 0;
2538 profile->trc[i].parametric = tf[i];
2539 }
2540
2541 assert_usable_as_destination(profile);
2542 return true;
2543}
2544
2545bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2546 // Operate on a copy of profile, so we can choose the best TF for the original curves
2547 skcms_ICCProfile result = *profile;
2548 if (!skcms_MakeUsableAsDestination(&result)) {
2549 return false;
2550 }
2551
2552 int best_tf = 0;
2553 float min_max_error = INFINITY_;
2554 for (int i = 0; i < 3; i++) {
2555 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002556 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2557 return false;
2558 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002559
2560 float err = 0;
2561 for (int j = 0; j < 3; ++j) {
skia-autorolld95243b2019-11-20 19:40:25 +00002562 err = fmaxf_(err, skcms_MaxRoundtripError(&profile->trc[j], &inv));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002563 }
2564 if (min_max_error > err) {
2565 min_max_error = err;
2566 best_tf = i;
2567 }
2568 }
2569
2570 for (int i = 0; i < 3; i++) {
2571 result.trc[i].parametric = result.trc[best_tf].parametric;
2572 }
2573
2574 *profile = result;
2575 assert_usable_as_destination(profile);
2576 return true;
2577}