Make transfer function classification part of the public API
Change-Id: I9580a89ab6211b411c3e741b16cd6d0980b806b2
Reviewed-on: https://skia-review.googlesource.com/c/skcms/+/616520
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/skcms.cc b/skcms.cc
index 4db244d..c1818c7 100644
--- a/skcms.cc
+++ b/skcms.cc
@@ -130,28 +130,39 @@
// 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,K_minus_1; };
// We didn't originally support a scale factor K for HLG, and instead just stored 0 in
// the unused `f` field of skcms_TransferFunction for HLGish and HLGInvish transfer functions.
// By storing f=K-1, those old unusued f=0 values now mean K=1, a noop scale factor.
-static float TFKind_marker(TFKind kind) {
+static float TFKind_marker(skcms_TFType 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) {
+static skcms_TFType classify(const skcms_TransferFunction& tf, TF_PQish* pq = nullptr
+ , TF_HLGish* hlg = nullptr) {
if (tf.g < 0 && static_cast<float>(static_cast<int>(tf.g)) == tf.g) {
// TODO: soundness 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;
+ case -skcms_TFType_PQish:
+ if (pq) {
+ memcpy(pq , &tf.a, sizeof(*pq ));
+ }
+ return skcms_TFType_PQish;
+ case -skcms_TFType_HLGish:
+ if (hlg) {
+ memcpy(hlg, &tf.a, sizeof(*hlg));
+ }
+ return skcms_TFType_HLGish;
+ case -skcms_TFType_HLGinvish:
+ if (hlg) {
+ memcpy(hlg, &tf.a, sizeof(*hlg));
+ }
+ return skcms_TFType_HLGinvish;
}
- return Bad;
+ return skcms_TFType_Invalid;
}
// Basic soundness checks for sRGBish transfer functions.
@@ -163,26 +174,29 @@
&& 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 skcms_TFType_sRGBish;
}
- return Bad;
+ return skcms_TFType_Invalid;
}
+skcms_TFType skcms_TransferFunction_getType(const skcms_TransferFunction* tf) {
+ return classify(*tf);
+}
bool skcms_TransferFunction_isSRGBish(const skcms_TransferFunction* tf) {
- return classify(*tf) == sRGBish;
+ return classify(*tf) == skcms_TFType_sRGBish;
}
bool skcms_TransferFunction_isPQish(const skcms_TransferFunction* tf) {
- return classify(*tf) == PQish;
+ return classify(*tf) == skcms_TFType_PQish;
}
bool skcms_TransferFunction_isHLGish(const skcms_TransferFunction* tf) {
- return classify(*tf) == HLGish;
+ return classify(*tf) == skcms_TFType_HLGish;
}
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 };
+ *tf = { TFKind_marker(skcms_TFType_PQish), A,B,C,D,E,F };
assert(skcms_TransferFunction_isPQish(tf));
return true;
}
@@ -190,7 +204,7 @@
bool skcms_TransferFunction_makeScaledHLGish(skcms_TransferFunction* tf,
float K, float R, float G,
float a, float b, float c) {
- *tf = { TFKind_marker(HLGish), R,G, a,b,c, K-1.0f };
+ *tf = { TFKind_marker(skcms_TFType_HLGish), R,G, a,b,c, K-1.0f };
assert(skcms_TransferFunction_isHLGish(tf));
return true;
}
@@ -202,29 +216,29 @@
TF_PQish pq;
TF_HLGish hlg;
switch (classify(*tf, &pq, &hlg)) {
- case Bad: break;
+ case skcms_TFType_Invalid: break;
- case HLGish: {
+ case skcms_TFType_HLGish: {
const float K = hlg.K_minus_1 + 1.0f;
return K * sign * (x*hlg.R <= 1 ? powf_(x*hlg.R, hlg.G)
: expf_((x-hlg.c)*hlg.a) + hlg.b);
}
// skcms_TransferFunction_invert() inverts R, G, and a for HLGinvish so this math is fast.
- case HLGinvish: {
+ case skcms_TFType_HLGinvish: {
const float K = hlg.K_minus_1 + 1.0f;
x /= K;
return sign * (x <= 1 ? hlg.R * powf_(x, hlg.G)
: hlg.a * logf_(x - hlg.b) + hlg.c);
}
+ case skcms_TFType_sRGBish:
+ return sign * (x < tf->d ? tf->c * x + tf->f
+ : powf_(tf->a * x + tf->b, tf->g) + tf->e);
- 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);
+ case skcms_TFType_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;
}
@@ -1850,28 +1864,28 @@
TF_PQish pq;
TF_HLGish hlg;
switch (classify(*src, &pq, &hlg)) {
- case Bad: return false;
- case sRGBish: break; // handled below
+ case skcms_TFType_Invalid: return false;
+ case skcms_TFType_sRGBish: break; // handled below
- case PQish:
- *dst = { TFKind_marker(PQish), -pq.A, pq.D, 1.0f/pq.F
- , pq.B, -pq.E, 1.0f/pq.C};
+ case skcms_TFType_PQish:
+ *dst = { TFKind_marker(skcms_TFType_PQish), -pq.A, pq.D, 1.0f/pq.F
+ , pq.B, -pq.E, 1.0f/pq.C};
return true;
- case HLGish:
- *dst = { TFKind_marker(HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
- , 1.0f/hlg.a, hlg.b, hlg.c
- , hlg.K_minus_1 };
+ case skcms_TFType_HLGish:
+ *dst = { TFKind_marker(skcms_TFType_HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
+ , 1.0f/hlg.a, hlg.b, hlg.c
+ , hlg.K_minus_1 };
return true;
- case HLGinvish:
- *dst = { TFKind_marker(HLGish), 1.0f/hlg.R, 1.0f/hlg.G
- , 1.0f/hlg.a, hlg.b, hlg.c
- , hlg.K_minus_1 };
+ case skcms_TFType_HLGinvish:
+ *dst = { TFKind_marker(skcms_TFType_HLGish), 1.0f/hlg.R, 1.0f/hlg.G
+ , 1.0f/hlg.a, hlg.b, hlg.c
+ , hlg.K_minus_1 };
return true;
}
- assert (classify(*src) == sRGBish);
+ assert (classify(*src) == skcms_TFType_sRGBish);
// We're inverting this function, solving for x in terms of y.
// y = (cx + f) x < d
@@ -1932,7 +1946,7 @@
// That should usually make classify(inv) == sRGBish true, but there are a couple situations
// where we might still fail here, like non-finite parameter values.
- if (classify(inv) != sRGBish) {
+ if (classify(inv) != skcms_TFType_sRGBish) {
return false;
}
@@ -1956,7 +1970,7 @@
}
*dst = inv;
- return classify(*dst) == sRGBish;
+ return classify(*dst) == skcms_TFType_sRGBish;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
@@ -2110,7 +2124,7 @@
static float max_roundtrip_error_checked(const skcms_Curve* curve,
const skcms_TransferFunction* tf_inv) {
skcms_TransferFunction tf;
- if (!skcms_TransferFunction_invert(tf_inv, &tf) || sRGBish != classify(tf)) {
+ if (!skcms_TransferFunction_invert(tf_inv, &tf) || skcms_TFType_sRGBish != classify(tf)) {
return INFINITY_;
}
@@ -2257,7 +2271,7 @@
// Other non-Bad TFs would be fine, but we know we've only ever tried to fit sRGBish;
// anything else is just some accident of math and the way we pun tf.g as a type flag.
// fit_nonlinear() should guarantee this, but the special cases may fail this test.
- if (sRGBish != classify(tf)) {
+ if (skcms_TFType_sRGBish != classify(tf)) {
continue;
}
@@ -2550,11 +2564,11 @@
}
switch (classify(tf)) {
- case Bad: return noop;
- case sRGBish: return OpAndArg{op.sRGBish, &tf};
- case PQish: return OpAndArg{op.PQish, &tf};
- case HLGish: return OpAndArg{op.HLGish, &tf};
- case HLGinvish: return OpAndArg{op.HLGinvish, &tf};
+ case skcms_TFType_Invalid: return noop;
+ case skcms_TFType_sRGBish: return OpAndArg{op.sRGBish, &tf};
+ case skcms_TFType_PQish: return OpAndArg{op.PQish, &tf};
+ case skcms_TFType_HLGish: return OpAndArg{op.HLGish, &tf};
+ case skcms_TFType_HLGinvish: return OpAndArg{op.HLGinvish, &tf};
}
}
return OpAndArg{op.table, curve};
diff --git a/skcms.h b/skcms.h
index 2ee5693..1510a13 100644
--- a/skcms.h
+++ b/skcms.h
@@ -51,6 +51,17 @@
SKCMS_API bool skcms_TransferFunction_invert(const skcms_TransferFunction*,
skcms_TransferFunction*);
+typedef enum skcms_TFType {
+ skcms_TFType_Invalid,
+ skcms_TFType_sRGBish,
+ skcms_TFType_PQish,
+ skcms_TFType_HLGish,
+ skcms_TFType_HLGinvish,
+} skcms_TFType;
+
+// Identify which kind of transfer function is encoded in an skcms_TransferFunction
+SKCMS_API skcms_TFType skcms_TransferFunction_getType(const skcms_TransferFunction*);
+
// We can jam a couple alternate transfer function forms into skcms_TransferFunction,
// including those matching the general forms of the SMPTE ST 2084 PQ function or HLG.
//