blob: 5d6ae3d882b2bf55e4edb2f768ded25a85b6abc5 [file] [log] [blame]
Mike Kleinded7a552018-04-10 10:05:31 -04001/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00008#include "skcms.h"
9#include "skcms_internal.h"
10#include <assert.h>
11#include <float.h>
12#include <limits.h>
13#include <stdlib.h>
14#include <string.h>
Mike Kleinded7a552018-04-10 10:05:31 -040015
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +000016#if defined(__ARM_NEON)
17 #include <arm_neon.h>
18#elif defined(__SSE__)
19 #include <immintrin.h>
skia-autorollba6087c2019-04-09 13:57:02 +000020
21 #if defined(__clang__)
22 // That #include <immintrin.h> is usually enough, but Clang's headers
23 // "helpfully" skip including the whole kitchen sink when _MSC_VER is
24 // defined, because lots of programs on Windows would include that and
25 // it'd be a lot slower. But we want all those headers included so we
26 // can use their features after runtime checks later.
27 #include <smmintrin.h>
28 #include <avxintrin.h>
29 #include <avx2intrin.h>
30 #include <avx512fintrin.h>
31 #include <avx512dqintrin.h>
32 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +000033#endif
34
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000035// sizeof(x) will return size_t, which is 32-bit on some machines and 64-bit on others.
36// We have better testing on 64-bit machines, so force 32-bit machines to behave like 64-bit.
37//
38// Please do not use sizeof() directly, and size_t only when required.
39// (We have no way of enforcing these requests...)
40#define SAFE_SIZEOF(x) ((uint64_t)sizeof(x))
41
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +000042// Same sort of thing for _Layout structs with a variable sized array at the end (named "variable").
43#define SAFE_FIXED_SIZE(type) ((uint64_t)offsetof(type, variable))
44
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000045static const union {
46 uint32_t bits;
47 float f;
48} inf_ = { 0x7f800000 };
49#define INFINITY_ inf_.f
50
skia-autoroll5f8588c2019-10-14 13:54:14 +000051#if defined(__clang__) || defined(__GNUC__)
52 #define small_memcpy __builtin_memcpy
53#else
54 #define small_memcpy memcpy
55#endif
56
57static float log2f_(float x) {
58 // The first approximation of log2(x) is its exponent 'e', minus 127.
59 int32_t bits;
60 small_memcpy(&bits, &x, sizeof(bits));
61
62 float e = (float)bits * (1.0f / (1<<23));
63
64 // If we use the mantissa too we can refine the error signficantly.
65 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
66 float m;
67 small_memcpy(&m, &m_bits, sizeof(m));
68
69 return (e - 124.225514990f
70 - 1.498030302f*m
71 - 1.725879990f/(0.3520887068f + m));
72}
73static float logf_(float x) {
74 const float ln2 = 0.69314718f;
75 return ln2*log2f_(x);
76}
77
78static float exp2f_(float x) {
79 float fract = x - floorf_(x);
80
81 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
82 - 1.490129070f*fract
83 + 27.728023300f/(4.84252568f - fract));
84
85 // Before we cast fbits to int32_t, check for out of range values to pacify UBSAN.
86 // INT_MAX is not exactly representable as a float, so exclude it as effectively infinite.
skia-autoroll4262f6b2020-05-19 17:48:12 +000087 // Negative values are effectively underflow - we'll end up returning a (different) negative
88 // value, which makes no sense. So clamp to zero.
skia-autoroll5f8588c2019-10-14 13:54:14 +000089 if (fbits >= (float)INT_MAX) {
90 return INFINITY_;
skia-autoroll4262f6b2020-05-19 17:48:12 +000091 } else if (fbits < 0) {
92 return 0;
skia-autoroll5f8588c2019-10-14 13:54:14 +000093 }
94
95 int32_t bits = (int32_t)fbits;
96 small_memcpy(&x, &bits, sizeof(x));
97 return x;
98}
99
100// Not static, as it's used by some test tools.
101float powf_(float x, float y) {
102 assert (x >= 0);
103 return (x == 0) || (x == 1) ? x
104 : exp2f_(log2f_(x) * y);
105}
106
107static float expf_(float x) {
skia-autoroll775981d2019-10-14 14:54:13 +0000108 const float log2_e = 1.4426950408889634074f;
109 return exp2f_(log2_e * x);
skia-autoroll5f8588c2019-10-14 13:54:14 +0000110}
111
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000112static float fmaxf_(float x, float y) { return x > y ? x : y; }
113static float fminf_(float x, float y) { return x < y ? x : y; }
114
115static bool isfinitef_(float x) { return 0 == x*0; }
116
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000117static float minus_1_ulp(float x) {
118 int32_t bits;
119 memcpy(&bits, &x, sizeof(bits));
120 bits = bits - 1;
121 memcpy(&x, &bits, sizeof(bits));
122 return x;
123}
124
skia-autorollc093cc82019-10-08 19:22:13 +0000125// Most transfer functions we work with are sRGBish.
126// For exotic HDR transfer functions, we encode them using a tf.g that makes no sense,
127// and repurpose the other fields to hold the parameters of the HDR functions.
128enum TFKind { Bad, sRGBish, PQish, HLGish, HLGinvish };
129struct TF_PQish { float A,B,C,D,E,F; };
130struct TF_HLGish { float R,G,a,b,c; };
131
132static float TFKind_marker(TFKind kind) {
133 // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM.
134 return -(float)kind;
135}
136
137static TFKind classify(const skcms_TransferFunction& tf, TF_PQish* pq = nullptr
138 , TF_HLGish* hlg = nullptr) {
139 if (tf.g < 0 && (int)tf.g == tf.g) {
140 // TODO: sanity checks for PQ/HLG like we do for sRGBish.
skia-autorolle1227722019-10-28 20:17:07 +0000141 switch ((int)tf.g) {
142 case -PQish: if (pq ) { memcpy(pq , &tf.a, sizeof(*pq )); } return PQish;
143 case -HLGish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGish;
144 case -HLGinvish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGinvish;
skia-autorollc093cc82019-10-08 19:22:13 +0000145 }
146 return Bad;
147 }
148
149 // Basic sanity checks for sRGBish transfer functions.
150 if (isfinitef_(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g)
151 // a,c,d,g should be non-negative to make any sense.
152 && tf.a >= 0
153 && tf.c >= 0
154 && tf.d >= 0
155 && tf.g >= 0
156 // Raising a negative value to a fractional tf->g produces complex numbers.
157 && tf.a * tf.d + tf.b >= 0) {
158 return sRGBish;
159 }
160
161 return Bad;
162}
163
skia-autorollc093cc82019-10-08 19:22:13 +0000164bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
165 float A, float B, float C,
166 float D, float E, float F) {
167 *tf = { TFKind_marker(PQish), A,B,C,D,E,F };
168 assert(classify(*tf) == PQish);
169 return true;
170}
171
skia-autoroll5f8588c2019-10-14 13:54:14 +0000172bool skcms_TransferFunction_makeHLGish(skcms_TransferFunction* tf,
173 float R, float G,
174 float a, float b, float c) {
skia-autorollf6d50642019-10-14 15:31:13 +0000175 *tf = { TFKind_marker(HLGish), R,G, a,b,c, 0 };
skia-autoroll5f8588c2019-10-14 13:54:14 +0000176 assert(classify(*tf) == HLGish);
177 return true;
178}
179
skia-autorollc093cc82019-10-08 19:22:13 +0000180float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
181 float sign = x < 0 ? -1.0f : 1.0f;
182 x *= sign;
183
skia-autoroll5f8588c2019-10-14 13:54:14 +0000184 TF_PQish pq;
185 TF_HLGish hlg;
186 switch (classify(*tf, &pq, &hlg)) {
skia-autorollc093cc82019-10-08 19:22:13 +0000187 case Bad: break;
skia-autoroll5f8588c2019-10-14 13:54:14 +0000188
skia-autoroll5f8588c2019-10-14 13:54:14 +0000189 case HLGish: return sign * (x*hlg.R <= 1 ? powf_(x*hlg.R, hlg.G)
190 : expf_((x-hlg.c)*hlg.a) + hlg.b);
191
skia-autorollf6d50642019-10-14 15:31:13 +0000192 // skcms_TransferFunction_invert() inverts R, G, and a for HLGinvish so this math is fast.
skia-autoroll5f8588c2019-10-14 13:54:14 +0000193 case HLGinvish: return sign * (x <= 1 ? hlg.R * powf_(x, hlg.G)
194 : hlg.a * logf_(x - hlg.b) + hlg.c);
195
skia-autorollc093cc82019-10-08 19:22:13 +0000196
197 case sRGBish: return sign * (x < tf->d ? tf->c * x + tf->f
198 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
199
200 case PQish: return sign * powf_(fmaxf_(pq.A + pq.B * powf_(x, pq.C), 0)
201 / (pq.D + pq.E * powf_(x, pq.C)),
202 pq.F);
203 }
204 return 0;
205}
206
207
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000208static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000209 if (curve->table_entries == 0) {
210 return skcms_TransferFunction_eval(&curve->parametric, x);
211 }
212
213 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 +0000214 int lo = (int) ix ,
215 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000216 float t = ix - (float)lo;
217
218 float l, h;
219 if (curve->table_8) {
220 l = curve->table_8[lo] * (1/255.0f);
221 h = curve->table_8[hi] * (1/255.0f);
222 } else {
223 uint16_t be_l, be_h;
224 memcpy(&be_l, curve->table_16 + 2*lo, 2);
225 memcpy(&be_h, curve->table_16 + 2*hi, 2);
226 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
227 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
228 l = le_l * (1/65535.0f);
229 h = le_h * (1/65535.0f);
230 }
231 return l + (h-l)*t;
232}
233
skia-autorolld95243b2019-11-20 19:40:25 +0000234float skcms_MaxRoundtripError(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000235 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
236 const float dx = 1.0f / (N - 1);
237 float err = 0;
238 for (uint32_t i = 0; i < N; i++) {
239 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000240 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000241 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
242 }
243 return err;
244}
245
246bool skcms_AreApproximateInverses(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skia-autorolld95243b2019-11-20 19:40:25 +0000247 return skcms_MaxRoundtripError(curve, inv_tf) < (1/512.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000248}
249
250// Additional ICC signature values that are only used internally
251enum {
252 // File signature
253 skcms_Signature_acsp = 0x61637370,
254
255 // Tag signatures
256 skcms_Signature_rTRC = 0x72545243,
257 skcms_Signature_gTRC = 0x67545243,
258 skcms_Signature_bTRC = 0x62545243,
259 skcms_Signature_kTRC = 0x6B545243,
260
261 skcms_Signature_rXYZ = 0x7258595A,
262 skcms_Signature_gXYZ = 0x6758595A,
263 skcms_Signature_bXYZ = 0x6258595A,
264
265 skcms_Signature_A2B0 = 0x41324230,
266 skcms_Signature_A2B1 = 0x41324231,
267 skcms_Signature_mAB = 0x6D414220,
268
269 skcms_Signature_CHAD = 0x63686164,
skia-autoroll2565a6d2019-11-11 16:48:43 +0000270 skcms_Signature_WTPT = 0x77747074,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000271
272 // Type signatures
273 skcms_Signature_curv = 0x63757276,
274 skcms_Signature_mft1 = 0x6D667431,
275 skcms_Signature_mft2 = 0x6D667432,
276 skcms_Signature_para = 0x70617261,
277 skcms_Signature_sf32 = 0x73663332,
278 // XYZ is also a PCS signature, so it's defined in skcms.h
279 // skcms_Signature_XYZ = 0x58595A20,
280};
281
282static uint16_t read_big_u16(const uint8_t* ptr) {
283 uint16_t be;
284 memcpy(&be, ptr, sizeof(be));
285#if defined(_MSC_VER)
286 return _byteswap_ushort(be);
287#else
288 return __builtin_bswap16(be);
289#endif
290}
291
292static uint32_t read_big_u32(const uint8_t* ptr) {
293 uint32_t be;
294 memcpy(&be, ptr, sizeof(be));
295#if defined(_MSC_VER)
296 return _byteswap_ulong(be);
297#else
298 return __builtin_bswap32(be);
299#endif
300}
301
302static int32_t read_big_i32(const uint8_t* ptr) {
303 return (int32_t)read_big_u32(ptr);
304}
305
306static float read_big_fixed(const uint8_t* ptr) {
307 return read_big_i32(ptr) * (1.0f / 65536.0f);
308}
309
310// Maps to an in-memory profile so that fields line up to the locations specified
311// in ICC.1:2010, section 7.2
312typedef struct {
313 uint8_t size [ 4];
314 uint8_t cmm_type [ 4];
315 uint8_t version [ 4];
316 uint8_t profile_class [ 4];
317 uint8_t data_color_space [ 4];
318 uint8_t pcs [ 4];
319 uint8_t creation_date_time [12];
320 uint8_t signature [ 4];
321 uint8_t platform [ 4];
322 uint8_t flags [ 4];
323 uint8_t device_manufacturer [ 4];
324 uint8_t device_model [ 4];
325 uint8_t device_attributes [ 8];
326 uint8_t rendering_intent [ 4];
327 uint8_t illuminant_X [ 4];
328 uint8_t illuminant_Y [ 4];
329 uint8_t illuminant_Z [ 4];
330 uint8_t creator [ 4];
331 uint8_t profile_id [16];
332 uint8_t reserved [28];
333 uint8_t tag_count [ 4]; // Technically not part of header, but required
334} header_Layout;
335
336typedef struct {
337 uint8_t signature [4];
338 uint8_t offset [4];
339 uint8_t size [4];
340} tag_Layout;
341
342static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
343 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
344}
345
346// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
347// use of the type is for the CHAD tag that stores exactly nine values.
348typedef struct {
349 uint8_t type [ 4];
350 uint8_t reserved [ 4];
351 uint8_t values [36];
352} sf32_Layout;
353
354bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
355 skcms_ICCTag tag;
356 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
357 return false;
358 }
359
360 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
361 return false;
362 }
363
364 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
365 const uint8_t* values = sf32Tag->values;
366 for (int r = 0; r < 3; ++r)
367 for (int c = 0; c < 3; ++c, values += 4) {
368 m->vals[r][c] = read_big_fixed(values);
369 }
370 return true;
371}
372
373// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
374// the type are for tags/data that store exactly one triple.
375typedef struct {
376 uint8_t type [4];
377 uint8_t reserved [4];
378 uint8_t X [4];
379 uint8_t Y [4];
380 uint8_t Z [4];
381} XYZ_Layout;
382
383static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
384 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
385 return false;
386 }
387
388 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
389
390 *x = read_big_fixed(xyzTag->X);
391 *y = read_big_fixed(xyzTag->Y);
392 *z = read_big_fixed(xyzTag->Z);
393 return true;
394}
395
skia-autoroll2565a6d2019-11-11 16:48:43 +0000396bool skcms_GetWTPT(const skcms_ICCProfile* profile, float xyz[3]) {
397 skcms_ICCTag tag;
398 return skcms_GetTagBySignature(profile, skcms_Signature_WTPT, &tag) &&
399 read_tag_xyz(&tag, &xyz[0], &xyz[1], &xyz[2]);
400}
401
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000402static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
403 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
404 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
405 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
406 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
407}
408
409typedef struct {
410 uint8_t type [4];
411 uint8_t reserved_a [4];
412 uint8_t function_type [2];
413 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000414 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 +0000415} para_Layout;
416
417static bool read_curve_para(const uint8_t* buf, uint32_t size,
418 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000419 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000420 return false;
421 }
422
423 const para_Layout* paraTag = (const para_Layout*)buf;
424
425 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
426 uint16_t function_type = read_big_u16(paraTag->function_type);
427 if (function_type > kGABCDEF) {
428 return false;
429 }
430
431 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 +0000432 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 +0000433 return false;
434 }
435
436 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000437 *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 +0000438 }
439
440 curve->table_entries = 0;
441 curve->parametric.a = 1.0f;
442 curve->parametric.b = 0.0f;
443 curve->parametric.c = 0.0f;
444 curve->parametric.d = 0.0f;
445 curve->parametric.e = 0.0f;
446 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000447 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000448
449 switch (function_type) {
450 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000451 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
452 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000453 if (curve->parametric.a == 0) {
454 return false;
455 }
456 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
457 break;
458 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000459 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
460 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
461 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000462 if (curve->parametric.a == 0) {
463 return false;
464 }
465 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
466 curve->parametric.f = curve->parametric.e;
467 break;
468 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000469 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
470 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
471 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
472 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000473 break;
474 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000475 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
476 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
477 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
478 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
479 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
480 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000481 break;
482 }
skia-autorollf6d50642019-10-14 15:31:13 +0000483 return classify(curve->parametric) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000484}
485
486typedef struct {
487 uint8_t type [4];
488 uint8_t reserved [4];
489 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000490 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 +0000491} curv_Layout;
492
493static bool read_curve_curv(const uint8_t* buf, uint32_t size,
494 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000495 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000496 return false;
497 }
498
499 const curv_Layout* curvTag = (const curv_Layout*)buf;
500
501 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 +0000502 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 +0000503 return false;
504 }
505
506 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000507 *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 +0000508 }
509
510 if (value_count < 2) {
511 curve->table_entries = 0;
512 curve->parametric.a = 1.0f;
513 curve->parametric.b = 0.0f;
514 curve->parametric.c = 0.0f;
515 curve->parametric.d = 0.0f;
516 curve->parametric.e = 0.0f;
517 curve->parametric.f = 0.0f;
518 if (value_count == 0) {
519 // Empty tables are a shorthand for an identity curve
520 curve->parametric.g = 1.0f;
521 } else {
522 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000523 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 +0000524 }
525 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000526 curve->table_8 = nullptr;
527 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000528 curve->table_entries = value_count;
529 }
530
531 return true;
532}
533
534// 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 +0000535// 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 +0000536static bool read_curve(const uint8_t* buf, uint32_t size,
537 skcms_Curve* curve, uint32_t* curve_size) {
538 if (!buf || size < 4 || !curve) {
539 return false;
540 }
541
542 uint32_t type = read_big_u32(buf);
543 if (type == skcms_Signature_para) {
544 return read_curve_para(buf, size, curve, curve_size);
545 } else if (type == skcms_Signature_curv) {
546 return read_curve_curv(buf, size, curve, curve_size);
547 }
548
549 return false;
550}
551
552// mft1 and mft2 share a large chunk of data
553typedef struct {
554 uint8_t type [ 4];
555 uint8_t reserved_a [ 4];
556 uint8_t input_channels [ 1];
557 uint8_t output_channels [ 1];
558 uint8_t grid_points [ 1];
559 uint8_t reserved_b [ 1];
560 uint8_t matrix [36];
561} mft_CommonLayout;
562
563typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000564 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000565
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000566 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000567} mft1_Layout;
568
569typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000570 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000571
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000572 uint8_t input_table_entries [2];
573 uint8_t output_table_entries [2];
574 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000575} mft2_Layout;
576
577static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
578 // MFT matrices are applied before the first set of curves, but must be identity unless the
579 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
580 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
581 // field/flag.
582 a2b->matrix_channels = 0;
583
584 a2b->input_channels = mftTag->input_channels[0];
585 a2b->output_channels = mftTag->output_channels[0];
586
587 // We require exactly three (ie XYZ/Lab/RGB) output channels
588 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
589 return false;
590 }
591 // We require at least one, and no more than four (ie CMYK) input channels
592 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
593 return false;
594 }
595
596 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
597 a2b->grid_points[i] = mftTag->grid_points[0];
598 }
599 // The grid only makes sense with at least two points along each axis
600 if (a2b->grid_points[0] < 2) {
601 return false;
602 }
603
604 return true;
605}
606
607static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
608 uint32_t input_table_entries, uint32_t output_table_entries,
609 skcms_A2B* a2b) {
610 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
611 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
612 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
613
614 // [input|output]_channels are <= 4, so still no overflow
615 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
616 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
617
618 uint64_t grid_size = a2b->output_channels * byte_width;
619 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
620 grid_size *= a2b->grid_points[axis];
621 }
622
623 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
624 return false;
625 }
626
627 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
628 a2b->input_curves[i].table_entries = input_table_entries;
629 if (byte_width == 1) {
630 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 +0000631 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000632 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000633 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000634 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
635 }
636 }
637
638 if (byte_width == 1) {
639 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 +0000640 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000641 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000642 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000643 a2b->grid_16 = table_base + byte_len_all_input_tables;
644 }
645
646 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
647 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
648 a2b->output_curves[i].table_entries = output_table_entries;
649 if (byte_width == 1) {
650 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 +0000651 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000652 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000653 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000654 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
655 }
656 }
657
658 return true;
659}
660
661static 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 +0000662 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000663 return false;
664 }
665
666 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
667 if (!read_mft_common(mftTag->common, a2b)) {
668 return false;
669 }
670
671 uint32_t input_table_entries = 256;
672 uint32_t output_table_entries = 256;
673
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000674 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 +0000675 input_table_entries, output_table_entries, a2b);
676}
677
678static 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 +0000679 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000680 return false;
681 }
682
683 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
684 if (!read_mft_common(mftTag->common, a2b)) {
685 return false;
686 }
687
688 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
689 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
690
691 // ICC spec mandates that 2 <= table_entries <= 4096
692 if (input_table_entries < 2 || input_table_entries > 4096 ||
693 output_table_entries < 2 || output_table_entries > 4096) {
694 return false;
695 }
696
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000697 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 +0000698 input_table_entries, output_table_entries, a2b);
699}
700
701static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
702 uint32_t num_curves, skcms_Curve* curves) {
703 for (uint32_t i = 0; i < num_curves; ++i) {
704 if (curve_offset > size) {
705 return false;
706 }
707
708 uint32_t curve_bytes;
709 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
710 return false;
711 }
712
713 if (curve_bytes > UINT32_MAX - 3) {
714 return false;
715 }
716 curve_bytes = (curve_bytes + 3) & ~3U;
717
718 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
719 curve_offset = (uint32_t)new_offset_64;
720 if (new_offset_64 != curve_offset) {
721 return false;
722 }
723 }
724
725 return true;
726}
727
728typedef struct {
729 uint8_t type [ 4];
730 uint8_t reserved_a [ 4];
731 uint8_t input_channels [ 1];
732 uint8_t output_channels [ 1];
733 uint8_t reserved_b [ 2];
734 uint8_t b_curve_offset [ 4];
735 uint8_t matrix_offset [ 4];
736 uint8_t m_curve_offset [ 4];
737 uint8_t clut_offset [ 4];
738 uint8_t a_curve_offset [ 4];
739} mAB_Layout;
740
741typedef struct {
742 uint8_t grid_points [16];
743 uint8_t grid_byte_width [ 1];
744 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000745 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000746} mABCLUT_Layout;
747
748static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
749 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
750 return false;
751 }
752
753 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
754
755 a2b->input_channels = mABTag->input_channels[0];
756 a2b->output_channels = mABTag->output_channels[0];
757
758 // We require exactly three (ie XYZ/Lab/RGB) output channels
759 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
760 return false;
761 }
762 // We require no more than four (ie CMYK) input channels
763 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
764 return false;
765 }
766
767 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
768 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
769 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
770 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
771 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
772
773 // "B" curves must be present
774 if (0 == b_curve_offset) {
775 return false;
776 }
777
778 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
779 a2b->output_curves)) {
780 return false;
781 }
782
783 // "M" curves and Matrix must be used together
784 if (0 != m_curve_offset) {
785 if (0 == matrix_offset) {
786 return false;
787 }
788 a2b->matrix_channels = a2b->output_channels;
789 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
790 a2b->matrix_curves)) {
791 return false;
792 }
793
794 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
795 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
796 return false;
797 }
798 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
799 const uint8_t* mtx_buf = tag->buf + matrix_offset;
800 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
801 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
802 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
803 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
804 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
805 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
806 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
807 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
808 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
809 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
810 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
811 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
812 } else {
813 if (0 != matrix_offset) {
814 return false;
815 }
816 a2b->matrix_channels = 0;
817 }
818
819 // "A" curves and CLUT must be used together
820 if (0 != a_curve_offset) {
821 if (0 == clut_offset) {
822 return false;
823 }
824 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
825 a2b->input_curves)) {
826 return false;
827 }
828
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000829 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 +0000830 return false;
831 }
832 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
833
834 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000835 a2b->grid_8 = clut->variable;
836 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000837 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000838 a2b->grid_8 = nullptr;
839 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000840 } else {
841 return false;
842 }
843
844 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
845 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
846 a2b->grid_points[i] = clut->grid_points[i];
847 // The grid only makes sense with at least two points along each axis
848 if (a2b->grid_points[i] < 2) {
849 return false;
850 }
851 grid_size *= a2b->grid_points[i];
852 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000853 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 +0000854 return false;
855 }
856 } else {
857 if (0 != clut_offset) {
858 return false;
859 }
860
861 // If there is no CLUT, the number of input and output channels must match
862 if (a2b->input_channels != a2b->output_channels) {
863 return false;
864 }
865
866 // Zero out the number of input channels to signal that we're skipping this stage
867 a2b->input_channels = 0;
868 }
869
870 return true;
871}
872
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000873// If you pass f, we'll fit a possibly-non-zero value for *f.
874// If you pass nullptr, we'll assume you want *f to be treated as zero.
875static int fit_linear(const skcms_Curve* curve, int N, float tol,
876 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000877 assert(N > 1);
878 // We iteratively fit the first points to the TF's linear piece.
879 // We want the cx + f line to pass through the first and last points we fit exactly.
880 //
881 // As we walk along the points we find the minimum and maximum slope of the line before the
882 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
883 // emtpy, when we definitely can't add any more points.
884 //
885 // Some points' error intervals may intersect the running interval but not lie fully
886 // within it. So we keep track of the last point we saw that is a valid end point candidate,
887 // and once the search is done, back up to build the line through *that* point.
888 const float dx = 1.0f / (N - 1);
889
890 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000891
892 float f_zero = 0.0f;
893 if (f) {
894 *f = eval_curve(curve, 0);
895 } else {
896 f = &f_zero;
897 }
898
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000899
900 float slope_min = -INFINITY_;
901 float slope_max = +INFINITY_;
902 for (int i = 1; i < N; ++i) {
903 float x = i * dx;
904 float y = eval_curve(curve, x);
905
906 float slope_max_i = (y + tol - *f) / x,
907 slope_min_i = (y - tol - *f) / x;
908 if (slope_max_i < slope_min || slope_max < slope_min_i) {
909 // Slope intervals would no longer overlap.
910 break;
911 }
912 slope_max = fminf_(slope_max, slope_max_i);
913 slope_min = fmaxf_(slope_min, slope_min_i);
914
915 float cur_slope = (y - *f) / x;
916 if (slope_min <= cur_slope && cur_slope <= slope_max) {
917 lin_points = i + 1;
918 *c = cur_slope;
919 }
920 }
921
922 // Set D to the last point that met our tolerance.
923 *d = (lin_points - 1) * dx;
924 return lin_points;
925}
926
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000927static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
928 bool ok = false;
929 if (tag->type == skcms_Signature_mft1) {
930 ok = read_tag_mft1(tag, a2b);
931 } else if (tag->type == skcms_Signature_mft2) {
932 ok = read_tag_mft2(tag, a2b);
933 } else if (tag->type == skcms_Signature_mAB) {
934 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
935 }
936 if (!ok) {
937 return false;
938 }
939
940 // Detect and canonicalize identity tables.
941 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000942 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
943 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
944 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
945 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
946 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
947 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
948 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
949 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
950 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
951 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000952 };
953
954 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
955 skcms_Curve* curve = curves[i];
956
957 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
958 int N = (int)curve->table_entries;
959
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000960 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 +0000961 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 +0000962 && c == 1.0f
963 && f == 0.0f) {
964 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000965 curve->table_8 = nullptr;
966 curve->table_16 = nullptr;
967 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 +0000968 }
969 }
970 }
971
972 return true;
973}
974
975void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
976 if (!profile || !profile->buffer || !tag) { return; }
977 if (idx > profile->tag_count) { return; }
978 const tag_Layout* tags = get_tag_table(profile);
979 tag->signature = read_big_u32(tags[idx].signature);
980 tag->size = read_big_u32(tags[idx].size);
981 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
982 tag->type = read_big_u32(tag->buf);
983}
984
985bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
986 if (!profile || !profile->buffer || !tag) { return false; }
987 const tag_Layout* tags = get_tag_table(profile);
988 for (uint32_t i = 0; i < profile->tag_count; ++i) {
989 if (read_big_u32(tags[i].signature) == sig) {
990 tag->signature = sig;
991 tag->size = read_big_u32(tags[i].size);
992 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
993 tag->type = read_big_u32(tag->buf);
994 return true;
995 }
996 }
997 return false;
998}
999
1000static bool usable_as_src(const skcms_ICCProfile* profile) {
1001 return profile->has_A2B
1002 || (profile->has_trc && profile->has_toXYZD50);
1003}
1004
1005bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
1006 assert(SAFE_SIZEOF(header_Layout) == 132);
1007
1008 if (!profile) {
1009 return false;
1010 }
1011 memset(profile, 0, SAFE_SIZEOF(*profile));
1012
1013 if (len < SAFE_SIZEOF(header_Layout)) {
1014 return false;
1015 }
1016
1017 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001018 const header_Layout* header = (const header_Layout*)buf;
1019 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001020 profile->size = read_big_u32(header->size);
1021 uint32_t version = read_big_u32(header->version);
1022 profile->data_color_space = read_big_u32(header->data_color_space);
1023 profile->pcs = read_big_u32(header->pcs);
1024 uint32_t signature = read_big_u32(header->signature);
1025 float illuminant_X = read_big_fixed(header->illuminant_X);
1026 float illuminant_Y = read_big_fixed(header->illuminant_Y);
1027 float illuminant_Z = read_big_fixed(header->illuminant_Z);
1028 profile->tag_count = read_big_u32(header->tag_count);
1029
1030 // Validate signature, size (smaller than buffer, large enough to hold tag table),
1031 // and major version
1032 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1033 if (signature != skcms_Signature_acsp ||
1034 profile->size > len ||
1035 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1036 (version >> 24) > 4) {
1037 return false;
1038 }
1039
1040 // Validate that illuminant is D50 white
1041 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1042 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1043 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1044 return false;
1045 }
1046
1047 // Validate that all tag entries have sane offset + size
1048 const tag_Layout* tags = get_tag_table(profile);
1049 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1050 uint32_t tag_offset = read_big_u32(tags[i].offset);
1051 uint32_t tag_size = read_big_u32(tags[i].size);
1052 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
1053 if (tag_size < 4 || tag_end > profile->size) {
1054 return false;
1055 }
1056 }
1057
1058 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1059 return false;
1060 }
1061
1062 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1063
1064 // Pre-parse commonly used tags.
1065 skcms_ICCTag kTRC;
1066 if (profile->data_color_space == skcms_Signature_Gray &&
1067 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001068 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 +00001069 // Malformed tag
1070 return false;
1071 }
1072 profile->trc[1] = profile->trc[0];
1073 profile->trc[2] = profile->trc[0];
1074 profile->has_trc = true;
1075
1076 if (pcs_is_xyz) {
1077 profile->toXYZD50.vals[0][0] = illuminant_X;
1078 profile->toXYZD50.vals[1][1] = illuminant_Y;
1079 profile->toXYZD50.vals[2][2] = illuminant_Z;
1080 profile->has_toXYZD50 = true;
1081 }
1082 } else {
1083 skcms_ICCTag rTRC, gTRC, bTRC;
1084 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1085 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1086 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001087 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1088 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1089 !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 +00001090 // Malformed TRC tags
1091 return false;
1092 }
1093 profile->has_trc = true;
1094 }
1095
1096 skcms_ICCTag rXYZ, gXYZ, bXYZ;
1097 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1098 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1099 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1100 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1101 // Malformed XYZ tags
1102 return false;
1103 }
1104 profile->has_toXYZD50 = true;
1105 }
1106 }
1107
1108 skcms_ICCTag a2b_tag;
1109
1110 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
1111 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
1112 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
1113 // and all our known users are thinking exclusively in terms of relative colormetric.
1114 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
1115 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
1116 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
1117 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
1118 // Malformed A2B tag
1119 return false;
1120 }
1121 profile->has_A2B = true;
1122 break;
1123 }
1124 }
1125
1126 return usable_as_src(profile);
1127}
1128
1129
1130const skcms_ICCProfile* skcms_sRGB_profile() {
1131 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001132 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001133
1134 0, // size, moot here
1135 skcms_Signature_RGB, // data_color_space
1136 skcms_Signature_XYZ, // pcs
1137 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001138
1139 // We choose to represent sRGB with its canonical transfer function,
1140 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001141 true, // has_trc, followed by the 3 trc curves
1142 {
1143 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1144 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1145 {{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 +00001146 },
1147
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001148 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1149 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001150 { 0.436065674f, 0.385147095f, 0.143066406f },
1151 { 0.222488403f, 0.716873169f, 0.060607910f },
1152 { 0.013916016f, 0.097076416f, 0.714096069f },
1153 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001154
1155 false, // has_A2B, followed by a2b itself which we don't care about.
1156 {
1157 0,
1158 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001159 {{0, {0,0, 0,0,0,0,0}}},
1160 {{0, {0,0, 0,0,0,0,0}}},
1161 {{0, {0,0, 0,0,0,0,0}}},
1162 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001163 },
1164 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001165 nullptr,
1166 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001167
1168 0,
1169 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001170 {{0, {0,0, 0,0,0,0,0}}},
1171 {{0, {0,0, 0,0,0,0,0}}},
1172 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001173 },
1174 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001175 { 0,0,0,0 },
1176 { 0,0,0,0 },
1177 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001178 }},
1179
1180 0,
1181 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001182 {{0, {0,0, 0,0,0,0,0}}},
1183 {{0, {0,0, 0,0,0,0,0}}},
1184 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001185 },
1186 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001187 };
1188 return &sRGB_profile;
1189}
1190
1191const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001192 // 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 +00001193 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001194 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001195
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001196 0, // size, moot here
1197 skcms_Signature_RGB, // data_color_space
1198 skcms_Signature_XYZ, // pcs
1199 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001200
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001201 true, // has_trc, followed by the 3 trc curves
1202 {
1203 {{0, {1,1, 0,0,0,0,0}}},
1204 {{0, {1,1, 0,0,0,0,0}}},
1205 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001206 },
1207
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001208 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1209 {{
1210 { 1,0,0 },
1211 { 0,1,0 },
1212 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001213 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001214
1215 false, // has_A2B, followed by a2b itself which we don't care about.
1216 {
1217 0,
1218 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001219 {{0, {0,0, 0,0,0,0,0}}},
1220 {{0, {0,0, 0,0,0,0,0}}},
1221 {{0, {0,0, 0,0,0,0,0}}},
1222 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001223 },
1224 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001225 nullptr,
1226 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001227
1228 0,
1229 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001230 {{0, {0,0, 0,0,0,0,0}}},
1231 {{0, {0,0, 0,0,0,0,0}}},
1232 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001233 },
1234 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001235 { 0,0,0,0 },
1236 { 0,0,0,0 },
1237 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001238 }},
1239
1240 0,
1241 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001242 {{0, {0,0, 0,0,0,0,0}}},
1243 {{0, {0,0, 0,0,0,0,0}}},
1244 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001245 },
1246 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001247 };
1248
1249 return &XYZD50_profile;
1250}
1251
1252const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1253 return &skcms_sRGB_profile()->trc[0].parametric;
1254}
1255
1256const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1257 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001258 {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 +00001259 return &sRGB_inv;
1260}
1261
1262const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1263 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1264 return &identity;
1265}
1266
1267const uint8_t skcms_252_random_bytes[] = {
1268 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1269 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1270 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1271 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1272 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1273 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1274 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1275 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1276 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1277 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1278 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1279 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1280 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1281 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1282 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1283 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1284};
1285
1286bool 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 +00001287 // Test for exactly equal profiles first.
1288 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1289 return true;
1290 }
1291
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001292 // For now this is the essentially the same strategy we use in test_only.c
1293 // for our skcms_Transform() smoke tests:
1294 // 1) transform A to XYZD50
1295 // 2) transform B to XYZD50
1296 // 3) return true if they're similar enough
1297 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1298
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001299 // 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 +00001300 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1301
1302 if (A->data_color_space != B->data_color_space) {
1303 return false;
1304 }
1305
1306 // 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 +00001307 // 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 +00001308 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1309 size_t npixels = 84;
1310 if (A->data_color_space == skcms_Signature_CMYK) {
1311 fmt = skcms_PixelFormat_RGBA_8888;
1312 npixels = 63;
1313 }
1314
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001315 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1316 // 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 +00001317 uint8_t dstA[252],
1318 dstB[252];
1319 if (!skcms_Transform(
1320 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1321 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1322 npixels)) {
1323 return false;
1324 }
1325 if (!skcms_Transform(
1326 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1327 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1328 npixels)) {
1329 return false;
1330 }
1331
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001332 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001333 for (size_t i = 0; i < 252; i++) {
1334 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1335 return false;
1336 }
1337 }
1338 return true;
1339}
1340
1341bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1342 const skcms_TransferFunction* inv_tf) {
1343 if (!profile || !profile->has_trc) {
1344 return false;
1345 }
1346
1347 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1348 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1349 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1350}
1351
1352static bool is_zero_to_one(float x) {
1353 return 0 <= x && x <= 1;
1354}
1355
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001356typedef struct { float vals[3]; } skcms_Vector3;
1357
1358static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1359 skcms_Vector3 dst = {{0,0,0}};
1360 for (int row = 0; row < 3; ++row) {
1361 dst.vals[row] = m->vals[row][0] * v->vals[0]
1362 + m->vals[row][1] * v->vals[1]
1363 + m->vals[row][2] * v->vals[2];
1364 }
1365 return dst;
1366}
1367
skia-autoroll70b6b5e2019-12-11 18:40:05 +00001368bool skcms_AdaptToXYZD50(float wx, float wy,
1369 skcms_Matrix3x3* toXYZD50) {
1370 if (!is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1371 !toXYZD50) {
1372 return false;
1373 }
1374
1375 // Assumes that Y is 1.0f.
1376 skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
1377
1378 // Now convert toXYZ matrix to toXYZD50.
1379 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1380
1381 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1382 // the matrices below. The Bradford method is used by Adobe and is widely considered
1383 // to be the best.
1384 skcms_Matrix3x3 xyz_to_lms = {{
1385 { 0.8951f, 0.2664f, -0.1614f },
1386 { -0.7502f, 1.7135f, 0.0367f },
1387 { 0.0389f, -0.0685f, 1.0296f },
1388 }};
1389 skcms_Matrix3x3 lms_to_xyz = {{
1390 { 0.9869929f, -0.1470543f, 0.1599627f },
1391 { 0.4323053f, 0.5183603f, 0.0492912f },
1392 { -0.0085287f, 0.0400428f, 0.9684867f },
1393 }};
1394
1395 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1396 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
1397
1398 *toXYZD50 = {{
1399 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1400 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1401 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1402 }};
1403 *toXYZD50 = skcms_Matrix3x3_concat(toXYZD50, &xyz_to_lms);
1404 *toXYZD50 = skcms_Matrix3x3_concat(&lms_to_xyz, toXYZD50);
1405
1406 return true;
1407}
1408
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001409bool skcms_PrimariesToXYZD50(float rx, float ry,
1410 float gx, float gy,
1411 float bx, float by,
1412 float wx, float wy,
1413 skcms_Matrix3x3* toXYZD50) {
1414 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1415 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1416 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1417 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1418 !toXYZD50) {
1419 return false;
1420 }
1421
1422 // First, we need to convert xy values (primaries) to XYZ.
1423 skcms_Matrix3x3 primaries = {{
1424 { rx, gx, bx },
1425 { ry, gy, by },
1426 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1427 }};
1428 skcms_Matrix3x3 primaries_inv;
1429 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1430 return false;
1431 }
1432
1433 // Assumes that Y is 1.0f.
1434 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 +00001435 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001436
1437 skcms_Matrix3x3 toXYZ = {{
1438 { XYZ.vals[0], 0, 0 },
1439 { 0, XYZ.vals[1], 0 },
1440 { 0, 0, XYZ.vals[2] },
1441 }};
1442 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1443
skia-autoroll70b6b5e2019-12-11 18:40:05 +00001444 skcms_Matrix3x3 DXtoD50;
1445 if (!skcms_AdaptToXYZD50(wx, wy, &DXtoD50)) {
1446 return false;
1447 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001448
1449 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1450 return true;
1451}
1452
1453
1454bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1455 double a00 = src->vals[0][0],
1456 a01 = src->vals[1][0],
1457 a02 = src->vals[2][0],
1458 a10 = src->vals[0][1],
1459 a11 = src->vals[1][1],
1460 a12 = src->vals[2][1],
1461 a20 = src->vals[0][2],
1462 a21 = src->vals[1][2],
1463 a22 = src->vals[2][2];
1464
1465 double b0 = a00*a11 - a01*a10,
1466 b1 = a00*a12 - a02*a10,
1467 b2 = a01*a12 - a02*a11,
1468 b3 = a20,
1469 b4 = a21,
1470 b5 = a22;
1471
1472 double determinant = b0*b5
1473 - b1*b4
1474 + b2*b3;
1475
1476 if (determinant == 0) {
1477 return false;
1478 }
1479
1480 double invdet = 1.0 / determinant;
1481 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1482 return false;
1483 }
1484
1485 b0 *= invdet;
1486 b1 *= invdet;
1487 b2 *= invdet;
1488 b3 *= invdet;
1489 b4 *= invdet;
1490 b5 *= invdet;
1491
1492 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1493 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1494 dst->vals[2][0] = (float)( + b2 );
1495 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1496 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1497 dst->vals[2][1] = (float)( - b1 );
1498 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1499 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1500 dst->vals[2][2] = (float)( + b0 );
1501
1502 for (int r = 0; r < 3; ++r)
1503 for (int c = 0; c < 3; ++c) {
1504 if (!isfinitef_(dst->vals[r][c])) {
1505 return false;
1506 }
1507 }
1508 return true;
1509}
1510
1511skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1512 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1513 for (int r = 0; r < 3; r++)
1514 for (int c = 0; c < 3; c++) {
1515 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1516 + A->vals[r][1] * B->vals[1][c]
1517 + A->vals[r][2] * B->vals[2][c];
1518 }
1519 return m;
1520}
1521
skia-autorollacd6e012019-01-08 14:10:52 +00001522#if defined(__clang__)
skia-autorollf6d50642019-10-14 15:31:13 +00001523 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by classify() on the way out.
skia-autorollacd6e012019-01-08 14:10:52 +00001524#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001525bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
skia-autorollf6d50642019-10-14 15:31:13 +00001526 TF_PQish pq;
1527 TF_HLGish hlg;
1528 switch (classify(*src, &pq, &hlg)) {
1529 case Bad: return false;
1530 case sRGBish: break; // handled below
1531
1532 case PQish:
1533 *dst = { TFKind_marker(PQish), -pq.A, pq.D, 1.0f/pq.F
1534 , pq.B, -pq.E, 1.0f/pq.C};
1535 return true;
1536
1537 case HLGish:
1538 *dst = { TFKind_marker(HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
1539 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1540 return true;
1541
1542 case HLGinvish:
1543 *dst = { TFKind_marker(HLGish), 1.0f/hlg.R, 1.0f/hlg.G
1544 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1545 return true;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001546 }
1547
skia-autorollf6d50642019-10-14 15:31:13 +00001548 assert (classify(*src) == sRGBish);
1549
skia-autorolld0b577f2019-01-07 19:46:57 +00001550 // We're inverting this function, solving for x in terms of y.
1551 // y = (cx + f) x < d
1552 // (ax + b)^g + e x ≥ d
1553 // The inverse of this function can be expressed in the same piecewise form.
1554 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 +00001555
skia-autorolld0b577f2019-01-07 19:46:57 +00001556 // We'll start by finding the new threshold inv.d.
1557 // In principle we should be able to find that by solving for y at x=d from either side.
1558 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1559 float d_l = src->c * src->d + src->f,
1560 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1561 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001562 return false;
1563 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001564 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 +00001565
skia-autorolld0b577f2019-01-07 19:46:57 +00001566 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1567 if (inv.d > 0) {
1568 // Inverting the linear section is pretty straightfoward:
1569 // y = cx + f
1570 // y - f = cx
1571 // (1/c)y - f/c = x
1572 inv.c = 1.0f/src->c;
1573 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001574 }
1575
skia-autorolld0b577f2019-01-07 19:46:57 +00001576 // The interesting part is inverting the nonlinear section:
1577 // y = (ax + b)^g + e.
1578 // y - e = (ax + b)^g
1579 // (y - e)^1/g = ax + b
1580 // (y - e)^1/g - b = ax
1581 // (1/a)(y - e)^1/g - b/a = x
1582 //
1583 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1584 // let k = (1/a)^g
1585 // (1/a)( y - e)^1/g - b/a = x
1586 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001587
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001588 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001589 inv.g = 1.0f / src->g;
1590 inv.a = k;
1591 inv.b = -k * src->e;
1592 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001593
skia-autorollfe16a332019-08-20 19:44:54 +00001594 // We need to enforce the same constraints here that we do when fitting a curve,
skia-autorollf6d50642019-10-14 15:31:13 +00001595 // a >= 0 and ad+b >= 0. These constraints are checked by classify(), so they're true
skia-autorollbf388232019-08-21 14:17:54 +00001596 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001597
skia-autorollbf388232019-08-21 14:17:54 +00001598 // Just like when fitting the curve, there's really no way to rescue a < 0.
1599 if (inv.a < 0) {
1600 return false;
1601 }
1602 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001603 if (inv.a * inv.d + inv.b < 0) {
1604 inv.b = -inv.a * inv.d;
1605 }
skia-autorollbf388232019-08-21 14:17:54 +00001606
skia-autorollf6d50642019-10-14 15:31:13 +00001607 // That should usually make classify(inv) == sRGBish true, but there are a couple situations
skia-autorolla0ed0702019-08-23 16:49:54 +00001608 // where we might still fail here, like non-finite parameter values.
skia-autorollf6d50642019-10-14 15:31:13 +00001609 if (classify(inv) != sRGBish) {
skia-autorolla0ed0702019-08-23 16:49:54 +00001610 return false;
1611 }
1612
skia-autorollbf388232019-08-21 14:17:54 +00001613 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001614 assert (inv.a * inv.d + inv.b >= 0);
1615
skia-autorolla7b28742019-01-09 18:35:46 +00001616 // Now in principle we're done.
skia-autorollad10df62019-08-21 15:14:54 +00001617 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
1618 // e or f of the inverse, depending on which segment contains src(1.0f).
1619 float s = skcms_TransferFunction_eval(src, 1.0f);
skia-autorolla0ed0702019-08-23 16:49:54 +00001620 if (!isfinitef_(s)) {
1621 return false;
1622 }
1623
skia-autorollad10df62019-08-21 15:14:54 +00001624 float sign = s < 0 ? -1.0f : 1.0f;
1625 s *= sign;
1626 if (s < inv.d) {
1627 inv.f = 1.0f - sign * inv.c * s;
1628 } else {
1629 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
1630 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001631
skia-autorolld0b577f2019-01-07 19:46:57 +00001632 *dst = inv;
skia-autorollf6d50642019-10-14 15:31:13 +00001633 return classify(*dst) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001634}
1635
1636// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1637
1638// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1639//
1640// tf(x) = cx + f x < d
1641// tf(x) = (ax + b)^g + e x ≥ d
1642//
1643// When fitting, we add the additional constraint that both pieces meet at d:
1644//
1645// cd + f = (ad + b)^g + e
1646//
1647// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1648//
1649// tf(x) = cx + f x < d
1650// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1651//
1652// Our overall strategy is then:
1653// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001654// - 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 +00001655// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001656// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1657// (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 +00001658// Return the parameters with least maximum error.
1659//
1660// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1661// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1662//
1663// let y = Table(x)
1664// r(x) = x - f_inv(y)
1665//
1666// ∂r/∂g = ln(ay + b)*(ay + b)^g
1667// - ln(ad + b)*(ad + b)^g
1668// ∂r/∂a = yg(ay + b)^(g-1)
1669// - dg(ad + b)^(g-1)
1670// ∂r/∂b = g(ay + b)^(g-1)
1671// - g(ad + b)^(g-1)
1672
1673// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1674// and fill out the gradient of the residual into dfdP.
1675static float rg_nonlinear(float x,
1676 const skcms_Curve* curve,
1677 const skcms_TransferFunction* tf,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001678 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001679 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001680
skia-autorolld07418c2019-11-25 14:26:50 +00001681 const float g = tf->g, a = tf->a, b = tf->b,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001682 c = tf->c, d = tf->d, f = tf->f;
1683
1684 const float Y = fmaxf_(a*y + b, 0.0f),
1685 D = a*d + b;
1686 assert (D >= 0);
1687
1688 // The gradient.
skia-autoroll5f8588c2019-10-14 13:54:14 +00001689 dfdP[0] = logf_(Y)*powf_(Y, g)
1690 - logf_(D)*powf_(D, g);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001691 dfdP[1] = y*g*powf_(Y, g-1)
1692 - d*g*powf_(D, g-1);
1693 dfdP[2] = g*powf_(Y, g-1)
1694 - g*powf_(D, g-1);
1695
1696 // The residual.
1697 const float f_inv = powf_(Y, g)
1698 - powf_(D, g)
1699 + c*d + f;
1700 return x - f_inv;
1701}
1702
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001703static bool gauss_newton_step(const skcms_Curve* curve,
skia-autorolld07418c2019-11-25 14:26:50 +00001704 skcms_TransferFunction* tf,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001705 float x0, float dx, int N) {
1706 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1707 //
skia-autorolld07418c2019-11-25 14:26:50 +00001708 // Let P = [ tf->g, tf->a, tf->b ] (the three terms that we're adjusting).
1709 //
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001710 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1711 // where r(P) is the residual vector
1712 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1713 //
1714 // Let's review the shape of each of these expressions:
1715 // r(P) is [N x 1], a column vector with one entry per value of x tested
1716 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1717 // Jf^T is [3 x N], the transpose of Jf
1718 //
1719 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1720 // and so is its inverse (Jf^T Jf)^-1
1721 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1722 //
1723 // Our implementation strategy to get to the final ∆P is
1724 // 1) evaluate Jf^T Jf, call that lhs
1725 // 2) evaluate Jf^T r(P), call that rhs
1726 // 3) invert lhs
1727 // 4) multiply inverse lhs by rhs
1728 //
1729 // This is a friendly implementation strategy because we don't have to have any
1730 // buffers that scale with N, and equally nice don't have to perform any matrix
1731 // operations that are variable size.
1732 //
1733 // Other implementation strategies could trade this off, e.g. evaluating the
1734 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1735 // the residuals. That would probably require implementing singular value
1736 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1737 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1738 // possibility of this gauss_newton_step() function ever failing.
1739
1740 // 0) start off with lhs and rhs safely zeroed.
1741 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1742 skcms_Vector3 rhs = { {0,0,0} };
1743
1744 // 1,2) evaluate lhs and evaluate rhs
1745 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1746 // so we'll have to update lhs and rhs at the same time.
1747 for (int i = 0; i < N; i++) {
1748 float x = x0 + i*dx;
1749
1750 float dfdP[3] = {0,0,0};
skia-autorolld07418c2019-11-25 14:26:50 +00001751 float resid = rg_nonlinear(x,curve,tf, dfdP);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001752
1753 for (int r = 0; r < 3; r++) {
1754 for (int c = 0; c < 3; c++) {
1755 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1756 }
1757 rhs.vals[r] += dfdP[r] * resid;
1758 }
1759 }
1760
1761 // If any of the 3 P parameters are unused, this matrix will be singular.
1762 // Detect those cases and fix them up to indentity instead, so we can invert.
1763 for (int k = 0; k < 3; k++) {
1764 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1765 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1766 lhs.vals[k][k] = 1;
1767 }
1768 }
1769
1770 // 3) invert lhs
1771 skcms_Matrix3x3 lhs_inv;
1772 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1773 return false;
1774 }
1775
1776 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001777 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skia-autorolld07418c2019-11-25 14:26:50 +00001778 tf->g += dP.vals[0];
1779 tf->a += dP.vals[1];
1780 tf->b += dP.vals[2];
1781 return isfinitef_(tf->g) && isfinitef_(tf->a) && isfinitef_(tf->b);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001782}
1783
skia-autorolld07418c2019-11-25 14:26:50 +00001784static float max_roundtrip_error_checked(const skcms_Curve* curve,
1785 const skcms_TransferFunction* tf_inv) {
1786 skcms_TransferFunction tf;
1787 if (!skcms_TransferFunction_invert(tf_inv, &tf) || sRGBish != classify(tf)) {
1788 return INFINITY_;
1789 }
1790
1791 skcms_TransferFunction tf_inv_again;
1792 if (!skcms_TransferFunction_invert(&tf, &tf_inv_again)) {
1793 return INFINITY_;
1794 }
1795
1796 return skcms_MaxRoundtripError(curve, &tf_inv_again);
1797}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001798
1799// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1800static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
skia-autorolld07418c2019-11-25 14:26:50 +00001801 // This enforces a few constraints that are not modeled in gauss_newton_step()'s optimization.
1802 auto fixup_tf = [tf]() {
1803 // a must be non-negative. That ensures the function is monotonically increasing.
1804 // We don't really know how to fix up a if it goes negative.
1805 if (tf->a < 0) {
1806 return false;
1807 }
1808 // ad+b must be non-negative. That ensures we don't end up with complex numbers in powf.
1809 // We feel just barely not uneasy enough to tweak b so ad+b is zero in this case.
1810 if (tf->a * tf->d + tf->b < 0) {
1811 tf->b = -tf->a * tf->d;
1812 }
1813 assert (tf->a >= 0 &&
1814 tf->a * tf->d + tf->b >= 0);
1815
1816 // cd+f must be ~= (ad+b)^g+e. That ensures the function is continuous. We keep e as a free
1817 // parameter so we can guarantee this.
1818 tf->e = tf->c*tf->d + tf->f
1819 - powf_(tf->a*tf->d + tf->b, tf->g);
1820
1821 return true;
1822 };
1823
1824 if (!fixup_tf()) {
1825 return false;
1826 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001827
1828 // No matter where we start, dx should always represent N even steps from 0 to 1.
1829 const float dx = 1.0f / (N-1);
1830
skia-autorolld07418c2019-11-25 14:26:50 +00001831 skcms_TransferFunction best_tf = *tf;
1832 float best_max_error = INFINITY_;
1833
1834 // Need this or several curves get worse... *sigh*
1835 float init_error = max_roundtrip_error_checked(curve, tf);
1836 if (init_error < best_max_error) {
1837 best_max_error = init_error;
1838 best_tf = *tf;
1839 }
1840
skia-autorolld9718822019-08-23 18:16:54 +00001841 // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
skia-autorolld07418c2019-11-25 14:26:50 +00001842 for (int j = 0; j < 8; j++) {
1843 if (!gauss_newton_step(curve, tf, L*dx, dx, N-L) || !fixup_tf()) {
1844 *tf = best_tf;
1845 return isfinitef_(best_max_error);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001846 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001847
skia-autorolld07418c2019-11-25 14:26:50 +00001848 float max_error = max_roundtrip_error_checked(curve, tf);
1849 if (max_error < best_max_error) {
1850 best_max_error = max_error;
1851 best_tf = *tf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001852 }
1853 }
1854
skia-autorolld07418c2019-11-25 14:26:50 +00001855 *tf = best_tf;
1856 return isfinitef_(best_max_error);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001857}
1858
1859bool skcms_ApproximateCurve(const skcms_Curve* curve,
1860 skcms_TransferFunction* approx,
1861 float* max_error) {
1862 if (!curve || !approx || !max_error) {
1863 return false;
1864 }
1865
1866 if (curve->table_entries == 0) {
1867 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1868 return false;
1869 }
1870
1871 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1872 // We need at least two points, and must put some reasonable cap on the maximum number.
1873 return false;
1874 }
1875
1876 int N = (int)curve->table_entries;
1877 const float dx = 1.0f / (N - 1);
1878
1879 *max_error = INFINITY_;
1880 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1881 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1882 skcms_TransferFunction tf,
1883 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001884
1885 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1886 tf.f = 0.0f;
1887 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 +00001888
1889 if (L == N) {
1890 // If the entire data set was linear, move the coefficients to the nonlinear portion
1891 // with G == 1. This lets use a canonical representation with d == 0.
1892 tf.g = 1;
1893 tf.a = tf.c;
1894 tf.b = tf.f;
1895 tf.c = tf.d = tf.e = tf.f = 0;
1896 } else if (L == N - 1) {
1897 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1898 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001899 tf.a = (eval_curve(curve, (N-1)*dx) -
1900 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001901 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001902 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001903 - tf.a * (N-2)*dx;
1904 tf.e = 0;
1905 } else {
1906 // Start by guessing a gamma-only curve through the midpoint.
1907 int mid = (L + N) / 2;
1908 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001909 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001910 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001911 tf.a = 1;
1912 tf.b = 0;
1913 tf.e = tf.c*tf.d + tf.f
1914 - powf_(tf.a*tf.d + tf.b, tf.g);
1915
1916
1917 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1918 !fit_nonlinear(curve, L,N, &tf_inv)) {
1919 continue;
1920 }
1921
1922 // We fit tf_inv, so calculate tf to keep in sync.
skia-autorolld07418c2019-11-25 14:26:50 +00001923 // fit_nonlinear() should guarantee invertibility.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001924 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
skia-autorolld07418c2019-11-25 14:26:50 +00001925 assert(false);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001926 continue;
1927 }
1928 }
1929
skia-autorollc7a4e772019-10-15 19:47:13 +00001930 // We'd better have a sane, sRGB-ish TF by now.
1931 // Other non-Bad TFs would be fine, but we know we've only ever tried to fit sRGBish;
1932 // anything else is just some accident of math and the way we pun tf.g as a type flag.
skia-autorollea47b0e2019-12-13 19:14:51 +00001933 // fit_nonlinear() should guarantee this, but the special cases may fail this test.
skia-autorollc7a4e772019-10-15 19:47:13 +00001934 if (sRGBish != classify(tf)) {
1935 continue;
1936 }
1937
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001938 // We find our error by roundtripping the table through tf_inv.
1939 //
1940 // (The most likely use case for this approximation is to be inverted and
1941 // used as the transfer function for a destination color space.)
1942 //
1943 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1944 // invertible, so re-verify that here (and use the new inverse for testing).
skia-autorolle9e35f72019-12-12 19:48:51 +00001945 // fit_nonlinear() should guarantee this, but the special cases that don't use
1946 // it may fail this test.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001947 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1948 continue;
1949 }
1950
skia-autorolld95243b2019-11-20 19:40:25 +00001951 float err = skcms_MaxRoundtripError(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001952 if (*max_error > err) {
1953 *max_error = err;
1954 *approx = tf;
1955 }
1956 }
1957 return isfinitef_(*max_error);
1958}
1959
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001960// ~~~~ Impl. of skcms_Transform() ~~~~
1961
1962typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001963 Op_load_a8,
1964 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001965 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001966 Op_load_4444,
1967 Op_load_565,
1968 Op_load_888,
1969 Op_load_8888,
1970 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001971 Op_load_161616LE,
1972 Op_load_16161616LE,
1973 Op_load_161616BE,
1974 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001975 Op_load_hhh,
1976 Op_load_hhhh,
1977 Op_load_fff,
1978 Op_load_ffff,
1979
1980 Op_swap_rb,
1981 Op_clamp,
1982 Op_invert,
1983 Op_force_opaque,
1984 Op_premul,
1985 Op_unpremul,
1986 Op_matrix_3x3,
1987 Op_matrix_3x4,
1988 Op_lab_to_xyz,
1989
1990 Op_tf_r,
1991 Op_tf_g,
1992 Op_tf_b,
1993 Op_tf_a,
1994
skia-autorollf6d50642019-10-14 15:31:13 +00001995 Op_pq_r,
1996 Op_pq_g,
1997 Op_pq_b,
1998 Op_pq_a,
1999
2000 Op_hlg_r,
2001 Op_hlg_g,
2002 Op_hlg_b,
2003 Op_hlg_a,
2004
2005 Op_hlginv_r,
2006 Op_hlginv_g,
2007 Op_hlginv_b,
2008 Op_hlginv_a,
2009
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002010 Op_table_r,
2011 Op_table_g,
2012 Op_table_b,
2013 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002014
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002015 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002016
2017 Op_store_a8,
2018 Op_store_g8,
2019 Op_store_4444,
2020 Op_store_565,
2021 Op_store_888,
2022 Op_store_8888,
2023 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002024 Op_store_161616LE,
2025 Op_store_16161616LE,
2026 Op_store_161616BE,
2027 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002028 Op_store_hhh,
2029 Op_store_hhhh,
2030 Op_store_fff,
2031 Op_store_ffff,
2032} Op;
2033
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002034#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002035 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 +00002036#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002037 // For some reason GCC accepts this nonsense, but not the more straightforward version,
2038 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
2039 template <int N, typename T>
2040 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 +00002041
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002042 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 +00002043#endif
2044
2045// First, instantiate our default exec_ops() implementation using the default compiliation target.
2046
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002047namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00002048#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
2049 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002050 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00002051 template <typename T> using V = T;
2052 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002053#elif defined(__AVX512F__)
2054 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002055 template <typename T> using V = Vec<N,T>;
2056 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002057#elif defined(__AVX__)
2058 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002059 template <typename T> using V = Vec<N,T>;
2060 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00002061#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
2062 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002063 template <typename T> using V = Vec<N,T>;
2064 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002065#else
2066 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00002067 template <typename T> using V = Vec<N,T>;
2068 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002069#endif
2070
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002071 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002072 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002073}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002074
2075// 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 +00002076#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00002077 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00002078 (( defined(__clang__) && __clang_major__ >= 5) || \
2079 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00002080 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002081
skia-autorollba6087c2019-04-09 13:57:02 +00002082 #if !defined(__AVX2__)
2083 #if defined(__clang__)
2084 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
2085 #elif defined(__GNUC__)
2086 #pragma GCC push_options
2087 #pragma GCC target("avx2,f16c")
2088 #endif
2089
2090 namespace hsw {
2091 #define USING_AVX
2092 #define USING_AVX_F16C
2093 #define USING_AVX2
2094 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002095 template <typename T> using V = Vec<N,T>;
2096 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00002097
2098 #include "src/Transform_inl.h"
2099
2100 // src/Transform_inl.h will undefine USING_* for us.
2101 #undef N
2102 }
2103
2104 #if defined(__clang__)
2105 #pragma clang attribute pop
2106 #elif defined(__GNUC__)
2107 #pragma GCC pop_options
2108 #endif
2109
2110 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002111 #endif
2112
skia-autorollba6087c2019-04-09 13:57:02 +00002113 #if !defined(__AVX512F__)
2114 #if defined(__clang__)
2115 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
2116 #elif defined(__GNUC__)
2117 #pragma GCC push_options
2118 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
2119 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002120
skia-autorollba6087c2019-04-09 13:57:02 +00002121 namespace skx {
2122 #define USING_AVX512F
2123 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002124 template <typename T> using V = Vec<N,T>;
2125 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002126
skia-autorollba6087c2019-04-09 13:57:02 +00002127 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002128
skia-autorollba6087c2019-04-09 13:57:02 +00002129 // src/Transform_inl.h will undefine USING_* for us.
2130 #undef N
2131 }
2132
2133 #if defined(__clang__)
2134 #pragma clang attribute pop
2135 #elif defined(__GNUC__)
2136 #pragma GCC pop_options
2137 #endif
2138
2139 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002140 #endif
2141
skia-autorollba6087c2019-04-09 13:57:02 +00002142 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
2143 enum class CpuType { None, HSW, SKX };
2144 static CpuType cpu_type() {
2145 static const CpuType type = []{
2146 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002147
skia-autorollba6087c2019-04-09 13:57:02 +00002148 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2149 uint32_t eax, ebx, ecx, edx;
2150 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2151 : "0"(1), "2"(0));
2152 if ((edx & (1u<<25)) && // SSE
2153 (edx & (1u<<26)) && // SSE2
2154 (ecx & (1u<< 0)) && // SSE3
2155 (ecx & (1u<< 9)) && // SSSE3
2156 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
2157 (ecx & (1u<<19)) && // SSE4.1
2158 (ecx & (1u<<20)) && // SSE4.2
2159 (ecx & (1u<<26)) && // XSAVE
2160 (ecx & (1u<<27)) && // OSXSAVE
2161 (ecx & (1u<<28)) && // AVX
2162 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002163
skia-autorollba6087c2019-04-09 13:57:02 +00002164 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2165 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2166 : "0"(7), "2"(0));
2167 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2168 uint32_t xcr0, dont_need_edx;
2169 __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 +00002170
skia-autorollba6087c2019-04-09 13:57:02 +00002171 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2172 (xcr0 & (1u<<2)) && // YMM register state saved?
2173 (ebx & (1u<<5))) { // AVX2
2174 // At this point we're at least HSW. Continue checking for SKX.
2175 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2176 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2177 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2178 (ebx & (1u<<16)) && // AVX512F
2179 (ebx & (1u<<17)) && // AVX512DQ
2180 (ebx & (1u<<28)) && // AVX512CD
2181 (ebx & (1u<<30)) && // AVX512BW
2182 (ebx & (1u<<31))) { // AVX512VL
2183 return CpuType::SKX;
2184 }
2185 return CpuType::HSW;
2186 }
2187 }
2188 return CpuType::None;
2189 }();
2190 return type;
2191 }
2192 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002193
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002194#endif
2195
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002196typedef struct {
2197 Op op;
2198 const void* arg;
2199} OpAndArg;
2200
2201static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autorollf6d50642019-10-14 15:31:13 +00002202 static const struct { Op sRGBish, PQish, HLGish, HLGinvish, table; } ops[] = {
2203 { Op_tf_r, Op_pq_r, Op_hlg_r, Op_hlginv_r, Op_table_r },
2204 { Op_tf_g, Op_pq_g, Op_hlg_g, Op_hlginv_g, Op_table_g },
2205 { Op_tf_b, Op_pq_b, Op_hlg_b, Op_hlginv_b, Op_table_b },
2206 { Op_tf_a, Op_pq_a, Op_hlg_a, Op_hlginv_a, Op_table_a },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002207 };
skia-autorollf6d50642019-10-14 15:31:13 +00002208 const auto& op = ops[channel];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002209
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002210 if (curve->table_entries == 0) {
skia-autorollf6d50642019-10-14 15:31:13 +00002211 const OpAndArg noop = { Op_load_a8/*doesn't matter*/, nullptr };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002212
skia-autorollf6d50642019-10-14 15:31:13 +00002213 const skcms_TransferFunction& tf = curve->parametric;
2214
2215 if (tf.g == 1 && tf.a == 1 &&
2216 tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) {
2217 return noop;
2218 }
2219
2220 switch (classify(tf)) {
2221 case Bad: return noop;
2222 case sRGBish: return OpAndArg{op.sRGBish, &tf};
2223 case PQish: return OpAndArg{op.PQish, &tf};
2224 case HLGish: return OpAndArg{op.HLGish, &tf};
2225 case HLGinvish: return OpAndArg{op.HLGinvish, &tf};
2226 }
2227 }
2228 return OpAndArg{op.table, curve};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002229}
2230
2231static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2232 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002233 case skcms_PixelFormat_A_8 >> 1: return 1;
2234 case skcms_PixelFormat_G_8 >> 1: return 1;
2235 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2236 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2237 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2238 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2239 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002240 case skcms_PixelFormat_RGBA_8888_sRGB >> 1: return 4;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002241 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2242 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2243 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2244 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2245 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002246 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2247 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002248 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2249 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2250 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2251 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002252 }
2253 assert(false);
2254 return 0;
2255}
2256
2257static bool prep_for_destination(const skcms_ICCProfile* profile,
2258 skcms_Matrix3x3* fromXYZD50,
2259 skcms_TransferFunction* invR,
2260 skcms_TransferFunction* invG,
2261 skcms_TransferFunction* invB) {
2262 // We only support destinations with parametric transfer functions
2263 // and with gamuts that can be transformed from XYZD50.
2264 return profile->has_trc
2265 && profile->has_toXYZD50
2266 && profile->trc[0].table_entries == 0
2267 && profile->trc[1].table_entries == 0
2268 && profile->trc[2].table_entries == 0
2269 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2270 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2271 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2272 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2273}
2274
2275bool skcms_Transform(const void* src,
2276 skcms_PixelFormat srcFmt,
2277 skcms_AlphaFormat srcAlpha,
2278 const skcms_ICCProfile* srcProfile,
2279 void* dst,
2280 skcms_PixelFormat dstFmt,
2281 skcms_AlphaFormat dstAlpha,
2282 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002283 size_t npixels) {
2284 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2285 dst, dstFmt, dstAlpha, dstProfile,
2286 npixels, nullptr);
2287}
2288
2289bool skcms_TransformWithPalette(const void* src,
2290 skcms_PixelFormat srcFmt,
2291 skcms_AlphaFormat srcAlpha,
2292 const skcms_ICCProfile* srcProfile,
2293 void* dst,
2294 skcms_PixelFormat dstFmt,
2295 skcms_AlphaFormat dstAlpha,
2296 const skcms_ICCProfile* dstProfile,
2297 size_t nz,
2298 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002299 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2300 src_bpp = bytes_per_pixel(srcFmt);
2301 // Let's just refuse if the request is absurdly big.
2302 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2303 return false;
2304 }
2305 int n = (int)nz;
2306
2307 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2308 if (!srcProfile) {
2309 srcProfile = skcms_sRGB_profile();
2310 }
2311 if (!dstProfile) {
2312 dstProfile = skcms_sRGB_profile();
2313 }
2314
2315 // 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 +00002316 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002317 return false;
2318 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002319 // TODO: more careful alias rejection (like, dst == src + 1)?
2320
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002321 if (needs_palette(srcFmt) && !palette) {
2322 return false;
2323 }
2324
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002325 Op program [32];
2326 const void* arguments[32];
2327
2328 Op* ops = program;
2329 const void** args = arguments;
2330
skia-autorollf6d50642019-10-14 15:31:13 +00002331 // These are always parametric curves of some sort.
2332 skcms_Curve dst_curves[3];
2333 dst_curves[0].table_entries =
2334 dst_curves[1].table_entries =
2335 dst_curves[2].table_entries = 0;
2336
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002337 skcms_Matrix3x3 from_xyz;
2338
2339 switch (srcFmt >> 1) {
2340 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002341 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2342 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2343 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2344 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2345 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2346 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2347 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2348 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2349 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2350 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2351 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002352 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2353 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 +00002354 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2355 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2356 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2357 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002358
2359 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2360 *args++ = palette;
2361 break;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002362 case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2363 *ops++ = Op_load_8888;
2364 *ops++ = Op_tf_r; *args++ = skcms_sRGB_TransferFunction();
2365 *ops++ = Op_tf_g; *args++ = skcms_sRGB_TransferFunction();
2366 *ops++ = Op_tf_b; *args++ = skcms_sRGB_TransferFunction();
2367 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002368 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002369 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2370 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2371 *ops++ = Op_clamp;
2372 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002373 if (srcFmt & 1) {
2374 *ops++ = Op_swap_rb;
2375 }
2376 skcms_ICCProfile gray_dst_profile;
2377 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2378 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2379 // luminance (Y) by the destination transfer function.
2380 gray_dst_profile = *dstProfile;
2381 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2382 dstProfile = &gray_dst_profile;
2383 }
2384
2385 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2386 // Photoshop creates CMYK images as inverse CMYK.
2387 // These happen to be the only ones we've _ever_ seen.
2388 *ops++ = Op_invert;
2389 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2390 srcAlpha = skcms_AlphaFormat_Unpremul;
2391 }
2392
2393 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2394 *ops++ = Op_force_opaque;
2395 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2396 *ops++ = Op_unpremul;
2397 }
2398
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002399 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002400
2401 if (!prep_for_destination(dstProfile,
skia-autorollf6d50642019-10-14 15:31:13 +00002402 &from_xyz,
2403 &dst_curves[0].parametric,
2404 &dst_curves[1].parametric,
2405 &dst_curves[2].parametric)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002406 return false;
2407 }
2408
2409 if (srcProfile->has_A2B) {
2410 if (srcProfile->A2B.input_channels) {
2411 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2412 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 +00002413 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002414 *ops++ = oa.op;
2415 *args++ = oa.arg;
2416 }
2417 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002418 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002419 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002420 *args++ = &srcProfile->A2B;
2421 }
2422
2423 if (srcProfile->A2B.matrix_channels == 3) {
2424 for (int i = 0; i < 3; i++) {
2425 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 +00002426 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002427 *ops++ = oa.op;
2428 *args++ = oa.arg;
2429 }
2430 }
2431
2432 static const skcms_Matrix3x4 I = {{
2433 {1,0,0,0},
2434 {0,1,0,0},
2435 {0,0,1,0},
2436 }};
2437 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2438 *ops++ = Op_matrix_3x4;
2439 *args++ = &srcProfile->A2B.matrix;
2440 }
2441 }
2442
2443 if (srcProfile->A2B.output_channels == 3) {
2444 for (int i = 0; i < 3; i++) {
2445 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 +00002446 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002447 *ops++ = oa.op;
2448 *args++ = oa.arg;
2449 }
2450 }
2451 }
2452
2453 if (srcProfile->pcs == skcms_Signature_Lab) {
2454 *ops++ = Op_lab_to_xyz;
2455 }
2456
2457 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2458 for (int i = 0; i < 3; i++) {
2459 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002460 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002461 *ops++ = oa.op;
2462 *args++ = oa.arg;
2463 }
2464 }
2465 } else {
2466 return false;
2467 }
2468
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002469 // A2B sources should already be in XYZD50 at this point.
2470 // Others still need to be transformed using their toXYZD50 matrix.
2471 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2472 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2473 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2474 static const skcms_Matrix3x3 I = {{
2475 { 1.0f, 0.0f, 0.0f },
2476 { 0.0f, 1.0f, 0.0f },
2477 { 0.0f, 0.0f, 1.0f },
2478 }};
2479 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2480
2481 // There's a chance the source and destination gamuts are identical,
2482 // in which case we can skip the gamut transform.
2483 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2484 // Concat the entire gamut transform into from_xyz,
2485 // now slightly misnamed but it's a handy spot to stash the result.
2486 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2487 *ops++ = Op_matrix_3x3;
2488 *args++ = &from_xyz;
2489 }
2490
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002491 // Encode back to dst RGB using its parametric transfer functions.
skia-autorollf6d50642019-10-14 15:31:13 +00002492 for (int i = 0; i < 3; i++) {
2493 OpAndArg oa = select_curve_op(dst_curves+i, i);
2494 if (oa.arg) {
2495 assert (oa.op != Op_table_r &&
2496 oa.op != Op_table_g &&
2497 oa.op != Op_table_b &&
2498 oa.op != Op_table_a);
2499 *ops++ = oa.op;
2500 *args++ = oa.arg;
2501 }
2502 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002503 }
2504
skia-autoroll2e4fa242019-03-11 21:14:18 +00002505 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2506 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002507 //
2508 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2509 // 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 +00002510 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2511 *ops++ = Op_clamp;
2512 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002513 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2514 *ops++ = Op_force_opaque;
2515 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2516 *ops++ = Op_premul;
2517 }
2518 if (dstFmt & 1) {
2519 *ops++ = Op_swap_rb;
2520 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002521 switch (dstFmt >> 1) {
2522 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002523 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2524 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2525 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2526 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2527 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2528 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2529 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2530 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2531 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2532 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2533 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002534 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2535 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 +00002536 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2537 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2538 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2539 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_store_ffff; break;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002540
2541 case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2542 *ops++ = Op_tf_r; *args++ = skcms_sRGB_Inverse_TransferFunction();
2543 *ops++ = Op_tf_g; *args++ = skcms_sRGB_Inverse_TransferFunction();
2544 *ops++ = Op_tf_b; *args++ = skcms_sRGB_Inverse_TransferFunction();
2545 *ops++ = Op_store_8888;
2546 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002547 }
2548
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002549 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002550#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002551 switch (cpu_type()) {
2552 case CpuType::None: break;
2553 case CpuType::HSW: run = hsw::run_program; break;
2554 case CpuType::SKX: run = hsw::run_program; break;
2555 }
2556#endif
2557#if defined(TEST_FOR_SKX)
2558 switch (cpu_type()) {
2559 case CpuType::None: break;
2560 case CpuType::HSW: break;
2561 case CpuType::SKX: run = skx::run_program; break;
2562 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002563#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002564 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 +00002565 return true;
2566}
2567
2568static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2569#if defined(NDEBUG)
2570 (void)profile;
2571#else
2572 skcms_Matrix3x3 fromXYZD50;
2573 skcms_TransferFunction invR, invG, invB;
2574 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2575#endif
2576}
2577
2578bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2579 skcms_Matrix3x3 fromXYZD50;
2580 if (!profile->has_trc || !profile->has_toXYZD50
2581 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2582 return false;
2583 }
2584
2585 skcms_TransferFunction tf[3];
2586 for (int i = 0; i < 3; i++) {
2587 skcms_TransferFunction inv;
2588 if (profile->trc[i].table_entries == 0
2589 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2590 tf[i] = profile->trc[i].parametric;
2591 continue;
2592 }
2593
2594 float max_error;
2595 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2596 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2597 return false;
2598 }
2599 }
2600
2601 for (int i = 0; i < 3; ++i) {
2602 profile->trc[i].table_entries = 0;
2603 profile->trc[i].parametric = tf[i];
2604 }
2605
2606 assert_usable_as_destination(profile);
2607 return true;
2608}
2609
2610bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2611 // Operate on a copy of profile, so we can choose the best TF for the original curves
2612 skcms_ICCProfile result = *profile;
2613 if (!skcms_MakeUsableAsDestination(&result)) {
2614 return false;
2615 }
2616
2617 int best_tf = 0;
2618 float min_max_error = INFINITY_;
2619 for (int i = 0; i < 3; i++) {
2620 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002621 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2622 return false;
2623 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002624
2625 float err = 0;
2626 for (int j = 0; j < 3; ++j) {
skia-autorolld95243b2019-11-20 19:40:25 +00002627 err = fmaxf_(err, skcms_MaxRoundtripError(&profile->trc[j], &inv));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002628 }
2629 if (min_max_error > err) {
2630 min_max_error = err;
2631 best_tf = i;
2632 }
2633 }
2634
2635 for (int i = 0; i < 3; i++) {
2636 result.trc[i].parametric = result.trc[best_tf].parametric;
2637 }
2638
2639 *profile = result;
2640 assert_usable_as_destination(profile);
2641 return true;
2642}