blob: f6f7158e7acaa6ccd335831d6c2eea746ade969b [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
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000723static int fit_linear(const skcms_Curve* curve, int N, float tol, float* c, float* d, float* f) {
724 assert(N > 1);
725 // We iteratively fit the first points to the TF's linear piece.
726 // We want the cx + f line to pass through the first and last points we fit exactly.
727 //
728 // As we walk along the points we find the minimum and maximum slope of the line before the
729 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
730 // emtpy, when we definitely can't add any more points.
731 //
732 // Some points' error intervals may intersect the running interval but not lie fully
733 // within it. So we keep track of the last point we saw that is a valid end point candidate,
734 // and once the search is done, back up to build the line through *that* point.
735 const float dx = 1.0f / (N - 1);
736
737 int lin_points = 1;
738 *f = eval_curve(curve, 0);
739
740 float slope_min = -INFINITY_;
741 float slope_max = +INFINITY_;
742 for (int i = 1; i < N; ++i) {
743 float x = i * dx;
744 float y = eval_curve(curve, x);
745
746 float slope_max_i = (y + tol - *f) / x,
747 slope_min_i = (y - tol - *f) / x;
748 if (slope_max_i < slope_min || slope_max < slope_min_i) {
749 // Slope intervals would no longer overlap.
750 break;
751 }
752 slope_max = fminf_(slope_max, slope_max_i);
753 slope_min = fmaxf_(slope_min, slope_min_i);
754
755 float cur_slope = (y - *f) / x;
756 if (slope_min <= cur_slope && cur_slope <= slope_max) {
757 lin_points = i + 1;
758 *c = cur_slope;
759 }
760 }
761
762 // Set D to the last point that met our tolerance.
763 *d = (lin_points - 1) * dx;
764 return lin_points;
765}
766
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000767static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
768 bool ok = false;
769 if (tag->type == skcms_Signature_mft1) {
770 ok = read_tag_mft1(tag, a2b);
771 } else if (tag->type == skcms_Signature_mft2) {
772 ok = read_tag_mft2(tag, a2b);
773 } else if (tag->type == skcms_Signature_mAB) {
774 ok = read_tag_mab(tag, a2b, pcs_is_xyz);
775 }
776 if (!ok) {
777 return false;
778 }
779
780 // Detect and canonicalize identity tables.
781 skcms_Curve* curves[] = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000782 a2b->input_channels > 0 ? a2b->input_curves + 0 : nullptr,
783 a2b->input_channels > 1 ? a2b->input_curves + 1 : nullptr,
784 a2b->input_channels > 2 ? a2b->input_curves + 2 : nullptr,
785 a2b->input_channels > 3 ? a2b->input_curves + 3 : nullptr,
786 a2b->matrix_channels > 0 ? a2b->matrix_curves + 0 : nullptr,
787 a2b->matrix_channels > 1 ? a2b->matrix_curves + 1 : nullptr,
788 a2b->matrix_channels > 2 ? a2b->matrix_curves + 2 : nullptr,
789 a2b->output_channels > 0 ? a2b->output_curves + 0 : nullptr,
790 a2b->output_channels > 1 ? a2b->output_curves + 1 : nullptr,
791 a2b->output_channels > 2 ? a2b->output_curves + 2 : nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000792 };
793
794 for (int i = 0; i < ARRAY_COUNT(curves); i++) {
795 skcms_Curve* curve = curves[i];
796
797 if (curve && curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
798 int N = (int)curve->table_entries;
799
800 float c,d,f;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +0000801 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 +0000802 && c == 1.0f
803 && f == 0.0f) {
804 curve->table_entries = 0;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000805 curve->table_8 = nullptr;
806 curve->table_16 = nullptr;
807 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 +0000808 }
809 }
810 }
811
812 return true;
813}
814
815void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
816 if (!profile || !profile->buffer || !tag) { return; }
817 if (idx > profile->tag_count) { return; }
818 const tag_Layout* tags = get_tag_table(profile);
819 tag->signature = read_big_u32(tags[idx].signature);
820 tag->size = read_big_u32(tags[idx].size);
821 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
822 tag->type = read_big_u32(tag->buf);
823}
824
825bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
826 if (!profile || !profile->buffer || !tag) { return false; }
827 const tag_Layout* tags = get_tag_table(profile);
828 for (uint32_t i = 0; i < profile->tag_count; ++i) {
829 if (read_big_u32(tags[i].signature) == sig) {
830 tag->signature = sig;
831 tag->size = read_big_u32(tags[i].size);
832 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
833 tag->type = read_big_u32(tag->buf);
834 return true;
835 }
836 }
837 return false;
838}
839
840static bool usable_as_src(const skcms_ICCProfile* profile) {
841 return profile->has_A2B
842 || (profile->has_trc && profile->has_toXYZD50);
843}
844
845bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
846 assert(SAFE_SIZEOF(header_Layout) == 132);
847
848 if (!profile) {
849 return false;
850 }
851 memset(profile, 0, SAFE_SIZEOF(*profile));
852
853 if (len < SAFE_SIZEOF(header_Layout)) {
854 return false;
855 }
856
857 // Byte-swap all header fields
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000858 const header_Layout* header = (const header_Layout*)buf;
859 profile->buffer = (const uint8_t*)buf;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000860 profile->size = read_big_u32(header->size);
861 uint32_t version = read_big_u32(header->version);
862 profile->data_color_space = read_big_u32(header->data_color_space);
863 profile->pcs = read_big_u32(header->pcs);
864 uint32_t signature = read_big_u32(header->signature);
865 float illuminant_X = read_big_fixed(header->illuminant_X);
866 float illuminant_Y = read_big_fixed(header->illuminant_Y);
867 float illuminant_Z = read_big_fixed(header->illuminant_Z);
868 profile->tag_count = read_big_u32(header->tag_count);
869
870 // Validate signature, size (smaller than buffer, large enough to hold tag table),
871 // and major version
872 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
873 if (signature != skcms_Signature_acsp ||
874 profile->size > len ||
875 profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
876 (version >> 24) > 4) {
877 return false;
878 }
879
880 // Validate that illuminant is D50 white
881 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
882 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
883 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
884 return false;
885 }
886
887 // Validate that all tag entries have sane offset + size
888 const tag_Layout* tags = get_tag_table(profile);
889 for (uint32_t i = 0; i < profile->tag_count; ++i) {
890 uint32_t tag_offset = read_big_u32(tags[i].offset);
891 uint32_t tag_size = read_big_u32(tags[i].size);
892 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
893 if (tag_size < 4 || tag_end > profile->size) {
894 return false;
895 }
896 }
897
898 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
899 return false;
900 }
901
902 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
903
904 // Pre-parse commonly used tags.
905 skcms_ICCTag kTRC;
906 if (profile->data_color_space == skcms_Signature_Gray &&
907 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000908 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 +0000909 // Malformed tag
910 return false;
911 }
912 profile->trc[1] = profile->trc[0];
913 profile->trc[2] = profile->trc[0];
914 profile->has_trc = true;
915
916 if (pcs_is_xyz) {
917 profile->toXYZD50.vals[0][0] = illuminant_X;
918 profile->toXYZD50.vals[1][1] = illuminant_Y;
919 profile->toXYZD50.vals[2][2] = illuminant_Z;
920 profile->has_toXYZD50 = true;
921 }
922 } else {
923 skcms_ICCTag rTRC, gTRC, bTRC;
924 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
925 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
926 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000927 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
928 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
929 !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 +0000930 // Malformed TRC tags
931 return false;
932 }
933 profile->has_trc = true;
934 }
935
936 skcms_ICCTag rXYZ, gXYZ, bXYZ;
937 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
938 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
939 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
940 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
941 // Malformed XYZ tags
942 return false;
943 }
944 profile->has_toXYZD50 = true;
945 }
946 }
947
948 skcms_ICCTag a2b_tag;
949
950 // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
951 // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
952 // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
953 // and all our known users are thinking exclusively in terms of relative colormetric.
954 const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
955 for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
956 if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
957 if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
958 // Malformed A2B tag
959 return false;
960 }
961 profile->has_A2B = true;
962 break;
963 }
964 }
965
966 return usable_as_src(profile);
967}
968
969
970const skcms_ICCProfile* skcms_sRGB_profile() {
971 static const skcms_ICCProfile sRGB_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +0000972 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000973
974 0, // size, moot here
975 skcms_Signature_RGB, // data_color_space
976 skcms_Signature_XYZ, // pcs
977 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000978
979 // We choose to represent sRGB with its canonical transfer function,
980 // and with its canonical XYZD50 gamut matrix.
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000981 true, // has_trc, followed by the 3 trc curves
982 {
983 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
984 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
985 {{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 +0000986 },
987
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000988 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
989 {{
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +0000990 { 0.436065674f, 0.385147095f, 0.143066406f },
991 { 0.222488403f, 0.716873169f, 0.060607910f },
992 { 0.013916016f, 0.097076416f, 0.714096069f },
993 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +0000994
995 false, // has_A2B, followed by a2b itself which we don't care about.
996 {
997 0,
998 {
999 {{0, {1,1, 0,0,0,0,0}}},
1000 {{0, {1,1, 0,0,0,0,0}}},
1001 {{0, {1,1, 0,0,0,0,0}}},
1002 {{0, {1,1, 0,0,0,0,0}}},
1003 },
1004 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001005 nullptr,
1006 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001007
1008 0,
1009 {
1010 {{0, {1,1, 0,0,0,0,0}}},
1011 {{0, {1,1, 0,0,0,0,0}}},
1012 {{0, {1,1, 0,0,0,0,0}}},
1013 },
1014 {{
1015 { 1,0,0,0 },
1016 { 0,1,0,0 },
1017 { 0,0,1,0 },
1018 }},
1019
1020 0,
1021 {
1022 {{0, {1,1, 0,0,0,0,0}}},
1023 {{0, {1,1, 0,0,0,0,0}}},
1024 {{0, {1,1, 0,0,0,0,0}}},
1025 },
1026 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001027 };
1028 return &sRGB_profile;
1029}
1030
1031const skcms_ICCProfile* skcms_XYZD50_profile() {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001032 // 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 +00001033 static const skcms_ICCProfile XYZD50_profile = {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001034 nullptr, // buffer, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001035
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001036 0, // size, moot here
1037 skcms_Signature_RGB, // data_color_space
1038 skcms_Signature_XYZ, // pcs
1039 0, // tag count, moot here
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001040
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001041 true, // has_trc, followed by the 3 trc curves
1042 {
1043 {{0, {1,1, 0,0,0,0,0}}},
1044 {{0, {1,1, 0,0,0,0,0}}},
1045 {{0, {1,1, 0,0,0,0,0}}},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001046 },
1047
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001048 true, // has_toXYZD50, followed by 3x3 toXYZD50 matrix
1049 {{
1050 { 1,0,0 },
1051 { 0,1,0 },
1052 { 0,0,1 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001053 }},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001054
1055 false, // has_A2B, followed by a2b itself which we don't care about.
1056 {
1057 0,
1058 {
1059 {{0, {1,1, 0,0,0,0,0}}},
1060 {{0, {1,1, 0,0,0,0,0}}},
1061 {{0, {1,1, 0,0,0,0,0}}},
1062 {{0, {1,1, 0,0,0,0,0}}},
1063 },
1064 {0,0,0,0},
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001065 nullptr,
1066 nullptr,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00001067
1068 0,
1069 {
1070 {{0, {1,1, 0,0,0,0,0}}},
1071 {{0, {1,1, 0,0,0,0,0}}},
1072 {{0, {1,1, 0,0,0,0,0}}},
1073 },
1074 {{
1075 { 1,0,0,0 },
1076 { 0,1,0,0 },
1077 { 0,0,1,0 },
1078 }},
1079
1080 0,
1081 {
1082 {{0, {1,1, 0,0,0,0,0}}},
1083 {{0, {1,1, 0,0,0,0,0}}},
1084 {{0, {1,1, 0,0,0,0,0}}},
1085 },
1086 },
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001087 };
1088
1089 return &XYZD50_profile;
1090}
1091
1092const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1093 return &skcms_sRGB_profile()->trc[0].parametric;
1094}
1095
1096const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1097 static const skcms_TransferFunction sRGB_inv =
1098 { (float)(1/2.4), 1.137119f, 0, 12.92f, 0.0031308f, -0.055f, 0 };
1099 return &sRGB_inv;
1100}
1101
1102const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1103 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1104 return &identity;
1105}
1106
1107const uint8_t skcms_252_random_bytes[] = {
1108 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1109 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1110 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1111 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1112 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1113 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1114 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1115 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1116 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1117 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1118 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1119 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1120 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1121 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1122 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1123 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1124};
1125
1126bool skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile* A, const skcms_ICCProfile* B) {
1127 // For now this is the essentially the same strategy we use in test_only.c
1128 // for our skcms_Transform() smoke tests:
1129 // 1) transform A to XYZD50
1130 // 2) transform B to XYZD50
1131 // 3) return true if they're similar enough
1132 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1133
1134 // Here are 252 of a random shuffle of all possible bytes.
1135 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1136
1137 if (A->data_color_space != B->data_color_space) {
1138 return false;
1139 }
1140
1141 // Interpret as RGB_888 if data color space is RGB or GRAY, RGBA_8888 if CMYK.
1142 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1143 size_t npixels = 84;
1144 if (A->data_color_space == skcms_Signature_CMYK) {
1145 fmt = skcms_PixelFormat_RGBA_8888;
1146 npixels = 63;
1147 }
1148
1149 uint8_t dstA[252],
1150 dstB[252];
1151 if (!skcms_Transform(
1152 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1153 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1154 npixels)) {
1155 return false;
1156 }
1157 if (!skcms_Transform(
1158 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1159 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1160 npixels)) {
1161 return false;
1162 }
1163
1164 for (size_t i = 0; i < 252; i++) {
1165 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1166 return false;
1167 }
1168 }
1169 return true;
1170}
1171
1172bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1173 const skcms_TransferFunction* inv_tf) {
1174 if (!profile || !profile->has_trc) {
1175 return false;
1176 }
1177
1178 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1179 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1180 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1181}
1182
1183static bool is_zero_to_one(float x) {
1184 return 0 <= x && x <= 1;
1185}
1186
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001187typedef struct { float vals[3]; } skcms_Vector3;
1188
1189static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1190 skcms_Vector3 dst = {{0,0,0}};
1191 for (int row = 0; row < 3; ++row) {
1192 dst.vals[row] = m->vals[row][0] * v->vals[0]
1193 + m->vals[row][1] * v->vals[1]
1194 + m->vals[row][2] * v->vals[2];
1195 }
1196 return dst;
1197}
1198
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001199bool skcms_PrimariesToXYZD50(float rx, float ry,
1200 float gx, float gy,
1201 float bx, float by,
1202 float wx, float wy,
1203 skcms_Matrix3x3* toXYZD50) {
1204 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1205 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1206 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1207 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1208 !toXYZD50) {
1209 return false;
1210 }
1211
1212 // First, we need to convert xy values (primaries) to XYZ.
1213 skcms_Matrix3x3 primaries = {{
1214 { rx, gx, bx },
1215 { ry, gy, by },
1216 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1217 }};
1218 skcms_Matrix3x3 primaries_inv;
1219 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1220 return false;
1221 }
1222
1223 // Assumes that Y is 1.0f.
1224 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 +00001225 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001226
1227 skcms_Matrix3x3 toXYZ = {{
1228 { XYZ.vals[0], 0, 0 },
1229 { 0, XYZ.vals[1], 0 },
1230 { 0, 0, XYZ.vals[2] },
1231 }};
1232 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1233
1234 // Now convert toXYZ matrix to toXYZD50.
1235 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1236
1237 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1238 // the matrices below. The Bradford method is used by Adobe and is widely considered
1239 // to be the best.
1240 skcms_Matrix3x3 xyz_to_lms = {{
1241 { 0.8951f, 0.2664f, -0.1614f },
1242 { -0.7502f, 1.7135f, 0.0367f },
1243 { 0.0389f, -0.0685f, 1.0296f },
1244 }};
1245 skcms_Matrix3x3 lms_to_xyz = {{
1246 { 0.9869929f, -0.1470543f, 0.1599627f },
1247 { 0.4323053f, 0.5183603f, 0.0492912f },
1248 { -0.0085287f, 0.0400428f, 0.9684867f },
1249 }};
1250
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001251 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1252 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001253
1254 skcms_Matrix3x3 DXtoD50 = {{
1255 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1256 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1257 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1258 }};
1259 DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
1260 DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
1261
1262 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1263 return true;
1264}
1265
1266
1267bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1268 double a00 = src->vals[0][0],
1269 a01 = src->vals[1][0],
1270 a02 = src->vals[2][0],
1271 a10 = src->vals[0][1],
1272 a11 = src->vals[1][1],
1273 a12 = src->vals[2][1],
1274 a20 = src->vals[0][2],
1275 a21 = src->vals[1][2],
1276 a22 = src->vals[2][2];
1277
1278 double b0 = a00*a11 - a01*a10,
1279 b1 = a00*a12 - a02*a10,
1280 b2 = a01*a12 - a02*a11,
1281 b3 = a20,
1282 b4 = a21,
1283 b5 = a22;
1284
1285 double determinant = b0*b5
1286 - b1*b4
1287 + b2*b3;
1288
1289 if (determinant == 0) {
1290 return false;
1291 }
1292
1293 double invdet = 1.0 / determinant;
1294 if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1295 return false;
1296 }
1297
1298 b0 *= invdet;
1299 b1 *= invdet;
1300 b2 *= invdet;
1301 b3 *= invdet;
1302 b4 *= invdet;
1303 b5 *= invdet;
1304
1305 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1306 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1307 dst->vals[2][0] = (float)( + b2 );
1308 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1309 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1310 dst->vals[2][1] = (float)( - b1 );
1311 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1312 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1313 dst->vals[2][2] = (float)( + b0 );
1314
1315 for (int r = 0; r < 3; ++r)
1316 for (int c = 0; c < 3; ++c) {
1317 if (!isfinitef_(dst->vals[r][c])) {
1318 return false;
1319 }
1320 }
1321 return true;
1322}
1323
1324skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1325 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1326 for (int r = 0; r < 3; r++)
1327 for (int c = 0; c < 3; c++) {
1328 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1329 + A->vals[r][1] * B->vals[1][c]
1330 + A->vals[r][2] * B->vals[2][c];
1331 }
1332 return m;
1333}
1334
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001335#if defined(__clang__) || defined(__GNUC__)
1336 #define small_memcpy __builtin_memcpy
1337#else
1338 #define small_memcpy memcpy
1339#endif
1340
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001341static float log2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001342 // The first approximation of log2(x) is its exponent 'e', minus 127.
1343 int32_t bits;
1344 small_memcpy(&bits, &x, sizeof(bits));
1345
1346 float e = (float)bits * (1.0f / (1<<23));
1347
1348 // If we use the mantissa too we can refine the error signficantly.
1349 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
1350 float m;
1351 small_memcpy(&m, &m_bits, sizeof(m));
1352
1353 return (e - 124.225514990f
1354 - 1.498030302f*m
1355 - 1.725879990f/(0.3520887068f + m));
1356}
1357
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001358static float exp2f_(float x) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001359 float fract = x - floorf_(x);
1360
1361 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
1362 - 1.490129070f*fract
1363 + 27.728023300f/(4.84252568f - fract));
1364 if (fbits > INT_MAX) {
1365 return INFINITY_;
1366 } else if (fbits < INT_MIN) {
1367 return -INFINITY_;
1368 }
1369 int32_t bits = (int32_t)fbits;
1370 small_memcpy(&x, &bits, sizeof(x));
1371 return x;
1372}
1373
1374float powf_(float x, float y) {
1375 return (x == 0) || (x == 1) ? x
1376 : exp2f_(log2f_(x) * y);
1377}
1378
1379float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
1380 float sign = x < 0 ? -1.0f : 1.0f;
1381 x *= sign;
1382
1383 return sign * (x < tf->d ? tf->c * x + tf->f
1384 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
1385}
1386
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001387// TODO: Adjust logic here? This still assumes that purely linear inputs will have D > 1, which
1388// we never generate. It also emits inverted linear using the same formulation. Standardize on
1389// G == 1 here, too?
1390bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
1391 // Original equation is: y = (ax + b)^g + e for x >= d
1392 // y = cx + f otherwise
1393 //
1394 // so 1st inverse is: (y - e)^(1/g) = ax + b
1395 // x = ((y - e)^(1/g) - b) / a
1396 //
1397 // which can be re-written as: x = (1/a)(y - e)^(1/g) - b/a
1398 // x = ((1/a)^g)^(1/g) * (y - e)^(1/g) - b/a
1399 // x = ([(1/a)^g]y + [-((1/a)^g)e]) ^ [1/g] + [-b/a]
1400 //
1401 // and 2nd inverse is: x = (y - f) / c
1402 // which can be re-written as: x = [1/c]y + [-f/c]
1403 //
1404 // and now both can be expressed in terms of the same parametric form as the
1405 // original - parameters are enclosed in square brackets.
1406 skcms_TransferFunction tf_inv = { 0, 0, 0, 0, 0, 0, 0 };
1407
1408 // This rejects obviously malformed inputs, as well as decreasing functions
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001409 if (!tf_is_valid(src)) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001410 return false;
1411 }
1412
1413 // There are additional constraints to be invertible
1414 bool has_nonlinear = (src->d <= 1);
1415 bool has_linear = (src->d > 0);
1416
1417 // Is the linear section not invertible?
1418 if (has_linear && src->c == 0) {
1419 return false;
1420 }
1421
1422 // Is the nonlinear section not invertible?
1423 if (has_nonlinear && (src->a == 0 || src->g == 0)) {
1424 return false;
1425 }
1426
1427 // If both segments are present, they need to line up
1428 if (has_linear && has_nonlinear) {
1429 float l_at_d = src->c * src->d + src->f;
1430 float n_at_d = powf_(src->a * src->d + src->b, src->g) + src->e;
1431 if (fabsf_(l_at_d - n_at_d) > (1 / 512.0f)) {
1432 return false;
1433 }
1434 }
1435
1436 // Invert linear segment
1437 if (has_linear) {
1438 tf_inv.c = 1.0f / src->c;
1439 tf_inv.f = -src->f / src->c;
1440 }
1441
1442 // Invert nonlinear segment
1443 if (has_nonlinear) {
1444 tf_inv.g = 1.0f / src->g;
1445 tf_inv.a = powf_(1.0f / src->a, src->g);
1446 tf_inv.b = -tf_inv.a * src->e;
1447 tf_inv.e = -src->b / src->a;
1448 }
1449
1450 if (!has_linear) {
1451 tf_inv.d = 0;
1452 } else if (!has_nonlinear) {
1453 // Any value larger than 1 works
1454 tf_inv.d = 2.0f;
1455 } else {
1456 tf_inv.d = src->c * src->d + src->f;
1457 }
1458
1459 *dst = tf_inv;
1460 return true;
1461}
1462
1463// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
1464
1465// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
1466//
1467// tf(x) = cx + f x < d
1468// tf(x) = (ax + b)^g + e x ≥ d
1469//
1470// When fitting, we add the additional constraint that both pieces meet at d:
1471//
1472// cd + f = (ad + b)^g + e
1473//
1474// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
1475//
1476// tf(x) = cx + f x < d
1477// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
1478//
1479// Our overall strategy is then:
1480// For a couple tolerances,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001481// - 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 +00001482// - invert c,d,f
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001483// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
1484// (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 +00001485// Return the parameters with least maximum error.
1486//
1487// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
1488// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
1489//
1490// let y = Table(x)
1491// r(x) = x - f_inv(y)
1492//
1493// ∂r/∂g = ln(ay + b)*(ay + b)^g
1494// - ln(ad + b)*(ad + b)^g
1495// ∂r/∂a = yg(ay + b)^(g-1)
1496// - dg(ad + b)^(g-1)
1497// ∂r/∂b = g(ay + b)^(g-1)
1498// - g(ad + b)^(g-1)
1499
1500// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
1501// and fill out the gradient of the residual into dfdP.
1502static float rg_nonlinear(float x,
1503 const skcms_Curve* curve,
1504 const skcms_TransferFunction* tf,
1505 const float P[3],
1506 float dfdP[3]) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001507 const float y = eval_curve(curve, x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001508
1509 const float g = P[0], a = P[1], b = P[2],
1510 c = tf->c, d = tf->d, f = tf->f;
1511
1512 const float Y = fmaxf_(a*y + b, 0.0f),
1513 D = a*d + b;
1514 assert (D >= 0);
1515
1516 // The gradient.
1517 dfdP[0] = 0.69314718f*log2f_(Y)*powf_(Y, g)
1518 - 0.69314718f*log2f_(D)*powf_(D, g);
1519 dfdP[1] = y*g*powf_(Y, g-1)
1520 - d*g*powf_(D, g-1);
1521 dfdP[2] = g*powf_(Y, g-1)
1522 - g*powf_(D, g-1);
1523
1524 // The residual.
1525 const float f_inv = powf_(Y, g)
1526 - powf_(D, g)
1527 + c*d + f;
1528 return x - f_inv;
1529}
1530
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001531static bool gauss_newton_step(const skcms_Curve* curve,
1532 const skcms_TransferFunction* tf,
1533 float P[3],
1534 float x0, float dx, int N) {
1535 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
1536 //
1537 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
1538 // where r(P) is the residual vector
1539 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
1540 //
1541 // Let's review the shape of each of these expressions:
1542 // r(P) is [N x 1], a column vector with one entry per value of x tested
1543 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
1544 // Jf^T is [3 x N], the transpose of Jf
1545 //
1546 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
1547 // and so is its inverse (Jf^T Jf)^-1
1548 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
1549 //
1550 // Our implementation strategy to get to the final ∆P is
1551 // 1) evaluate Jf^T Jf, call that lhs
1552 // 2) evaluate Jf^T r(P), call that rhs
1553 // 3) invert lhs
1554 // 4) multiply inverse lhs by rhs
1555 //
1556 // This is a friendly implementation strategy because we don't have to have any
1557 // buffers that scale with N, and equally nice don't have to perform any matrix
1558 // operations that are variable size.
1559 //
1560 // Other implementation strategies could trade this off, e.g. evaluating the
1561 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
1562 // the residuals. That would probably require implementing singular value
1563 // decomposition, and would create a [3 x N] matrix to be multiplied by the
1564 // [N x 1] residual vector, but on the upside I think that'd eliminate the
1565 // possibility of this gauss_newton_step() function ever failing.
1566
1567 // 0) start off with lhs and rhs safely zeroed.
1568 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
1569 skcms_Vector3 rhs = { {0,0,0} };
1570
1571 // 1,2) evaluate lhs and evaluate rhs
1572 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
1573 // so we'll have to update lhs and rhs at the same time.
1574 for (int i = 0; i < N; i++) {
1575 float x = x0 + i*dx;
1576
1577 float dfdP[3] = {0,0,0};
1578 float resid = rg_nonlinear(x,curve,tf,P, dfdP);
1579
1580 for (int r = 0; r < 3; r++) {
1581 for (int c = 0; c < 3; c++) {
1582 lhs.vals[r][c] += dfdP[r] * dfdP[c];
1583 }
1584 rhs.vals[r] += dfdP[r] * resid;
1585 }
1586 }
1587
1588 // If any of the 3 P parameters are unused, this matrix will be singular.
1589 // Detect those cases and fix them up to indentity instead, so we can invert.
1590 for (int k = 0; k < 3; k++) {
1591 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
1592 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
1593 lhs.vals[k][k] = 1;
1594 }
1595 }
1596
1597 // 3) invert lhs
1598 skcms_Matrix3x3 lhs_inv;
1599 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
1600 return false;
1601 }
1602
1603 // 4) multiply inverse lhs by rhs
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001604 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001605 P[0] += dP.vals[0];
1606 P[1] += dP.vals[1];
1607 P[2] += dP.vals[2];
1608 return isfinitef_(P[0]) && isfinitef_(P[1]) && isfinitef_(P[2]);
1609}
1610
1611
1612// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
1613static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
1614 float P[3] = { tf->g, tf->a, tf->b };
1615
1616 // No matter where we start, dx should always represent N even steps from 0 to 1.
1617 const float dx = 1.0f / (N-1);
1618
1619 for (int j = 0; j < 3/*TODO: tune*/; j++) {
1620 // These extra constraints a >= 0 and ad+b >= 0 are not modeled in the optimization.
1621 // We don't really know how to fix up a if it goes negative.
1622 if (P[1] < 0) {
1623 return false;
1624 }
1625 // If ad+b goes negative, we feel just barely not uneasy enough to tweak b so ad+b is zero.
1626 if (P[1] * tf->d + P[2] < 0) {
1627 P[2] = -P[1] * tf->d;
1628 }
1629 assert (P[1] >= 0 &&
1630 P[1] * tf->d + P[2] >= 0);
1631
1632 if (!gauss_newton_step(curve, tf,
1633 P,
1634 L*dx, dx, N-L)) {
1635 return false;
1636 }
1637 }
1638
1639 // We need to apply our fixups one last time
1640 if (P[1] < 0) {
1641 return false;
1642 }
1643 if (P[1] * tf->d + P[2] < 0) {
1644 P[2] = -P[1] * tf->d;
1645 }
1646
1647 tf->g = P[0];
1648 tf->a = P[1];
1649 tf->b = P[2];
1650 tf->e = tf->c*tf->d + tf->f
1651 - powf_(tf->a*tf->d + tf->b, tf->g);
1652 return true;
1653}
1654
1655bool skcms_ApproximateCurve(const skcms_Curve* curve,
1656 skcms_TransferFunction* approx,
1657 float* max_error) {
1658 if (!curve || !approx || !max_error) {
1659 return false;
1660 }
1661
1662 if (curve->table_entries == 0) {
1663 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
1664 return false;
1665 }
1666
1667 if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
1668 // We need at least two points, and must put some reasonable cap on the maximum number.
1669 return false;
1670 }
1671
1672 int N = (int)curve->table_entries;
1673 const float dx = 1.0f / (N - 1);
1674
1675 *max_error = INFINITY_;
1676 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
1677 for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
1678 skcms_TransferFunction tf,
1679 tf_inv;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001680 int L = fit_linear(curve, N, kTolerances[t], &tf.c, &tf.d, &tf.f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001681
1682 if (L == N) {
1683 // If the entire data set was linear, move the coefficients to the nonlinear portion
1684 // with G == 1. This lets use a canonical representation with d == 0.
1685 tf.g = 1;
1686 tf.a = tf.c;
1687 tf.b = tf.f;
1688 tf.c = tf.d = tf.e = tf.f = 0;
1689 } else if (L == N - 1) {
1690 // Degenerate case with only two points in the nonlinear segment. Solve directly.
1691 tf.g = 1;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001692 tf.a = (eval_curve(curve, (N-1)*dx) -
1693 eval_curve(curve, (N-2)*dx))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001694 / dx;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001695 tf.b = eval_curve(curve, (N-2)*dx)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001696 - tf.a * (N-2)*dx;
1697 tf.e = 0;
1698 } else {
1699 // Start by guessing a gamma-only curve through the midpoint.
1700 int mid = (L + N) / 2;
1701 float mid_x = mid / (N - 1.0f);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001702 float mid_y = eval_curve(curve, mid_x);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001703 tf.g = log2f_(mid_y) / log2f_(mid_x);;
1704 tf.a = 1;
1705 tf.b = 0;
1706 tf.e = tf.c*tf.d + tf.f
1707 - powf_(tf.a*tf.d + tf.b, tf.g);
1708
1709
1710 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
1711 !fit_nonlinear(curve, L,N, &tf_inv)) {
1712 continue;
1713 }
1714
1715 // We fit tf_inv, so calculate tf to keep in sync.
1716 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
1717 continue;
1718 }
1719 }
1720
1721 // We find our error by roundtripping the table through tf_inv.
1722 //
1723 // (The most likely use case for this approximation is to be inverted and
1724 // used as the transfer function for a destination color space.)
1725 //
1726 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
1727 // invertible, so re-verify that here (and use the new inverse for testing).
1728 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
1729 continue;
1730 }
1731
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001732 float err = max_roundtrip_error(curve, &tf_inv);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001733 if (*max_error > err) {
1734 *max_error = err;
1735 *approx = tf;
1736 }
1737 }
1738 return isfinitef_(*max_error);
1739}
1740
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001741// ~~~~ Impl. of skcms_Transform() ~~~~
1742
1743typedef enum {
1744 Op_noop,
1745
1746 Op_load_a8,
1747 Op_load_g8,
1748 Op_load_4444,
1749 Op_load_565,
1750 Op_load_888,
1751 Op_load_8888,
1752 Op_load_1010102,
1753 Op_load_161616,
1754 Op_load_16161616,
1755 Op_load_hhh,
1756 Op_load_hhhh,
1757 Op_load_fff,
1758 Op_load_ffff,
1759
1760 Op_swap_rb,
1761 Op_clamp,
1762 Op_invert,
1763 Op_force_opaque,
1764 Op_premul,
1765 Op_unpremul,
1766 Op_matrix_3x3,
1767 Op_matrix_3x4,
1768 Op_lab_to_xyz,
1769
1770 Op_tf_r,
1771 Op_tf_g,
1772 Op_tf_b,
1773 Op_tf_a,
1774
1775 Op_table_8_r,
1776 Op_table_8_g,
1777 Op_table_8_b,
1778 Op_table_8_a,
1779
1780 Op_table_16_r,
1781 Op_table_16_g,
1782 Op_table_16_b,
1783 Op_table_16_a,
1784
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comdf99a9f2018-08-16 12:54:45 +00001785 Op_clut_1D_8,
1786 Op_clut_1D_16,
1787 Op_clut_2D_8,
1788 Op_clut_2D_16,
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00001789 Op_clut_3D_8,
1790 Op_clut_3D_16,
1791 Op_clut_4D_8,
1792 Op_clut_4D_16,
1793
1794 Op_store_a8,
1795 Op_store_g8,
1796 Op_store_4444,
1797 Op_store_565,
1798 Op_store_888,
1799 Op_store_8888,
1800 Op_store_1010102,
1801 Op_store_161616,
1802 Op_store_16161616,
1803 Op_store_hhh,
1804 Op_store_hhhh,
1805 Op_store_fff,
1806 Op_store_ffff,
1807} Op;
1808
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001809// Without this wasm would try to use the N=4 128-bit vector code path,
1810// which while ideal, causes tons of compiler problems. This would be
1811// a good thing to revisit as emcc matures (currently 1.38.5).
1812#if 1 && defined(__EMSCRIPTEN_major__)
1813 #if !defined(SKCMS_PORTABLE)
1814 #define SKCMS_PORTABLE
1815 #endif
1816#endif
1817
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001818#if defined(__clang__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001819 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 +00001820#elif defined(__GNUC__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001821 // For some reason GCC accepts this nonsense, but not the more straightforward version,
1822 // template <int N, typename T> using Vec = T __attribute__((vector_size(N*sizeof(T))));
1823 template <int N, typename T>
1824 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 +00001825
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001826 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 +00001827#endif
1828
1829// First, instantiate our default exec_ops() implementation using the default compiliation target.
1830
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001831namespace baseline {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001832#if defined(SKCMS_PORTABLE) || !(defined(__clang__) || defined(__GNUC__))
1833 #define N 1
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001834 using F = float;
1835 using U64 = uint64_t;
1836 using U32 = uint32_t;
1837 using I32 = int32_t;
1838 using U16 = uint16_t;
1839 using U8 = uint8_t;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001840
1841#elif defined(__AVX512F__)
1842 #define N 16
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001843 using F = Vec<N,float>;
1844 using I32 = Vec<N,int32_t>;
1845 using U64 = Vec<N,uint64_t>;
1846 using U32 = Vec<N,uint32_t>;
1847 using U16 = Vec<N,uint16_t>;
1848 using U8 = Vec<N,uint8_t>;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001849#elif defined(__AVX__)
1850 #define N 8
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001851 using F = Vec<N,float>;
1852 using I32 = Vec<N,int32_t>;
1853 using U64 = Vec<N,uint64_t>;
1854 using U32 = Vec<N,uint32_t>;
1855 using U16 = Vec<N,uint16_t>;
1856 using U8 = Vec<N,uint8_t>;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001857#else
1858 #define N 4
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001859 using F = Vec<N,float>;
1860 using I32 = Vec<N,int32_t>;
1861 using U64 = Vec<N,uint64_t>;
1862 using U32 = Vec<N,uint32_t>;
1863 using U16 = Vec<N,uint16_t>;
1864 using U8 = Vec<N,uint8_t>;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001865#endif
1866
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001867 #define ATTR
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001868 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001869 #undef N
1870 #undef ATTR
1871}
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001872
1873// Now, instantiate any other versions of run_program() we may want for runtime detection.
1874#if !defined(SKCMS_PORTABLE) && (defined(__clang__) || defined(__GNUC__)) \
1875 && defined(__x86_64__) && !defined(__AVX2__)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001876
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001877 namespace hsw {
1878 #define N 8
1879 using F = Vec<N,float>;
1880 using I32 = Vec<N,int32_t>;
1881 using U64 = Vec<N,uint64_t>;
1882 using U32 = Vec<N,uint32_t>;
1883 using U16 = Vec<N,uint16_t>;
1884 using U8 = Vec<N,uint8_t>;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001885
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001886 #define ATTR __attribute__((target("avx2,f16c")))
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001887
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001888 // We check these guards to see if we have support for these features.
1889 // They're likely _not_ defined here in our baseline build config.
1890 #ifndef __AVX__
1891 #define __AVX__ 1
1892 #define UNDEF_AVX
1893 #endif
1894 #ifndef __F16C__
1895 #define __F16C__ 1
1896 #define UNDEF_F16C
1897 #endif
1898 #ifndef __AVX2__
1899 #define __AVX2__ 1
1900 #define UNDEF_AVX2
1901 #endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001902
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001903 #include "src/Transform_inl.h"
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001904
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00001905 #undef N
1906 #undef ATTR
1907
1908 #ifdef UNDEF_AVX
1909 #undef __AVX__
1910 #undef UNDEF_AVX
1911 #endif
1912 #ifdef UNDEF_F16C
1913 #undef __F16C__
1914 #undef UNDEF_F16C
1915 #endif
1916 #ifdef UNDEF_AVX2
1917 #undef __AVX2__
1918 #undef UNDEF_AVX2
1919 #endif
1920 }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001921
1922 #define TEST_FOR_HSW
1923
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001924 static bool hsw_ok() {
1925 static const bool ok = []{
1926 // See http://www.sandpile.org/x86/cpuid.htm
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001927
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001928 // First, a basic cpuid(1).
1929 uint32_t eax, ebx, ecx, edx;
1930 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1931 : "0"(1), "2"(0));
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001932
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001933 // Sanity check for prerequisites.
1934 if ((edx & (1<<25)) != (1<<25)) { return false; } // SSE
1935 if ((edx & (1<<26)) != (1<<26)) { return false; } // SSE2
1936 if ((ecx & (1<< 0)) != (1<< 0)) { return false; } // SSE3
1937 if ((ecx & (1<< 9)) != (1<< 9)) { return false; } // SSSE3
1938 if ((ecx & (1<<19)) != (1<<19)) { return false; } // SSE4.1
1939 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 +00001940
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001941 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 +00001942
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001943 {
1944 uint32_t eax_xgetbv, edx_xgetbv;
1945 __asm__ __volatile__("xgetbv" : "=a"(eax_xgetbv), "=d"(edx_xgetbv) : "c"(0));
1946 if ((eax_xgetbv & (3<<1)) != (3<<1)) { return false; } // XMM+YMM state saved?
1947 }
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 if ((ecx & (1<<28)) != (1<<28)) { return false; } // AVX
1950 if ((ecx & (1<<29)) != (1<<29)) { return false; } // F16C
1951 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 +00001952
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001953 // Call cpuid(7) to check for our final AVX2 feature bit!
1954 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
1955 : "0"(7), "2"(0));
1956 if ((ebx & (1<< 5)) != (1<< 5)) { return false; } // AVX2
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001957
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com76cf60c2018-07-10 14:59:26 +00001958 return true;
1959 }();
1960
1961 return ok;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001962 }
1963
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001964#endif
1965
1966static bool is_identity_tf(const skcms_TransferFunction* tf) {
1967 return tf->g == 1 && tf->a == 1
1968 && tf->b == 0 && tf->c == 0 && tf->d == 0 && tf->e == 0 && tf->f == 0;
1969}
1970
1971typedef struct {
1972 Op op;
1973 const void* arg;
1974} OpAndArg;
1975
1976static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
1977 static const struct { Op parametric, table_8, table_16; } ops[] = {
1978 { Op_tf_r, Op_table_8_r, Op_table_16_r },
1979 { Op_tf_g, Op_table_8_g, Op_table_16_g },
1980 { Op_tf_b, Op_table_8_b, Op_table_16_b },
1981 { Op_tf_a, Op_table_8_a, Op_table_16_a },
1982 };
1983
1984 if (curve->table_entries == 0) {
1985 return is_identity_tf(&curve->parametric)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001986 ? OpAndArg{ Op_noop, nullptr }
1987 : OpAndArg{ ops[channel].parametric, &curve->parametric };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001988 } else if (curve->table_8) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001989 return OpAndArg{ ops[channel].table_8, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001990 } else if (curve->table_16) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001991 return OpAndArg{ ops[channel].table_16, curve };
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001992 }
1993
1994 assert(false);
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comc940f5d2018-07-03 21:00:31 +00001995 return OpAndArg{Op_noop,nullptr};
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00001996}
1997
1998static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
1999 switch (fmt >> 1) { // ignore rgb/bgr
2000 case skcms_PixelFormat_A_8 >> 1: return 1;
2001 case skcms_PixelFormat_G_8 >> 1: return 1;
2002 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2003 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2004 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2005 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2006 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2007 case skcms_PixelFormat_RGB_161616 >> 1: return 6;
2008 case skcms_PixelFormat_RGBA_16161616 >> 1: return 8;
2009 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2010 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2011 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2012 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
2013 }
2014 assert(false);
2015 return 0;
2016}
2017
2018static bool prep_for_destination(const skcms_ICCProfile* profile,
2019 skcms_Matrix3x3* fromXYZD50,
2020 skcms_TransferFunction* invR,
2021 skcms_TransferFunction* invG,
2022 skcms_TransferFunction* invB) {
2023 // We only support destinations with parametric transfer functions
2024 // and with gamuts that can be transformed from XYZD50.
2025 return profile->has_trc
2026 && profile->has_toXYZD50
2027 && profile->trc[0].table_entries == 0
2028 && profile->trc[1].table_entries == 0
2029 && profile->trc[2].table_entries == 0
2030 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2031 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2032 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2033 && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2034}
2035
2036bool skcms_Transform(const void* src,
2037 skcms_PixelFormat srcFmt,
2038 skcms_AlphaFormat srcAlpha,
2039 const skcms_ICCProfile* srcProfile,
2040 void* dst,
2041 skcms_PixelFormat dstFmt,
2042 skcms_AlphaFormat dstAlpha,
2043 const skcms_ICCProfile* dstProfile,
2044 size_t nz) {
2045 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2046 src_bpp = bytes_per_pixel(srcFmt);
2047 // Let's just refuse if the request is absurdly big.
2048 if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2049 return false;
2050 }
2051 int n = (int)nz;
2052
2053 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2054 if (!srcProfile) {
2055 srcProfile = skcms_sRGB_profile();
2056 }
2057 if (!dstProfile) {
2058 dstProfile = skcms_sRGB_profile();
2059 }
2060
2061 // We can't transform in place unless the PixelFormats are the same size.
2062 if (dst == src && (dstFmt >> 1) != (srcFmt >> 1)) {
2063 return false;
2064 }
2065 // TODO: this check lazilly disallows U16 <-> F16, but that would actually be fine.
2066 // TODO: more careful alias rejection (like, dst == src + 1)?
2067
2068 Op program [32];
2069 const void* arguments[32];
2070
2071 Op* ops = program;
2072 const void** args = arguments;
2073
2074 skcms_TransferFunction inv_dst_tf_r, inv_dst_tf_g, inv_dst_tf_b;
2075 skcms_Matrix3x3 from_xyz;
2076
2077 switch (srcFmt >> 1) {
2078 default: return false;
2079 case skcms_PixelFormat_A_8 >> 1: *ops++ = Op_load_a8; break;
2080 case skcms_PixelFormat_G_8 >> 1: *ops++ = Op_load_g8; break;
2081 case skcms_PixelFormat_ABGR_4444 >> 1: *ops++ = Op_load_4444; break;
2082 case skcms_PixelFormat_RGB_565 >> 1: *ops++ = Op_load_565; break;
2083 case skcms_PixelFormat_RGB_888 >> 1: *ops++ = Op_load_888; break;
2084 case skcms_PixelFormat_RGBA_8888 >> 1: *ops++ = Op_load_8888; break;
2085 case skcms_PixelFormat_RGBA_1010102 >> 1: *ops++ = Op_load_1010102; break;
2086 case skcms_PixelFormat_RGB_161616 >> 1: *ops++ = Op_load_161616; break;
2087 case skcms_PixelFormat_RGBA_16161616 >> 1: *ops++ = Op_load_16161616; break;
2088 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_load_hhh; break;
2089 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_load_hhhh; break;
2090 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_load_fff; break;
2091 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_load_ffff; break;
2092 }
2093 if (srcFmt & 1) {
2094 *ops++ = Op_swap_rb;
2095 }
2096 skcms_ICCProfile gray_dst_profile;
2097 if ((dstFmt >> 1) == (skcms_PixelFormat_G_8 >> 1)) {
2098 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2099 // luminance (Y) by the destination transfer function.
2100 gray_dst_profile = *dstProfile;
2101 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2102 dstProfile = &gray_dst_profile;
2103 }
2104
2105 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2106 // Photoshop creates CMYK images as inverse CMYK.
2107 // These happen to be the only ones we've _ever_ seen.
2108 *ops++ = Op_invert;
2109 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2110 srcAlpha = skcms_AlphaFormat_Unpremul;
2111 }
2112
2113 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2114 *ops++ = Op_force_opaque;
2115 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2116 *ops++ = Op_unpremul;
2117 }
2118
2119 // TODO: We can skip this work if both srcAlpha and dstAlpha are PremulLinear, and the profiles
2120 // are the same. Also, if dstAlpha is PremulLinear, and SrcAlpha is Opaque.
2121 if (dstProfile != srcProfile ||
2122 srcAlpha == skcms_AlphaFormat_PremulLinear ||
2123 dstAlpha == skcms_AlphaFormat_PremulLinear) {
2124
2125 if (!prep_for_destination(dstProfile,
2126 &from_xyz, &inv_dst_tf_r, &inv_dst_tf_b, &inv_dst_tf_g)) {
2127 return false;
2128 }
2129
2130 if (srcProfile->has_A2B) {
2131 if (srcProfile->A2B.input_channels) {
2132 for (int i = 0; i < (int)srcProfile->A2B.input_channels; i++) {
2133 OpAndArg oa = select_curve_op(&srcProfile->A2B.input_curves[i], i);
2134 if (oa.op != Op_noop) {
2135 *ops++ = oa.op;
2136 *args++ = oa.arg;
2137 }
2138 }
2139 switch (srcProfile->A2B.input_channels) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comdf99a9f2018-08-16 12:54:45 +00002140 case 1: *ops++ = srcProfile->A2B.grid_8 ? Op_clut_1D_8 : Op_clut_1D_16; break;
2141 case 2: *ops++ = srcProfile->A2B.grid_8 ? Op_clut_2D_8 : Op_clut_2D_16; break;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002142 case 3: *ops++ = srcProfile->A2B.grid_8 ? Op_clut_3D_8 : Op_clut_3D_16; break;
2143 case 4: *ops++ = srcProfile->A2B.grid_8 ? Op_clut_4D_8 : Op_clut_4D_16; break;
2144 default: return false;
2145 }
2146 *args++ = &srcProfile->A2B;
2147 }
2148
2149 if (srcProfile->A2B.matrix_channels == 3) {
2150 for (int i = 0; i < 3; i++) {
2151 OpAndArg oa = select_curve_op(&srcProfile->A2B.matrix_curves[i], i);
2152 if (oa.op != Op_noop) {
2153 *ops++ = oa.op;
2154 *args++ = oa.arg;
2155 }
2156 }
2157
2158 static const skcms_Matrix3x4 I = {{
2159 {1,0,0,0},
2160 {0,1,0,0},
2161 {0,0,1,0},
2162 }};
2163 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2164 *ops++ = Op_matrix_3x4;
2165 *args++ = &srcProfile->A2B.matrix;
2166 }
2167 }
2168
2169 if (srcProfile->A2B.output_channels == 3) {
2170 for (int i = 0; i < 3; i++) {
2171 OpAndArg oa = select_curve_op(&srcProfile->A2B.output_curves[i], i);
2172 if (oa.op != Op_noop) {
2173 *ops++ = oa.op;
2174 *args++ = oa.arg;
2175 }
2176 }
2177 }
2178
2179 if (srcProfile->pcs == skcms_Signature_Lab) {
2180 *ops++ = Op_lab_to_xyz;
2181 }
2182
2183 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2184 for (int i = 0; i < 3; i++) {
2185 OpAndArg oa = select_curve_op(&srcProfile->trc[i], i);
2186 if (oa.op != Op_noop) {
2187 *ops++ = oa.op;
2188 *args++ = oa.arg;
2189 }
2190 }
2191 } else {
2192 return false;
2193 }
2194
2195 // At this point our source colors are linear, either RGB (XYZ-type profiles)
2196 // or XYZ (A2B-type profiles). Unpremul is a linear operation (multiply by a
2197 // constant 1/a), so either way we can do it now if needed.
2198 if (srcAlpha == skcms_AlphaFormat_PremulLinear) {
2199 *ops++ = Op_unpremul;
2200 }
2201
2202 // A2B sources should already be in XYZD50 at this point.
2203 // Others still need to be transformed using their toXYZD50 matrix.
2204 // N.B. There are profiles that contain both A2B tags and toXYZD50 matrices.
2205 // If we use the A2B tags, we need to ignore the XYZD50 matrix entirely.
2206 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2207 static const skcms_Matrix3x3 I = {{
2208 { 1.0f, 0.0f, 0.0f },
2209 { 0.0f, 1.0f, 0.0f },
2210 { 0.0f, 0.0f, 1.0f },
2211 }};
2212 const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2213
2214 // There's a chance the source and destination gamuts are identical,
2215 // in which case we can skip the gamut transform.
2216 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2217 // Concat the entire gamut transform into from_xyz,
2218 // now slightly misnamed but it's a handy spot to stash the result.
2219 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2220 *ops++ = Op_matrix_3x3;
2221 *args++ = &from_xyz;
2222 }
2223
2224 if (dstAlpha == skcms_AlphaFormat_PremulLinear) {
2225 *ops++ = Op_premul;
2226 }
2227
2228 // 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;
2252 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_161616 >> 1: *ops++ = Op_store_161616; break;
2260 case skcms_PixelFormat_RGBA_16161616 >> 1: *ops++ = Op_store_16161616; break;
2261 case skcms_PixelFormat_RGB_hhh >> 1: *ops++ = Op_store_hhh; break;
2262 case skcms_PixelFormat_RGBA_hhhh >> 1: *ops++ = Op_store_hhhh; break;
2263 case skcms_PixelFormat_RGB_fff >> 1: *ops++ = Op_store_fff; break;
2264 case skcms_PixelFormat_RGBA_ffff >> 1: *ops++ = Op_store_ffff; break;
2265 }
2266
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002267 auto run = baseline::run_program;
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002268#if defined(TEST_FOR_HSW)
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com4241bab2018-07-30 13:00:30 +00002269 if (hsw_ok()) { run = hsw::run_program; }
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com5155d382018-07-02 19:08:45 +00002270#endif
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.comaae58022018-07-02 20:24:31 +00002271 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 +00002272 return true;
2273}
2274
2275static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2276#if defined(NDEBUG)
2277 (void)profile;
2278#else
2279 skcms_Matrix3x3 fromXYZD50;
2280 skcms_TransferFunction invR, invG, invB;
2281 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2282#endif
2283}
2284
2285bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2286 skcms_Matrix3x3 fromXYZD50;
2287 if (!profile->has_trc || !profile->has_toXYZD50
2288 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2289 return false;
2290 }
2291
2292 skcms_TransferFunction tf[3];
2293 for (int i = 0; i < 3; i++) {
2294 skcms_TransferFunction inv;
2295 if (profile->trc[i].table_entries == 0
2296 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2297 tf[i] = profile->trc[i].parametric;
2298 continue;
2299 }
2300
2301 float max_error;
2302 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2303 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2304 return false;
2305 }
2306 }
2307
2308 for (int i = 0; i < 3; ++i) {
2309 profile->trc[i].table_entries = 0;
2310 profile->trc[i].parametric = tf[i];
2311 }
2312
2313 assert_usable_as_destination(profile);
2314 return true;
2315}
2316
2317bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2318 // Operate on a copy of profile, so we can choose the best TF for the original curves
2319 skcms_ICCProfile result = *profile;
2320 if (!skcms_MakeUsableAsDestination(&result)) {
2321 return false;
2322 }
2323
2324 int best_tf = 0;
2325 float min_max_error = INFINITY_;
2326 for (int i = 0; i < 3; i++) {
2327 skcms_TransferFunction inv;
2328 skcms_TransferFunction_invert(&result.trc[i].parametric, &inv);
2329
2330 float err = 0;
2331 for (int j = 0; j < 3; ++j) {
skcms-skia-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com2f046f12018-07-02 19:40:45 +00002332 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 +00002333 }
2334 if (min_max_error > err) {
2335 min_max_error = err;
2336 best_tf = i;
2337 }
2338 }
2339
2340 for (int i = 0; i < 3; i++) {
2341 result.trc[i].parametric = result.trc[best_tf].parametric;
2342 }
2343
2344 *profile = result;
2345 assert_usable_as_destination(profile);
2346 return true;
2347}