blob: 92af9634e6bbed606ce9f18438c65a4db04a223c [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-autorollc093cc82019-10-08 19:22:13 +0000169bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
170 float A, float B, float C,
171 float D, float E, float F) {
172 *tf = { TFKind_marker(PQish), A,B,C,D,E,F };
173 assert(classify(*tf) == PQish);
174 return true;
175}
176
skia-autoroll5f8588c2019-10-14 13:54:14 +0000177bool skcms_TransferFunction_makeHLGish(skcms_TransferFunction* tf,
178 float R, float G,
179 float a, float b, float c) {
skia-autorollf6d50642019-10-14 15:31:13 +0000180 *tf = { TFKind_marker(HLGish), R,G, a,b,c, 0 };
skia-autoroll5f8588c2019-10-14 13:54:14 +0000181 assert(classify(*tf) == HLGish);
182 return true;
183}
184
skia-autorollc093cc82019-10-08 19:22:13 +0000185float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
186 float sign = x < 0 ? -1.0f : 1.0f;
187 x *= sign;
188
skia-autoroll5f8588c2019-10-14 13:54:14 +0000189 TF_PQish pq;
190 TF_HLGish hlg;
191 switch (classify(*tf, &pq, &hlg)) {
skia-autorollc093cc82019-10-08 19:22:13 +0000192 case Bad: break;
skia-autoroll5f8588c2019-10-14 13:54:14 +0000193
skia-autoroll5f8588c2019-10-14 13:54:14 +0000194 case HLGish: return sign * (x*hlg.R <= 1 ? powf_(x*hlg.R, hlg.G)
195 : expf_((x-hlg.c)*hlg.a) + hlg.b);
196
skia-autorollf6d50642019-10-14 15:31:13 +0000197 // skcms_TransferFunction_invert() inverts R, G, and a for HLGinvish so this math is fast.
skia-autoroll5f8588c2019-10-14 13:54:14 +0000198 case HLGinvish: return sign * (x <= 1 ? hlg.R * powf_(x, hlg.G)
199 : hlg.a * logf_(x - hlg.b) + hlg.c);
200
skia-autorollc093cc82019-10-08 19:22:13 +0000201
202 case sRGBish: return sign * (x < tf->d ? tf->c * x + tf->f
203 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
204
205 case PQish: return sign * powf_(fmaxf_(pq.A + pq.B * powf_(x, pq.C), 0)
206 / (pq.D + pq.E * powf_(x, pq.C)),
207 pq.F);
208 }
209 return 0;
210}
211
212
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000213static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000214 if (curve->table_entries == 0) {
215 return skcms_TransferFunction_eval(&curve->parametric, x);
216 }
217
218 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 +0000219 int lo = (int) ix ,
220 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000221 float t = ix - (float)lo;
222
223 float l, h;
224 if (curve->table_8) {
225 l = curve->table_8[lo] * (1/255.0f);
226 h = curve->table_8[hi] * (1/255.0f);
227 } else {
228 uint16_t be_l, be_h;
229 memcpy(&be_l, curve->table_16 + 2*lo, 2);
230 memcpy(&be_h, curve->table_16 + 2*hi, 2);
231 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
232 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
233 l = le_l * (1/65535.0f);
234 h = le_h * (1/65535.0f);
235 }
236 return l + (h-l)*t;
237}
238
skia-autorolld95243b2019-11-20 19:40:25 +0000239float 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 +0000240 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
241 const float dx = 1.0f / (N - 1);
242 float err = 0;
243 for (uint32_t i = 0; i < N; i++) {
244 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000245 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000246 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
247 }
248 return err;
249}
250
251bool skcms_AreApproximateInverses(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skia-autorolld95243b2019-11-20 19:40:25 +0000252 return skcms_MaxRoundtripError(curve, inv_tf) < (1/512.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000253}
254
255// Additional ICC signature values that are only used internally
256enum {
257 // File signature
258 skcms_Signature_acsp = 0x61637370,
259
260 // Tag signatures
261 skcms_Signature_rTRC = 0x72545243,
262 skcms_Signature_gTRC = 0x67545243,
263 skcms_Signature_bTRC = 0x62545243,
264 skcms_Signature_kTRC = 0x6B545243,
265
266 skcms_Signature_rXYZ = 0x7258595A,
267 skcms_Signature_gXYZ = 0x6758595A,
268 skcms_Signature_bXYZ = 0x6258595A,
269
270 skcms_Signature_A2B0 = 0x41324230,
271 skcms_Signature_A2B1 = 0x41324231,
272 skcms_Signature_mAB = 0x6D414220,
273
274 skcms_Signature_CHAD = 0x63686164,
skia-autoroll2565a6d2019-11-11 16:48:43 +0000275 skcms_Signature_WTPT = 0x77747074,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000276
277 // Type signatures
278 skcms_Signature_curv = 0x63757276,
279 skcms_Signature_mft1 = 0x6D667431,
280 skcms_Signature_mft2 = 0x6D667432,
281 skcms_Signature_para = 0x70617261,
282 skcms_Signature_sf32 = 0x73663332,
283 // XYZ is also a PCS signature, so it's defined in skcms.h
284 // skcms_Signature_XYZ = 0x58595A20,
285};
286
287static uint16_t read_big_u16(const uint8_t* ptr) {
288 uint16_t be;
289 memcpy(&be, ptr, sizeof(be));
290#if defined(_MSC_VER)
291 return _byteswap_ushort(be);
292#else
293 return __builtin_bswap16(be);
294#endif
295}
296
297static uint32_t read_big_u32(const uint8_t* ptr) {
298 uint32_t be;
299 memcpy(&be, ptr, sizeof(be));
300#if defined(_MSC_VER)
301 return _byteswap_ulong(be);
302#else
303 return __builtin_bswap32(be);
304#endif
305}
306
307static int32_t read_big_i32(const uint8_t* ptr) {
308 return (int32_t)read_big_u32(ptr);
309}
310
311static float read_big_fixed(const uint8_t* ptr) {
312 return read_big_i32(ptr) * (1.0f / 65536.0f);
313}
314
315// Maps to an in-memory profile so that fields line up to the locations specified
316// in ICC.1:2010, section 7.2
317typedef struct {
318 uint8_t size [ 4];
319 uint8_t cmm_type [ 4];
320 uint8_t version [ 4];
321 uint8_t profile_class [ 4];
322 uint8_t data_color_space [ 4];
323 uint8_t pcs [ 4];
324 uint8_t creation_date_time [12];
325 uint8_t signature [ 4];
326 uint8_t platform [ 4];
327 uint8_t flags [ 4];
328 uint8_t device_manufacturer [ 4];
329 uint8_t device_model [ 4];
330 uint8_t device_attributes [ 8];
331 uint8_t rendering_intent [ 4];
332 uint8_t illuminant_X [ 4];
333 uint8_t illuminant_Y [ 4];
334 uint8_t illuminant_Z [ 4];
335 uint8_t creator [ 4];
336 uint8_t profile_id [16];
337 uint8_t reserved [28];
338 uint8_t tag_count [ 4]; // Technically not part of header, but required
339} header_Layout;
340
341typedef struct {
342 uint8_t signature [4];
343 uint8_t offset [4];
344 uint8_t size [4];
345} tag_Layout;
346
347static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
348 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
349}
350
351// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
352// use of the type is for the CHAD tag that stores exactly nine values.
353typedef struct {
354 uint8_t type [ 4];
355 uint8_t reserved [ 4];
356 uint8_t values [36];
357} sf32_Layout;
358
359bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
360 skcms_ICCTag tag;
361 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
362 return false;
363 }
364
365 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
366 return false;
367 }
368
369 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
370 const uint8_t* values = sf32Tag->values;
371 for (int r = 0; r < 3; ++r)
372 for (int c = 0; c < 3; ++c, values += 4) {
373 m->vals[r][c] = read_big_fixed(values);
374 }
375 return true;
376}
377
378// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
379// the type are for tags/data that store exactly one triple.
380typedef struct {
381 uint8_t type [4];
382 uint8_t reserved [4];
383 uint8_t X [4];
384 uint8_t Y [4];
385 uint8_t Z [4];
386} XYZ_Layout;
387
388static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
389 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
390 return false;
391 }
392
393 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
394
395 *x = read_big_fixed(xyzTag->X);
396 *y = read_big_fixed(xyzTag->Y);
397 *z = read_big_fixed(xyzTag->Z);
398 return true;
399}
400
skia-autoroll2565a6d2019-11-11 16:48:43 +0000401bool skcms_GetWTPT(const skcms_ICCProfile* profile, float xyz[3]) {
402 skcms_ICCTag tag;
403 return skcms_GetTagBySignature(profile, skcms_Signature_WTPT, &tag) &&
404 read_tag_xyz(&tag, &xyz[0], &xyz[1], &xyz[2]);
405}
406
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000407static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
408 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
409 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
410 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
411 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
412}
413
414typedef struct {
415 uint8_t type [4];
416 uint8_t reserved_a [4];
417 uint8_t function_type [2];
418 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000419 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 +0000420} para_Layout;
421
422static bool read_curve_para(const uint8_t* buf, uint32_t size,
423 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000424 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000425 return false;
426 }
427
428 const para_Layout* paraTag = (const para_Layout*)buf;
429
430 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
431 uint16_t function_type = read_big_u16(paraTag->function_type);
432 if (function_type > kGABCDEF) {
433 return false;
434 }
435
436 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 +0000437 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 +0000438 return false;
439 }
440
441 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000442 *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 +0000443 }
444
445 curve->table_entries = 0;
446 curve->parametric.a = 1.0f;
447 curve->parametric.b = 0.0f;
448 curve->parametric.c = 0.0f;
449 curve->parametric.d = 0.0f;
450 curve->parametric.e = 0.0f;
451 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000452 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000453
454 switch (function_type) {
455 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000456 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
457 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000458 if (curve->parametric.a == 0) {
459 return false;
460 }
461 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
462 break;
463 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000464 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
465 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
466 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000467 if (curve->parametric.a == 0) {
468 return false;
469 }
470 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
471 curve->parametric.f = curve->parametric.e;
472 break;
473 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000474 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
475 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
476 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
477 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000478 break;
479 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000480 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
481 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
482 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
483 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
484 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
485 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000486 break;
487 }
skia-autorollf6d50642019-10-14 15:31:13 +0000488 return classify(curve->parametric) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000489}
490
491typedef struct {
492 uint8_t type [4];
493 uint8_t reserved [4];
494 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000495 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 +0000496} curv_Layout;
497
498static bool read_curve_curv(const uint8_t* buf, uint32_t size,
499 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000500 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000501 return false;
502 }
503
504 const curv_Layout* curvTag = (const curv_Layout*)buf;
505
506 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 +0000507 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 +0000508 return false;
509 }
510
511 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000512 *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 +0000513 }
514
515 if (value_count < 2) {
516 curve->table_entries = 0;
517 curve->parametric.a = 1.0f;
518 curve->parametric.b = 0.0f;
519 curve->parametric.c = 0.0f;
520 curve->parametric.d = 0.0f;
521 curve->parametric.e = 0.0f;
522 curve->parametric.f = 0.0f;
523 if (value_count == 0) {
524 // Empty tables are a shorthand for an identity curve
525 curve->parametric.g = 1.0f;
526 } else {
527 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000528 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 +0000529 }
530 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000531 curve->table_8 = nullptr;
532 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000533 curve->table_entries = value_count;
534 }
535
536 return true;
537}
538
539// 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 +0000540// 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 +0000541static bool read_curve(const uint8_t* buf, uint32_t size,
542 skcms_Curve* curve, uint32_t* curve_size) {
543 if (!buf || size < 4 || !curve) {
544 return false;
545 }
546
547 uint32_t type = read_big_u32(buf);
548 if (type == skcms_Signature_para) {
549 return read_curve_para(buf, size, curve, curve_size);
550 } else if (type == skcms_Signature_curv) {
551 return read_curve_curv(buf, size, curve, curve_size);
552 }
553
554 return false;
555}
556
557// mft1 and mft2 share a large chunk of data
558typedef struct {
559 uint8_t type [ 4];
560 uint8_t reserved_a [ 4];
561 uint8_t input_channels [ 1];
562 uint8_t output_channels [ 1];
563 uint8_t grid_points [ 1];
564 uint8_t reserved_b [ 1];
565 uint8_t matrix [36];
566} mft_CommonLayout;
567
568typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000569 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000570
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000571 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000572} mft1_Layout;
573
574typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000575 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000576
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000577 uint8_t input_table_entries [2];
578 uint8_t output_table_entries [2];
579 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000580} mft2_Layout;
581
582static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
583 // MFT matrices are applied before the first set of curves, but must be identity unless the
584 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
585 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
586 // field/flag.
587 a2b->matrix_channels = 0;
588
589 a2b->input_channels = mftTag->input_channels[0];
590 a2b->output_channels = mftTag->output_channels[0];
591
592 // We require exactly three (ie XYZ/Lab/RGB) output channels
593 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
594 return false;
595 }
596 // We require at least one, and no more than four (ie CMYK) input channels
597 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
598 return false;
599 }
600
601 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
602 a2b->grid_points[i] = mftTag->grid_points[0];
603 }
604 // The grid only makes sense with at least two points along each axis
605 if (a2b->grid_points[0] < 2) {
606 return false;
607 }
608
609 return true;
610}
611
612static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
613 uint32_t input_table_entries, uint32_t output_table_entries,
614 skcms_A2B* a2b) {
615 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
616 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
617 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
618
619 // [input|output]_channels are <= 4, so still no overflow
620 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
621 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
622
623 uint64_t grid_size = a2b->output_channels * byte_width;
624 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
625 grid_size *= a2b->grid_points[axis];
626 }
627
628 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
629 return false;
630 }
631
632 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
633 a2b->input_curves[i].table_entries = input_table_entries;
634 if (byte_width == 1) {
635 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 +0000636 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000637 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000638 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000639 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
640 }
641 }
642
643 if (byte_width == 1) {
644 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 +0000645 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000646 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000647 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000648 a2b->grid_16 = table_base + byte_len_all_input_tables;
649 }
650
651 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
652 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
653 a2b->output_curves[i].table_entries = output_table_entries;
654 if (byte_width == 1) {
655 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 +0000656 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000657 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000658 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000659 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
660 }
661 }
662
663 return true;
664}
665
666static 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 +0000667 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000668 return false;
669 }
670
671 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
672 if (!read_mft_common(mftTag->common, a2b)) {
673 return false;
674 }
675
676 uint32_t input_table_entries = 256;
677 uint32_t output_table_entries = 256;
678
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000679 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 +0000680 input_table_entries, output_table_entries, a2b);
681}
682
683static 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 +0000684 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000685 return false;
686 }
687
688 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
689 if (!read_mft_common(mftTag->common, a2b)) {
690 return false;
691 }
692
693 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
694 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
695
696 // ICC spec mandates that 2 <= table_entries <= 4096
697 if (input_table_entries < 2 || input_table_entries > 4096 ||
698 output_table_entries < 2 || output_table_entries > 4096) {
699 return false;
700 }
701
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000702 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 +0000703 input_table_entries, output_table_entries, a2b);
704}
705
706static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
707 uint32_t num_curves, skcms_Curve* curves) {
708 for (uint32_t i = 0; i < num_curves; ++i) {
709 if (curve_offset > size) {
710 return false;
711 }
712
713 uint32_t curve_bytes;
714 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
715 return false;
716 }
717
718 if (curve_bytes > UINT32_MAX - 3) {
719 return false;
720 }
721 curve_bytes = (curve_bytes + 3) & ~3U;
722
723 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
724 curve_offset = (uint32_t)new_offset_64;
725 if (new_offset_64 != curve_offset) {
726 return false;
727 }
728 }
729
730 return true;
731}
732
733typedef struct {
734 uint8_t type [ 4];
735 uint8_t reserved_a [ 4];
736 uint8_t input_channels [ 1];
737 uint8_t output_channels [ 1];
738 uint8_t reserved_b [ 2];
739 uint8_t b_curve_offset [ 4];
740 uint8_t matrix_offset [ 4];
741 uint8_t m_curve_offset [ 4];
742 uint8_t clut_offset [ 4];
743 uint8_t a_curve_offset [ 4];
744} mAB_Layout;
745
746typedef struct {
747 uint8_t grid_points [16];
748 uint8_t grid_byte_width [ 1];
749 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000750 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000751} mABCLUT_Layout;
752
753static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
754 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
755 return false;
756 }
757
758 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
759
760 a2b->input_channels = mABTag->input_channels[0];
761 a2b->output_channels = mABTag->output_channels[0];
762
763 // We require exactly three (ie XYZ/Lab/RGB) output channels
764 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
765 return false;
766 }
767 // We require no more than four (ie CMYK) input channels
768 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
769 return false;
770 }
771
772 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
773 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
774 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
775 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
776 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
777
778 // "B" curves must be present
779 if (0 == b_curve_offset) {
780 return false;
781 }
782
783 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
784 a2b->output_curves)) {
785 return false;
786 }
787
788 // "M" curves and Matrix must be used together
789 if (0 != m_curve_offset) {
790 if (0 == matrix_offset) {
791 return false;
792 }
793 a2b->matrix_channels = a2b->output_channels;
794 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
795 a2b->matrix_curves)) {
796 return false;
797 }
798
799 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
800 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
801 return false;
802 }
803 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
804 const uint8_t* mtx_buf = tag->buf + matrix_offset;
805 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
806 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
807 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
808 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
809 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
810 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
811 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
812 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
813 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
814 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
815 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
816 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
817 } else {
818 if (0 != matrix_offset) {
819 return false;
820 }
821 a2b->matrix_channels = 0;
822 }
823
824 // "A" curves and CLUT must be used together
825 if (0 != a_curve_offset) {
826 if (0 == clut_offset) {
827 return false;
828 }
829 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
830 a2b->input_curves)) {
831 return false;
832 }
833
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000834 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 +0000835 return false;
836 }
837 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
838
839 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000840 a2b->grid_8 = clut->variable;
841 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000842 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000843 a2b->grid_8 = nullptr;
844 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000845 } else {
846 return false;
847 }
848
849 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
850 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
851 a2b->grid_points[i] = clut->grid_points[i];
852 // The grid only makes sense with at least two points along each axis
853 if (a2b->grid_points[i] < 2) {
854 return false;
855 }
856 grid_size *= a2b->grid_points[i];
857 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000858 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 +0000859 return false;
860 }
861 } else {
862 if (0 != clut_offset) {
863 return false;
864 }
865
866 // If there is no CLUT, the number of input and output channels must match
867 if (a2b->input_channels != a2b->output_channels) {
868 return false;
869 }
870
871 // Zero out the number of input channels to signal that we're skipping this stage
872 a2b->input_channels = 0;
873 }
874
875 return true;
876}
877
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000878// If you pass f, we'll fit a possibly-non-zero value for *f.
879// If you pass nullptr, we'll assume you want *f to be treated as zero.
880static int fit_linear(const skcms_Curve* curve, int N, float tol,
881 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000882 assert(N > 1);
883 // We iteratively fit the first points to the TF's linear piece.
884 // We want the cx + f line to pass through the first and last points we fit exactly.
885 //
886 // As we walk along the points we find the minimum and maximum slope of the line before the
887 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
888 // emtpy, when we definitely can't add any more points.
889 //
890 // Some points' error intervals may intersect the running interval but not lie fully
891 // within it. So we keep track of the last point we saw that is a valid end point candidate,
892 // and once the search is done, back up to build the line through *that* point.
893 const float dx = 1.0f / (N - 1);
894
895 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000896
897 float f_zero = 0.0f;
898 if (f) {
899 *f = eval_curve(curve, 0);
900 } else {
901 f = &f_zero;
902 }
903
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000904
905 float slope_min = -INFINITY_;
906 float slope_max = +INFINITY_;
907 for (int i = 1; i < N; ++i) {
908 float x = i * dx;
909 float y = eval_curve(curve, x);
910
911 float slope_max_i = (y + tol - *f) / x,
912 slope_min_i = (y - tol - *f) / x;
913 if (slope_max_i < slope_min || slope_max < slope_min_i) {
914 // Slope intervals would no longer overlap.
915 break;
916 }
917 slope_max = fminf_(slope_max, slope_max_i);
918 slope_min = fmaxf_(slope_min, slope_min_i);
919
920 float cur_slope = (y - *f) / x;
921 if (slope_min <= cur_slope && cur_slope <= slope_max) {
922 lin_points = i + 1;
923 *c = cur_slope;
924 }
925 }
926
927 // Set D to the last point that met our tolerance.
928 *d = (lin_points - 1) * dx;
929 return lin_points;
930}
931
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000932static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
933 bool ok = false;
934 if (tag->type == skcms_Signature_mft1) {
935 ok = read_tag_mft1(tag, a2b);
936 } else if (tag->type == skcms_Signature_mft2) {
937 ok = read_tag_mft2(tag, a2b);
938 } else if (tag->type == skcms_Signature_mAB) {
939 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
940 }
941 if (!ok) {
942 return false;
943 }
944
945 // Detect and canonicalize identity tables.
946 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000947 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
948 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
949 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
950 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
951 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
952 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
953 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
954 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
955 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
956 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000957 };
958
959 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
960 skcms_Curve* curve = curves[i];
961
962 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
963 int N = (int)curve->table_entries;
964
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000965 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 +0000966 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 +0000967 && c == 1.0f
968 && f == 0.0f) {
969 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000970 curve->table_8 = nullptr;
971 curve->table_16 = nullptr;
972 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 +0000973 }
974 }
975 }
976
977 return true;
978}
979
980void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
981 if (!profile || !profile->buffer || !tag) { return; }
982 if (idx > profile->tag_count) { return; }
983 const tag_Layout* tags = get_tag_table(profile);
984 tag->signature = read_big_u32(tags[idx].signature);
985 tag->size = read_big_u32(tags[idx].size);
986 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
987 tag->type = read_big_u32(tag->buf);
988}
989
990bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
991 if (!profile || !profile->buffer || !tag) { return false; }
992 const tag_Layout* tags = get_tag_table(profile);
993 for (uint32_t i = 0; i < profile->tag_count; ++i) {
994 if (read_big_u32(tags[i].signature) == sig) {
995 tag->signature = sig;
996 tag->size = read_big_u32(tags[i].size);
997 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
998 tag->type = read_big_u32(tag->buf);
999 return true;
1000 }
1001 }
1002 return false;
1003}
1004
1005static bool usable_as_src(const skcms_ICCProfile* profile) {
1006 return profile->has_A2B
1007 || (profile->has_trc && profile->has_toXYZD50);
1008}
1009
1010bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
1011 assert(SAFE_SIZEOF(header_Layout) == 132);
1012
1013 if (!profile) {
1014 return false;
1015 }
1016 memset(profile, 0, SAFE_SIZEOF(*profile));
1017
1018 if (len < SAFE_SIZEOF(header_Layout)) {
1019 return false;
1020 }
1021
1022 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001023 const header_Layout* header = (const header_Layout*)buf;
1024 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001025 profile->size = read_big_u32(header->size);
1026 uint32_t version = read_big_u32(header->version);
1027 profile->data_color_space = read_big_u32(header->data_color_space);
1028 profile->pcs = read_big_u32(header->pcs);
1029 uint32_t signature = read_big_u32(header->signature);
1030 float illuminant_X = read_big_fixed(header->illuminant_X);
1031 float illuminant_Y = read_big_fixed(header->illuminant_Y);
1032 float illuminant_Z = read_big_fixed(header->illuminant_Z);
1033 profile->tag_count = read_big_u32(header->tag_count);
1034
1035 // Validate signature, size (smaller than buffer, large enough to hold tag table),
1036 // and major version
1037 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1038 if (signature != skcms_Signature_acsp ||
1039 profile->size > len ||
1040 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1041 (version >> 24) > 4) {
1042 return false;
1043 }
1044
1045 // Validate that illuminant is D50 white
1046 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1047 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1048 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1049 return false;
1050 }
1051
1052 // Validate that all tag entries have sane offset + size
1053 const tag_Layout* tags = get_tag_table(profile);
1054 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1055 uint32_t tag_offset = read_big_u32(tags[i].offset);
1056 uint32_t tag_size = read_big_u32(tags[i].size);
1057 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
1058 if (tag_size < 4 || tag_end > profile->size) {
1059 return false;
1060 }
1061 }
1062
1063 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1064 return false;
1065 }
1066
1067 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1068
1069 // Pre-parse commonly used tags.
1070 skcms_ICCTag kTRC;
1071 if (profile->data_color_space == skcms_Signature_Gray &&
1072 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001073 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 +00001074 // Malformed tag
1075 return false;
1076 }
1077 profile->trc[1] = profile->trc[0];
1078 profile->trc[2] = profile->trc[0];
1079 profile->has_trc = true;
1080
1081 if (pcs_is_xyz) {
1082 profile->toXYZD50.vals[0][0] = illuminant_X;
1083 profile->toXYZD50.vals[1][1] = illuminant_Y;
1084 profile->toXYZD50.vals[2][2] = illuminant_Z;
1085 profile->has_toXYZD50 = true;
1086 }
1087 } else {
1088 skcms_ICCTag rTRC, gTRC, bTRC;
1089 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1090 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1091 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001092 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1093 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1094 !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 +00001095 // Malformed TRC tags
1096 return false;
1097 }
1098 profile->has_trc = true;
1099 }
1100
1101 skcms_ICCTag rXYZ, gXYZ, bXYZ;
1102 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1103 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1104 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1105 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1106 // Malformed XYZ tags
1107 return false;
1108 }
1109 profile->has_toXYZD50 = true;
1110 }
1111 }
1112
1113 skcms_ICCTag a2b_tag;
1114
1115 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
1116 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
1117 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
1118 // and all our known users are thinking exclusively in terms of relative colormetric.
1119 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
1120 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
1121 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
1122 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
1123 // Malformed A2B tag
1124 return false;
1125 }
1126 profile->has_A2B = true;
1127 break;
1128 }
1129 }
1130
1131 return usable_as_src(profile);
1132}
1133
1134
1135const skcms_ICCProfile* skcms_sRGB_profile() {
1136 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001137 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001138
1139 0, // size, moot here
1140 skcms_Signature_RGB, // data_color_space
1141 skcms_Signature_XYZ, // pcs
1142 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001143
1144 // We choose to represent sRGB with its canonical transfer function,
1145 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001146 true, // has_trc, followed by the 3 trc curves
1147 {
1148 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1149 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1150 {{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 +00001151 },
1152
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001153 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1154 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001155 { 0.436065674f, 0.385147095f, 0.143066406f },
1156 { 0.222488403f, 0.716873169f, 0.060607910f },
1157 { 0.013916016f, 0.097076416f, 0.714096069f },
1158 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001159
1160 false, // has_A2B, followed by a2b itself which we don't care about.
1161 {
1162 0,
1163 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001164 {{0, {0,0, 0,0,0,0,0}}},
1165 {{0, {0,0, 0,0,0,0,0}}},
1166 {{0, {0,0, 0,0,0,0,0}}},
1167 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001168 },
1169 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001170 nullptr,
1171 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001172
1173 0,
1174 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001175 {{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 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001180 { 0,0,0,0 },
1181 { 0,0,0,0 },
1182 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001183 }},
1184
1185 0,
1186 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001187 {{0, {0,0, 0,0,0,0,0}}},
1188 {{0, {0,0, 0,0,0,0,0}}},
1189 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001190 },
1191 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001192 };
1193 return &sRGB_profile;
1194}
1195
1196const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001197 // 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 +00001198 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001199 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001200
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001201 0, // size, moot here
1202 skcms_Signature_RGB, // data_color_space
1203 skcms_Signature_XYZ, // pcs
1204 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001205
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001206 true, // has_trc, followed by the 3 trc curves
1207 {
1208 {{0, {1,1, 0,0,0,0,0}}},
1209 {{0, {1,1, 0,0,0,0,0}}},
1210 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001211 },
1212
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001213 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1214 {{
1215 { 1,0,0 },
1216 { 0,1,0 },
1217 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001218 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001219
1220 false, // has_A2B, followed by a2b itself which we don't care about.
1221 {
1222 0,
1223 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001224 {{0, {0,0, 0,0,0,0,0}}},
1225 {{0, {0,0, 0,0,0,0,0}}},
1226 {{0, {0,0, 0,0,0,0,0}}},
1227 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001228 },
1229 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001230 nullptr,
1231 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001232
1233 0,
1234 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001235 {{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 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001240 { 0,0,0,0 },
1241 { 0,0,0,0 },
1242 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001243 }},
1244
1245 0,
1246 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001247 {{0, {0,0, 0,0,0,0,0}}},
1248 {{0, {0,0, 0,0,0,0,0}}},
1249 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001250 },
1251 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001252 };
1253
1254 return &XYZD50_profile;
1255}
1256
1257const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1258 return &skcms_sRGB_profile()->trc[0].parametric;
1259}
1260
1261const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1262 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001263 {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 +00001264 return &sRGB_inv;
1265}
1266
1267const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1268 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1269 return &identity;
1270}
1271
1272const uint8_t skcms_252_random_bytes[] = {
1273 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1274 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1275 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1276 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1277 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1278 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1279 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1280 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1281 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1282 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1283 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1284 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1285 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1286 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1287 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1288 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1289};
1290
1291bool 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 +00001292 // Test for exactly equal profiles first.
1293 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1294 return true;
1295 }
1296
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001297 // For now this is the essentially the same strategy we use in test_only.c
1298 // for our skcms_Transform() smoke tests:
1299 // 1) transform A to XYZD50
1300 // 2) transform B to XYZD50
1301 // 3) return true if they're similar enough
1302 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1303
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001304 // 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 +00001305 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1306
1307 if (A->data_color_space != B->data_color_space) {
1308 return false;
1309 }
1310
1311 // 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 +00001312 // 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 +00001313 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1314 size_t npixels = 84;
1315 if (A->data_color_space == skcms_Signature_CMYK) {
1316 fmt = skcms_PixelFormat_RGBA_8888;
1317 npixels = 63;
1318 }
1319
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001320 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1321 // 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 +00001322 uint8_t dstA[252],
1323 dstB[252];
1324 if (!skcms_Transform(
1325 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1326 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1327 npixels)) {
1328 return false;
1329 }
1330 if (!skcms_Transform(
1331 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1332 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1333 npixels)) {
1334 return false;
1335 }
1336
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001337 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001338 for (size_t i = 0; i < 252; i++) {
1339 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1340 return false;
1341 }
1342 }
1343 return true;
1344}
1345
1346bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1347 const skcms_TransferFunction* inv_tf) {
1348 if (!profile || !profile->has_trc) {
1349 return false;
1350 }
1351
1352 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1353 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1354 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1355}
1356
1357static bool is_zero_to_one(float x) {
1358 return 0 <= x && x <= 1;
1359}
1360
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001361typedef struct { float vals[3]; } skcms_Vector3;
1362
1363static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1364 skcms_Vector3 dst = {{0,0,0}};
1365 for (int row = 0; row < 3; ++row) {
1366 dst.vals[row] = m->vals[row][0] * v->vals[0]
1367 + m->vals[row][1] * v->vals[1]
1368 + m->vals[row][2] * v->vals[2];
1369 }
1370 return dst;
1371}
1372
skia-autoroll70b6b5e2019-12-11 18:40:05 +00001373bool skcms_AdaptToXYZD50(float wx, float wy,
1374 skcms_Matrix3x3* toXYZD50) {
1375 if (!is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1376 !toXYZD50) {
1377 return false;
1378 }
1379
1380 // Assumes that Y is 1.0f.
1381 skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
1382
1383 // Now convert toXYZ matrix to toXYZD50.
1384 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1385
1386 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1387 // the matrices below. The Bradford method is used by Adobe and is widely considered
1388 // to be the best.
1389 skcms_Matrix3x3 xyz_to_lms = {{
1390 { 0.8951f, 0.2664f, -0.1614f },
1391 { -0.7502f, 1.7135f, 0.0367f },
1392 { 0.0389f, -0.0685f, 1.0296f },
1393 }};
1394 skcms_Matrix3x3 lms_to_xyz = {{
1395 { 0.9869929f, -0.1470543f, 0.1599627f },
1396 { 0.4323053f, 0.5183603f, 0.0492912f },
1397 { -0.0085287f, 0.0400428f, 0.9684867f },
1398 }};
1399
1400 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1401 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
1402
1403 *toXYZD50 = {{
1404 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1405 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1406 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1407 }};
1408 *toXYZD50 = skcms_Matrix3x3_concat(toXYZD50, &xyz_to_lms);
1409 *toXYZD50 = skcms_Matrix3x3_concat(&lms_to_xyz, toXYZD50);
1410
1411 return true;
1412}
1413
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001414bool skcms_PrimariesToXYZD50(float rx, float ry,
1415 float gx, float gy,
1416 float bx, float by,
1417 float wx, float wy,
1418 skcms_Matrix3x3* toXYZD50) {
1419 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1420 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1421 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1422 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1423 !toXYZD50) {
1424 return false;
1425 }
1426
1427 // First, we need to convert xy values (primaries) to XYZ.
1428 skcms_Matrix3x3 primaries = {{
1429 { rx, gx, bx },
1430 { ry, gy, by },
1431 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1432 }};
1433 skcms_Matrix3x3 primaries_inv;
1434 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1435 return false;
1436 }
1437
1438 // Assumes that Y is 1.0f.
1439 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 +00001440 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001441
1442 skcms_Matrix3x3 toXYZ = {{
1443 { XYZ.vals[0], 0, 0 },
1444 { 0, XYZ.vals[1], 0 },
1445 { 0, 0, XYZ.vals[2] },
1446 }};
1447 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1448
skia-autoroll70b6b5e2019-12-11 18:40:05 +00001449 skcms_Matrix3x3 DXtoD50;
1450 if (!skcms_AdaptToXYZD50(wx, wy, &DXtoD50)) {
1451 return false;
1452 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001453
1454 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1455 return true;
1456}
1457
1458
1459bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1460 double a00 = src->vals[0][0],
1461 a01 = src->vals[1][0],
1462 a02 = src->vals[2][0],
1463 a10 = src->vals[0][1],
1464 a11 = src->vals[1][1],
1465 a12 = src->vals[2][1],
1466 a20 = src->vals[0][2],
1467 a21 = src->vals[1][2],
1468 a22 = src->vals[2][2];
1469
1470 double b0 = a00*a11 - a01*a10,
1471 b1 = a00*a12 - a02*a10,
1472 b2 = a01*a12 - a02*a11,
1473 b3 = a20,
1474 b4 = a21,
1475 b5 = a22;
1476
1477 double determinant = b0*b5
1478 - b1*b4
1479 + b2*b3;
1480
1481 if (determinant == 0) {
1482 return false;
1483 }
1484
1485 double invdet = 1.0 / determinant;
1486 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1487 return false;
1488 }
1489
1490 b0 *= invdet;
1491 b1 *= invdet;
1492 b2 *= invdet;
1493 b3 *= invdet;
1494 b4 *= invdet;
1495 b5 *= invdet;
1496
1497 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1498 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1499 dst->vals[2][0] = (float)( + b2 );
1500 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1501 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1502 dst->vals[2][1] = (float)( - b1 );
1503 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1504 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1505 dst->vals[2][2] = (float)( + b0 );
1506
1507 for (int r = 0; r < 3; ++r)
1508 for (int c = 0; c < 3; ++c) {
1509 if (!isfinitef_(dst->vals[r][c])) {
1510 return false;
1511 }
1512 }
1513 return true;
1514}
1515
1516skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1517 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1518 for (int r = 0; r < 3; r++)
1519 for (int c = 0; c < 3; c++) {
1520 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1521 + A->vals[r][1] * B->vals[1][c]
1522 + A->vals[r][2] * B->vals[2][c];
1523 }
1524 return m;
1525}
1526
skia-autorollacd6e012019-01-08 14:10:52 +00001527#if defined(__clang__)
skia-autorollf6d50642019-10-14 15:31:13 +00001528 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by classify() on the way out.
skia-autorollacd6e012019-01-08 14:10:52 +00001529#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001530bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
skia-autorollf6d50642019-10-14 15:31:13 +00001531 TF_PQish pq;
1532 TF_HLGish hlg;
1533 switch (classify(*src, &pq, &hlg)) {
1534 case Bad: return false;
1535 case sRGBish: break; // handled below
1536
1537 case PQish:
1538 *dst = { TFKind_marker(PQish), -pq.A, pq.D, 1.0f/pq.F
1539 , pq.B, -pq.E, 1.0f/pq.C};
1540 return true;
1541
1542 case HLGish:
1543 *dst = { TFKind_marker(HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
1544 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1545 return true;
1546
1547 case HLGinvish:
1548 *dst = { TFKind_marker(HLGish), 1.0f/hlg.R, 1.0f/hlg.G
1549 , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
1550 return true;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001551 }
1552
skia-autorollf6d50642019-10-14 15:31:13 +00001553 assert (classify(*src) == sRGBish);
1554
skia-autorolld0b577f2019-01-07 19:46:57 +00001555 // We're inverting this function, solving for x in terms of y.
1556 // y = (cx + f) x < d
1557 // (ax + b)^g + e x ≥ d
1558 // The inverse of this function can be expressed in the same piecewise form.
1559 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 +00001560
skia-autorolld0b577f2019-01-07 19:46:57 +00001561 // We'll start by finding the new threshold inv.d.
1562 // In principle we should be able to find that by solving for y at x=d from either side.
1563 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1564 float d_l = src->c * src->d + src->f,
1565 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1566 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001567 return false;
1568 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001569 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 +00001570
skia-autorolld0b577f2019-01-07 19:46:57 +00001571 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1572 if (inv.d > 0) {
1573 // Inverting the linear section is pretty straightfoward:
1574 // y = cx + f
1575 // y - f = cx
1576 // (1/c)y - f/c = x
1577 inv.c = 1.0f/src->c;
1578 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001579 }
1580
skia-autorolld0b577f2019-01-07 19:46:57 +00001581 // The interesting part is inverting the nonlinear section:
1582 // y = (ax + b)^g + e.
1583 // y - e = (ax + b)^g
1584 // (y - e)^1/g = ax + b
1585 // (y - e)^1/g - b = ax
1586 // (1/a)(y - e)^1/g - b/a = x
1587 //
1588 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1589 // let k = (1/a)^g
1590 // (1/a)( y - e)^1/g - b/a = x
1591 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001592
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001593 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001594 inv.g = 1.0f / src->g;
1595 inv.a = k;
1596 inv.b = -k * src->e;
1597 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001598
skia-autorollfe16a332019-08-20 19:44:54 +00001599 // We need to enforce the same constraints here that we do when fitting a curve,
skia-autorollf6d50642019-10-14 15:31:13 +00001600 // a >= 0 and ad+b >= 0. These constraints are checked by classify(), so they're true
skia-autorollbf388232019-08-21 14:17:54 +00001601 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001602
skia-autorollbf388232019-08-21 14:17:54 +00001603 // Just like when fitting the curve, there's really no way to rescue a < 0.
1604 if (inv.a < 0) {
1605 return false;
1606 }
1607 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001608 if (inv.a * inv.d + inv.b < 0) {
1609 inv.b = -inv.a * inv.d;
1610 }
skia-autorollbf388232019-08-21 14:17:54 +00001611
skia-autorollf6d50642019-10-14 15:31:13 +00001612 // That should usually make classify(inv) == sRGBish true, but there are a couple situations
skia-autorolla0ed0702019-08-23 16:49:54 +00001613 // where we might still fail here, like non-finite parameter values.
skia-autorollf6d50642019-10-14 15:31:13 +00001614 if (classify(inv) != sRGBish) {
skia-autorolla0ed0702019-08-23 16:49:54 +00001615 return false;
1616 }
1617
skia-autorollbf388232019-08-21 14:17:54 +00001618 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001619 assert (inv.a * inv.d + inv.b >= 0);
1620
skia-autorolla7b28742019-01-09 18:35:46 +00001621 // Now in principle we're done.
skia-autorollad10df62019-08-21 15:14:54 +00001622 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
1623 // e or f of the inverse, depending on which segment contains src(1.0f).
1624 float s = skcms_TransferFunction_eval(src, 1.0f);
skia-autorolla0ed0702019-08-23 16:49:54 +00001625 if (!isfinitef_(s)) {
1626 return false;
1627 }
1628
skia-autorollad10df62019-08-21 15:14:54 +00001629 float sign = s < 0 ? -1.0f : 1.0f;
1630 s *= sign;
1631 if (s < inv.d) {
1632 inv.f = 1.0f - sign * inv.c * s;
1633 } else {
1634 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
1635 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001636
skia-autorolld0b577f2019-01-07 19:46:57 +00001637 *dst = inv;
skia-autorollf6d50642019-10-14 15:31:13 +00001638 return classify(*dst) == sRGBish;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001639}
1640
1641// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1642
1643// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1644//
1645// tf(x) = cx + f x < d
1646// tf(x) = (ax + b)^g + e x ≥ d
1647//
1648// When fitting, we add the additional constraint that both pieces meet at d:
1649//
1650// cd + f = (ad + b)^g + e
1651//
1652// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1653//
1654// tf(x) = cx + f x < d
1655// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1656//
1657// Our overall strategy is then:
1658// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001659// - 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 +00001660// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001661// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1662// (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 +00001663// Return the parameters with least maximum error.
1664//
1665// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1666// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1667//
1668// let y = Table(x)
1669// r(x) = x - f_inv(y)
1670//
1671// ∂r/∂g = ln(ay + b)*(ay + b)^g
1672// - ln(ad + b)*(ad + b)^g
1673// ∂r/∂a = yg(ay + b)^(g-1)
1674// - dg(ad + b)^(g-1)
1675// ∂r/∂b = g(ay + b)^(g-1)
1676// - g(ad + b)^(g-1)
1677
1678// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1679// and fill out the gradient of the residual into dfdP.
1680static float rg_nonlinear(float x,
1681 const skcms_Curve* curve,
1682 const skcms_TransferFunction* tf,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001683 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001684 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001685
skia-autorolld07418c2019-11-25 14:26:50 +00001686 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 +00001687 c = tf->c, d = tf->d, f = tf->f;
1688
1689 const float Y = fmaxf_(a*y + b, 0.0f),
1690 D = a*d + b;
1691 assert (D >= 0);
1692
1693 // The gradient.
skia-autoroll5f8588c2019-10-14 13:54:14 +00001694 dfdP[0] = logf_(Y)*powf_(Y, g)
1695 - logf_(D)*powf_(D, g);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001696 dfdP[1] = y*g*powf_(Y, g-1)
1697 - d*g*powf_(D, g-1);
1698 dfdP[2] = g*powf_(Y, g-1)
1699 - g*powf_(D, g-1);
1700
1701 // The residual.
1702 const float f_inv = powf_(Y, g)
1703 - powf_(D, g)
1704 + c*d + f;
1705 return x - f_inv;
1706}
1707
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001708static bool gauss_newton_step(const skcms_Curve* curve,
skia-autorolld07418c2019-11-25 14:26:50 +00001709 skcms_TransferFunction* tf,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001710 float x0, float dx, int N) {
1711 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1712 //
skia-autorolld07418c2019-11-25 14:26:50 +00001713 // Let P = [ tf->g, tf->a, tf->b ] (the three terms that we're adjusting).
1714 //
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001715 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1716 // where r(P) is the residual vector
1717 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1718 //
1719 // Let's review the shape of each of these expressions:
1720 // r(P) is [N x 1], a column vector with one entry per value of x tested
1721 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1722 // Jf^T is [3 x N], the transpose of Jf
1723 //
1724 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1725 // and so is its inverse (Jf^T Jf)^-1
1726 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1727 //
1728 // Our implementation strategy to get to the final ∆P is
1729 // 1) evaluate Jf^T Jf, call that lhs
1730 // 2) evaluate Jf^T r(P), call that rhs
1731 // 3) invert lhs
1732 // 4) multiply inverse lhs by rhs
1733 //
1734 // This is a friendly implementation strategy because we don't have to have any
1735 // buffers that scale with N, and equally nice don't have to perform any matrix
1736 // operations that are variable size.
1737 //
1738 // Other implementation strategies could trade this off, e.g. evaluating the
1739 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1740 // the residuals. That would probably require implementing singular value
1741 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1742 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1743 // possibility of this gauss_newton_step() function ever failing.
1744
1745 // 0) start off with lhs and rhs safely zeroed.
1746 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1747 skcms_Vector3 rhs = { {0,0,0} };
1748
1749 // 1,2) evaluate lhs and evaluate rhs
1750 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1751 // so we'll have to update lhs and rhs at the same time.
1752 for (int i = 0; i < N; i++) {
1753 float x = x0 + i*dx;
1754
1755 float dfdP[3] = {0,0,0};
skia-autorolld07418c2019-11-25 14:26:50 +00001756 float resid = rg_nonlinear(x,curve,tf, dfdP);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001757
1758 for (int r = 0; r < 3; r++) {
1759 for (int c = 0; c < 3; c++) {
1760 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1761 }
1762 rhs.vals[r] += dfdP[r] * resid;
1763 }
1764 }
1765
1766 // If any of the 3 P parameters are unused, this matrix will be singular.
1767 // Detect those cases and fix them up to indentity instead, so we can invert.
1768 for (int k = 0; k < 3; k++) {
1769 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1770 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1771 lhs.vals[k][k] = 1;
1772 }
1773 }
1774
1775 // 3) invert lhs
1776 skcms_Matrix3x3 lhs_inv;
1777 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1778 return false;
1779 }
1780
1781 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001782 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skia-autorolld07418c2019-11-25 14:26:50 +00001783 tf->g += dP.vals[0];
1784 tf->a += dP.vals[1];
1785 tf->b += dP.vals[2];
1786 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 +00001787}
1788
skia-autorolld07418c2019-11-25 14:26:50 +00001789static float max_roundtrip_error_checked(const skcms_Curve* curve,
1790 const skcms_TransferFunction* tf_inv) {
1791 skcms_TransferFunction tf;
1792 if (!skcms_TransferFunction_invert(tf_inv, &tf) || sRGBish != classify(tf)) {
1793 return INFINITY_;
1794 }
1795
1796 skcms_TransferFunction tf_inv_again;
1797 if (!skcms_TransferFunction_invert(&tf, &tf_inv_again)) {
1798 return INFINITY_;
1799 }
1800
1801 return skcms_MaxRoundtripError(curve, &tf_inv_again);
1802}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001803
1804// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1805static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
skia-autorolld07418c2019-11-25 14:26:50 +00001806 // This enforces a few constraints that are not modeled in gauss_newton_step()'s optimization.
1807 auto fixup_tf = [tf]() {
1808 // a must be non-negative. That ensures the function is monotonically increasing.
1809 // We don't really know how to fix up a if it goes negative.
1810 if (tf->a < 0) {
1811 return false;
1812 }
1813 // ad+b must be non-negative. That ensures we don't end up with complex numbers in powf.
1814 // We feel just barely not uneasy enough to tweak b so ad+b is zero in this case.
1815 if (tf->a * tf->d + tf->b < 0) {
1816 tf->b = -tf->a * tf->d;
1817 }
1818 assert (tf->a >= 0 &&
1819 tf->a * tf->d + tf->b >= 0);
1820
1821 // cd+f must be ~= (ad+b)^g+e. That ensures the function is continuous. We keep e as a free
1822 // parameter so we can guarantee this.
1823 tf->e = tf->c*tf->d + tf->f
1824 - powf_(tf->a*tf->d + tf->b, tf->g);
1825
1826 return true;
1827 };
1828
1829 if (!fixup_tf()) {
1830 return false;
1831 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001832
1833 // No matter where we start, dx should always represent N even steps from 0 to 1.
1834 const float dx = 1.0f / (N-1);
1835
skia-autorolld07418c2019-11-25 14:26:50 +00001836 skcms_TransferFunction best_tf = *tf;
1837 float best_max_error = INFINITY_;
1838
1839 // Need this or several curves get worse... *sigh*
1840 float init_error = max_roundtrip_error_checked(curve, tf);
1841 if (init_error < best_max_error) {
1842 best_max_error = init_error;
1843 best_tf = *tf;
1844 }
1845
skia-autorolld9718822019-08-23 18:16:54 +00001846 // 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 +00001847 for (int j = 0; j < 8; j++) {
1848 if (!gauss_newton_step(curve, tf, L*dx, dx, N-L) || !fixup_tf()) {
1849 *tf = best_tf;
1850 return isfinitef_(best_max_error);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001851 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001852
skia-autorolld07418c2019-11-25 14:26:50 +00001853 float max_error = max_roundtrip_error_checked(curve, tf);
1854 if (max_error < best_max_error) {
1855 best_max_error = max_error;
1856 best_tf = *tf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001857 }
1858 }
1859
skia-autorolld07418c2019-11-25 14:26:50 +00001860 *tf = best_tf;
1861 return isfinitef_(best_max_error);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001862}
1863
1864bool skcms_ApproximateCurve(const skcms_Curve* curve,
1865 skcms_TransferFunction* approx,
1866 float* max_error) {
1867 if (!curve || !approx || !max_error) {
1868 return false;
1869 }
1870
1871 if (curve->table_entries == 0) {
1872 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1873 return false;
1874 }
1875
1876 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1877 // We need at least two points, and must put some reasonable cap on the maximum number.
1878 return false;
1879 }
1880
1881 int N = (int)curve->table_entries;
1882 const float dx = 1.0f / (N - 1);
1883
1884 *max_error = INFINITY_;
1885 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1886 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1887 skcms_TransferFunction tf,
1888 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001889
1890 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1891 tf.f = 0.0f;
1892 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 +00001893
1894 if (L == N) {
1895 // If the entire data set was linear, move the coefficients to the nonlinear portion
1896 // with G == 1. This lets use a canonical representation with d == 0.
1897 tf.g = 1;
1898 tf.a = tf.c;
1899 tf.b = tf.f;
1900 tf.c = tf.d = tf.e = tf.f = 0;
1901 } else if (L == N - 1) {
1902 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1903 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001904 tf.a = (eval_curve(curve, (N-1)*dx) -
1905 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001906 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001907 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001908 - tf.a * (N-2)*dx;
1909 tf.e = 0;
1910 } else {
1911 // Start by guessing a gamma-only curve through the midpoint.
1912 int mid = (L + N) / 2;
1913 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001914 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001915 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001916 tf.a = 1;
1917 tf.b = 0;
1918 tf.e = tf.c*tf.d + tf.f
1919 - powf_(tf.a*tf.d + tf.b, tf.g);
1920
1921
1922 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1923 !fit_nonlinear(curve, L,N, &tf_inv)) {
1924 continue;
1925 }
1926
1927 // We fit tf_inv, so calculate tf to keep in sync.
skia-autorolld07418c2019-11-25 14:26:50 +00001928 // fit_nonlinear() should guarantee invertibility.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001929 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
skia-autorolld07418c2019-11-25 14:26:50 +00001930 assert(false);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001931 continue;
1932 }
1933 }
1934
skia-autorollc7a4e772019-10-15 19:47:13 +00001935 // We'd better have a sane, sRGB-ish TF by now.
1936 // Other non-Bad TFs would be fine, but we know we've only ever tried to fit sRGBish;
1937 // 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 +00001938 // fit_nonlinear() should guarantee this, but the special cases may fail this test.
skia-autorollc7a4e772019-10-15 19:47:13 +00001939 if (sRGBish != classify(tf)) {
1940 continue;
1941 }
1942
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001943 // We find our error by roundtripping the table through tf_inv.
1944 //
1945 // (The most likely use case for this approximation is to be inverted and
1946 // used as the transfer function for a destination color space.)
1947 //
1948 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1949 // invertible, so re-verify that here (and use the new inverse for testing).
skia-autorolle9e35f72019-12-12 19:48:51 +00001950 // fit_nonlinear() should guarantee this, but the special cases that don't use
1951 // it may fail this test.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001952 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1953 continue;
1954 }
1955
skia-autorolld95243b2019-11-20 19:40:25 +00001956 float err = skcms_MaxRoundtripError(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001957 if (*max_error > err) {
1958 *max_error = err;
1959 *approx = tf;
1960 }
1961 }
1962 return isfinitef_(*max_error);
1963}
1964
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001965// ~~~~ Impl. of skcms_Transform() ~~~~
1966
1967typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001968 Op_load_a8,
1969 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001970 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001971 Op_load_4444,
1972 Op_load_565,
1973 Op_load_888,
1974 Op_load_8888,
1975 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001976 Op_load_161616LE,
1977 Op_load_16161616LE,
1978 Op_load_161616BE,
1979 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001980 Op_load_hhh,
1981 Op_load_hhhh,
1982 Op_load_fff,
1983 Op_load_ffff,
1984
1985 Op_swap_rb,
1986 Op_clamp,
1987 Op_invert,
1988 Op_force_opaque,
1989 Op_premul,
1990 Op_unpremul,
1991 Op_matrix_3x3,
1992 Op_matrix_3x4,
1993 Op_lab_to_xyz,
1994
1995 Op_tf_r,
1996 Op_tf_g,
1997 Op_tf_b,
1998 Op_tf_a,
1999
skia-autorollf6d50642019-10-14 15:31:13 +00002000 Op_pq_r,
2001 Op_pq_g,
2002 Op_pq_b,
2003 Op_pq_a,
2004
2005 Op_hlg_r,
2006 Op_hlg_g,
2007 Op_hlg_b,
2008 Op_hlg_a,
2009
2010 Op_hlginv_r,
2011 Op_hlginv_g,
2012 Op_hlginv_b,
2013 Op_hlginv_a,
2014
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002015 Op_table_r,
2016 Op_table_g,
2017 Op_table_b,
2018 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002019
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002020 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002021
2022 Op_store_a8,
2023 Op_store_g8,
2024 Op_store_4444,
2025 Op_store_565,
2026 Op_store_888,
2027 Op_store_8888,
2028 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002029 Op_store_161616LE,
2030 Op_store_16161616LE,
2031 Op_store_161616BE,
2032 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002033 Op_store_hhh,
2034 Op_store_hhhh,
2035 Op_store_fff,
2036 Op_store_ffff,
2037} Op;
2038
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002039#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002040 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 +00002041#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002042 // For some reason GCC accepts this nonsense, but not the more straightforward version,
2043 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
2044 template <int N, typename T>
2045 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 +00002046
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002047 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 +00002048#endif
2049
2050// First, instantiate our default exec_ops() implementation using the default compiliation target.
2051
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002052namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00002053#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
2054 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002055 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00002056 template <typename T> using V = T;
2057 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002058#elif defined(__AVX512F__)
2059 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002060 template <typename T> using V = Vec<N,T>;
2061 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002062#elif defined(__AVX__)
2063 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002064 template <typename T> using V = Vec<N,T>;
2065 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00002066#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
2067 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002068 template <typename T> using V = Vec<N,T>;
2069 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002070#else
2071 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00002072 template <typename T> using V = Vec<N,T>;
2073 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002074#endif
2075
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002076 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002077 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002078}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002079
2080// 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 +00002081#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00002082 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00002083 (( defined(__clang__) && __clang_major__ >= 5) || \
2084 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00002085 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002086
skia-autorollba6087c2019-04-09 13:57:02 +00002087 #if !defined(__AVX2__)
2088 #if defined(__clang__)
2089 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
2090 #elif defined(__GNUC__)
2091 #pragma GCC push_options
2092 #pragma GCC target("avx2,f16c")
2093 #endif
2094
2095 namespace hsw {
2096 #define USING_AVX
2097 #define USING_AVX_F16C
2098 #define USING_AVX2
2099 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00002100 template <typename T> using V = Vec<N,T>;
2101 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00002102
2103 #include "src/Transform_inl.h"
2104
2105 // src/Transform_inl.h will undefine USING_* for us.
2106 #undef N
2107 }
2108
2109 #if defined(__clang__)
2110 #pragma clang attribute pop
2111 #elif defined(__GNUC__)
2112 #pragma GCC pop_options
2113 #endif
2114
2115 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002116 #endif
2117
skia-autorollba6087c2019-04-09 13:57:02 +00002118 #if !defined(__AVX512F__)
2119 #if defined(__clang__)
2120 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
2121 #elif defined(__GNUC__)
2122 #pragma GCC push_options
2123 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
2124 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002125
skia-autorollba6087c2019-04-09 13:57:02 +00002126 namespace skx {
2127 #define USING_AVX512F
2128 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00002129 template <typename T> using V = Vec<N,T>;
2130 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002131
skia-autorollba6087c2019-04-09 13:57:02 +00002132 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002133
skia-autorollba6087c2019-04-09 13:57:02 +00002134 // src/Transform_inl.h will undefine USING_* for us.
2135 #undef N
2136 }
2137
2138 #if defined(__clang__)
2139 #pragma clang attribute pop
2140 #elif defined(__GNUC__)
2141 #pragma GCC pop_options
2142 #endif
2143
2144 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00002145 #endif
2146
skia-autorollba6087c2019-04-09 13:57:02 +00002147 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
2148 enum class CpuType { None, HSW, SKX };
2149 static CpuType cpu_type() {
2150 static const CpuType type = []{
skia-autoroll15e78d02020-05-26 15:40:45 +00002151 if (!runtime_cpu_detection) {
2152 return CpuType::None;
2153 }
skia-autorollba6087c2019-04-09 13:57:02 +00002154 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002155
skia-autorollba6087c2019-04-09 13:57:02 +00002156 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2157 uint32_t eax, ebx, ecx, edx;
2158 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2159 : "0"(1), "2"(0));
2160 if ((edx & (1u<<25)) && // SSE
2161 (edx & (1u<<26)) && // SSE2
2162 (ecx & (1u<< 0)) && // SSE3
2163 (ecx & (1u<< 9)) && // SSSE3
2164 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
2165 (ecx & (1u<<19)) && // SSE4.1
2166 (ecx & (1u<<20)) && // SSE4.2
2167 (ecx & (1u<<26)) && // XSAVE
2168 (ecx & (1u<<27)) && // OSXSAVE
2169 (ecx & (1u<<28)) && // AVX
2170 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002171
skia-autorollba6087c2019-04-09 13:57:02 +00002172 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2173 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2174 : "0"(7), "2"(0));
2175 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2176 uint32_t xcr0, dont_need_edx;
2177 __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 +00002178
skia-autorollba6087c2019-04-09 13:57:02 +00002179 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2180 (xcr0 & (1u<<2)) && // YMM register state saved?
2181 (ebx & (1u<<5))) { // AVX2
2182 // At this point we're at least HSW. Continue checking for SKX.
2183 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2184 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2185 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2186 (ebx & (1u<<16)) && // AVX512F
2187 (ebx & (1u<<17)) && // AVX512DQ
2188 (ebx & (1u<<28)) && // AVX512CD
2189 (ebx & (1u<<30)) && // AVX512BW
2190 (ebx & (1u<<31))) { // AVX512VL
2191 return CpuType::SKX;
2192 }
2193 return CpuType::HSW;
2194 }
2195 }
2196 return CpuType::None;
2197 }();
2198 return type;
2199 }
2200 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002201
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002202#endif
2203
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002204typedef struct {
2205 Op op;
2206 const void* arg;
2207} OpAndArg;
2208
2209static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autorollf6d50642019-10-14 15:31:13 +00002210 static const struct { Op sRGBish, PQish, HLGish, HLGinvish, table; } ops[] = {
2211 { Op_tf_r, Op_pq_r, Op_hlg_r, Op_hlginv_r, Op_table_r },
2212 { Op_tf_g, Op_pq_g, Op_hlg_g, Op_hlginv_g, Op_table_g },
2213 { Op_tf_b, Op_pq_b, Op_hlg_b, Op_hlginv_b, Op_table_b },
2214 { 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 +00002215 };
skia-autorollf6d50642019-10-14 15:31:13 +00002216 const auto& op = ops[channel];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002217
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002218 if (curve->table_entries == 0) {
skia-autorollf6d50642019-10-14 15:31:13 +00002219 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 +00002220
skia-autorollf6d50642019-10-14 15:31:13 +00002221 const skcms_TransferFunction& tf = curve->parametric;
2222
2223 if (tf.g == 1 && tf.a == 1 &&
2224 tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) {
2225 return noop;
2226 }
2227
2228 switch (classify(tf)) {
2229 case Bad: return noop;
2230 case sRGBish: return OpAndArg{op.sRGBish, &tf};
2231 case PQish: return OpAndArg{op.PQish, &tf};
2232 case HLGish: return OpAndArg{op.HLGish, &tf};
2233 case HLGinvish: return OpAndArg{op.HLGinvish, &tf};
2234 }
2235 }
2236 return OpAndArg{op.table, curve};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002237}
2238
2239static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2240 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002241 case skcms_PixelFormat_A_8 >> 1: return 1;
2242 case skcms_PixelFormat_G_8 >> 1: return 1;
2243 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2244 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2245 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2246 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2247 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002248 case skcms_PixelFormat_RGBA_8888_sRGB >> 1: return 4;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002249 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2250 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2251 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2252 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2253 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002254 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2255 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002256 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2257 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2258 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2259 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002260 }
2261 assert(false);
2262 return 0;
2263}
2264
2265static bool prep_for_destination(const skcms_ICCProfile* profile,
2266 skcms_Matrix3x3* fromXYZD50,
2267 skcms_TransferFunction* invR,
2268 skcms_TransferFunction* invG,
2269 skcms_TransferFunction* invB) {
2270 // We only support destinations with parametric transfer functions
2271 // and with gamuts that can be transformed from XYZD50.
2272 return profile->has_trc
2273 && profile->has_toXYZD50
2274 && profile->trc[0].table_entries == 0
2275 && profile->trc[1].table_entries == 0
2276 && profile->trc[2].table_entries == 0
2277 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2278 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2279 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2280 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2281}
2282
2283bool skcms_Transform(const void* src,
2284 skcms_PixelFormat srcFmt,
2285 skcms_AlphaFormat srcAlpha,
2286 const skcms_ICCProfile* srcProfile,
2287 void* dst,
2288 skcms_PixelFormat dstFmt,
2289 skcms_AlphaFormat dstAlpha,
2290 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002291 size_t npixels) {
2292 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2293 dst, dstFmt, dstAlpha, dstProfile,
2294 npixels, nullptr);
2295}
2296
2297bool skcms_TransformWithPalette(const void* src,
2298 skcms_PixelFormat srcFmt,
2299 skcms_AlphaFormat srcAlpha,
2300 const skcms_ICCProfile* srcProfile,
2301 void* dst,
2302 skcms_PixelFormat dstFmt,
2303 skcms_AlphaFormat dstAlpha,
2304 const skcms_ICCProfile* dstProfile,
2305 size_t nz,
2306 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002307 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2308 src_bpp = bytes_per_pixel(srcFmt);
2309 // Let's just refuse if the request is absurdly big.
2310 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2311 return false;
2312 }
2313 int n = (int)nz;
2314
2315 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2316 if (!srcProfile) {
2317 srcProfile = skcms_sRGB_profile();
2318 }
2319 if (!dstProfile) {
2320 dstProfile = skcms_sRGB_profile();
2321 }
2322
2323 // 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 +00002324 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002325 return false;
2326 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002327 // TODO: more careful alias rejection (like, dst == src + 1)?
2328
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002329 if (needs_palette(srcFmt) && !palette) {
2330 return false;
2331 }
2332
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002333 Op program [32];
2334 const void* arguments[32];
2335
2336 Op* ops = program;
2337 const void** args = arguments;
2338
skia-autorollf6d50642019-10-14 15:31:13 +00002339 // These are always parametric curves of some sort.
2340 skcms_Curve dst_curves[3];
2341 dst_curves[0].table_entries =
2342 dst_curves[1].table_entries =
2343 dst_curves[2].table_entries = 0;
2344
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002345 skcms_Matrix3x3 from_xyz;
2346
2347 switch (srcFmt >> 1) {
2348 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002349 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2350 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2351 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2352 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2353 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2354 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2355 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2356 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2357 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2358 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2359 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002360 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2361 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 +00002362 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2363 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2364 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2365 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002366
2367 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2368 *args++ = palette;
2369 break;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002370 case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2371 *ops++ = Op_load_8888;
2372 *ops++ = Op_tf_r; *args++ = skcms_sRGB_TransferFunction();
2373 *ops++ = Op_tf_g; *args++ = skcms_sRGB_TransferFunction();
2374 *ops++ = Op_tf_b; *args++ = skcms_sRGB_TransferFunction();
2375 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002376 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002377 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2378 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2379 *ops++ = Op_clamp;
2380 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002381 if (srcFmt & 1) {
2382 *ops++ = Op_swap_rb;
2383 }
2384 skcms_ICCProfile gray_dst_profile;
2385 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2386 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2387 // luminance (Y) by the destination transfer function.
2388 gray_dst_profile = *dstProfile;
2389 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2390 dstProfile = &gray_dst_profile;
2391 }
2392
2393 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2394 // Photoshop creates CMYK images as inverse CMYK.
2395 // These happen to be the only ones we've _ever_ seen.
2396 *ops++ = Op_invert;
2397 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2398 srcAlpha = skcms_AlphaFormat_Unpremul;
2399 }
2400
2401 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2402 *ops++ = Op_force_opaque;
2403 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2404 *ops++ = Op_unpremul;
2405 }
2406
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002407 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002408
2409 if (!prep_for_destination(dstProfile,
skia-autorollf6d50642019-10-14 15:31:13 +00002410 &from_xyz,
2411 &dst_curves[0].parametric,
2412 &dst_curves[1].parametric,
2413 &dst_curves[2].parametric)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002414 return false;
2415 }
2416
2417 if (srcProfile->has_A2B) {
2418 if (srcProfile->A2B.input_channels) {
2419 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2420 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 +00002421 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002422 *ops++ = oa.op;
2423 *args++ = oa.arg;
2424 }
2425 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002426 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002427 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002428 *args++ = &srcProfile->A2B;
2429 }
2430
2431 if (srcProfile->A2B.matrix_channels == 3) {
2432 for (int i = 0; i < 3; i++) {
2433 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 +00002434 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002435 *ops++ = oa.op;
2436 *args++ = oa.arg;
2437 }
2438 }
2439
2440 static const skcms_Matrix3x4 I = {{
2441 {1,0,0,0},
2442 {0,1,0,0},
2443 {0,0,1,0},
2444 }};
2445 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2446 *ops++ = Op_matrix_3x4;
2447 *args++ = &srcProfile->A2B.matrix;
2448 }
2449 }
2450
2451 if (srcProfile->A2B.output_channels == 3) {
2452 for (int i = 0; i < 3; i++) {
2453 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 +00002454 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002455 *ops++ = oa.op;
2456 *args++ = oa.arg;
2457 }
2458 }
2459 }
2460
2461 if (srcProfile->pcs == skcms_Signature_Lab) {
2462 *ops++ = Op_lab_to_xyz;
2463 }
2464
2465 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2466 for (int i = 0; i < 3; i++) {
2467 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002468 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002469 *ops++ = oa.op;
2470 *args++ = oa.arg;
2471 }
2472 }
2473 } else {
2474 return false;
2475 }
2476
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002477 // A2B sources should already be in XYZD50 at this point.
2478 // Others still need to be transformed using their toXYZD50 matrix.
2479 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2480 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2481 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2482 static const skcms_Matrix3x3 I = {{
2483 { 1.0f, 0.0f, 0.0f },
2484 { 0.0f, 1.0f, 0.0f },
2485 { 0.0f, 0.0f, 1.0f },
2486 }};
2487 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2488
2489 // There's a chance the source and destination gamuts are identical,
2490 // in which case we can skip the gamut transform.
2491 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2492 // Concat the entire gamut transform into from_xyz,
2493 // now slightly misnamed but it's a handy spot to stash the result.
2494 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2495 *ops++ = Op_matrix_3x3;
2496 *args++ = &from_xyz;
2497 }
2498
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002499 // Encode back to dst RGB using its parametric transfer functions.
skia-autorollf6d50642019-10-14 15:31:13 +00002500 for (int i = 0; i < 3; i++) {
2501 OpAndArg oa = select_curve_op(dst_curves+i, i);
2502 if (oa.arg) {
2503 assert (oa.op != Op_table_r &&
2504 oa.op != Op_table_g &&
2505 oa.op != Op_table_b &&
2506 oa.op != Op_table_a);
2507 *ops++ = oa.op;
2508 *args++ = oa.arg;
2509 }
2510 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002511 }
2512
skia-autoroll2e4fa242019-03-11 21:14:18 +00002513 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2514 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002515 //
2516 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2517 // 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 +00002518 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2519 *ops++ = Op_clamp;
2520 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002521 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2522 *ops++ = Op_force_opaque;
2523 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2524 *ops++ = Op_premul;
2525 }
2526 if (dstFmt & 1) {
2527 *ops++ = Op_swap_rb;
2528 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002529 switch (dstFmt >> 1) {
2530 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002531 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2532 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2533 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2534 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2535 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2536 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2537 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2538 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2539 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2540 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2541 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002542 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2543 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 +00002544 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2545 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2546 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2547 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_store_ffff; break;
skia-autoroll0f4eba52020-03-24 17:26:57 +00002548
2549 case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2550 *ops++ = Op_tf_r; *args++ = skcms_sRGB_Inverse_TransferFunction();
2551 *ops++ = Op_tf_g; *args++ = skcms_sRGB_Inverse_TransferFunction();
2552 *ops++ = Op_tf_b; *args++ = skcms_sRGB_Inverse_TransferFunction();
2553 *ops++ = Op_store_8888;
2554 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002555 }
2556
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002557 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002558#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002559 switch (cpu_type()) {
2560 case CpuType::None: break;
2561 case CpuType::HSW: run = hsw::run_program; break;
2562 case CpuType::SKX: run = hsw::run_program; break;
2563 }
2564#endif
2565#if defined(TEST_FOR_SKX)
2566 switch (cpu_type()) {
2567 case CpuType::None: break;
2568 case CpuType::HSW: break;
2569 case CpuType::SKX: run = skx::run_program; break;
2570 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002571#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002572 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 +00002573 return true;
2574}
2575
2576static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2577#if defined(NDEBUG)
2578 (void)profile;
2579#else
2580 skcms_Matrix3x3 fromXYZD50;
2581 skcms_TransferFunction invR, invG, invB;
2582 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2583#endif
2584}
2585
2586bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2587 skcms_Matrix3x3 fromXYZD50;
2588 if (!profile->has_trc || !profile->has_toXYZD50
2589 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2590 return false;
2591 }
2592
2593 skcms_TransferFunction tf[3];
2594 for (int i = 0; i < 3; i++) {
2595 skcms_TransferFunction inv;
2596 if (profile->trc[i].table_entries == 0
2597 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2598 tf[i] = profile->trc[i].parametric;
2599 continue;
2600 }
2601
2602 float max_error;
2603 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2604 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2605 return false;
2606 }
2607 }
2608
2609 for (int i = 0; i < 3; ++i) {
2610 profile->trc[i].table_entries = 0;
2611 profile->trc[i].parametric = tf[i];
2612 }
2613
2614 assert_usable_as_destination(profile);
2615 return true;
2616}
2617
2618bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2619 // Operate on a copy of profile, so we can choose the best TF for the original curves
2620 skcms_ICCProfile result = *profile;
2621 if (!skcms_MakeUsableAsDestination(&result)) {
2622 return false;
2623 }
2624
2625 int best_tf = 0;
2626 float min_max_error = INFINITY_;
2627 for (int i = 0; i < 3; i++) {
2628 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002629 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2630 return false;
2631 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002632
2633 float err = 0;
2634 for (int j = 0; j < 3; ++j) {
skia-autorolld95243b2019-11-20 19:40:25 +00002635 err = fmaxf_(err, skcms_MaxRoundtripError(&profile->trc[j], &inv));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002636 }
2637 if (min_max_error > err) {
2638 min_max_error = err;
2639 best_tf = i;
2640 }
2641 }
2642
2643 for (int i = 0; i < 3; i++) {
2644 result.trc[i].parametric = result.trc[best_tf].parametric;
2645 }
2646
2647 *profile = result;
2648 assert_usable_as_destination(profile);
2649 return true;
2650}