blob: 12c12291b385ea1543cfa24c423ade37b7f6d220 [file] [log] [blame]
Mike Kleinded7a552018-04-10 10:05:31 -04001/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00008#include "skcms.h"
9#include "skcms_internal.h"
10#include <assert.h>
11#include <float.h>
12#include <limits.h>
13#include <stdlib.h>
14#include <string.h>
Mike Kleinded7a552018-04-10 10:05:31 -040015
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +000016#if defined(__ARM_NEON)
17 #include <arm_neon.h>
18#elif defined(__SSE__)
19 #include <immintrin.h>
skia-autorollba6087c2019-04-09 13:57:02 +000020
21 #if defined(__clang__)
22 // That #include <immintrin.h> is usually enough, but Clang's headers
23 // "helpfully" skip including the whole kitchen sink when _MSC_VER is
24 // defined, because lots of programs on Windows would include that and
25 // it'd be a lot slower. But we want all those headers included so we
26 // can use their features after runtime checks later.
27 #include <smmintrin.h>
28 #include <avxintrin.h>
29 #include <avx2intrin.h>
30 #include <avx512fintrin.h>
31 #include <avx512dqintrin.h>
32 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +000033#endif
34
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000035// sizeof(x) will return size_t, which is 32-bit on some machines and 64-bit on others.
36// We have better testing on 64-bit machines, so force 32-bit machines to behave like 64-bit.
37//
38// Please do not use sizeof() directly, and size_t only when required.
39// (We have no way of enforcing these requests...)
40#define SAFE_SIZEOF(x) ((uint64_t)sizeof(x))
41
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +000042// Same sort of thing for _Layout structs with a variable sized array at the end (named "variable").
43#define SAFE_FIXED_SIZE(type) ((uint64_t)offsetof(type, variable))
44
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000045static const union {
46 uint32_t bits;
47 float f;
48} inf_ = { 0x7f800000 };
49#define INFINITY_ inf_.f
50
51static float fmaxf_(float x, float y) { return x > y ? x : y; }
52static float fminf_(float x, float y) { return x < y ? x : y; }
53
54static bool isfinitef_(float x) { return 0 == x*0; }
55
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000056static float minus_1_ulp(float x) {
57 int32_t bits;
58 memcpy(&bits, &x, sizeof(bits));
59 bits = bits - 1;
60 memcpy(&x, &bits, sizeof(bits));
61 return x;
62}
63
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000064static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000065 if (curve->table_entries == 0) {
66 return skcms_TransferFunction_eval(&curve->parametric, x);
67 }
68
69 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 +000070 int lo = (int) ix ,
71 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000072 float t = ix - (float)lo;
73
74 float l, h;
75 if (curve->table_8) {
76 l = curve->table_8[lo] * (1/255.0f);
77 h = curve->table_8[hi] * (1/255.0f);
78 } else {
79 uint16_t be_l, be_h;
80 memcpy(&be_l, curve->table_16 + 2*lo, 2);
81 memcpy(&be_h, curve->table_16 + 2*hi, 2);
82 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
83 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
84 l = le_l * (1/65535.0f);
85 h = le_h * (1/65535.0f);
86 }
87 return l + (h-l)*t;
88}
89
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000090static float max_roundtrip_error(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000091 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
92 const float dx = 1.0f / (N - 1);
93 float err = 0;
94 for (uint32_t i = 0; i < N; i++) {
95 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000096 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000097 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
98 }
99 return err;
100}
101
102bool skcms_AreApproximateInverses(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000103 return max_roundtrip_error(curve, inv_tf) < (1/512.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000104}
105
106// Additional ICC signature values that are only used internally
107enum {
108 // File signature
109 skcms_Signature_acsp = 0x61637370,
110
111 // Tag signatures
112 skcms_Signature_rTRC = 0x72545243,
113 skcms_Signature_gTRC = 0x67545243,
114 skcms_Signature_bTRC = 0x62545243,
115 skcms_Signature_kTRC = 0x6B545243,
116
117 skcms_Signature_rXYZ = 0x7258595A,
118 skcms_Signature_gXYZ = 0x6758595A,
119 skcms_Signature_bXYZ = 0x6258595A,
120
121 skcms_Signature_A2B0 = 0x41324230,
122 skcms_Signature_A2B1 = 0x41324231,
123 skcms_Signature_mAB = 0x6D414220,
124
125 skcms_Signature_CHAD = 0x63686164,
126
127 // Type signatures
128 skcms_Signature_curv = 0x63757276,
129 skcms_Signature_mft1 = 0x6D667431,
130 skcms_Signature_mft2 = 0x6D667432,
131 skcms_Signature_para = 0x70617261,
132 skcms_Signature_sf32 = 0x73663332,
133 // XYZ is also a PCS signature, so it's defined in skcms.h
134 // skcms_Signature_XYZ = 0x58595A20,
135};
136
137static uint16_t read_big_u16(const uint8_t* ptr) {
138 uint16_t be;
139 memcpy(&be, ptr, sizeof(be));
140#if defined(_MSC_VER)
141 return _byteswap_ushort(be);
142#else
143 return __builtin_bswap16(be);
144#endif
145}
146
147static uint32_t read_big_u32(const uint8_t* ptr) {
148 uint32_t be;
149 memcpy(&be, ptr, sizeof(be));
150#if defined(_MSC_VER)
151 return _byteswap_ulong(be);
152#else
153 return __builtin_bswap32(be);
154#endif
155}
156
157static int32_t read_big_i32(const uint8_t* ptr) {
158 return (int32_t)read_big_u32(ptr);
159}
160
161static float read_big_fixed(const uint8_t* ptr) {
162 return read_big_i32(ptr) * (1.0f / 65536.0f);
163}
164
165// Maps to an in-memory profile so that fields line up to the locations specified
166// in ICC.1:2010, section 7.2
167typedef struct {
168 uint8_t size [ 4];
169 uint8_t cmm_type [ 4];
170 uint8_t version [ 4];
171 uint8_t profile_class [ 4];
172 uint8_t data_color_space [ 4];
173 uint8_t pcs [ 4];
174 uint8_t creation_date_time [12];
175 uint8_t signature [ 4];
176 uint8_t platform [ 4];
177 uint8_t flags [ 4];
178 uint8_t device_manufacturer [ 4];
179 uint8_t device_model [ 4];
180 uint8_t device_attributes [ 8];
181 uint8_t rendering_intent [ 4];
182 uint8_t illuminant_X [ 4];
183 uint8_t illuminant_Y [ 4];
184 uint8_t illuminant_Z [ 4];
185 uint8_t creator [ 4];
186 uint8_t profile_id [16];
187 uint8_t reserved [28];
188 uint8_t tag_count [ 4]; // Technically not part of header, but required
189} header_Layout;
190
191typedef struct {
192 uint8_t signature [4];
193 uint8_t offset [4];
194 uint8_t size [4];
195} tag_Layout;
196
197static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
198 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
199}
200
201// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
202// use of the type is for the CHAD tag that stores exactly nine values.
203typedef struct {
204 uint8_t type [ 4];
205 uint8_t reserved [ 4];
206 uint8_t values [36];
207} sf32_Layout;
208
209bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
210 skcms_ICCTag tag;
211 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
212 return false;
213 }
214
215 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
216 return false;
217 }
218
219 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
220 const uint8_t* values = sf32Tag->values;
221 for (int r = 0; r < 3; ++r)
222 for (int c = 0; c < 3; ++c, values += 4) {
223 m->vals[r][c] = read_big_fixed(values);
224 }
225 return true;
226}
227
228// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
229// the type are for tags/data that store exactly one triple.
230typedef struct {
231 uint8_t type [4];
232 uint8_t reserved [4];
233 uint8_t X [4];
234 uint8_t Y [4];
235 uint8_t Z [4];
236} XYZ_Layout;
237
238static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
239 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
240 return false;
241 }
242
243 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
244
245 *x = read_big_fixed(xyzTag->X);
246 *y = read_big_fixed(xyzTag->Y);
247 *z = read_big_fixed(xyzTag->Z);
248 return true;
249}
250
251static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
252 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
253 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
254 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
255 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
256}
257
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000258static bool tf_is_valid(const skcms_TransferFunction* tf) {
259 // Reject obviously malformed inputs
260 if (!isfinitef_(tf->a + tf->b + tf->c + tf->d + tf->e + tf->f + tf->g)) {
261 return false;
262 }
263
264 // All of these parameters should be non-negative
265 if (tf->a < 0 || tf->c < 0 || tf->d < 0 || tf->g < 0) {
266 return false;
267 }
268
skia-autorollfe16a332019-08-20 19:44:54 +0000269 // It's rather _complex_ to raise a negative number to a fractional power tf->g.
270 if (tf->a * tf->d + tf->b < 0) {
271 return false;
272 }
273
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000274 return true;
275}
276
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000277typedef struct {
278 uint8_t type [4];
279 uint8_t reserved_a [4];
280 uint8_t function_type [2];
281 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000282 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 +0000283} para_Layout;
284
285static bool read_curve_para(const uint8_t* buf, uint32_t size,
286 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000287 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000288 return false;
289 }
290
291 const para_Layout* paraTag = (const para_Layout*)buf;
292
293 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
294 uint16_t function_type = read_big_u16(paraTag->function_type);
295 if (function_type > kGABCDEF) {
296 return false;
297 }
298
299 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 +0000300 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 +0000301 return false;
302 }
303
304 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000305 *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 +0000306 }
307
308 curve->table_entries = 0;
309 curve->parametric.a = 1.0f;
310 curve->parametric.b = 0.0f;
311 curve->parametric.c = 0.0f;
312 curve->parametric.d = 0.0f;
313 curve->parametric.e = 0.0f;
314 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000315 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000316
317 switch (function_type) {
318 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000319 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
320 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000321 if (curve->parametric.a == 0) {
322 return false;
323 }
324 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
325 break;
326 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000327 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
328 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
329 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000330 if (curve->parametric.a == 0) {
331 return false;
332 }
333 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
334 curve->parametric.f = curve->parametric.e;
335 break;
336 case kGABCD:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000337 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
338 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
339 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
340 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000341 break;
342 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000343 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
344 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
345 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
346 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
347 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
348 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000349 break;
350 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000351 return tf_is_valid(&curve->parametric);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000352}
353
354typedef struct {
355 uint8_t type [4];
356 uint8_t reserved [4];
357 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000358 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 +0000359} curv_Layout;
360
361static bool read_curve_curv(const uint8_t* buf, uint32_t size,
362 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000363 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000364 return false;
365 }
366
367 const curv_Layout* curvTag = (const curv_Layout*)buf;
368
369 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 +0000370 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 +0000371 return false;
372 }
373
374 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000375 *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 +0000376 }
377
378 if (value_count < 2) {
379 curve->table_entries = 0;
380 curve->parametric.a = 1.0f;
381 curve->parametric.b = 0.0f;
382 curve->parametric.c = 0.0f;
383 curve->parametric.d = 0.0f;
384 curve->parametric.e = 0.0f;
385 curve->parametric.f = 0.0f;
386 if (value_count == 0) {
387 // Empty tables are a shorthand for an identity curve
388 curve->parametric.g = 1.0f;
389 } else {
390 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000391 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 +0000392 }
393 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000394 curve->table_8 = nullptr;
395 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000396 curve->table_entries = value_count;
397 }
398
399 return true;
400}
401
402// 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 +0000403// 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 +0000404static bool read_curve(const uint8_t* buf, uint32_t size,
405 skcms_Curve* curve, uint32_t* curve_size) {
406 if (!buf || size < 4 || !curve) {
407 return false;
408 }
409
410 uint32_t type = read_big_u32(buf);
411 if (type == skcms_Signature_para) {
412 return read_curve_para(buf, size, curve, curve_size);
413 } else if (type == skcms_Signature_curv) {
414 return read_curve_curv(buf, size, curve, curve_size);
415 }
416
417 return false;
418}
419
420// mft1 and mft2 share a large chunk of data
421typedef struct {
422 uint8_t type [ 4];
423 uint8_t reserved_a [ 4];
424 uint8_t input_channels [ 1];
425 uint8_t output_channels [ 1];
426 uint8_t grid_points [ 1];
427 uint8_t reserved_b [ 1];
428 uint8_t matrix [36];
429} mft_CommonLayout;
430
431typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000432 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000433
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000434 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000435} mft1_Layout;
436
437typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000438 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000439
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000440 uint8_t input_table_entries [2];
441 uint8_t output_table_entries [2];
442 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000443} mft2_Layout;
444
445static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
446 // MFT matrices are applied before the first set of curves, but must be identity unless the
447 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
448 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
449 // field/flag.
450 a2b->matrix_channels = 0;
451
452 a2b->input_channels = mftTag->input_channels[0];
453 a2b->output_channels = mftTag->output_channels[0];
454
455 // We require exactly three (ie XYZ/Lab/RGB) output channels
456 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
457 return false;
458 }
459 // We require at least one, and no more than four (ie CMYK) input channels
460 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
461 return false;
462 }
463
464 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
465 a2b->grid_points[i] = mftTag->grid_points[0];
466 }
467 // The grid only makes sense with at least two points along each axis
468 if (a2b->grid_points[0] < 2) {
469 return false;
470 }
471
472 return true;
473}
474
475static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
476 uint32_t input_table_entries, uint32_t output_table_entries,
477 skcms_A2B* a2b) {
478 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
479 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
480 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
481
482 // [input|output]_channels are <= 4, so still no overflow
483 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
484 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
485
486 uint64_t grid_size = a2b->output_channels * byte_width;
487 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
488 grid_size *= a2b->grid_points[axis];
489 }
490
491 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
492 return false;
493 }
494
495 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
496 a2b->input_curves[i].table_entries = input_table_entries;
497 if (byte_width == 1) {
498 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 +0000499 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000500 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000501 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000502 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
503 }
504 }
505
506 if (byte_width == 1) {
507 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 +0000508 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000509 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000510 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000511 a2b->grid_16 = table_base + byte_len_all_input_tables;
512 }
513
514 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
515 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
516 a2b->output_curves[i].table_entries = output_table_entries;
517 if (byte_width == 1) {
518 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 +0000519 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000520 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000521 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000522 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
523 }
524 }
525
526 return true;
527}
528
529static 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 +0000530 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000531 return false;
532 }
533
534 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
535 if (!read_mft_common(mftTag->common, a2b)) {
536 return false;
537 }
538
539 uint32_t input_table_entries = 256;
540 uint32_t output_table_entries = 256;
541
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000542 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 +0000543 input_table_entries, output_table_entries, a2b);
544}
545
546static 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 +0000547 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000548 return false;
549 }
550
551 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
552 if (!read_mft_common(mftTag->common, a2b)) {
553 return false;
554 }
555
556 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
557 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
558
559 // ICC spec mandates that 2 <= table_entries <= 4096
560 if (input_table_entries < 2 || input_table_entries > 4096 ||
561 output_table_entries < 2 || output_table_entries > 4096) {
562 return false;
563 }
564
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000565 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 +0000566 input_table_entries, output_table_entries, a2b);
567}
568
569static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
570 uint32_t num_curves, skcms_Curve* curves) {
571 for (uint32_t i = 0; i < num_curves; ++i) {
572 if (curve_offset > size) {
573 return false;
574 }
575
576 uint32_t curve_bytes;
577 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
578 return false;
579 }
580
581 if (curve_bytes > UINT32_MAX - 3) {
582 return false;
583 }
584 curve_bytes = (curve_bytes + 3) & ~3U;
585
586 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
587 curve_offset = (uint32_t)new_offset_64;
588 if (new_offset_64 != curve_offset) {
589 return false;
590 }
591 }
592
593 return true;
594}
595
596typedef struct {
597 uint8_t type [ 4];
598 uint8_t reserved_a [ 4];
599 uint8_t input_channels [ 1];
600 uint8_t output_channels [ 1];
601 uint8_t reserved_b [ 2];
602 uint8_t b_curve_offset [ 4];
603 uint8_t matrix_offset [ 4];
604 uint8_t m_curve_offset [ 4];
605 uint8_t clut_offset [ 4];
606 uint8_t a_curve_offset [ 4];
607} mAB_Layout;
608
609typedef struct {
610 uint8_t grid_points [16];
611 uint8_t grid_byte_width [ 1];
612 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000613 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000614} mABCLUT_Layout;
615
616static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
617 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
618 return false;
619 }
620
621 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
622
623 a2b->input_channels = mABTag->input_channels[0];
624 a2b->output_channels = mABTag->output_channels[0];
625
626 // We require exactly three (ie XYZ/Lab/RGB) output channels
627 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
628 return false;
629 }
630 // We require no more than four (ie CMYK) input channels
631 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
632 return false;
633 }
634
635 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
636 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
637 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
638 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
639 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
640
641 // "B" curves must be present
642 if (0 == b_curve_offset) {
643 return false;
644 }
645
646 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
647 a2b->output_curves)) {
648 return false;
649 }
650
651 // "M" curves and Matrix must be used together
652 if (0 != m_curve_offset) {
653 if (0 == matrix_offset) {
654 return false;
655 }
656 a2b->matrix_channels = a2b->output_channels;
657 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
658 a2b->matrix_curves)) {
659 return false;
660 }
661
662 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
663 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
664 return false;
665 }
666 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
667 const uint8_t* mtx_buf = tag->buf + matrix_offset;
668 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
669 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
670 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
671 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
672 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
673 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
674 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
675 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
676 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
677 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
678 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
679 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
680 } else {
681 if (0 != matrix_offset) {
682 return false;
683 }
684 a2b->matrix_channels = 0;
685 }
686
687 // "A" curves and CLUT must be used together
688 if (0 != a_curve_offset) {
689 if (0 == clut_offset) {
690 return false;
691 }
692 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
693 a2b->input_curves)) {
694 return false;
695 }
696
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000697 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 +0000698 return false;
699 }
700 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
701
702 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000703 a2b->grid_8 = clut->variable;
704 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000705 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000706 a2b->grid_8 = nullptr;
707 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000708 } else {
709 return false;
710 }
711
712 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
713 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
714 a2b->grid_points[i] = clut->grid_points[i];
715 // The grid only makes sense with at least two points along each axis
716 if (a2b->grid_points[i] < 2) {
717 return false;
718 }
719 grid_size *= a2b->grid_points[i];
720 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000721 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 +0000722 return false;
723 }
724 } else {
725 if (0 != clut_offset) {
726 return false;
727 }
728
729 // If there is no CLUT, the number of input and output channels must match
730 if (a2b->input_channels != a2b->output_channels) {
731 return false;
732 }
733
734 // Zero out the number of input channels to signal that we're skipping this stage
735 a2b->input_channels = 0;
736 }
737
738 return true;
739}
740
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000741// If you pass f, we'll fit a possibly-non-zero value for *f.
742// If you pass nullptr, we'll assume you want *f to be treated as zero.
743static int fit_linear(const skcms_Curve* curve, int N, float tol,
744 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000745 assert(N > 1);
746 // We iteratively fit the first points to the TF's linear piece.
747 // We want the cx + f line to pass through the first and last points we fit exactly.
748 //
749 // As we walk along the points we find the minimum and maximum slope of the line before the
750 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
751 // emtpy, when we definitely can't add any more points.
752 //
753 // Some points' error intervals may intersect the running interval but not lie fully
754 // within it. So we keep track of the last point we saw that is a valid end point candidate,
755 // and once the search is done, back up to build the line through *that* point.
756 const float dx = 1.0f / (N - 1);
757
758 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000759
760 float f_zero = 0.0f;
761 if (f) {
762 *f = eval_curve(curve, 0);
763 } else {
764 f = &f_zero;
765 }
766
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000767
768 float slope_min = -INFINITY_;
769 float slope_max = +INFINITY_;
770 for (int i = 1; i < N; ++i) {
771 float x = i * dx;
772 float y = eval_curve(curve, x);
773
774 float slope_max_i = (y + tol - *f) / x,
775 slope_min_i = (y - tol - *f) / x;
776 if (slope_max_i < slope_min || slope_max < slope_min_i) {
777 // Slope intervals would no longer overlap.
778 break;
779 }
780 slope_max = fminf_(slope_max, slope_max_i);
781 slope_min = fmaxf_(slope_min, slope_min_i);
782
783 float cur_slope = (y - *f) / x;
784 if (slope_min <= cur_slope && cur_slope <= slope_max) {
785 lin_points = i + 1;
786 *c = cur_slope;
787 }
788 }
789
790 // Set D to the last point that met our tolerance.
791 *d = (lin_points - 1) * dx;
792 return lin_points;
793}
794
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000795static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
796 bool ok = false;
797 if (tag->type == skcms_Signature_mft1) {
798 ok = read_tag_mft1(tag, a2b);
799 } else if (tag->type == skcms_Signature_mft2) {
800 ok = read_tag_mft2(tag, a2b);
801 } else if (tag->type == skcms_Signature_mAB) {
802 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
803 }
804 if (!ok) {
805 return false;
806 }
807
808 // Detect and canonicalize identity tables.
809 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000810 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
811 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
812 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
813 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
814 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
815 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
816 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
817 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
818 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
819 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000820 };
821
822 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
823 skcms_Curve* curve = curves[i];
824
825 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
826 int N = (int)curve->table_entries;
827
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000828 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 +0000829 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 +0000830 && c == 1.0f
831 && f == 0.0f) {
832 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000833 curve->table_8 = nullptr;
834 curve->table_16 = nullptr;
835 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 +0000836 }
837 }
838 }
839
840 return true;
841}
842
843void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
844 if (!profile || !profile->buffer || !tag) { return; }
845 if (idx > profile->tag_count) { return; }
846 const tag_Layout* tags = get_tag_table(profile);
847 tag->signature = read_big_u32(tags[idx].signature);
848 tag->size = read_big_u32(tags[idx].size);
849 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
850 tag->type = read_big_u32(tag->buf);
851}
852
853bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
854 if (!profile || !profile->buffer || !tag) { return false; }
855 const tag_Layout* tags = get_tag_table(profile);
856 for (uint32_t i = 0; i < profile->tag_count; ++i) {
857 if (read_big_u32(tags[i].signature) == sig) {
858 tag->signature = sig;
859 tag->size = read_big_u32(tags[i].size);
860 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
861 tag->type = read_big_u32(tag->buf);
862 return true;
863 }
864 }
865 return false;
866}
867
868static bool usable_as_src(const skcms_ICCProfile* profile) {
869 return profile->has_A2B
870 || (profile->has_trc && profile->has_toXYZD50);
871}
872
873bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
874 assert(SAFE_SIZEOF(header_Layout) == 132);
875
876 if (!profile) {
877 return false;
878 }
879 memset(profile, 0, SAFE_SIZEOF(*profile));
880
881 if (len < SAFE_SIZEOF(header_Layout)) {
882 return false;
883 }
884
885 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000886 const header_Layout* header = (const header_Layout*)buf;
887 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000888 profile->size = read_big_u32(header->size);
889 uint32_t version = read_big_u32(header->version);
890 profile->data_color_space = read_big_u32(header->data_color_space);
891 profile->pcs = read_big_u32(header->pcs);
892 uint32_t signature = read_big_u32(header->signature);
893 float illuminant_X = read_big_fixed(header->illuminant_X);
894 float illuminant_Y = read_big_fixed(header->illuminant_Y);
895 float illuminant_Z = read_big_fixed(header->illuminant_Z);
896 profile->tag_count = read_big_u32(header->tag_count);
897
898 // Validate signature, size (smaller than buffer, large enough to hold tag table),
899 // and major version
900 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
901 if (signature != skcms_Signature_acsp ||
902 profile->size > len ||
903 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
904 (version >> 24) > 4) {
905 return false;
906 }
907
908 // Validate that illuminant is D50 white
909 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
910 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
911 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
912 return false;
913 }
914
915 // Validate that all tag entries have sane offset + size
916 const tag_Layout* tags = get_tag_table(profile);
917 for (uint32_t i = 0; i < profile->tag_count; ++i) {
918 uint32_t tag_offset = read_big_u32(tags[i].offset);
919 uint32_t tag_size = read_big_u32(tags[i].size);
920 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
921 if (tag_size < 4 || tag_end > profile->size) {
922 return false;
923 }
924 }
925
926 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
927 return false;
928 }
929
930 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
931
932 // Pre-parse commonly used tags.
933 skcms_ICCTag kTRC;
934 if (profile->data_color_space == skcms_Signature_Gray &&
935 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000936 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 +0000937 // Malformed tag
938 return false;
939 }
940 profile->trc[1] = profile->trc[0];
941 profile->trc[2] = profile->trc[0];
942 profile->has_trc = true;
943
944 if (pcs_is_xyz) {
945 profile->toXYZD50.vals[0][0] = illuminant_X;
946 profile->toXYZD50.vals[1][1] = illuminant_Y;
947 profile->toXYZD50.vals[2][2] = illuminant_Z;
948 profile->has_toXYZD50 = true;
949 }
950 } else {
951 skcms_ICCTag rTRC, gTRC, bTRC;
952 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
953 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
954 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000955 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
956 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
957 !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 +0000958 // Malformed TRC tags
959 return false;
960 }
961 profile->has_trc = true;
962 }
963
964 skcms_ICCTag rXYZ, gXYZ, bXYZ;
965 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
966 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
967 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
968 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
969 // Malformed XYZ tags
970 return false;
971 }
972 profile->has_toXYZD50 = true;
973 }
974 }
975
976 skcms_ICCTag a2b_tag;
977
978 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
979 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
980 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
981 // and all our known users are thinking exclusively in terms of relative colormetric.
982 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
983 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
984 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
985 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
986 // Malformed A2B tag
987 return false;
988 }
989 profile->has_A2B = true;
990 break;
991 }
992 }
993
994 return usable_as_src(profile);
995}
996
997
998const skcms_ICCProfile* skcms_sRGB_profile() {
999 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001000 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001001
1002 0, // size, moot here
1003 skcms_Signature_RGB, // data_color_space
1004 skcms_Signature_XYZ, // pcs
1005 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001006
1007 // We choose to represent sRGB with its canonical transfer function,
1008 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001009 true, // has_trc, followed by the 3 trc curves
1010 {
1011 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1012 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1013 {{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 +00001014 },
1015
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001016 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1017 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001018 { 0.436065674f, 0.385147095f, 0.143066406f },
1019 { 0.222488403f, 0.716873169f, 0.060607910f },
1020 { 0.013916016f, 0.097076416f, 0.714096069f },
1021 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001022
1023 false, // has_A2B, followed by a2b itself which we don't care about.
1024 {
1025 0,
1026 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001027 {{0, {0,0, 0,0,0,0,0}}},
1028 {{0, {0,0, 0,0,0,0,0}}},
1029 {{0, {0,0, 0,0,0,0,0}}},
1030 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001031 },
1032 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001033 nullptr,
1034 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001035
1036 0,
1037 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001038 {{0, {0,0, 0,0,0,0,0}}},
1039 {{0, {0,0, 0,0,0,0,0}}},
1040 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001041 },
1042 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001043 { 0,0,0,0 },
1044 { 0,0,0,0 },
1045 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001046 }},
1047
1048 0,
1049 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001050 {{0, {0,0, 0,0,0,0,0}}},
1051 {{0, {0,0, 0,0,0,0,0}}},
1052 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001053 },
1054 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001055 };
1056 return &sRGB_profile;
1057}
1058
1059const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001060 // 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 +00001061 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001062 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001063
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001064 0, // size, moot here
1065 skcms_Signature_RGB, // data_color_space
1066 skcms_Signature_XYZ, // pcs
1067 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001068
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001069 true, // has_trc, followed by the 3 trc curves
1070 {
1071 {{0, {1,1, 0,0,0,0,0}}},
1072 {{0, {1,1, 0,0,0,0,0}}},
1073 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001074 },
1075
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001076 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1077 {{
1078 { 1,0,0 },
1079 { 0,1,0 },
1080 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001081 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001082
1083 false, // has_A2B, followed by a2b itself which we don't care about.
1084 {
1085 0,
1086 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001087 {{0, {0,0, 0,0,0,0,0}}},
1088 {{0, {0,0, 0,0,0,0,0}}},
1089 {{0, {0,0, 0,0,0,0,0}}},
1090 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001091 },
1092 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001093 nullptr,
1094 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001095
1096 0,
1097 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001098 {{0, {0,0, 0,0,0,0,0}}},
1099 {{0, {0,0, 0,0,0,0,0}}},
1100 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001101 },
1102 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001103 { 0,0,0,0 },
1104 { 0,0,0,0 },
1105 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001106 }},
1107
1108 0,
1109 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001110 {{0, {0,0, 0,0,0,0,0}}},
1111 {{0, {0,0, 0,0,0,0,0}}},
1112 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001113 },
1114 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001115 };
1116
1117 return &XYZD50_profile;
1118}
1119
1120const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1121 return &skcms_sRGB_profile()->trc[0].parametric;
1122}
1123
1124const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1125 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001126 {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 +00001127 return &sRGB_inv;
1128}
1129
1130const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1131 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1132 return &identity;
1133}
1134
1135const uint8_t skcms_252_random_bytes[] = {
1136 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1137 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1138 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1139 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1140 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1141 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1142 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1143 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1144 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1145 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1146 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1147 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1148 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1149 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1150 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1151 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1152};
1153
1154bool 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 +00001155 // Test for exactly equal profiles first.
1156 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1157 return true;
1158 }
1159
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001160 // For now this is the essentially the same strategy we use in test_only.c
1161 // for our skcms_Transform() smoke tests:
1162 // 1) transform A to XYZD50
1163 // 2) transform B to XYZD50
1164 // 3) return true if they're similar enough
1165 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1166
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001167 // 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 +00001168 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1169
1170 if (A->data_color_space != B->data_color_space) {
1171 return false;
1172 }
1173
1174 // 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 +00001175 // 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 +00001176 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1177 size_t npixels = 84;
1178 if (A->data_color_space == skcms_Signature_CMYK) {
1179 fmt = skcms_PixelFormat_RGBA_8888;
1180 npixels = 63;
1181 }
1182
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001183 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1184 // 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 +00001185 uint8_t dstA[252],
1186 dstB[252];
1187 if (!skcms_Transform(
1188 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1189 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1190 npixels)) {
1191 return false;
1192 }
1193 if (!skcms_Transform(
1194 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1195 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1196 npixels)) {
1197 return false;
1198 }
1199
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001200 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001201 for (size_t i = 0; i < 252; i++) {
1202 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1203 return false;
1204 }
1205 }
1206 return true;
1207}
1208
1209bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1210 const skcms_TransferFunction* inv_tf) {
1211 if (!profile || !profile->has_trc) {
1212 return false;
1213 }
1214
1215 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1216 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1217 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1218}
1219
1220static bool is_zero_to_one(float x) {
1221 return 0 <= x && x <= 1;
1222}
1223
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001224typedef struct { float vals[3]; } skcms_Vector3;
1225
1226static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1227 skcms_Vector3 dst = {{0,0,0}};
1228 for (int row = 0; row < 3; ++row) {
1229 dst.vals[row] = m->vals[row][0] * v->vals[0]
1230 + m->vals[row][1] * v->vals[1]
1231 + m->vals[row][2] * v->vals[2];
1232 }
1233 return dst;
1234}
1235
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001236bool skcms_PrimariesToXYZD50(float rx, float ry,
1237 float gx, float gy,
1238 float bx, float by,
1239 float wx, float wy,
1240 skcms_Matrix3x3* toXYZD50) {
1241 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1242 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1243 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1244 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1245 !toXYZD50) {
1246 return false;
1247 }
1248
1249 // First, we need to convert xy values (primaries) to XYZ.
1250 skcms_Matrix3x3 primaries = {{
1251 { rx, gx, bx },
1252 { ry, gy, by },
1253 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1254 }};
1255 skcms_Matrix3x3 primaries_inv;
1256 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1257 return false;
1258 }
1259
1260 // Assumes that Y is 1.0f.
1261 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 +00001262 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001263
1264 skcms_Matrix3x3 toXYZ = {{
1265 { XYZ.vals[0], 0, 0 },
1266 { 0, XYZ.vals[1], 0 },
1267 { 0, 0, XYZ.vals[2] },
1268 }};
1269 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1270
1271 // Now convert toXYZ matrix to toXYZD50.
1272 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1273
1274 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1275 // the matrices below. The Bradford method is used by Adobe and is widely considered
1276 // to be the best.
1277 skcms_Matrix3x3 xyz_to_lms = {{
1278 { 0.8951f, 0.2664f, -0.1614f },
1279 { -0.7502f, 1.7135f, 0.0367f },
1280 { 0.0389f, -0.0685f, 1.0296f },
1281 }};
1282 skcms_Matrix3x3 lms_to_xyz = {{
1283 { 0.9869929f, -0.1470543f, 0.1599627f },
1284 { 0.4323053f, 0.5183603f, 0.0492912f },
1285 { -0.0085287f, 0.0400428f, 0.9684867f },
1286 }};
1287
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001288 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1289 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001290
1291 skcms_Matrix3x3 DXtoD50 = {{
1292 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1293 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1294 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1295 }};
1296 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1297 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1298
1299 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1300 return true;
1301}
1302
1303
1304bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1305 double a00 = src->vals[0][0],
1306 a01 = src->vals[1][0],
1307 a02 = src->vals[2][0],
1308 a10 = src->vals[0][1],
1309 a11 = src->vals[1][1],
1310 a12 = src->vals[2][1],
1311 a20 = src->vals[0][2],
1312 a21 = src->vals[1][2],
1313 a22 = src->vals[2][2];
1314
1315 double b0 = a00*a11 - a01*a10,
1316 b1 = a00*a12 - a02*a10,
1317 b2 = a01*a12 - a02*a11,
1318 b3 = a20,
1319 b4 = a21,
1320 b5 = a22;
1321
1322 double determinant = b0*b5
1323 - b1*b4
1324 + b2*b3;
1325
1326 if (determinant == 0) {
1327 return false;
1328 }
1329
1330 double invdet = 1.0 / determinant;
1331 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1332 return false;
1333 }
1334
1335 b0 *= invdet;
1336 b1 *= invdet;
1337 b2 *= invdet;
1338 b3 *= invdet;
1339 b4 *= invdet;
1340 b5 *= invdet;
1341
1342 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1343 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1344 dst->vals[2][0] = (float)( + b2 );
1345 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1346 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1347 dst->vals[2][1] = (float)( - b1 );
1348 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1349 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1350 dst->vals[2][2] = (float)( + b0 );
1351
1352 for (int r = 0; r < 3; ++r)
1353 for (int c = 0; c < 3; ++c) {
1354 if (!isfinitef_(dst->vals[r][c])) {
1355 return false;
1356 }
1357 }
1358 return true;
1359}
1360
1361skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1362 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1363 for (int r = 0; r < 3; r++)
1364 for (int c = 0; c < 3; c++) {
1365 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1366 + A->vals[r][1] * B->vals[1][c]
1367 + A->vals[r][2] * B->vals[2][c];
1368 }
1369 return m;
1370}
1371
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001372#if defined(__clang__) || defined(__GNUC__)
1373 #define small_memcpy __builtin_memcpy
1374#else
1375 #define small_memcpy memcpy
1376#endif
1377
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001378static float log2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001379 // The first approximation of log2(x) is its exponent 'e', minus 127.
1380 int32_t bits;
1381 small_memcpy(&bits, &x, sizeof(bits));
1382
1383 float e = (float)bits * (1.0f / (1<<23));
1384
1385 // If we use the mantissa too we can refine the error signficantly.
1386 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
1387 float m;
1388 small_memcpy(&m, &m_bits, sizeof(m));
1389
1390 return (e - 124.225514990f
1391 - 1.498030302f*m
1392 - 1.725879990f/(0.3520887068f + m));
1393}
1394
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001395static float exp2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001396 float fract = x - floorf_(x);
1397
1398 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
1399 - 1.490129070f*fract
1400 + 27.728023300f/(4.84252568f - fract));
1401 if (fbits > INT_MAX) {
1402 return INFINITY_;
1403 } else if (fbits < INT_MIN) {
1404 return -INFINITY_;
1405 }
1406 int32_t bits = (int32_t)fbits;
1407 small_memcpy(&x, &bits, sizeof(x));
1408 return x;
1409}
1410
1411float powf_(float x, float y) {
skia-autorollfe16a332019-08-20 19:44:54 +00001412 assert (x >= 0);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001413 return (x == 0) || (x == 1) ? x
1414 : exp2f_(log2f_(x) * y);
1415}
1416
1417float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
1418 float sign = x < 0 ? -1.0f : 1.0f;
1419 x *= sign;
1420
1421 return sign * (x < tf->d ? tf->c * x + tf->f
1422 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
1423}
1424
skia-autorollacd6e012019-01-08 14:10:52 +00001425#if defined(__clang__)
1426 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by tf_is_valid() on the way out.
1427#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001428bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001429 if (!tf_is_valid(src)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001430 return false;
1431 }
1432
skia-autorolld0b577f2019-01-07 19:46:57 +00001433 // We're inverting this function, solving for x in terms of y.
1434 // y = (cx + f) x < d
1435 // (ax + b)^g + e x ≥ d
1436 // The inverse of this function can be expressed in the same piecewise form.
1437 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 +00001438
skia-autorolld0b577f2019-01-07 19:46:57 +00001439 // We'll start by finding the new threshold inv.d.
1440 // In principle we should be able to find that by solving for y at x=d from either side.
1441 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1442 float d_l = src->c * src->d + src->f,
1443 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1444 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001445 return false;
1446 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001447 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 +00001448
skia-autorolld0b577f2019-01-07 19:46:57 +00001449 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1450 if (inv.d > 0) {
1451 // Inverting the linear section is pretty straightfoward:
1452 // y = cx + f
1453 // y - f = cx
1454 // (1/c)y - f/c = x
1455 inv.c = 1.0f/src->c;
1456 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001457 }
1458
skia-autorolld0b577f2019-01-07 19:46:57 +00001459 // The interesting part is inverting the nonlinear section:
1460 // y = (ax + b)^g + e.
1461 // y - e = (ax + b)^g
1462 // (y - e)^1/g = ax + b
1463 // (y - e)^1/g - b = ax
1464 // (1/a)(y - e)^1/g - b/a = x
1465 //
1466 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1467 // let k = (1/a)^g
1468 // (1/a)( y - e)^1/g - b/a = x
1469 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001470
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001471 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
skia-autorolld0b577f2019-01-07 19:46:57 +00001472 inv.g = 1.0f / src->g;
1473 inv.a = k;
1474 inv.b = -k * src->e;
1475 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001476
skia-autorollfe16a332019-08-20 19:44:54 +00001477 // We need to enforce the same constraints here that we do when fitting a curve,
1478 // a >= 0 and ad+b >= 0. These constraints are checked by tf_is_valid(), so they're true
skia-autorollbf388232019-08-21 14:17:54 +00001479 // of the source function if we're here.
skia-autorollfe16a332019-08-20 19:44:54 +00001480
skia-autorollbf388232019-08-21 14:17:54 +00001481 // Just like when fitting the curve, there's really no way to rescue a < 0.
1482 if (inv.a < 0) {
1483 return false;
1484 }
1485 // On the other hand we can rescue an ad+b that's gone slightly negative here.
skia-autorollfe16a332019-08-20 19:44:54 +00001486 if (inv.a * inv.d + inv.b < 0) {
1487 inv.b = -inv.a * inv.d;
1488 }
skia-autorollbf388232019-08-21 14:17:54 +00001489
1490 assert (inv.a >= 0);
skia-autorollfe16a332019-08-20 19:44:54 +00001491 assert (inv.a * inv.d + inv.b >= 0);
1492
skia-autorolla7b28742019-01-09 18:35:46 +00001493 // Now in principle we're done.
1494 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f,
1495 // we'll tweak e. These two values should be close to each other,
1496 // just down to numerical precision issues, especially from powf_.
1497 float s = powf_(src->a + src->b, src->g) + src->e;
1498 inv.e = 1.0f - powf_(inv.a * s + inv.b, inv.g);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001499
skia-autorolld0b577f2019-01-07 19:46:57 +00001500 *dst = inv;
1501 return tf_is_valid(dst);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001502}
1503
1504// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1505
1506// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1507//
1508// tf(x) = cx + f x < d
1509// tf(x) = (ax + b)^g + e x ≥ d
1510//
1511// When fitting, we add the additional constraint that both pieces meet at d:
1512//
1513// cd + f = (ad + b)^g + e
1514//
1515// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1516//
1517// tf(x) = cx + f x < d
1518// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1519//
1520// Our overall strategy is then:
1521// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001522// - 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 +00001523// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001524// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1525// (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 +00001526// Return the parameters with least maximum error.
1527//
1528// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1529// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1530//
1531// let y = Table(x)
1532// r(x) = x - f_inv(y)
1533//
1534// ∂r/∂g = ln(ay + b)*(ay + b)^g
1535// - ln(ad + b)*(ad + b)^g
1536// ∂r/∂a = yg(ay + b)^(g-1)
1537// - dg(ad + b)^(g-1)
1538// ∂r/∂b = g(ay + b)^(g-1)
1539// - g(ad + b)^(g-1)
1540
1541// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1542// and fill out the gradient of the residual into dfdP.
1543static float rg_nonlinear(float x,
1544 const skcms_Curve* curve,
1545 const skcms_TransferFunction* tf,
1546 const float P[3],
1547 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001548 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001549
1550 const float g = P[0], a = P[1], b = P[2],
1551 c = tf->c, d = tf->d, f = tf->f;
1552
1553 const float Y = fmaxf_(a*y + b, 0.0f),
1554 D = a*d + b;
1555 assert (D >= 0);
1556
1557 // The gradient.
1558 dfdP[0] = 0.69314718f*log2f_(Y)*powf_(Y, g)
1559 - 0.69314718f*log2f_(D)*powf_(D, g);
1560 dfdP[1] = y*g*powf_(Y, g-1)
1561 - d*g*powf_(D, g-1);
1562 dfdP[2] = g*powf_(Y, g-1)
1563 - g*powf_(D, g-1);
1564
1565 // The residual.
1566 const float f_inv = powf_(Y, g)
1567 - powf_(D, g)
1568 + c*d + f;
1569 return x - f_inv;
1570}
1571
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001572static bool gauss_newton_step(const skcms_Curve* curve,
1573 const skcms_TransferFunction* tf,
1574 float P[3],
1575 float x0, float dx, int N) {
1576 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1577 //
1578 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1579 // where r(P) is the residual vector
1580 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1581 //
1582 // Let's review the shape of each of these expressions:
1583 // r(P) is [N x 1], a column vector with one entry per value of x tested
1584 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1585 // Jf^T is [3 x N], the transpose of Jf
1586 //
1587 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1588 // and so is its inverse (Jf^T Jf)^-1
1589 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1590 //
1591 // Our implementation strategy to get to the final ∆P is
1592 // 1) evaluate Jf^T Jf, call that lhs
1593 // 2) evaluate Jf^T r(P), call that rhs
1594 // 3) invert lhs
1595 // 4) multiply inverse lhs by rhs
1596 //
1597 // This is a friendly implementation strategy because we don't have to have any
1598 // buffers that scale with N, and equally nice don't have to perform any matrix
1599 // operations that are variable size.
1600 //
1601 // Other implementation strategies could trade this off, e.g. evaluating the
1602 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1603 // the residuals. That would probably require implementing singular value
1604 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1605 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1606 // possibility of this gauss_newton_step() function ever failing.
1607
1608 // 0) start off with lhs and rhs safely zeroed.
1609 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1610 skcms_Vector3 rhs = { {0,0,0} };
1611
1612 // 1,2) evaluate lhs and evaluate rhs
1613 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1614 // so we'll have to update lhs and rhs at the same time.
1615 for (int i = 0; i < N; i++) {
1616 float x = x0 + i*dx;
1617
1618 float dfdP[3] = {0,0,0};
1619 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1620
1621 for (int r = 0; r < 3; r++) {
1622 for (int c = 0; c < 3; c++) {
1623 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1624 }
1625 rhs.vals[r] += dfdP[r] * resid;
1626 }
1627 }
1628
1629 // If any of the 3 P parameters are unused, this matrix will be singular.
1630 // Detect those cases and fix them up to indentity instead, so we can invert.
1631 for (int k = 0; k < 3; k++) {
1632 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1633 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1634 lhs.vals[k][k] = 1;
1635 }
1636 }
1637
1638 // 3) invert lhs
1639 skcms_Matrix3x3 lhs_inv;
1640 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1641 return false;
1642 }
1643
1644 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001645 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001646 P[0] += dP.vals[0];
1647 P[1] += dP.vals[1];
1648 P[2] += dP.vals[2];
1649 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1650}
1651
1652
1653// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1654static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1655 float P[3] = { tf->g, tf->a, tf->b };
1656
1657 // No matter where we start, dx should always represent N even steps from 0 to 1.
1658 const float dx = 1.0f / (N-1);
1659
1660 for (int j = 0; j < 3/*TODO: tune*/; j++) {
1661 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1662 // We don't really know how to fix up a if it goes negative.
1663 if (P[1] < 0) {
1664 return false;
1665 }
1666 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1667 if (P[1] * tf->d + P[2] < 0) {
1668 P[2] = -P[1] * tf->d;
1669 }
1670 assert (P[1] >= 0 &&
1671 P[1] * tf->d + P[2] >= 0);
1672
1673 if (!gauss_newton_step(curve, tf,
1674 P,
1675 L*dx, dx, N-L)) {
1676 return false;
1677 }
1678 }
1679
1680 // We need to apply our fixups one last time
1681 if (P[1] < 0) {
1682 return false;
1683 }
1684 if (P[1] * tf->d + P[2] < 0) {
1685 P[2] = -P[1] * tf->d;
1686 }
1687
skia-autorollfe16a332019-08-20 19:44:54 +00001688 assert (P[1] >= 0 &&
1689 P[1] * tf->d + P[2] >= 0);
1690
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001691 tf->g = P[0];
1692 tf->a = P[1];
1693 tf->b = P[2];
1694 tf->e = tf->c*tf->d + tf->f
1695 - powf_(tf->a*tf->d + tf->b, tf->g);
1696 return true;
1697}
1698
1699bool skcms_ApproximateCurve(const skcms_Curve* curve,
1700 skcms_TransferFunction* approx,
1701 float* max_error) {
1702 if (!curve || !approx || !max_error) {
1703 return false;
1704 }
1705
1706 if (curve->table_entries == 0) {
1707 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1708 return false;
1709 }
1710
1711 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1712 // We need at least two points, and must put some reasonable cap on the maximum number.
1713 return false;
1714 }
1715
1716 int N = (int)curve->table_entries;
1717 const float dx = 1.0f / (N - 1);
1718
1719 *max_error = INFINITY_;
1720 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1721 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1722 skcms_TransferFunction tf,
1723 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001724
1725 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1726 tf.f = 0.0f;
1727 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 +00001728
1729 if (L == N) {
1730 // If the entire data set was linear, move the coefficients to the nonlinear portion
1731 // with G == 1. This lets use a canonical representation with d == 0.
1732 tf.g = 1;
1733 tf.a = tf.c;
1734 tf.b = tf.f;
1735 tf.c = tf.d = tf.e = tf.f = 0;
1736 } else if (L == N - 1) {
1737 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1738 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001739 tf.a = (eval_curve(curve, (N-1)*dx) -
1740 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001741 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001742 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001743 - tf.a * (N-2)*dx;
1744 tf.e = 0;
1745 } else {
1746 // Start by guessing a gamma-only curve through the midpoint.
1747 int mid = (L + N) / 2;
1748 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001749 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001750 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001751 tf.a = 1;
1752 tf.b = 0;
1753 tf.e = tf.c*tf.d + tf.f
1754 - powf_(tf.a*tf.d + tf.b, tf.g);
1755
1756
1757 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1758 !fit_nonlinear(curve, L,N, &tf_inv)) {
1759 continue;
1760 }
1761
1762 // We fit tf_inv, so calculate tf to keep in sync.
1763 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1764 continue;
1765 }
1766 }
1767
1768 // We find our error by roundtripping the table through tf_inv.
1769 //
1770 // (The most likely use case for this approximation is to be inverted and
1771 // used as the transfer function for a destination color space.)
1772 //
1773 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1774 // invertible, so re-verify that here (and use the new inverse for testing).
1775 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1776 continue;
1777 }
1778
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001779 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001780 if (*max_error > err) {
1781 *max_error = err;
1782 *approx = tf;
1783 }
1784 }
1785 return isfinitef_(*max_error);
1786}
1787
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001788// ~~~~ Impl. of skcms_Transform() ~~~~
1789
1790typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001791 Op_load_a8,
1792 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001793 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001794 Op_load_4444,
1795 Op_load_565,
1796 Op_load_888,
1797 Op_load_8888,
1798 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001799 Op_load_161616LE,
1800 Op_load_16161616LE,
1801 Op_load_161616BE,
1802 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001803 Op_load_hhh,
1804 Op_load_hhhh,
1805 Op_load_fff,
1806 Op_load_ffff,
1807
1808 Op_swap_rb,
1809 Op_clamp,
1810 Op_invert,
1811 Op_force_opaque,
1812 Op_premul,
1813 Op_unpremul,
1814 Op_matrix_3x3,
1815 Op_matrix_3x4,
1816 Op_lab_to_xyz,
1817
1818 Op_tf_r,
1819 Op_tf_g,
1820 Op_tf_b,
1821 Op_tf_a,
1822
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001823 Op_table_r,
1824 Op_table_g,
1825 Op_table_b,
1826 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001827
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001828 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001829
1830 Op_store_a8,
1831 Op_store_g8,
1832 Op_store_4444,
1833 Op_store_565,
1834 Op_store_888,
1835 Op_store_8888,
1836 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001837 Op_store_161616LE,
1838 Op_store_16161616LE,
1839 Op_store_161616BE,
1840 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001841 Op_store_hhh,
1842 Op_store_hhhh,
1843 Op_store_fff,
1844 Op_store_ffff,
1845} Op;
1846
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001847#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001848 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 +00001849#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001850 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1851 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1852 template <int N, typename T>
1853 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 +00001854
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001855 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 +00001856#endif
1857
1858// First, instantiate our default exec_ops() implementation using the default compiliation target.
1859
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001860namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00001861#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
1862 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001863 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00001864 template <typename T> using V = T;
1865 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001866#elif defined(__AVX512F__)
1867 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001868 template <typename T> using V = Vec<N,T>;
1869 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001870#elif defined(__AVX__)
1871 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001872 template <typename T> using V = Vec<N,T>;
1873 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00001874#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
1875 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001876 template <typename T> using V = Vec<N,T>;
1877 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001878#else
1879 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00001880 template <typename T> using V = Vec<N,T>;
1881 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001882#endif
1883
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001884 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001885 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001886}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001887
1888// 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 +00001889#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00001890 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00001891 (( defined(__clang__) && __clang_major__ >= 5) || \
1892 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00001893 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001894
skia-autorollba6087c2019-04-09 13:57:02 +00001895 #if !defined(__AVX2__)
1896 #if defined(__clang__)
1897 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
1898 #elif defined(__GNUC__)
1899 #pragma GCC push_options
1900 #pragma GCC target("avx2,f16c")
1901 #endif
1902
1903 namespace hsw {
1904 #define USING_AVX
1905 #define USING_AVX_F16C
1906 #define USING_AVX2
1907 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001908 template <typename T> using V = Vec<N,T>;
1909 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00001910
1911 #include "src/Transform_inl.h"
1912
1913 // src/Transform_inl.h will undefine USING_* for us.
1914 #undef N
1915 }
1916
1917 #if defined(__clang__)
1918 #pragma clang attribute pop
1919 #elif defined(__GNUC__)
1920 #pragma GCC pop_options
1921 #endif
1922
1923 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001924 #endif
1925
skia-autorollba6087c2019-04-09 13:57:02 +00001926 #if !defined(__AVX512F__)
1927 #if defined(__clang__)
1928 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
1929 #elif defined(__GNUC__)
1930 #pragma GCC push_options
1931 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
1932 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001933
skia-autorollba6087c2019-04-09 13:57:02 +00001934 namespace skx {
1935 #define USING_AVX512F
1936 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001937 template <typename T> using V = Vec<N,T>;
1938 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001939
skia-autorollba6087c2019-04-09 13:57:02 +00001940 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001941
skia-autorollba6087c2019-04-09 13:57:02 +00001942 // src/Transform_inl.h will undefine USING_* for us.
1943 #undef N
1944 }
1945
1946 #if defined(__clang__)
1947 #pragma clang attribute pop
1948 #elif defined(__GNUC__)
1949 #pragma GCC pop_options
1950 #endif
1951
1952 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001953 #endif
1954
skia-autorollba6087c2019-04-09 13:57:02 +00001955 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
1956 enum class CpuType { None, HSW, SKX };
1957 static CpuType cpu_type() {
1958 static const CpuType type = []{
1959 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001960
skia-autorollba6087c2019-04-09 13:57:02 +00001961 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
1962 uint32_t eax, ebx, ecx, edx;
1963 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1964 : "0"(1), "2"(0));
1965 if ((edx & (1u<<25)) && // SSE
1966 (edx & (1u<<26)) && // SSE2
1967 (ecx & (1u<< 0)) && // SSE3
1968 (ecx & (1u<< 9)) && // SSSE3
1969 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
1970 (ecx & (1u<<19)) && // SSE4.1
1971 (ecx & (1u<<20)) && // SSE4.2
1972 (ecx & (1u<<26)) && // XSAVE
1973 (ecx & (1u<<27)) && // OSXSAVE
1974 (ecx & (1u<<28)) && // AVX
1975 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001976
skia-autorollba6087c2019-04-09 13:57:02 +00001977 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
1978 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1979 : "0"(7), "2"(0));
1980 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
1981 uint32_t xcr0, dont_need_edx;
1982 __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 +00001983
skia-autorollba6087c2019-04-09 13:57:02 +00001984 if ((xcr0 & (1u<<1)) && // XMM register state saved?
1985 (xcr0 & (1u<<2)) && // YMM register state saved?
1986 (ebx & (1u<<5))) { // AVX2
1987 // At this point we're at least HSW. Continue checking for SKX.
1988 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
1989 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
1990 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
1991 (ebx & (1u<<16)) && // AVX512F
1992 (ebx & (1u<<17)) && // AVX512DQ
1993 (ebx & (1u<<28)) && // AVX512CD
1994 (ebx & (1u<<30)) && // AVX512BW
1995 (ebx & (1u<<31))) { // AVX512VL
1996 return CpuType::SKX;
1997 }
1998 return CpuType::HSW;
1999 }
2000 }
2001 return CpuType::None;
2002 }();
2003 return type;
2004 }
2005 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002006
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002007#endif
2008
2009static bool is_identity_tf(const skcms_TransferFunction* tf) {
2010 return tf->g == 1 && tf->a == 1
2011 && tf->b == 0 && tf->c == 0 && tf->d == 0 && tf->e == 0 && tf->f == 0;
2012}
2013
2014typedef struct {
2015 Op op;
2016 const void* arg;
2017} OpAndArg;
2018
2019static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002020 static const struct { Op parametric, table; } ops[] = {
2021 { Op_tf_r, Op_table_r },
2022 { Op_tf_g, Op_table_g },
2023 { Op_tf_b, Op_table_b },
2024 { Op_tf_a, Op_table_a },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002025 };
2026
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002027 const OpAndArg noop = { Op_load_a8/*doesn't matter*/, nullptr };
2028
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002029 if (curve->table_entries == 0) {
2030 return is_identity_tf(&curve->parametric)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002031 ? noop
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002032 : OpAndArg{ ops[channel].parametric, &curve->parametric };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002033 }
2034
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002035 return OpAndArg{ ops[channel].table, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002036}
2037
2038static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2039 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002040 case skcms_PixelFormat_A_8 >> 1: return 1;
2041 case skcms_PixelFormat_G_8 >> 1: return 1;
2042 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2043 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2044 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2045 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2046 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2047 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2048 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2049 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2050 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2051 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002052 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2053 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002054 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2055 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2056 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2057 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002058 }
2059 assert(false);
2060 return 0;
2061}
2062
2063static bool prep_for_destination(const skcms_ICCProfile* profile,
2064 skcms_Matrix3x3* fromXYZD50,
2065 skcms_TransferFunction* invR,
2066 skcms_TransferFunction* invG,
2067 skcms_TransferFunction* invB) {
2068 // We only support destinations with parametric transfer functions
2069 // and with gamuts that can be transformed from XYZD50.
2070 return profile->has_trc
2071 && profile->has_toXYZD50
2072 && profile->trc[0].table_entries == 0
2073 && profile->trc[1].table_entries == 0
2074 && profile->trc[2].table_entries == 0
2075 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2076 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2077 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2078 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2079}
2080
2081bool skcms_Transform(const void* src,
2082 skcms_PixelFormat srcFmt,
2083 skcms_AlphaFormat srcAlpha,
2084 const skcms_ICCProfile* srcProfile,
2085 void* dst,
2086 skcms_PixelFormat dstFmt,
2087 skcms_AlphaFormat dstAlpha,
2088 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002089 size_t npixels) {
2090 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2091 dst, dstFmt, dstAlpha, dstProfile,
2092 npixels, nullptr);
2093}
2094
2095bool skcms_TransformWithPalette(const void* src,
2096 skcms_PixelFormat srcFmt,
2097 skcms_AlphaFormat srcAlpha,
2098 const skcms_ICCProfile* srcProfile,
2099 void* dst,
2100 skcms_PixelFormat dstFmt,
2101 skcms_AlphaFormat dstAlpha,
2102 const skcms_ICCProfile* dstProfile,
2103 size_t nz,
2104 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002105 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2106 src_bpp = bytes_per_pixel(srcFmt);
2107 // Let's just refuse if the request is absurdly big.
2108 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2109 return false;
2110 }
2111 int n = (int)nz;
2112
2113 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2114 if (!srcProfile) {
2115 srcProfile = skcms_sRGB_profile();
2116 }
2117 if (!dstProfile) {
2118 dstProfile = skcms_sRGB_profile();
2119 }
2120
2121 // 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 +00002122 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002123 return false;
2124 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002125 // TODO: more careful alias rejection (like, dst == src + 1)?
2126
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002127 if (needs_palette(srcFmt) && !palette) {
2128 return false;
2129 }
2130
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002131 Op program [32];
2132 const void* arguments[32];
2133
2134 Op* ops = program;
2135 const void** args = arguments;
2136
2137 skcms_TransferFunction inv_dst_tf_r, inv_dst_tf_g, inv_dst_tf_b;
2138 skcms_Matrix3x3 from_xyz;
2139
2140 switch (srcFmt >> 1) {
2141 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002142 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2143 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2144 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2145 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2146 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2147 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2148 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2149 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2150 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2151 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2152 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002153 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2154 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 +00002155 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2156 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2157 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2158 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002159
2160 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2161 *args++ = palette;
2162 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002163 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002164 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2165 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2166 *ops++ = Op_clamp;
2167 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002168 if (srcFmt & 1) {
2169 *ops++ = Op_swap_rb;
2170 }
2171 skcms_ICCProfile gray_dst_profile;
2172 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2173 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2174 // luminance (Y) by the destination transfer function.
2175 gray_dst_profile = *dstProfile;
2176 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2177 dstProfile = &gray_dst_profile;
2178 }
2179
2180 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2181 // Photoshop creates CMYK images as inverse CMYK.
2182 // These happen to be the only ones we've _ever_ seen.
2183 *ops++ = Op_invert;
2184 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2185 srcAlpha = skcms_AlphaFormat_Unpremul;
2186 }
2187
2188 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2189 *ops++ = Op_force_opaque;
2190 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2191 *ops++ = Op_unpremul;
2192 }
2193
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002194 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002195
2196 if (!prep_for_destination(dstProfile,
2197 &from_xyz, &inv_dst_tf_r, &inv_dst_tf_b, &inv_dst_tf_g)) {
2198 return false;
2199 }
2200
2201 if (srcProfile->has_A2B) {
2202 if (srcProfile->A2B.input_channels) {
2203 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2204 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 +00002205 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002206 *ops++ = oa.op;
2207 *args++ = oa.arg;
2208 }
2209 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002210 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002211 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002212 *args++ = &srcProfile->A2B;
2213 }
2214
2215 if (srcProfile->A2B.matrix_channels == 3) {
2216 for (int i = 0; i < 3; i++) {
2217 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 +00002218 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002219 *ops++ = oa.op;
2220 *args++ = oa.arg;
2221 }
2222 }
2223
2224 static const skcms_Matrix3x4 I = {{
2225 {1,0,0,0},
2226 {0,1,0,0},
2227 {0,0,1,0},
2228 }};
2229 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2230 *ops++ = Op_matrix_3x4;
2231 *args++ = &srcProfile->A2B.matrix;
2232 }
2233 }
2234
2235 if (srcProfile->A2B.output_channels == 3) {
2236 for (int i = 0; i < 3; i++) {
2237 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 +00002238 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002239 *ops++ = oa.op;
2240 *args++ = oa.arg;
2241 }
2242 }
2243 }
2244
2245 if (srcProfile->pcs == skcms_Signature_Lab) {
2246 *ops++ = Op_lab_to_xyz;
2247 }
2248
2249 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2250 for (int i = 0; i < 3; i++) {
2251 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002252 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002253 *ops++ = oa.op;
2254 *args++ = oa.arg;
2255 }
2256 }
2257 } else {
2258 return false;
2259 }
2260
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002261 // A2B sources should already be in XYZD50 at this point.
2262 // Others still need to be transformed using their toXYZD50 matrix.
2263 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2264 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2265 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2266 static const skcms_Matrix3x3 I = {{
2267 { 1.0f, 0.0f, 0.0f },
2268 { 0.0f, 1.0f, 0.0f },
2269 { 0.0f, 0.0f, 1.0f },
2270 }};
2271 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2272
2273 // There's a chance the source and destination gamuts are identical,
2274 // in which case we can skip the gamut transform.
2275 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2276 // Concat the entire gamut transform into from_xyz,
2277 // now slightly misnamed but it's a handy spot to stash the result.
2278 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2279 *ops++ = Op_matrix_3x3;
2280 *args++ = &from_xyz;
2281 }
2282
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002283 // Encode back to dst RGB using its parametric transfer functions.
2284 if (!is_identity_tf(&inv_dst_tf_r)) { *ops++ = Op_tf_r; *args++ = &inv_dst_tf_r; }
2285 if (!is_identity_tf(&inv_dst_tf_g)) { *ops++ = Op_tf_g; *args++ = &inv_dst_tf_g; }
2286 if (!is_identity_tf(&inv_dst_tf_b)) { *ops++ = Op_tf_b; *args++ = &inv_dst_tf_b; }
2287 }
2288
skia-autoroll2e4fa242019-03-11 21:14:18 +00002289 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2290 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002291 //
2292 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2293 // 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 +00002294 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2295 *ops++ = Op_clamp;
2296 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002297 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2298 *ops++ = Op_force_opaque;
2299 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2300 *ops++ = Op_premul;
2301 }
2302 if (dstFmt & 1) {
2303 *ops++ = Op_swap_rb;
2304 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002305 switch (dstFmt >> 1) {
2306 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002307 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2308 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2309 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2310 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2311 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2312 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2313 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2314 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2315 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2316 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2317 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002318 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2319 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 +00002320 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2321 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2322 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2323 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_store_ffff; break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002324 }
2325
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002326 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002327#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002328 switch (cpu_type()) {
2329 case CpuType::None: break;
2330 case CpuType::HSW: run = hsw::run_program; break;
2331 case CpuType::SKX: run = hsw::run_program; break;
2332 }
2333#endif
2334#if defined(TEST_FOR_SKX)
2335 switch (cpu_type()) {
2336 case CpuType::None: break;
2337 case CpuType::HSW: break;
2338 case CpuType::SKX: run = skx::run_program; break;
2339 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002340#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002341 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 +00002342 return true;
2343}
2344
2345static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2346#if defined(NDEBUG)
2347 (void)profile;
2348#else
2349 skcms_Matrix3x3 fromXYZD50;
2350 skcms_TransferFunction invR, invG, invB;
2351 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2352#endif
2353}
2354
2355bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2356 skcms_Matrix3x3 fromXYZD50;
2357 if (!profile->has_trc || !profile->has_toXYZD50
2358 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2359 return false;
2360 }
2361
2362 skcms_TransferFunction tf[3];
2363 for (int i = 0; i < 3; i++) {
2364 skcms_TransferFunction inv;
2365 if (profile->trc[i].table_entries == 0
2366 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2367 tf[i] = profile->trc[i].parametric;
2368 continue;
2369 }
2370
2371 float max_error;
2372 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2373 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2374 return false;
2375 }
2376 }
2377
2378 for (int i = 0; i < 3; ++i) {
2379 profile->trc[i].table_entries = 0;
2380 profile->trc[i].parametric = tf[i];
2381 }
2382
2383 assert_usable_as_destination(profile);
2384 return true;
2385}
2386
2387bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2388 // Operate on a copy of profile, so we can choose the best TF for the original curves
2389 skcms_ICCProfile result = *profile;
2390 if (!skcms_MakeUsableAsDestination(&result)) {
2391 return false;
2392 }
2393
2394 int best_tf = 0;
2395 float min_max_error = INFINITY_;
2396 for (int i = 0; i < 3; i++) {
2397 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002398 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2399 return false;
2400 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002401
2402 float err = 0;
2403 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002404 err = fmaxf_(err, max_roundtrip_error(&profile->trc[j], &inv));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002405 }
2406 if (min_max_error > err) {
2407 min_max_error = err;
2408 best_tf = i;
2409 }
2410 }
2411
2412 for (int i = 0; i < 3; i++) {
2413 result.trc[i].parametric = result.trc[best_tf].parametric;
2414 }
2415
2416 *profile = result;
2417 assert_usable_as_destination(profile);
2418 return true;
2419}