Bug Summary

File:root/firefox-clang/gfx/skia/skia/modules/skcms/skcms.cc
Warning:line 165, column 41
The left operand of '!=' is a garbage value

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O3 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name skcms.cc -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/gfx/skia -fcoverage-compilation-dir=/root/firefox-clang/obj-x86_64-pc-linux-gnu/gfx/skia -resource-dir /usr/lib/llvm-23/lib/clang/23 -include /root/firefox-clang/config/gcc_hidden.h -include /root/firefox-clang/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -D _GLIBCXX_ASSERTIONS=1 -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D MOZ_SKIA -D SKIA_IMPLEMENTATION=1 -D SK_PDF_USE_HARFBUZZ_SUBSET=1 -D MOZ_HAS_MOZGLUE -D MOZILLA_INTERNAL_API -D IMPL_LIBXUL -D MOZ_SUPPORT_LEAKCHECKING -D STATIC_EXPORTABLE_JS_API -I /root/firefox-clang/gfx/skia -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/gfx/skia -I /root/firefox-clang/gfx/skia/skia -I /root/firefox-clang/gfx/harfbuzz/src -I /root/firefox-clang/gfx/cairo/cairo/src -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /root/firefox-clang/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -I /usr/include/freetype2 -I /usr/include/libpng16 -I /usr/include/freetype2 -I /usr/include/libpng16 -I /usr/include/pango-1.0 -I /usr/include/harfbuzz -I /usr/include/glib-2.0 -I /usr/lib/x86_64-linux-gnu/glib-2.0/include -I /usr/include/libmount -I /usr/include/blkid -I /usr/include/fribidi -I /usr/include/cairo -I /usr/include/freetype2 -I /usr/include/libpng16 -I /usr/include/pixman-1 -I /usr/include/sysprof-6 -D SKCMS_DISABLE_SKX -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/x86_64-linux-gnu/c++/16 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../include/c++/16/backward -internal-isystem /usr/lib/llvm-23/lib/clang/23/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/16/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-error=pessimizing-move -Wno-error=large-by-value-copy=128 -Wno-error=implicit-int-float-conversion -Wno-error=thread-safety-analysis -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -Wno-character-conversion -Wno-deprecated-declarations -Wno-overloaded-virtual -Wno-sign-compare -Wno-unreachable-code -Wno-unused-function -Wno-implicit-fallthrough -Wno-inconsistent-missing-override -Wno-macro-redefined -Wno-unused-private-field -std=gnu++20 -fdeprecated-macro -ferror-limit 19 -fstrict-flex-arrays=1 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-implicit-modules -fskip-odr-check-in-gmf -fno-sized-deallocation -fno-aligned-allocation -fdiagnostics-absolute-paths -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -mllvm -dwarf-linkage-names=Abstract -faddrsig -fdwarf2-cfi-asm -o /tmp/scan-build-2026-09-01-224014-2642839-1 -x c++ /root/firefox-clang/gfx/skia/skia/modules/skcms/skcms.cc
1/*
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
8#include "src/skcms_public.h" // NO_G3_REWRITE
9#include "src/skcms_internals.h" // NO_G3_REWRITE
10#include "src/skcms_Transform.h" // NO_G3_REWRITE
11#include <assert.h>
12#include <float.h>
13#include <limits.h>
14#include <stdlib.h>
15#include <string.h>
16
17#if defined(__ARM_NEON)
18 #include <arm_neon.h>
19#elif defined(__SSE__1)
20 #include <immintrin.h>
21
22 #if defined(__clang__1)
23 // That #include <immintrin.h> is usually enough, but Clang's headers
24 // "helpfully" skip including the whole kitchen sink when _MSC_VER is
25 // defined, because lots of programs on Windows would include that and
26 // it'd be a lot slower. But we want all those headers included so we
27 // can use their features after runtime checks later.
28 #include <smmintrin.h>
29 #include <avxintrin.h>
30 #include <avx2intrin.h>
31 #include <avx512fintrin.h>
32 #include <avx512dqintrin.h>
33 #endif
34#endif
35
36using namespace skcms_private;
37
38static bool sAllowRuntimeCPUDetection = true;
39
40void skcms_DisableRuntimeCPUDetection() {
41 sAllowRuntimeCPUDetection = false;
42}
43
44static float log2f_(float x) {
45 // The first approximation of log2(x) is its exponent 'e', minus 127.
46 int32_t bits;
47 memcpy(&bits, &x, sizeof(bits));
48
49 float e = (float)bits * (1.0f / (1<<23));
50
51 // If we use the mantissa too we can refine the error signficantly.
52 int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
53 float m;
54 memcpy(&m, &m_bits, sizeof(m));
55
56 return (e - 124.225514990f
57 - 1.498030302f*m
58 - 1.725879990f/(0.3520887068f + m));
59}
60static float logf_(float x) {
61 const float ln2 = 0.69314718f;
62 return ln2*log2f_(x);
63}
64
65static float exp2f_(float x) {
66 if (x > 128.0f) {
67 return INFINITY_;
68 } else if (x < -127.0f) {
69 return 0.0f;
70 }
71 float fract = x - floorf_(x);
72
73 float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
74 - 1.490129070f*fract
75 + 27.728023300f/(4.84252568f - fract));
76
77 // Before we cast fbits to int32_t, check for out of range values to pacify UBSAN.
78 // INT_MAX is not exactly representable as a float, so exclude it as effectively infinite.
79 // Negative values are effectively underflow - we'll end up returning a (different) negative
80 // value, which makes no sense. So clamp to zero.
81 if (fbits >= (float)INT_MAX2147483647) {
82 return INFINITY_;
83 } else if (fbits < 0) {
84 return 0;
85 }
86
87 int32_t bits = (int32_t)fbits;
88 memcpy(&x, &bits, sizeof(x));
89 return x;
90}
91
92// Not static, as it's used by some test tools.
93float powf_(float x, float y) {
94 if (x <= 0.f) {
95 return 0.f;
96 }
97 if (x == 1.f) {
98 return 1.f;
99 }
100 return exp2f_(log2f_(x) * y);
101}
102
103static float expf_(float x) {
104 const float log2_e = 1.4426950408889634074f;
105 return exp2f_(log2_e * x);
106}
107
108static float fmaxf_(float x, float y) { return x > y ? x : y; }
109static float fminf_(float x, float y) { return x < y ? x : y; }
110
111static bool isfinitef_(float x) { return 0 == x*0; }
112
113static float minus_1_ulp(float x) {
114 int32_t bits;
115 memcpy(&bits, &x, sizeof(bits));
116 bits = bits - 1;
117 memcpy(&x, &bits, sizeof(bits));
118 return x;
119}
120
121// Most transfer functions we work with are sRGBish.
122// For exotic HDR transfer functions, we encode them using a tf.g that makes no sense,
123// and repurpose the other fields to hold the parameters of the HDR functions.
124struct TF_PQish { float A,B,C,D,E,F; };
125struct TF_HLGish { float R,G,a,b,c,K_minus_1; };
126// We didn't originally support a scale factor K for HLG, and instead just stored 0 in
127// the unused `f` field of skcms_TransferFunction for HLGish and HLGInvish transfer functions.
128// By storing f=K-1, those old unusued f=0 values now mean K=1, a noop scale factor.
129
130static float TFKind_marker(skcms_TFType kind) {
131 // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM.
132 return -(float)kind;
133}
134
135static skcms_TFType classify(const skcms_TransferFunction& tf, TF_PQish* pq = nullptr
136 , TF_HLGish* hlg = nullptr) {
137 if (tf.g < 0) {
27
Assuming field 'g' is < 0
28
Taking true branch
138 // Negative "g" is mapped to enum values; large negative are for sure invalid.
139 if (tf.g < -128) {
29
Assuming the condition is false
30
Taking false branch
140 return skcms_TFType_Invalid;
141 }
142 int enum_g = -static_cast<int>(tf.g);
143 // Non-whole "g" values are invalid as well.
144 if (static_cast<float>(-enum_g) != tf.g) {
31
Assuming the condition is false
32
Taking false branch
145 return skcms_TFType_Invalid;
146 }
147 // TODO: soundness checks for PQ/HLG like we do for sRGBish?
148 switch (enum_g) {
33
Control jumps to 'case skcms_TFType_PQ:' at line 164
149 case skcms_TFType_PQish:
150 if (pq) {
151 memcpy(pq , &tf.a, sizeof(*pq ));
152 }
153 return skcms_TFType_PQish;
154 case skcms_TFType_HLGish:
155 if (hlg) {
156 memcpy(hlg, &tf.a, sizeof(*hlg));
157 }
158 return skcms_TFType_HLGish;
159 case skcms_TFType_HLGinvish:
160 if (hlg) {
161 memcpy(hlg, &tf.a, sizeof(*hlg));
162 }
163 return skcms_TFType_HLGinvish;
164 case skcms_TFType_PQ:
165 if (tf.b != 0.f || tf.c != 0.f || tf.d != 0.f || tf.e != 0.f || tf.f != 0.f) {
34
Assuming the condition is false
35
The left operand of '!=' is a garbage value
166 return skcms_TFType_Invalid;
167 }
168 return skcms_TFType_PQ;
169 case skcms_TFType_HLG:
170 if (tf.d != 0.f || tf.e != 0.f || tf.f != 0.f) {
171 return skcms_TFType_Invalid;
172 }
173 return skcms_TFType_HLG;
174 }
175 return skcms_TFType_Invalid;
176 }
177
178 // Basic soundness checks for sRGBish transfer functions.
179 if (isfinitef_(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g)
180 // a,c,d,g should be non-negative to make any sense.
181 && tf.a >= 0
182 && tf.c >= 0
183 && tf.d >= 0
184 && tf.g >= 0
185 // Raising a negative value to a fractional tf->g produces complex numbers.
186 && tf.a * tf.d + tf.b >= 0) {
187 return skcms_TFType_sRGBish;
188 }
189
190 return skcms_TFType_Invalid;
191}
192
193skcms_TFType skcms_TransferFunction_getType(const skcms_TransferFunction* tf) {
194 return classify(*tf);
195}
196bool skcms_TransferFunction_isSRGBish(const skcms_TransferFunction* tf) {
197 return classify(*tf) == skcms_TFType_sRGBish;
198}
199bool skcms_TransferFunction_isPQish(const skcms_TransferFunction* tf) {
200 return classify(*tf) == skcms_TFType_PQish;
201}
202bool skcms_TransferFunction_isHLGish(const skcms_TransferFunction* tf) {
203 return classify(*tf) == skcms_TFType_HLGish;
204}
205bool skcms_TransferFunction_isPQ(const skcms_TransferFunction* tf) {
206 return classify(*tf) == skcms_TFType_PQ;
207}
208bool skcms_TransferFunction_isHLG(const skcms_TransferFunction* tf) {
209 return classify(*tf) == skcms_TFType_HLG;
210}
211
212bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
213 float A, float B, float C,
214 float D, float E, float F) {
215 *tf = { TFKind_marker(skcms_TFType_PQish), A,B,C,D,E,F };
216 assert(skcms_TransferFunction_isPQish(tf))(static_cast <bool> (skcms_TransferFunction_isPQish(tf)
) ? void (0) : __assert_fail ("skcms_TransferFunction_isPQish(tf)"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
217 return true;
218}
219
220bool skcms_TransferFunction_makeScaledHLGish(skcms_TransferFunction* tf,
221 float K, float R, float G,
222 float a, float b, float c) {
223 *tf = { TFKind_marker(skcms_TFType_HLGish), R,G, a,b,c, K-1.0f };
224 assert(skcms_TransferFunction_isHLGish(tf))(static_cast <bool> (skcms_TransferFunction_isHLGish(tf
)) ? void (0) : __assert_fail ("skcms_TransferFunction_isHLGish(tf)"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
225 return true;
226}
227
228void skcms_TransferFunction_makePQ(
229 skcms_TransferFunction* tf,
230 float hdr_reference_white_luminance) {
231 *tf = { TFKind_marker(skcms_TFType_PQ),
232 hdr_reference_white_luminance,
233 0.f,0.f,0.f,0.f,0.f };
234 assert(skcms_TransferFunction_isPQ(tf))(static_cast <bool> (skcms_TransferFunction_isPQ(tf)) ?
void (0) : __assert_fail ("skcms_TransferFunction_isPQ(tf)",
__builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
235}
236
237void skcms_TransferFunction_makeHLG(
238 skcms_TransferFunction* tf,
239 float hdr_reference_white_luminance,
240 float peak_luminance,
241 float system_gamma) {
242 *tf = { TFKind_marker(skcms_TFType_HLG),
243 hdr_reference_white_luminance,
244 peak_luminance,
245 system_gamma,
246 0.f, 0.f, 0.f };
247 assert(skcms_TransferFunction_isHLG(tf))(static_cast <bool> (skcms_TransferFunction_isHLG(tf)) ?
void (0) : __assert_fail ("skcms_TransferFunction_isHLG(tf)"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
248}
249
250float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
251 float sign = x < 0 ? -1.0f : 1.0f;
252 x *= sign;
253
254 TF_PQish pq;
255 TF_HLGish hlg;
256 switch (classify(*tf, &pq, &hlg)) {
257 case skcms_TFType_Invalid: break;
258
259 case skcms_TFType_HLG: {
260 const float a = 0.17883277f;
261 const float b = 0.28466892f;
262 const float c = 0.55991073f;
263 return sign * (x <= 0.5f ? x*x/3.f : (expf_((x-c)/a) + b) / 12.f);
264 }
265
266 case skcms_TFType_HLGish: {
267 const float K = hlg.K_minus_1 + 1.0f;
268 return K * sign * (x*hlg.R <= 1 ? powf_(x*hlg.R, hlg.G)
269 : expf_((x-hlg.c)*hlg.a) + hlg.b);
270 }
271
272 // skcms_TransferFunction_invert() inverts R, G, and a for HLGinvish so this math is fast.
273 case skcms_TFType_HLGinvish: {
274 const float K = hlg.K_minus_1 + 1.0f;
275 x /= K;
276 return sign * (x <= 1 ? hlg.R * powf_(x, hlg.G)
277 : hlg.a * logf_(x - hlg.b) + hlg.c);
278 }
279
280 case skcms_TFType_sRGBish:
281 return sign * (x < tf->d ? tf->c * x + tf->f
282 : powf_(tf->a * x + tf->b, tf->g) + tf->e);
283
284 case skcms_TFType_PQ: {
285 const float c1 = 107 / 128.f;
286 const float c2 = 2413 / 128.f;
287 const float c3 = 2392 / 128.f;
288 const float m1 = 1305 / 8192.f;
289 const float m2 = 2523 / 32.f;
290 const float p = powf_(x, 1.f / m2);
291 return powf_((p - c1) / (c2 - c3 * p), 1.f / m1);
292 }
293
294 case skcms_TFType_PQish:
295 return sign *
296 powf_((pq.A + pq.B * powf_(x, pq.C)) / (pq.D + pq.E * powf_(x, pq.C)), pq.F);
297 }
298 return 0;
299}
300
301
302static float eval_curve(const skcms_Curve* curve, float x) {
303 if (curve->table_entries == 0) {
304 return skcms_TransferFunction_eval(&curve->parametric, x);
305 }
306
307 float ix = fmaxf_(0, fminf_(x, 1)) * static_cast<float>(curve->table_entries - 1);
308 int lo = (int) ix ,
309 hi = (int)(float)minus_1_ulp(ix + 1.0f);
310 float t = ix - (float)lo;
311
312 float l, h;
313 if (curve->table_8) {
314 l = curve->table_8[lo] * (1/255.0f);
315 h = curve->table_8[hi] * (1/255.0f);
316 } else {
317 uint16_t be_l, be_h;
318 memcpy(&be_l, curve->table_16 + 2*lo, 2);
319 memcpy(&be_h, curve->table_16 + 2*hi, 2);
320 uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
321 uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
322 l = le_l * (1/65535.0f);
323 h = le_h * (1/65535.0f);
324 }
325 return l + (h-l)*t;
326}
327
328float skcms_MaxRoundtripError(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
329 uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
330 const float dx = 1.0f / static_cast<float>(N - 1);
331 float err = 0;
332 for (uint32_t i = 0; i < N; i++) {
333 float x = static_cast<float>(i) * dx,
334 y = eval_curve(curve, x);
335 err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
336 }
337 return err;
338}
339
340bool skcms_AreApproximateInverses(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
341 return skcms_MaxRoundtripError(curve, inv_tf) < (1/512.0f);
342}
343
344// Additional ICC signature values that are only used internally
345enum {
346 // File signature
347 skcms_Signature_acsp = 0x61637370,
348
349 // Tag signatures
350 skcms_Signature_rTRC = 0x72545243,
351 skcms_Signature_gTRC = 0x67545243,
352 skcms_Signature_bTRC = 0x62545243,
353 skcms_Signature_kTRC = 0x6B545243,
354
355 skcms_Signature_rXYZ = 0x7258595A,
356 skcms_Signature_gXYZ = 0x6758595A,
357 skcms_Signature_bXYZ = 0x6258595A,
358
359 skcms_Signature_A2B0 = 0x41324230,
360 skcms_Signature_B2A0 = 0x42324130,
361
362 skcms_Signature_CHAD = 0x63686164,
363 skcms_Signature_WTPT = 0x77747074,
364
365 skcms_Signature_CICP = 0x63696370,
366
367 // Type signatures
368 skcms_Signature_curv = 0x63757276,
369 skcms_Signature_mft1 = 0x6D667431,
370 skcms_Signature_mft2 = 0x6D667432,
371 skcms_Signature_mAB = 0x6D414220,
372 skcms_Signature_mBA = 0x6D424120,
373 skcms_Signature_para = 0x70617261,
374 skcms_Signature_sf32 = 0x73663332,
375 // XYZ is also a PCS signature, so it's defined in skcms.h
376 // skcms_Signature_XYZ = 0x58595A20,
377};
378
379static uint16_t read_big_u16(const uint8_t* ptr) {
380 uint16_t be;
381 memcpy(&be, ptr, sizeof(be));
382#if defined(_MSC_VER)
383 return _byteswap_ushort(be);
384#else
385 return __builtin_bswap16(be);
386#endif
387}
388
389static uint32_t read_big_u32(const uint8_t* ptr) {
390 uint32_t be;
391 memcpy(&be, ptr, sizeof(be));
392#if defined(_MSC_VER)
393 return _byteswap_ulong(be);
394#else
395 return __builtin_bswap32(be);
396#endif
397}
398
399static int32_t read_big_i32(const uint8_t* ptr) {
400 return (int32_t)read_big_u32(ptr);
401}
402
403static float read_big_fixed(const uint8_t* ptr) {
404 return static_cast<float>(read_big_i32(ptr)) * (1.0f / 65536.0f);
405}
406
407// Maps to an in-memory profile so that fields line up to the locations specified
408// in ICC.1:2010, section 7.2
409typedef struct {
410 uint8_t size [ 4];
411 uint8_t cmm_type [ 4];
412 uint8_t version [ 4];
413 uint8_t profile_class [ 4];
414 uint8_t data_color_space [ 4];
415 uint8_t pcs [ 4];
416 uint8_t creation_date_time [12];
417 uint8_t signature [ 4];
418 uint8_t platform [ 4];
419 uint8_t flags [ 4];
420 uint8_t device_manufacturer [ 4];
421 uint8_t device_model [ 4];
422 uint8_t device_attributes [ 8];
423 uint8_t rendering_intent [ 4];
424 uint8_t illuminant_X [ 4];
425 uint8_t illuminant_Y [ 4];
426 uint8_t illuminant_Z [ 4];
427 uint8_t creator [ 4];
428 uint8_t profile_id [16];
429 uint8_t reserved [28];
430 uint8_t tag_count [ 4]; // Technically not part of header, but required
431} header_Layout;
432
433typedef struct {
434 uint8_t signature [4];
435 uint8_t offset [4];
436 uint8_t size [4];
437} tag_Layout;
438
439static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
440 return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout)((uint64_t)sizeof(header_Layout)));
441}
442
443// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
444// use of the type is for the CHAD tag that stores exactly nine values.
445typedef struct {
446 uint8_t type [ 4];
447 uint8_t reserved [ 4];
448 uint8_t values [36];
449} sf32_Layout;
450
451bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
452 skcms_ICCTag tag;
453 if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
454 return false;
455 }
456
457 if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)((uint64_t)sizeof(sf32_Layout))) {
458 return false;
459 }
460
461 const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
462 const uint8_t* values = sf32Tag->values;
463 for (int r = 0; r < 3; ++r)
464 for (int c = 0; c < 3; ++c, values += 4) {
465 m->vals[r][c] = read_big_fixed(values);
466 }
467 return true;
468}
469
470// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
471// the type are for tags/data that store exactly one triple.
472typedef struct {
473 uint8_t type [4];
474 uint8_t reserved [4];
475 uint8_t X [4];
476 uint8_t Y [4];
477 uint8_t Z [4];
478} XYZ_Layout;
479
480static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
481 if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)((uint64_t)sizeof(XYZ_Layout))) {
482 return false;
483 }
484
485 const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
486
487 *x = read_big_fixed(xyzTag->X);
488 *y = read_big_fixed(xyzTag->Y);
489 *z = read_big_fixed(xyzTag->Z);
490 return true;
491}
492
493bool skcms_GetWTPT(const skcms_ICCProfile* profile, float xyz[3]) {
494 skcms_ICCTag tag;
495 return skcms_GetTagBySignature(profile, skcms_Signature_WTPT, &tag) &&
496 read_tag_xyz(&tag, &xyz[0], &xyz[1], &xyz[2]);
497}
498
499static int data_color_space_channel_count(uint32_t data_color_space) {
500 switch (data_color_space) {
501 case skcms_Signature_CMYK: return 4;
502 case skcms_Signature_Gray: return 1;
503 case skcms_Signature_RGB: return 3;
504 case skcms_Signature_Lab: return 3;
505 case skcms_Signature_XYZ: return 3;
506 case skcms_Signature_CIELUV: return 3;
507 case skcms_Signature_YCbCr: return 3;
508 case skcms_Signature_CIEYxy: return 3;
509 case skcms_Signature_HSV: return 3;
510 case skcms_Signature_HLS: return 3;
511 case skcms_Signature_CMY: return 3;
512 case skcms_Signature_2CLR: return 2;
513 case skcms_Signature_3CLR: return 3;
514 case skcms_Signature_4CLR: return 4;
515 case skcms_Signature_5CLR: return 5;
516 case skcms_Signature_6CLR: return 6;
517 case skcms_Signature_7CLR: return 7;
518 case skcms_Signature_8CLR: return 8;
519 case skcms_Signature_9CLR: return 9;
520 case skcms_Signature_10CLR: return 10;
521 case skcms_Signature_11CLR: return 11;
522 case skcms_Signature_12CLR: return 12;
523 case skcms_Signature_13CLR: return 13;
524 case skcms_Signature_14CLR: return 14;
525 case skcms_Signature_15CLR: return 15;
526 default: return -1;
527 }
528}
529
530int skcms_GetInputChannelCount(const skcms_ICCProfile* profile) {
531 int a2b_count = 0;
532 if (profile->has_A2B) {
533 a2b_count = profile->A2B.input_channels != 0
534 ? static_cast<int>(profile->A2B.input_channels)
535 : 3;
536 }
537
538 skcms_ICCTag tag;
539 int trc_count = 0;
540 if (skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &tag)) {
541 trc_count = 1;
542 } else if (profile->has_trc) {
543 trc_count = 3;
544 }
545
546 int dcs_count = data_color_space_channel_count(profile->data_color_space);
547
548 if (dcs_count < 0) {
549 return -1;
550 }
551
552 if (a2b_count > 0 && a2b_count != dcs_count) {
553 return -1;
554 }
555 if (trc_count > 0 && trc_count != dcs_count) {
556 return -1;
557 }
558
559 return dcs_count;
560}
561
562static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
563 const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
564 return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
565 read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
566 read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
567}
568
569typedef struct {
570 uint8_t type [4];
571 uint8_t reserved_a [4];
572 uint8_t function_type [2];
573 uint8_t reserved_b [2];
574 uint8_t variable [1/*variable*/]; // 1, 3, 4, 5, or 7 s15.16, depending on function_type
575} para_Layout;
576
577static bool read_curve_para(const uint8_t* buf, uint32_t size,
578 skcms_Curve* curve, uint32_t* curve_size) {
579 if (size < SAFE_FIXED_SIZE(para_Layout)((uint64_t)__builtin_offsetof(para_Layout, variable))) {
580 return false;
581 }
582
583 const para_Layout* paraTag = (const para_Layout*)buf;
584
585 enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
586 uint16_t function_type = read_big_u16(paraTag->function_type);
587 if (function_type > kGABCDEF) {
588 return false;
589 }
590
591 static const uint32_t curve_bytes[] = { 4, 12, 16, 20, 28 };
592 if (size < SAFE_FIXED_SIZE(para_Layout)((uint64_t)__builtin_offsetof(para_Layout, variable)) + curve_bytes[function_type]) {
593 return false;
594 }
595
596 if (curve_size) {
597 *curve_size = SAFE_FIXED_SIZE(para_Layout)((uint64_t)__builtin_offsetof(para_Layout, variable)) + curve_bytes[function_type];
598 }
599
600 curve->table_entries = 0;
601 curve->parametric.a = 1.0f;
602 curve->parametric.b = 0.0f;
603 curve->parametric.c = 0.0f;
604 curve->parametric.d = 0.0f;
605 curve->parametric.e = 0.0f;
606 curve->parametric.f = 0.0f;
607 curve->parametric.g = read_big_fixed(paraTag->variable);
608
609 switch (function_type) {
610 case kGAB:
611 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
612 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
613 if (curve->parametric.a == 0) {
614 return false;
615 }
616 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
617 break;
618 case kGABC:
619 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
620 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
621 curve->parametric.e = read_big_fixed(paraTag->variable + 12);
622 if (curve->parametric.a == 0) {
623 return false;
624 }
625 curve->parametric.d = -curve->parametric.b / curve->parametric.a;
626 curve->parametric.f = curve->parametric.e;
627 break;
628 case kGABCD:
629 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
630 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
631 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
632 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
633 break;
634 case kGABCDEF:
635 curve->parametric.a = read_big_fixed(paraTag->variable + 4);
636 curve->parametric.b = read_big_fixed(paraTag->variable + 8);
637 curve->parametric.c = read_big_fixed(paraTag->variable + 12);
638 curve->parametric.d = read_big_fixed(paraTag->variable + 16);
639 curve->parametric.e = read_big_fixed(paraTag->variable + 20);
640 curve->parametric.f = read_big_fixed(paraTag->variable + 24);
641 break;
642 }
643 return skcms_TransferFunction_isSRGBish(&curve->parametric);
644}
645
646typedef struct {
647 uint8_t type [4];
648 uint8_t reserved [4];
649 uint8_t value_count [4];
650 uint8_t variable [1/*variable*/]; // value_count, 8.8 if 1, uint16 (n*65535) if > 1
651} curv_Layout;
652
653// See https://crbug.com/492744328 for how this was determined.
654static const uint32_t kMaxTableEntries = 1 << 24; // 16,777,216
655
656static bool read_curve_curv(const uint8_t* buf, uint32_t size,
657 skcms_Curve* curve, uint32_t* curve_size) {
658 if (size < SAFE_FIXED_SIZE(curv_Layout)((uint64_t)__builtin_offsetof(curv_Layout, variable))) {
659 return false;
660 }
661
662 const curv_Layout* curvTag = (const curv_Layout*)buf;
663
664 uint32_t value_count = read_big_u32(curvTag->value_count);
665 if (size < SAFE_FIXED_SIZE(curv_Layout)((uint64_t)__builtin_offsetof(curv_Layout, variable)) + value_count * SAFE_SIZEOF(uint16_t)((uint64_t)sizeof(uint16_t))) {
666 return false;
667 }
668
669 if (curve_size) {
670 *curve_size = SAFE_FIXED_SIZE(curv_Layout)((uint64_t)__builtin_offsetof(curv_Layout, variable)) + value_count * SAFE_SIZEOF(uint16_t)((uint64_t)sizeof(uint16_t));
671 }
672
673 if (value_count < 2) {
674 curve->table_entries = 0;
675 curve->parametric.a = 1.0f;
676 curve->parametric.b = 0.0f;
677 curve->parametric.c = 0.0f;
678 curve->parametric.d = 0.0f;
679 curve->parametric.e = 0.0f;
680 curve->parametric.f = 0.0f;
681 if (value_count == 0) {
682 // Empty tables are a shorthand for an identity curve
683 curve->parametric.g = 1.0f;
684 } else {
685 // Single entry tables are a shorthand for simple gamma
686 curve->parametric.g = read_big_u16(curvTag->variable) * (1.0f / 256.0f);
687 }
688 return true;
689 }
690 if (value_count > kMaxTableEntries) {
691 return false;
692 }
693 curve->table_8 = nullptr;
694 curve->table_16 = curvTag->variable;
695 curve->table_entries = value_count;
696 return true;
697}
698
699// Parses both curveType and parametricCurveType data. Ensures that at most 'size' bytes are read.
700// If curve_size is not nullptr, writes the number of bytes used by the curve in (*curve_size).
701static bool read_curve(const uint8_t* buf, uint32_t size,
702 skcms_Curve* curve, uint32_t* curve_size) {
703 if (!buf || size < 4 || !curve) {
704 return false;
705 }
706
707 uint32_t type = read_big_u32(buf);
708 if (type == skcms_Signature_para) {
709 return read_curve_para(buf, size, curve, curve_size);
710 } else if (type == skcms_Signature_curv) {
711 return read_curve_curv(buf, size, curve, curve_size);
712 }
713
714 return false;
715}
716
717// mft1 and mft2 share a large chunk of data
718typedef struct {
719 uint8_t type [ 4];
720 uint8_t reserved_a [ 4];
721 uint8_t input_channels [ 1];
722 uint8_t output_channels [ 1];
723 uint8_t grid_points [ 1];
724 uint8_t reserved_b [ 1];
725 uint8_t matrix [36];
726} mft_CommonLayout;
727
728typedef struct {
729 mft_CommonLayout common [1];
730
731 uint8_t variable [1/*variable*/];
732} mft1_Layout;
733
734typedef struct {
735 mft_CommonLayout common [1];
736
737 uint8_t input_table_entries [2];
738 uint8_t output_table_entries [2];
739 uint8_t variable [1/*variable*/];
740} mft2_Layout;
741
742static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
743 // MFT matrices are applied before the first set of curves, but must be identity unless the
744 // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
745 // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
746 // field/flag.
747 a2b->matrix_channels = 0;
748 a2b-> input_channels = mftTag-> input_channels[0];
749 a2b->output_channels = mftTag->output_channels[0];
750
751 // We require exactly three (ie XYZ/Lab/RGB) output channels
752 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)(int)(sizeof((a2b->output_curves)) / sizeof(*(a2b->output_curves
)))
) {
753 return false;
754 }
755 // We require at least one, and no more than four (ie CMYK) input channels
756 if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)(int)(sizeof((a2b->input_curves)) / sizeof(*(a2b->input_curves
)))
) {
757 return false;
758 }
759
760 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
761 a2b->grid_points[i] = mftTag->grid_points[0];
762 }
763 // The grid only makes sense with at least two points along each axis
764 if (a2b->grid_points[0] < 2) {
765 return false;
766 }
767 return true;
768}
769
770// All as the A2B version above, except where noted.
771static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_B2A* b2a) {
772 // Same as A2B.
773 b2a->matrix_channels = 0;
774 b2a-> input_channels = mftTag-> input_channels[0];
775 b2a->output_channels = mftTag->output_channels[0];
776
777
778 // For B2A, exactly 3 input channels (XYZ) and 3 (RGB) or 4 (CMYK) output channels.
779 if (b2a->input_channels != ARRAY_COUNT(b2a->input_curves)(int)(sizeof((b2a->input_curves)) / sizeof(*(b2a->input_curves
)))
) {
780 return false;
781 }
782 if (b2a->output_channels < 3 || b2a->output_channels > ARRAY_COUNT(b2a->output_curves)(int)(sizeof((b2a->output_curves)) / sizeof(*(b2a->output_curves
)))
) {
783 return false;
784 }
785
786 // Same as A2B.
787 for (uint32_t i = 0; i < b2a->input_channels; ++i) {
788 b2a->grid_points[i] = mftTag->grid_points[0];
789 }
790 if (b2a->grid_points[0] < 2) {
791 return false;
792 }
793 return true;
794}
795
796template <typename A2B_or_B2A>
797static bool init_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
798 uint32_t input_table_entries, uint32_t output_table_entries,
799 A2B_or_B2A* out) {
800 // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
801 uint32_t byte_len_per_input_table = input_table_entries * byte_width;
802 uint32_t byte_len_per_output_table = output_table_entries * byte_width;
803
804 // [input|output]_channels are <= 4, so still no overflow
805 uint32_t byte_len_all_input_tables = out->input_channels * byte_len_per_input_table;
806 uint32_t byte_len_all_output_tables = out->output_channels * byte_len_per_output_table;
807
808 uint64_t grid_size = out->output_channels * byte_width;
809 for (uint32_t axis = 0; axis < out->input_channels; ++axis) {
810 grid_size *= out->grid_points[axis];
811 }
812
813 if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
814 return false;
815 }
816
817 for (uint32_t i = 0; i < out->input_channels; ++i) {
818 out->input_curves[i].table_entries = input_table_entries;
819 if (byte_width == 1) {
820 out->input_curves[i].table_8 = table_base + i * byte_len_per_input_table;
821 out->input_curves[i].table_16 = nullptr;
822 } else {
823 out->input_curves[i].table_8 = nullptr;
824 out->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
825 }
826 }
827
828 if (byte_width == 1) {
829 out->grid_8 = table_base + byte_len_all_input_tables;
830 out->grid_16 = nullptr;
831 } else {
832 out->grid_8 = nullptr;
833 out->grid_16 = table_base + byte_len_all_input_tables;
834 }
835
836 const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
837 for (uint32_t i = 0; i < out->output_channels; ++i) {
838 out->output_curves[i].table_entries = output_table_entries;
839 if (byte_width == 1) {
840 out->output_curves[i].table_8 = output_table_base + i * byte_len_per_output_table;
841 out->output_curves[i].table_16 = nullptr;
842 } else {
843 out->output_curves[i].table_8 = nullptr;
844 out->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
845 }
846 }
847
848 return true;
849}
850
851template <typename A2B_or_B2A>
852static bool read_tag_mft1(const skcms_ICCTag* tag, A2B_or_B2A* out) {
853 if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)((uint64_t)__builtin_offsetof(mft1_Layout, variable))) {
854 return false;
855 }
856
857 const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
858 if (!read_mft_common(mftTag->common, out)) {
859 return false;
860 }
861
862 uint32_t input_table_entries = 256;
863 uint32_t output_table_entries = 256;
864
865 return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout)((uint64_t)__builtin_offsetof(mft1_Layout, variable)), 1,
866 input_table_entries, output_table_entries, out);
867}
868
869template <typename A2B_or_B2A>
870static bool read_tag_mft2(const skcms_ICCTag* tag, A2B_or_B2A* out) {
871 if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)((uint64_t)__builtin_offsetof(mft2_Layout, variable))) {
872 return false;
873 }
874
875 const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
876 if (!read_mft_common(mftTag->common, out)) {
877 return false;
878 }
879
880 uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
881 uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
882
883 // ICC spec mandates that 2 <= table_entries <= 4096
884 if (input_table_entries < 2 || input_table_entries > 4096 ||
885 output_table_entries < 2 || output_table_entries > 4096) {
886 return false;
887 }
888
889 return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout)((uint64_t)__builtin_offsetof(mft2_Layout, variable)), 2,
890 input_table_entries, output_table_entries, out);
891}
892
893static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
894 uint32_t num_curves, skcms_Curve* curves) {
895 for (uint32_t i = 0; i < num_curves; ++i) {
896 if (curve_offset > size) {
897 return false;
898 }
899
900 uint32_t curve_bytes;
901 if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
902 return false;
903 }
904
905 if (curve_bytes > UINT32_MAX(4294967295U) - 3) {
906 return false;
907 }
908 curve_bytes = (curve_bytes + 3) & ~3U;
909
910 uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
911 curve_offset = (uint32_t)new_offset_64;
912 if (new_offset_64 != curve_offset) {
913 return false;
914 }
915 }
916
917 return true;
918}
919
920// mAB and mBA tags use the same encoding, including color lookup tables.
921typedef struct {
922 uint8_t type [ 4];
923 uint8_t reserved_a [ 4];
924 uint8_t input_channels [ 1];
925 uint8_t output_channels [ 1];
926 uint8_t reserved_b [ 2];
927 uint8_t b_curve_offset [ 4];
928 uint8_t matrix_offset [ 4];
929 uint8_t m_curve_offset [ 4];
930 uint8_t clut_offset [ 4];
931 uint8_t a_curve_offset [ 4];
932} mAB_or_mBA_Layout;
933
934typedef struct {
935 uint8_t grid_points [16];
936 uint8_t grid_byte_width [ 1];
937 uint8_t reserved [ 3];
938 uint8_t variable [1/*variable*/];
939} CLUT_Layout;
940
941static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz,
942 const uint8_t* endOfBuffer) {
943 if (tag->size < SAFE_SIZEOF(mAB_or_mBA_Layout)((uint64_t)sizeof(mAB_or_mBA_Layout))) {
944 return false;
945 }
946
947 const mAB_or_mBA_Layout* mABTag = (const mAB_or_mBA_Layout*)tag->buf;
948
949 a2b->input_channels = mABTag->input_channels[0];
950 a2b->output_channels = mABTag->output_channels[0];
951
952 // We require exactly three (ie XYZ/Lab/RGB) output channels
953 if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)(int)(sizeof((a2b->output_curves)) / sizeof(*(a2b->output_curves
)))
) {
954 return false;
955 }
956 // We require no more than four (ie CMYK) input channels
957 if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)(int)(sizeof((a2b->input_curves)) / sizeof(*(a2b->input_curves
)))
) {
958 return false;
959 }
960
961 uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
962 uint32_t matrix_offset = read_big_u32(mABTag->matrix_offset);
963 uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
964 uint32_t clut_offset = read_big_u32(mABTag->clut_offset);
965 uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
966
967 // "B" curves must be present
968 if (0 == b_curve_offset) {
969 return false;
970 }
971
972 if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
973 a2b->output_curves)) {
974 return false;
975 }
976
977 // "M" curves and Matrix must be used together
978 if (0 != m_curve_offset) {
979 if (0 == matrix_offset) {
980 return false;
981 }
982 a2b->matrix_channels = a2b->output_channels;
983 if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
984 a2b->matrix_curves)) {
985 return false;
986 }
987
988 // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
989 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)((uint64_t)sizeof(uint32_t))) {
990 return false;
991 }
992 float encoding_factor = pcs_is_xyz ? (65535 / 32768.0f) : 1.0f;
993 const uint8_t* mtx_buf = tag->buf + matrix_offset;
994 a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
995 a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
996 a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
997 a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
998 a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
999 a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
1000 a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
1001 a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
1002 a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
1003 a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
1004 a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
1005 a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
1006 } else {
1007 if (0 != matrix_offset) {
1008 return false;
1009 }
1010 a2b->matrix_channels = 0;
1011 }
1012
1013 // "A" curves and CLUT must be used together
1014 if (0 != a_curve_offset) {
1015 if (0 == clut_offset) {
1016 return false;
1017 }
1018 if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
1019 a2b->input_curves)) {
1020 return false;
1021 }
1022
1023 if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)((uint64_t)__builtin_offsetof(CLUT_Layout, variable))) {
1024 return false;
1025 }
1026 const CLUT_Layout* clut = (const CLUT_Layout*)(tag->buf + clut_offset);
1027
1028 if (clut->grid_byte_width[0] == 1) {
1029 a2b->grid_8 = clut->variable;
1030 a2b->grid_16 = nullptr;
1031 } else if (clut->grid_byte_width[0] == 2) {
1032 a2b->grid_8 = nullptr;
1033 a2b->grid_16 = clut->variable;
1034 } else {
1035 return false;
1036 }
1037
1038 uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0]; // the payload
1039 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
1040 a2b->grid_points[i] = clut->grid_points[i];
1041 // The grid only makes sense with at least two points along each axis
1042 if (a2b->grid_points[i] < 2) {
1043 return false;
1044 }
1045 grid_size *= a2b->grid_points[i];
1046 }
1047 const uint64_t table_size = clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)((uint64_t)__builtin_offsetof(CLUT_Layout, variable)) + grid_size;
1048 if (table_size > tag->size) {
1049 return false;
1050 }
1051
1052 // gather_24 and gather_48 read 1 or 2 extra bytes.
1053 // We must ensure that those extra bytes are within the provided buffer limit.
1054 uint32_t slack = 0;
1055 if (a2b->output_channels == 3) {
1056 slack = clut->grid_byte_width[0] == 1 ? 1 : 2;
1057 }
1058 if (tag->buf + table_size + slack > endOfBuffer) {
1059 return false;
1060 }
1061 } else {
1062 if (0 != clut_offset) {
1063 return false;
1064 }
1065
1066 // If there is no CLUT, the number of input and output channels must match
1067 if (a2b->input_channels != a2b->output_channels) {
1068 return false;
1069 }
1070
1071 // Zero out the number of input channels to signal that we're skipping this stage
1072 a2b->input_channels = 0;
1073 }
1074
1075 return true;
1076}
1077
1078// Exactly the same as read_tag_mab(), except where there are comments.
1079// TODO: refactor the two to eliminate common code?
1080static bool read_tag_mba(const skcms_ICCTag* tag, skcms_B2A* b2a, bool pcs_is_xyz,
1081 const uint8_t* endOfBuffer) {
1082 if (tag->size < SAFE_SIZEOF(mAB_or_mBA_Layout)((uint64_t)sizeof(mAB_or_mBA_Layout))) {
1083 return false;
1084 }
1085
1086 const mAB_or_mBA_Layout* mBATag = (const mAB_or_mBA_Layout*)tag->buf;
1087
1088 b2a->input_channels = mBATag->input_channels[0];
1089 b2a->output_channels = mBATag->output_channels[0];
1090
1091 // Require exactly 3 inputs (XYZ) and 3 (RGB) or 4 (CMYK) outputs.
1092 if (b2a->input_channels != ARRAY_COUNT(b2a->input_curves)(int)(sizeof((b2a->input_curves)) / sizeof(*(b2a->input_curves
)))
) {
1093 return false;
1094 }
1095 if (b2a->output_channels < 3 || b2a->output_channels > ARRAY_COUNT(b2a->output_curves)(int)(sizeof((b2a->output_curves)) / sizeof(*(b2a->output_curves
)))
) {
1096 return false;
1097 }
1098
1099 uint32_t b_curve_offset = read_big_u32(mBATag->b_curve_offset);
1100 uint32_t matrix_offset = read_big_u32(mBATag->matrix_offset);
1101 uint32_t m_curve_offset = read_big_u32(mBATag->m_curve_offset);
1102 uint32_t clut_offset = read_big_u32(mBATag->clut_offset);
1103 uint32_t a_curve_offset = read_big_u32(mBATag->a_curve_offset);
1104
1105 if (0 == b_curve_offset) {
1106 return false;
1107 }
1108
1109 // "B" curves are our inputs, not outputs.
1110 if (!read_curves(tag->buf, tag->size, b_curve_offset, b2a->input_channels,
1111 b2a->input_curves)) {
1112 return false;
1113 }
1114
1115 if (0 != m_curve_offset) {
1116 if (0 == matrix_offset) {
1117 return false;
1118 }
1119 // Matrix channels is tied to input_channels (3), not output_channels.
1120 b2a->matrix_channels = b2a->input_channels;
1121
1122 if (!read_curves(tag->buf, tag->size, m_curve_offset, b2a->matrix_channels,
1123 b2a->matrix_curves)) {
1124 return false;
1125 }
1126
1127 if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)((uint64_t)sizeof(uint32_t))) {
1128 return false;
1129 }
1130 float encoding_factor = pcs_is_xyz ? (32768 / 65535.0f) : 1.0f; // TODO: understand
1131 const uint8_t* mtx_buf = tag->buf + matrix_offset;
1132 b2a->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf + 0);
1133 b2a->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf + 4);
1134 b2a->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf + 8);
1135 b2a->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
1136 b2a->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
1137 b2a->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
1138 b2a->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
1139 b2a->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
1140 b2a->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
1141 b2a->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
1142 b2a->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
1143 b2a->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
1144 } else {
1145 if (0 != matrix_offset) {
1146 return false;
1147 }
1148 b2a->matrix_channels = 0;
1149 }
1150
1151 if (0 != a_curve_offset) {
1152 if (0 == clut_offset) {
1153 return false;
1154 }
1155
1156 // "A" curves are our output, not input.
1157 if (!read_curves(tag->buf, tag->size, a_curve_offset, b2a->output_channels,
1158 b2a->output_curves)) {
1159 return false;
1160 }
1161
1162 if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)((uint64_t)__builtin_offsetof(CLUT_Layout, variable))) {
1163 return false;
1164 }
1165 const CLUT_Layout* clut = (const CLUT_Layout*)(tag->buf + clut_offset);
1166
1167 if (clut->grid_byte_width[0] == 1) {
1168 b2a->grid_8 = clut->variable;
1169 b2a->grid_16 = nullptr;
1170 } else if (clut->grid_byte_width[0] == 2) {
1171 b2a->grid_8 = nullptr;
1172 b2a->grid_16 = clut->variable;
1173 } else {
1174 return false;
1175 }
1176
1177 uint64_t grid_size = b2a->output_channels * clut->grid_byte_width[0];
1178 for (uint32_t i = 0; i < b2a->input_channels; ++i) {
1179 b2a->grid_points[i] = clut->grid_points[i];
1180 if (b2a->grid_points[i] < 2) {
1181 return false;
1182 }
1183 grid_size *= b2a->grid_points[i];
1184 }
1185 if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)((uint64_t)__builtin_offsetof(CLUT_Layout, variable)) + grid_size) {
1186 return false;
1187 }
1188
1189 // gather_24 and gather_48 read 1 or 2 extra bytes.
1190 // We must ensure that those extra bytes are within the provided buffer limit.
1191 uint32_t slack = 0;
1192 if (b2a->output_channels == 3) {
1193 slack = clut->grid_byte_width[0] == 1 ? 1 : 2;
1194 }
1195 if (tag->buf + clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)((uint64_t)__builtin_offsetof(CLUT_Layout, variable)) + grid_size + slack > endOfBuffer) {
1196 return false;
1197 }
1198 } else {
1199 if (0 != clut_offset) {
1200 return false;
1201 }
1202
1203 if (b2a->input_channels != b2a->output_channels) {
1204 return false;
1205 }
1206
1207 // Zero out *output* channels to skip this stage.
1208 b2a->output_channels = 0;
1209 }
1210 return true;
1211}
1212
1213// If you pass f, we'll fit a possibly-non-zero value for *f.
1214// If you pass nullptr, we'll assume you want *f to be treated as zero.
1215static int fit_linear(const skcms_Curve* curve, int N, float tol,
1216 float* c, float* d, float* f = nullptr) {
1217 assert(N > 1)(static_cast <bool> (N > 1) ? void (0) : __assert_fail
("N > 1", __builtin_FILE (), __builtin_LINE (), __extension__
__PRETTY_FUNCTION__))
;
16
'?' condition is true
1218 // We iteratively fit the first points to the TF's linear piece.
1219 // We want the cx + f line to pass through the first and last points we fit exactly.
1220 //
1221 // As we walk along the points we find the minimum and maximum slope of the line before the
1222 // error would exceed our tolerance. We stop when the range [slope_min, slope_max] becomes
1223 // emtpy, when we definitely can't add any more points.
1224 //
1225 // Some points' error intervals may intersect the running interval but not lie fully
1226 // within it. So we keep track of the last point we saw that is a valid end point candidate,
1227 // and once the search is done, back up to build the line through *that* point.
1228 const float dx = 1.0f / static_cast<float>(N - 1);
1229
1230 int lin_points = 1;
1231
1232 float f_zero = 0.0f;
1233 if (f
16.1
'f' is null
) {
17
Taking false branch
1234 *f = eval_curve(curve, 0);
1235 } else {
1236 f = &f_zero;
1237 }
1238
1239
1240 float slope_min = -INFINITY_;
1241 float slope_max = +INFINITY_;
1242 for (int i = 1; i
17.1
'i' is < 'N'
< N; ++i) {
18
Loop condition is true. Entering loop body
1243 float x = static_cast<float>(i) * dx;
1244 float y = eval_curve(curve, x);
1245
1246 float slope_max_i = (y + tol - *f) / x,
1247 slope_min_i = (y - tol - *f) / x;
1248 if (slope_max_i < slope_min || slope_max < slope_min_i) {
19
Assuming 'slope_max_i' is < 'slope_min'
1249 // Slope intervals would no longer overlap.
1250 break;
20
Execution continues on line 1263
1251 }
1252 slope_max = fminf_(slope_max, slope_max_i);
1253 slope_min = fmaxf_(slope_min, slope_min_i);
1254
1255 float cur_slope = (y - *f) / x;
1256 if (slope_min <= cur_slope && cur_slope <= slope_max) {
1257 lin_points = i + 1;
1258 *c = cur_slope;
1259 }
1260 }
1261
1262 // Set D to the last point that met our tolerance.
1263 *d = static_cast<float>(lin_points - 1) * dx;
1264 return lin_points;
21
Returning without writing to '*c'
1265}
1266
1267// If this skcms_Curve holds an identity table, rewrite it as an identity skcms_TransferFunction.
1268static void canonicalize_identity(skcms_Curve* curve) {
1269 if (curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX2147483647) {
1270 int N = (int)curve->table_entries;
1271
1272 float c = 0.0f, d = 0.0f, f = 0.0f;
1273 if (N == fit_linear(curve, N, 1.0f/static_cast<float>(2*N), &c,&d,&f)
1274 && c == 1.0f
1275 && f == 0.0f) {
1276 curve->table_entries = 0;
1277 curve->table_8 = nullptr;
1278 curve->table_16 = nullptr;
1279 curve->parametric = skcms_TransferFunction{1,1,0,0,0,0,0};
1280 }
1281 }
1282}
1283
1284static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz, const uint8_t* eob) {
1285 bool ok = false;
1286 if (tag->type == skcms_Signature_mft1) { ok = read_tag_mft1(tag, a2b); }
1287 if (tag->type == skcms_Signature_mft2) { ok = read_tag_mft2(tag, a2b); }
1288 if (tag->type == skcms_Signature_mAB ) { ok = read_tag_mab(tag, a2b, pcs_is_xyz, eob); }
1289 if (!ok) {
1290 return false;
1291 }
1292
1293 if (a2b->input_channels > 0) { canonicalize_identity(a2b->input_curves + 0); }
1294 if (a2b->input_channels > 1) { canonicalize_identity(a2b->input_curves + 1); }
1295 if (a2b->input_channels > 2) { canonicalize_identity(a2b->input_curves + 2); }
1296 if (a2b->input_channels > 3) { canonicalize_identity(a2b->input_curves + 3); }
1297
1298 if (a2b->matrix_channels > 0) { canonicalize_identity(a2b->matrix_curves + 0); }
1299 if (a2b->matrix_channels > 1) { canonicalize_identity(a2b->matrix_curves + 1); }
1300 if (a2b->matrix_channels > 2) { canonicalize_identity(a2b->matrix_curves + 2); }
1301
1302 if (a2b->output_channels > 0) { canonicalize_identity(a2b->output_curves + 0); }
1303 if (a2b->output_channels > 1) { canonicalize_identity(a2b->output_curves + 1); }
1304 if (a2b->output_channels > 2) { canonicalize_identity(a2b->output_curves + 2); }
1305
1306 return true;
1307}
1308
1309static bool read_b2a(const skcms_ICCTag* tag, skcms_B2A* b2a, bool pcs_is_xyz, const uint8_t* eob) {
1310 bool ok = false;
1311 if (tag->type == skcms_Signature_mft1) { ok = read_tag_mft1(tag, b2a); }
1312 if (tag->type == skcms_Signature_mft2) { ok = read_tag_mft2(tag, b2a); }
1313 if (tag->type == skcms_Signature_mBA ) { ok = read_tag_mba(tag, b2a, pcs_is_xyz, eob); }
1314 if (!ok) {
1315 return false;
1316 }
1317
1318 if (b2a->input_channels > 0) { canonicalize_identity(b2a->input_curves + 0); }
1319 if (b2a->input_channels > 1) { canonicalize_identity(b2a->input_curves + 1); }
1320 if (b2a->input_channels > 2) { canonicalize_identity(b2a->input_curves + 2); }
1321
1322 if (b2a->matrix_channels > 0) { canonicalize_identity(b2a->matrix_curves + 0); }
1323 if (b2a->matrix_channels > 1) { canonicalize_identity(b2a->matrix_curves + 1); }
1324 if (b2a->matrix_channels > 2) { canonicalize_identity(b2a->matrix_curves + 2); }
1325
1326 if (b2a->output_channels > 0) { canonicalize_identity(b2a->output_curves + 0); }
1327 if (b2a->output_channels > 1) { canonicalize_identity(b2a->output_curves + 1); }
1328 if (b2a->output_channels > 2) { canonicalize_identity(b2a->output_curves + 2); }
1329 if (b2a->output_channels > 3) { canonicalize_identity(b2a->output_curves + 3); }
1330
1331 return true;
1332}
1333
1334typedef struct {
1335 uint8_t type [4];
1336 uint8_t reserved [4];
1337 uint8_t color_primaries [1];
1338 uint8_t transfer_characteristics [1];
1339 uint8_t matrix_coefficients [1];
1340 uint8_t video_full_range_flag [1];
1341} CICP_Layout;
1342
1343static bool read_cicp(const skcms_ICCTag* tag, skcms_CICP* cicp) {
1344 if (tag->type != skcms_Signature_CICP || tag->size < SAFE_SIZEOF(CICP_Layout)((uint64_t)sizeof(CICP_Layout))) {
1345 return false;
1346 }
1347
1348 const CICP_Layout* cicpTag = (const CICP_Layout*)tag->buf;
1349
1350 cicp->color_primaries = cicpTag->color_primaries[0];
1351 cicp->transfer_characteristics = cicpTag->transfer_characteristics[0];
1352 cicp->matrix_coefficients = cicpTag->matrix_coefficients[0];
1353 cicp->video_full_range_flag = cicpTag->video_full_range_flag[0];
1354 return true;
1355}
1356
1357void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
1358 if (!profile || !profile->buffer || !tag) { return; }
1359 if (idx > profile->tag_count) { return; }
1360 const tag_Layout* tags = get_tag_table(profile);
1361 tag->signature = read_big_u32(tags[idx].signature);
1362 tag->size = read_big_u32(tags[idx].size);
1363 tag->buf = read_big_u32(tags[idx].offset) + profile->buffer;
1364 tag->type = read_big_u32(tag->buf);
1365}
1366
1367bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
1368 if (!profile || !profile->buffer || !tag) { return false; }
1369 const tag_Layout* tags = get_tag_table(profile);
1370 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1371 if (read_big_u32(tags[i].signature) == sig) {
1372 tag->signature = sig;
1373 tag->size = read_big_u32(tags[i].size);
1374 tag->buf = read_big_u32(tags[i].offset) + profile->buffer;
1375 tag->type = read_big_u32(tag->buf);
1376 return true;
1377 }
1378 }
1379 return false;
1380}
1381
1382static bool usable_as_src(const skcms_ICCProfile* profile) {
1383 return profile->has_A2B
1384 || (profile->has_trc && profile->has_toXYZD50);
1385}
1386
1387bool skcms_ParseWithA2BPriority(const void* buf, size_t len,
1388 const int priority[], const int priorities,
1389 skcms_ICCProfile* profile) {
1390 static_assert(SAFE_SIZEOF(header_Layout)((uint64_t)sizeof(header_Layout)) == 132, "need to update header code");
1391
1392 if (!profile) {
1393 return false;
1394 }
1395 memset(profile, 0, SAFE_SIZEOF(*profile)((uint64_t)sizeof(*profile)));
1396
1397 if (len < SAFE_SIZEOF(header_Layout)((uint64_t)sizeof(header_Layout))) {
1398 return false;
1399 }
1400
1401 // Byte-swap all header fields
1402 const header_Layout* header = (const header_Layout*)buf;
1403 profile->buffer = (const uint8_t*)buf;
1404 profile->size = read_big_u32(header->size);
1405 uint32_t version = read_big_u32(header->version);
1406 profile->data_color_space = read_big_u32(header->data_color_space);
1407 profile->pcs = read_big_u32(header->pcs);
1408 uint32_t signature = read_big_u32(header->signature);
1409 float illuminant_X = read_big_fixed(header->illuminant_X);
1410 float illuminant_Y = read_big_fixed(header->illuminant_Y);
1411 float illuminant_Z = read_big_fixed(header->illuminant_Z);
1412 profile->tag_count = read_big_u32(header->tag_count);
1413
1414 // Validate signature, size (smaller than buffer, large enough to hold tag table),
1415 // and major version
1416 uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout)((uint64_t)sizeof(tag_Layout));
1417 if (signature != skcms_Signature_acsp ||
1418 profile->size > len ||
1419 profile->size < SAFE_SIZEOF(header_Layout)((uint64_t)sizeof(header_Layout)) + tag_table_size ||
1420 (version >> 24) > 4) {
1421 return false;
1422 }
1423
1424 // Validate that illuminant is D50 white
1425 if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1426 fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1427 fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1428 return false;
1429 }
1430
1431 // Validate that all tag entries have sane offset + size
1432 const tag_Layout* tags = get_tag_table(profile);
1433 for (uint32_t i = 0; i < profile->tag_count; ++i) {
1434 uint32_t tag_offset = read_big_u32(tags[i].offset);
1435 uint32_t tag_size = read_big_u32(tags[i].size);
1436 uint64_t tag_end = (uint64_t)tag_offset + (uint64_t)tag_size;
1437 if (tag_size < 4 || tag_end > profile->size) {
1438 return false;
1439 }
1440 }
1441
1442 if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1443 return false;
1444 }
1445
1446 bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1447
1448 // Pre-parse commonly used tags.
1449 skcms_ICCTag kTRC;
1450 if (profile->data_color_space == skcms_Signature_Gray &&
1451 skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
1452 if (!read_curve(kTRC.buf, kTRC.size, &profile->trc[0], nullptr)) {
1453 // Malformed tag
1454 return false;
1455 }
1456 profile->trc[1] = profile->trc[0];
1457 profile->trc[2] = profile->trc[0];
1458 profile->has_trc = true;
1459
1460 if (pcs_is_xyz) {
1461 profile->toXYZD50.vals[0][0] = illuminant_X;
1462 profile->toXYZD50.vals[1][1] = illuminant_Y;
1463 profile->toXYZD50.vals[2][2] = illuminant_Z;
1464 profile->has_toXYZD50 = true;
1465 }
1466 } else {
1467 skcms_ICCTag rTRC, gTRC, bTRC;
1468 if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1469 skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1470 skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
1471 if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1472 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1473 !read_curve(bTRC.buf, bTRC.size, &profile->trc[2], nullptr)) {
1474 // Malformed TRC tags
1475 return false;
1476 }
1477 profile->has_trc = true;
1478 }
1479
1480 skcms_ICCTag rXYZ, gXYZ, bXYZ;
1481 if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1482 skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1483 skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1484 if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1485 // Malformed XYZ tags
1486 return false;
1487 }
1488 profile->has_toXYZD50 = true;
1489 }
1490 }
1491
1492 const uint8_t* endOfBuffer = (const uint8_t*)buf + len;
1493 for (int i = 0; i < priorities; i++) {
1494 // enum { perceptual, relative_colormetric, saturation }
1495 if (priority[i] < 0 || priority[i] > 2) {
1496 return false;
1497 }
1498 uint32_t sig = skcms_Signature_A2B0 + static_cast<uint32_t>(priority[i]);
1499 skcms_ICCTag tag;
1500 if (skcms_GetTagBySignature(profile, sig, &tag)) {
1501 if (!read_a2b(&tag, &profile->A2B, pcs_is_xyz, endOfBuffer)) {
1502 // Malformed A2B tag
1503 return false;
1504 }
1505 profile->has_A2B = true;
1506 break;
1507 }
1508 }
1509
1510 for (int i = 0; i < priorities; i++) {
1511 // enum { perceptual, relative_colormetric, saturation }
1512 if (priority[i] < 0 || priority[i] > 2) {
1513 return false;
1514 }
1515 uint32_t sig = skcms_Signature_B2A0 + static_cast<uint32_t>(priority[i]);
1516 skcms_ICCTag tag;
1517 if (skcms_GetTagBySignature(profile, sig, &tag)) {
1518 if (!read_b2a(&tag, &profile->B2A, pcs_is_xyz, endOfBuffer)) {
1519 // Malformed B2A tag
1520 return false;
1521 }
1522 profile->has_B2A = true;
1523 break;
1524 }
1525 }
1526
1527 skcms_ICCTag cicp_tag;
1528 if (skcms_GetTagBySignature(profile, skcms_Signature_CICP, &cicp_tag)) {
1529 if (!read_cicp(&cicp_tag, &profile->CICP)) {
1530 // Malformed CICP tag
1531 return false;
1532 }
1533 profile->has_CICP = true;
1534 }
1535
1536 return usable_as_src(profile);
1537}
1538
1539
1540const skcms_ICCProfile* skcms_sRGB_profile() {
1541 static const skcms_ICCProfile sRGB_profile = {
1542 nullptr, // buffer, moot here
1543
1544 0, // size, moot here
1545 skcms_Signature_RGB, // data_color_space
1546 skcms_Signature_XYZ, // pcs
1547 0, // tag count, moot here
1548
1549 // We choose to represent sRGB with its canonical transfer function,
1550 // and with its canonical XYZD50 gamut matrix.
1551 { // the 3 trc curves
1552 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1553 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1554 {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1555 },
1556
1557 {{ // 3x3 toXYZD50 matrix
1558 { 0.436065674f, 0.385147095f, 0.143066406f },
1559 { 0.222488403f, 0.716873169f, 0.060607910f },
1560 { 0.013916016f, 0.097076416f, 0.714096069f },
1561 }},
1562
1563 { // an empty A2B
1564 { // input_curves
1565 {{0, {0,0, 0,0,0,0,0}}},
1566 {{0, {0,0, 0,0,0,0,0}}},
1567 {{0, {0,0, 0,0,0,0,0}}},
1568 {{0, {0,0, 0,0,0,0,0}}},
1569 },
1570 nullptr, // grid_8
1571 nullptr, // grid_16
1572 0, // input_channels
1573 {0,0,0,0}, // grid_points
1574
1575 { // matrix_curves
1576 {{0, {0,0, 0,0,0,0,0}}},
1577 {{0, {0,0, 0,0,0,0,0}}},
1578 {{0, {0,0, 0,0,0,0,0}}},
1579 },
1580 {{ // matrix (3x4)
1581 { 0,0,0,0 },
1582 { 0,0,0,0 },
1583 { 0,0,0,0 },
1584 }},
1585 0, // matrix_channels
1586
1587 0, // output_channels
1588 { // output_curves
1589 {{0, {0,0, 0,0,0,0,0}}},
1590 {{0, {0,0, 0,0,0,0,0}}},
1591 {{0, {0,0, 0,0,0,0,0}}},
1592 },
1593 },
1594
1595 { // an empty B2A
1596 { // input_curves
1597 {{0, {0,0, 0,0,0,0,0}}},
1598 {{0, {0,0, 0,0,0,0,0}}},
1599 {{0, {0,0, 0,0,0,0,0}}},
1600 },
1601 0, // input_channels
1602
1603 0, // matrix_channels
1604 { // matrix_curves
1605 {{0, {0,0, 0,0,0,0,0}}},
1606 {{0, {0,0, 0,0,0,0,0}}},
1607 {{0, {0,0, 0,0,0,0,0}}},
1608 },
1609 {{ // matrix (3x4)
1610 { 0,0,0,0 },
1611 { 0,0,0,0 },
1612 { 0,0,0,0 },
1613 }},
1614
1615 { // output_curves
1616 {{0, {0,0, 0,0,0,0,0}}},
1617 {{0, {0,0, 0,0,0,0,0}}},
1618 {{0, {0,0, 0,0,0,0,0}}},
1619 {{0, {0,0, 0,0,0,0,0}}},
1620 },
1621 nullptr, // grid_8
1622 nullptr, // grid_16
1623 {0,0,0,0}, // grid_points
1624 0, // output_channels
1625 },
1626
1627 { 0, 0, 0, 0 }, // an empty CICP
1628
1629 true, // has_trc
1630 true, // has_toXYZD50
1631 false, // has_A2B
1632 false, // has B2A
1633 false, // has_CICP
1634 };
1635 return &sRGB_profile;
1636}
1637
1638const skcms_ICCProfile* skcms_XYZD50_profile() {
1639 // Just like sRGB above, but with identity transfer functions and toXYZD50 matrix.
1640 static const skcms_ICCProfile XYZD50_profile = {
1641 nullptr, // buffer, moot here
1642
1643 0, // size, moot here
1644 skcms_Signature_RGB, // data_color_space
1645 skcms_Signature_XYZ, // pcs
1646 0, // tag count, moot here
1647
1648 { // the 3 trc curves
1649 {{0, {1,1, 0,0,0,0,0}}},
1650 {{0, {1,1, 0,0,0,0,0}}},
1651 {{0, {1,1, 0,0,0,0,0}}},
1652 },
1653
1654 {{ // 3x3 toXYZD50 matrix
1655 { 1,0,0 },
1656 { 0,1,0 },
1657 { 0,0,1 },
1658 }},
1659
1660 { // an empty A2B
1661 { // input_curves
1662 {{0, {0,0, 0,0,0,0,0}}},
1663 {{0, {0,0, 0,0,0,0,0}}},
1664 {{0, {0,0, 0,0,0,0,0}}},
1665 {{0, {0,0, 0,0,0,0,0}}},
1666 },
1667 nullptr, // grid_8
1668 nullptr, // grid_16
1669 0, // input_channels
1670 {0,0,0,0}, // grid_points
1671
1672 { // matrix_curves
1673 {{0, {0,0, 0,0,0,0,0}}},
1674 {{0, {0,0, 0,0,0,0,0}}},
1675 {{0, {0,0, 0,0,0,0,0}}},
1676 },
1677 {{ // matrix (3x4)
1678 { 0,0,0,0 },
1679 { 0,0,0,0 },
1680 { 0,0,0,0 },
1681 }},
1682 0, // matrix_channels
1683
1684 0, // output_channels
1685 { // output_curves
1686 {{0, {0,0, 0,0,0,0,0}}},
1687 {{0, {0,0, 0,0,0,0,0}}},
1688 {{0, {0,0, 0,0,0,0,0}}},
1689 },
1690 },
1691
1692 { // an empty B2A
1693 { // input_curves
1694 {{0, {0,0, 0,0,0,0,0}}},
1695 {{0, {0,0, 0,0,0,0,0}}},
1696 {{0, {0,0, 0,0,0,0,0}}},
1697 },
1698 0, // input_channels
1699
1700 0, // matrix_channels
1701 { // matrix_curves
1702 {{0, {0,0, 0,0,0,0,0}}},
1703 {{0, {0,0, 0,0,0,0,0}}},
1704 {{0, {0,0, 0,0,0,0,0}}},
1705 },
1706 {{ // matrix (3x4)
1707 { 0,0,0,0 },
1708 { 0,0,0,0 },
1709 { 0,0,0,0 },
1710 }},
1711
1712 { // output_curves
1713 {{0, {0,0, 0,0,0,0,0}}},
1714 {{0, {0,0, 0,0,0,0,0}}},
1715 {{0, {0,0, 0,0,0,0,0}}},
1716 {{0, {0,0, 0,0,0,0,0}}},
1717 },
1718 nullptr, // grid_8
1719 nullptr, // grid_16
1720 {0,0,0,0}, // grid_points
1721 0, // output_channels
1722 },
1723
1724 { 0, 0, 0, 0 }, // an empty CICP
1725
1726 true, // has_trc
1727 true, // has_toXYZD50
1728 false, // has_A2B
1729 false, // has B2A
1730 false, // has_CICP
1731 };
1732
1733 return &XYZD50_profile;
1734}
1735
1736const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1737 return &skcms_sRGB_profile()->trc[0].parametric;
1738}
1739
1740const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1741 static const skcms_TransferFunction sRGB_inv =
1742 {0.416666657f, 1.137283325f, -0.0f, 12.920000076f, 0.003130805f, -0.054969788f, -0.0f};
1743 return &sRGB_inv;
1744}
1745
1746const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1747 static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1748 return &identity;
1749}
1750
1751const uint8_t skcms_252_random_bytes[] = {
1752 8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1753 119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1754 154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1755 194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1756 108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1757 70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1758 137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1759 9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1760 129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1761 140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1762 219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1763 123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1764 189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1765 174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1766 2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1767 112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1768};
1769
1770bool skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile* A, const skcms_ICCProfile* B) {
1771 // Test for exactly equal profiles first.
1772 if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1773 return true;
1774 }
1775
1776 // For now this is the essentially the same strategy we use in test_only.c
1777 // for our skcms_Transform() smoke tests:
1778 // 1) transform A to XYZD50
1779 // 2) transform B to XYZD50
1780 // 3) return true if they're similar enough
1781 // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1782
1783 // skcms_252_random_bytes are 252 of a random shuffle of all possible bytes.
1784 // 252 is evenly divisible by 3 and 4. Only 192, 10, 241, and 43 are missing.
1785
1786 // We want to allow otherwise equivalent profiles tagged as grayscale and RGB
1787 // to be treated as equal. But CMYK profiles are a totally different ballgame.
1788 const auto CMYK = skcms_Signature_CMYK;
1789 if ((A->data_color_space == CMYK) != (B->data_color_space == CMYK)) {
1790 return false;
1791 }
1792
1793 // Interpret as RGB_888 if data color space is RGB or GRAY, RGBA_8888 if CMYK.
1794 // TODO: working with RGBA_8888 either way is probably fastest.
1795 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1796 size_t npixels = 84;
1797 if (A->data_color_space == skcms_Signature_CMYK) {
1798 fmt = skcms_PixelFormat_RGBA_8888;
1799 npixels = 63;
1800 }
1801
1802 // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1803 // use pre-canned results and skip that skcms_Transform() call?
1804 uint8_t dstA[252],
1805 dstB[252];
1806 if (!skcms_Transform(
1807 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, A,
1808 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1809 npixels)) {
1810 return false;
1811 }
1812 if (!skcms_Transform(
1813 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, B,
1814 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1815 npixels)) {
1816 return false;
1817 }
1818
1819 // TODO: make sure this final check has reasonable codegen.
1820 for (size_t i = 0; i < 252; i++) {
1821 if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1822 return false;
1823 }
1824 }
1825 return true;
1826}
1827
1828bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1829 const skcms_TransferFunction* inv_tf) {
1830 if (!profile || !profile->has_trc) {
1831 return false;
1832 }
1833
1834 return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1835 skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1836 skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1837}
1838
1839static bool is_zero_to_one(float x) {
1840 return 0 <= x && x <= 1;
1841}
1842
1843typedef struct { float vals[3]; } skcms_Vector3;
1844
1845static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1846 skcms_Vector3 dst = {{0,0,0}};
1847 for (int row = 0; row < 3; ++row) {
1848 dst.vals[row] = m->vals[row][0] * v->vals[0]
1849 + m->vals[row][1] * v->vals[1]
1850 + m->vals[row][2] * v->vals[2];
1851 }
1852 return dst;
1853}
1854
1855bool skcms_AdaptToXYZD50(float wx, float wy,
1856 skcms_Matrix3x3* toXYZD50) {
1857 if (!is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1858 !toXYZD50) {
1859 return false;
1860 }
1861
1862 // Assumes that Y is 1.0f.
1863 skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
1864
1865 // Now convert toXYZ matrix to toXYZD50.
1866 skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1867
1868 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus
1869 // the matrices below. The Bradford method is used by Adobe and is widely considered
1870 // to be the best.
1871 skcms_Matrix3x3 xyz_to_lms = {{
1872 { 0.8951f, 0.2664f, -0.1614f },
1873 { -0.7502f, 1.7135f, 0.0367f },
1874 { 0.0389f, -0.0685f, 1.0296f },
1875 }};
1876 skcms_Matrix3x3 lms_to_xyz = {{
1877 { 0.9869929f, -0.1470543f, 0.1599627f },
1878 { 0.4323053f, 0.5183603f, 0.0492912f },
1879 { -0.0085287f, 0.0400428f, 0.9684867f },
1880 }};
1881
1882 skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1883 skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
1884
1885 *toXYZD50 = {{
1886 { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1887 { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1888 { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1889 }};
1890 *toXYZD50 = skcms_Matrix3x3_concat(toXYZD50, &xyz_to_lms);
1891 *toXYZD50 = skcms_Matrix3x3_concat(&lms_to_xyz, toXYZD50);
1892
1893 return true;
1894}
1895
1896bool skcms_PrimariesToXYZD50(float rx, float ry,
1897 float gx, float gy,
1898 float bx, float by,
1899 float wx, float wy,
1900 skcms_Matrix3x3* toXYZD50) {
1901 if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1902 !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1903 !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1904 !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1905 !toXYZD50) {
1906 return false;
1907 }
1908
1909 // First, we need to convert xy values (primaries) to XYZ.
1910 skcms_Matrix3x3 primaries = {{
1911 { rx, gx, bx },
1912 { ry, gy, by },
1913 { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1914 }};
1915 skcms_Matrix3x3 primaries_inv;
1916 if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1917 return false;
1918 }
1919
1920 // Assumes that Y is 1.0f.
1921 skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
1922 skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
1923
1924 skcms_Matrix3x3 toXYZ = {{
1925 { XYZ.vals[0], 0, 0 },
1926 { 0, XYZ.vals[1], 0 },
1927 { 0, 0, XYZ.vals[2] },
1928 }};
1929 toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1930
1931 skcms_Matrix3x3 DXtoD50;
1932 if (!skcms_AdaptToXYZD50(wx, wy, &DXtoD50)) {
1933 return false;
1934 }
1935
1936 *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1937 return true;
1938}
1939
1940
1941bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1942 double a00 = src->vals[0][0],
1943 a01 = src->vals[1][0],
1944 a02 = src->vals[2][0],
1945 a10 = src->vals[0][1],
1946 a11 = src->vals[1][1],
1947 a12 = src->vals[2][1],
1948 a20 = src->vals[0][2],
1949 a21 = src->vals[1][2],
1950 a22 = src->vals[2][2];
1951
1952 double b0 = a00*a11 - a01*a10,
1953 b1 = a00*a12 - a02*a10,
1954 b2 = a01*a12 - a02*a11,
1955 b3 = a20,
1956 b4 = a21,
1957 b5 = a22;
1958
1959 double determinant = b0*b5
1960 - b1*b4
1961 + b2*b3;
1962
1963 if (determinant == 0) {
1964 return false;
1965 }
1966
1967 double invdet = 1.0 / determinant;
1968 if (invdet > +FLT_MAX3.40282347e+38F || invdet < -FLT_MAX3.40282347e+38F || !isfinitef_((float)invdet)) {
1969 return false;
1970 }
1971
1972 b0 *= invdet;
1973 b1 *= invdet;
1974 b2 *= invdet;
1975 b3 *= invdet;
1976 b4 *= invdet;
1977 b5 *= invdet;
1978
1979 dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1980 dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1981 dst->vals[2][0] = (float)( + b2 );
1982 dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1983 dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1984 dst->vals[2][1] = (float)( - b1 );
1985 dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1986 dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1987 dst->vals[2][2] = (float)( + b0 );
1988
1989 for (int r = 0; r < 3; ++r)
1990 for (int c = 0; c < 3; ++c) {
1991 if (!isfinitef_(dst->vals[r][c])) {
1992 return false;
1993 }
1994 }
1995 return true;
1996}
1997
1998skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1999 skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
2000 for (int r = 0; r < 3; r++)
2001 for (int c = 0; c < 3; c++) {
2002 m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
2003 + A->vals[r][1] * B->vals[1][c]
2004 + A->vals[r][2] * B->vals[2][c];
2005 }
2006 return m;
2007}
2008
2009#if defined(__clang__1)
2010 [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by classify() on the way out.
2011#endif
2012bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
2013 TF_PQish pq;
2014 TF_HLGish hlg;
2015 switch (classify(*src, &pq, &hlg)) {
2016 case skcms_TFType_Invalid: return false;
2017 case skcms_TFType_PQ: return false;
2018 case skcms_TFType_HLG: return false;
2019 case skcms_TFType_sRGBish: break; // handled below
2020
2021 case skcms_TFType_PQish:
2022 *dst = { TFKind_marker(skcms_TFType_PQish), -pq.A, pq.D, 1.0f/pq.F
2023 , pq.B, -pq.E, 1.0f/pq.C};
2024 return true;
2025
2026 case skcms_TFType_HLGish:
2027 *dst = { TFKind_marker(skcms_TFType_HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
2028 , 1.0f/hlg.a, hlg.b, hlg.c
2029 , hlg.K_minus_1 };
2030 return true;
2031
2032 case skcms_TFType_HLGinvish:
2033 *dst = { TFKind_marker(skcms_TFType_HLGish), 1.0f/hlg.R, 1.0f/hlg.G
2034 , 1.0f/hlg.a, hlg.b, hlg.c
2035 , hlg.K_minus_1 };
2036 return true;
2037 }
2038
2039 assert (classify(*src) == skcms_TFType_sRGBish)(static_cast <bool> (classify(*src) == skcms_TFType_sRGBish
) ? void (0) : __assert_fail ("classify(*src) == skcms_TFType_sRGBish"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
2040
2041 // We're inverting this function, solving for x in terms of y.
2042 // y = (cx + f) x < d
2043 // (ax + b)^g + e x ≥ d
2044 // The inverse of this function can be expressed in the same piecewise form.
2045 skcms_TransferFunction inv = {0,0,0,0,0,0,0};
2046
2047 // We'll start by finding the new threshold inv.d.
2048 // In principle we should be able to find that by solving for y at x=d from either side.
2049 // (If those two d values aren't the same, it's a discontinuous transfer function.)
2050 float d_l = src->c * src->d + src->f,
2051 d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
2052 if (fabsf_(d_l - d_r) > 1/512.0f) {
2053 return false;
2054 }
2055 inv.d = d_l; // TODO(mtklein): better in practice to choose d_r?
2056
2057 // When d=0, the linear section collapses to a point. We leave c,d,f all zero in that case.
2058 if (inv.d > 0) {
2059 // Inverting the linear section is pretty straightfoward:
2060 // y = cx + f
2061 // y - f = cx
2062 // (1/c)y - f/c = x
2063 inv.c = 1.0f/src->c;
2064 inv.f = -src->f/src->c;
2065 }
2066
2067 // The interesting part is inverting the nonlinear section:
2068 // y = (ax + b)^g + e.
2069 // y - e = (ax + b)^g
2070 // (y - e)^1/g = ax + b
2071 // (y - e)^1/g - b = ax
2072 // (1/a)(y - e)^1/g - b/a = x
2073 //
2074 // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
2075 // let k = (1/a)^g
2076 // (1/a)( y - e)^1/g - b/a = x
2077 // (ky - ke)^1/g - b/a = x
2078
2079 float k = powf_(src->a, -src->g); // (1/a)^g == a^-g
2080 inv.g = 1.0f / src->g;
2081 inv.a = k;
2082 inv.b = -k * src->e;
2083 inv.e = -src->b / src->a;
2084
2085 // We need to enforce the same constraints here that we do when fitting a curve,
2086 // a >= 0 and ad+b >= 0. These constraints are checked by classify(), so they're true
2087 // of the source function if we're here.
2088
2089 // Just like when fitting the curve, there's really no way to rescue a < 0.
2090 if (inv.a < 0) {
2091 return false;
2092 }
2093 // On the other hand we can rescue an ad+b that's gone slightly negative here.
2094 if (inv.a * inv.d + inv.b < 0) {
2095 inv.b = -inv.a * inv.d;
2096 }
2097
2098 // That should usually make classify(inv) == sRGBish true, but there are a couple situations
2099 // where we might still fail here, like non-finite parameter values.
2100 if (classify(inv) != skcms_TFType_sRGBish) {
2101 return false;
2102 }
2103
2104 assert (inv.a >= 0)(static_cast <bool> (inv.a >= 0) ? void (0) : __assert_fail
("inv.a >= 0", __builtin_FILE (), __builtin_LINE (), __extension__
__PRETTY_FUNCTION__))
;
2105 assert (inv.a * inv.d + inv.b >= 0)(static_cast <bool> (inv.a * inv.d + inv.b >= 0) ? void
(0) : __assert_fail ("inv.a * inv.d + inv.b >= 0", __builtin_FILE
(), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__))
;
2106
2107 // Now in principle we're done.
2108 // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
2109 // e or f of the inverse, depending on which segment contains src(1.0f).
2110 float s = skcms_TransferFunction_eval(src, 1.0f);
2111 if (!isfinitef_(s)) {
2112 return false;
2113 }
2114
2115 float sign = s < 0 ? -1.0f : 1.0f;
2116 s *= sign;
2117 if (s < inv.d) {
2118 inv.f = 1.0f - sign * inv.c * s;
2119 } else {
2120 inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
2121 }
2122
2123 *dst = inv;
2124 return classify(*dst) == skcms_TFType_sRGBish;
2125}
2126
2127// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
2128
2129// From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
2130//
2131// tf(x) = cx + f x < d
2132// tf(x) = (ax + b)^g + e x ≥ d
2133//
2134// When fitting, we add the additional constraint that both pieces meet at d:
2135//
2136// cd + f = (ad + b)^g + e
2137//
2138// Solving for e and folding it through gives an alternate formulation of the non-linear piece:
2139//
2140// tf(x) = cx + f x < d
2141// tf(x) = (ax + b)^g - (ad + b)^g + cd + f x ≥ d
2142//
2143// Our overall strategy is then:
2144// For a couple tolerances,
2145// - fit_linear(): fit c,d,f iteratively to as many points as our tolerance allows
2146// - invert c,d,f
2147// - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
2148// (and by constraint, inverted e) to the inverse of the table.
2149// Return the parameters with least maximum error.
2150//
2151// To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
2152// of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
2153//
2154// let y = Table(x)
2155// r(x) = x - f_inv(y)
2156//
2157// ∂r/∂g = ln(ay + b)*(ay + b)^g
2158// - ln(ad + b)*(ad + b)^g
2159// ∂r/∂a = yg(ay + b)^(g-1)
2160// - dg(ad + b)^(g-1)
2161// ∂r/∂b = g(ay + b)^(g-1)
2162// - g(ad + b)^(g-1)
2163
2164// Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
2165// and fill out the gradient of the residual into dfdP.
2166static float rg_nonlinear(float x,
2167 const skcms_Curve* curve,
2168 const skcms_TransferFunction* tf,
2169 float dfdP[3]) {
2170 const float y = eval_curve(curve, x);
2171
2172 const float g = tf->g, a = tf->a, b = tf->b,
2173 c = tf->c, d = tf->d, f = tf->f;
2174
2175 const float Y = fmaxf_(a*y + b, 0.0f),
2176 D = a*d + b;
2177 assert (D >= 0)(static_cast <bool> (D >= 0) ? void (0) : __assert_fail
("D >= 0", __builtin_FILE (), __builtin_LINE (), __extension__
__PRETTY_FUNCTION__))
;
2178
2179 // The gradient.
2180 dfdP[0] = logf_(Y)*powf_(Y, g)
2181 - logf_(D)*powf_(D, g);
2182 dfdP[1] = y*g*powf_(Y, g-1)
2183 - d*g*powf_(D, g-1);
2184 dfdP[2] = g*powf_(Y, g-1)
2185 - g*powf_(D, g-1);
2186
2187 // The residual.
2188 const float f_inv = powf_(Y, g)
2189 - powf_(D, g)
2190 + c*d + f;
2191 return x - f_inv;
2192}
2193
2194static bool gauss_newton_step(const skcms_Curve* curve,
2195 skcms_TransferFunction* tf,
2196 float x0, float dx, int N) {
2197 // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
2198 //
2199 // Let P = [ tf->g, tf->a, tf->b ] (the three terms that we're adjusting).
2200 //
2201 // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
2202 // where r(P) is the residual vector
2203 // and Jf is the Jacobian matrix of f(), ∂r/∂P.
2204 //
2205 // Let's review the shape of each of these expressions:
2206 // r(P) is [N x 1], a column vector with one entry per value of x tested
2207 // Jf is [N x 3], a matrix with an entry for each (x,P) pair
2208 // Jf^T is [3 x N], the transpose of Jf
2209 //
2210 // Jf^T Jf is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
2211 // and so is its inverse (Jf^T Jf)^-1
2212 // Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
2213 //
2214 // Our implementation strategy to get to the final ∆P is
2215 // 1) evaluate Jf^T Jf, call that lhs
2216 // 2) evaluate Jf^T r(P), call that rhs
2217 // 3) invert lhs
2218 // 4) multiply inverse lhs by rhs
2219 //
2220 // This is a friendly implementation strategy because we don't have to have any
2221 // buffers that scale with N, and equally nice don't have to perform any matrix
2222 // operations that are variable size.
2223 //
2224 // Other implementation strategies could trade this off, e.g. evaluating the
2225 // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
2226 // the residuals. That would probably require implementing singular value
2227 // decomposition, and would create a [3 x N] matrix to be multiplied by the
2228 // [N x 1] residual vector, but on the upside I think that'd eliminate the
2229 // possibility of this gauss_newton_step() function ever failing.
2230
2231 // 0) start off with lhs and rhs safely zeroed.
2232 skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
2233 skcms_Vector3 rhs = { {0,0,0} };
2234
2235 // 1,2) evaluate lhs and evaluate rhs
2236 // We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
2237 // so we'll have to update lhs and rhs at the same time.
2238 for (int i = 0; i < N; i++) {
2239 float x = x0 + static_cast<float>(i)*dx;
2240
2241 float dfdP[3] = {0,0,0};
2242 float resid = rg_nonlinear(x,curve,tf, dfdP);
2243
2244 for (int r = 0; r < 3; r++) {
2245 for (int c = 0; c < 3; c++) {
2246 lhs.vals[r][c] += dfdP[r] * dfdP[c];
2247 }
2248 rhs.vals[r] += dfdP[r] * resid;
2249 }
2250 }
2251
2252 // If any of the 3 P parameters are unused, this matrix will be singular.
2253 // Detect those cases and fix them up to indentity instead, so we can invert.
2254 for (int k = 0; k < 3; k++) {
2255 if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
2256 lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
2257 lhs.vals[k][k] = 1;
2258 }
2259 }
2260
2261 // 3) invert lhs
2262 skcms_Matrix3x3 lhs_inv;
2263 if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
2264 return false;
2265 }
2266
2267 // 4) multiply inverse lhs by rhs
2268 skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
2269 tf->g += dP.vals[0];
2270 tf->a += dP.vals[1];
2271 tf->b += dP.vals[2];
2272 return isfinitef_(tf->g) && isfinitef_(tf->a) && isfinitef_(tf->b);
2273}
2274
2275static float max_roundtrip_error_checked(const skcms_Curve* curve,
2276 const skcms_TransferFunction* tf_inv) {
2277 skcms_TransferFunction tf;
2278 if (!skcms_TransferFunction_invert(tf_inv, &tf) || skcms_TFType_sRGBish != classify(tf)) {
2279 return INFINITY_;
2280 }
2281
2282 skcms_TransferFunction tf_inv_again;
2283 if (!skcms_TransferFunction_invert(&tf, &tf_inv_again)) {
2284 return INFINITY_;
2285 }
2286
2287 return skcms_MaxRoundtripError(curve, &tf_inv_again);
2288}
2289
2290// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
2291static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
2292 // This enforces a few constraints that are not modeled in gauss_newton_step()'s optimization.
2293 auto fixup_tf = [tf]() {
2294 // a must be non-negative. That ensures the function is monotonically increasing.
2295 // We don't really know how to fix up a if it goes negative.
2296 if (tf->a < 0) {
2297 return false;
2298 }
2299 // ad+b must be non-negative. That ensures we don't end up with complex numbers in powf.
2300 // We feel just barely not uneasy enough to tweak b so ad+b is zero in this case.
2301 if (tf->a * tf->d + tf->b < 0) {
2302 tf->b = -tf->a * tf->d;
2303 }
2304 assert (tf->a >= 0 &&(static_cast <bool> (tf->a >= 0 && tf->
a * tf->d + tf->b >= 0) ? void (0) : __assert_fail (
"tf->a >= 0 && tf->a * tf->d + tf->b >= 0"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
2305 tf->a * tf->d + tf->b >= 0)(static_cast <bool> (tf->a >= 0 && tf->
a * tf->d + tf->b >= 0) ? void (0) : __assert_fail (
"tf->a >= 0 && tf->a * tf->d + tf->b >= 0"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
2306
2307 // cd+f must be ~= (ad+b)^g+e. That ensures the function is continuous. We keep e as a free
2308 // parameter so we can guarantee this.
2309 tf->e = tf->c*tf->d + tf->f
2310 - powf_(tf->a*tf->d + tf->b, tf->g);
2311
2312 return isfinitef_(tf->e);
2313 };
2314
2315 if (!fixup_tf()) {
2316 return false;
2317 }
2318
2319 // No matter where we start, dx should always represent N even steps from 0 to 1.
2320 const float dx = 1.0f / static_cast<float>(N-1);
2321
2322 skcms_TransferFunction best_tf = *tf;
2323 float best_max_error = INFINITY_;
2324
2325 // Need this or several curves get worse... *sigh*
2326 float init_error = max_roundtrip_error_checked(curve, tf);
2327 if (init_error < best_max_error) {
2328 best_max_error = init_error;
2329 best_tf = *tf;
2330 }
2331
2332 // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
2333 for (int j = 0; j < 8; j++) {
2334 if (!gauss_newton_step(curve, tf, static_cast<float>(L)*dx, dx, N-L) || !fixup_tf()) {
2335 *tf = best_tf;
2336 return isfinitef_(best_max_error);
2337 }
2338
2339 float max_error = max_roundtrip_error_checked(curve, tf);
2340 if (max_error < best_max_error) {
2341 best_max_error = max_error;
2342 best_tf = *tf;
2343 }
2344 }
2345
2346 *tf = best_tf;
2347 return isfinitef_(best_max_error);
2348}
2349
2350bool skcms_ApproximateCurve(const skcms_Curve* curve,
2351 skcms_TransferFunction* approx,
2352 float* max_error) {
2353 if (!curve
9.1
'curve' is non-null
|| !approx
9.2
'approx' is non-null
|| !max_error
9.3
'max_error' is non-null
) {
10
Taking false branch
2354 return false;
2355 }
2356
2357 if (curve->table_entries
10.1
Field 'table_entries' is not equal to 0
== 0) {
2358 // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
2359 return false;
2360 }
2361
2362 if (curve->table_entries == 1 || curve->table_entries > kMaxTableEntries) {
11
Assuming field 'table_entries' is not equal to 1
12
Assuming 'kMaxTableEntries' is >= field 'table_entries'
13
Taking false branch
2363 // We need at least two points, and must put some reasonable cap on the maximum number.
2364 return false;
2365 }
2366
2367 int N = (int)curve->table_entries;
2368 const float dx = 1.0f / static_cast<float>(N - 1);
2369
2370 *max_error = INFINITY_;
2371 const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
2372 for (int t = 0; t < ARRAY_COUNT(kTolerances)(int)(sizeof((kTolerances)) / sizeof(*(kTolerances))); t++) {
14
Loop condition is true. Entering loop body
2373 skcms_TransferFunction tf,
2374 tf_inv;
2375
2376 // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
2377 tf.f = 0.0f;
2378 int L = fit_linear(curve, N, kTolerances[t], &tf.c, &tf.d);
15
Calling 'fit_linear'
22
Returning from 'fit_linear'
2379
2380 if (L
22.1
'L' is not equal to 'N'
== N) {
23
Taking false branch
2381 // If the entire data set was linear, move the coefficients to the nonlinear portion
2382 // with G == 1. This lets use a canonical representation with d == 0.
2383 tf.g = 1;
2384 tf.a = tf.c;
2385 tf.b = tf.f;
2386 tf.c = tf.d = tf.e = tf.f = 0;
2387 } else if (L == N - 1) {
24
Assuming the condition is true
25
Taking true branch
2388 // Degenerate case with only two points in the nonlinear segment. Solve directly.
2389 tf.g = 1;
2390 tf.a = (eval_curve(curve, static_cast<float>(N-1)*dx) -
2391 eval_curve(curve, static_cast<float>(N-2)*dx))
2392 / dx;
2393 tf.b = eval_curve(curve, static_cast<float>(N-2)*dx)
2394 - tf.a * static_cast<float>(N-2)*dx;
2395 tf.e = 0;
2396 } else {
2397 // Start by guessing a gamma-only curve through the midpoint.
2398 int mid = (L + N) / 2;
2399 float mid_x = static_cast<float>(mid) / static_cast<float>(N - 1);
2400 float mid_y = eval_curve(curve, mid_x);
2401 tf.g = log2f_(mid_y) / log2f_(mid_x);
2402 tf.a = 1;
2403 tf.b = 0;
2404 tf.e = tf.c*tf.d + tf.f
2405 - powf_(tf.a*tf.d + tf.b, tf.g);
2406
2407
2408 if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
2409 !fit_nonlinear(curve, L,N, &tf_inv)) {
2410 continue;
2411 }
2412
2413 // We fit tf_inv, so calculate tf to keep in sync.
2414 // fit_nonlinear() should guarantee invertibility.
2415 if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
2416 assert(false)(static_cast <bool> (false) ? void (0) : __assert_fail (
"false", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
2417 continue;
2418 }
2419 }
2420
2421 // We'd better have a sane, sRGB-ish TF by now.
2422 // Other non-Bad TFs would be fine, but we know we've only ever tried to fit sRGBish;
2423 // anything else is just some accident of math and the way we pun tf.g as a type flag.
2424 // fit_nonlinear() should guarantee this, but the special cases may fail this test.
2425 if (skcms_TFType_sRGBish != classify(tf)) {
26
Calling 'classify'
2426 continue;
2427 }
2428
2429 // We find our error by roundtripping the table through tf_inv.
2430 //
2431 // (The most likely use case for this approximation is to be inverted and
2432 // used as the transfer function for a destination color space.)
2433 //
2434 // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
2435 // invertible, so re-verify that here (and use the new inverse for testing).
2436 // fit_nonlinear() should guarantee this, but the special cases that don't use
2437 // it may fail this test.
2438 if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
2439 continue;
2440 }
2441
2442 float err = skcms_MaxRoundtripError(curve, &tf_inv);
2443 if (*max_error > err) {
2444 *max_error = err;
2445 *approx = tf;
2446 }
2447 }
2448 return isfinitef_(*max_error);
2449}
2450
2451enum class CpuType { Baseline, HSW, SKX };
2452
2453static CpuType cpu_type() {
2454 #if defined(SKCMS_PORTABLE) || !defined(__x86_64__1) || defined(SKCMS_FORCE_BASELINE)
2455 return CpuType::Baseline;
2456 #elif defined(SKCMS_FORCE_HSW)
2457 return CpuType::HSW;
2458 #elif defined(SKCMS_FORCE_SKX)
2459 return CpuType::SKX;
2460 #else
2461 static const CpuType type = []{
2462 if (!sAllowRuntimeCPUDetection) {
2463 return CpuType::Baseline;
2464 }
2465 // See http://www.sandpile.org/x86/cpuid.htm
2466
2467 // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2468 uint32_t eax, ebx, ecx, edx;
2469 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2470 : "0"(1), "2"(0));
2471 if ((edx & (1u<<25)) && // SSE
2472 (edx & (1u<<26)) && // SSE2
2473 (ecx & (1u<< 0)) && // SSE3
2474 (ecx & (1u<< 9)) && // SSSE3
2475 (ecx & (1u<<12)) && // FMA (N.B. not used, avoided even)
2476 (ecx & (1u<<19)) && // SSE4.1
2477 (ecx & (1u<<20)) && // SSE4.2
2478 (ecx & (1u<<26)) && // XSAVE
2479 (ecx & (1u<<27)) && // OSXSAVE
2480 (ecx & (1u<<28)) && // AVX
2481 (ecx & (1u<<29))) { // F16C
2482
2483 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2484 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2485 : "0"(7), "2"(0));
2486 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2487 uint32_t xcr0, dont_need_edx;
2488 __asm__ __volatile__("xgetbv" : "=a"(xcr0), "=d"(dont_need_edx) : "c"(0));
2489
2490 if ((xcr0 & (1u<<1)) && // XMM register state saved?
2491 (xcr0 & (1u<<2)) && // YMM register state saved?
2492 (ebx & (1u<<5))) { // AVX2
2493 // At this point we're at least HSW. Continue checking for SKX.
2494 if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2495 (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2496 (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2497 (ebx & (1u<<16)) && // AVX512F
2498 (ebx & (1u<<17)) && // AVX512DQ
2499 (ebx & (1u<<28)) && // AVX512CD
2500 (ebx & (1u<<30)) && // AVX512BW
2501 (ebx & (1u<<31))) { // AVX512VL
2502 return CpuType::SKX;
2503 }
2504 return CpuType::HSW;
2505 }
2506 }
2507 return CpuType::Baseline;
2508 }();
2509 return type;
2510 #endif
2511}
2512
2513static bool tf_is_gamma(const skcms_TransferFunction& tf) {
2514 return tf.g > 0 && tf.a == 1 &&
2515 tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0;
2516}
2517
2518struct OpAndArg {
2519 Op op;
2520 const void* arg;
2521};
2522
2523static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
2524 struct OpType {
2525 Op sGamma, sRGBish, PQish, HLGish, HLGinvish, table;
2526 };
2527 static constexpr OpType kOps[] = {
2528 { Op::gamma_r, Op::tf_r, Op::pq_r, Op::hlg_r, Op::hlginv_r, Op::table_r },
2529 { Op::gamma_g, Op::tf_g, Op::pq_g, Op::hlg_g, Op::hlginv_g, Op::table_g },
2530 { Op::gamma_b, Op::tf_b, Op::pq_b, Op::hlg_b, Op::hlginv_b, Op::table_b },
2531 { Op::gamma_a, Op::tf_a, Op::pq_a, Op::hlg_a, Op::hlginv_a, Op::table_a },
2532 };
2533 const auto& op = kOps[channel];
2534
2535 if (curve->table_entries == 0) {
2536 const OpAndArg noop = { Op::load_a8/*doesn't matter*/, nullptr };
2537
2538 const skcms_TransferFunction& tf = curve->parametric;
2539
2540 if (tf_is_gamma(tf)) {
2541 return tf.g != 1 ? OpAndArg{op.sGamma, &tf}
2542 : noop;
2543 }
2544
2545 switch (classify(tf)) {
2546 case skcms_TFType_Invalid: return noop;
2547 // TODO(https://issues.skia.org/issues/420956739): Consider adding
2548 // support for PQ and HLG. Generally any code that goes through this
2549 // path would also want tone mapping too.
2550 case skcms_TFType_PQ: return noop;
2551 case skcms_TFType_HLG: return noop;
2552 case skcms_TFType_sRGBish: return OpAndArg{op.sRGBish, &tf};
2553 case skcms_TFType_PQish: return OpAndArg{op.PQish, &tf};
2554 case skcms_TFType_HLGish: return OpAndArg{op.HLGish, &tf};
2555 case skcms_TFType_HLGinvish: return OpAndArg{op.HLGinvish, &tf};
2556 }
2557 }
2558 // The table_* ops make this assumption.
2559 assert(curve->table_entries <= kMaxTableEntries)(static_cast <bool> (curve->table_entries <= kMaxTableEntries
) ? void (0) : __assert_fail ("curve->table_entries <= kMaxTableEntries"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
2560 return OpAndArg{op.table, curve};
2561}
2562
2563// Returns negative if any of the curves are malformed.
2564static int select_curve_ops(const skcms_Curve* curves, int numChannels, OpAndArg* ops) {
2565 // We process the channels in reverse order, yielding ops in ABGR order.
2566 // (Working backwards allows us to fuse trailing B+G+R ops into a single RGB op.)
2567 int cursor = 0;
2568 for (int index = numChannels; index-- > 0; ) {
2569 if (curves[index].table_entries > kMaxTableEntries) {
2570 return -1;
2571 }
2572 ops[cursor] = select_curve_op(&curves[index], index);
2573 if (ops[cursor].arg) {
2574 ++cursor;
2575 }
2576 }
2577
2578 // Identify separate B+G+R ops and fuse them into a single RGB op.
2579 if (cursor >= 3) {
2580 struct FusableOps {
2581 Op r, g, b, rgb;
2582 };
2583 static constexpr FusableOps kFusableOps[] = {
2584 {Op::gamma_r, Op::gamma_g, Op::gamma_b, Op::gamma_rgb},
2585 {Op::tf_r, Op::tf_g, Op::tf_b, Op::tf_rgb},
2586 {Op::pq_r, Op::pq_g, Op::pq_b, Op::pq_rgb},
2587 {Op::hlg_r, Op::hlg_g, Op::hlg_b, Op::hlg_rgb},
2588 {Op::hlginv_r, Op::hlginv_g, Op::hlginv_b, Op::hlginv_rgb},
2589 };
2590
2591 int posR = cursor - 1;
2592 int posG = cursor - 2;
2593 int posB = cursor - 3;
2594 for (const FusableOps& fusableOp : kFusableOps) {
2595 if (ops[posR].op == fusableOp.r &&
2596 ops[posG].op == fusableOp.g &&
2597 ops[posB].op == fusableOp.b &&
2598 (0 == memcmp(ops[posR].arg, ops[posG].arg, sizeof(skcms_TransferFunction))) &&
2599 (0 == memcmp(ops[posR].arg, ops[posB].arg, sizeof(skcms_TransferFunction)))) {
2600 // Fuse the three matching ops into one.
2601 ops[posB].op = fusableOp.rgb;
2602 cursor -= 2;
2603 break;
2604 }
2605 }
2606 }
2607
2608 return cursor;
2609}
2610
2611static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2612 switch (fmt >> 1) { // ignore rgb/bgr
2613 case skcms_PixelFormat_A_8 >> 1: return 1;
2614 case skcms_PixelFormat_G_8 >> 1: return 1;
2615 case skcms_PixelFormat_GA_88 >> 1: return 2;
2616 case skcms_PixelFormat_ABGR_4444 >> 1: return 2;
2617 case skcms_PixelFormat_RGB_565 >> 1: return 2;
2618 case skcms_PixelFormat_RGB_888 >> 1: return 3;
2619 case skcms_PixelFormat_RGBA_8888 >> 1: return 4;
2620 case skcms_PixelFormat_RGBA_8888_sRGB >> 1: return 4;
2621 case skcms_PixelFormat_RGBA_1010102 >> 1: return 4;
2622 case skcms_PixelFormat_RGB_101010x_XR >> 1: return 4;
2623 case skcms_PixelFormat_RGB_161616LE >> 1: return 6;
2624 case skcms_PixelFormat_RGBA_10101010_XR >> 1: return 8;
2625 case skcms_PixelFormat_RGBA_16161616LE >> 1: return 8;
2626 case skcms_PixelFormat_RGB_161616BE >> 1: return 6;
2627 case skcms_PixelFormat_RGBA_16161616BE >> 1: return 8;
2628 case skcms_PixelFormat_RGB_hhh_Norm >> 1: return 6;
2629 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: return 8;
2630 case skcms_PixelFormat_RGB_hhh >> 1: return 6;
2631 case skcms_PixelFormat_RGBA_hhhh >> 1: return 8;
2632 case skcms_PixelFormat_RGB_fff >> 1: return 12;
2633 case skcms_PixelFormat_RGBA_ffff >> 1: return 16;
2634 }
2635 assert(false)(static_cast <bool> (false) ? void (0) : __assert_fail (
"false", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
2636 return 0;
2637}
2638
2639// See ITU-T H.273 Table 3 for the full list of codes.
2640const uint8_t kTransferCicpIdPQ = 16;
2641const uint8_t kTransferCicpIdHLG = 18;
2642
2643static bool has_cicp_pq_trc(const skcms_ICCProfile* profile) {
2644 return profile->has_CICP
2645 && profile->CICP.transfer_characteristics == kTransferCicpIdPQ;
2646}
2647
2648static bool has_cicp_hlg_trc(const skcms_ICCProfile* profile) {
2649 return profile->has_CICP
2650 && profile->CICP.transfer_characteristics == kTransferCicpIdHLG;
2651}
2652
2653// Set tf to be the PQ transfer function, scaled such that 1.0 will map to 10,000 / 203.
2654static void set_reference_pq_ish_trc(skcms_TransferFunction* tf) {
2655 // Initialize such that 1.0 maps to 1.0.
2656 skcms_TransferFunction_makePQish(tf,
2657 -107/128.0f, 1.0f, 32/2523.0f, 2413/128.0f, -2392/128.0f, 8192/1305.0f);
2658
2659 // Distribute scaling factor W by scaling A and B with X ^ (1/F):
2660 // ((A + Bx^C) / (D + Ex^C))^F * W = ((A + Bx^C) / (D + Ex^C) * W^(1/F))^F
2661 // See https://crbug.com/1058580#c32 for discussion.
2662 const float w = 10000.0f / 203.0f;
2663 const float ws = powf_(w, 1.0f / tf->f);
2664 tf->a = ws * tf->a;
2665 tf->b = ws * tf->b;
2666}
2667
2668// Set tf to be the HLG inverse OETF, scaled such that 1.0 will map to 1.0.
2669// While this is one version of HLG, there are many others. A better version
2670// would be to use the 1,000 nit reference version, but that will require
2671// adding opt-optical transform support.
2672static void set_sdr_hlg_ish_trc(skcms_TransferFunction* tf) {
2673 skcms_TransferFunction_makeHLGish(tf,
2674 2.0f, 2.0f, 1/0.17883277f, 0.28466892f, 0.55991073f);
2675 tf->f = 1.0f / 12.0f - 1.0f;
2676}
2677
2678static bool prep_for_destination(const skcms_ICCProfile* profile,
2679 skcms_Matrix3x3* fromXYZD50,
2680 skcms_TransferFunction* invR,
2681 skcms_TransferFunction* invG,
2682 skcms_TransferFunction* invB,
2683 bool* dst_using_B2A,
2684 bool* dst_using_hlg_ootf) {
2685 const bool has_xyzd50 =
2686 profile->has_toXYZD50 &&
2687 skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2688 *dst_using_B2A = false;
2689 *dst_using_hlg_ootf = false;
2690
2691 // CICP-specified PQ or HLG transfer functions take precedence.
2692 // TODO: Add the ability to parse CICP primaries to not require
2693 // the XYZD50 matrix.
2694 if (has_cicp_pq_trc(profile) && has_xyzd50) {
2695 skcms_TransferFunction trc_pq;
2696 set_reference_pq_ish_trc(&trc_pq);
2697 skcms_TransferFunction_invert(&trc_pq, invR);
2698 skcms_TransferFunction_invert(&trc_pq, invG);
2699 skcms_TransferFunction_invert(&trc_pq, invB);
2700 return true;
2701 }
2702 if (has_cicp_hlg_trc(profile) && has_xyzd50) {
2703 skcms_TransferFunction trc_hlg;
2704 set_sdr_hlg_ish_trc(&trc_hlg);
2705 skcms_TransferFunction_invert(&trc_hlg, invR);
2706 skcms_TransferFunction_invert(&trc_hlg, invG);
2707 skcms_TransferFunction_invert(&trc_hlg, invB);
2708 *dst_using_hlg_ootf = true;
2709 return true;
2710 }
2711
2712 // Then prefer the B2A transformation.
2713 // skcms_Transform() supports B2A destinations.
2714 if (profile->has_B2A) {
2715 *dst_using_B2A = true;
2716 return true;
2717 }
2718
2719 // Finally use parametric transfer functions.
2720 // TODO: Reject non sRGB-ish transfer functions here.
2721 return has_xyzd50
2722 && profile->has_trc
2723 && profile->trc[0].table_entries == 0
2724 && profile->trc[1].table_entries == 0
2725 && profile->trc[2].table_entries == 0
2726 && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2727 && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2728 && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB);
2729}
2730
2731bool skcms_Transform(const void* src,
2732 skcms_PixelFormat srcFmt,
2733 skcms_AlphaFormat srcAlpha,
2734 const skcms_ICCProfile* srcProfile,
2735 void* dst,
2736 skcms_PixelFormat dstFmt,
2737 skcms_AlphaFormat dstAlpha,
2738 const skcms_ICCProfile* dstProfile,
2739 size_t nz) {
2740 const size_t dst_bpp = bytes_per_pixel(dstFmt),
2741 src_bpp = bytes_per_pixel(srcFmt);
2742 // Let's just refuse if the request is absurdly big.
2743 if (nz * dst_bpp > INT_MAX2147483647 || nz * src_bpp > INT_MAX2147483647) {
2744 return false;
2745 }
2746 int n = (int)nz;
2747
2748 // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2749 if (!srcProfile) {
2750 srcProfile = skcms_sRGB_profile();
2751 }
2752 if (!dstProfile) {
2753 dstProfile = skcms_sRGB_profile();
2754 }
2755
2756 // We can't transform in place unless the PixelFormats are the same size.
2757 if (dst == src && dst_bpp != src_bpp) {
2758 return false;
2759 }
2760 // TODO: more careful alias rejection (like, dst == src + 1)?
2761
2762 Op program[32];
2763 const void* context[32];
2764
2765 Op* ops = program;
2766 const void** contexts = context;
2767
2768 auto add_op = [&](Op o) {
2769 *ops++ = o;
2770 *contexts++ = nullptr;
2771 };
2772
2773 auto add_op_ctx = [&](Op o, const void* c) {
2774 *ops++ = o;
2775 *contexts++ = c;
2776 };
2777
2778 auto add_curve_ops = [&](const skcms_Curve* curves, int numChannels) -> bool {
2779 OpAndArg oa[4];
2780 assert(numChannels <= ARRAY_COUNT(oa))(static_cast <bool> (numChannels <= (int)(sizeof((oa
)) / sizeof(*(oa)))) ? void (0) : __assert_fail ("numChannels <= ARRAY_COUNT(oa)"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
2781
2782 int numOps = select_curve_ops(curves, numChannels, oa);
2783 if (numOps < 0) {
2784 return false;
2785 }
2786
2787 for (int i = 0; i < numOps; ++i) {
2788 add_op_ctx(oa[i].op, oa[i].arg);
2789 }
2790 return true;
2791 };
2792
2793 // If the source has a TRC that is specified by CICP and not the TRC
2794 // entries, then store it here for future use.
2795 skcms_TransferFunction src_cicp_trc;
2796
2797 // These are always parametric curves of some sort.
2798 skcms_Curve dst_curves[3];
2799 dst_curves[0].table_entries =
2800 dst_curves[1].table_entries =
2801 dst_curves[2].table_entries = 0;
2802
2803 // This will store the XYZD50 to destination gamut conversion matrix, if it is needed.
2804 skcms_Matrix3x3 dst_from_xyz;
2805
2806 // This will store the full source to destination gamut conversion matrix, if it is needed.
2807 skcms_Matrix3x3 dst_from_src;
2808
2809 switch (srcFmt >> 1) {
2810 default: return false;
2811 case skcms_PixelFormat_A_8 >> 1: add_op(Op::load_a8); break;
2812 case skcms_PixelFormat_G_8 >> 1: add_op(Op::load_g8); break;
2813 case skcms_PixelFormat_GA_88 >> 1: add_op(Op::load_ga88); break;
2814 case skcms_PixelFormat_ABGR_4444 >> 1: add_op(Op::load_4444); break;
2815 case skcms_PixelFormat_RGB_565 >> 1: add_op(Op::load_565); break;
2816 case skcms_PixelFormat_RGB_888 >> 1: add_op(Op::load_888); break;
2817 case skcms_PixelFormat_RGBA_8888 >> 1: add_op(Op::load_8888); break;
2818 case skcms_PixelFormat_RGBA_1010102 >> 1: add_op(Op::load_1010102); break;
2819 case skcms_PixelFormat_RGB_101010x_XR >> 1: add_op(Op::load_101010x_XR); break;
2820 case skcms_PixelFormat_RGBA_10101010_XR >> 1: add_op(Op::load_10101010_XR); break;
2821 case skcms_PixelFormat_RGB_161616LE >> 1: add_op(Op::load_161616LE); break;
2822 case skcms_PixelFormat_RGBA_16161616LE >> 1: add_op(Op::load_16161616LE); break;
2823 case skcms_PixelFormat_RGB_161616BE >> 1: add_op(Op::load_161616BE); break;
2824 case skcms_PixelFormat_RGBA_16161616BE >> 1: add_op(Op::load_16161616BE); break;
2825 case skcms_PixelFormat_RGB_hhh_Norm >> 1: add_op(Op::load_hhh); break;
2826 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: add_op(Op::load_hhhh); break;
2827 case skcms_PixelFormat_RGB_hhh >> 1: add_op(Op::load_hhh); break;
2828 case skcms_PixelFormat_RGBA_hhhh >> 1: add_op(Op::load_hhhh); break;
2829 case skcms_PixelFormat_RGB_fff >> 1: add_op(Op::load_fff); break;
2830 case skcms_PixelFormat_RGBA_ffff >> 1: add_op(Op::load_ffff); break;
2831
2832 case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2833 add_op(Op::load_8888);
2834 add_op_ctx(Op::tf_rgb, skcms_sRGB_TransferFunction());
2835 break;
2836 }
2837 if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2838 srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2839 add_op(Op::clamp);
2840 }
2841 if (srcFmt & 1) {
2842 add_op(Op::swap_rb);
2843 }
2844 skcms_ICCProfile gray_dst_profile;
2845 switch (dstFmt >> 1) {
2846 case skcms_PixelFormat_G_8:
2847 case skcms_PixelFormat_GA_88:
2848 // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2849 // luminance (Y) by the destination transfer function.
2850 gray_dst_profile = *dstProfile;
2851 skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2852 dstProfile = &gray_dst_profile;
2853 break;
2854 default:
2855 break;
2856 }
2857
2858 if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2859 // Photoshop creates CMYK images as inverse CMYK.
2860 // These happen to be the only ones we've _ever_ seen.
2861 add_op(Op::invert);
2862 // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2863 srcAlpha = skcms_AlphaFormat_Unpremul;
2864 }
2865
2866 if (srcAlpha == skcms_AlphaFormat_Opaque) {
2867 add_op(Op::force_opaque);
2868 } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2869 add_op(Op::unpremul);
2870 }
2871
2872 if (dstProfile != srcProfile) {
2873
2874 // Track whether or not the A2B or B2A transforms are used. the CICP
2875 // values take precedence over A2B and B2A.
2876 bool src_using_A2B = false;
2877 bool src_using_hlg_ootf = false;
2878 bool dst_using_B2A = false;
2879 bool dst_using_hlg_ootf = false;
2880
2881 if (!prep_for_destination(dstProfile,
2882 &dst_from_xyz,
2883 &dst_curves[0].parametric,
2884 &dst_curves[1].parametric,
2885 &dst_curves[2].parametric,
2886 &dst_using_B2A,
2887 &dst_using_hlg_ootf)) {
2888 return false;
2889 }
2890
2891 if (has_cicp_pq_trc(srcProfile) && srcProfile->has_toXYZD50) {
2892 set_reference_pq_ish_trc(&src_cicp_trc);
2893 add_op_ctx(Op::pq_rgb, &src_cicp_trc);
2894 } else if (has_cicp_hlg_trc(srcProfile) && srcProfile->has_toXYZD50) {
2895 src_using_hlg_ootf = true;
2896 set_sdr_hlg_ish_trc(&src_cicp_trc);
2897 add_op_ctx(Op::hlg_rgb, &src_cicp_trc);
2898 } else if (srcProfile->has_A2B) {
2899 src_using_A2B = true;
2900 if (srcProfile->A2B.input_channels) {
2901 if (!add_curve_ops(srcProfile->A2B.input_curves,
2902 (int)srcProfile->A2B.input_channels)) {
2903 return false;
2904 }
2905 add_op(Op::clamp);
2906 add_op_ctx(Op::clut_A2B, &srcProfile->A2B);
2907 }
2908
2909 if (srcProfile->A2B.matrix_channels == 3) {
2910 if (!add_curve_ops(srcProfile->A2B.matrix_curves, /*numChannels=*/3)) {
2911 return false;
2912 }
2913
2914 static const skcms_Matrix3x4 I = {{
2915 {1,0,0,0},
2916 {0,1,0,0},
2917 {0,0,1,0},
2918 }};
2919 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2920 add_op_ctx(Op::matrix_3x4, &srcProfile->A2B.matrix);
2921 }
2922 }
2923
2924 if (srcProfile->A2B.output_channels == 3) {
2925 if (!add_curve_ops(srcProfile->A2B.output_curves, /*numChannels=*/3)) {
2926 return false;
2927 }
2928 }
2929
2930 if (srcProfile->pcs == skcms_Signature_Lab) {
2931 add_op(Op::lab_to_xyz);
2932 }
2933
2934 } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2935 if (!add_curve_ops(srcProfile->trc, /*numChannels=*/3)) {
2936 return false;
2937 }
2938 } else {
2939 return false;
2940 }
2941
2942 // A2B sources are in XYZD50 by now, but TRC sources are still in their original gamut.
2943 assert (srcProfile->has_A2B || srcProfile->has_toXYZD50)(static_cast <bool> (srcProfile->has_A2B || srcProfile
->has_toXYZD50) ? void (0) : __assert_fail ("srcProfile->has_A2B || srcProfile->has_toXYZD50"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
2944
2945 if (dst_using_B2A) {
2946 // B2A needs its input in XYZD50, so transform TRC sources now.
2947 if (!src_using_A2B) {
2948 add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
2949 // Apply the HLG OOTF in XYZD50 space, if needed.
2950 if (src_using_hlg_ootf) {
2951 add_op(Op::hlg_ootf_scale);
2952 }
2953 }
2954
2955 if (dstProfile->pcs == skcms_Signature_Lab) {
2956 add_op(Op::xyz_to_lab);
2957 }
2958
2959 if (dstProfile->B2A.input_channels == 3) {
2960 if (!add_curve_ops(dstProfile->B2A.input_curves, /*numChannels=*/3)) {
2961 return false;
2962 }
2963 }
2964
2965 if (dstProfile->B2A.matrix_channels == 3) {
2966 static const skcms_Matrix3x4 I = {{
2967 {1,0,0,0},
2968 {0,1,0,0},
2969 {0,0,1,0},
2970 }};
2971 if (0 != memcmp(&I, &dstProfile->B2A.matrix, sizeof(I))) {
2972 add_op_ctx(Op::matrix_3x4, &dstProfile->B2A.matrix);
2973 }
2974
2975 if (!add_curve_ops(dstProfile->B2A.matrix_curves, /*numChannels=*/3)) {
2976 return false;
2977 }
2978 }
2979
2980 if (dstProfile->B2A.output_channels) {
2981 add_op(Op::clamp);
2982 add_op_ctx(Op::clut_B2A, &dstProfile->B2A);
2983
2984 if (!add_curve_ops(dstProfile->B2A.output_curves,
2985 (int)dstProfile->B2A.output_channels)) {
2986 return false;
2987 }
2988 }
2989 } else {
2990 // This is a TRC destination.
2991
2992 // Transform to the destination gamut.
2993 if (src_using_hlg_ootf != dst_using_hlg_ootf) {
2994 // If just the src or the dst has an HLG OOTF then we will apply the OOTF in XYZD50
2995 // space. If both the src and dst has an HLG OOTF then they will cancel.
2996 if (!src_using_A2B) {
2997 add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
2998 }
2999 if (src_using_hlg_ootf) {
3000 add_op(Op::hlg_ootf_scale);
3001 }
3002 if (dst_using_hlg_ootf) {
3003 add_op(Op::hlginv_ootf_scale);
3004 }
3005 add_op_ctx(Op::matrix_3x3, &dst_from_xyz);
3006 } else if (src_using_A2B) {
3007 // If the source is A2B then we are already in XYZD50. Just apply the xyz->dst
3008 // matrix.
3009 add_op_ctx(Op::matrix_3x3, &dst_from_xyz);
3010 } else {
3011 const skcms_Matrix3x3* to_xyz = &srcProfile->toXYZD50;
3012 // There's a chance the source and destination gamuts are identical,
3013 // in which case we can skip the gamut transform.
3014 if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
3015 // Concat the entire gamut transform into dst_from_src.
3016 dst_from_src = skcms_Matrix3x3_concat(&dst_from_xyz, to_xyz);
3017 add_op_ctx(Op::matrix_3x3, &dst_from_src);
3018 }
3019 }
3020
3021 // Encode back to dst RGB using its parametric transfer functions.
3022 OpAndArg oa[3];
3023 int numOps = select_curve_ops(dst_curves, /*numChannels=*/3, oa);
3024 // All dst_curves should be parametric, not table-based, so select_curve_ops should
3025 // not fail.
3026 assert(numOps >= 0)(static_cast <bool> (numOps >= 0) ? void (0) : __assert_fail
("numOps >= 0", __builtin_FILE (), __builtin_LINE (), __extension__
__PRETTY_FUNCTION__))
;
3027 for (int index = 0; index < numOps; ++index) {
3028 assert(oa[index].op != Op::table_r &&(static_cast <bool> (oa[index].op != Op::table_r &&
oa[index].op != Op::table_g && oa[index].op != Op::table_b
&& oa[index].op != Op::table_a) ? void (0) : __assert_fail
("oa[index].op != Op::table_r && oa[index].op != Op::table_g && oa[index].op != Op::table_b && oa[index].op != Op::table_a"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
3029 oa[index].op != Op::table_g &&(static_cast <bool> (oa[index].op != Op::table_r &&
oa[index].op != Op::table_g && oa[index].op != Op::table_b
&& oa[index].op != Op::table_a) ? void (0) : __assert_fail
("oa[index].op != Op::table_r && oa[index].op != Op::table_g && oa[index].op != Op::table_b && oa[index].op != Op::table_a"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
3030 oa[index].op != Op::table_b &&(static_cast <bool> (oa[index].op != Op::table_r &&
oa[index].op != Op::table_g && oa[index].op != Op::table_b
&& oa[index].op != Op::table_a) ? void (0) : __assert_fail
("oa[index].op != Op::table_r && oa[index].op != Op::table_g && oa[index].op != Op::table_b && oa[index].op != Op::table_a"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
3031 oa[index].op != Op::table_a)(static_cast <bool> (oa[index].op != Op::table_r &&
oa[index].op != Op::table_g && oa[index].op != Op::table_b
&& oa[index].op != Op::table_a) ? void (0) : __assert_fail
("oa[index].op != Op::table_r && oa[index].op != Op::table_g && oa[index].op != Op::table_b && oa[index].op != Op::table_a"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
3032 add_op_ctx(oa[index].op, oa[index].arg);
3033 }
3034 }
3035 }
3036
3037 // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
3038 // not just to values that fit in [0,1].
3039 //
3040 // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
3041 // but would be carrying r > 1, which is really unexpected for downstream consumers.
3042 if (dstFmt < skcms_PixelFormat_RGB_hhh) {
3043 add_op(Op::clamp);
3044 }
3045
3046 if (dstProfile->data_color_space == skcms_Signature_CMYK) {
3047 // Photoshop creates CMYK images as inverse CMYK.
3048 // These happen to be the only ones we've _ever_ seen.
3049 add_op(Op::invert);
3050
3051 // CMYK has no alpha channel, so make sure dstAlpha is a no-op.
3052 dstAlpha = skcms_AlphaFormat_Unpremul;
3053 }
3054
3055 if (dstAlpha == skcms_AlphaFormat_Opaque) {
3056 add_op(Op::force_opaque);
3057 } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
3058 add_op(Op::premul);
3059 }
3060 if (dstFmt & 1) {
3061 add_op(Op::swap_rb);
3062 }
3063 switch (dstFmt >> 1) {
3064 default: return false;
3065 case skcms_PixelFormat_A_8 >> 1: add_op(Op::store_a8); break;
3066 case skcms_PixelFormat_G_8 >> 1: add_op(Op::store_g8); break;
3067 case skcms_PixelFormat_GA_88 >> 1: add_op(Op::store_ga88); break;
3068 case skcms_PixelFormat_ABGR_4444 >> 1: add_op(Op::store_4444); break;
3069 case skcms_PixelFormat_RGB_565 >> 1: add_op(Op::store_565); break;
3070 case skcms_PixelFormat_RGB_888 >> 1: add_op(Op::store_888); break;
3071 case skcms_PixelFormat_RGBA_8888 >> 1: add_op(Op::store_8888); break;
3072 case skcms_PixelFormat_RGBA_1010102 >> 1: add_op(Op::store_1010102); break;
3073 case skcms_PixelFormat_RGB_161616LE >> 1: add_op(Op::store_161616LE); break;
3074 case skcms_PixelFormat_RGBA_16161616LE >> 1: add_op(Op::store_16161616LE); break;
3075 case skcms_PixelFormat_RGB_161616BE >> 1: add_op(Op::store_161616BE); break;
3076 case skcms_PixelFormat_RGBA_16161616BE >> 1: add_op(Op::store_16161616BE); break;
3077 case skcms_PixelFormat_RGB_hhh_Norm >> 1: add_op(Op::store_hhh); break;
3078 case skcms_PixelFormat_RGBA_hhhh_Norm >> 1: add_op(Op::store_hhhh); break;
3079 case skcms_PixelFormat_RGB_101010x_XR >> 1: add_op(Op::store_101010x_XR); break;
3080 case skcms_PixelFormat_RGBA_10101010_XR >> 1: add_op(Op::store_10101010_XR); break;
3081 case skcms_PixelFormat_RGB_hhh >> 1: add_op(Op::store_hhh); break;
3082 case skcms_PixelFormat_RGBA_hhhh >> 1: add_op(Op::store_hhhh); break;
3083 case skcms_PixelFormat_RGB_fff >> 1: add_op(Op::store_fff); break;
3084 case skcms_PixelFormat_RGBA_ffff >> 1: add_op(Op::store_ffff); break;
3085
3086 case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
3087 add_op_ctx(Op::tf_rgb, skcms_sRGB_Inverse_TransferFunction());
3088 add_op(Op::store_8888);
3089 break;
3090 }
3091
3092 assert(ops <= program + ARRAY_COUNT(program))(static_cast <bool> (ops <= program + (int)(sizeof((
program)) / sizeof(*(program)))) ? void (0) : __assert_fail (
"ops <= program + ARRAY_COUNT(program)", __builtin_FILE ()
, __builtin_LINE (), __extension__ __PRETTY_FUNCTION__))
;
3093 assert(contexts <= context + ARRAY_COUNT(context))(static_cast <bool> (contexts <= context + (int)(sizeof
((context)) / sizeof(*(context)))) ? void (0) : __assert_fail
("contexts <= context + ARRAY_COUNT(context)", __builtin_FILE
(), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__))
;
3094
3095 auto run = baseline::run_program;
3096 switch (cpu_type()) {
3097 case CpuType::SKX:
3098 #if !defined(SKCMS_DISABLE_SKX1)
3099 run = skx::run_program;
3100 break;
3101 #endif
3102
3103 case CpuType::HSW:
3104 #if !defined(SKCMS_DISABLE_HSW)
3105 run = hsw::run_program;
3106 break;
3107 #endif
3108
3109 case CpuType::Baseline:
3110 break;
3111 }
3112
3113 run(program, context, ops - program, (const char*)src, (char*)dst, n, src_bpp,dst_bpp);
3114 return true;
3115}
3116
3117static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
3118#if defined(NDEBUG)
3119 (void)profile;
3120#else
3121 skcms_Matrix3x3 fromXYZD50;
3122 skcms_TransferFunction invR, invG, invB;
3123 bool useB2A = false;
3124 bool useHlgOotf = false;
3125 assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB, &useB2A, &useHlgOotf))(static_cast <bool> (prep_for_destination(profile, &
fromXYZD50, &invR, &invG, &invB, &useB2A, &
useHlgOotf)) ? void (0) : __assert_fail ("prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB, &useB2A, &useHlgOotf)"
, __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__
))
;
3126#endif
3127}
3128
3129bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
3130 if (!profile->has_B2A
1.1
Field 'has_B2A' is false
) {
2
Taking true branch
3131 skcms_Matrix3x3 fromXYZD50;
3132 if (!profile->has_trc || !profile->has_toXYZD50
3
Assuming field 'has_trc' is true
4
Assuming field 'has_toXYZD50' is true
6
Taking false branch
3133 || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
5
Assuming the condition is false
3134 return false;
3135 }
3136
3137 skcms_TransferFunction tf[3];
3138 for (int i = 0; i < 3; i++) {
7
Loop condition is true. Entering loop body
3139 skcms_TransferFunction inv;
3140 if (profile->trc[i].table_entries == 0
8
Assuming field 'table_entries' is not equal to 0
3141 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
3142 tf[i] = profile->trc[i].parametric;
3143 continue;
3144 }
3145
3146 float max_error;
3147 // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
3148 if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
9
Calling 'skcms_ApproximateCurve'
3149 return false;
3150 }
3151 }
3152
3153 for (int i = 0; i < 3; ++i) {
3154 profile->trc[i].table_entries = 0;
3155 profile->trc[i].parametric = tf[i];
3156 }
3157 }
3158 assert_usable_as_destination(profile);
3159 return true;
3160}
3161
3162bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
3163 // Call skcms_MakeUsableAsDestination() with B2A disabled;
3164 // on success that'll return a TRC/XYZ profile with three skcms_TransferFunctions.
3165 skcms_ICCProfile result = *profile;
3166 result.has_B2A = false;
3167 if (!skcms_MakeUsableAsDestination(&result)) {
1
Calling 'skcms_MakeUsableAsDestination'
3168 return false;
3169 }
3170
3171 // Of the three, pick the transfer function that best fits the other two.
3172 int best_tf = 0;
3173 float min_max_error = INFINITY_;
3174 for (int i = 0; i < 3; i++) {
3175 skcms_TransferFunction inv;
3176 if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
3177 return false;
3178 }
3179
3180 float err = 0;
3181 for (int j = 0; j < 3; ++j) {
3182 err = fmaxf_(err, skcms_MaxRoundtripError(&profile->trc[j], &inv));
3183 }
3184 if (min_max_error > err) {
3185 min_max_error = err;
3186 best_tf = i;
3187 }
3188 }
3189
3190 for (int i = 0; i < 3; i++) {
3191 result.trc[i].parametric = result.trc[best_tf].parametric;
3192 }
3193
3194 *profile = result;
3195 assert_usable_as_destination(profile);
3196 return true;
3197}