2025-05-12 14:47:27 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright 2025 Collabora, Ltd.
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
|
* a copy of this software and associated documentation files (the
|
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
|
* the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice (including the
|
|
|
|
|
* next paragraph) shall be included in all copies or substantial
|
|
|
|
|
* portions of the Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
* SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
|
|
#include "weston-test-client-helper.h"
|
|
|
|
|
#include "weston-test-assert.h"
|
|
|
|
|
#include "libweston/color.h"
|
|
|
|
|
#include "shared/helpers.h"
|
2025-04-29 17:25:10 +03:00
|
|
|
#include <libweston/colorimetry.h>
|
2025-05-12 14:47:27 -03:00
|
|
|
|
|
|
|
|
float lut_ascendent[] =
|
|
|
|
|
{0.0, 2.0, 3.0, 6.0, 9.0, 12.0, 15.0, 16.0, 20.0, 25.0};
|
|
|
|
|
|
|
|
|
|
float lut_descendent[] =
|
|
|
|
|
{25.0, 20.0, 16.0, 15.0, 12.0, 9.0, 6.0, 3.0, 2.0, 0.0};
|
|
|
|
|
|
|
|
|
|
struct test_case {
|
|
|
|
|
float *lut;
|
|
|
|
|
uint32_t len_lut;
|
|
|
|
|
float val;
|
|
|
|
|
uint32_t index_A;
|
|
|
|
|
uint32_t index_B;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct test_case tests[] = {
|
|
|
|
|
{
|
|
|
|
|
/* Value at the extreme left. */
|
|
|
|
|
.lut = lut_ascendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_ascendent),
|
|
|
|
|
.val = 0.0f,
|
|
|
|
|
.index_A = 0,
|
|
|
|
|
.index_B = 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value at the extreme right. */
|
|
|
|
|
.lut = lut_ascendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_ascendent),
|
|
|
|
|
.val = 25.0f,
|
|
|
|
|
.index_A = 8,
|
|
|
|
|
.index_B = 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Just a value that is present in the LUT. */
|
|
|
|
|
.lut = lut_ascendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_ascendent),
|
|
|
|
|
.val = 6.0f,
|
|
|
|
|
.index_A = 2,
|
|
|
|
|
.index_B = 3,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value not present on LUT. */
|
|
|
|
|
.lut = lut_ascendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_ascendent),
|
|
|
|
|
.val = 10.0f,
|
|
|
|
|
.index_A = 4,
|
|
|
|
|
.index_B = 5,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Another value not present on LUT. */
|
|
|
|
|
.lut = lut_ascendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_ascendent),
|
|
|
|
|
.val = 1.0f,
|
|
|
|
|
.index_A = 0,
|
|
|
|
|
.index_B = 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Another value not present on LUT. */
|
|
|
|
|
.lut = lut_ascendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_ascendent),
|
|
|
|
|
.val = 23.0f,
|
|
|
|
|
.index_A = 8,
|
|
|
|
|
.index_B = 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value that would be before the extreme left, but not present. */
|
|
|
|
|
.lut = lut_ascendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_ascendent),
|
|
|
|
|
.val = -1.0f,
|
|
|
|
|
.index_A = 0,
|
|
|
|
|
.index_B = 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value that would be after the extreme right, but not present. */
|
|
|
|
|
.lut = lut_ascendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_ascendent),
|
|
|
|
|
.val = 26.0f,
|
|
|
|
|
.index_A = 8,
|
|
|
|
|
.index_B = 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value at the extreme left. */
|
|
|
|
|
.lut = lut_descendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_descendent),
|
|
|
|
|
.val = 25.0f,
|
|
|
|
|
.index_A = 0,
|
|
|
|
|
.index_B = 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value at the extreme right. */
|
|
|
|
|
.lut = lut_descendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_descendent),
|
|
|
|
|
.val = 0.0f,
|
|
|
|
|
.index_A = 8,
|
|
|
|
|
.index_B = 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Just a value that is present in the LUT. */
|
|
|
|
|
.lut = lut_descendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_descendent),
|
|
|
|
|
.val = 6.0f,
|
|
|
|
|
.index_A = 5,
|
|
|
|
|
.index_B = 6,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value not present on LUT. */
|
|
|
|
|
.lut = lut_descendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_descendent),
|
|
|
|
|
.val = 10.0f,
|
|
|
|
|
.index_A = 4,
|
|
|
|
|
.index_B = 5,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Another value not present on LUT. */
|
|
|
|
|
.lut = lut_descendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_descendent),
|
|
|
|
|
.val = 1.0f,
|
|
|
|
|
.index_A = 8,
|
|
|
|
|
.index_B = 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Another value not present on LUT. */
|
|
|
|
|
.lut = lut_descendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_descendent),
|
|
|
|
|
.val = 23.0f,
|
|
|
|
|
.index_A = 0,
|
|
|
|
|
.index_B = 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value that would be before the extreme right, but not present. */
|
|
|
|
|
.lut = lut_descendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_descendent),
|
|
|
|
|
.val = -1.0f,
|
|
|
|
|
.index_A = 8,
|
|
|
|
|
.index_B = 9,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* Value that would be after the extreme left, but not present. */
|
|
|
|
|
.lut = lut_descendent,
|
|
|
|
|
.len_lut = ARRAY_LENGTH(lut_descendent),
|
|
|
|
|
.val = 26.0f,
|
|
|
|
|
.index_A = 0,
|
|
|
|
|
.index_B = 1,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TEST(find_neighbors_test)
|
|
|
|
|
{
|
|
|
|
|
struct weston_compositor *compositor = NULL;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
uint32_t index_neigh_A, index_neigh_B;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(tests); i++) {
|
|
|
|
|
find_neighbors(compositor, tests[i].len_lut, tests[i].lut,
|
|
|
|
|
tests[i].val, &index_neigh_A, &index_neigh_B);
|
|
|
|
|
|
|
|
|
|
test_assert_u32_eq(index_neigh_A, tests[i].index_A);
|
|
|
|
|
test_assert_u32_eq(index_neigh_B, tests[i].index_B);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RESULT_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static float
|
|
|
|
|
sample_power_22(float input)
|
|
|
|
|
{
|
|
|
|
|
return powf(input, 2.2f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static float
|
|
|
|
|
sample_power_22_complement(float input)
|
|
|
|
|
{
|
|
|
|
|
return 1.0f - powf(input, 2.2f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum test_result_code
|
|
|
|
|
test_inverse_lut_with_curve(float (*sample_fn)(float))
|
|
|
|
|
{
|
|
|
|
|
const uint32_t len_lut = 1024;
|
|
|
|
|
struct weston_compositor *compositor = NULL;
|
|
|
|
|
float lut[len_lut];
|
|
|
|
|
uint32_t divider = len_lut - 1;
|
|
|
|
|
float input, output;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
|
|
/* Create 1D LUT using function sample_fn. */
|
|
|
|
|
for (i = 0; i < len_lut; i++) {
|
|
|
|
|
input = (float)i / divider;
|
|
|
|
|
lut[i] = sample_fn(input);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sample data (dividing i by a prime number, 79) not well behaved on
|
|
|
|
|
* purpose. Sample that with the LUT, then its inverse, and ensure that
|
|
|
|
|
* the result is the same as evaluating with an identity curve.
|
|
|
|
|
*/
|
|
|
|
|
for (i = 0; i < 80; i++) {
|
|
|
|
|
input = i / 79.0f;
|
|
|
|
|
output = sample_fn(input);
|
|
|
|
|
output = weston_inverse_evaluate_lut1d(compositor,
|
|
|
|
|
len_lut, lut,
|
|
|
|
|
output);
|
|
|
|
|
test_assert_f32_lt(fabs(input - output), 1e-3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RESULT_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(inverse_lut)
|
|
|
|
|
{
|
|
|
|
|
return test_inverse_lut_with_curve(sample_power_22);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(inverse_lut_descendant)
|
|
|
|
|
{
|
|
|
|
|
return test_inverse_lut_with_curve(sample_power_22_complement);
|
|
|
|
|
}
|
2025-04-29 17:25:10 +03:00
|
|
|
|
|
|
|
|
struct npm_test_case {
|
|
|
|
|
struct weston_color_gamut gm;
|
|
|
|
|
struct weston_mat3f expected;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The reference data is from https://www.colour-science.org/ Python library.
|
|
|
|
|
* >>> import colour
|
|
|
|
|
* We use the "Derived NPM" as the expected matrix.
|
|
|
|
|
*/
|
|
|
|
|
static const struct npm_test_case npm_test_cases[] = {
|
|
|
|
|
{
|
|
|
|
|
/* >>> print(colour.RGB_COLOURSPACES['sRGB']) */
|
|
|
|
|
.gm = {
|
|
|
|
|
.primary = { { 0.64, 0.33 }, /* RGB order */
|
|
|
|
|
{ 0.30, 0.60 },
|
|
|
|
|
{ 0.15, 0.06 },
|
|
|
|
|
},
|
|
|
|
|
.white_point = { 0.3127, 0.3290 },
|
|
|
|
|
},
|
|
|
|
|
.expected = WESTON_MAT3F(
|
|
|
|
|
0.4123908, 0.35758434, 0.18048079,
|
|
|
|
|
0.21263901, 0.71516868, 0.07219232,
|
|
|
|
|
0.01933082, 0.11919478, 0.95053215
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* >>> print(colour.RGB_COLOURSPACES['Adobe RGB (1998)']) */
|
|
|
|
|
.gm = {
|
|
|
|
|
.primary = { { 0.64, 0.33 }, /* RGB order */
|
|
|
|
|
{ 0.21, 0.71 },
|
|
|
|
|
{ 0.15, 0.06 },
|
|
|
|
|
},
|
|
|
|
|
.white_point = { 0.3127, 0.3290 },
|
|
|
|
|
},
|
|
|
|
|
.expected = WESTON_MAT3F(
|
|
|
|
|
0.57666904, 0.18555824, 0.18822865,
|
|
|
|
|
0.29734498, 0.62736357, 0.07529146,
|
|
|
|
|
0.02703136, 0.07068885, 0.99133754
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* >>> print(colour.RGB_COLOURSPACES['ITU-R BT.2020']) */
|
|
|
|
|
.gm = {
|
|
|
|
|
.primary = { { 0.708, 0.292 }, /* RGB order */
|
|
|
|
|
{ 0.170, 0.797 },
|
|
|
|
|
{ 0.131, 0.046 },
|
|
|
|
|
},
|
|
|
|
|
.white_point = { 0.3127, 0.3290 },
|
|
|
|
|
},
|
|
|
|
|
.expected = WESTON_MAT3F(
|
|
|
|
|
6.36958048e-01, 1.44616904e-01, 1.68880975e-01,
|
|
|
|
|
2.62700212e-01, 6.77998072e-01, 5.93017165e-02,
|
|
|
|
|
4.99410657e-17, 2.80726930e-02, 1.06098506e+00
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
/* >>> print(colour.RGB_COLOURSPACES['NTSC (1953)']) */
|
|
|
|
|
.gm = {
|
|
|
|
|
.primary = { { 0.67, 0.33 }, /* RGB order */
|
|
|
|
|
{ 0.21, 0.71 },
|
|
|
|
|
{ 0.14, 0.08 },
|
|
|
|
|
},
|
|
|
|
|
.white_point = { 0.31006, 0.31616 },
|
|
|
|
|
},
|
|
|
|
|
.expected = WESTON_MAT3F(
|
|
|
|
|
6.06863809e-01, 1.73507281e-01, 2.00334881e-01,
|
|
|
|
|
2.98903070e-01, 5.86619855e-01, 1.14477075e-01,
|
|
|
|
|
-5.02801622e-17, 6.60980118e-02, 1.11615148e+00
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Return the equivalence precision in bits
|
|
|
|
|
*
|
|
|
|
|
* Infinity norm of the residual is our measure.
|
|
|
|
|
* See https://gitlab.freedesktop.org/pq/fourbyfour/-/blob/master/README.d/precision_testing.md
|
|
|
|
|
*/
|
|
|
|
|
static float
|
|
|
|
|
diff_precision(struct weston_mat3f M, struct weston_mat3f ref)
|
|
|
|
|
{
|
|
|
|
|
struct weston_mat3f E = weston_m3f_sub_m3f(M, ref);
|
|
|
|
|
return -log2f(weston_m3f_inf_norm(E));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Test that weston_normalized_primary_matrix() produces known-good results
|
|
|
|
|
* for NPM, an that the NPM⁻¹ is actually the inverse matrix.
|
|
|
|
|
*/
|
|
|
|
|
TEST_P(npm, npm_test_cases)
|
|
|
|
|
{
|
|
|
|
|
const float precision_bits = 21;
|
|
|
|
|
const struct npm_test_case *t = data;
|
|
|
|
|
struct weston_mat3f npm;
|
|
|
|
|
struct weston_mat3f npm_inv;
|
|
|
|
|
struct weston_mat3f roundtrip;
|
|
|
|
|
|
|
|
|
|
test_assert_true(weston_normalized_primary_matrix_init(&npm, &t->gm, WESTON_NPM_FORWARD));
|
|
|
|
|
test_assert_f32_ge(diff_precision(npm, t->expected), precision_bits);
|
|
|
|
|
|
|
|
|
|
test_assert_true(weston_normalized_primary_matrix_init(&npm_inv, &t->gm, WESTON_NPM_INVERSE));
|
|
|
|
|
roundtrip = weston_m3f_mul_m3f(npm_inv, npm);
|
|
|
|
|
test_assert_f32_ge(diff_precision(roundtrip, WESTON_MAT3F_IDENTITY), precision_bits);
|
|
|
|
|
|
|
|
|
|
return RESULT_OK;
|
|
|
|
|
}
|