diff --git a/include/third_party/skcms/skcms.h b/include/third_party/skcms/skcms.h index c316385b5d..bd3a96e642 100644 --- a/include/third_party/skcms/skcms.h +++ b/include/third_party/skcms/skcms.h @@ -51,6 +51,50 @@ SKCMS_API float skcms_TransferFunction_eval (const skcms_TransferFunction*, flo SKCMS_API bool skcms_TransferFunction_invert(const skcms_TransferFunction*, skcms_TransferFunction*); +// We can jam a couple alternate transfer function forms into skcms_TransferFunction, +// including those matching the general form of the SMPTE ST 2084 PQ function and its inverse: +// +// max(A + B|x|^C, 0) +// tf(x) = sign(x) * (------------------) ^ F + // (D + E|x|^C) +SKCMS_API bool skcms_TransferFunction_makePQish(skcms_TransferFunction*, + float A, float B, float C, + float D, float E, float F); + +static inline bool skcms_TransferFunction_makePQ(skcms_TransferFunction* tf) { + return skcms_TransferFunction_makePQish(tf, -107/128.0f, 1.0f, 32/2523.0f + , 2413/128.0f, -2392/128.0f, 8192/1305.0f); +} +static inline bool skcms_TransferFunction_makePQinv(skcms_TransferFunction* tf) { + return skcms_TransferFunction_makePQish(tf, 107/128.0f, 2413/128.0f, 1305/8192.0f + , 1.0f, 2392/128.0f, 2523/ 32.0f); +} + +// skcms_TransferFunction also supports functions of the form of HLG's inverse... +// +// { sign(linear) * ( R|linear|^G ) when 0 <= |linear| <= 1 +// encoded = { sign(linear) * ( a ln(|linear|-b) + c ) when 1 < |linear| +SKCMS_API bool skcms_TransferFunction_makeHLGinvish(skcms_TransferFunction*, + float R, float G, + float a, float b, float c); + +// ... and of the form of HLG itself, factored to take the same five parameters. +// +// { sign(encoded) * ( (|encoded|/R)^(1/G) ) when 0 <= |encoded| <= R +// linear = { sign(encoded) * ( e^( (|encoded|-c)/a ) + b ) when R < |encoded| +SKCMS_API bool skcms_TransferFunction_makeHLGish(skcms_TransferFunction*, + float R, float G, + float a, float b, float c); + +static inline bool skcms_TransferFunction_makeHLG(skcms_TransferFunction* tf) { + return skcms_TransferFunction_makeHLGish(tf, + 0.5f, 0.5f , 0.17883277f, 0.28466892f, 0.55991073f); +} +static inline bool skcms_TransferFunction_makeHLGinv(skcms_TransferFunction* tf) { + return skcms_TransferFunction_makeHLGinvish(tf, + 0.5f, 0.5f , 0.17883277f, 0.28466892f, 0.55991073f); +} + // Unified representation of 'curv' or 'para' tag data, or a 1D table from 'mft1' or 'mft2' typedef union skcms_Curve { struct { diff --git a/third_party/skcms/skcms.cc b/third_party/skcms/skcms.cc index 2a86753b3e..587671b149 100644 --- a/third_party/skcms/skcms.cc +++ b/third_party/skcms/skcms.cc @@ -61,6 +61,80 @@ static float minus_1_ulp(float x) { return x; } +// Most transfer functions we work with are sRGBish. +// For exotic HDR transfer functions, we encode them using a tf.g that makes no sense, +// and repurpose the other fields to hold the parameters of the HDR functions. +enum TFKind { Bad, sRGBish, PQish, HLGish, HLGinvish }; +struct TF_PQish { float A,B,C,D,E,F; }; +struct TF_HLGish { float R,G,a,b,c; }; + +static float TFKind_marker(TFKind kind) { + // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM. + return -(float)kind; +} + +static TFKind classify(const skcms_TransferFunction& tf, TF_PQish* pq = nullptr + , TF_HLGish* hlg = nullptr) { + if (tf.g < 0 && (int)tf.g == tf.g) { + // TODO: sanity checks for PQ/HLG like we do for sRGBish. + switch (-(int)tf.g) { + case PQish: if (pq ) { memcpy(pq , &tf.a, sizeof(*pq )); } return PQish; + case HLGish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGish; + case HLGinvish: if (hlg) { memcpy(hlg, &tf.a, sizeof(*hlg)); } return HLGinvish; + } + return Bad; + } + + // Basic sanity checks for sRGBish transfer functions. + if (isfinitef_(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g) + // a,c,d,g should be non-negative to make any sense. + && tf.a >= 0 + && tf.c >= 0 + && tf.d >= 0 + && tf.g >= 0 + // Raising a negative value to a fractional tf->g produces complex numbers. + && tf.a * tf.d + tf.b >= 0) { + return sRGBish; + } + + return Bad; +} + +// TODO: temporary shim for old call sites +static bool tf_is_valid(const skcms_TransferFunction* tf) { + return classify(*tf) == sRGBish; +} + + +bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf, + float A, float B, float C, + float D, float E, float F) { + *tf = { TFKind_marker(PQish), A,B,C,D,E,F }; + assert(classify(*tf) == PQish); + return true; +} + +float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) { + float sign = x < 0 ? -1.0f : 1.0f; + x *= sign; + + TF_PQish pq; + switch (classify(*tf, &pq)) { + case Bad: break; + case HLGish: break; + case HLGinvish: break; + + case sRGBish: return sign * (x < tf->d ? tf->c * x + tf->f + : powf_(tf->a * x + tf->b, tf->g) + tf->e); + + case PQish: return sign * powf_(fmaxf_(pq.A + pq.B * powf_(x, pq.C), 0) + / (pq.D + pq.E * powf_(x, pq.C)), + pq.F); + } + return 0; +} + + static float eval_curve(const skcms_Curve* curve, float x) { if (curve->table_entries == 0) { return skcms_TransferFunction_eval(&curve->parametric, x); @@ -255,25 +329,6 @@ static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ, read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]); } -static bool tf_is_valid(const skcms_TransferFunction* tf) { - // Reject obviously malformed inputs - if (!isfinitef_(tf->a + tf->b + tf->c + tf->d + tf->e + tf->f + tf->g)) { - return false; - } - - // All of these parameters should be non-negative - if (tf->a < 0 || tf->c < 0 || tf->d < 0 || tf->g < 0) { - return false; - } - - // It's rather _complex_ to raise a negative number to a fractional power tf->g. - if (tf->a * tf->d + tf->b < 0) { - return false; - } - - return true; -} - typedef struct { uint8_t type [4]; uint8_t reserved_a [4]; @@ -1419,14 +1474,6 @@ float powf_(float x, float y) { : exp2f_(log2f_(x) * y); } -float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) { - float sign = x < 0 ? -1.0f : 1.0f; - x *= sign; - - return sign * (x < tf->d ? tf->c * x + tf->f - : powf_(tf->a * x + tf->b, tf->g) + tf->e); -} - #if defined(__clang__) [[clang::no_sanitize("float-divide-by-zero")]] // Checked for by tf_is_valid() on the way out. #endif diff --git a/third_party/skcms/version.sha1 b/third_party/skcms/version.sha1 index 9848dff99f..bdf8f83534 100755 --- a/third_party/skcms/version.sha1 +++ b/third_party/skcms/version.sha1 @@ -1 +1 @@ -5c593bf702db07bdd782b58f9bde28e8ee67061b \ No newline at end of file +a52db47aa53f894d1fd478173c7b7c4eb0984f42 \ No newline at end of file