blob: 587671b14913ebfe3489548b6d1165ae5792c0aa [file] [log] [blame]
Mike Kleinded7a552018-04-10 10:05:31 -04001/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00008#include "skcms.h"
9#include "skcms_internal.h"
10#include <assert.h>
11#include <float.h>
12#include <limits.h>
13#include <stdlib.h>
14#include <string.h>
Mike Kleinded7a552018-04-10 10:05:31 -040015
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +000016#if defined(__ARM_NEON)
17 #include <arm_neon.h>
18#elif defined(__SSE__)
19 #include <immintrin.h>
skia-autorollba6087c2019-04-09 13:57:02 +000020
21 #if defined(__clang__)
22 // That #include <immintrin.h> is usually enough, but Clang's headers
23 // "helpfully" skip including the whole kitchen sink when _MSC_VER is
24 // defined, because lots of programs on Windows would include that and
25 // it'd be a lot slower. But we want all those headers included so we
26 // can use their features after runtime checks later.
27 #include <smmintrin.h>
28 #include <avxintrin.h>
29 #include <avx2intrin.h>
30 #include <avx512fintrin.h>
31 #include <avx512dqintrin.h>
32 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +000033#endif
34
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000035// sizeof(x) will return size_t, which is 32-bit on some machines and 64-bit on others.
36// We have better testing on 64-bit machines, so force 32-bit machines to behave like 64-bit.
37//
38// Please do not use sizeof() directly, and size_t only when required.
39// (We have no way of enforcing these requests...)
40#define SAFE_SIZEOF(x) ((uint64_t)sizeof(x))
41
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +000042// Same sort of thing for _Layout structs with a variable sized array at the end (named "variable").
43#define SAFE_FIXED_SIZE(type) ((uint64_t)offsetof(type, variable))
44
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000045static const union {
46 uint32_t bits;
47 float f;
48} inf_ = { 0x7f800000 };
49#define INFINITY_ inf_.f
50
51static float fmaxf_(float x, float y) { return x > y ? x : y; }
52static float fminf_(float x, float y) { return x < y ? x : y; }
53
54static bool isfinitef_(float x) { return 0 == x*0; }
55
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000056static float minus_1_ulp(float x) {
57 int32_t bits;
58 memcpy(&bits, &x, sizeof(bits));
59 bits = bits - 1;
60 memcpy(&x, &bits, sizeof(bits));
61 return x;
62}
63
skia-autorollc093cc82019-10-08 19:22:13 +000064// Most transfer functions we work with are sRGBish.
65// For exotic HDR transfer functions, we encode them using a tf.g that makes no sense,
66// and repurpose the other fields to hold the parameters of the HDR functions.
67enum TFKind { Bad, sRGBish, PQish, HLGish, HLGinvish };
68struct TF_PQish { float A,B,C,D,E,F; };
69struct TF_HLGish { float R,G,a,b,c; };
70
71static float TFKind_marker(TFKind kind) {
72 // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM.
73 return -(float)kind;
74}
75
76static TFKind classify(const skcms_TransferFunction& tf, TF_PQish* pq = nullptr
77 , TF_HLGish* hlg = nullptr) {
78 if (tf.g < 0 && (int)tf.g == tf.g) {
79 // TODO: sanity checks for PQ/HLG like we do for sRGBish.
80 switch (-(int)tf.g) {
81 case PQish: if (pq ) { memcpy(pq , &tf.a, sizeof(*pq )); } return PQish;
82 case HLGish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGish;
83 case HLGinvish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGinvish;
84 }
85 return Bad;
86 }
87
88 // Basic sanity checks for sRGBish transfer functions.
89 if (isfinitef_(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g)
90 // a,c,d,g should be non-negative to make any sense.
91 && tf.a >= 0
92 && tf.c >= 0
93 && tf.d >= 0
94 && tf.g >= 0
95 // Raising a negative value to a fractional tf->g produces complex numbers.
96 && tf.a * tf.d + tf.b >= 0) {
97 return sRGBish;
98 }
99
100 return Bad;
101}
102
103// TODO: temporary shim for old call sites
104static bool tf_is_valid(const skcms_TransferFunction* tf) {
105 return classify(*tf) == sRGBish;
106}
107
108
109bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
110 float A, float B, float C,
111 float D, float E, float F) {
112 *tf = { TFKind_marker(PQish), A,B,C,D,E,F };
113 assert(classify(*tf) == PQish);
114 return true;
115}
116
117float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
118 float sign = x < 0 ? -1.0f : 1.0f;
119 x *= sign;
120
121 TF_PQish pq;
122 switch (classify(*tf, &pq)) {
123 case Bad: break;
124 case HLGish: break;
125 case HLGinvish: break;
126
127 case sRGBish: return sign * (x < tf->d ? tf->c * x + tf->f
128 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
129
130 case PQish: return sign * powf_(fmaxf_(pq.A + pq.B * powf_(x, pq.C), 0)
131 / (pq.D + pq.E * powf_(x, pq.C)),
132 pq.F);
133 }
134 return 0;
135}
136
137
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000138static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000139 if (curve->table_entries == 0) {
140 return skcms_TransferFunction_eval(&curve->parametric, x);
141 }
142
143 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 +0000144 int lo = (int) ix ,
145 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000146 float t = ix - (float)lo;
147
148 float l, h;
149 if (curve->table_8) {
150 l = curve->table_8[lo] * (1/255.0f);
151 h = curve->table_8[hi] * (1/255.0f);
152 } else {
153 uint16_t be_l, be_h;
154 memcpy(&be_l, curve->table_16 + 2*lo, 2);
155 memcpy(&be_h, curve->table_16 + 2*hi, 2);
156 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
157 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
158 l = le_l * (1/65535.0f);
159 h = le_h * (1/65535.0f);
160 }
161 return l + (h-l)*t;
162}
163
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000164static float max_roundtrip_error(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000165 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
166 const float dx = 1.0f / (N - 1);
167 float err = 0;
168 for (uint32_t i = 0; i < N; i++) {
169 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000170 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000171 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
172 }
173 return err;
174}
175
176bool skcms_AreApproximateInverses(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000177 return max_roundtrip_error(curve, inv_tf) < (1/512.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000178}
179
180// Additional ICC signature values that are only used internally
181enum {
182 // File signature
183 skcms_Signature_acsp = 0x61637370,
184
185 // Tag signatures
186 skcms_Signature_rTRC = 0x72545243,
187 skcms_Signature_gTRC = 0x67545243,
188 skcms_Signature_bTRC = 0x62545243,
189 skcms_Signature_kTRC = 0x6B545243,
190
191 skcms_Signature_rXYZ = 0x7258595A,
192 skcms_Signature_gXYZ = 0x6758595A,
193 skcms_Signature_bXYZ = 0x6258595A,
194
195 skcms_Signature_A2B0 = 0x41324230,
196 skcms_Signature_A2B1 = 0x41324231,
197 skcms_Signature_mAB = 0x6D414220,
198
199 skcms_Signature_CHAD = 0x63686164,
200
201 // Type signatures
202 skcms_Signature_curv = 0x63757276,
203 skcms_Signature_mft1 = 0x6D667431,
204 skcms_Signature_mft2 = 0x6D667432,
205 skcms_Signature_para = 0x70617261,
206 skcms_Signature_sf32 = 0x73663332,
207 // XYZ is also a PCS signature, so it's defined in skcms.h
208 // skcms_Signature_XYZ = 0x58595A20,
209};
210
211static uint16_t read_big_u16(const uint8_t* ptr) {
212 uint16_t be;
213 memcpy(&be, ptr, sizeof(be));
214#if defined(_MSC_VER)
215 return _byteswap_ushort(be);
216#else
217 return __builtin_bswap16(be);
218#endif
219}
220
221static uint32_t read_big_u32(const uint8_t* ptr) {
222 uint32_t be;
223 memcpy(&be, ptr, sizeof(be));
224#if defined(_MSC_VER)
225 return _byteswap_ulong(be);
226#else
227 return __builtin_bswap32(be);
228#endif
229}
230
231static int32_t read_big_i32(const uint8_t* ptr) {
232 return (int32_t)read_big_u32(ptr);
233}
234
235static float read_big_fixed(const uint8_t* ptr) {
236 return read_big_i32(ptr) * (1.0f / 65536.0f);
237}
238
239// Maps to an in-memory profile so that fields line up to the locations specified
240// in ICC.1:2010, section 7.2
241typedef struct {
242 uint8_t size [ 4];
243 uint8_t cmm_type [ 4];
244 uint8_t version [ 4];
245 uint8_t profile_class [ 4];
246 uint8_t data_color_space [ 4];
247 uint8_t pcs [ 4];
248 uint8_t creation_date_time [12];
249 uint8_t signature [ 4];
250 uint8_t platform [ 4];
251 uint8_t flags [ 4];
252 uint8_t device_manufacturer [ 4];
253 uint8_t device_model [ 4];
254 uint8_t device_attributes [ 8];
255 uint8_t rendering_intent [ 4];
256 uint8_t illuminant_X [ 4];
257 uint8_t illuminant_Y [ 4];
258 uint8_t illuminant_Z [ 4];
259 uint8_t creator [ 4];
260 uint8_t profile_id [16];
261 uint8_t reserved [28];
262 uint8_t tag_count [ 4]; // Technically not part of header, but required
263} header_Layout;
264
265typedef struct {
266 uint8_t signature [4];
267 uint8_t offset [4];
268 uint8_t size [4];
269} tag_Layout;
270
271static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
272 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
273}
274
275// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
276// use of the type is for the CHAD tag that stores exactly nine values.
277typedef struct {
278 uint8_t type [ 4];
279 uint8_t reserved [ 4];
280 uint8_t values [36];
281} sf32_Layout;
282
283bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
284 skcms_ICCTag tag;
285 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
286 return false;
287 }
288
289 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
290 return false;
291 }
292
293 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
294 const uint8_t* values = sf32Tag->values;
295 for (int r = 0; r < 3; ++r)
296 for (int c = 0; c < 3; ++c, values += 4) {
297 m->vals[r][c] = read_big_fixed(values);
298 }
299 return true;
300}
301
302// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
303// the type are for tags/data that store exactly one triple.
304typedef struct {
305 uint8_t type [4];
306 uint8_t reserved [4];
307 uint8_t X [4];
308 uint8_t Y [4];
309 uint8_t Z [4];
310} XYZ_Layout;
311
312static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
313 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
314 return false;
315 }
316
317 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
318
319 *x = read_big_fixed(xyzTag->X);
320 *y = read_big_fixed(xyzTag->Y);
321 *z = read_big_fixed(xyzTag->Z);
322 return true;
323}
324
325static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
326 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
327 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
328 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
329 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
330}
331
332typedef struct {
333 uint8_t type [4];
334 uint8_t reserved_a [4];
335 uint8_t function_type [2];
336 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000337 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 +0000338} para_Layout;
339
340static bool read_curve_para(const uint8_t* buf, uint32_t size,
341 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000342 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000343 return false;
344 }
345
346 const para_Layout* paraTag = (const para_Layout*)buf;
347
348 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
349 uint16_t function_type = read_big_u16(paraTag->function_type);
350 if (function_type > kGABCDEF) {
351 return false;
352 }
353
354 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 +0000355 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 +0000356 return false;
357 }
358
359 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000360 *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 +0000361 }
362
363 curve->table_entries = 0;
364 curve->parametric.a = 1.0f;
365 curve->parametric.b = 0.0f;
366 curve->parametric.c = 0.0f;
367 curve->parametric.d = 0.0f;
368 curve->parametric.e = 0.0f;
369 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000370 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000371
372 switch (function_type) {
373 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000374 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
375 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000376 if (curve->parametric.a == 0) {
377 return false;
378 }
379 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
380 break;
381 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000382 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
383 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
384 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000385 if (curve->parametric.a == 0) {
386 return false;
387 }
388 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
389 curve->parametric.f = curve->parametric.e;
390 break;
391 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000392 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
393 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
394 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
395 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000396 break;
397 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000398 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
399 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
400 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
401 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
402 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
403 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000404 break;
405 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000406 return tf_is_valid(&curve->parametric);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000407}
408
409typedef struct {
410 uint8_t type [4];
411 uint8_t reserved [4];
412 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000413 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 +0000414} curv_Layout;
415
416static bool read_curve_curv(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(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000419 return false;
420 }
421
422 const curv_Layout* curvTag = (const curv_Layout*)buf;
423
424 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 +0000425 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 +0000426 return false;
427 }
428
429 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000430 *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 +0000431 }
432
433 if (value_count < 2) {
434 curve->table_entries = 0;
435 curve->parametric.a = 1.0f;
436 curve->parametric.b = 0.0f;
437 curve->parametric.c = 0.0f;
438 curve->parametric.d = 0.0f;
439 curve->parametric.e = 0.0f;
440 curve->parametric.f = 0.0f;
441 if (value_count == 0) {
442 // Empty tables are a shorthand for an identity curve
443 curve->parametric.g = 1.0f;
444 } else {
445 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000446 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 +0000447 }
448 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000449 curve->table_8 = nullptr;
450 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000451 curve->table_entries = value_count;
452 }
453
454 return true;
455}
456
457// 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 +0000458// 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 +0000459static bool read_curve(const uint8_t* buf, uint32_t size,
460 skcms_Curve* curve, uint32_t* curve_size) {
461 if (!buf || size < 4 || !curve) {
462 return false;
463 }
464
465 uint32_t type = read_big_u32(buf);
466 if (type == skcms_Signature_para) {
467 return read_curve_para(buf, size, curve, curve_size);
468 } else if (type == skcms_Signature_curv) {
469 return read_curve_curv(buf, size, curve, curve_size);
470 }
471
472 return false;
473}
474
475// mft1 and mft2 share a large chunk of data
476typedef struct {
477 uint8_t type [ 4];
478 uint8_t reserved_a [ 4];
479 uint8_t input_channels [ 1];
480 uint8_t output_channels [ 1];
481 uint8_t grid_points [ 1];
482 uint8_t reserved_b [ 1];
483 uint8_t matrix [36];
484} mft_CommonLayout;
485
486typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000487 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000488
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000489 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000490} mft1_Layout;
491
492typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000493 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000494
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000495 uint8_t input_table_entries [2];
496 uint8_t output_table_entries [2];
497 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000498} mft2_Layout;
499
500static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
501 // MFT matrices are applied before the first set of curves, but must be identity unless the
502 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
503 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
504 // field/flag.
505 a2b->matrix_channels = 0;
506
507 a2b->input_channels = mftTag->input_channels[0];
508 a2b->output_channels = mftTag->output_channels[0];
509
510 // We require exactly three (ie XYZ/Lab/RGB) output channels
511 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
512 return false;
513 }
514 // We require at least one, and no more than four (ie CMYK) input channels
515 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
516 return false;
517 }
518
519 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
520 a2b->grid_points[i] = mftTag->grid_points[0];
521 }
522 // The grid only makes sense with at least two points along each axis
523 if (a2b->grid_points[0] < 2) {
524 return false;
525 }
526
527 return true;
528}
529
530static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
531 uint32_t input_table_entries, uint32_t output_table_entries,
532 skcms_A2B* a2b) {
533 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
534 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
535 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
536
537 // [input|output]_channels are <= 4, so still no overflow
538 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
539 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
540
541 uint64_t grid_size = a2b->output_channels * byte_width;
542 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
543 grid_size *= a2b->grid_points[axis];
544 }
545
546 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
547 return false;
548 }
549
550 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
551 a2b->input_curves[i].table_entries = input_table_entries;
552 if (byte_width == 1) {
553 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 +0000554 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000555 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000556 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000557 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
558 }
559 }
560
561 if (byte_width == 1) {
562 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 +0000563 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000564 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000565 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000566 a2b->grid_16 = table_base + byte_len_all_input_tables;
567 }
568
569 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
570 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
571 a2b->output_curves[i].table_entries = output_table_entries;
572 if (byte_width == 1) {
573 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 +0000574 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000575 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000576 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000577 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
578 }
579 }
580
581 return true;
582}
583
584static 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 +0000585 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000586 return false;
587 }
588
589 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
590 if (!read_mft_common(mftTag->common, a2b)) {
591 return false;
592 }
593
594 uint32_t input_table_entries = 256;
595 uint32_t output_table_entries = 256;
596
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000597 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 +0000598 input_table_entries, output_table_entries, a2b);
599}
600
601static 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 +0000602 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000603 return false;
604 }
605
606 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
607 if (!read_mft_common(mftTag->common, a2b)) {
608 return false;
609 }
610
611 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
612 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
613
614 // ICC spec mandates that 2 <= table_entries <= 4096
615 if (input_table_entries < 2 || input_table_entries > 4096 ||
616 output_table_entries < 2 || output_table_entries > 4096) {
617 return false;
618 }
619
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000620 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 +0000621 input_table_entries, output_table_entries, a2b);
622}
623
624static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
625 uint32_t num_curves, skcms_Curve* curves) {
626 for (uint32_t i = 0; i < num_curves; ++i) {
627 if (curve_offset > size) {
628 return false;
629 }
630
631 uint32_t curve_bytes;
632 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
633 return false;
634 }
635
636 if (curve_bytes > UINT32_MAX - 3) {
637 return false;
638 }
639 curve_bytes = (curve_bytes + 3) & ~3U;
640
641 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
642 curve_offset = (uint32_t)new_offset_64;
643 if (new_offset_64 != curve_offset) {
644 return false;
645 }
646 }
647
648 return true;
649}
650
651typedef struct {
652 uint8_t type [ 4];
653 uint8_t reserved_a [ 4];
654 uint8_t input_channels [ 1];
655 uint8_t output_channels [ 1];
656 uint8_t reserved_b [ 2];
657 uint8_t b_curve_offset [ 4];
658 uint8_t matrix_offset [ 4];
659 uint8_t m_curve_offset [ 4];
660 uint8_t clut_offset [ 4];
661 uint8_t a_curve_offset [ 4];
662} mAB_Layout;
663
664typedef struct {
665 uint8_t grid_points [16];
666 uint8_t grid_byte_width [ 1];
667 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000668 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000669} mABCLUT_Layout;
670
671static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
672 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
673 return false;
674 }
675
676 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
677
678 a2b->input_channels = mABTag->input_channels[0];
679 a2b->output_channels = mABTag->output_channels[0];
680
681 // We require exactly three (ie XYZ/Lab/RGB) output channels
682 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
683 return false;
684 }
685 // We require no more than four (ie CMYK) input channels
686 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
687 return false;
688 }
689
690 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
691 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
692 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
693 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
694 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
695
696 // "B" curves must be present
697 if (0 == b_curve_offset) {
698 return false;
699 }
700
701 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
702 a2b->output_curves)) {
703 return false;
704 }
705
706 // "M" curves and Matrix must be used together
707 if (0 != m_curve_offset) {
708 if (0 == matrix_offset) {
709 return false;
710 }
711 a2b->matrix_channels = a2b->output_channels;
712 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
713 a2b->matrix_curves)) {
714 return false;
715 }
716
717 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
718 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
719 return false;
720 }
721 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
722 const uint8_t* mtx_buf = tag->buf + matrix_offset;
723 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
724 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
725 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
726 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
727 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
728 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
729 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
730 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
731 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
732 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
733 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
734 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
735 } else {
736 if (0 != matrix_offset) {
737 return false;
738 }
739 a2b->matrix_channels = 0;
740 }
741
742 // "A" curves and CLUT must be used together
743 if (0 != a_curve_offset) {
744 if (0 == clut_offset) {
745 return false;
746 }
747 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
748 a2b->input_curves)) {
749 return false;
750 }
751
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000752 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 +0000753 return false;
754 }
755 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
756
757 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000758 a2b->grid_8 = clut->variable;
759 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000760 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000761 a2b->grid_8 = nullptr;
762 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000763 } else {
764 return false;
765 }
766
767 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
768 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
769 a2b->grid_points[i] = clut->grid_points[i];
770 // The grid only makes sense with at least two points along each axis
771 if (a2b->grid_points[i] < 2) {
772 return false;
773 }
774 grid_size *= a2b->grid_points[i];
775 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000776 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 +0000777 return false;
778 }
779 } else {
780 if (0 != clut_offset) {
781 return false;
782 }
783
784 // If there is no CLUT, the number of input and output channels must match
785 if (a2b->input_channels != a2b->output_channels) {
786 return false;
787 }
788
789 // Zero out the number of input channels to signal that we're skipping this stage
790 a2b->input_channels = 0;
791 }
792
793 return true;
794}
795
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000796// If you pass f, we'll fit a possibly-non-zero value for *f.
797// If you pass nullptr, we'll assume you want *f to be treated as zero.
798static int fit_linear(const skcms_Curve* curve, int N, float tol,
799 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000800 assert(N > 1);
801 // We iteratively fit the first points to the TF's linear piece.
802 // We want the cx + f line to pass through the first and last points we fit exactly.
803 //
804 // As we walk along the points we find the minimum and maximum slope of the line before the
805 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
806 // emtpy, when we definitely can't add any more points.
807 //
808 // Some points' error intervals may intersect the running interval but not lie fully
809 // within it. So we keep track of the last point we saw that is a valid end point candidate,
810 // and once the search is done, back up to build the line through *that* point.
811 const float dx = 1.0f / (N - 1);
812
813 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000814
815 float f_zero = 0.0f;
816 if (f) {
817 *f = eval_curve(curve, 0);
818 } else {
819 f = &f_zero;
820 }
821
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000822
823 float slope_min = -INFINITY_;
824 float slope_max = +INFINITY_;
825 for (int i = 1; i < N; ++i) {
826 float x = i * dx;
827 float y = eval_curve(curve, x);
828
829 float slope_max_i = (y + tol - *f) / x,
830 slope_min_i = (y - tol - *f) / x;
831 if (slope_max_i < slope_min || slope_max < slope_min_i) {
832 // Slope intervals would no longer overlap.
833 break;
834 }
835 slope_max = fminf_(slope_max, slope_max_i);
836 slope_min = fmaxf_(slope_min, slope_min_i);
837
838 float cur_slope = (y - *f) / x;
839 if (slope_min <= cur_slope && cur_slope <= slope_max) {
840 lin_points = i + 1;
841 *c = cur_slope;
842 }
843 }
844
845 // Set D to the last point that met our tolerance.
846 *d = (lin_points - 1) * dx;
847 return lin_points;
848}
849
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000850static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
851 bool ok = false;
852 if (tag->type == skcms_Signature_mft1) {
853 ok = read_tag_mft1(tag, a2b);
854 } else if (tag->type == skcms_Signature_mft2) {
855 ok = read_tag_mft2(tag, a2b);
856 } else if (tag->type == skcms_Signature_mAB) {
857 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
858 }
859 if (!ok) {
860 return false;
861 }
862
863 // Detect and canonicalize identity tables.
864 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000865 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
866 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
867 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
868 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
869 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
870 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
871 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
872 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
873 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
874 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000875 };
876
877 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
878 skcms_Curve* curve = curves[i];
879
880 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
881 int N = (int)curve->table_entries;
882
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000883 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 +0000884 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 +0000885 && c == 1.0f
886 && f == 0.0f) {
887 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000888 curve->table_8 = nullptr;
889 curve->table_16 = nullptr;
890 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 +0000891 }
892 }
893 }
894
895 return true;
896}
897
898void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
899 if (!profile || !profile->buffer || !tag) { return; }
900 if (idx > profile->tag_count) { return; }
901 const tag_Layout* tags = get_tag_table(profile);
902 tag->signature = read_big_u32(tags[idx].signature);
903 tag->size = read_big_u32(tags[idx].size);
904 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
905 tag->type = read_big_u32(tag->buf);
906}
907
908bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
909 if (!profile || !profile->buffer || !tag) { return false; }
910 const tag_Layout* tags = get_tag_table(profile);
911 for (uint32_t i = 0; i < profile->tag_count; ++i) {
912 if (read_big_u32(tags[i].signature) == sig) {
913 tag->signature = sig;
914 tag->size = read_big_u32(tags[i].size);
915 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
916 tag->type = read_big_u32(tag->buf);
917 return true;
918 }
919 }
920 return false;
921}
922
923static bool usable_as_src(const skcms_ICCProfile* profile) {
924 return profile->has_A2B
925 || (profile->has_trc && profile->has_toXYZD50);
926}
927
928bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
929 assert(SAFE_SIZEOF(header_Layout) == 132);
930
931 if (!profile) {
932 return false;
933 }
934 memset(profile, 0, SAFE_SIZEOF(*profile));
935
936 if (len < SAFE_SIZEOF(header_Layout)) {
937 return false;
938 }
939
940 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000941 const header_Layout* header = (const header_Layout*)buf;
942 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000943 profile->size = read_big_u32(header->size);
944 uint32_t version = read_big_u32(header->version);
945 profile->data_color_space = read_big_u32(header->data_color_space);
946 profile->pcs = read_big_u32(header->pcs);
947 uint32_t signature = read_big_u32(header->signature);
948 float illuminant_X = read_big_fixed(header->illuminant_X);
949 float illuminant_Y = read_big_fixed(header->illuminant_Y);
950 float illuminant_Z = read_big_fixed(header->illuminant_Z);
951 profile->tag_count = read_big_u32(header->tag_count);
952
953 // Validate signature, size (smaller than buffer, large enough to hold tag table),
954 // and major version
955 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
956 if (signature != skcms_Signature_acsp ||
957 profile->size > len ||
958 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
959 (version >> 24) > 4) {
960 return false;
961 }
962
963 // Validate that illuminant is D50 white
964 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
965 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
966 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
967 return false;
968 }
969
970 // Validate that all tag entries have sane offset + size
971 const tag_Layout* tags = get_tag_table(profile);
972 for (uint32_t i = 0; i < profile->tag_count; ++i) {
973 uint32_t tag_offset = read_big_u32(tags[i].offset);
974 uint32_t tag_size = read_big_u32(tags[i].size);
975 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
976 if (tag_size < 4 || tag_end > profile->size) {
977 return false;
978 }
979 }
980
981 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
982 return false;
983 }
984
985 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
986
987 // Pre-parse commonly used tags.
988 skcms_ICCTag kTRC;
989 if (profile->data_color_space == skcms_Signature_Gray &&
990 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000991 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 +0000992 // Malformed tag
993 return false;
994 }
995 profile->trc[1] = profile->trc[0];
996 profile->trc[2] = profile->trc[0];
997 profile->has_trc = true;
998
999 if (pcs_is_xyz) {
1000 profile->toXYZD50.vals[0][0] = illuminant_X;
1001 profile->toXYZD50.vals[1][1] = illuminant_Y;
1002 profile->toXYZD50.vals[2][2] = illuminant_Z;
1003 profile->has_toXYZD50 = true;
1004 }
1005 } else {
1006 skcms_ICCTag rTRC, gTRC, bTRC;
1007 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1008 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1009 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001010 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1011 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1012 !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 +00001013 // Malformed TRC tags
1014 return false;
1015 }
1016 profile->has_trc = true;
1017 }
1018
1019 skcms_ICCTag rXYZ, gXYZ, bXYZ;
1020 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1021 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1022 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1023 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1024 // Malformed XYZ tags
1025 return false;
1026 }
1027 profile->has_toXYZD50 = true;
1028 }
1029 }
1030
1031 skcms_ICCTag a2b_tag;
1032
1033 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
1034 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
1035 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
1036 // and all our known users are thinking exclusively in terms of relative colormetric.
1037 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
1038 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
1039 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
1040 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
1041 // Malformed A2B tag
1042 return false;
1043 }
1044 profile->has_A2B = true;
1045 break;
1046 }
1047 }
1048
1049 return usable_as_src(profile);
1050}
1051
1052
1053const skcms_ICCProfile* skcms_sRGB_profile() {
1054 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001055 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001056
1057 0, // size, moot here
1058 skcms_Signature_RGB, // data_color_space
1059 skcms_Signature_XYZ, // pcs
1060 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001061
1062 // We choose to represent sRGB with its canonical transfer function,
1063 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001064 true, // has_trc, followed by the 3 trc curves
1065 {
1066 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1067 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1068 {{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 +00001069 },
1070
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001071 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1072 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001073 { 0.436065674f, 0.385147095f, 0.143066406f },
1074 { 0.222488403f, 0.716873169f, 0.060607910f },
1075 { 0.013916016f, 0.097076416f, 0.714096069f },
1076 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001077
1078 false, // has_A2B, followed by a2b itself which we don't care about.
1079 {
1080 0,
1081 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001082 {{0, {0,0, 0,0,0,0,0}}},
1083 {{0, {0,0, 0,0,0,0,0}}},
1084 {{0, {0,0, 0,0,0,0,0}}},
1085 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001086 },
1087 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001088 nullptr,
1089 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001090
1091 0,
1092 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001093 {{0, {0,0, 0,0,0,0,0}}},
1094 {{0, {0,0, 0,0,0,0,0}}},
1095 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001096 },
1097 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001098 { 0,0,0,0 },
1099 { 0,0,0,0 },
1100 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001101 }},
1102
1103 0,
1104 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001105 {{0, {0,0, 0,0,0,0,0}}},
1106 {{0, {0,0, 0,0,0,0,0}}},
1107 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001108 },
1109 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001110 };
1111 return &sRGB_profile;
1112}
1113
1114const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001115 // 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 +00001116 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001117 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001118
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001119 0, // size, moot here
1120 skcms_Signature_RGB, // data_color_space
1121 skcms_Signature_XYZ, // pcs
1122 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001123
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001124 true, // has_trc, followed by the 3 trc curves
1125 {
1126 {{0, {1,1, 0,0,0,0,0}}},
1127 {{0, {1,1, 0,0,0,0,0}}},
1128 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001129 },
1130
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001131 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1132 {{
1133 { 1,0,0 },
1134 { 0,1,0 },
1135 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001136 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001137
1138 false, // has_A2B, followed by a2b itself which we don't care about.
1139 {
1140 0,
1141 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001142 {{0, {0,0, 0,0,0,0,0}}},
1143 {{0, {0,0, 0,0,0,0,0}}},
1144 {{0, {0,0, 0,0,0,0,0}}},
1145 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001146 },
1147 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001148 nullptr,
1149 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001150
1151 0,
1152 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001153 {{0, {0,0, 0,0,0,0,0}}},
1154 {{0, {0,0, 0,0,0,0,0}}},
1155 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001156 },
1157 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001158 { 0,0,0,0 },
1159 { 0,0,0,0 },
1160 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001161 }},
1162
1163 0,
1164 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001165 {{0, {0,0, 0,0,0,0,0}}},
1166 {{0, {0,0, 0,0,0,0,0}}},
1167 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001168 },
1169 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001170 };
1171
1172 return &XYZD50_profile;
1173}
1174
1175const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1176 return &skcms_sRGB_profile()->trc[0].parametric;
1177}
1178
1179const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1180 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001181 {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 +00001182 return &sRGB_inv;
1183}
1184
1185const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1186 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1187 return &identity;
1188}
1189
1190const uint8_t skcms_252_random_bytes[] = {
1191 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1192 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1193 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1194 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1195 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1196 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1197 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1198 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1199 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1200 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1201 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1202 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1203 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1204 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1205 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1206 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1207};
1208
1209bool 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 +00001210 // Test for exactly equal profiles first.
1211 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1212 return true;
1213 }
1214
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001215 // For now this is the essentially the same strategy we use in test_only.c
1216 // for our skcms_Transform() smoke tests:
1217 // 1) transform A to XYZD50
1218 // 2) transform B to XYZD50
1219 // 3) return true if they're similar enough
1220 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1221
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001222 // 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 +00001223 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1224
1225 if (A->data_color_space != B->data_color_space) {
1226 return false;
1227 }
1228
1229 // 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 +00001230 // 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 +00001231 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1232 size_t npixels = 84;
1233 if (A->data_color_space == skcms_Signature_CMYK) {
1234 fmt = skcms_PixelFormat_RGBA_8888;
1235 npixels = 63;
1236 }
1237
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001238 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1239 // 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 +00001240 uint8_t dstA[252],
1241 dstB[252];
1242 if (!skcms_Transform(
1243 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1244 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1245 npixels)) {
1246 return false;
1247 }
1248 if (!skcms_Transform(
1249 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1250 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1251 npixels)) {
1252 return false;
1253 }
1254
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001255 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001256 for (size_t i = 0; i < 252; i++) {
1257 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1258 return false;
1259 }
1260 }
1261 return true;
1262}
1263
1264bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1265 const skcms_TransferFunction* inv_tf) {
1266 if (!profile || !profile->has_trc) {
1267 return false;
1268 }
1269
1270 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1271 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1272 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1273}
1274
1275static bool is_zero_to_one(float x) {
1276 return 0 <= x && x <= 1;
1277}
1278
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001279typedef struct { float vals[3]; } skcms_Vector3;
1280
1281static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1282 skcms_Vector3 dst = {{0,0,0}};
1283 for (int row = 0; row < 3; ++row) {
1284 dst.vals[row] = m->vals[row][0] * v->vals[0]
1285 + m->vals[row][1] * v->vals[1]
1286 + m->vals[row][2] * v->vals[2];
1287 }
1288 return dst;
1289}
1290
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001291bool skcms_PrimariesToXYZD50(float rx, float ry,
1292 float gx, float gy,
1293 float bx, float by,
1294 float wx, float wy,
1295 skcms_Matrix3x3* toXYZD50) {
1296 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1297 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1298 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1299 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1300 !toXYZD50) {
1301 return false;
1302 }
1303
1304 // First, we need to convert xy values (primaries) to XYZ.
1305 skcms_Matrix3x3 primaries = {{
1306 { rx, gx, bx },
1307 { ry, gy, by },
1308 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1309 }};
1310 skcms_Matrix3x3 primaries_inv;
1311 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1312 return false;
1313 }
1314
1315 // Assumes that Y is 1.0f.
1316 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 +00001317 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001318
1319 skcms_Matrix3x3 toXYZ = {{
1320 { XYZ.vals[0], 0, 0 },
1321 { 0, XYZ.vals[1], 0 },
1322 { 0, 0, XYZ.vals[2] },
1323 }};
1324 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1325
1326 // Now convert toXYZ matrix to toXYZD50.
1327 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1328
1329 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1330 // the matrices below. The Bradford method is used by Adobe and is widely considered
1331 // to be the best.
1332 skcms_Matrix3x3 xyz_to_lms = {{
1333 { 0.8951f, 0.2664f, -0.1614f },
1334 { -0.7502f, 1.7135f, 0.0367f },
1335 { 0.0389f, -0.0685f, 1.0296f },
1336 }};
1337 skcms_Matrix3x3 lms_to_xyz = {{
1338 { 0.9869929f, -0.1470543f, 0.1599627f },
1339 { 0.4323053f, 0.5183603f, 0.0492912f },
1340 { -0.0085287f, 0.0400428f, 0.9684867f },
1341 }};
1342
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001343 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1344 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001345
1346 skcms_Matrix3x3 DXtoD50 = {{
1347 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1348 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1349 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1350 }};
1351 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1352 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1353
1354 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1355 return true;
1356}
1357
1358
1359bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1360 double a00 = src->vals[0][0],
1361 a01 = src->vals[1][0],
1362 a02 = src->vals[2][0],
1363 a10 = src->vals[0][1],
1364 a11 = src->vals[1][1],
1365 a12 = src->vals[2][1],
1366 a20 = src->vals[0][2],
1367 a21 = src->vals[1][2],
1368 a22 = src->vals[2][2];
1369
1370 double b0 = a00*a11 - a01*a10,
1371 b1 = a00*a12 - a02*a10,
1372 b2 = a01*a12 - a02*a11,
1373 b3 = a20,
1374 b4 = a21,
1375 b5 = a22;
1376
1377 double determinant = b0*b5
1378 - b1*b4
1379 + b2*b3;
1380
1381 if (determinant == 0) {
1382 return false;
1383 }
1384
1385 double invdet = 1.0 / determinant;
1386 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1387 return false;
1388 }
1389
1390 b0 *= invdet;
1391 b1 *= invdet;
1392 b2 *= invdet;
1393 b3 *= invdet;
1394 b4 *= invdet;
1395 b5 *= invdet;
1396
1397 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1398 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1399 dst->vals[2][0] = (float)( + b2 );
1400 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1401 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1402 dst->vals[2][1] = (float)( - b1 );
1403 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1404 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1405 dst->vals[2][2] = (float)( + b0 );
1406
1407 for (int r = 0; r < 3; ++r)
1408 for (int c = 0; c < 3; ++c) {
1409 if (!isfinitef_(dst->vals[r][c])) {
1410 return false;
1411 }
1412 }
1413 return true;
1414}
1415
1416skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1417 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1418 for (int r = 0; r < 3; r++)
1419 for (int c = 0; c < 3; c++) {
1420 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1421 + A->vals[r][1] * B->vals[1][c]
1422 + A->vals[r][2] * B->vals[2][c];
1423 }
1424 return m;
1425}
1426
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001427#if defined(__clang__) || defined(__GNUC__)
1428 #define small_memcpy __builtin_memcpy
1429#else
1430 #define small_memcpy memcpy
1431#endif
1432
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001433static float log2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001434 // The first approximation of log2(x) is its exponent 'e', minus 127.
1435 int32_t bits;
1436 small_memcpy(&bits, &x, sizeof(bits));
1437
1438 float e = (float)bits * (1.0f / (1<<23));
1439
1440 // If we use the mantissa too we can refine the error signficantly.
1441 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
1442 float m;
1443 small_memcpy(&m, &m_bits, sizeof(m));
1444
1445 return (e - 124.225514990f
1446 - 1.498030302f*m
1447 - 1.725879990f/(0.3520887068f + m));
1448}
1449
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001450static float exp2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001451 float fract = x - floorf_(x);
1452
1453 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
1454 - 1.490129070f*fract
1455 + 27.728023300f/(4.84252568f - fract));
skia-autoroll03fccf42019-08-26 19:38:13 +00001456
1457 // Before we cast fbits to int32_t, check for out of range values to pacify UBSAN.
1458 // INT_MAX is not exactly representable as a float, so exclude it as effectively infinite.
1459 // INT_MIN is a power of 2 and exactly representable as a float, so it's fine.
1460 if (fbits >= (float)INT_MAX) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001461 return INFINITY_;
skia-autoroll03fccf42019-08-26 19:38:13 +00001462 } else if (fbits < (float)INT_MIN) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001463 return -INFINITY_;
1464 }
skia-autoroll03fccf42019-08-26 19:38:13 +00001465
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001466 int32_t bits = (int32_t)fbits;
1467 small_memcpy(&x, &bits, sizeof(x));
1468 return x;
1469}
1470
1471float powf_(float x, float y) {
skia-autorollfe16a332019-08-20 19:44:54 +00001472 assert (x >= 0);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001473 return (x == 0) || (x == 1) ? x
1474 : exp2f_(log2f_(x) * y);
1475}
1476
skia-autorollacd6e012019-01-08 14:10:52 +00001477#if defined(__clang__)
1478 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by tf_is_valid() on the way out.
1479#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001480bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001481 if (!tf_is_valid(src)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001482 return false;
1483 }
1484
skia-autorolld0b577f2019-01-07 19:46:57 +00001485 // We're inverting this function, solving for x in terms of y.
1486 // y = (cx + f) x < d
1487 // (ax + b)^g + e x ≥ d
1488 // The inverse of this function can be expressed in the same piecewise form.
1489 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 +00001490
skia-autorolld0b577f2019-01-07 19:46:57 +00001491 // We'll start by finding the new threshold inv.d.
1492 // In principle we should be able to find that by solving for y at x=d from either side.
1493 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1494 float d_l = src->c * src->d + src->f,
1495 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1496 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001497 return false;
1498 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001499 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 +00001500
skia-autorolld0b577f2019-01-07 19:46:57 +00001501 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1502 if (inv.d > 0) {
1503 // Inverting the linear section is pretty straightfoward:
1504 // y = cx + f
1505 // y - f = cx
1506 // (1/c)y - f/c = x
1507 inv.c = 1.0f/src->c;
1508 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001509 }
1510
skia-autorolld0b577f2019-01-07 19:46:57 +00001511 // The interesting part is inverting the nonlinear section:
1512 // y = (ax + b)^g + e.
1513 // y - e = (ax + b)^g
1514 // (y - e)^1/g = ax + b
1515 // (y - e)^1/g - b = ax
1516 // (1/a)(y - e)^1/g - b/a = x
1517 //
1518 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1519 // let k = (1/a)^g
1520 // (1/a)( y - e)^1/g - b/a = x
1521 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001522
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001523 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001524 inv.g = 1.0f / src->g;
1525 inv.a = k;
1526 inv.b = -k * src->e;
1527 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001528
skia-autorollfe16a332019-08-20 19:44:54 +00001529 // We need to enforce the same constraints here that we do when fitting a curve,
1530 // a >= 0 and ad+b >= 0. These constraints are checked by tf_is_valid(), so they're true
skia-autorollbf388232019-08-21 14:17:54 +00001531 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001532
skia-autorollbf388232019-08-21 14:17:54 +00001533 // Just like when fitting the curve, there's really no way to rescue a < 0.
1534 if (inv.a < 0) {
1535 return false;
1536 }
1537 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001538 if (inv.a * inv.d + inv.b < 0) {
1539 inv.b = -inv.a * inv.d;
1540 }
skia-autorollbf388232019-08-21 14:17:54 +00001541
skia-autorolla0ed0702019-08-23 16:49:54 +00001542 // That should usually make tf_is_valid(&inv) true, but there are a couple situations
1543 // where we might still fail here, like non-finite parameter values.
1544 if (!tf_is_valid(&inv)) {
1545 return false;
1546 }
1547
skia-autorollbf388232019-08-21 14:17:54 +00001548 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001549 assert (inv.a * inv.d + inv.b >= 0);
1550
skia-autorolla7b28742019-01-09 18:35:46 +00001551 // Now in principle we're done.
skia-autorollad10df62019-08-21 15:14:54 +00001552 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
1553 // e or f of the inverse, depending on which segment contains src(1.0f).
1554 float s = skcms_TransferFunction_eval(src, 1.0f);
skia-autorolla0ed0702019-08-23 16:49:54 +00001555 if (!isfinitef_(s)) {
1556 return false;
1557 }
1558
skia-autorollad10df62019-08-21 15:14:54 +00001559 float sign = s < 0 ? -1.0f : 1.0f;
1560 s *= sign;
1561 if (s < inv.d) {
1562 inv.f = 1.0f - sign * inv.c * s;
1563 } else {
1564 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
1565 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001566
skia-autorolld0b577f2019-01-07 19:46:57 +00001567 *dst = inv;
1568 return tf_is_valid(dst);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001569}
1570
1571// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1572
1573// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1574//
1575// tf(x) = cx + f x < d
1576// tf(x) = (ax + b)^g + e x ≥ d
1577//
1578// When fitting, we add the additional constraint that both pieces meet at d:
1579//
1580// cd + f = (ad + b)^g + e
1581//
1582// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1583//
1584// tf(x) = cx + f x < d
1585// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1586//
1587// Our overall strategy is then:
1588// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001589// - 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 +00001590// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001591// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1592// (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 +00001593// Return the parameters with least maximum error.
1594//
1595// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1596// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1597//
1598// let y = Table(x)
1599// r(x) = x - f_inv(y)
1600//
1601// ∂r/∂g = ln(ay + b)*(ay + b)^g
1602// - ln(ad + b)*(ad + b)^g
1603// ∂r/∂a = yg(ay + b)^(g-1)
1604// - dg(ad + b)^(g-1)
1605// ∂r/∂b = g(ay + b)^(g-1)
1606// - g(ad + b)^(g-1)
1607
1608// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1609// and fill out the gradient of the residual into dfdP.
1610static float rg_nonlinear(float x,
1611 const skcms_Curve* curve,
1612 const skcms_TransferFunction* tf,
1613 const float P[3],
1614 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001615 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001616
1617 const float g = P[0], a = P[1], b = P[2],
1618 c = tf->c, d = tf->d, f = tf->f;
1619
1620 const float Y = fmaxf_(a*y + b, 0.0f),
1621 D = a*d + b;
1622 assert (D >= 0);
1623
1624 // The gradient.
1625 dfdP[0] = 0.69314718f*log2f_(Y)*powf_(Y, g)
1626 - 0.69314718f*log2f_(D)*powf_(D, g);
1627 dfdP[1] = y*g*powf_(Y, g-1)
1628 - d*g*powf_(D, g-1);
1629 dfdP[2] = g*powf_(Y, g-1)
1630 - g*powf_(D, g-1);
1631
1632 // The residual.
1633 const float f_inv = powf_(Y, g)
1634 - powf_(D, g)
1635 + c*d + f;
1636 return x - f_inv;
1637}
1638
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001639static bool gauss_newton_step(const skcms_Curve* curve,
1640 const skcms_TransferFunction* tf,
1641 float P[3],
1642 float x0, float dx, int N) {
1643 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1644 //
1645 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1646 // where r(P) is the residual vector
1647 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1648 //
1649 // Let's review the shape of each of these expressions:
1650 // r(P) is [N x 1], a column vector with one entry per value of x tested
1651 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1652 // Jf^T is [3 x N], the transpose of Jf
1653 //
1654 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1655 // and so is its inverse (Jf^T Jf)^-1
1656 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1657 //
1658 // Our implementation strategy to get to the final ∆P is
1659 // 1) evaluate Jf^T Jf, call that lhs
1660 // 2) evaluate Jf^T r(P), call that rhs
1661 // 3) invert lhs
1662 // 4) multiply inverse lhs by rhs
1663 //
1664 // This is a friendly implementation strategy because we don't have to have any
1665 // buffers that scale with N, and equally nice don't have to perform any matrix
1666 // operations that are variable size.
1667 //
1668 // Other implementation strategies could trade this off, e.g. evaluating the
1669 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1670 // the residuals. That would probably require implementing singular value
1671 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1672 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1673 // possibility of this gauss_newton_step() function ever failing.
1674
1675 // 0) start off with lhs and rhs safely zeroed.
1676 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1677 skcms_Vector3 rhs = { {0,0,0} };
1678
1679 // 1,2) evaluate lhs and evaluate rhs
1680 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1681 // so we'll have to update lhs and rhs at the same time.
1682 for (int i = 0; i < N; i++) {
1683 float x = x0 + i*dx;
1684
1685 float dfdP[3] = {0,0,0};
1686 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1687
1688 for (int r = 0; r < 3; r++) {
1689 for (int c = 0; c < 3; c++) {
1690 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1691 }
1692 rhs.vals[r] += dfdP[r] * resid;
1693 }
1694 }
1695
1696 // If any of the 3 P parameters are unused, this matrix will be singular.
1697 // Detect those cases and fix them up to indentity instead, so we can invert.
1698 for (int k = 0; k < 3; k++) {
1699 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1700 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1701 lhs.vals[k][k] = 1;
1702 }
1703 }
1704
1705 // 3) invert lhs
1706 skcms_Matrix3x3 lhs_inv;
1707 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1708 return false;
1709 }
1710
1711 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001712 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001713 P[0] += dP.vals[0];
1714 P[1] += dP.vals[1];
1715 P[2] += dP.vals[2];
1716 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1717}
1718
1719
1720// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1721static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1722 float P[3] = { tf->g, tf->a, tf->b };
1723
1724 // No matter where we start, dx should always represent N even steps from 0 to 1.
1725 const float dx = 1.0f / (N-1);
1726
skia-autorolld9718822019-08-23 18:16:54 +00001727 // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
1728 for (int j = 0; j < 2; j++) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001729 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1730 // We don't really know how to fix up a if it goes negative.
1731 if (P[1] < 0) {
1732 return false;
1733 }
1734 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1735 if (P[1] * tf->d + P[2] < 0) {
1736 P[2] = -P[1] * tf->d;
1737 }
1738 assert (P[1] >= 0 &&
1739 P[1] * tf->d + P[2] >= 0);
1740
1741 if (!gauss_newton_step(curve, tf,
1742 P,
1743 L*dx, dx, N-L)) {
1744 return false;
1745 }
1746 }
1747
1748 // We need to apply our fixups one last time
1749 if (P[1] < 0) {
1750 return false;
1751 }
1752 if (P[1] * tf->d + P[2] < 0) {
1753 P[2] = -P[1] * tf->d;
1754 }
1755
skia-autorollfe16a332019-08-20 19:44:54 +00001756 assert (P[1] >= 0 &&
1757 P[1] * tf->d + P[2] >= 0);
1758
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001759 tf->g = P[0];
1760 tf->a = P[1];
1761 tf->b = P[2];
1762 tf->e = tf->c*tf->d + tf->f
1763 - powf_(tf->a*tf->d + tf->b, tf->g);
1764 return true;
1765}
1766
1767bool skcms_ApproximateCurve(const skcms_Curve* curve,
1768 skcms_TransferFunction* approx,
1769 float* max_error) {
1770 if (!curve || !approx || !max_error) {
1771 return false;
1772 }
1773
1774 if (curve->table_entries == 0) {
1775 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1776 return false;
1777 }
1778
1779 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1780 // We need at least two points, and must put some reasonable cap on the maximum number.
1781 return false;
1782 }
1783
1784 int N = (int)curve->table_entries;
1785 const float dx = 1.0f / (N - 1);
1786
1787 *max_error = INFINITY_;
1788 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1789 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1790 skcms_TransferFunction tf,
1791 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001792
1793 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1794 tf.f = 0.0f;
1795 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 +00001796
1797 if (L == N) {
1798 // If the entire data set was linear, move the coefficients to the nonlinear portion
1799 // with G == 1. This lets use a canonical representation with d == 0.
1800 tf.g = 1;
1801 tf.a = tf.c;
1802 tf.b = tf.f;
1803 tf.c = tf.d = tf.e = tf.f = 0;
1804 } else if (L == N - 1) {
1805 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1806 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001807 tf.a = (eval_curve(curve, (N-1)*dx) -
1808 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001809 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001810 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001811 - tf.a * (N-2)*dx;
1812 tf.e = 0;
1813 } else {
1814 // Start by guessing a gamma-only curve through the midpoint.
1815 int mid = (L + N) / 2;
1816 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001817 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001818 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001819 tf.a = 1;
1820 tf.b = 0;
1821 tf.e = tf.c*tf.d + tf.f
1822 - powf_(tf.a*tf.d + tf.b, tf.g);
1823
1824
1825 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1826 !fit_nonlinear(curve, L,N, &tf_inv)) {
1827 continue;
1828 }
1829
1830 // We fit tf_inv, so calculate tf to keep in sync.
1831 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1832 continue;
1833 }
1834 }
1835
1836 // We find our error by roundtripping the table through tf_inv.
1837 //
1838 // (The most likely use case for this approximation is to be inverted and
1839 // used as the transfer function for a destination color space.)
1840 //
1841 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1842 // invertible, so re-verify that here (and use the new inverse for testing).
1843 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1844 continue;
1845 }
1846
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001847 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001848 if (*max_error > err) {
1849 *max_error = err;
1850 *approx = tf;
1851 }
1852 }
1853 return isfinitef_(*max_error);
1854}
1855
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001856// ~~~~ Impl. of skcms_Transform() ~~~~
1857
1858typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001859 Op_load_a8,
1860 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001861 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001862 Op_load_4444,
1863 Op_load_565,
1864 Op_load_888,
1865 Op_load_8888,
1866 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001867 Op_load_161616LE,
1868 Op_load_16161616LE,
1869 Op_load_161616BE,
1870 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001871 Op_load_hhh,
1872 Op_load_hhhh,
1873 Op_load_fff,
1874 Op_load_ffff,
1875
1876 Op_swap_rb,
1877 Op_clamp,
1878 Op_invert,
1879 Op_force_opaque,
1880 Op_premul,
1881 Op_unpremul,
1882 Op_matrix_3x3,
1883 Op_matrix_3x4,
1884 Op_lab_to_xyz,
1885
1886 Op_tf_r,
1887 Op_tf_g,
1888 Op_tf_b,
1889 Op_tf_a,
1890
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001891 Op_table_r,
1892 Op_table_g,
1893 Op_table_b,
1894 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001895
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001896 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001897
1898 Op_store_a8,
1899 Op_store_g8,
1900 Op_store_4444,
1901 Op_store_565,
1902 Op_store_888,
1903 Op_store_8888,
1904 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001905 Op_store_161616LE,
1906 Op_store_16161616LE,
1907 Op_store_161616BE,
1908 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001909 Op_store_hhh,
1910 Op_store_hhhh,
1911 Op_store_fff,
1912 Op_store_ffff,
1913} Op;
1914
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001915#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001916 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 +00001917#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001918 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1919 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1920 template <int N, typename T>
1921 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 +00001922
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001923 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 +00001924#endif
1925
1926// First, instantiate our default exec_ops() implementation using the default compiliation target.
1927
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001928namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00001929#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
1930 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001931 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00001932 template <typename T> using V = T;
1933 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001934#elif defined(__AVX512F__)
1935 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001936 template <typename T> using V = Vec<N,T>;
1937 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001938#elif defined(__AVX__)
1939 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001940 template <typename T> using V = Vec<N,T>;
1941 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00001942#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
1943 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001944 template <typename T> using V = Vec<N,T>;
1945 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001946#else
1947 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00001948 template <typename T> using V = Vec<N,T>;
1949 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001950#endif
1951
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001952 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001953 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001954}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001955
1956// 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 +00001957#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00001958 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00001959 (( defined(__clang__) && __clang_major__ >= 5) || \
1960 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00001961 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001962
skia-autorollba6087c2019-04-09 13:57:02 +00001963 #if !defined(__AVX2__)
1964 #if defined(__clang__)
1965 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
1966 #elif defined(__GNUC__)
1967 #pragma GCC push_options
1968 #pragma GCC target("avx2,f16c")
1969 #endif
1970
1971 namespace hsw {
1972 #define USING_AVX
1973 #define USING_AVX_F16C
1974 #define USING_AVX2
1975 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001976 template <typename T> using V = Vec<N,T>;
1977 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00001978
1979 #include "src/Transform_inl.h"
1980
1981 // src/Transform_inl.h will undefine USING_* for us.
1982 #undef N
1983 }
1984
1985 #if defined(__clang__)
1986 #pragma clang attribute pop
1987 #elif defined(__GNUC__)
1988 #pragma GCC pop_options
1989 #endif
1990
1991 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001992 #endif
1993
skia-autorollba6087c2019-04-09 13:57:02 +00001994 #if !defined(__AVX512F__)
1995 #if defined(__clang__)
1996 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
1997 #elif defined(__GNUC__)
1998 #pragma GCC push_options
1999 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
2000 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002001
skia-autorollba6087c2019-04-09 13:57:02 +00002002 namespace skx {
2003 #define USING_AVX512F
2004 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002005 template <typename T> using V = Vec<N,T>;
2006 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002007
skia-autorollba6087c2019-04-09 13:57:02 +00002008 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002009
skia-autorollba6087c2019-04-09 13:57:02 +00002010 // src/Transform_inl.h will undefine USING_* for us.
2011 #undef N
2012 }
2013
2014 #if defined(__clang__)
2015 #pragma clang attribute pop
2016 #elif defined(__GNUC__)
2017 #pragma GCC pop_options
2018 #endif
2019
2020 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002021 #endif
2022
skia-autorollba6087c2019-04-09 13:57:02 +00002023 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
2024 enum class CpuType { None, HSW, SKX };
2025 static CpuType cpu_type() {
2026 static const CpuType type = []{
2027 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002028
skia-autorollba6087c2019-04-09 13:57:02 +00002029 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2030 uint32_t eax, ebx, ecx, edx;
2031 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2032 : "0"(1), "2"(0));
2033 if ((edx & (1u<<25)) && // SSE
2034 (edx & (1u<<26)) && // SSE2
2035 (ecx & (1u<< 0)) && // SSE3
2036 (ecx & (1u<< 9)) && // SSSE3
2037 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
2038 (ecx & (1u<<19)) && // SSE4.1
2039 (ecx & (1u<<20)) && // SSE4.2
2040 (ecx & (1u<<26)) && // XSAVE
2041 (ecx & (1u<<27)) && // OSXSAVE
2042 (ecx & (1u<<28)) && // AVX
2043 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002044
skia-autorollba6087c2019-04-09 13:57:02 +00002045 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2046 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2047 : "0"(7), "2"(0));
2048 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2049 uint32_t xcr0, dont_need_edx;
2050 __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 +00002051
skia-autorollba6087c2019-04-09 13:57:02 +00002052 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2053 (xcr0 & (1u<<2)) && // YMM register state saved?
2054 (ebx & (1u<<5))) { // AVX2
2055 // At this point we're at least HSW. Continue checking for SKX.
2056 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2057 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2058 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2059 (ebx & (1u<<16)) && // AVX512F
2060 (ebx & (1u<<17)) && // AVX512DQ
2061 (ebx & (1u<<28)) && // AVX512CD
2062 (ebx & (1u<<30)) && // AVX512BW
2063 (ebx & (1u<<31))) { // AVX512VL
2064 return CpuType::SKX;
2065 }
2066 return CpuType::HSW;
2067 }
2068 }
2069 return CpuType::None;
2070 }();
2071 return type;
2072 }
2073 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002074
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002075#endif
2076
2077static bool is_identity_tf(const skcms_TransferFunction* tf) {
2078 return tf->g == 1 && tf->a == 1
2079 && tf->b == 0 && tf->c == 0 && tf->d == 0 && tf->e == 0 && tf->f == 0;
2080}
2081
2082typedef struct {
2083 Op op;
2084 const void* arg;
2085} OpAndArg;
2086
2087static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002088 static const struct { Op parametric, table; } ops[] = {
2089 { Op_tf_r, Op_table_r },
2090 { Op_tf_g, Op_table_g },
2091 { Op_tf_b, Op_table_b },
2092 { Op_tf_a, Op_table_a },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002093 };
2094
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002095 const OpAndArg noop = { Op_load_a8/*doesn't matter*/, nullptr };
2096
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002097 if (curve->table_entries == 0) {
2098 return is_identity_tf(&curve->parametric)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002099 ? noop
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002100 : OpAndArg{ ops[channel].parametric, &curve->parametric };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002101 }
2102
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002103 return OpAndArg{ ops[channel].table, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002104}
2105
2106static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2107 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002108 case skcms_PixelFormat_A_8 >> 1: return 1;
2109 case skcms_PixelFormat_G_8 >> 1: return 1;
2110 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2111 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2112 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2113 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2114 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2115 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2116 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2117 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2118 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2119 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002120 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2121 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002122 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2123 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2124 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2125 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002126 }
2127 assert(false);
2128 return 0;
2129}
2130
2131static bool prep_for_destination(const skcms_ICCProfile* profile,
2132 skcms_Matrix3x3* fromXYZD50,
2133 skcms_TransferFunction* invR,
2134 skcms_TransferFunction* invG,
2135 skcms_TransferFunction* invB) {
2136 // We only support destinations with parametric transfer functions
2137 // and with gamuts that can be transformed from XYZD50.
2138 return profile->has_trc
2139 && profile->has_toXYZD50
2140 && profile->trc[0].table_entries == 0
2141 && profile->trc[1].table_entries == 0
2142 && profile->trc[2].table_entries == 0
2143 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2144 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2145 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2146 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2147}
2148
2149bool skcms_Transform(const void* src,
2150 skcms_PixelFormat srcFmt,
2151 skcms_AlphaFormat srcAlpha,
2152 const skcms_ICCProfile* srcProfile,
2153 void* dst,
2154 skcms_PixelFormat dstFmt,
2155 skcms_AlphaFormat dstAlpha,
2156 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002157 size_t npixels) {
2158 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2159 dst, dstFmt, dstAlpha, dstProfile,
2160 npixels, nullptr);
2161}
2162
2163bool skcms_TransformWithPalette(const void* src,
2164 skcms_PixelFormat srcFmt,
2165 skcms_AlphaFormat srcAlpha,
2166 const skcms_ICCProfile* srcProfile,
2167 void* dst,
2168 skcms_PixelFormat dstFmt,
2169 skcms_AlphaFormat dstAlpha,
2170 const skcms_ICCProfile* dstProfile,
2171 size_t nz,
2172 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002173 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2174 src_bpp = bytes_per_pixel(srcFmt);
2175 // Let's just refuse if the request is absurdly big.
2176 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2177 return false;
2178 }
2179 int n = (int)nz;
2180
2181 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2182 if (!srcProfile) {
2183 srcProfile = skcms_sRGB_profile();
2184 }
2185 if (!dstProfile) {
2186 dstProfile = skcms_sRGB_profile();
2187 }
2188
2189 // 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 +00002190 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002191 return false;
2192 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002193 // TODO: more careful alias rejection (like, dst == src + 1)?
2194
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002195 if (needs_palette(srcFmt) && !palette) {
2196 return false;
2197 }
2198
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002199 Op program [32];
2200 const void* arguments[32];
2201
2202 Op* ops = program;
2203 const void** args = arguments;
2204
2205 skcms_TransferFunction inv_dst_tf_r, inv_dst_tf_g, inv_dst_tf_b;
2206 skcms_Matrix3x3 from_xyz;
2207
2208 switch (srcFmt >> 1) {
2209 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002210 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2211 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2212 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2213 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2214 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2215 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2216 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2217 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2218 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2219 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2220 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002221 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2222 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 +00002223 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2224 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2225 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2226 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002227
2228 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2229 *args++ = palette;
2230 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002231 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002232 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2233 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2234 *ops++ = Op_clamp;
2235 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002236 if (srcFmt & 1) {
2237 *ops++ = Op_swap_rb;
2238 }
2239 skcms_ICCProfile gray_dst_profile;
2240 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2241 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2242 // luminance (Y) by the destination transfer function.
2243 gray_dst_profile = *dstProfile;
2244 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2245 dstProfile = &gray_dst_profile;
2246 }
2247
2248 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2249 // Photoshop creates CMYK images as inverse CMYK.
2250 // These happen to be the only ones we've _ever_ seen.
2251 *ops++ = Op_invert;
2252 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2253 srcAlpha = skcms_AlphaFormat_Unpremul;
2254 }
2255
2256 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2257 *ops++ = Op_force_opaque;
2258 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2259 *ops++ = Op_unpremul;
2260 }
2261
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002262 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002263
2264 if (!prep_for_destination(dstProfile,
2265 &from_xyz, &inv_dst_tf_r, &inv_dst_tf_b, &inv_dst_tf_g)) {
2266 return false;
2267 }
2268
2269 if (srcProfile->has_A2B) {
2270 if (srcProfile->A2B.input_channels) {
2271 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2272 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 +00002273 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002274 *ops++ = oa.op;
2275 *args++ = oa.arg;
2276 }
2277 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002278 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002279 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002280 *args++ = &srcProfile->A2B;
2281 }
2282
2283 if (srcProfile->A2B.matrix_channels == 3) {
2284 for (int i = 0; i < 3; i++) {
2285 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 +00002286 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002287 *ops++ = oa.op;
2288 *args++ = oa.arg;
2289 }
2290 }
2291
2292 static const skcms_Matrix3x4 I = {{
2293 {1,0,0,0},
2294 {0,1,0,0},
2295 {0,0,1,0},
2296 }};
2297 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2298 *ops++ = Op_matrix_3x4;
2299 *args++ = &srcProfile->A2B.matrix;
2300 }
2301 }
2302
2303 if (srcProfile->A2B.output_channels == 3) {
2304 for (int i = 0; i < 3; i++) {
2305 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 +00002306 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002307 *ops++ = oa.op;
2308 *args++ = oa.arg;
2309 }
2310 }
2311 }
2312
2313 if (srcProfile->pcs == skcms_Signature_Lab) {
2314 *ops++ = Op_lab_to_xyz;
2315 }
2316
2317 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2318 for (int i = 0; i < 3; i++) {
2319 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002320 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002321 *ops++ = oa.op;
2322 *args++ = oa.arg;
2323 }
2324 }
2325 } else {
2326 return false;
2327 }
2328
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002329 // A2B sources should already be in XYZD50 at this point.
2330 // Others still need to be transformed using their toXYZD50 matrix.
2331 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2332 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2333 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2334 static const skcms_Matrix3x3 I = {{
2335 { 1.0f, 0.0f, 0.0f },
2336 { 0.0f, 1.0f, 0.0f },
2337 { 0.0f, 0.0f, 1.0f },
2338 }};
2339 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2340
2341 // There's a chance the source and destination gamuts are identical,
2342 // in which case we can skip the gamut transform.
2343 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2344 // Concat the entire gamut transform into from_xyz,
2345 // now slightly misnamed but it's a handy spot to stash the result.
2346 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2347 *ops++ = Op_matrix_3x3;
2348 *args++ = &from_xyz;
2349 }
2350
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002351 // Encode back to dst RGB using its parametric transfer functions.
2352 if (!is_identity_tf(&inv_dst_tf_r)) { *ops++ = Op_tf_r; *args++ = &inv_dst_tf_r; }
2353 if (!is_identity_tf(&inv_dst_tf_g)) { *ops++ = Op_tf_g; *args++ = &inv_dst_tf_g; }
2354 if (!is_identity_tf(&inv_dst_tf_b)) { *ops++ = Op_tf_b; *args++ = &inv_dst_tf_b; }
2355 }
2356
skia-autoroll2e4fa242019-03-11 21:14:18 +00002357 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2358 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002359 //
2360 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2361 // 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 +00002362 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2363 *ops++ = Op_clamp;
2364 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002365 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2366 *ops++ = Op_force_opaque;
2367 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2368 *ops++ = Op_premul;
2369 }
2370 if (dstFmt & 1) {
2371 *ops++ = Op_swap_rb;
2372 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002373 switch (dstFmt >> 1) {
2374 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002375 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2376 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2377 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2378 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2379 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2380 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2381 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2382 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2383 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2384 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2385 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002386 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2387 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 +00002388 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2389 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2390 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2391 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 +00002392 }
2393
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002394 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002395#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002396 switch (cpu_type()) {
2397 case CpuType::None: break;
2398 case CpuType::HSW: run = hsw::run_program; break;
2399 case CpuType::SKX: run = hsw::run_program; break;
2400 }
2401#endif
2402#if defined(TEST_FOR_SKX)
2403 switch (cpu_type()) {
2404 case CpuType::None: break;
2405 case CpuType::HSW: break;
2406 case CpuType::SKX: run = skx::run_program; break;
2407 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002408#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002409 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 +00002410 return true;
2411}
2412
2413static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2414#if defined(NDEBUG)
2415 (void)profile;
2416#else
2417 skcms_Matrix3x3 fromXYZD50;
2418 skcms_TransferFunction invR, invG, invB;
2419 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2420#endif
2421}
2422
2423bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2424 skcms_Matrix3x3 fromXYZD50;
2425 if (!profile->has_trc || !profile->has_toXYZD50
2426 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2427 return false;
2428 }
2429
2430 skcms_TransferFunction tf[3];
2431 for (int i = 0; i < 3; i++) {
2432 skcms_TransferFunction inv;
2433 if (profile->trc[i].table_entries == 0
2434 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2435 tf[i] = profile->trc[i].parametric;
2436 continue;
2437 }
2438
2439 float max_error;
2440 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2441 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2442 return false;
2443 }
2444 }
2445
2446 for (int i = 0; i < 3; ++i) {
2447 profile->trc[i].table_entries = 0;
2448 profile->trc[i].parametric = tf[i];
2449 }
2450
2451 assert_usable_as_destination(profile);
2452 return true;
2453}
2454
2455bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2456 // Operate on a copy of profile, so we can choose the best TF for the original curves
2457 skcms_ICCProfile result = *profile;
2458 if (!skcms_MakeUsableAsDestination(&result)) {
2459 return false;
2460 }
2461
2462 int best_tf = 0;
2463 float min_max_error = INFINITY_;
2464 for (int i = 0; i < 3; i++) {
2465 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002466 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2467 return false;
2468 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002469
2470 float err = 0;
2471 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002472 err = fmaxf_(err, max_roundtrip_error(&profile->trc[j], &inv));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002473 }
2474 if (min_max_error > err) {
2475 min_max_error = err;
2476 best_tf = i;
2477 }
2478 }
2479
2480 for (int i = 0; i < 3; i++) {
2481 result.trc[i].parametric = result.trc[best_tf].parametric;
2482 }
2483
2484 *profile = result;
2485 assert_usable_as_destination(profile);
2486 return true;
2487}