blob: 0489a8a4d90ffde5fd8c7618be7be1da456bf18f [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
skia-autoroll15e78d02020-05-26 15:40:45 +000035static bool runtime_cpu_detection = true;
36void skcms_DisableRuntimeCPUDetection() {
37 runtime_cpu_detection = false;
38}
39
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000040// sizeof(x) will return size_t, which is 32-bit on some machines and 64-bit on others.
41// We have better testing on 64-bit machines, so force 32-bit machines to behave like 64-bit.
42//
43// Please do not use sizeof() directly, and size_t only when required.
44// (We have no way of enforcing these requests...)
45#define SAFE_SIZEOF(x) ((uint64_t)sizeof(x))
46
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +000047// Same sort of thing for _Layout structs with a variable sized array at the end (named "variable").
48#define SAFE_FIXED_SIZE(type) ((uint64_t)offsetof(type, variable))
49
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000050static const union {
51 uint32_t bits;
52 float f;
53} inf_ = { 0x7f800000 };
54#define INFINITY_ inf_.f
55
skia-autoroll5f8588c2019-10-14 13:54:14 +000056#if defined(__clang__) || defined(__GNUC__)
57 #define small_memcpy __builtin_memcpy
58#else
59 #define small_memcpy memcpy
60#endif
61
62static float log2f_(float x) {
63 // The first approximation of log2(x) is its exponent 'e', minus 127.
64 int32_t bits;
65 small_memcpy(&bits, &x, sizeof(bits));
66
67 float e = (float)bits * (1.0f / (1<<23));
68
69 // If we use the mantissa too we can refine the error signficantly.
70 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
71 float m;
72 small_memcpy(&m, &m_bits, sizeof(m));
73
74 return (e - 124.225514990f
75 - 1.498030302f*m
76 - 1.725879990f/(0.3520887068f + m));
77}
78static float logf_(float x) {
79 const float ln2 = 0.69314718f;
80 return ln2*log2f_(x);
81}
82
83static float exp2f_(float x) {
84 float fract = x - floorf_(x);
85
86 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
87 - 1.490129070f*fract
88 + 27.728023300f/(4.84252568f - fract));
89
90 // Before we cast fbits to int32_t, check for out of range values to pacify UBSAN.
91 // INT_MAX is not exactly representable as a float, so exclude it as effectively infinite.
skia-autoroll4262f6b2020-05-19 17:48:12 +000092 // Negative values are effectively underflow - we'll end up returning a (different) negative
93 // value, which makes no sense. So clamp to zero.
skia-autoroll5f8588c2019-10-14 13:54:14 +000094 if (fbits >= (float)INT_MAX) {
95 return INFINITY_;
skia-autoroll4262f6b2020-05-19 17:48:12 +000096 } else if (fbits < 0) {
97 return 0;
skia-autoroll5f8588c2019-10-14 13:54:14 +000098 }
99
100 int32_t bits = (int32_t)fbits;
101 small_memcpy(&x, &bits, sizeof(x));
102 return x;
103}
104
105// Not static, as it's used by some test tools.
106float powf_(float x, float y) {
107 assert (x >= 0);
108 return (x == 0) || (x == 1) ? x
109 : exp2f_(log2f_(x) * y);
110}
111
112static float expf_(float x) {
skia-autoroll775981d2019-10-14 14:54:13 +0000113 const float log2_e = 1.4426950408889634074f;
114 return exp2f_(log2_e * x);
skia-autoroll5f8588c2019-10-14 13:54:14 +0000115}
116
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000117static float fmaxf_(float x, float y) { return x > y ? x : y; }
118static float fminf_(float x, float y) { return x < y ? x : y; }
119
120static bool isfinitef_(float x) { return 0 == x*0; }
121
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000122static float minus_1_ulp(float x) {
123 int32_t bits;
124 memcpy(&bits, &x, sizeof(bits));
125 bits = bits - 1;
126 memcpy(&x, &bits, sizeof(bits));
127 return x;
128}
129
skia-autorollc093cc82019-10-08 19:22:13 +0000130// Most transfer functions we work with are sRGBish.
131// For exotic HDR transfer functions, we encode them using a tf.g that makes no sense,
132// and repurpose the other fields to hold the parameters of the HDR functions.
133enum TFKind { Bad, sRGBish, PQish, HLGish, HLGinvish };
134struct TF_PQish { float A,B,C,D,E,F; };
135struct TF_HLGish { float R,G,a,b,c; };
136
137static float TFKind_marker(TFKind kind) {
138 // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM.
139 return -(float)kind;
140}
141
142static TFKind classify(const skcms_TransferFunction& tf, TF_PQish* pq = nullptr
143 , TF_HLGish* hlg = nullptr) {
144 if (tf.g < 0 && (int)tf.g == tf.g) {
145 // TODO: sanity checks for PQ/HLG like we do for sRGBish.
skia-autorolle1227722019-10-28 20:17:07 +0000146 switch ((int)tf.g) {
147 case -PQish: if (pq ) { memcpy(pq , &tf.a, sizeof(*pq )); } return PQish;
148 case -HLGish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGish;
149 case -HLGinvish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGinvish;
skia-autorollc093cc82019-10-08 19:22:13 +0000150 }
151 return Bad;
152 }
153
154 // Basic sanity checks for sRGBish transfer functions.
155 if (isfinitef_(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g)
156 // a,c,d,g should be non-negative to make any sense.
157 && tf.a >= 0
158 && tf.c >= 0
159 && tf.d >= 0
160 && tf.g >= 0
161 // Raising a negative value to a fractional tf->g produces complex numbers.
162 && tf.a * tf.d + tf.b >= 0) {
163 return sRGBish;
164 }
165
166 return Bad;
167}
168
skia-autoroll3164b442020-07-16 20:26:50 +0000169bool skcms_TransferFunction_isSRGBish(const skcms_TransferFunction* tf) {
170 return classify(*tf) == sRGBish;
171}
172bool skcms_TransferFunction_isPQish(const skcms_TransferFunction* tf) {
173 return classify(*tf) == PQish;
174}
175bool skcms_TransferFunction_isHLGish(const skcms_TransferFunction* tf) {
176 return classify(*tf) == HLGish;
177}
178
skia-autorollc093cc82019-10-08 19:22:13 +0000179bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
180 float A, float B, float C,
181 float D, float E, float F) {
182 *tf = { TFKind_marker(PQish), A,B,C,D,E,F };
skia-autoroll3164b442020-07-16 20:26:50 +0000183 assert(skcms_TransferFunction_isPQish(tf));
skia-autorollc093cc82019-10-08 19:22:13 +0000184 return true;
185}
186
skia-autoroll5f8588c2019-10-14 13:54:14 +0000187bool skcms_TransferFunction_makeHLGish(skcms_TransferFunction* tf,
188 float R, float G,
189 float a, float b, float c) {
skia-autorollf6d50642019-10-14 15:31:13 +0000190 *tf = { TFKind_marker(HLGish), R,G, a,b,c, 0 };
skia-autoroll3164b442020-07-16 20:26:50 +0000191 assert(skcms_TransferFunction_isHLGish(tf));
skia-autoroll5f8588c2019-10-14 13:54:14 +0000192 return true;
193}
194
skia-autorollc093cc82019-10-08 19:22:13 +0000195float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
196 float sign = x < 0 ? -1.0f : 1.0f;
197 x *= sign;
198
skia-autoroll5f8588c2019-10-14 13:54:14 +0000199 TF_PQish pq;
200 TF_HLGish hlg;
201 switch (classify(*tf, &pq, &hlg)) {
skia-autorollc093cc82019-10-08 19:22:13 +0000202 case Bad: break;
skia-autoroll5f8588c2019-10-14 13:54:14 +0000203
skia-autoroll5f8588c2019-10-14 13:54:14 +0000204 case HLGish: return sign * (x*hlg.R <= 1 ? powf_(x*hlg.R, hlg.G)
205 : expf_((x-hlg.c)*hlg.a) + hlg.b);
206
skia-autorollf6d50642019-10-14 15:31:13 +0000207 // skcms_TransferFunction_invert() inverts R, G, and a for HLGinvish so this math is fast.
skia-autoroll5f8588c2019-10-14 13:54:14 +0000208 case HLGinvish: return sign * (x <= 1 ? hlg.R * powf_(x, hlg.G)
209 : hlg.a * logf_(x - hlg.b) + hlg.c);
210
skia-autorollc093cc82019-10-08 19:22:13 +0000211
212 case sRGBish: return sign * (x < tf->d ? tf->c * x + tf->f
213 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
214
215 case PQish: return sign * powf_(fmaxf_(pq.A + pq.B * powf_(x, pq.C), 0)
216 / (pq.D + pq.E * powf_(x, pq.C)),
217 pq.F);
218 }
219 return 0;
220}
221
222
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000223static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000224 if (curve->table_entries == 0) {
225 return skcms_TransferFunction_eval(&curve->parametric, x);
226 }
227
228 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 +0000229 int lo = (int) ix ,
230 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000231 float t = ix - (float)lo;
232
233 float l, h;
234 if (curve->table_8) {
235 l = curve->table_8[lo] * (1/255.0f);
236 h = curve->table_8[hi] * (1/255.0f);
237 } else {
238 uint16_t be_l, be_h;
239 memcpy(&be_l, curve->table_16 + 2*lo, 2);
240 memcpy(&be_h, curve->table_16 + 2*hi, 2);
241 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
242 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
243 l = le_l * (1/65535.0f);
244 h = le_h * (1/65535.0f);
245 }
246 return l + (h-l)*t;
247}
248
skia-autorolld95243b2019-11-20 19:40:25 +0000249float 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 +0000250 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
251 const float dx = 1.0f / (N - 1);
252 float err = 0;
253 for (uint32_t i = 0; i < N; i++) {
254 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000255 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000256 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
257 }
258 return err;
259}
260
261bool skcms_AreApproximateInverses(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skia-autorolld95243b2019-11-20 19:40:25 +0000262 return skcms_MaxRoundtripError(curve, inv_tf) < (1/512.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000263}
264
265// Additional ICC signature values that are only used internally
266enum {
267 // File signature
268 skcms_Signature_acsp = 0x61637370,
269
270 // Tag signatures
271 skcms_Signature_rTRC = 0x72545243,
272 skcms_Signature_gTRC = 0x67545243,
273 skcms_Signature_bTRC = 0x62545243,
274 skcms_Signature_kTRC = 0x6B545243,
275
276 skcms_Signature_rXYZ = 0x7258595A,
277 skcms_Signature_gXYZ = 0x6758595A,
278 skcms_Signature_bXYZ = 0x6258595A,
279
280 skcms_Signature_A2B0 = 0x41324230,
281 skcms_Signature_A2B1 = 0x41324231,
282 skcms_Signature_mAB = 0x6D414220,
283
284 skcms_Signature_CHAD = 0x63686164,
skia-autoroll2565a6d2019-11-11 16:48:43 +0000285 skcms_Signature_WTPT = 0x77747074,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000286
287 // Type signatures
288 skcms_Signature_curv = 0x63757276,
289 skcms_Signature_mft1 = 0x6D667431,
290 skcms_Signature_mft2 = 0x6D667432,
291 skcms_Signature_para = 0x70617261,
292 skcms_Signature_sf32 = 0x73663332,
293 // XYZ is also a PCS signature, so it's defined in skcms.h
294 // skcms_Signature_XYZ = 0x58595A20,
295};
296
297static uint16_t read_big_u16(const uint8_t* ptr) {
298 uint16_t be;
299 memcpy(&be, ptr, sizeof(be));
300#if defined(_MSC_VER)
301 return _byteswap_ushort(be);
302#else
303 return __builtin_bswap16(be);
304#endif
305}
306
307static uint32_t read_big_u32(const uint8_t* ptr) {
308 uint32_t be;
309 memcpy(&be, ptr, sizeof(be));
310#if defined(_MSC_VER)
311 return _byteswap_ulong(be);
312#else
313 return __builtin_bswap32(be);
314#endif
315}
316
317static int32_t read_big_i32(const uint8_t* ptr) {
318 return (int32_t)read_big_u32(ptr);
319}
320
321static float read_big_fixed(const uint8_t* ptr) {
322 return read_big_i32(ptr) * (1.0f / 65536.0f);
323}
324
325// Maps to an in-memory profile so that fields line up to the locations specified
326// in ICC.1:2010, section 7.2
327typedef struct {
328 uint8_t size [ 4];
329 uint8_t cmm_type [ 4];
330 uint8_t version [ 4];
331 uint8_t profile_class [ 4];
332 uint8_t data_color_space [ 4];
333 uint8_t pcs [ 4];
334 uint8_t creation_date_time [12];
335 uint8_t signature [ 4];
336 uint8_t platform [ 4];
337 uint8_t flags [ 4];
338 uint8_t device_manufacturer [ 4];
339 uint8_t device_model [ 4];
340 uint8_t device_attributes [ 8];
341 uint8_t rendering_intent [ 4];
342 uint8_t illuminant_X [ 4];
343 uint8_t illuminant_Y [ 4];
344 uint8_t illuminant_Z [ 4];
345 uint8_t creator [ 4];
346 uint8_t profile_id [16];
347 uint8_t reserved [28];
348 uint8_t tag_count [ 4]; // Technically not part of header, but required
349} header_Layout;
350
351typedef struct {
352 uint8_t signature [4];
353 uint8_t offset [4];
354 uint8_t size [4];
355} tag_Layout;
356
357static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
358 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
359}
360
361// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
362// use of the type is for the CHAD tag that stores exactly nine values.
363typedef struct {
364 uint8_t type [ 4];
365 uint8_t reserved [ 4];
366 uint8_t values [36];
367} sf32_Layout;
368
369bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
370 skcms_ICCTag tag;
371 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
372 return false;
373 }
374
375 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
376 return false;
377 }
378
379 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
380 const uint8_t* values = sf32Tag->values;
381 for (int r = 0; r < 3; ++r)
382 for (int c = 0; c < 3; ++c, values += 4) {
383 m->vals[r][c] = read_big_fixed(values);
384 }
385 return true;
386}
387
388// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
389// the type are for tags/data that store exactly one triple.
390typedef struct {
391 uint8_t type [4];
392 uint8_t reserved [4];
393 uint8_t X [4];
394 uint8_t Y [4];
395 uint8_t Z [4];
396} XYZ_Layout;
397
398static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
399 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
400 return false;
401 }
402
403 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
404
405 *x = read_big_fixed(xyzTag->X);
406 *y = read_big_fixed(xyzTag->Y);
407 *z = read_big_fixed(xyzTag->Z);
408 return true;
409}
410
skia-autoroll2565a6d2019-11-11 16:48:43 +0000411bool skcms_GetWTPT(const skcms_ICCProfile* profile, float xyz[3]) {
412 skcms_ICCTag tag;
413 return skcms_GetTagBySignature(profile, skcms_Signature_WTPT, &tag) &&
414 read_tag_xyz(&tag, &xyz[0], &xyz[1], &xyz[2]);
415}
416
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000417static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
418 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
419 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
420 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
421 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
422}
423
424typedef struct {
425 uint8_t type [4];
426 uint8_t reserved_a [4];
427 uint8_t function_type [2];
428 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000429 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 +0000430} para_Layout;
431
432static bool read_curve_para(const uint8_t* buf, uint32_t size,
433 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000434 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000435 return false;
436 }
437
438 const para_Layout* paraTag = (const para_Layout*)buf;
439
440 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
441 uint16_t function_type = read_big_u16(paraTag->function_type);
442 if (function_type > kGABCDEF) {
443 return false;
444 }
445
446 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 +0000447 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 +0000448 return false;
449 }
450
451 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000452 *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 +0000453 }
454
455 curve->table_entries = 0;
456 curve->parametric.a = 1.0f;
457 curve->parametric.b = 0.0f;
458 curve->parametric.c = 0.0f;
459 curve->parametric.d = 0.0f;
460 curve->parametric.e = 0.0f;
461 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000462 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000463
464 switch (function_type) {
465 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000466 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
467 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000468 if (curve->parametric.a == 0) {
469 return false;
470 }
471 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
472 break;
473 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000474 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
475 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
476 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000477 if (curve->parametric.a == 0) {
478 return false;
479 }
480 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
481 curve->parametric.f = curve->parametric.e;
482 break;
483 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000484 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
485 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
486 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
487 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000488 break;
489 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000490 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
491 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
492 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
493 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
494 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
495 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000496 break;
497 }
skia-autoroll3164b442020-07-16 20:26:50 +0000498 return skcms_TransferFunction_isSRGBish(&curve->parametric);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000499}
500
501typedef struct {
502 uint8_t type [4];
503 uint8_t reserved [4];
504 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000505 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 +0000506} curv_Layout;
507
508static bool read_curve_curv(const uint8_t* buf, uint32_t size,
509 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000510 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000511 return false;
512 }
513
514 const curv_Layout* curvTag = (const curv_Layout*)buf;
515
516 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 +0000517 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 +0000518 return false;
519 }
520
521 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000522 *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 +0000523 }
524
525 if (value_count < 2) {
526 curve->table_entries = 0;
527 curve->parametric.a = 1.0f;
528 curve->parametric.b = 0.0f;
529 curve->parametric.c = 0.0f;
530 curve->parametric.d = 0.0f;
531 curve->parametric.e = 0.0f;
532 curve->parametric.f = 0.0f;
533 if (value_count == 0) {
534 // Empty tables are a shorthand for an identity curve
535 curve->parametric.g = 1.0f;
536 } else {
537 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000538 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 +0000539 }
540 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000541 curve->table_8 = nullptr;
542 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000543 curve->table_entries = value_count;
544 }
545
546 return true;
547}
548
549// 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 +0000550// 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 +0000551static bool read_curve(const uint8_t* buf, uint32_t size,
552 skcms_Curve* curve, uint32_t* curve_size) {
553 if (!buf || size < 4 || !curve) {
554 return false;
555 }
556
557 uint32_t type = read_big_u32(buf);
558 if (type == skcms_Signature_para) {
559 return read_curve_para(buf, size, curve, curve_size);
560 } else if (type == skcms_Signature_curv) {
561 return read_curve_curv(buf, size, curve, curve_size);
562 }
563
564 return false;
565}
566
567// mft1 and mft2 share a large chunk of data
568typedef struct {
569 uint8_t type [ 4];
570 uint8_t reserved_a [ 4];
571 uint8_t input_channels [ 1];
572 uint8_t output_channels [ 1];
573 uint8_t grid_points [ 1];
574 uint8_t reserved_b [ 1];
575 uint8_t matrix [36];
576} mft_CommonLayout;
577
578typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000579 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000580
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000581 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000582} mft1_Layout;
583
584typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000585 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000586
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000587 uint8_t input_table_entries [2];
588 uint8_t output_table_entries [2];
589 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000590} mft2_Layout;
591
592static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
593 // MFT matrices are applied before the first set of curves, but must be identity unless the
594 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
595 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
596 // field/flag.
597 a2b->matrix_channels = 0;
598
599 a2b->input_channels = mftTag->input_channels[0];
600 a2b->output_channels = mftTag->output_channels[0];
601
602 // We require exactly three (ie XYZ/Lab/RGB) output channels
603 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
604 return false;
605 }
606 // We require at least one, and no more than four (ie CMYK) input channels
607 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
608 return false;
609 }
610
611 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
612 a2b->grid_points[i] = mftTag->grid_points[0];
613 }
614 // The grid only makes sense with at least two points along each axis
615 if (a2b->grid_points[0] < 2) {
616 return false;
617 }
618
619 return true;
620}
621
622static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
623 uint32_t input_table_entries, uint32_t output_table_entries,
624 skcms_A2B* a2b) {
625 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
626 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
627 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
628
629 // [input|output]_channels are <= 4, so still no overflow
630 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
631 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
632
633 uint64_t grid_size = a2b->output_channels * byte_width;
634 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
635 grid_size *= a2b->grid_points[axis];
636 }
637
638 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
639 return false;
640 }
641
642 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
643 a2b->input_curves[i].table_entries = input_table_entries;
644 if (byte_width == 1) {
645 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 +0000646 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000647 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000648 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000649 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
650 }
651 }
652
653 if (byte_width == 1) {
654 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 +0000655 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000656 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000657 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000658 a2b->grid_16 = table_base + byte_len_all_input_tables;
659 }
660
661 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
662 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
663 a2b->output_curves[i].table_entries = output_table_entries;
664 if (byte_width == 1) {
665 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 +0000666 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000667 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000668 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000669 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
670 }
671 }
672
673 return true;
674}
675
676static 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 +0000677 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000678 return false;
679 }
680
681 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
682 if (!read_mft_common(mftTag->common, a2b)) {
683 return false;
684 }
685
686 uint32_t input_table_entries = 256;
687 uint32_t output_table_entries = 256;
688
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000689 return init_a2b_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000690 input_table_entries, output_table_entries, a2b);
691}
692
693static bool read_tag_mft2(const skcms_ICCTag* tag, skcms_A2B* a2b) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000694 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000695 return false;
696 }
697
698 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
699 if (!read_mft_common(mftTag->common, a2b)) {
700 return false;
701 }
702
703 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
704 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
705
706 // ICC spec mandates that 2 <= table_entries <= 4096
707 if (input_table_entries < 2 || input_table_entries > 4096 ||
708 output_table_entries < 2 || output_table_entries > 4096) {
709 return false;
710 }
711
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000712 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 +0000713 input_table_entries, output_table_entries, a2b);
714}
715
716static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
717 uint32_t num_curves, skcms_Curve* curves) {
718 for (uint32_t i = 0; i < num_curves; ++i) {
719 if (curve_offset > size) {
720 return false;
721 }
722
723 uint32_t curve_bytes;
724 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
725 return false;
726 }
727
728 if (curve_bytes > UINT32_MAX - 3) {
729 return false;
730 }
731 curve_bytes = (curve_bytes + 3) & ~3U;
732
733 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
734 curve_offset = (uint32_t)new_offset_64;
735 if (new_offset_64 != curve_offset) {
736 return false;
737 }
738 }
739
740 return true;
741}
742
743typedef struct {
744 uint8_t type [ 4];
745 uint8_t reserved_a [ 4];
746 uint8_t input_channels [ 1];
747 uint8_t output_channels [ 1];
748 uint8_t reserved_b [ 2];
749 uint8_t b_curve_offset [ 4];
750 uint8_t matrix_offset [ 4];
751 uint8_t m_curve_offset [ 4];
752 uint8_t clut_offset [ 4];
753 uint8_t a_curve_offset [ 4];
754} mAB_Layout;
755
756typedef struct {
757 uint8_t grid_points [16];
758 uint8_t grid_byte_width [ 1];
759 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000760 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000761} mABCLUT_Layout;
762
763static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
764 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
765 return false;
766 }
767
768 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
769
770 a2b->input_channels = mABTag->input_channels[0];
771 a2b->output_channels = mABTag->output_channels[0];
772
773 // We require exactly three (ie XYZ/Lab/RGB) output channels
774 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
775 return false;
776 }
777 // We require no more than four (ie CMYK) input channels
778 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
779 return false;
780 }
781
782 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
783 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
784 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
785 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
786 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
787
788 // "B" curves must be present
789 if (0 == b_curve_offset) {
790 return false;
791 }
792
793 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
794 a2b->output_curves)) {
795 return false;
796 }
797
798 // "M" curves and Matrix must be used together
799 if (0 != m_curve_offset) {
800 if (0 == matrix_offset) {
801 return false;
802 }
803 a2b->matrix_channels = a2b->output_channels;
804 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
805 a2b->matrix_curves)) {
806 return false;
807 }
808
809 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
810 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
811 return false;
812 }
813 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
814 const uint8_t* mtx_buf = tag->buf + matrix_offset;
815 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
816 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
817 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
818 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
819 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
820 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
821 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
822 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
823 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
824 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
825 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
826 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
827 } else {
828 if (0 != matrix_offset) {
829 return false;
830 }
831 a2b->matrix_channels = 0;
832 }
833
834 // "A" curves and CLUT must be used together
835 if (0 != a_curve_offset) {
836 if (0 == clut_offset) {
837 return false;
838 }
839 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
840 a2b->input_curves)) {
841 return false;
842 }
843
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000844 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 +0000845 return false;
846 }
847 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
848
849 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000850 a2b->grid_8 = clut->variable;
851 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000852 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000853 a2b->grid_8 = nullptr;
854 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000855 } else {
856 return false;
857 }
858
859 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
860 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
861 a2b->grid_points[i] = clut->grid_points[i];
862 // The grid only makes sense with at least two points along each axis
863 if (a2b->grid_points[i] < 2) {
864 return false;
865 }
866 grid_size *= a2b->grid_points[i];
867 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000868 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 +0000869 return false;
870 }
871 } else {
872 if (0 != clut_offset) {
873 return false;
874 }
875
876 // If there is no CLUT, the number of input and output channels must match
877 if (a2b->input_channels != a2b->output_channels) {
878 return false;
879 }
880
881 // Zero out the number of input channels to signal that we're skipping this stage
882 a2b->input_channels = 0;
883 }
884
885 return true;
886}
887
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000888// If you pass f, we'll fit a possibly-non-zero value for *f.
889// If you pass nullptr, we'll assume you want *f to be treated as zero.
890static int fit_linear(const skcms_Curve* curve, int N, float tol,
891 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000892 assert(N > 1);
893 // We iteratively fit the first points to the TF's linear piece.
894 // We want the cx + f line to pass through the first and last points we fit exactly.
895 //
896 // As we walk along the points we find the minimum and maximum slope of the line before the
897 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
898 // emtpy, when we definitely can't add any more points.
899 //
900 // Some points' error intervals may intersect the running interval but not lie fully
901 // within it. So we keep track of the last point we saw that is a valid end point candidate,
902 // and once the search is done, back up to build the line through *that* point.
903 const float dx = 1.0f / (N - 1);
904
905 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000906
907 float f_zero = 0.0f;
908 if (f) {
909 *f = eval_curve(curve, 0);
910 } else {
911 f = &f_zero;
912 }
913
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000914
915 float slope_min = -INFINITY_;
916 float slope_max = +INFINITY_;
917 for (int i = 1; i < N; ++i) {
918 float x = i * dx;
919 float y = eval_curve(curve, x);
920
921 float slope_max_i = (y + tol - *f) / x,
922 slope_min_i = (y - tol - *f) / x;
923 if (slope_max_i < slope_min || slope_max < slope_min_i) {
924 // Slope intervals would no longer overlap.
925 break;
926 }
927 slope_max = fminf_(slope_max, slope_max_i);
928 slope_min = fmaxf_(slope_min, slope_min_i);
929
930 float cur_slope = (y - *f) / x;
931 if (slope_min <= cur_slope && cur_slope <= slope_max) {
932 lin_points = i + 1;
933 *c = cur_slope;
934 }
935 }
936
937 // Set D to the last point that met our tolerance.
938 *d = (lin_points - 1) * dx;
939 return lin_points;
940}
941
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000942static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
943 bool ok = false;
944 if (tag->type == skcms_Signature_mft1) {
945 ok = read_tag_mft1(tag, a2b);
946 } else if (tag->type == skcms_Signature_mft2) {
947 ok = read_tag_mft2(tag, a2b);
948 } else if (tag->type == skcms_Signature_mAB) {
949 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
950 }
951 if (!ok) {
952 return false;
953 }
954
955 // Detect and canonicalize identity tables.
956 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000957 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
958 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
959 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
960 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
961 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
962 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
963 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
964 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
965 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
966 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000967 };
968
969 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
970 skcms_Curve* curve = curves[i];
971
972 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
973 int N = (int)curve->table_entries;
974
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000975 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 +0000976 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 +0000977 && c == 1.0f
978 && f == 0.0f) {
979 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000980 curve->table_8 = nullptr;
981 curve->table_16 = nullptr;
982 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 +0000983 }
984 }
985 }
986
987 return true;
988}
989
990void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
991 if (!profile || !profile->buffer || !tag) { return; }
992 if (idx > profile->tag_count) { return; }
993 const tag_Layout* tags = get_tag_table(profile);
994 tag->signature = read_big_u32(tags[idx].signature);
995 tag->size = read_big_u32(tags[idx].size);
996 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
997 tag->type = read_big_u32(tag->buf);
998}
999
1000bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
1001 if (!profile || !profile->buffer || !tag) { return false; }
1002 const tag_Layout* tags = get_tag_table(profile);
1003 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1004 if (read_big_u32(tags[i].signature) == sig) {
1005 tag->signature = sig;
1006 tag->size = read_big_u32(tags[i].size);
1007 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
1008 tag->type = read_big_u32(tag->buf);
1009 return true;
1010 }
1011 }
1012 return false;
1013}
1014
1015static bool usable_as_src(const skcms_ICCProfile* profile) {
1016 return profile->has_A2B
1017 || (profile->has_trc && profile->has_toXYZD50);
1018}
1019
1020bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
1021 assert(SAFE_SIZEOF(header_Layout) == 132);
1022
1023 if (!profile) {
1024 return false;
1025 }
1026 memset(profile, 0, SAFE_SIZEOF(*profile));
1027
1028 if (len < SAFE_SIZEOF(header_Layout)) {
1029 return false;
1030 }
1031
1032 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001033 const header_Layout* header = (const header_Layout*)buf;
1034 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001035 profile->size = read_big_u32(header->size);
1036 uint32_t version = read_big_u32(header->version);
1037 profile->data_color_space = read_big_u32(header->data_color_space);
1038 profile->pcs = read_big_u32(header->pcs);
1039 uint32_t signature = read_big_u32(header->signature);
1040 float illuminant_X = read_big_fixed(header->illuminant_X);
1041 float illuminant_Y = read_big_fixed(header->illuminant_Y);
1042 float illuminant_Z = read_big_fixed(header->illuminant_Z);
1043 profile->tag_count = read_big_u32(header->tag_count);
1044
1045 // Validate signature, size (smaller than buffer, large enough to hold tag table),
1046 // and major version
1047 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1048 if (signature != skcms_Signature_acsp ||
1049 profile->size > len ||
1050 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1051 (version >> 24) > 4) {
1052 return false;
1053 }
1054
1055 // Validate that illuminant is D50 white
1056 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1057 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1058 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1059 return false;
1060 }
1061
1062 // Validate that all tag entries have sane offset + size
1063 const tag_Layout* tags = get_tag_table(profile);
1064 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1065 uint32_t tag_offset = read_big_u32(tags[i].offset);
1066 uint32_t tag_size = read_big_u32(tags[i].size);
1067 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
1068 if (tag_size < 4 || tag_end > profile->size) {
1069 return false;
1070 }
1071 }
1072
1073 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1074 return false;
1075 }
1076
1077 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1078
1079 // Pre-parse commonly used tags.
1080 skcms_ICCTag kTRC;
1081 if (profile->data_color_space == skcms_Signature_Gray &&
1082 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001083 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 +00001084 // Malformed tag
1085 return false;
1086 }
1087 profile->trc[1] = profile->trc[0];
1088 profile->trc[2] = profile->trc[0];
1089 profile->has_trc = true;
1090
1091 if (pcs_is_xyz) {
1092 profile->toXYZD50.vals[0][0] = illuminant_X;
1093 profile->toXYZD50.vals[1][1] = illuminant_Y;
1094 profile->toXYZD50.vals[2][2] = illuminant_Z;
1095 profile->has_toXYZD50 = true;
1096 }
1097 } else {
1098 skcms_ICCTag rTRC, gTRC, bTRC;
1099 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1100 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1101 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001102 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1103 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1104 !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 +00001105 // Malformed TRC tags
1106 return false;
1107 }
1108 profile->has_trc = true;
1109 }
1110
1111 skcms_ICCTag rXYZ, gXYZ, bXYZ;
1112 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1113 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1114 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1115 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1116 // Malformed XYZ tags
1117 return false;
1118 }
1119 profile->has_toXYZD50 = true;
1120 }
1121 }
1122
1123 skcms_ICCTag a2b_tag;
1124
1125 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
1126 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
1127 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
1128 // and all our known users are thinking exclusively in terms of relative colormetric.
1129 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
1130 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
1131 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
1132 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
1133 // Malformed A2B tag
1134 return false;
1135 }
1136 profile->has_A2B = true;
1137 break;
1138 }
1139 }
1140
1141 return usable_as_src(profile);
1142}
1143
1144
1145const skcms_ICCProfile* skcms_sRGB_profile() {
1146 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001147 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001148
1149 0, // size, moot here
1150 skcms_Signature_RGB, // data_color_space
1151 skcms_Signature_XYZ, // pcs
1152 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001153
1154 // We choose to represent sRGB with its canonical transfer function,
1155 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001156 true, // has_trc, followed by the 3 trc curves
1157 {
1158 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1159 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1160 {{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 +00001161 },
1162
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001163 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1164 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001165 { 0.436065674f, 0.385147095f, 0.143066406f },
1166 { 0.222488403f, 0.716873169f, 0.060607910f },
1167 { 0.013916016f, 0.097076416f, 0.714096069f },
1168 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001169
1170 false, // has_A2B, followed by a2b itself which we don't care about.
1171 {
1172 0,
1173 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001174 {{0, {0,0, 0,0,0,0,0}}},
1175 {{0, {0,0, 0,0,0,0,0}}},
1176 {{0, {0,0, 0,0,0,0,0}}},
1177 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001178 },
1179 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001180 nullptr,
1181 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001182
1183 0,
1184 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001185 {{0, {0,0, 0,0,0,0,0}}},
1186 {{0, {0,0, 0,0,0,0,0}}},
1187 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001188 },
1189 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001190 { 0,0,0,0 },
1191 { 0,0,0,0 },
1192 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001193 }},
1194
1195 0,
1196 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001197 {{0, {0,0, 0,0,0,0,0}}},
1198 {{0, {0,0, 0,0,0,0,0}}},
1199 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001200 },
1201 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001202 };
1203 return &sRGB_profile;
1204}
1205
1206const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001207 // 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 +00001208 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001209 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001210
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001211 0, // size, moot here
1212 skcms_Signature_RGB, // data_color_space
1213 skcms_Signature_XYZ, // pcs
1214 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001215
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001216 true, // has_trc, followed by the 3 trc curves
1217 {
1218 {{0, {1,1, 0,0,0,0,0}}},
1219 {{0, {1,1, 0,0,0,0,0}}},
1220 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001221 },
1222
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001223 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1224 {{
1225 { 1,0,0 },
1226 { 0,1,0 },
1227 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001228 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001229
1230 false, // has_A2B, followed by a2b itself which we don't care about.
1231 {
1232 0,
1233 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001234 {{0, {0,0, 0,0,0,0,0}}},
1235 {{0, {0,0, 0,0,0,0,0}}},
1236 {{0, {0,0, 0,0,0,0,0}}},
1237 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001238 },
1239 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001240 nullptr,
1241 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001242
1243 0,
1244 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001245 {{0, {0,0, 0,0,0,0,0}}},
1246 {{0, {0,0, 0,0,0,0,0}}},
1247 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001248 },
1249 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001250 { 0,0,0,0 },
1251 { 0,0,0,0 },
1252 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001253 }},
1254
1255 0,
1256 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001257 {{0, {0,0, 0,0,0,0,0}}},
1258 {{0, {0,0, 0,0,0,0,0}}},
1259 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001260 },
1261 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001262 };
1263
1264 return &XYZD50_profile;
1265}
1266
1267const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1268 return &skcms_sRGB_profile()->trc[0].parametric;
1269}
1270
1271const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1272 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001273 {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 +00001274 return &sRGB_inv;
1275}
1276
1277const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1278 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1279 return &identity;
1280}
1281
1282const uint8_t skcms_252_random_bytes[] = {
1283 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1284 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1285 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1286 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1287 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1288 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1289 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1290 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1291 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1292 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1293 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1294 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1295 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1296 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1297 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1298 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1299};
1300
1301bool 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 +00001302 // Test for exactly equal profiles first.
1303 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1304 return true;
1305 }
1306
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001307 // For now this is the essentially the same strategy we use in test_only.c
1308 // for our skcms_Transform() smoke tests:
1309 // 1) transform A to XYZD50
1310 // 2) transform B to XYZD50
1311 // 3) return true if they're similar enough
1312 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1313
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001314 // 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 +00001315 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1316
1317 if (A->data_color_space != B->data_color_space) {
1318 return false;
1319 }
1320
1321 // 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 +00001322 // 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 +00001323 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1324 size_t npixels = 84;
1325 if (A->data_color_space == skcms_Signature_CMYK) {
1326 fmt = skcms_PixelFormat_RGBA_8888;
1327 npixels = 63;
1328 }
1329
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001330 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1331 // 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 +00001332 uint8_t dstA[252],
1333 dstB[252];
1334 if (!skcms_Transform(
1335 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1336 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1337 npixels)) {
1338 return false;
1339 }
1340 if (!skcms_Transform(
1341 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1342 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1343 npixels)) {
1344 return false;
1345 }
1346
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001347 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001348 for (size_t i = 0; i < 252; i++) {
1349 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1350 return false;
1351 }
1352 }
1353 return true;
1354}
1355
1356bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1357 const skcms_TransferFunction* inv_tf) {
1358 if (!profile || !profile->has_trc) {
1359 return false;
1360 }
1361
1362 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1363 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1364 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1365}
1366
1367static bool is_zero_to_one(float x) {
1368 return 0 <= x && x <= 1;
1369}
1370
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001371typedef struct { float vals[3]; } skcms_Vector3;
1372
1373static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1374 skcms_Vector3 dst = {{0,0,0}};
1375 for (int row = 0; row < 3; ++row) {
1376 dst.vals[row] = m->vals[row][0] * v->vals[0]
1377 + m->vals[row][1] * v->vals[1]
1378 + m->vals[row][2] * v->vals[2];
1379 }
1380 return dst;
1381}
1382
skia-autoroll70b6b5e2019-12-11 18:40:05 +00001383bool skcms_AdaptToXYZD50(float wx, float wy,
1384 skcms_Matrix3x3* toXYZD50) {
1385 if (!is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1386 !toXYZD50) {
1387 return false;
1388 }
1389
1390 // Assumes that Y is 1.0f.
1391 skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
1392
1393 // Now convert toXYZ matrix to toXYZD50.
1394 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1395
1396 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1397 // the matrices below. The Bradford method is used by Adobe and is widely considered
1398 // to be the best.
1399 skcms_Matrix3x3 xyz_to_lms = {{
1400 { 0.8951f, 0.2664f, -0.1614f },
1401 { -0.7502f, 1.7135f, 0.0367f },
1402 { 0.0389f, -0.0685f, 1.0296f },
1403 }};
1404 skcms_Matrix3x3 lms_to_xyz = {{
1405 { 0.9869929f, -0.1470543f, 0.1599627f },
1406 { 0.4323053f, 0.5183603f, 0.0492912f },
1407 { -0.0085287f, 0.0400428f, 0.9684867f },
1408 }};
1409
1410 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1411 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
1412
1413 *toXYZD50 = {{
1414 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1415 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1416 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1417 }};
1418 *toXYZD50 = skcms_Matrix3x3_concat(toXYZD50, &xyz_to_lms);
1419 *toXYZD50 = skcms_Matrix3x3_concat(&lms_to_xyz, toXYZD50);
1420
1421 return true;
1422}
1423
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001424bool skcms_PrimariesToXYZD50(float rx, float ry,
1425 float gx, float gy,
1426 float bx, float by,
1427 float wx, float wy,
1428 skcms_Matrix3x3* toXYZD50) {
1429 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1430 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1431 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1432 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1433 !toXYZD50) {
1434 return false;
1435 }
1436
1437 // First, we need to convert xy values (primaries) to XYZ.
1438 skcms_Matrix3x3 primaries = {{
1439 { rx, gx, bx },
1440 { ry, gy, by },
1441 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1442 }};
1443 skcms_Matrix3x3 primaries_inv;
1444 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1445 return false;
1446 }
1447
1448 // Assumes that Y is 1.0f.
1449 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 +00001450 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001451
1452 skcms_Matrix3x3 toXYZ = {{
1453 { XYZ.vals[0], 0, 0 },
1454 { 0, XYZ.vals[1], 0 },
1455 { 0, 0, XYZ.vals[2] },
1456 }};
1457 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1458
skia-autoroll70b6b5e2019-12-11 18:40:05 +00001459 skcms_Matrix3x3 DXtoD50;
1460 if (!skcms_AdaptToXYZD50(wx, wy, &DXtoD50)) {
1461 return false;
1462 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001463
1464 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1465 return true;
1466}
1467
1468
1469bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1470 double a00 = src->vals[0][0],
1471 a01 = src->vals[1][0],
1472 a02 = src->vals[2][0],
1473 a10 = src->vals[0][1],
1474 a11 = src->vals[1][1],
1475 a12 = src->vals[2][1],
1476 a20 = src->vals[0][2],
1477 a21 = src->vals[1][2],
1478 a22 = src->vals[2][2];
1479
1480 double b0 = a00*a11 - a01*a10,
1481 b1 = a00*a12 - a02*a10,
1482 b2 = a01*a12 - a02*a11,
1483 b3 = a20,
1484 b4 = a21,
1485 b5 = a22;
1486
1487 double determinant = b0*b5
1488 - b1*b4
1489 + b2*b3;
1490
1491 if (determinant == 0) {
1492 return false;
1493 }
1494
1495 double invdet = 1.0 / determinant;
1496 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1497 return false;
1498 }
1499
1500 b0 *= invdet;
1501 b1 *= invdet;
1502 b2 *= invdet;
1503 b3 *= invdet;
1504 b4 *= invdet;
1505 b5 *= invdet;
1506
1507 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1508 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1509 dst->vals[2][0] = (float)( + b2 );
1510 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1511 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1512 dst->vals[2][1] = (float)( - b1 );
1513 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1514 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1515 dst->vals[2][2] = (float)( + b0 );
1516
1517 for (int r = 0; r < 3; ++r)
1518 for (int c = 0; c < 3; ++c) {
1519 if (!isfinitef_(dst->vals[r][c])) {
1520 return false;
1521 }
1522 }
1523 return true;
1524}
1525
1526skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1527 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1528 for (int r = 0; r < 3; r++)
1529 for (int c = 0; c < 3; c++) {
1530 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1531 + A->vals[r][1] * B->vals[1][c]
1532 + A->vals[r][2] * B->vals[2][c];
1533 }
1534 return m;
1535}
1536
skia-autorollacd6e012019-01-08 14:10:52 +00001537#if defined(__clang__)
skia-autorollf6d50642019-10-14 15:31:13 +00001538 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by classify() on the way out.
skia-autorollacd6e012019-01-08 14:10:52 +00001539#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001540bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
skia-autorollf6d50642019-10-14 15:31:13 +00001541 TF_PQish pq;
1542 TF_HLGish hlg;
1543 switch (classify(*src, &pq, &hlg)) {
1544 case Bad: return false;
1545 case sRGBish: break; // handled below
1546
1547 case PQish:
1548 *dst = { TFKind_marker(PQish), -pq.A, pq.D, 1.0f/pq.F
1549 , pq.B, -pq.E, 1.0f/pq.C};
1550 return true;
1551
1552 case HLGish:
1553 *dst = { TFKind_marker(HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
1554 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1555 return true;
1556
1557 case HLGinvish:
1558 *dst = { TFKind_marker(HLGish), 1.0f/hlg.R, 1.0f/hlg.G
1559 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1560 return true;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001561 }
1562
skia-autorollf6d50642019-10-14 15:31:13 +00001563 assert (classify(*src) == sRGBish);
1564
skia-autorolld0b577f2019-01-07 19:46:57 +00001565 // We're inverting this function, solving for x in terms of y.
1566 // y = (cx + f) x < d
1567 // (ax + b)^g + e x ≥ d
1568 // The inverse of this function can be expressed in the same piecewise form.
1569 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 +00001570
skia-autorolld0b577f2019-01-07 19:46:57 +00001571 // We'll start by finding the new threshold inv.d.
1572 // In principle we should be able to find that by solving for y at x=d from either side.
1573 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1574 float d_l = src->c * src->d + src->f,
1575 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1576 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001577 return false;
1578 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001579 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 +00001580
skia-autorolld0b577f2019-01-07 19:46:57 +00001581 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1582 if (inv.d > 0) {
1583 // Inverting the linear section is pretty straightfoward:
1584 // y = cx + f
1585 // y - f = cx
1586 // (1/c)y - f/c = x
1587 inv.c = 1.0f/src->c;
1588 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001589 }
1590
skia-autorolld0b577f2019-01-07 19:46:57 +00001591 // The interesting part is inverting the nonlinear section:
1592 // y = (ax + b)^g + e.
1593 // y - e = (ax + b)^g
1594 // (y - e)^1/g = ax + b
1595 // (y - e)^1/g - b = ax
1596 // (1/a)(y - e)^1/g - b/a = x
1597 //
1598 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1599 // let k = (1/a)^g
1600 // (1/a)( y - e)^1/g - b/a = x
1601 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001602
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001603 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001604 inv.g = 1.0f / src->g;
1605 inv.a = k;
1606 inv.b = -k * src->e;
1607 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001608
skia-autorollfe16a332019-08-20 19:44:54 +00001609 // We need to enforce the same constraints here that we do when fitting a curve,
skia-autorollf6d50642019-10-14 15:31:13 +00001610 // a >= 0 and ad+b >= 0. These constraints are checked by classify(), so they're true
skia-autorollbf388232019-08-21 14:17:54 +00001611 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001612
skia-autorollbf388232019-08-21 14:17:54 +00001613 // Just like when fitting the curve, there's really no way to rescue a < 0.
1614 if (inv.a < 0) {
1615 return false;
1616 }
1617 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001618 if (inv.a * inv.d + inv.b < 0) {
1619 inv.b = -inv.a * inv.d;
1620 }
skia-autorollbf388232019-08-21 14:17:54 +00001621
skia-autorollf6d50642019-10-14 15:31:13 +00001622 // That should usually make classify(inv) == sRGBish true, but there are a couple situations
skia-autorolla0ed0702019-08-23 16:49:54 +00001623 // where we might still fail here, like non-finite parameter values.
skia-autorollf6d50642019-10-14 15:31:13 +00001624 if (classify(inv) != sRGBish) {
skia-autorolla0ed0702019-08-23 16:49:54 +00001625 return false;
1626 }
1627
skia-autorollbf388232019-08-21 14:17:54 +00001628 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001629 assert (inv.a * inv.d + inv.b >= 0);
1630
skia-autorolla7b28742019-01-09 18:35:46 +00001631 // Now in principle we're done.
skia-autorollad10df62019-08-21 15:14:54 +00001632 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
1633 // e or f of the inverse, depending on which segment contains src(1.0f).
1634 float s = skcms_TransferFunction_eval(src, 1.0f);
skia-autorolla0ed0702019-08-23 16:49:54 +00001635 if (!isfinitef_(s)) {
1636 return false;
1637 }
1638
skia-autorollad10df62019-08-21 15:14:54 +00001639 float sign = s < 0 ? -1.0f : 1.0f;
1640 s *= sign;
1641 if (s < inv.d) {
1642 inv.f = 1.0f - sign * inv.c * s;
1643 } else {
1644 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
1645 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001646
skia-autorolld0b577f2019-01-07 19:46:57 +00001647 *dst = inv;
skia-autorollf6d50642019-10-14 15:31:13 +00001648 return classify(*dst) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001649}
1650
1651// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1652
1653// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1654//
1655// tf(x) = cx + f x < d
1656// tf(x) = (ax + b)^g + e x ≥ d
1657//
1658// When fitting, we add the additional constraint that both pieces meet at d:
1659//
1660// cd + f = (ad + b)^g + e
1661//
1662// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1663//
1664// tf(x) = cx + f x < d
1665// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1666//
1667// Our overall strategy is then:
1668// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001669// - 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 +00001670// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001671// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1672// (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 +00001673// Return the parameters with least maximum error.
1674//
1675// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1676// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1677//
1678// let y = Table(x)
1679// r(x) = x - f_inv(y)
1680//
1681// ∂r/∂g = ln(ay + b)*(ay + b)^g
1682// - ln(ad + b)*(ad + b)^g
1683// ∂r/∂a = yg(ay + b)^(g-1)
1684// - dg(ad + b)^(g-1)
1685// ∂r/∂b = g(ay + b)^(g-1)
1686// - g(ad + b)^(g-1)
1687
1688// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1689// and fill out the gradient of the residual into dfdP.
1690static float rg_nonlinear(float x,
1691 const skcms_Curve* curve,
1692 const skcms_TransferFunction* tf,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001693 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001694 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001695
skia-autorolld07418c2019-11-25 14:26:50 +00001696 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 +00001697 c = tf->c, d = tf->d, f = tf->f;
1698
1699 const float Y = fmaxf_(a*y + b, 0.0f),
1700 D = a*d + b;
1701 assert (D >= 0);
1702
1703 // The gradient.
skia-autoroll5f8588c2019-10-14 13:54:14 +00001704 dfdP[0] = logf_(Y)*powf_(Y, g)
1705 - logf_(D)*powf_(D, g);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001706 dfdP[1] = y*g*powf_(Y, g-1)
1707 - d*g*powf_(D, g-1);
1708 dfdP[2] = g*powf_(Y, g-1)
1709 - g*powf_(D, g-1);
1710
1711 // The residual.
1712 const float f_inv = powf_(Y, g)
1713 - powf_(D, g)
1714 + c*d + f;
1715 return x - f_inv;
1716}
1717
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001718static bool gauss_newton_step(const skcms_Curve* curve,
skia-autorolld07418c2019-11-25 14:26:50 +00001719 skcms_TransferFunction* tf,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001720 float x0, float dx, int N) {
1721 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1722 //
skia-autorolld07418c2019-11-25 14:26:50 +00001723 // Let P = [ tf->g, tf->a, tf->b ] (the three terms that we're adjusting).
1724 //
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001725 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1726 // where r(P) is the residual vector
1727 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1728 //
1729 // Let's review the shape of each of these expressions:
1730 // r(P) is [N x 1], a column vector with one entry per value of x tested
1731 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1732 // Jf^T is [3 x N], the transpose of Jf
1733 //
1734 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1735 // and so is its inverse (Jf^T Jf)^-1
1736 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1737 //
1738 // Our implementation strategy to get to the final ∆P is
1739 // 1) evaluate Jf^T Jf, call that lhs
1740 // 2) evaluate Jf^T r(P), call that rhs
1741 // 3) invert lhs
1742 // 4) multiply inverse lhs by rhs
1743 //
1744 // This is a friendly implementation strategy because we don't have to have any
1745 // buffers that scale with N, and equally nice don't have to perform any matrix
1746 // operations that are variable size.
1747 //
1748 // Other implementation strategies could trade this off, e.g. evaluating the
1749 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1750 // the residuals. That would probably require implementing singular value
1751 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1752 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1753 // possibility of this gauss_newton_step() function ever failing.
1754
1755 // 0) start off with lhs and rhs safely zeroed.
1756 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1757 skcms_Vector3 rhs = { {0,0,0} };
1758
1759 // 1,2) evaluate lhs and evaluate rhs
1760 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1761 // so we'll have to update lhs and rhs at the same time.
1762 for (int i = 0; i < N; i++) {
1763 float x = x0 + i*dx;
1764
1765 float dfdP[3] = {0,0,0};
skia-autorolld07418c2019-11-25 14:26:50 +00001766 float resid = rg_nonlinear(x,curve,tf, dfdP);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001767
1768 for (int r = 0; r < 3; r++) {
1769 for (int c = 0; c < 3; c++) {
1770 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1771 }
1772 rhs.vals[r] += dfdP[r] * resid;
1773 }
1774 }
1775
1776 // If any of the 3 P parameters are unused, this matrix will be singular.
1777 // Detect those cases and fix them up to indentity instead, so we can invert.
1778 for (int k = 0; k < 3; k++) {
1779 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1780 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1781 lhs.vals[k][k] = 1;
1782 }
1783 }
1784
1785 // 3) invert lhs
1786 skcms_Matrix3x3 lhs_inv;
1787 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1788 return false;
1789 }
1790
1791 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001792 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skia-autorolld07418c2019-11-25 14:26:50 +00001793 tf->g += dP.vals[0];
1794 tf->a += dP.vals[1];
1795 tf->b += dP.vals[2];
1796 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 +00001797}
1798
skia-autorolld07418c2019-11-25 14:26:50 +00001799static float max_roundtrip_error_checked(const skcms_Curve* curve,
1800 const skcms_TransferFunction* tf_inv) {
1801 skcms_TransferFunction tf;
1802 if (!skcms_TransferFunction_invert(tf_inv, &tf) || sRGBish != classify(tf)) {
1803 return INFINITY_;
1804 }
1805
1806 skcms_TransferFunction tf_inv_again;
1807 if (!skcms_TransferFunction_invert(&tf, &tf_inv_again)) {
1808 return INFINITY_;
1809 }
1810
1811 return skcms_MaxRoundtripError(curve, &tf_inv_again);
1812}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001813
1814// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1815static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
skia-autorolld07418c2019-11-25 14:26:50 +00001816 // This enforces a few constraints that are not modeled in gauss_newton_step()'s optimization.
1817 auto fixup_tf = [tf]() {
1818 // a must be non-negative. That ensures the function is monotonically increasing.
1819 // We don't really know how to fix up a if it goes negative.
1820 if (tf->a < 0) {
1821 return false;
1822 }
1823 // ad+b must be non-negative. That ensures we don't end up with complex numbers in powf.
1824 // We feel just barely not uneasy enough to tweak b so ad+b is zero in this case.
1825 if (tf->a * tf->d + tf->b < 0) {
1826 tf->b = -tf->a * tf->d;
1827 }
1828 assert (tf->a >= 0 &&
1829 tf->a * tf->d + tf->b >= 0);
1830
1831 // cd+f must be ~= (ad+b)^g+e. That ensures the function is continuous. We keep e as a free
1832 // parameter so we can guarantee this.
1833 tf->e = tf->c*tf->d + tf->f
1834 - powf_(tf->a*tf->d + tf->b, tf->g);
1835
1836 return true;
1837 };
1838
1839 if (!fixup_tf()) {
1840 return false;
1841 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001842
1843 // No matter where we start, dx should always represent N even steps from 0 to 1.
1844 const float dx = 1.0f / (N-1);
1845
skia-autorolld07418c2019-11-25 14:26:50 +00001846 skcms_TransferFunction best_tf = *tf;
1847 float best_max_error = INFINITY_;
1848
1849 // Need this or several curves get worse... *sigh*
1850 float init_error = max_roundtrip_error_checked(curve, tf);
1851 if (init_error < best_max_error) {
1852 best_max_error = init_error;
1853 best_tf = *tf;
1854 }
1855
skia-autorolld9718822019-08-23 18:16:54 +00001856 // 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 +00001857 for (int j = 0; j < 8; j++) {
1858 if (!gauss_newton_step(curve, tf, L*dx, dx, N-L) || !fixup_tf()) {
1859 *tf = best_tf;
1860 return isfinitef_(best_max_error);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001861 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001862
skia-autorolld07418c2019-11-25 14:26:50 +00001863 float max_error = max_roundtrip_error_checked(curve, tf);
1864 if (max_error < best_max_error) {
1865 best_max_error = max_error;
1866 best_tf = *tf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001867 }
1868 }
1869
skia-autorolld07418c2019-11-25 14:26:50 +00001870 *tf = best_tf;
1871 return isfinitef_(best_max_error);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001872}
1873
1874bool skcms_ApproximateCurve(const skcms_Curve* curve,
1875 skcms_TransferFunction* approx,
1876 float* max_error) {
1877 if (!curve || !approx || !max_error) {
1878 return false;
1879 }
1880
1881 if (curve->table_entries == 0) {
1882 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1883 return false;
1884 }
1885
1886 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1887 // We need at least two points, and must put some reasonable cap on the maximum number.
1888 return false;
1889 }
1890
1891 int N = (int)curve->table_entries;
1892 const float dx = 1.0f / (N - 1);
1893
1894 *max_error = INFINITY_;
1895 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1896 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1897 skcms_TransferFunction tf,
1898 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001899
1900 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1901 tf.f = 0.0f;
1902 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 +00001903
1904 if (L == N) {
1905 // If the entire data set was linear, move the coefficients to the nonlinear portion
1906 // with G == 1. This lets use a canonical representation with d == 0.
1907 tf.g = 1;
1908 tf.a = tf.c;
1909 tf.b = tf.f;
1910 tf.c = tf.d = tf.e = tf.f = 0;
1911 } else if (L == N - 1) {
1912 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1913 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001914 tf.a = (eval_curve(curve, (N-1)*dx) -
1915 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001916 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001917 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001918 - tf.a * (N-2)*dx;
1919 tf.e = 0;
1920 } else {
1921 // Start by guessing a gamma-only curve through the midpoint.
1922 int mid = (L + N) / 2;
1923 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001924 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001925 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001926 tf.a = 1;
1927 tf.b = 0;
1928 tf.e = tf.c*tf.d + tf.f
1929 - powf_(tf.a*tf.d + tf.b, tf.g);
1930
1931
1932 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1933 !fit_nonlinear(curve, L,N, &tf_inv)) {
1934 continue;
1935 }
1936
1937 // We fit tf_inv, so calculate tf to keep in sync.
skia-autorolld07418c2019-11-25 14:26:50 +00001938 // fit_nonlinear() should guarantee invertibility.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001939 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
skia-autorolld07418c2019-11-25 14:26:50 +00001940 assert(false);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001941 continue;
1942 }
1943 }
1944
skia-autorollc7a4e772019-10-15 19:47:13 +00001945 // We'd better have a sane, sRGB-ish TF by now.
1946 // Other non-Bad TFs would be fine, but we know we've only ever tried to fit sRGBish;
1947 // 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 +00001948 // fit_nonlinear() should guarantee this, but the special cases may fail this test.
skia-autorollc7a4e772019-10-15 19:47:13 +00001949 if (sRGBish != classify(tf)) {
1950 continue;
1951 }
1952
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001953 // We find our error by roundtripping the table through tf_inv.
1954 //
1955 // (The most likely use case for this approximation is to be inverted and
1956 // used as the transfer function for a destination color space.)
1957 //
1958 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1959 // invertible, so re-verify that here (and use the new inverse for testing).
skia-autorolle9e35f72019-12-12 19:48:51 +00001960 // fit_nonlinear() should guarantee this, but the special cases that don't use
1961 // it may fail this test.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001962 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1963 continue;
1964 }
1965
skia-autorolld95243b2019-11-20 19:40:25 +00001966 float err = skcms_MaxRoundtripError(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001967 if (*max_error > err) {
1968 *max_error = err;
1969 *approx = tf;
1970 }
1971 }
1972 return isfinitef_(*max_error);
1973}
1974
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001975// ~~~~ Impl. of skcms_Transform() ~~~~
1976
1977typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001978 Op_load_a8,
1979 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001980 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001981 Op_load_4444,
1982 Op_load_565,
1983 Op_load_888,
1984 Op_load_8888,
1985 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001986 Op_load_161616LE,
1987 Op_load_16161616LE,
1988 Op_load_161616BE,
1989 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001990 Op_load_hhh,
1991 Op_load_hhhh,
1992 Op_load_fff,
1993 Op_load_ffff,
1994
1995 Op_swap_rb,
1996 Op_clamp,
1997 Op_invert,
1998 Op_force_opaque,
1999 Op_premul,
2000 Op_unpremul,
2001 Op_matrix_3x3,
2002 Op_matrix_3x4,
2003 Op_lab_to_xyz,
2004
2005 Op_tf_r,
2006 Op_tf_g,
2007 Op_tf_b,
2008 Op_tf_a,
2009
skia-autorollf6d50642019-10-14 15:31:13 +00002010 Op_pq_r,
2011 Op_pq_g,
2012 Op_pq_b,
2013 Op_pq_a,
2014
2015 Op_hlg_r,
2016 Op_hlg_g,
2017 Op_hlg_b,
2018 Op_hlg_a,
2019
2020 Op_hlginv_r,
2021 Op_hlginv_g,
2022 Op_hlginv_b,
2023 Op_hlginv_a,
2024
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002025 Op_table_r,
2026 Op_table_g,
2027 Op_table_b,
2028 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002029
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002030 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002031
2032 Op_store_a8,
2033 Op_store_g8,
2034 Op_store_4444,
2035 Op_store_565,
2036 Op_store_888,
2037 Op_store_8888,
2038 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002039 Op_store_161616LE,
2040 Op_store_16161616LE,
2041 Op_store_161616BE,
2042 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002043 Op_store_hhh,
2044 Op_store_hhhh,
2045 Op_store_fff,
2046 Op_store_ffff,
2047} Op;
2048
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002049#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002050 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 +00002051#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002052 // For some reason GCC accepts this nonsense, but not the more straightforward version,
2053 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
2054 template <int N, typename T>
2055 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 +00002056
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002057 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 +00002058#endif
2059
2060// First, instantiate our default exec_ops() implementation using the default compiliation target.
2061
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002062namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00002063#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
2064 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002065 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00002066 template <typename T> using V = T;
2067 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002068#elif defined(__AVX512F__)
2069 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002070 template <typename T> using V = Vec<N,T>;
2071 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002072#elif defined(__AVX__)
2073 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002074 template <typename T> using V = Vec<N,T>;
2075 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00002076#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
2077 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002078 template <typename T> using V = Vec<N,T>;
2079 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002080#else
2081 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00002082 template <typename T> using V = Vec<N,T>;
2083 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002084#endif
2085
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002086 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002087 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002088}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002089
2090// 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 +00002091#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00002092 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00002093 (( defined(__clang__) && __clang_major__ >= 5) || \
2094 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00002095 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002096
skia-autorollba6087c2019-04-09 13:57:02 +00002097 #if !defined(__AVX2__)
2098 #if defined(__clang__)
2099 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
2100 #elif defined(__GNUC__)
2101 #pragma GCC push_options
2102 #pragma GCC target("avx2,f16c")
2103 #endif
2104
2105 namespace hsw {
2106 #define USING_AVX
2107 #define USING_AVX_F16C
2108 #define USING_AVX2
2109 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002110 template <typename T> using V = Vec<N,T>;
2111 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00002112
2113 #include "src/Transform_inl.h"
2114
2115 // src/Transform_inl.h will undefine USING_* for us.
2116 #undef N
2117 }
2118
2119 #if defined(__clang__)
2120 #pragma clang attribute pop
2121 #elif defined(__GNUC__)
2122 #pragma GCC pop_options
2123 #endif
2124
2125 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002126 #endif
2127
skia-autorollba6087c2019-04-09 13:57:02 +00002128 #if !defined(__AVX512F__)
2129 #if defined(__clang__)
2130 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
2131 #elif defined(__GNUC__)
2132 #pragma GCC push_options
2133 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
2134 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002135
skia-autorollba6087c2019-04-09 13:57:02 +00002136 namespace skx {
2137 #define USING_AVX512F
2138 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002139 template <typename T> using V = Vec<N,T>;
2140 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002141
skia-autorollba6087c2019-04-09 13:57:02 +00002142 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002143
skia-autorollba6087c2019-04-09 13:57:02 +00002144 // src/Transform_inl.h will undefine USING_* for us.
2145 #undef N
2146 }
2147
2148 #if defined(__clang__)
2149 #pragma clang attribute pop
2150 #elif defined(__GNUC__)
2151 #pragma GCC pop_options
2152 #endif
2153
2154 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002155 #endif
2156
skia-autorollba6087c2019-04-09 13:57:02 +00002157 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
2158 enum class CpuType { None, HSW, SKX };
2159 static CpuType cpu_type() {
2160 static const CpuType type = []{
skia-autoroll15e78d02020-05-26 15:40:45 +00002161 if (!runtime_cpu_detection) {
2162 return CpuType::None;
2163 }
skia-autorollba6087c2019-04-09 13:57:02 +00002164 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002165
skia-autorollba6087c2019-04-09 13:57:02 +00002166 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2167 uint32_t eax, ebx, ecx, edx;
2168 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2169 : "0"(1), "2"(0));
2170 if ((edx & (1u<<25)) && // SSE
2171 (edx & (1u<<26)) && // SSE2
2172 (ecx & (1u<< 0)) && // SSE3
2173 (ecx & (1u<< 9)) && // SSSE3
2174 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
2175 (ecx & (1u<<19)) && // SSE4.1
2176 (ecx & (1u<<20)) && // SSE4.2
2177 (ecx & (1u<<26)) && // XSAVE
2178 (ecx & (1u<<27)) && // OSXSAVE
2179 (ecx & (1u<<28)) && // AVX
2180 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002181
skia-autorollba6087c2019-04-09 13:57:02 +00002182 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2183 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2184 : "0"(7), "2"(0));
2185 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2186 uint32_t xcr0, dont_need_edx;
2187 __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 +00002188
skia-autorollba6087c2019-04-09 13:57:02 +00002189 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2190 (xcr0 & (1u<<2)) && // YMM register state saved?
2191 (ebx & (1u<<5))) { // AVX2
2192 // At this point we're at least HSW. Continue checking for SKX.
2193 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2194 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2195 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2196 (ebx & (1u<<16)) && // AVX512F
2197 (ebx & (1u<<17)) && // AVX512DQ
2198 (ebx & (1u<<28)) && // AVX512CD
2199 (ebx & (1u<<30)) && // AVX512BW
2200 (ebx & (1u<<31))) { // AVX512VL
2201 return CpuType::SKX;
2202 }
2203 return CpuType::HSW;
2204 }
2205 }
2206 return CpuType::None;
2207 }();
2208 return type;
2209 }
2210 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002211
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002212#endif
2213
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002214typedef struct {
2215 Op op;
2216 const void* arg;
2217} OpAndArg;
2218
2219static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autorollf6d50642019-10-14 15:31:13 +00002220 static const struct { Op sRGBish, PQish, HLGish, HLGinvish, table; } ops[] = {
2221 { Op_tf_r, Op_pq_r, Op_hlg_r, Op_hlginv_r, Op_table_r },
2222 { Op_tf_g, Op_pq_g, Op_hlg_g, Op_hlginv_g, Op_table_g },
2223 { Op_tf_b, Op_pq_b, Op_hlg_b, Op_hlginv_b, Op_table_b },
2224 { 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 +00002225 };
skia-autorollf6d50642019-10-14 15:31:13 +00002226 const auto& op = ops[channel];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002227
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002228 if (curve->table_entries == 0) {
skia-autorollf6d50642019-10-14 15:31:13 +00002229 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 +00002230
skia-autorollf6d50642019-10-14 15:31:13 +00002231 const skcms_TransferFunction& tf = curve->parametric;
2232
2233 if (tf.g == 1 && tf.a == 1 &&
2234 tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) {
2235 return noop;
2236 }
2237
2238 switch (classify(tf)) {
2239 case Bad: return noop;
2240 case sRGBish: return OpAndArg{op.sRGBish, &tf};
2241 case PQish: return OpAndArg{op.PQish, &tf};
2242 case HLGish: return OpAndArg{op.HLGish, &tf};
2243 case HLGinvish: return OpAndArg{op.HLGinvish, &tf};
2244 }
2245 }
2246 return OpAndArg{op.table, curve};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002247}
2248
2249static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2250 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002251 case skcms_PixelFormat_A_8 >> 1: return 1;
2252 case skcms_PixelFormat_G_8 >> 1: return 1;
2253 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2254 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2255 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2256 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2257 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002258 case skcms_PixelFormat_RGBA_8888_sRGB >> 1: return 4;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002259 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2260 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2261 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2262 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2263 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002264 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2265 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002266 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2267 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2268 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2269 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002270 }
2271 assert(false);
2272 return 0;
2273}
2274
2275static bool prep_for_destination(const skcms_ICCProfile* profile,
2276 skcms_Matrix3x3* fromXYZD50,
2277 skcms_TransferFunction* invR,
2278 skcms_TransferFunction* invG,
2279 skcms_TransferFunction* invB) {
2280 // We only support destinations with parametric transfer functions
2281 // and with gamuts that can be transformed from XYZD50.
2282 return profile->has_trc
2283 && profile->has_toXYZD50
2284 && profile->trc[0].table_entries == 0
2285 && profile->trc[1].table_entries == 0
2286 && profile->trc[2].table_entries == 0
2287 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2288 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2289 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2290 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2291}
2292
2293bool skcms_Transform(const void* src,
2294 skcms_PixelFormat srcFmt,
2295 skcms_AlphaFormat srcAlpha,
2296 const skcms_ICCProfile* srcProfile,
2297 void* dst,
2298 skcms_PixelFormat dstFmt,
2299 skcms_AlphaFormat dstAlpha,
2300 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002301 size_t npixels) {
2302 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2303 dst, dstFmt, dstAlpha, dstProfile,
2304 npixels, nullptr);
2305}
2306
2307bool skcms_TransformWithPalette(const void* src,
2308 skcms_PixelFormat srcFmt,
2309 skcms_AlphaFormat srcAlpha,
2310 const skcms_ICCProfile* srcProfile,
2311 void* dst,
2312 skcms_PixelFormat dstFmt,
2313 skcms_AlphaFormat dstAlpha,
2314 const skcms_ICCProfile* dstProfile,
2315 size_t nz,
2316 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002317 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2318 src_bpp = bytes_per_pixel(srcFmt);
2319 // Let's just refuse if the request is absurdly big.
2320 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2321 return false;
2322 }
2323 int n = (int)nz;
2324
2325 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2326 if (!srcProfile) {
2327 srcProfile = skcms_sRGB_profile();
2328 }
2329 if (!dstProfile) {
2330 dstProfile = skcms_sRGB_profile();
2331 }
2332
2333 // 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 +00002334 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002335 return false;
2336 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002337 // TODO: more careful alias rejection (like, dst == src + 1)?
2338
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002339 if (needs_palette(srcFmt) && !palette) {
2340 return false;
2341 }
2342
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002343 Op program [32];
2344 const void* arguments[32];
2345
2346 Op* ops = program;
2347 const void** args = arguments;
2348
skia-autorollf6d50642019-10-14 15:31:13 +00002349 // These are always parametric curves of some sort.
2350 skcms_Curve dst_curves[3];
2351 dst_curves[0].table_entries =
2352 dst_curves[1].table_entries =
2353 dst_curves[2].table_entries = 0;
2354
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002355 skcms_Matrix3x3 from_xyz;
2356
2357 switch (srcFmt >> 1) {
2358 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002359 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2360 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2361 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2362 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2363 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2364 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2365 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2366 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2367 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2368 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2369 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002370 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2371 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 +00002372 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2373 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2374 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2375 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002376
2377 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2378 *args++ = palette;
2379 break;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002380 case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2381 *ops++ = Op_load_8888;
2382 *ops++ = Op_tf_r; *args++ = skcms_sRGB_TransferFunction();
2383 *ops++ = Op_tf_g; *args++ = skcms_sRGB_TransferFunction();
2384 *ops++ = Op_tf_b; *args++ = skcms_sRGB_TransferFunction();
2385 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002386 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002387 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2388 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2389 *ops++ = Op_clamp;
2390 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002391 if (srcFmt & 1) {
2392 *ops++ = Op_swap_rb;
2393 }
2394 skcms_ICCProfile gray_dst_profile;
2395 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2396 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2397 // luminance (Y) by the destination transfer function.
2398 gray_dst_profile = *dstProfile;
2399 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2400 dstProfile = &gray_dst_profile;
2401 }
2402
2403 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2404 // Photoshop creates CMYK images as inverse CMYK.
2405 // These happen to be the only ones we've _ever_ seen.
2406 *ops++ = Op_invert;
2407 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2408 srcAlpha = skcms_AlphaFormat_Unpremul;
2409 }
2410
2411 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2412 *ops++ = Op_force_opaque;
2413 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2414 *ops++ = Op_unpremul;
2415 }
2416
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002417 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002418
2419 if (!prep_for_destination(dstProfile,
skia-autorollf6d50642019-10-14 15:31:13 +00002420 &from_xyz,
2421 &dst_curves[0].parametric,
2422 &dst_curves[1].parametric,
2423 &dst_curves[2].parametric)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002424 return false;
2425 }
2426
2427 if (srcProfile->has_A2B) {
2428 if (srcProfile->A2B.input_channels) {
2429 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2430 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 +00002431 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002432 *ops++ = oa.op;
2433 *args++ = oa.arg;
2434 }
2435 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002436 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002437 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002438 *args++ = &srcProfile->A2B;
2439 }
2440
2441 if (srcProfile->A2B.matrix_channels == 3) {
2442 for (int i = 0; i < 3; i++) {
2443 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 +00002444 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002445 *ops++ = oa.op;
2446 *args++ = oa.arg;
2447 }
2448 }
2449
2450 static const skcms_Matrix3x4 I = {{
2451 {1,0,0,0},
2452 {0,1,0,0},
2453 {0,0,1,0},
2454 }};
2455 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2456 *ops++ = Op_matrix_3x4;
2457 *args++ = &srcProfile->A2B.matrix;
2458 }
2459 }
2460
2461 if (srcProfile->A2B.output_channels == 3) {
2462 for (int i = 0; i < 3; i++) {
2463 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 +00002464 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002465 *ops++ = oa.op;
2466 *args++ = oa.arg;
2467 }
2468 }
2469 }
2470
2471 if (srcProfile->pcs == skcms_Signature_Lab) {
2472 *ops++ = Op_lab_to_xyz;
2473 }
2474
2475 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2476 for (int i = 0; i < 3; i++) {
2477 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002478 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002479 *ops++ = oa.op;
2480 *args++ = oa.arg;
2481 }
2482 }
2483 } else {
2484 return false;
2485 }
2486
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002487 // A2B sources should already be in XYZD50 at this point.
2488 // Others still need to be transformed using their toXYZD50 matrix.
2489 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2490 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2491 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2492 static const skcms_Matrix3x3 I = {{
2493 { 1.0f, 0.0f, 0.0f },
2494 { 0.0f, 1.0f, 0.0f },
2495 { 0.0f, 0.0f, 1.0f },
2496 }};
2497 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2498
2499 // There's a chance the source and destination gamuts are identical,
2500 // in which case we can skip the gamut transform.
2501 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2502 // Concat the entire gamut transform into from_xyz,
2503 // now slightly misnamed but it's a handy spot to stash the result.
2504 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2505 *ops++ = Op_matrix_3x3;
2506 *args++ = &from_xyz;
2507 }
2508
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002509 // Encode back to dst RGB using its parametric transfer functions.
skia-autorollf6d50642019-10-14 15:31:13 +00002510 for (int i = 0; i < 3; i++) {
2511 OpAndArg oa = select_curve_op(dst_curves+i, i);
2512 if (oa.arg) {
2513 assert (oa.op != Op_table_r &&
2514 oa.op != Op_table_g &&
2515 oa.op != Op_table_b &&
2516 oa.op != Op_table_a);
2517 *ops++ = oa.op;
2518 *args++ = oa.arg;
2519 }
2520 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002521 }
2522
skia-autoroll2e4fa242019-03-11 21:14:18 +00002523 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2524 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002525 //
2526 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2527 // 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 +00002528 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2529 *ops++ = Op_clamp;
2530 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002531 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2532 *ops++ = Op_force_opaque;
2533 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2534 *ops++ = Op_premul;
2535 }
2536 if (dstFmt & 1) {
2537 *ops++ = Op_swap_rb;
2538 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002539 switch (dstFmt >> 1) {
2540 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002541 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2542 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2543 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2544 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2545 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2546 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2547 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2548 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2549 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2550 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2551 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002552 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2553 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 +00002554 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2555 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2556 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2557 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_store_ffff; break;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002558
2559 case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2560 *ops++ = Op_tf_r; *args++ = skcms_sRGB_Inverse_TransferFunction();
2561 *ops++ = Op_tf_g; *args++ = skcms_sRGB_Inverse_TransferFunction();
2562 *ops++ = Op_tf_b; *args++ = skcms_sRGB_Inverse_TransferFunction();
2563 *ops++ = Op_store_8888;
2564 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002565 }
2566
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002567 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002568#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002569 switch (cpu_type()) {
2570 case CpuType::None: break;
2571 case CpuType::HSW: run = hsw::run_program; break;
2572 case CpuType::SKX: run = hsw::run_program; break;
2573 }
2574#endif
2575#if defined(TEST_FOR_SKX)
2576 switch (cpu_type()) {
2577 case CpuType::None: break;
2578 case CpuType::HSW: break;
2579 case CpuType::SKX: run = skx::run_program; break;
2580 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002581#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002582 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 +00002583 return true;
2584}
2585
2586static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2587#if defined(NDEBUG)
2588 (void)profile;
2589#else
2590 skcms_Matrix3x3 fromXYZD50;
2591 skcms_TransferFunction invR, invG, invB;
2592 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2593#endif
2594}
2595
2596bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2597 skcms_Matrix3x3 fromXYZD50;
2598 if (!profile->has_trc || !profile->has_toXYZD50
2599 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2600 return false;
2601 }
2602
2603 skcms_TransferFunction tf[3];
2604 for (int i = 0; i < 3; i++) {
2605 skcms_TransferFunction inv;
2606 if (profile->trc[i].table_entries == 0
2607 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2608 tf[i] = profile->trc[i].parametric;
2609 continue;
2610 }
2611
2612 float max_error;
2613 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2614 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2615 return false;
2616 }
2617 }
2618
2619 for (int i = 0; i < 3; ++i) {
2620 profile->trc[i].table_entries = 0;
2621 profile->trc[i].parametric = tf[i];
2622 }
2623
2624 assert_usable_as_destination(profile);
2625 return true;
2626}
2627
2628bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2629 // Operate on a copy of profile, so we can choose the best TF for the original curves
2630 skcms_ICCProfile result = *profile;
2631 if (!skcms_MakeUsableAsDestination(&result)) {
2632 return false;
2633 }
2634
2635 int best_tf = 0;
2636 float min_max_error = INFINITY_;
2637 for (int i = 0; i < 3; i++) {
2638 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002639 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2640 return false;
2641 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002642
2643 float err = 0;
2644 for (int j = 0; j < 3; ++j) {
skia-autorolld95243b2019-11-20 19:40:25 +00002645 err = fmaxf_(err, skcms_MaxRoundtripError(&profile->trc[j], &inv));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002646 }
2647 if (min_max_error > err) {
2648 min_max_error = err;
2649 best_tf = i;
2650 }
2651 }
2652
2653 for (int i = 0; i < 3; i++) {
2654 result.trc[i].parametric = result.trc[best_tf].parametric;
2655 }
2656
2657 *profile = result;
2658 assert_usable_as_destination(profile);
2659 return true;
2660}