blob: 3e8154654b185163e5fda05b1818c1eab1c02823 [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>
20#endif
21
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000022// sizeof(x) will return size_t, which is 32-bit on some machines and 64-bit on others.
23// We have better testing on 64-bit machines, so force 32-bit machines to behave like 64-bit.
24//
25// Please do not use sizeof() directly, and size_t only when required.
26// (We have no way of enforcing these requests...)
27#define SAFE_SIZEOF(x) ((uint64_t)sizeof(x))
28
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +000029// Same sort of thing for _Layout structs with a variable sized array at the end (named "variable").
30#define SAFE_FIXED_SIZE(type) ((uint64_t)offsetof(type, variable))
31
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000032static const union {
33 uint32_t bits;
34 float f;
35} inf_ = { 0x7f800000 };
36#define INFINITY_ inf_.f
37
38static float fmaxf_(float x, float y) { return x > y ? x : y; }
39static float fminf_(float x, float y) { return x < y ? x : y; }
40
41static bool isfinitef_(float x) { return 0 == x*0; }
42
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000043static float minus_1_ulp(float x) {
44 int32_t bits;
45 memcpy(&bits, &x, sizeof(bits));
46 bits = bits - 1;
47 memcpy(&x, &bits, sizeof(bits));
48 return x;
49}
50
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000051static float eval_curve(const skcms_Curve* curve, float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000052 if (curve->table_entries == 0) {
53 return skcms_TransferFunction_eval(&curve->parametric, x);
54 }
55
56 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 +000057 int lo = (int) ix ,
58 hi = (int)(float)minus_1_ulp(ix + 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000059 float t = ix - (float)lo;
60
61 float l, h;
62 if (curve->table_8) {
63 l = curve->table_8[lo] * (1/255.0f);
64 h = curve->table_8[hi] * (1/255.0f);
65 } else {
66 uint16_t be_l, be_h;
67 memcpy(&be_l, curve->table_16 + 2*lo, 2);
68 memcpy(&be_h, curve->table_16 + 2*hi, 2);
69 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
70 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
71 l = le_l * (1/65535.0f);
72 h = le_h * (1/65535.0f);
73 }
74 return l + (h-l)*t;
75}
76
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000077static 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 +000078 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
79 const float dx = 1.0f / (N - 1);
80 float err = 0;
81 for (uint32_t i = 0; i < N; i++) {
82 float x = i * dx,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +000083 y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +000084 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
85 }
86 return err;
87}
88
89bool 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 +000090 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 +000091}
92
93// Additional ICC signature values that are only used internally
94enum {
95 // File signature
96 skcms_Signature_acsp = 0x61637370,
97
98 // Tag signatures
99 skcms_Signature_rTRC = 0x72545243,
100 skcms_Signature_gTRC = 0x67545243,
101 skcms_Signature_bTRC = 0x62545243,
102 skcms_Signature_kTRC = 0x6B545243,
103
104 skcms_Signature_rXYZ = 0x7258595A,
105 skcms_Signature_gXYZ = 0x6758595A,
106 skcms_Signature_bXYZ = 0x6258595A,
107
108 skcms_Signature_A2B0 = 0x41324230,
109 skcms_Signature_A2B1 = 0x41324231,
110 skcms_Signature_mAB = 0x6D414220,
111
112 skcms_Signature_CHAD = 0x63686164,
113
114 // Type signatures
115 skcms_Signature_curv = 0x63757276,
116 skcms_Signature_mft1 = 0x6D667431,
117 skcms_Signature_mft2 = 0x6D667432,
118 skcms_Signature_para = 0x70617261,
119 skcms_Signature_sf32 = 0x73663332,
120 // XYZ is also a PCS signature, so it's defined in skcms.h
121 // skcms_Signature_XYZ = 0x58595A20,
122};
123
124static uint16_t read_big_u16(const uint8_t* ptr) {
125 uint16_t be;
126 memcpy(&be, ptr, sizeof(be));
127#if defined(_MSC_VER)
128 return _byteswap_ushort(be);
129#else
130 return __builtin_bswap16(be);
131#endif
132}
133
134static uint32_t read_big_u32(const uint8_t* ptr) {
135 uint32_t be;
136 memcpy(&be, ptr, sizeof(be));
137#if defined(_MSC_VER)
138 return _byteswap_ulong(be);
139#else
140 return __builtin_bswap32(be);
141#endif
142}
143
144static int32_t read_big_i32(const uint8_t* ptr) {
145 return (int32_t)read_big_u32(ptr);
146}
147
148static float read_big_fixed(const uint8_t* ptr) {
149 return read_big_i32(ptr) * (1.0f / 65536.0f);
150}
151
152// Maps to an in-memory profile so that fields line up to the locations specified
153// in ICC.1:2010, section 7.2
154typedef struct {
155 uint8_t size [ 4];
156 uint8_t cmm_type [ 4];
157 uint8_t version [ 4];
158 uint8_t profile_class [ 4];
159 uint8_t data_color_space [ 4];
160 uint8_t pcs [ 4];
161 uint8_t creation_date_time [12];
162 uint8_t signature [ 4];
163 uint8_t platform [ 4];
164 uint8_t flags [ 4];
165 uint8_t device_manufacturer [ 4];
166 uint8_t device_model [ 4];
167 uint8_t device_attributes [ 8];
168 uint8_t rendering_intent [ 4];
169 uint8_t illuminant_X [ 4];
170 uint8_t illuminant_Y [ 4];
171 uint8_t illuminant_Z [ 4];
172 uint8_t creator [ 4];
173 uint8_t profile_id [16];
174 uint8_t reserved [28];
175 uint8_t tag_count [ 4]; // Technically not part of header, but required
176} header_Layout;
177
178typedef struct {
179 uint8_t signature [4];
180 uint8_t offset [4];
181 uint8_t size [4];
182} tag_Layout;
183
184static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
185 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
186}
187
188// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
189// use of the type is for the CHAD tag that stores exactly nine values.
190typedef struct {
191 uint8_t type [ 4];
192 uint8_t reserved [ 4];
193 uint8_t values [36];
194} sf32_Layout;
195
196bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
197 skcms_ICCTag tag;
198 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
199 return false;
200 }
201
202 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
203 return false;
204 }
205
206 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
207 const uint8_t* values = sf32Tag->values;
208 for (int r = 0; r < 3; ++r)
209 for (int c = 0; c < 3; ++c, values += 4) {
210 m->vals[r][c] = read_big_fixed(values);
211 }
212 return true;
213}
214
215// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
216// the type are for tags/data that store exactly one triple.
217typedef struct {
218 uint8_t type [4];
219 uint8_t reserved [4];
220 uint8_t X [4];
221 uint8_t Y [4];
222 uint8_t Z [4];
223} XYZ_Layout;
224
225static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
226 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
227 return false;
228 }
229
230 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
231
232 *x = read_big_fixed(xyzTag->X);
233 *y = read_big_fixed(xyzTag->Y);
234 *z = read_big_fixed(xyzTag->Z);
235 return true;
236}
237
238static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
239 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
240 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
241 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
242 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
243}
244
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000245static bool tf_is_valid(const skcms_TransferFunction* tf) {
246 // Reject obviously malformed inputs
247 if (!isfinitef_(tf->a + tf->b + tf->c + tf->d + tf->e + tf->f + tf->g)) {
248 return false;
249 }
250
251 // All of these parameters should be non-negative
252 if (tf->a < 0 || tf->c < 0 || tf->d < 0 || tf->g < 0) {
253 return false;
254 }
255
256 return true;
257}
258
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000259typedef struct {
260 uint8_t type [4];
261 uint8_t reserved_a [4];
262 uint8_t function_type [2];
263 uint8_t reserved_b [2];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000264 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 +0000265} para_Layout;
266
267static bool read_curve_para(const uint8_t* buf, uint32_t size,
268 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000269 if (size < SAFE_FIXED_SIZE(para_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000270 return false;
271 }
272
273 const para_Layout* paraTag = (const para_Layout*)buf;
274
275 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
276 uint16_t function_type = read_big_u16(paraTag->function_type);
277 if (function_type > kGABCDEF) {
278 return false;
279 }
280
281 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 +0000282 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 +0000283 return false;
284 }
285
286 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000287 *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 +0000288 }
289
290 curve->table_entries = 0;
291 curve->parametric.a = 1.0f;
292 curve->parametric.b = 0.0f;
293 curve->parametric.c = 0.0f;
294 curve->parametric.d = 0.0f;
295 curve->parametric.e = 0.0f;
296 curve->parametric.f = 0.0f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000297 curve->parametric.g = read_big_fixed(paraTag->variable);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000298
299 switch (function_type) {
300 case kGAB:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000301 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
302 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000303 if (curve->parametric.a == 0) {
304 return false;
305 }
306 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
307 break;
308 case kGABC:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000309 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
310 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
311 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000312 if (curve->parametric.a == 0) {
313 return false;
314 }
315 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
316 curve->parametric.f = curve->parametric.e;
317 break;
318 case kGABCD:
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);
321 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
322 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000323 break;
324 case kGABCDEF:
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000325 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
326 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
327 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
328 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
329 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
330 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000331 break;
332 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000333 return tf_is_valid(&curve->parametric);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000334}
335
336typedef struct {
337 uint8_t type [4];
338 uint8_t reserved [4];
339 uint8_t value_count [4];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000340 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 +0000341} curv_Layout;
342
343static bool read_curve_curv(const uint8_t* buf, uint32_t size,
344 skcms_Curve* curve, uint32_t* curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000345 if (size < SAFE_FIXED_SIZE(curv_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000346 return false;
347 }
348
349 const curv_Layout* curvTag = (const curv_Layout*)buf;
350
351 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 +0000352 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 +0000353 return false;
354 }
355
356 if (curve_size) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000357 *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 +0000358 }
359
360 if (value_count < 2) {
361 curve->table_entries = 0;
362 curve->parametric.a = 1.0f;
363 curve->parametric.b = 0.0f;
364 curve->parametric.c = 0.0f;
365 curve->parametric.d = 0.0f;
366 curve->parametric.e = 0.0f;
367 curve->parametric.f = 0.0f;
368 if (value_count == 0) {
369 // Empty tables are a shorthand for an identity curve
370 curve->parametric.g = 1.0f;
371 } else {
372 // Single entry tables are a shorthand for simple gamma
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000373 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 +0000374 }
375 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000376 curve->table_8 = nullptr;
377 curve->table_16 = curvTag->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000378 curve->table_entries = value_count;
379 }
380
381 return true;
382}
383
384// 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 +0000385// 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 +0000386static bool read_curve(const uint8_t* buf, uint32_t size,
387 skcms_Curve* curve, uint32_t* curve_size) {
388 if (!buf || size < 4 || !curve) {
389 return false;
390 }
391
392 uint32_t type = read_big_u32(buf);
393 if (type == skcms_Signature_para) {
394 return read_curve_para(buf, size, curve, curve_size);
395 } else if (type == skcms_Signature_curv) {
396 return read_curve_curv(buf, size, curve, curve_size);
397 }
398
399 return false;
400}
401
402// mft1 and mft2 share a large chunk of data
403typedef struct {
404 uint8_t type [ 4];
405 uint8_t reserved_a [ 4];
406 uint8_t input_channels [ 1];
407 uint8_t output_channels [ 1];
408 uint8_t grid_points [ 1];
409 uint8_t reserved_b [ 1];
410 uint8_t matrix [36];
411} mft_CommonLayout;
412
413typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000414 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000415
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000416 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000417} mft1_Layout;
418
419typedef struct {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000420 mft_CommonLayout common [1];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000421
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000422 uint8_t input_table_entries [2];
423 uint8_t output_table_entries [2];
424 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000425} mft2_Layout;
426
427static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
428 // MFT matrices are applied before the first set of curves, but must be identity unless the
429 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
430 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
431 // field/flag.
432 a2b->matrix_channels = 0;
433
434 a2b->input_channels = mftTag->input_channels[0];
435 a2b->output_channels = mftTag->output_channels[0];
436
437 // We require exactly three (ie XYZ/Lab/RGB) output channels
438 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
439 return false;
440 }
441 // We require at least one, and no more than four (ie CMYK) input channels
442 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
443 return false;
444 }
445
446 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
447 a2b->grid_points[i] = mftTag->grid_points[0];
448 }
449 // The grid only makes sense with at least two points along each axis
450 if (a2b->grid_points[0] < 2) {
451 return false;
452 }
453
454 return true;
455}
456
457static bool init_a2b_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
458 uint32_t input_table_entries, uint32_t output_table_entries,
459 skcms_A2B* a2b) {
460 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
461 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
462 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
463
464 // [input|output]_channels are <= 4, so still no overflow
465 uint32_t byte_len_all_input_tables = a2b->input_channels * byte_len_per_input_table;
466 uint32_t byte_len_all_output_tables = a2b->output_channels * byte_len_per_output_table;
467
468 uint64_t grid_size = a2b->output_channels * byte_width;
469 for (uint32_t axis = 0; axis < a2b->input_channels; ++axis) {
470 grid_size *= a2b->grid_points[axis];
471 }
472
473 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
474 return false;
475 }
476
477 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
478 a2b->input_curves[i].table_entries = input_table_entries;
479 if (byte_width == 1) {
480 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 +0000481 a2b->input_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000482 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000483 a2b->input_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000484 a2b->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
485 }
486 }
487
488 if (byte_width == 1) {
489 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 +0000490 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000491 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000492 a2b->grid_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000493 a2b->grid_16 = table_base + byte_len_all_input_tables;
494 }
495
496 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
497 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
498 a2b->output_curves[i].table_entries = output_table_entries;
499 if (byte_width == 1) {
500 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 +0000501 a2b->output_curves[i].table_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000502 } else {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000503 a2b->output_curves[i].table_8 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000504 a2b->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
505 }
506 }
507
508 return true;
509}
510
511static 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 +0000512 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000513 return false;
514 }
515
516 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
517 if (!read_mft_common(mftTag->common, a2b)) {
518 return false;
519 }
520
521 uint32_t input_table_entries = 256;
522 uint32_t output_table_entries = 256;
523
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000524 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 +0000525 input_table_entries, output_table_entries, a2b);
526}
527
528static 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 +0000529 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000530 return false;
531 }
532
533 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
534 if (!read_mft_common(mftTag->common, a2b)) {
535 return false;
536 }
537
538 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
539 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
540
541 // ICC spec mandates that 2 <= table_entries <= 4096
542 if (input_table_entries < 2 || input_table_entries > 4096 ||
543 output_table_entries < 2 || output_table_entries > 4096) {
544 return false;
545 }
546
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000547 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 +0000548 input_table_entries, output_table_entries, a2b);
549}
550
551static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
552 uint32_t num_curves, skcms_Curve* curves) {
553 for (uint32_t i = 0; i < num_curves; ++i) {
554 if (curve_offset > size) {
555 return false;
556 }
557
558 uint32_t curve_bytes;
559 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
560 return false;
561 }
562
563 if (curve_bytes > UINT32_MAX - 3) {
564 return false;
565 }
566 curve_bytes = (curve_bytes + 3) & ~3U;
567
568 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
569 curve_offset = (uint32_t)new_offset_64;
570 if (new_offset_64 != curve_offset) {
571 return false;
572 }
573 }
574
575 return true;
576}
577
578typedef struct {
579 uint8_t type [ 4];
580 uint8_t reserved_a [ 4];
581 uint8_t input_channels [ 1];
582 uint8_t output_channels [ 1];
583 uint8_t reserved_b [ 2];
584 uint8_t b_curve_offset [ 4];
585 uint8_t matrix_offset [ 4];
586 uint8_t m_curve_offset [ 4];
587 uint8_t clut_offset [ 4];
588 uint8_t a_curve_offset [ 4];
589} mAB_Layout;
590
591typedef struct {
592 uint8_t grid_points [16];
593 uint8_t grid_byte_width [ 1];
594 uint8_t reserved [ 3];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000595 uint8_t variable [1/*variable*/];
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000596} mABCLUT_Layout;
597
598static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
599 if (tag->size < SAFE_SIZEOF(mAB_Layout)) {
600 return false;
601 }
602
603 const mAB_Layout* mABTag = (const mAB_Layout*)tag->buf;
604
605 a2b->input_channels = mABTag->input_channels[0];
606 a2b->output_channels = mABTag->output_channels[0];
607
608 // We require exactly three (ie XYZ/Lab/RGB) output channels
609 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
610 return false;
611 }
612 // We require no more than four (ie CMYK) input channels
613 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
614 return false;
615 }
616
617 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
618 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
619 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
620 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
621 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
622
623 // "B" curves must be present
624 if (0 == b_curve_offset) {
625 return false;
626 }
627
628 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
629 a2b->output_curves)) {
630 return false;
631 }
632
633 // "M" curves and Matrix must be used together
634 if (0 != m_curve_offset) {
635 if (0 == matrix_offset) {
636 return false;
637 }
638 a2b->matrix_channels = a2b->output_channels;
639 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
640 a2b->matrix_curves)) {
641 return false;
642 }
643
644 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
645 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
646 return false;
647 }
648 float encoding_factor = pcs_is_xyz ? 65535 / 32768.0f : 1.0f;
649 const uint8_t* mtx_buf = tag->buf + matrix_offset;
650 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
651 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
652 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
653 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
654 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
655 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
656 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
657 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
658 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
659 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
660 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
661 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
662 } else {
663 if (0 != matrix_offset) {
664 return false;
665 }
666 a2b->matrix_channels = 0;
667 }
668
669 // "A" curves and CLUT must be used together
670 if (0 != a_curve_offset) {
671 if (0 == clut_offset) {
672 return false;
673 }
674 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
675 a2b->input_curves)) {
676 return false;
677 }
678
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000679 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 +0000680 return false;
681 }
682 const mABCLUT_Layout* clut = (const mABCLUT_Layout*)(tag->buf + clut_offset);
683
684 if (clut->grid_byte_width[0] == 1) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000685 a2b->grid_8 = clut->variable;
686 a2b->grid_16 = nullptr;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000687 } else if (clut->grid_byte_width[0] == 2) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000688 a2b->grid_8 = nullptr;
689 a2b->grid_16 = clut->variable;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000690 } else {
691 return false;
692 }
693
694 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];
695 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
696 a2b->grid_points[i] = clut->grid_points[i];
697 // The grid only makes sense with at least two points along each axis
698 if (a2b->grid_points[i] < 2) {
699 return false;
700 }
701 grid_size *= a2b->grid_points[i];
702 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000703 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 +0000704 return false;
705 }
706 } else {
707 if (0 != clut_offset) {
708 return false;
709 }
710
711 // If there is no CLUT, the number of input and output channels must match
712 if (a2b->input_channels != a2b->output_channels) {
713 return false;
714 }
715
716 // Zero out the number of input channels to signal that we're skipping this stage
717 a2b->input_channels = 0;
718 }
719
720 return true;
721}
722
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000723// If you pass f, we'll fit a possibly-non-zero value for *f.
724// If you pass nullptr, we'll assume you want *f to be treated as zero.
725static int fit_linear(const skcms_Curve* curve, int N, float tol,
726 float* c, float* d, float* f = nullptr) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000727 assert(N > 1);
728 // We iteratively fit the first points to the TF's linear piece.
729 // We want the cx + f line to pass through the first and last points we fit exactly.
730 //
731 // As we walk along the points we find the minimum and maximum slope of the line before the
732 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
733 // emtpy, when we definitely can't add any more points.
734 //
735 // Some points' error intervals may intersect the running interval but not lie fully
736 // within it. So we keep track of the last point we saw that is a valid end point candidate,
737 // and once the search is done, back up to build the line through *that* point.
738 const float dx = 1.0f / (N - 1);
739
740 int lin_points = 1;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000741
742 float f_zero = 0.0f;
743 if (f) {
744 *f = eval_curve(curve, 0);
745 } else {
746 f = &f_zero;
747 }
748
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000749
750 float slope_min = -INFINITY_;
751 float slope_max = +INFINITY_;
752 for (int i = 1; i < N; ++i) {
753 float x = i * dx;
754 float y = eval_curve(curve, x);
755
756 float slope_max_i = (y + tol - *f) / x,
757 slope_min_i = (y - tol - *f) / x;
758 if (slope_max_i < slope_min || slope_max < slope_min_i) {
759 // Slope intervals would no longer overlap.
760 break;
761 }
762 slope_max = fminf_(slope_max, slope_max_i);
763 slope_min = fmaxf_(slope_min, slope_min_i);
764
765 float cur_slope = (y - *f) / x;
766 if (slope_min <= cur_slope && cur_slope <= slope_max) {
767 lin_points = i + 1;
768 *c = cur_slope;
769 }
770 }
771
772 // Set D to the last point that met our tolerance.
773 *d = (lin_points - 1) * dx;
774 return lin_points;
775}
776
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000777static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
778 bool ok = false;
779 if (tag->type == skcms_Signature_mft1) {
780 ok = read_tag_mft1(tag, a2b);
781 } else if (tag->type == skcms_Signature_mft2) {
782 ok = read_tag_mft2(tag, a2b);
783 } else if (tag->type == skcms_Signature_mAB) {
784 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
785 }
786 if (!ok) {
787 return false;
788 }
789
790 // Detect and canonicalize identity tables.
791 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000792 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
793 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
794 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
795 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
796 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
797 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
798 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
799 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
800 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
801 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000802 };
803
804 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
805 skcms_Curve* curve = curves[i];
806
807 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
808 int N = (int)curve->table_entries;
809
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +0000810 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 +0000811 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 +0000812 && c == 1.0f
813 && f == 0.0f) {
814 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000815 curve->table_8 = nullptr;
816 curve->table_16 = nullptr;
817 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 +0000818 }
819 }
820 }
821
822 return true;
823}
824
825void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
826 if (!profile || !profile->buffer || !tag) { return; }
827 if (idx > profile->tag_count) { return; }
828 const tag_Layout* tags = get_tag_table(profile);
829 tag->signature = read_big_u32(tags[idx].signature);
830 tag->size = read_big_u32(tags[idx].size);
831 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
832 tag->type = read_big_u32(tag->buf);
833}
834
835bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
836 if (!profile || !profile->buffer || !tag) { return false; }
837 const tag_Layout* tags = get_tag_table(profile);
838 for (uint32_t i = 0; i < profile->tag_count; ++i) {
839 if (read_big_u32(tags[i].signature) == sig) {
840 tag->signature = sig;
841 tag->size = read_big_u32(tags[i].size);
842 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
843 tag->type = read_big_u32(tag->buf);
844 return true;
845 }
846 }
847 return false;
848}
849
850static bool usable_as_src(const skcms_ICCProfile* profile) {
851 return profile->has_A2B
852 || (profile->has_trc && profile->has_toXYZD50);
853}
854
855bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
856 assert(SAFE_SIZEOF(header_Layout) == 132);
857
858 if (!profile) {
859 return false;
860 }
861 memset(profile, 0, SAFE_SIZEOF(*profile));
862
863 if (len < SAFE_SIZEOF(header_Layout)) {
864 return false;
865 }
866
867 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000868 const header_Layout* header = (const header_Layout*)buf;
869 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000870 profile->size = read_big_u32(header->size);
871 uint32_t version = read_big_u32(header->version);
872 profile->data_color_space = read_big_u32(header->data_color_space);
873 profile->pcs = read_big_u32(header->pcs);
874 uint32_t signature = read_big_u32(header->signature);
875 float illuminant_X = read_big_fixed(header->illuminant_X);
876 float illuminant_Y = read_big_fixed(header->illuminant_Y);
877 float illuminant_Z = read_big_fixed(header->illuminant_Z);
878 profile->tag_count = read_big_u32(header->tag_count);
879
880 // Validate signature, size (smaller than buffer, large enough to hold tag table),
881 // and major version
882 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
883 if (signature != skcms_Signature_acsp ||
884 profile->size > len ||
885 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
886 (version >> 24) > 4) {
887 return false;
888 }
889
890 // Validate that illuminant is D50 white
891 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
892 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
893 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
894 return false;
895 }
896
897 // Validate that all tag entries have sane offset + size
898 const tag_Layout* tags = get_tag_table(profile);
899 for (uint32_t i = 0; i < profile->tag_count; ++i) {
900 uint32_t tag_offset = read_big_u32(tags[i].offset);
901 uint32_t tag_size = read_big_u32(tags[i].size);
902 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
903 if (tag_size < 4 || tag_end > profile->size) {
904 return false;
905 }
906 }
907
908 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
909 return false;
910 }
911
912 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
913
914 // Pre-parse commonly used tags.
915 skcms_ICCTag kTRC;
916 if (profile->data_color_space == skcms_Signature_Gray &&
917 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000918 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 +0000919 // Malformed tag
920 return false;
921 }
922 profile->trc[1] = profile->trc[0];
923 profile->trc[2] = profile->trc[0];
924 profile->has_trc = true;
925
926 if (pcs_is_xyz) {
927 profile->toXYZD50.vals[0][0] = illuminant_X;
928 profile->toXYZD50.vals[1][1] = illuminant_Y;
929 profile->toXYZD50.vals[2][2] = illuminant_Z;
930 profile->has_toXYZD50 = true;
931 }
932 } else {
933 skcms_ICCTag rTRC, gTRC, bTRC;
934 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
935 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
936 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000937 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
938 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
939 !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 +0000940 // Malformed TRC tags
941 return false;
942 }
943 profile->has_trc = true;
944 }
945
946 skcms_ICCTag rXYZ, gXYZ, bXYZ;
947 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
948 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
949 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
950 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
951 // Malformed XYZ tags
952 return false;
953 }
954 profile->has_toXYZD50 = true;
955 }
956 }
957
958 skcms_ICCTag a2b_tag;
959
960 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
961 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
962 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
963 // and all our known users are thinking exclusively in terms of relative colormetric.
964 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
965 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
966 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
967 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
968 // Malformed A2B tag
969 return false;
970 }
971 profile->has_A2B = true;
972 break;
973 }
974 }
975
976 return usable_as_src(profile);
977}
978
979
980const skcms_ICCProfile* skcms_sRGB_profile() {
981 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000982 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000983
984 0, // size, moot here
985 skcms_Signature_RGB, // data_color_space
986 skcms_Signature_XYZ, // pcs
987 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000988
989 // We choose to represent sRGB with its canonical transfer function,
990 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000991 true, // has_trc, followed by the 3 trc curves
992 {
993 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
994 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
995 {{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 +0000996 },
997
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000998 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
999 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001000 { 0.436065674f, 0.385147095f, 0.143066406f },
1001 { 0.222488403f, 0.716873169f, 0.060607910f },
1002 { 0.013916016f, 0.097076416f, 0.714096069f },
1003 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001004
1005 false, // has_A2B, followed by a2b itself which we don't care about.
1006 {
1007 0,
1008 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001009 {{0, {0,0, 0,0,0,0,0}}},
1010 {{0, {0,0, 0,0,0,0,0}}},
1011 {{0, {0,0, 0,0,0,0,0}}},
1012 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001013 },
1014 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001015 nullptr,
1016 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001017
1018 0,
1019 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001020 {{0, {0,0, 0,0,0,0,0}}},
1021 {{0, {0,0, 0,0,0,0,0}}},
1022 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001023 },
1024 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001025 { 0,0,0,0 },
1026 { 0,0,0,0 },
1027 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001028 }},
1029
1030 0,
1031 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001032 {{0, {0,0, 0,0,0,0,0}}},
1033 {{0, {0,0, 0,0,0,0,0}}},
1034 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001035 },
1036 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001037 };
1038 return &sRGB_profile;
1039}
1040
1041const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001042 // 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 +00001043 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001044 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001045
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001046 0, // size, moot here
1047 skcms_Signature_RGB, // data_color_space
1048 skcms_Signature_XYZ, // pcs
1049 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001050
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001051 true, // has_trc, followed by the 3 trc curves
1052 {
1053 {{0, {1,1, 0,0,0,0,0}}},
1054 {{0, {1,1, 0,0,0,0,0}}},
1055 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001056 },
1057
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001058 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1059 {{
1060 { 1,0,0 },
1061 { 0,1,0 },
1062 { 0,0,1 },
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
1065 false, // has_A2B, followed by a2b itself which we don't care about.
1066 {
1067 0,
1068 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001069 {{0, {0,0, 0,0,0,0,0}}},
1070 {{0, {0,0, 0,0,0,0,0}}},
1071 {{0, {0,0, 0,0,0,0,0}}},
1072 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001073 },
1074 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001075 nullptr,
1076 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001077
1078 0,
1079 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001080 {{0, {0,0, 0,0,0,0,0}}},
1081 {{0, {0,0, 0,0,0,0,0}}},
1082 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001083 },
1084 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001085 { 0,0,0,0 },
1086 { 0,0,0,0 },
1087 { 0,0,0,0 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001088 }},
1089
1090 0,
1091 {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5957df02018-08-29 15:21:38 +00001092 {{0, {0,0, 0,0,0,0,0}}},
1093 {{0, {0,0, 0,0,0,0,0}}},
1094 {{0, {0,0, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001095 },
1096 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001097 };
1098
1099 return &XYZD50_profile;
1100}
1101
1102const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1103 return &skcms_sRGB_profile()->trc[0].parametric;
1104}
1105
1106const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1107 static const skcms_TransferFunction sRGB_inv =
skia-autorolla7b28742019-01-09 18:35:46 +00001108#ifndef SKCMS_LEGACY_TF_INVERT
1109 {0.416666657f, 1.137283325f, -0.0f, 12.920000076f, 0.003130805f, -0.054969788f, -0.0f};
1110#else
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001111 { (float)(1/2.4), 1.137119f, 0, 12.92f, 0.0031308f, -0.055f, 0 };
skia-autorolla7b28742019-01-09 18:35:46 +00001112#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001113 return &sRGB_inv;
1114}
1115
1116const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1117 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1118 return &identity;
1119}
1120
1121const uint8_t skcms_252_random_bytes[] = {
1122 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1123 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1124 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1125 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1126 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1127 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1128 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1129 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1130 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1131 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1132 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1133 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1134 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1135 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1136 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1137 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1138};
1139
1140bool 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 +00001141 // Test for exactly equal profiles first.
1142 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1143 return true;
1144 }
1145
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001146 // For now this is the essentially the same strategy we use in test_only.c
1147 // for our skcms_Transform() smoke tests:
1148 // 1) transform A to XYZD50
1149 // 2) transform B to XYZD50
1150 // 3) return true if they're similar enough
1151 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1152
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001153 // 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 +00001154 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1155
1156 if (A->data_color_space != B->data_color_space) {
1157 return false;
1158 }
1159
1160 // 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 +00001161 // 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 +00001162 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1163 size_t npixels = 84;
1164 if (A->data_color_space == skcms_Signature_CMYK) {
1165 fmt = skcms_PixelFormat_RGBA_8888;
1166 npixels = 63;
1167 }
1168
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001169 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1170 // 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 +00001171 uint8_t dstA[252],
1172 dstB[252];
1173 if (!skcms_Transform(
1174 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1175 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1176 npixels)) {
1177 return false;
1178 }
1179 if (!skcms_Transform(
1180 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1181 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1182 npixels)) {
1183 return false;
1184 }
1185
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com67eac382018-08-29 14:37:38 +00001186 // TODO: make sure this final check has reasonable codegen.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001187 for (size_t i = 0; i < 252; i++) {
1188 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1189 return false;
1190 }
1191 }
1192 return true;
1193}
1194
1195bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1196 const skcms_TransferFunction* inv_tf) {
1197 if (!profile || !profile->has_trc) {
1198 return false;
1199 }
1200
1201 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1202 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1203 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1204}
1205
1206static bool is_zero_to_one(float x) {
1207 return 0 <= x && x <= 1;
1208}
1209
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001210typedef struct { float vals[3]; } skcms_Vector3;
1211
1212static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1213 skcms_Vector3 dst = {{0,0,0}};
1214 for (int row = 0; row < 3; ++row) {
1215 dst.vals[row] = m->vals[row][0] * v->vals[0]
1216 + m->vals[row][1] * v->vals[1]
1217 + m->vals[row][2] * v->vals[2];
1218 }
1219 return dst;
1220}
1221
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001222bool skcms_PrimariesToXYZD50(float rx, float ry,
1223 float gx, float gy,
1224 float bx, float by,
1225 float wx, float wy,
1226 skcms_Matrix3x3* toXYZD50) {
1227 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1228 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1229 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1230 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1231 !toXYZD50) {
1232 return false;
1233 }
1234
1235 // First, we need to convert xy values (primaries) to XYZ.
1236 skcms_Matrix3x3 primaries = {{
1237 { rx, gx, bx },
1238 { ry, gy, by },
1239 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1240 }};
1241 skcms_Matrix3x3 primaries_inv;
1242 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1243 return false;
1244 }
1245
1246 // Assumes that Y is 1.0f.
1247 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 +00001248 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001249
1250 skcms_Matrix3x3 toXYZ = {{
1251 { XYZ.vals[0], 0, 0 },
1252 { 0, XYZ.vals[1], 0 },
1253 { 0, 0, XYZ.vals[2] },
1254 }};
1255 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1256
1257 // Now convert toXYZ matrix to toXYZD50.
1258 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1259
1260 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1261 // the matrices below. The Bradford method is used by Adobe and is widely considered
1262 // to be the best.
1263 skcms_Matrix3x3 xyz_to_lms = {{
1264 { 0.8951f, 0.2664f, -0.1614f },
1265 { -0.7502f, 1.7135f, 0.0367f },
1266 { 0.0389f, -0.0685f, 1.0296f },
1267 }};
1268 skcms_Matrix3x3 lms_to_xyz = {{
1269 { 0.9869929f, -0.1470543f, 0.1599627f },
1270 { 0.4323053f, 0.5183603f, 0.0492912f },
1271 { -0.0085287f, 0.0400428f, 0.9684867f },
1272 }};
1273
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001274 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1275 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001276
1277 skcms_Matrix3x3 DXtoD50 = {{
1278 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1279 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1280 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1281 }};
1282 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1283 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1284
1285 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1286 return true;
1287}
1288
1289
1290bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1291 double a00 = src->vals[0][0],
1292 a01 = src->vals[1][0],
1293 a02 = src->vals[2][0],
1294 a10 = src->vals[0][1],
1295 a11 = src->vals[1][1],
1296 a12 = src->vals[2][1],
1297 a20 = src->vals[0][2],
1298 a21 = src->vals[1][2],
1299 a22 = src->vals[2][2];
1300
1301 double b0 = a00*a11 - a01*a10,
1302 b1 = a00*a12 - a02*a10,
1303 b2 = a01*a12 - a02*a11,
1304 b3 = a20,
1305 b4 = a21,
1306 b5 = a22;
1307
1308 double determinant = b0*b5
1309 - b1*b4
1310 + b2*b3;
1311
1312 if (determinant == 0) {
1313 return false;
1314 }
1315
1316 double invdet = 1.0 / determinant;
1317 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1318 return false;
1319 }
1320
1321 b0 *= invdet;
1322 b1 *= invdet;
1323 b2 *= invdet;
1324 b3 *= invdet;
1325 b4 *= invdet;
1326 b5 *= invdet;
1327
1328 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1329 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1330 dst->vals[2][0] = (float)( + b2 );
1331 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1332 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1333 dst->vals[2][1] = (float)( - b1 );
1334 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1335 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1336 dst->vals[2][2] = (float)( + b0 );
1337
1338 for (int r = 0; r < 3; ++r)
1339 for (int c = 0; c < 3; ++c) {
1340 if (!isfinitef_(dst->vals[r][c])) {
1341 return false;
1342 }
1343 }
1344 return true;
1345}
1346
1347skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1348 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1349 for (int r = 0; r < 3; r++)
1350 for (int c = 0; c < 3; c++) {
1351 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1352 + A->vals[r][1] * B->vals[1][c]
1353 + A->vals[r][2] * B->vals[2][c];
1354 }
1355 return m;
1356}
1357
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001358#if defined(__clang__) || defined(__GNUC__)
1359 #define small_memcpy __builtin_memcpy
1360#else
1361 #define small_memcpy memcpy
1362#endif
1363
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001364static float log2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001365 // The first approximation of log2(x) is its exponent 'e', minus 127.
1366 int32_t bits;
1367 small_memcpy(&bits, &x, sizeof(bits));
1368
1369 float e = (float)bits * (1.0f / (1<<23));
1370
1371 // If we use the mantissa too we can refine the error signficantly.
1372 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
1373 float m;
1374 small_memcpy(&m, &m_bits, sizeof(m));
1375
1376 return (e - 124.225514990f
1377 - 1.498030302f*m
1378 - 1.725879990f/(0.3520887068f + m));
1379}
1380
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001381static float exp2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001382 float fract = x - floorf_(x);
1383
1384 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
1385 - 1.490129070f*fract
1386 + 27.728023300f/(4.84252568f - fract));
1387 if (fbits > INT_MAX) {
1388 return INFINITY_;
1389 } else if (fbits < INT_MIN) {
1390 return -INFINITY_;
1391 }
1392 int32_t bits = (int32_t)fbits;
1393 small_memcpy(&x, &bits, sizeof(x));
1394 return x;
1395}
1396
1397float powf_(float x, float y) {
1398 return (x == 0) || (x == 1) ? x
1399 : exp2f_(log2f_(x) * y);
1400}
1401
1402float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
1403 float sign = x < 0 ? -1.0f : 1.0f;
1404 x *= sign;
1405
1406 return sign * (x < tf->d ? tf->c * x + tf->f
1407 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
1408}
1409
skia-autorollacd6e012019-01-08 14:10:52 +00001410#if defined(__clang__)
1411 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by tf_is_valid() on the way out.
1412#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001413bool 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 +00001414 if (!tf_is_valid(src)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001415 return false;
1416 }
1417
skia-autorolld0b577f2019-01-07 19:46:57 +00001418 // We're inverting this function, solving for x in terms of y.
1419 // y = (cx + f) x < d
1420 // (ax + b)^g + e x ≥ d
1421 // The inverse of this function can be expressed in the same piecewise form.
1422 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 +00001423
skia-autorolld0b577f2019-01-07 19:46:57 +00001424 // We'll start by finding the new threshold inv.d.
1425 // In principle we should be able to find that by solving for y at x=d from either side.
1426 // (If those two d values aren't the same, it's a discontinuous transfer function.)
1427 float d_l = src->c * src->d + src->f,
1428 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1429 if (fabsf_(d_l - d_r) > 1/512.0f) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001430 return false;
1431 }
skia-autorolld0b577f2019-01-07 19:46:57 +00001432 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 +00001433
skia-autorolld0b577f2019-01-07 19:46:57 +00001434 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
1435 if (inv.d > 0) {
1436 // Inverting the linear section is pretty straightfoward:
1437 // y = cx + f
1438 // y - f = cx
1439 // (1/c)y - f/c = x
1440 inv.c = 1.0f/src->c;
1441 inv.f = -src->f/src->c;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001442 }
1443
skia-autorolld0b577f2019-01-07 19:46:57 +00001444 // The interesting part is inverting the nonlinear section:
1445 // y = (ax + b)^g + e.
1446 // y - e = (ax + b)^g
1447 // (y - e)^1/g = ax + b
1448 // (y - e)^1/g - b = ax
1449 // (1/a)(y - e)^1/g - b/a = x
1450 //
1451 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1452 // let k = (1/a)^g
1453 // (1/a)( y - e)^1/g - b/a = x
1454 // (ky - ke)^1/g - b/a = x
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001455
skia-autoroll7cb0fcc2019-01-07 22:02:19 +00001456#ifndef SKCMS_LEGACY_TF_INVERT
1457 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
1458#else
1459 float k = powf_(1.0f / src->a, src->g);
1460#endif
skia-autorolld0b577f2019-01-07 19:46:57 +00001461 inv.g = 1.0f / src->g;
1462 inv.a = k;
1463 inv.b = -k * src->e;
1464 inv.e = -src->b / src->a;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001465
skia-autorolla7b28742019-01-09 18:35:46 +00001466#ifndef SKCMS_LEGACY_TF_INVERT
1467 // Now in principle we're done.
1468 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f,
1469 // we'll tweak e. These two values should be close to each other,
1470 // just down to numerical precision issues, especially from powf_.
1471 float s = powf_(src->a + src->b, src->g) + src->e;
1472 inv.e = 1.0f - powf_(inv.a * s + inv.b, inv.g);
1473#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001474
skia-autorolld0b577f2019-01-07 19:46:57 +00001475 *dst = inv;
1476 return tf_is_valid(dst);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001477}
1478
1479// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1480
1481// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1482//
1483// tf(x) = cx + f x < d
1484// tf(x) = (ax + b)^g + e x ≥ d
1485//
1486// When fitting, we add the additional constraint that both pieces meet at d:
1487//
1488// cd + f = (ad + b)^g + e
1489//
1490// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1491//
1492// tf(x) = cx + f x < d
1493// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1494//
1495// Our overall strategy is then:
1496// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001497// - 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 +00001498// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001499// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1500// (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 +00001501// Return the parameters with least maximum error.
1502//
1503// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1504// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1505//
1506// let y = Table(x)
1507// r(x) = x - f_inv(y)
1508//
1509// ∂r/∂g = ln(ay + b)*(ay + b)^g
1510// - ln(ad + b)*(ad + b)^g
1511// ∂r/∂a = yg(ay + b)^(g-1)
1512// - dg(ad + b)^(g-1)
1513// ∂r/∂b = g(ay + b)^(g-1)
1514// - g(ad + b)^(g-1)
1515
1516// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1517// and fill out the gradient of the residual into dfdP.
1518static float rg_nonlinear(float x,
1519 const skcms_Curve* curve,
1520 const skcms_TransferFunction* tf,
1521 const float P[3],
1522 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001523 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001524
1525 const float g = P[0], a = P[1], b = P[2],
1526 c = tf->c, d = tf->d, f = tf->f;
1527
1528 const float Y = fmaxf_(a*y + b, 0.0f),
1529 D = a*d + b;
1530 assert (D >= 0);
1531
1532 // The gradient.
1533 dfdP[0] = 0.69314718f*log2f_(Y)*powf_(Y, g)
1534 - 0.69314718f*log2f_(D)*powf_(D, g);
1535 dfdP[1] = y*g*powf_(Y, g-1)
1536 - d*g*powf_(D, g-1);
1537 dfdP[2] = g*powf_(Y, g-1)
1538 - g*powf_(D, g-1);
1539
1540 // The residual.
1541 const float f_inv = powf_(Y, g)
1542 - powf_(D, g)
1543 + c*d + f;
1544 return x - f_inv;
1545}
1546
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001547static bool gauss_newton_step(const skcms_Curve* curve,
1548 const skcms_TransferFunction* tf,
1549 float P[3],
1550 float x0, float dx, int N) {
1551 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1552 //
1553 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1554 // where r(P) is the residual vector
1555 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1556 //
1557 // Let's review the shape of each of these expressions:
1558 // r(P) is [N x 1], a column vector with one entry per value of x tested
1559 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1560 // Jf^T is [3 x N], the transpose of Jf
1561 //
1562 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1563 // and so is its inverse (Jf^T Jf)^-1
1564 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1565 //
1566 // Our implementation strategy to get to the final ∆P is
1567 // 1) evaluate Jf^T Jf, call that lhs
1568 // 2) evaluate Jf^T r(P), call that rhs
1569 // 3) invert lhs
1570 // 4) multiply inverse lhs by rhs
1571 //
1572 // This is a friendly implementation strategy because we don't have to have any
1573 // buffers that scale with N, and equally nice don't have to perform any matrix
1574 // operations that are variable size.
1575 //
1576 // Other implementation strategies could trade this off, e.g. evaluating the
1577 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1578 // the residuals. That would probably require implementing singular value
1579 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1580 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1581 // possibility of this gauss_newton_step() function ever failing.
1582
1583 // 0) start off with lhs and rhs safely zeroed.
1584 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1585 skcms_Vector3 rhs = { {0,0,0} };
1586
1587 // 1,2) evaluate lhs and evaluate rhs
1588 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1589 // so we'll have to update lhs and rhs at the same time.
1590 for (int i = 0; i < N; i++) {
1591 float x = x0 + i*dx;
1592
1593 float dfdP[3] = {0,0,0};
1594 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1595
1596 for (int r = 0; r < 3; r++) {
1597 for (int c = 0; c < 3; c++) {
1598 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1599 }
1600 rhs.vals[r] += dfdP[r] * resid;
1601 }
1602 }
1603
1604 // If any of the 3 P parameters are unused, this matrix will be singular.
1605 // Detect those cases and fix them up to indentity instead, so we can invert.
1606 for (int k = 0; k < 3; k++) {
1607 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1608 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1609 lhs.vals[k][k] = 1;
1610 }
1611 }
1612
1613 // 3) invert lhs
1614 skcms_Matrix3x3 lhs_inv;
1615 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1616 return false;
1617 }
1618
1619 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001620 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001621 P[0] += dP.vals[0];
1622 P[1] += dP.vals[1];
1623 P[2] += dP.vals[2];
1624 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1625}
1626
1627
1628// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1629static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1630 float P[3] = { tf->g, tf->a, tf->b };
1631
1632 // No matter where we start, dx should always represent N even steps from 0 to 1.
1633 const float dx = 1.0f / (N-1);
1634
1635 for (int j = 0; j < 3/*TODO: tune*/; j++) {
1636 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1637 // We don't really know how to fix up a if it goes negative.
1638 if (P[1] < 0) {
1639 return false;
1640 }
1641 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1642 if (P[1] * tf->d + P[2] < 0) {
1643 P[2] = -P[1] * tf->d;
1644 }
1645 assert (P[1] >= 0 &&
1646 P[1] * tf->d + P[2] >= 0);
1647
1648 if (!gauss_newton_step(curve, tf,
1649 P,
1650 L*dx, dx, N-L)) {
1651 return false;
1652 }
1653 }
1654
1655 // We need to apply our fixups one last time
1656 if (P[1] < 0) {
1657 return false;
1658 }
1659 if (P[1] * tf->d + P[2] < 0) {
1660 P[2] = -P[1] * tf->d;
1661 }
1662
1663 tf->g = P[0];
1664 tf->a = P[1];
1665 tf->b = P[2];
1666 tf->e = tf->c*tf->d + tf->f
1667 - powf_(tf->a*tf->d + tf->b, tf->g);
1668 return true;
1669}
1670
1671bool skcms_ApproximateCurve(const skcms_Curve* curve,
1672 skcms_TransferFunction* approx,
1673 float* max_error) {
1674 if (!curve || !approx || !max_error) {
1675 return false;
1676 }
1677
1678 if (curve->table_entries == 0) {
1679 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1680 return false;
1681 }
1682
1683 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1684 // We need at least two points, and must put some reasonable cap on the maximum number.
1685 return false;
1686 }
1687
1688 int N = (int)curve->table_entries;
1689 const float dx = 1.0f / (N - 1);
1690
1691 *max_error = INFINITY_;
1692 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1693 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1694 skcms_TransferFunction tf,
1695 tf_inv;
skia-autoroll@skia-public.iam.gserviceaccount.comdfbbb2b2018-11-01 15:11:14 +00001696
1697 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
1698 tf.f = 0.0f;
1699 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 +00001700
1701 if (L == N) {
1702 // If the entire data set was linear, move the coefficients to the nonlinear portion
1703 // with G == 1. This lets use a canonical representation with d == 0.
1704 tf.g = 1;
1705 tf.a = tf.c;
1706 tf.b = tf.f;
1707 tf.c = tf.d = tf.e = tf.f = 0;
1708 } else if (L == N - 1) {
1709 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1710 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001711 tf.a = (eval_curve(curve, (N-1)*dx) -
1712 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001713 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001714 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001715 - tf.a * (N-2)*dx;
1716 tf.e = 0;
1717 } else {
1718 // Start by guessing a gamma-only curve through the midpoint.
1719 int mid = (L + N) / 2;
1720 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001721 float mid_y = eval_curve(curve, mid_x);
skia-autorolld0b577f2019-01-07 19:46:57 +00001722 tf.g = log2f_(mid_y) / log2f_(mid_x);;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001723 tf.a = 1;
1724 tf.b = 0;
1725 tf.e = tf.c*tf.d + tf.f
1726 - powf_(tf.a*tf.d + tf.b, tf.g);
1727
1728
1729 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1730 !fit_nonlinear(curve, L,N, &tf_inv)) {
1731 continue;
1732 }
1733
1734 // We fit tf_inv, so calculate tf to keep in sync.
1735 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1736 continue;
1737 }
1738 }
1739
1740 // We find our error by roundtripping the table through tf_inv.
1741 //
1742 // (The most likely use case for this approximation is to be inverted and
1743 // used as the transfer function for a destination color space.)
1744 //
1745 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1746 // invertible, so re-verify that here (and use the new inverse for testing).
1747 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1748 continue;
1749 }
1750
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001751 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001752 if (*max_error > err) {
1753 *max_error = err;
1754 *approx = tf;
1755 }
1756 }
1757 return isfinitef_(*max_error);
1758}
1759
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001760// ~~~~ Impl. of skcms_Transform() ~~~~
1761
1762typedef enum {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001763 Op_load_a8,
1764 Op_load_g8,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001765 Op_load_8888_palette8,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001766 Op_load_4444,
1767 Op_load_565,
1768 Op_load_888,
1769 Op_load_8888,
1770 Op_load_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001771 Op_load_161616LE,
1772 Op_load_16161616LE,
1773 Op_load_161616BE,
1774 Op_load_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001775 Op_load_hhh,
1776 Op_load_hhhh,
1777 Op_load_fff,
1778 Op_load_ffff,
1779
1780 Op_swap_rb,
1781 Op_clamp,
1782 Op_invert,
1783 Op_force_opaque,
1784 Op_premul,
1785 Op_unpremul,
1786 Op_matrix_3x3,
1787 Op_matrix_3x4,
1788 Op_lab_to_xyz,
1789
1790 Op_tf_r,
1791 Op_tf_g,
1792 Op_tf_b,
1793 Op_tf_a,
1794
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001795 Op_table_r,
1796 Op_table_g,
1797 Op_table_b,
1798 Op_table_a,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001799
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001800 Op_clut,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001801
1802 Op_store_a8,
1803 Op_store_g8,
1804 Op_store_4444,
1805 Op_store_565,
1806 Op_store_888,
1807 Op_store_8888,
1808 Op_store_1010102,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00001809 Op_store_161616LE,
1810 Op_store_16161616LE,
1811 Op_store_161616BE,
1812 Op_store_16161616BE,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001813 Op_store_hhh,
1814 Op_store_hhhh,
1815 Op_store_fff,
1816 Op_store_ffff,
1817} Op;
1818
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001819// Without this wasm would try to use the N=4 128-bit vector code path,
1820// which while ideal, causes tons of compiler problems. This would be
1821// a good thing to revisit as emcc matures (currently 1.38.5).
1822#if 1 && defined(__EMSCRIPTEN_major__)
1823 #if !defined(SKCMS_PORTABLE)
1824 #define SKCMS_PORTABLE
1825 #endif
1826#endif
1827
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001828#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001829 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 +00001830#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001831 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1832 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1833 template <int N, typename T>
1834 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 +00001835
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001836 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 +00001837#endif
1838
1839// First, instantiate our default exec_ops() implementation using the default compiliation target.
1840
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001841namespace baseline {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001842#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__))
1843 #define N 1
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001844 using F = float;
1845 using U64 = uint64_t;
1846 using U32 = uint32_t;
1847 using I32 = int32_t;
1848 using U16 = uint16_t;
1849 using U8 = uint8_t;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001850
1851#elif defined(__AVX512F__)
1852 #define N 16
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001853 using F = Vec<N,float>;
1854 using I32 = Vec<N,int32_t>;
1855 using U64 = Vec<N,uint64_t>;
1856 using U32 = Vec<N,uint32_t>;
1857 using U16 = Vec<N,uint16_t>;
1858 using U8 = Vec<N,uint8_t>;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001859#elif defined(__AVX__)
1860 #define N 8
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001861 using F = Vec<N,float>;
1862 using I32 = Vec<N,int32_t>;
1863 using U64 = Vec<N,uint64_t>;
1864 using U32 = Vec<N,uint32_t>;
1865 using U16 = Vec<N,uint16_t>;
1866 using U8 = Vec<N,uint8_t>;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001867#else
1868 #define N 4
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001869 using F = Vec<N,float>;
1870 using I32 = Vec<N,int32_t>;
1871 using U64 = Vec<N,uint64_t>;
1872 using U32 = Vec<N,uint32_t>;
1873 using U16 = Vec<N,uint16_t>;
1874 using U8 = Vec<N,uint8_t>;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001875#endif
1876
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001877 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001878 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001879}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001880
1881// 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 +00001882#if !defined(SKCMS_PORTABLE) && \
1883 (( defined(__clang__) && __clang_major__ >= 5) || \
1884 (!defined(__clang__) && defined(__GNUC__))) \
1885 && defined(__x86_64__) && !defined(__AVX2__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001886
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001887 #if defined(__clang__)
1888 #pragma clang attribute push(__attribute__((target("avx2,f16c"))), apply_to=function)
1889 #elif defined(__GNUC__)
1890 #pragma GCC push_options
1891 #pragma GCC target("avx2,f16c")
1892 #endif
1893
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001894 namespace hsw {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001895 #define USING_AVX
1896 #define USING_AVX_F16C
1897 #define USING_AVX2
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001898 #define N 8
1899 using F = Vec<N,float>;
1900 using I32 = Vec<N,int32_t>;
1901 using U64 = Vec<N,uint64_t>;
1902 using U32 = Vec<N,uint32_t>;
1903 using U16 = Vec<N,uint16_t>;
1904 using U8 = Vec<N,uint8_t>;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001905
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001906 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001907
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com628bc772018-08-31 12:25:38 +00001908 // src/Transform_inl.h will undefine USING_* for us.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001909 #undef N
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001910 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001911
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comd9212ca2018-08-31 15:00:38 +00001912 #if defined(__clang__)
1913 #pragma clang attribute pop
1914 #elif defined(__GNUC__)
1915 #pragma GCC pop_options
1916 #endif
1917
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001918 #define TEST_FOR_HSW
1919
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001920 static bool hsw_ok() {
1921 static const bool ok = []{
1922 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001923
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001924 // First, a basic cpuid(1).
1925 uint32_t eax, ebx, ecx, edx;
1926 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1927 : "0"(1), "2"(0));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001928
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001929 // Sanity check for prerequisites.
1930 if ((edx & (1<<25)) != (1<<25)) { return false; } // SSE
1931 if ((edx & (1<<26)) != (1<<26)) { return false; } // SSE2
1932 if ((ecx & (1<< 0)) != (1<< 0)) { return false; } // SSE3
1933 if ((ecx & (1<< 9)) != (1<< 9)) { return false; } // SSSE3
1934 if ((ecx & (1<<19)) != (1<<19)) { return false; } // SSE4.1
1935 if ((ecx & (1<<20)) != (1<<20)) { return false; } // SSE4.2
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001936
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001937 if ((ecx & (3<<26)) != (3<<26)) { return false; } // XSAVE + OSXSAVE
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001938
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001939 {
1940 uint32_t eax_xgetbv, edx_xgetbv;
1941 __asm__ __volatile__("xgetbv" : "=a"(eax_xgetbv), "=d"(edx_xgetbv) : "c"(0));
1942 if ((eax_xgetbv & (3<<1)) != (3<<1)) { return false; } // XMM+YMM state saved?
1943 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001944
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001945 if ((ecx & (1<<28)) != (1<<28)) { return false; } // AVX
1946 if ((ecx & (1<<29)) != (1<<29)) { return false; } // F16C
1947 if ((ecx & (1<<12)) != (1<<12)) { return false; } // FMA (TODO: not currently used)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001948
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001949 // Call cpuid(7) to check for our final AVX2 feature bit!
1950 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1951 : "0"(7), "2"(0));
1952 if ((ebx & (1<< 5)) != (1<< 5)) { return false; } // AVX2
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001953
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001954 return true;
1955 }();
1956
1957 return ok;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001958 }
1959
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001960#endif
1961
1962static bool is_identity_tf(const skcms_TransferFunction* tf) {
1963 return tf->g == 1 && tf->a == 1
1964 && tf->b == 0 && tf->c == 0 && tf->d == 0 && tf->e == 0 && tf->f == 0;
1965}
1966
1967typedef struct {
1968 Op op;
1969 const void* arg;
1970} OpAndArg;
1971
1972static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001973 static const struct { Op parametric, table; } ops[] = {
1974 { Op_tf_r, Op_table_r },
1975 { Op_tf_g, Op_table_g },
1976 { Op_tf_b, Op_table_b },
1977 { Op_tf_a, Op_table_a },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001978 };
1979
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00001980 const OpAndArg noop = { Op_load_a8/*doesn't matter*/, nullptr };
1981
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001982 if (curve->table_entries == 0) {
1983 return is_identity_tf(&curve->parametric)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00001984 ? noop
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001985 : OpAndArg{ ops[channel].parametric, &curve->parametric };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001986 }
1987
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00001988 return OpAndArg{ ops[channel].table, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001989}
1990
1991static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
1992 switch (fmt >> 1) { // ignore rgb/bgr
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00001993 case skcms_PixelFormat_A_8 >> 1: return 1;
1994 case skcms_PixelFormat_G_8 >> 1: return 1;
1995 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: return 1;
1996 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
1997 case skcms_PixelFormat_RGB_565 >> 1: return 2;
1998 case skcms_PixelFormat_RGB_888 >> 1: return 3;
1999 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2000 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2001 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2002 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2003 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2004 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
2005 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2006 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2007 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2008 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002009 }
2010 assert(false);
2011 return 0;
2012}
2013
2014static bool prep_for_destination(const skcms_ICCProfile* profile,
2015 skcms_Matrix3x3* fromXYZD50,
2016 skcms_TransferFunction* invR,
2017 skcms_TransferFunction* invG,
2018 skcms_TransferFunction* invB) {
2019 // We only support destinations with parametric transfer functions
2020 // and with gamuts that can be transformed from XYZD50.
2021 return profile->has_trc
2022 && profile->has_toXYZD50
2023 && profile->trc[0].table_entries == 0
2024 && profile->trc[1].table_entries == 0
2025 && profile->trc[2].table_entries == 0
2026 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2027 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2028 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2029 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2030}
2031
2032bool skcms_Transform(const void* src,
2033 skcms_PixelFormat srcFmt,
2034 skcms_AlphaFormat srcAlpha,
2035 const skcms_ICCProfile* srcProfile,
2036 void* dst,
2037 skcms_PixelFormat dstFmt,
2038 skcms_AlphaFormat dstAlpha,
2039 const skcms_ICCProfile* dstProfile,
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002040 size_t npixels) {
2041 return skcms_TransformWithPalette(src, srcFmt, srcAlpha, srcProfile,
2042 dst, dstFmt, dstAlpha, dstProfile,
2043 npixels, nullptr);
2044}
2045
2046bool skcms_TransformWithPalette(const void* src,
2047 skcms_PixelFormat srcFmt,
2048 skcms_AlphaFormat srcAlpha,
2049 const skcms_ICCProfile* srcProfile,
2050 void* dst,
2051 skcms_PixelFormat dstFmt,
2052 skcms_AlphaFormat dstAlpha,
2053 const skcms_ICCProfile* dstProfile,
2054 size_t nz,
2055 const void* palette) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002056 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2057 src_bpp = bytes_per_pixel(srcFmt);
2058 // Let's just refuse if the request is absurdly big.
2059 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2060 return false;
2061 }
2062 int n = (int)nz;
2063
2064 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2065 if (!srcProfile) {
2066 srcProfile = skcms_sRGB_profile();
2067 }
2068 if (!dstProfile) {
2069 dstProfile = skcms_sRGB_profile();
2070 }
2071
2072 // 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 +00002073 if (dst == src && dst_bpp != src_bpp) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002074 return false;
2075 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002076 // TODO: more careful alias rejection (like, dst == src + 1)?
2077
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002078 if (needs_palette(srcFmt) && !palette) {
2079 return false;
2080 }
2081
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002082 Op program [32];
2083 const void* arguments[32];
2084
2085 Op* ops = program;
2086 const void** args = arguments;
2087
2088 skcms_TransferFunction inv_dst_tf_r, inv_dst_tf_g, inv_dst_tf_b;
2089 skcms_Matrix3x3 from_xyz;
2090
2091 switch (srcFmt >> 1) {
2092 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002093 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2094 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2095 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2096 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2097 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2098 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2099 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2100 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_load_161616LE; break;
2101 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_load_16161616LE; break;
2102 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_load_161616BE; break;
2103 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_load_16161616BE; break;
2104 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2105 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2106 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2107 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
skia-autoroll@skia-public.iam.gserviceaccount.com1cb0e3a2018-10-18 19:20:14 +00002108
2109 case skcms_PixelFormat_RGBA_8888_Palette8 >> 1: *ops++ = Op_load_8888_palette8;
2110 *args++ = palette;
2111 break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002112 }
2113 if (srcFmt & 1) {
2114 *ops++ = Op_swap_rb;
2115 }
2116 skcms_ICCProfile gray_dst_profile;
2117 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2118 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2119 // luminance (Y) by the destination transfer function.
2120 gray_dst_profile = *dstProfile;
2121 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2122 dstProfile = &gray_dst_profile;
2123 }
2124
2125 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2126 // Photoshop creates CMYK images as inverse CMYK.
2127 // These happen to be the only ones we've _ever_ seen.
2128 *ops++ = Op_invert;
2129 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2130 srcAlpha = skcms_AlphaFormat_Unpremul;
2131 }
2132
2133 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2134 *ops++ = Op_force_opaque;
2135 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2136 *ops++ = Op_unpremul;
2137 }
2138
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5f0943f2018-08-30 21:16:38 +00002139 if (dstProfile != srcProfile) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002140
2141 if (!prep_for_destination(dstProfile,
2142 &from_xyz, &inv_dst_tf_r, &inv_dst_tf_b, &inv_dst_tf_g)) {
2143 return false;
2144 }
2145
2146 if (srcProfile->has_A2B) {
2147 if (srcProfile->A2B.input_channels) {
2148 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2149 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 +00002150 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002151 *ops++ = oa.op;
2152 *args++ = oa.arg;
2153 }
2154 }
skia-autoroll@skia-public.iam.gserviceaccount.comcb4db0e2018-10-15 19:27:22 +00002155 *ops++ = Op_clamp;
skia-autoroll@skia-public.iam.gserviceaccount.com6fa99262018-10-17 15:29:14 +00002156 *ops++ = Op_clut;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002157 *args++ = &srcProfile->A2B;
2158 }
2159
2160 if (srcProfile->A2B.matrix_channels == 3) {
2161 for (int i = 0; i < 3; i++) {
2162 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 +00002163 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002164 *ops++ = oa.op;
2165 *args++ = oa.arg;
2166 }
2167 }
2168
2169 static const skcms_Matrix3x4 I = {{
2170 {1,0,0,0},
2171 {0,1,0,0},
2172 {0,0,1,0},
2173 }};
2174 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2175 *ops++ = Op_matrix_3x4;
2176 *args++ = &srcProfile->A2B.matrix;
2177 }
2178 }
2179
2180 if (srcProfile->A2B.output_channels == 3) {
2181 for (int i = 0; i < 3; i++) {
2182 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 +00002183 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002184 *ops++ = oa.op;
2185 *args++ = oa.arg;
2186 }
2187 }
2188 }
2189
2190 if (srcProfile->pcs == skcms_Signature_Lab) {
2191 *ops++ = Op_lab_to_xyz;
2192 }
2193
2194 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2195 for (int i = 0; i < 3; i++) {
2196 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com300b6192018-09-07 15:26:38 +00002197 if (oa.arg) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002198 *ops++ = oa.op;
2199 *args++ = oa.arg;
2200 }
2201 }
2202 } else {
2203 return false;
2204 }
2205
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002206 // A2B sources should already be in XYZD50 at this point.
2207 // Others still need to be transformed using their toXYZD50 matrix.
2208 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2209 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2210 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2211 static const skcms_Matrix3x3 I = {{
2212 { 1.0f, 0.0f, 0.0f },
2213 { 0.0f, 1.0f, 0.0f },
2214 { 0.0f, 0.0f, 1.0f },
2215 }};
2216 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2217
2218 // There's a chance the source and destination gamuts are identical,
2219 // in which case we can skip the gamut transform.
2220 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2221 // Concat the entire gamut transform into from_xyz,
2222 // now slightly misnamed but it's a handy spot to stash the result.
2223 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2224 *ops++ = Op_matrix_3x3;
2225 *args++ = &from_xyz;
2226 }
2227
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002228 // Encode back to dst RGB using its parametric transfer functions.
2229 if (!is_identity_tf(&inv_dst_tf_r)) { *ops++ = Op_tf_r; *args++ = &inv_dst_tf_r; }
2230 if (!is_identity_tf(&inv_dst_tf_g)) { *ops++ = Op_tf_g; *args++ = &inv_dst_tf_g; }
2231 if (!is_identity_tf(&inv_dst_tf_b)) { *ops++ = Op_tf_b; *args++ = &inv_dst_tf_b; }
2232 }
2233
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com60f1d832018-07-27 14:43:30 +00002234 // Clamp here before premul to make sure we're clamping to fixed-point values _and_ gamut,
2235 // not just to values that fit in the fixed point representation.
2236 //
2237 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2238 // 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 +00002239 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2240 *ops++ = Op_clamp;
2241 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002242 if (dstAlpha == skcms_AlphaFormat_Opaque) {
2243 *ops++ = Op_force_opaque;
2244 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2245 *ops++ = Op_premul;
2246 }
2247 if (dstFmt & 1) {
2248 *ops++ = Op_swap_rb;
2249 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002250 switch (dstFmt >> 1) {
2251 default: return false;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com69bcd162018-09-12 20:28:38 +00002252 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_store_a8; break;
2253 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_store_g8; break;
2254 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_store_4444; break;
2255 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_store_565; break;
2256 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_store_888; break;
2257 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_store_8888; break;
2258 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_store_1010102; break;
2259 case skcms_PixelFormat_RGB_161616LE >> 1: *ops++ = Op_store_161616LE; break;
2260 case skcms_PixelFormat_RGBA_16161616LE >> 1: *ops++ = Op_store_16161616LE; break;
2261 case skcms_PixelFormat_RGB_161616BE >> 1: *ops++ = Op_store_161616BE; break;
2262 case skcms_PixelFormat_RGBA_16161616BE >> 1: *ops++ = Op_store_16161616BE; break;
2263 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2264 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2265 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2266 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 +00002267 }
2268
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002269 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002270#if defined(TEST_FOR_HSW)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002271 if (hsw_ok()) { run = hsw::run_program; }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002272#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002273 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 +00002274 return true;
2275}
2276
2277static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2278#if defined(NDEBUG)
2279 (void)profile;
2280#else
2281 skcms_Matrix3x3 fromXYZD50;
2282 skcms_TransferFunction invR, invG, invB;
2283 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2284#endif
2285}
2286
2287bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2288 skcms_Matrix3x3 fromXYZD50;
2289 if (!profile->has_trc || !profile->has_toXYZD50
2290 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2291 return false;
2292 }
2293
2294 skcms_TransferFunction tf[3];
2295 for (int i = 0; i < 3; i++) {
2296 skcms_TransferFunction inv;
2297 if (profile->trc[i].table_entries == 0
2298 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2299 tf[i] = profile->trc[i].parametric;
2300 continue;
2301 }
2302
2303 float max_error;
2304 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2305 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2306 return false;
2307 }
2308 }
2309
2310 for (int i = 0; i < 3; ++i) {
2311 profile->trc[i].table_entries = 0;
2312 profile->trc[i].parametric = tf[i];
2313 }
2314
2315 assert_usable_as_destination(profile);
2316 return true;
2317}
2318
2319bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2320 // Operate on a copy of profile, so we can choose the best TF for the original curves
2321 skcms_ICCProfile result = *profile;
2322 if (!skcms_MakeUsableAsDestination(&result)) {
2323 return false;
2324 }
2325
2326 int best_tf = 0;
2327 float min_max_error = INFINITY_;
2328 for (int i = 0; i < 3; i++) {
2329 skcms_TransferFunction inv;
skia-autoroll@skia-public.iam.gserviceaccount.comc064d0b2018-10-15 16:07:14 +00002330 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2331 return false;
2332 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002333
2334 float err = 0;
2335 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002336 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 +00002337 }
2338 if (min_max_error > err) {
2339 min_max_error = err;
2340 best_tf = i;
2341 }
2342 }
2343
2344 for (int i = 0; i < 3; i++) {
2345 result.trc[i].parametric = result.trc[best_tf].parametric;
2346 }
2347
2348 *profile = result;
2349 assert_usable_as_destination(profile);
2350 return true;
2351}