blob: 65188ca0f4846b1085c02596d9a985382d725e55 [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
1479 // of the source function if we're here. And if the source a >= 0, inv.a definitely is too.
1480 assert (inv.a >= 0);
1481
1482 // On the other hand our ad+b could have gone slightly negative here. Tweak inv.b if needed.
1483 if (inv.a * inv.d + inv.b < 0) {
1484 inv.b = -inv.a * inv.d;
1485 }
1486 assert (inv.a * inv.d + inv.b >= 0);
1487
skia-autorolla7b28742019-01-09 18:35:46 +00001488 // Now in principle we're done.
1489 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f,
1490 // we'll tweak e. These two values should be close to each other,
1491 // just down to numerical precision issues, especially from powf_.
1492 float s = powf_(src->a + src->b, src->g) + src->e;
1493 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 +00001494
skia-autorolld0b577f2019-01-07 19:46:57 +00001495 *dst = inv;
1496 return tf_is_valid(dst);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001497}
1498
1499// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1500
1501// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1502//
1503// tf(x) = cx + f x < d
1504// tf(x) = (ax + b)^g + e x ≥ d
1505//
1506// When fitting, we add the additional constraint that both pieces meet at d:
1507//
1508// cd + f = (ad + b)^g + e
1509//
1510// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1511//
1512// tf(x) = cx + f x < d
1513// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1514//
1515// Our overall strategy is then:
1516// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001517// - 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 +00001518// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001519// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1520// (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 +00001521// Return the parameters with least maximum error.
1522//
1523// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1524// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1525//
1526// let y = Table(x)
1527// r(x) = x - f_inv(y)
1528//
1529// ∂r/∂g = ln(ay + b)*(ay + b)^g
1530// - ln(ad + b)*(ad + b)^g
1531// ∂r/∂a = yg(ay + b)^(g-1)
1532// - dg(ad + b)^(g-1)
1533// ∂r/∂b = g(ay + b)^(g-1)
1534// - g(ad + b)^(g-1)
1535
1536// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1537// and fill out the gradient of the residual into dfdP.
1538static float rg_nonlinear(float x,
1539 const skcms_Curve* curve,
1540 const skcms_TransferFunction* tf,
1541 const float P[3],
1542 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001543 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001544
1545 const float g = P[0], a = P[1], b = P[2],
1546 c = tf->c, d = tf->d, f = tf->f;
1547
1548 const float Y = fmaxf_(a*y + b, 0.0f),
1549 D = a*d + b;
1550 assert (D >= 0);
1551
1552 // The gradient.
1553 dfdP[0] = 0.69314718f*log2f_(Y)*powf_(Y, g)
1554 - 0.69314718f*log2f_(D)*powf_(D, g);
1555 dfdP[1] = y*g*powf_(Y, g-1)
1556 - d*g*powf_(D, g-1);
1557 dfdP[2] = g*powf_(Y, g-1)
1558 - g*powf_(D, g-1);
1559
1560 // The residual.
1561 const float f_inv = powf_(Y, g)
1562 - powf_(D, g)
1563 + c*d + f;
1564 return x - f_inv;
1565}
1566
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001567static bool gauss_newton_step(const skcms_Curve* curve,
1568 const skcms_TransferFunction* tf,
1569 float P[3],
1570 float x0, float dx, int N) {
1571 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1572 //
1573 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1574 // where r(P) is the residual vector
1575 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1576 //
1577 // Let's review the shape of each of these expressions:
1578 // r(P) is [N x 1], a column vector with one entry per value of x tested
1579 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1580 // Jf^T is [3 x N], the transpose of Jf
1581 //
1582 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1583 // and so is its inverse (Jf^T Jf)^-1
1584 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1585 //
1586 // Our implementation strategy to get to the final ∆P is
1587 // 1) evaluate Jf^T Jf, call that lhs
1588 // 2) evaluate Jf^T r(P), call that rhs
1589 // 3) invert lhs
1590 // 4) multiply inverse lhs by rhs
1591 //
1592 // This is a friendly implementation strategy because we don't have to have any
1593 // buffers that scale with N, and equally nice don't have to perform any matrix
1594 // operations that are variable size.
1595 //
1596 // Other implementation strategies could trade this off, e.g. evaluating the
1597 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1598 // the residuals. That would probably require implementing singular value
1599 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1600 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1601 // possibility of this gauss_newton_step() function ever failing.
1602
1603 // 0) start off with lhs and rhs safely zeroed.
1604 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1605 skcms_Vector3 rhs = { {0,0,0} };
1606
1607 // 1,2) evaluate lhs and evaluate rhs
1608 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1609 // so we'll have to update lhs and rhs at the same time.
1610 for (int i = 0; i < N; i++) {
1611 float x = x0 + i*dx;
1612
1613 float dfdP[3] = {0,0,0};
1614 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1615
1616 for (int r = 0; r < 3; r++) {
1617 for (int c = 0; c < 3; c++) {
1618 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1619 }
1620 rhs.vals[r] += dfdP[r] * resid;
1621 }
1622 }
1623
1624 // If any of the 3 P parameters are unused, this matrix will be singular.
1625 // Detect those cases and fix them up to indentity instead, so we can invert.
1626 for (int k = 0; k < 3; k++) {
1627 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1628 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1629 lhs.vals[k][k] = 1;
1630 }
1631 }
1632
1633 // 3) invert lhs
1634 skcms_Matrix3x3 lhs_inv;
1635 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1636 return false;
1637 }
1638
1639 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001640 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001641 P[0] += dP.vals[0];
1642 P[1] += dP.vals[1];
1643 P[2] += dP.vals[2];
1644 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1645}
1646
1647
1648// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1649static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1650 float P[3] = { tf->g, tf->a, tf->b };
1651
1652 // No matter where we start, dx should always represent N even steps from 0 to 1.
1653 const float dx = 1.0f / (N-1);
1654
1655 for (int j = 0; j < 3/*TODO: tune*/; j++) {
1656 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1657 // We don't really know how to fix up a if it goes negative.
1658 if (P[1] < 0) {
1659 return false;
1660 }
1661 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1662 if (P[1] * tf->d + P[2] < 0) {
1663 P[2] = -P[1] * tf->d;
1664 }
1665 assert (P[1] >= 0 &&
1666 P[1] * tf->d + P[2] >= 0);
1667
1668 if (!gauss_newton_step(curve, tf,
1669 P,
1670 L*dx, dx, N-L)) {
1671 return false;
1672 }
1673 }
1674
1675 // We need to apply our fixups one last time
1676 if (P[1] < 0) {
1677 return false;
1678 }
1679 if (P[1] * tf->d + P[2] < 0) {
1680 P[2] = -P[1] * tf->d;
1681 }
1682
skia-autorollfe16a332019-08-20 19:44:54 +00001683 assert (P[1] >= 0 &&
1684 P[1] * tf->d + P[2] >= 0);
1685
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001686 tf->g = P[0];
1687 tf->a = P[1];
1688 tf->b = P[2];
1689 tf->e = tf->c*tf->d + tf->f
1690 - powf_(tf->a*tf->d + tf->b, tf->g);
1691 return true;
1692}
1693
1694bool skcms_ApproximateCurve(const skcms_Curve* curve,
1695 skcms_TransferFunction* approx,
1696 float* max_error) {
1697 if (!curve || !approx || !max_error) {
1698 return false;
1699 }
1700
1701 if (curve->table_entries == 0) {
1702 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1703 return false;
1704 }
1705
1706 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1707 // We need at least two points, and must put some reasonable cap on the maximum number.
1708 return false;
1709 }
1710
1711 int N = (int)curve->table_entries;
1712 const float dx = 1.0f / (N - 1);
1713
1714 *max_error = INFINITY_;
1715 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1716 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1717 skcms_TransferFunction tf,
1718 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001719
1720 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1721 tf.f = 0.0f;
1722 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 +00001723
1724 if (L == N) {
1725 // If the entire data set was linear, move the coefficients to the nonlinear portion
1726 // with G == 1. This lets use a canonical representation with d == 0.
1727 tf.g = 1;
1728 tf.a = tf.c;
1729 tf.b = tf.f;
1730 tf.c = tf.d = tf.e = tf.f = 0;
1731 } else if (L == N - 1) {
1732 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1733 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001734 tf.a = (eval_curve(curve, (N-1)*dx) -
1735 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001736 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001737 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001738 - tf.a * (N-2)*dx;
1739 tf.e = 0;
1740 } else {
1741 // Start by guessing a gamma-only curve through the midpoint.
1742 int mid = (L + N) / 2;
1743 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001744 float mid_y = eval_curve(curve, mid_x);
skia-autoroll8c703932019-03-21 13:14:23 +00001745 tf.g = log2f_(mid_y) / log2f_(mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001746 tf.a = 1;
1747 tf.b = 0;
1748 tf.e = tf.c*tf.d + tf.f
1749 - powf_(tf.a*tf.d + tf.b, tf.g);
1750
1751
1752 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1753 !fit_nonlinear(curve, L,N, &tf_inv)) {
1754 continue;
1755 }
1756
1757 // We fit tf_inv, so calculate tf to keep in sync.
1758 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1759 continue;
1760 }
1761 }
1762
1763 // We find our error by roundtripping the table through tf_inv.
1764 //
1765 // (The most likely use case for this approximation is to be inverted and
1766 // used as the transfer function for a destination color space.)
1767 //
1768 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1769 // invertible, so re-verify that here (and use the new inverse for testing).
1770 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1771 continue;
1772 }
1773
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001774 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001775 if (*max_error > err) {
1776 *max_error = err;
1777 *approx = tf;
1778 }
1779 }
1780 return isfinitef_(*max_error);
1781}
1782
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001783// ~~~~ Impl. of skcms_Transform() ~~~~
1784
1785typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001786 Op_load_a8,
1787 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001788 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001789 Op_load_4444,
1790 Op_load_565,
1791 Op_load_888,
1792 Op_load_8888,
1793 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001794 Op_load_161616LE,
1795 Op_load_16161616LE,
1796 Op_load_161616BE,
1797 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001798 Op_load_hhh,
1799 Op_load_hhhh,
1800 Op_load_fff,
1801 Op_load_ffff,
1802
1803 Op_swap_rb,
1804 Op_clamp,
1805 Op_invert,
1806 Op_force_opaque,
1807 Op_premul,
1808 Op_unpremul,
1809 Op_matrix_3x3,
1810 Op_matrix_3x4,
1811 Op_lab_to_xyz,
1812
1813 Op_tf_r,
1814 Op_tf_g,
1815 Op_tf_b,
1816 Op_tf_a,
1817
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001818 Op_table_r,
1819 Op_table_g,
1820 Op_table_b,
1821 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001822
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001823 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001824
1825 Op_store_a8,
1826 Op_store_g8,
1827 Op_store_4444,
1828 Op_store_565,
1829 Op_store_888,
1830 Op_store_8888,
1831 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001832 Op_store_161616LE,
1833 Op_store_16161616LE,
1834 Op_store_161616BE,
1835 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001836 Op_store_hhh,
1837 Op_store_hhhh,
1838 Op_store_fff,
1839 Op_store_ffff,
1840} Op;
1841
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001842#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001843 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 +00001844#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001845 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1846 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1847 template <int N, typename T>
1848 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 +00001849
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001850 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 +00001851#endif
1852
1853// First, instantiate our default exec_ops() implementation using the default compiliation target.
1854
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001855namespace baseline {
skia-autoroll6272ccd2019-03-06 18:13:22 +00001856#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__)) \
1857 || (defined(__EMSCRIPTEN_major__) && !defined(__wasm_simd128__))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001858 #define N 1
skia-autoroll9be94332019-05-24 18:35:04 +00001859 template <typename T> using V = T;
1860 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001861#elif defined(__AVX512F__)
1862 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001863 template <typename T> using V = Vec<N,T>;
1864 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001865#elif defined(__AVX__)
1866 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001867 template <typename T> using V = Vec<N,T>;
1868 using Color = float;
skia-autorolle92594a2019-05-24 15:39:55 +00001869#elif defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) && defined(SKCMS_OPT_INTO_NEON_FP16)
1870 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001871 template <typename T> using V = Vec<N,T>;
1872 using Color = _Float16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001873#else
1874 #define N 4
skia-autoroll9be94332019-05-24 18:35:04 +00001875 template <typename T> using V = Vec<N,T>;
1876 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001877#endif
1878
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001879 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001880 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001881}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001882
1883// 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 +00001884#if !defined(SKCMS_PORTABLE) && \
skia-autorollc8d66d32019-05-15 14:07:54 +00001885 !defined(SKCMS_NO_RUNTIME_CPU_DETECTION) && \
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com9951cbf2018-08-31 16:40:38 +00001886 (( defined(__clang__) && __clang_major__ >= 5) || \
1887 (!defined(__clang__) && defined(__GNUC__))) \
skia-autorollba6087c2019-04-09 13:57:02 +00001888 && defined(__x86_64__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001889
skia-autorollba6087c2019-04-09 13:57:02 +00001890 #if !defined(__AVX2__)
1891 #if defined(__clang__)
1892 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
1893 #elif defined(__GNUC__)
1894 #pragma GCC push_options
1895 #pragma GCC target("avx2,f16c")
1896 #endif
1897
1898 namespace hsw {
1899 #define USING_AVX
1900 #define USING_AVX_F16C
1901 #define USING_AVX2
1902 #define N 8
skia-autoroll9be94332019-05-24 18:35:04 +00001903 template <typename T> using V = Vec<N,T>;
1904 using Color = float;
skia-autorollba6087c2019-04-09 13:57:02 +00001905
1906 #include "src/Transform_inl.h"
1907
1908 // src/Transform_inl.h will undefine USING_* for us.
1909 #undef N
1910 }
1911
1912 #if defined(__clang__)
1913 #pragma clang attribute pop
1914 #elif defined(__GNUC__)
1915 #pragma GCC pop_options
1916 #endif
1917
1918 #define TEST_FOR_HSW
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001919 #endif
1920
skia-autorollba6087c2019-04-09 13:57:02 +00001921 #if !defined(__AVX512F__)
1922 #if defined(__clang__)
1923 #pragma clang attribute push(__attribute__((target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl"))), apply_to=function)
1924 #elif defined(__GNUC__)
1925 #pragma GCC push_options
1926 #pragma GCC target("avx512f,avx512dq,avx512cd,avx512bw,avx512vl")
1927 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001928
skia-autorollba6087c2019-04-09 13:57:02 +00001929 namespace skx {
1930 #define USING_AVX512F
1931 #define N 16
skia-autoroll9be94332019-05-24 18:35:04 +00001932 template <typename T> using V = Vec<N,T>;
1933 using Color = float;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001934
skia-autorollba6087c2019-04-09 13:57:02 +00001935 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001936
skia-autorollba6087c2019-04-09 13:57:02 +00001937 // src/Transform_inl.h will undefine USING_* for us.
1938 #undef N
1939 }
1940
1941 #if defined(__clang__)
1942 #pragma clang attribute pop
1943 #elif defined(__GNUC__)
1944 #pragma GCC pop_options
1945 #endif
1946
1947 #define TEST_FOR_SKX
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001948 #endif
1949
skia-autorollba6087c2019-04-09 13:57:02 +00001950 #if defined(TEST_FOR_HSW) || defined(TEST_FOR_SKX)
1951 enum class CpuType { None, HSW, SKX };
1952 static CpuType cpu_type() {
1953 static const CpuType type = []{
1954 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001955
skia-autorollba6087c2019-04-09 13:57:02 +00001956 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
1957 uint32_t eax, ebx, ecx, edx;
1958 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1959 : "0"(1), "2"(0));
1960 if ((edx & (1u<<25)) && // SSE
1961 (edx & (1u<<26)) && // SSE2
1962 (ecx & (1u<< 0)) && // SSE3
1963 (ecx & (1u<< 9)) && // SSSE3
1964 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
1965 (ecx & (1u<<19)) && // SSE4.1
1966 (ecx & (1u<<20)) && // SSE4.2
1967 (ecx & (1u<<26)) && // XSAVE
1968 (ecx & (1u<<27)) && // OSXSAVE
1969 (ecx & (1u<<28)) && // AVX
1970 (ecx & (1u<<29))) { // F16C
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001971
skia-autorollba6087c2019-04-09 13:57:02 +00001972 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
1973 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1974 : "0"(7), "2"(0));
1975 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
1976 uint32_t xcr0, dont_need_edx;
1977 __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 +00001978
skia-autorollba6087c2019-04-09 13:57:02 +00001979 if ((xcr0 & (1u<<1)) && // XMM register state saved?
1980 (xcr0 & (1u<<2)) && // YMM register state saved?
1981 (ebx & (1u<<5))) { // AVX2
1982 // At this point we're at least HSW. Continue checking for SKX.
1983 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
1984 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
1985 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
1986 (ebx & (1u<<16)) && // AVX512F
1987 (ebx & (1u<<17)) && // AVX512DQ
1988 (ebx & (1u<<28)) && // AVX512CD
1989 (ebx & (1u<<30)) && // AVX512BW
1990 (ebx & (1u<<31))) { // AVX512VL
1991 return CpuType::SKX;
1992 }
1993 return CpuType::HSW;
1994 }
1995 }
1996 return CpuType::None;
1997 }();
1998 return type;
1999 }
2000 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002001
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002002#endif
2003
2004static bool is_identity_tf(const skcms_TransferFunction* tf) {
2005 return tf->g == 1 && tf->a == 1
2006 && tf->b == 0 && tf->c == 0 && tf->d == 0 && tf->e == 0 && tf->f == 0;
2007}
2008
2009typedef struct {
2010 Op op;
2011 const void* arg;
2012} OpAndArg;
2013
2014static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002015 static const struct { Op parametric, table; } ops[] = {
2016 { Op_tf_r, Op_table_r },
2017 { Op_tf_g, Op_table_g },
2018 { Op_tf_b, Op_table_b },
2019 { Op_tf_a, Op_table_a },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002020 };
2021
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002022 const OpAndArg noop = { Op_load_a8/*doesn't matter*/, nullptr };
2023
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002024 if (curve->table_entries == 0) {
2025 return is_identity_tf(&curve->parametric)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002026 ? noop
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00002027 : OpAndArg{ ops[channel].parametric, &curve->parametric };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002028 }
2029
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002030 return OpAndArg{ ops[channel].table, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002031}
2032
2033static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2034 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002035 case skcms_PixelFormat_A_8 >> 1: return 1;
2036 case skcms_PixelFormat_G_8 >> 1: return 1;
2037 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
2038 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2039 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2040 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2041 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2042 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2043 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2044 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2045 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2046 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002047 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2048 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002049 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2050 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2051 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2052 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002053 }
2054 assert(false);
2055 return 0;
2056}
2057
2058static bool prep_for_destination(const skcms_ICCProfile* profile,
2059 skcms_Matrix3x3* fromXYZD50,
2060 skcms_TransferFunction* invR,
2061 skcms_TransferFunction* invG,
2062 skcms_TransferFunction* invB) {
2063 // We only support destinations with parametric transfer functions
2064 // and with gamuts that can be transformed from XYZD50.
2065 return profile->has_trc
2066 && profile->has_toXYZD50
2067 && profile->trc[0].table_entries == 0
2068 && profile->trc[1].table_entries == 0
2069 && profile->trc[2].table_entries == 0
2070 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2071 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2072 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2073 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2074}
2075
2076bool skcms_Transform(const void* src,
2077 skcms_PixelFormat srcFmt,
2078 skcms_AlphaFormat srcAlpha,
2079 const skcms_ICCProfile* srcProfile,
2080 void* dst,
2081 skcms_PixelFormat dstFmt,
2082 skcms_AlphaFormat dstAlpha,
2083 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002084 size_t npixels) {
2085 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2086 dst, dstFmt, dstAlpha, dstProfile,
2087 npixels, nullptr);
2088}
2089
2090bool skcms_TransformWithPalette(const void* src,
2091 skcms_PixelFormat srcFmt,
2092 skcms_AlphaFormat srcAlpha,
2093 const skcms_ICCProfile* srcProfile,
2094 void* dst,
2095 skcms_PixelFormat dstFmt,
2096 skcms_AlphaFormat dstAlpha,
2097 const skcms_ICCProfile* dstProfile,
2098 size_t nz,
2099 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002100 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2101 src_bpp = bytes_per_pixel(srcFmt);
2102 // Let's just refuse if the request is absurdly big.
2103 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2104 return false;
2105 }
2106 int n = (int)nz;
2107
2108 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2109 if (!srcProfile) {
2110 srcProfile = skcms_sRGB_profile();
2111 }
2112 if (!dstProfile) {
2113 dstProfile = skcms_sRGB_profile();
2114 }
2115
2116 // 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 +00002117 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002118 return false;
2119 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002120 // TODO: more careful alias rejection (like, dst == src + 1)?
2121
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002122 if (needs_palette(srcFmt) && !palette) {
2123 return false;
2124 }
2125
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002126 Op program [32];
2127 const void* arguments[32];
2128
2129 Op* ops = program;
2130 const void** args = arguments;
2131
2132 skcms_TransferFunction inv_dst_tf_r, inv_dst_tf_g, inv_dst_tf_b;
2133 skcms_Matrix3x3 from_xyz;
2134
2135 switch (srcFmt >> 1) {
2136 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002137 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2138 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2139 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2140 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2141 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2142 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2143 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2144 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2145 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2146 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2147 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002148 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_load_hhh; break;
2149 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 +00002150 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2151 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2152 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2153 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002154
2155 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2156 *args++ = palette;
2157 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002158 }
skia-autoroll2e4fa242019-03-11 21:14:18 +00002159 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2160 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2161 *ops++ = Op_clamp;
2162 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002163 if (srcFmt & 1) {
2164 *ops++ = Op_swap_rb;
2165 }
2166 skcms_ICCProfile gray_dst_profile;
2167 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2168 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2169 // luminance (Y) by the destination transfer function.
2170 gray_dst_profile = *dstProfile;
2171 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2172 dstProfile = &gray_dst_profile;
2173 }
2174
2175 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2176 // Photoshop creates CMYK images as inverse CMYK.
2177 // These happen to be the only ones we've _ever_ seen.
2178 *ops++ = Op_invert;
2179 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2180 srcAlpha = skcms_AlphaFormat_Unpremul;
2181 }
2182
2183 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2184 *ops++ = Op_force_opaque;
2185 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2186 *ops++ = Op_unpremul;
2187 }
2188
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002189 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002190
2191 if (!prep_for_destination(dstProfile,
2192 &from_xyz, &inv_dst_tf_r, &inv_dst_tf_b, &inv_dst_tf_g)) {
2193 return false;
2194 }
2195
2196 if (srcProfile->has_A2B) {
2197 if (srcProfile->A2B.input_channels) {
2198 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2199 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 +00002200 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002201 *ops++ = oa.op;
2202 *args++ = oa.arg;
2203 }
2204 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002205 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002206 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002207 *args++ = &srcProfile->A2B;
2208 }
2209
2210 if (srcProfile->A2B.matrix_channels == 3) {
2211 for (int i = 0; i < 3; i++) {
2212 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 +00002213 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002214 *ops++ = oa.op;
2215 *args++ = oa.arg;
2216 }
2217 }
2218
2219 static const skcms_Matrix3x4 I = {{
2220 {1,0,0,0},
2221 {0,1,0,0},
2222 {0,0,1,0},
2223 }};
2224 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2225 *ops++ = Op_matrix_3x4;
2226 *args++ = &srcProfile->A2B.matrix;
2227 }
2228 }
2229
2230 if (srcProfile->A2B.output_channels == 3) {
2231 for (int i = 0; i < 3; i++) {
2232 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 +00002233 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002234 *ops++ = oa.op;
2235 *args++ = oa.arg;
2236 }
2237 }
2238 }
2239
2240 if (srcProfile->pcs == skcms_Signature_Lab) {
2241 *ops++ = Op_lab_to_xyz;
2242 }
2243
2244 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2245 for (int i = 0; i < 3; i++) {
2246 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002247 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002248 *ops++ = oa.op;
2249 *args++ = oa.arg;
2250 }
2251 }
2252 } else {
2253 return false;
2254 }
2255
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002256 // A2B sources should already be in XYZD50 at this point.
2257 // Others still need to be transformed using their toXYZD50 matrix.
2258 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2259 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2260 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2261 static const skcms_Matrix3x3 I = {{
2262 { 1.0f, 0.0f, 0.0f },
2263 { 0.0f, 1.0f, 0.0f },
2264 { 0.0f, 0.0f, 1.0f },
2265 }};
2266 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2267
2268 // There's a chance the source and destination gamuts are identical,
2269 // in which case we can skip the gamut transform.
2270 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2271 // Concat the entire gamut transform into from_xyz,
2272 // now slightly misnamed but it's a handy spot to stash the result.
2273 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2274 *ops++ = Op_matrix_3x3;
2275 *args++ = &from_xyz;
2276 }
2277
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002278 // Encode back to dst RGB using its parametric transfer functions.
2279 if (!is_identity_tf(&inv_dst_tf_r)) { *ops++ = Op_tf_r; *args++ = &inv_dst_tf_r; }
2280 if (!is_identity_tf(&inv_dst_tf_g)) { *ops++ = Op_tf_g; *args++ = &inv_dst_tf_g; }
2281 if (!is_identity_tf(&inv_dst_tf_b)) { *ops++ = Op_tf_b; *args++ = &inv_dst_tf_b; }
2282 }
2283
skia-autoroll2e4fa242019-03-11 21:14:18 +00002284 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2285 // not just to values that fit in [0,1].
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002286 //
2287 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2288 // 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 +00002289 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2290 *ops++ = Op_clamp;
2291 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002292 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2293 *ops++ = Op_force_opaque;
2294 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2295 *ops++ = Op_premul;
2296 }
2297 if (dstFmt & 1) {
2298 *ops++ = Op_swap_rb;
2299 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002300 switch (dstFmt >> 1) {
2301 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002302 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2303 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2304 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2305 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2306 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2307 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2308 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2309 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2310 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2311 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2312 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
skia-autoroll2e4fa242019-03-11 21:14:18 +00002313 case skcms_PixelFormat_RGB_hhh_Norm >> 1: *ops++ = Op_store_hhh; break;
2314 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 +00002315 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2316 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2317 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2318 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 +00002319 }
2320
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002321 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002322#if defined(TEST_FOR_HSW)
skia-autorollba6087c2019-04-09 13:57:02 +00002323 switch (cpu_type()) {
2324 case CpuType::None: break;
2325 case CpuType::HSW: run = hsw::run_program; break;
2326 case CpuType::SKX: run = hsw::run_program; break;
2327 }
2328#endif
2329#if defined(TEST_FOR_SKX)
2330 switch (cpu_type()) {
2331 case CpuType::None: break;
2332 case CpuType::HSW: break;
2333 case CpuType::SKX: run = skx::run_program; break;
2334 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002335#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002336 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 +00002337 return true;
2338}
2339
2340static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2341#if defined(NDEBUG)
2342 (void)profile;
2343#else
2344 skcms_Matrix3x3 fromXYZD50;
2345 skcms_TransferFunction invR, invG, invB;
2346 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2347#endif
2348}
2349
2350bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2351 skcms_Matrix3x3 fromXYZD50;
2352 if (!profile->has_trc || !profile->has_toXYZD50
2353 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2354 return false;
2355 }
2356
2357 skcms_TransferFunction tf[3];
2358 for (int i = 0; i < 3; i++) {
2359 skcms_TransferFunction inv;
2360 if (profile->trc[i].table_entries == 0
2361 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2362 tf[i] = profile->trc[i].parametric;
2363 continue;
2364 }
2365
2366 float max_error;
2367 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2368 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2369 return false;
2370 }
2371 }
2372
2373 for (int i = 0; i < 3; ++i) {
2374 profile->trc[i].table_entries = 0;
2375 profile->trc[i].parametric = tf[i];
2376 }
2377
2378 assert_usable_as_destination(profile);
2379 return true;
2380}
2381
2382bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2383 // Operate on a copy of profile, so we can choose the best TF for the original curves
2384 skcms_ICCProfile result = *profile;
2385 if (!skcms_MakeUsableAsDestination(&result)) {
2386 return false;
2387 }
2388
2389 int best_tf = 0;
2390 float min_max_error = INFINITY_;
2391 for (int i = 0; i < 3; i++) {
2392 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002393 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2394 return false;
2395 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002396
2397 float err = 0;
2398 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002399 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 +00002400 }
2401 if (min_max_error > err) {
2402 min_max_error = err;
2403 best_tf = i;
2404 }
2405 }
2406
2407 for (int i = 0; i < 3; i++) {
2408 result.trc[i].parametric = result.trc[best_tf].parametric;
2409 }
2410
2411 *profile = result;
2412 assert_usable_as_destination(profile);
2413 return true;
2414}