Fix up transfer function symmetry

The easiest things trigger the silliest mistakes. Add tests
for various properties we want our transfer functions to have,
such as:
- be inverse of each other
- stay within the defined ranges
- by symmetric around 0
This commit is contained in:
Matthias Clasen 2024-08-14 08:01:05 -04:00
parent 0f85a40a25
commit f62606105e
2 changed files with 79 additions and 17 deletions

View File

@ -30,7 +30,7 @@ static inline float
srgb_oetf (float v)
{
if (fabsf (v) > 0.0031308f)
return 1.055f * sign (v) * powf (fabsf (v), 1.f / 2.4f) - 0.055f;
return sign (v) * (1.055f * powf (fabsf (v), 1.f / 2.4f) - 0.055f);
else
return 12.92f * v;
}
@ -118,7 +118,7 @@ bt709_oetf (float v)
if (fabsf (v) < b)
return v * 4.5f;
else
return a * sign (v) * powf (fabsf (v), 0.45f) - (a - 1);
return sign (v) * (a * powf (fabsf (v), 0.45f) - (a - 1));
}
static inline float
@ -128,10 +128,10 @@ hlg_eotf (float v)
const float b = 0.28466892;
const float c = 0.55991073;
if (v <= 0.5)
return (v * v) / 3;
if (fabsf (v) <= 0.5)
return sign (v) * (v * v) / 3;
else
return (expf ((v - c) / a) + b) / 12.0;
return sign (v) * (expf ((fabsf (v) - c) / a) + b) / 12.0;
}
static inline float
@ -141,10 +141,10 @@ hlg_oetf (float v)
const float b = 0.28466892;
const float c = 0.55991073;
if (v <= 1/12.0)
return sqrtf (3 * v);
if (fabsf (v) <= 1/12.0)
return sign (v) * sqrtf (3 * fabsf (v));
else
return a * logf (12 * v - b) + c;
return sign (v) * (a * logf (12 * fabsf (v) - b) + c);
}
/* See http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html

View File

@ -12,30 +12,84 @@ typedef struct
const char *name;
TransferFunc oetf;
TransferFunc eotf;
float o_range[2];
float e_range[2];
} TransferTest;
TransferTest transfers[] = {
{ "srgb", srgb_oetf, srgb_eotf },
{ "pq", pq_oetf, pq_eotf },
{ "bt709", bt709_oetf, bt709_eotf },
{ "hlg", hlg_oetf, hlg_eotf },
{ "gamma22", gamma22_oetf, gamma22_eotf },
{ "gamma28", gamma28_oetf, gamma28_eotf },
{ "srgb", srgb_oetf, srgb_eotf, { 0, 1 }, { 0, 1} },
{ "pq", pq_oetf, pq_eotf, { 0, 49.2610855 }, { 0, 1 } },
{ "bt709", bt709_oetf, bt709_eotf, { 0, 1 }, { 0, 1 } },
{ "hlg", hlg_oetf, hlg_eotf, { 0, 1}, { 0, 1} },
{ "gamma22", gamma22_oetf, gamma22_eotf, { 0, 1 }, { 0, 1 } },
{ "gamma28", gamma28_oetf, gamma28_eotf, { 0, 1 }, { 0, 1 } },
};
#define LERP(t, a, b) ((a) + (t) * ((b) - (a)))
#define ASSERT_IN_RANGE(v, a, b, epsilon) \
g_assert_cmpfloat_with_epsilon (MIN(v,a), a, epsilon); \
g_assert_cmpfloat_with_epsilon (MAX(v,b), b, epsilon); \
static void
test_transfer (gconstpointer data)
{
TransferTest *transfer = (TransferTest *) data;
float v, v1, v2;
for (int i = 0; i < 1000; i++)
for (int i = 0; i < 1001; i++)
{
float v = i / 1000.0;
float v2 = transfer->oetf (transfer->eotf (v));
v = LERP (i/1000.0, transfer->e_range[0], transfer->e_range[1]);
v1 = transfer->eotf (v);
ASSERT_IN_RANGE (v1, transfer->o_range[0], transfer->o_range[1], 0.0001);
v2 = transfer->oetf (v1);
g_assert_cmpfloat_with_epsilon (v, v2, 0.05);
}
for (int i = 0; i < 1001; i++)
{
v = LERP (i/1000.0, transfer->o_range[0], transfer->o_range[1]);
v1 = transfer->oetf (v);
ASSERT_IN_RANGE (v1, transfer->e_range[0], transfer->e_range[1], 0.0001);
v2 = transfer->eotf (v1);
g_assert_cmpfloat_with_epsilon (v, v2, 0.05);
}
}
static void
test_transfer_symmetry (gconstpointer data)
{
TransferTest *transfer = (TransferTest *) data;
float v, v1, v2;
for (int i = 0; i < 11; i++)
{
v = LERP (i/10.0, transfer->e_range[0], transfer->e_range[1]);
v1 = transfer->eotf (v);
v2 = -transfer->eotf (-v);
g_assert_cmpfloat_with_epsilon (v1, v2, 0.05);
}
for (int i = 0; i < 11; i++)
{
v = LERP (i/10.0, transfer->o_range[0], transfer->o_range[1]);
v1 = transfer->oetf (v);
v2 = -transfer->oetf (-v);
g_assert_cmpfloat_with_epsilon (v1, v2, 0.05);
}
}
typedef struct
{
const char *name;
@ -171,6 +225,14 @@ main (int argc, char *argv[])
g_free (path);
}
for (guint i = 0; i < G_N_ELEMENTS (transfers); i++)
{
TransferTest *test = &transfers[i];
char *path = g_strdup_printf ("/colorstate/transfer-symmetry/%s", test->name);
g_test_add_data_func (path, test, test_transfer_symmetry);
g_free (path);
}
for (guint i = 0; i < G_N_ELEMENTS (matrices); i++)
{
MatrixTest *test = &matrices[i];