2022-01-26 03:26:03 +00:00
|
|
|
/*
|
|
|
|
* Copyright 2021 Advanced Micro Devices, Inc.
|
2022-06-13 11:34:19 +00:00
|
|
|
* Copyright 2020, 2022 Collabora, Ltd.
|
2022-01-26 03:26:03 +00:00
|
|
|
*
|
|
|
|
* 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"
|
|
|
|
|
2022-05-04 23:27:16 +00:00
|
|
|
#include <lcms2.h>
|
2022-01-26 03:26:03 +00:00
|
|
|
|
|
|
|
#include "weston-test-client-helper.h"
|
2022-06-13 10:45:58 +00:00
|
|
|
#include "image-iter.h"
|
2022-05-04 23:27:16 +00:00
|
|
|
#include "lcms_util.h"
|
2022-01-26 03:26:03 +00:00
|
|
|
|
|
|
|
static const int WINDOW_WIDTH = 256;
|
|
|
|
static const int WINDOW_HEIGHT = 24;
|
|
|
|
|
2022-05-01 17:57:10 +00:00
|
|
|
enum profile_type {
|
|
|
|
PTYPE_MATRIX_SHAPER,
|
|
|
|
PTYPE_CLUT,
|
2022-01-26 03:26:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Using currently destination gamut bigger than source.
|
|
|
|
* Using https://www.colour-science.org/ we can extract conversion matrix:
|
|
|
|
* import colour
|
|
|
|
* colour.matrix_RGB_to_RGB(colour.RGB_COLOURSPACES['sRGB'], colour.RGB_COLOURSPACES['Adobe RGB (1998)'], None)
|
|
|
|
* colour.matrix_RGB_to_RGB(colour.RGB_COLOURSPACES['sRGB'], colour.RGB_COLOURSPACES['ITU-R BT.2020'], None)
|
|
|
|
*/
|
2022-05-01 17:57:10 +00:00
|
|
|
|
|
|
|
const struct lcms_pipeline pipeline_sRGB = {
|
|
|
|
.color_space = "sRGB",
|
|
|
|
.prim_output = {
|
|
|
|
.Red = { 0.640, 0.330, 1.0 },
|
|
|
|
.Green = { 0.300, 0.600, 1.0 },
|
|
|
|
.Blue = { 0.150, 0.060, 1.0 }
|
2022-01-26 03:26:03 +00:00
|
|
|
},
|
2022-05-01 17:57:10 +00:00
|
|
|
.pre_fn = TRANSFER_FN_SRGB_EOTF,
|
|
|
|
.mat = LCMSMAT3(1.0, 0.0, 0.0,
|
|
|
|
0.0, 1.0, 0.0,
|
|
|
|
0.0, 0.0, 1.0),
|
2022-05-04 23:27:16 +00:00
|
|
|
.mat2XYZ = LCMSMAT3(0.436037, 0.385124, 0.143039,
|
|
|
|
0.222482, 0.716913, 0.060605,
|
|
|
|
0.013922, 0.097078, 0.713899),
|
2022-05-01 17:57:10 +00:00
|
|
|
.post_fn = TRANSFER_FN_SRGB_EOTF_INVERSE
|
|
|
|
};
|
|
|
|
|
|
|
|
const struct lcms_pipeline pipeline_adobeRGB = {
|
|
|
|
.color_space = "adobeRGB",
|
|
|
|
.prim_output = {
|
|
|
|
.Red = { 0.640, 0.330, 1.0 },
|
|
|
|
.Green = { 0.210, 0.710, 1.0 },
|
|
|
|
.Blue = { 0.150, 0.060, 1.0 }
|
2022-01-26 03:26:03 +00:00
|
|
|
},
|
2022-05-01 17:57:10 +00:00
|
|
|
.pre_fn = TRANSFER_FN_SRGB_EOTF,
|
|
|
|
.mat = LCMSMAT3( 0.715127, 0.284868, 0.000005,
|
|
|
|
0.000001, 0.999995, 0.000004,
|
|
|
|
-0.000003, 0.041155, 0.958848),
|
2022-05-04 23:27:16 +00:00
|
|
|
.mat2XYZ = LCMSMAT3(0.609740, 0.205279, 0.149181,
|
|
|
|
0.311111, 0.625681, 0.063208,
|
|
|
|
0.019469, 0.060879, 0.744552),
|
2022-05-01 17:57:10 +00:00
|
|
|
.post_fn = TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE
|
|
|
|
};
|
|
|
|
|
|
|
|
const struct lcms_pipeline pipeline_BT2020 = {
|
|
|
|
.color_space = "bt2020",
|
|
|
|
.prim_output = {
|
|
|
|
.Red = { 0.708, 0.292, 1.0 },
|
|
|
|
.Green = { 0.170, 0.797, 1.0 },
|
|
|
|
.Blue = { 0.131, 0.046, 1.0 }
|
|
|
|
},
|
|
|
|
.pre_fn = TRANSFER_FN_SRGB_EOTF,
|
|
|
|
.mat = LCMSMAT3(0.627402, 0.329292, 0.043306,
|
|
|
|
0.069095, 0.919544, 0.011360,
|
|
|
|
0.016394, 0.088028, 0.895578),
|
|
|
|
/* this is equivalent to BT.1886 with zero black level */
|
|
|
|
.post_fn = TRANSFER_FN_POWER2_4_EOTF_INVERSE,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct setup_args {
|
|
|
|
struct fixture_metadata meta;
|
2022-05-23 11:39:06 +00:00
|
|
|
int ref_image_index;
|
2022-05-01 17:57:10 +00:00
|
|
|
const struct lcms_pipeline *pipeline;
|
2022-06-17 14:22:55 +00:00
|
|
|
|
2022-05-01 17:57:10 +00:00
|
|
|
/**
|
2022-06-17 14:22:55 +00:00
|
|
|
* Two-norm color error tolerance in units of 1.0/255, computed in
|
|
|
|
* output electrical space.
|
2022-05-01 17:57:10 +00:00
|
|
|
*
|
|
|
|
* Tolerance depends more on the 1D LUT used for the
|
|
|
|
* inv EOTF than the tested 3D LUT size:
|
|
|
|
* 9x9x9, 17x17x17, 33x33x33, 127x127x127
|
|
|
|
*
|
|
|
|
* TODO: when we add power-law in the curve enumeration
|
|
|
|
* in GL-renderer, then we should fix the tolerance
|
|
|
|
* as the error should reduce a lot.
|
|
|
|
*/
|
2022-06-17 14:22:55 +00:00
|
|
|
float tolerance;
|
|
|
|
|
2022-05-01 17:57:10 +00:00
|
|
|
/**
|
|
|
|
* 3DLUT dimension size
|
|
|
|
*/
|
|
|
|
int dim_size;
|
|
|
|
enum profile_type type;
|
2022-05-04 23:27:16 +00:00
|
|
|
|
|
|
|
/** Two-norm error limit for cLUT DToB->BToD roundtrip */
|
|
|
|
float clut_roundtrip_tolerance;
|
2023-04-21 18:27:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* VCGT tag exponents for each channel. If any is zeroed, we ignore
|
|
|
|
* the VCGT tag.
|
|
|
|
*/
|
|
|
|
double vcgt_exponents[COLOR_CHAN_NUM];
|
2022-05-01 17:57:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct setup_args my_setup_args[] = {
|
2023-04-21 18:27:30 +00:00
|
|
|
/* name, ref img, pipeline, tolerance, dim, profile type, clut tolerance, vcgt_exponents */
|
|
|
|
{ { "sRGB->sRGB MAT" }, 0, &pipeline_sRGB, 0.0, 0, PTYPE_MATRIX_SHAPER },
|
|
|
|
{ { "sRGB->sRGB MAT VCGT" }, 3, &pipeline_sRGB, 0.8, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} },
|
|
|
|
{ { "sRGB->adobeRGB MAT" }, 1, &pipeline_adobeRGB, 1.4, 0, PTYPE_MATRIX_SHAPER },
|
|
|
|
{ { "sRGB->adobeRGB MAT VCGT" }, 4, &pipeline_adobeRGB, 1.0, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} },
|
|
|
|
{ { "sRGB->BT2020 MAT" }, 2, &pipeline_BT2020, 4.5, 0, PTYPE_MATRIX_SHAPER },
|
|
|
|
{ { "sRGB->sRGB CLUT" }, 0, &pipeline_sRGB, 0.0, 17, PTYPE_CLUT, 0.0005 },
|
|
|
|
{ { "sRGB->sRGB CLUT VCGT" }, 3, &pipeline_sRGB, 0.9, 17, PTYPE_CLUT, 0.0005, {1.1, 1.2, 1.3} },
|
|
|
|
{ { "sRGB->adobeRGB CLUT" }, 1, &pipeline_adobeRGB, 1.8, 17, PTYPE_CLUT, 0.0065 },
|
|
|
|
{ { "sRGB->adobeRGB CLUT VCGT" }, 4, &pipeline_adobeRGB, 1.1, 17, PTYPE_CLUT, 0.0065, {1.1, 1.2, 1.3} },
|
2022-01-26 03:26:03 +00:00
|
|
|
};
|
|
|
|
|
2022-05-04 23:27:16 +00:00
|
|
|
/*
|
|
|
|
* Originally the cLUT profile test attempted to use the AToB/BToA tags. Those
|
|
|
|
* come with serious limitations though: at most uint16 representation for
|
|
|
|
* values in a LUT which means LUT entry precision is limited and range is
|
|
|
|
* [0.0, 1.0]. This poses difficulties such as:
|
|
|
|
* - for AToB, the resulting PCS XYZ values may need to be > 1.0
|
|
|
|
* - for BToA, it is easy to fall outside of device color volume meaning that
|
|
|
|
* out-of-range values are needed in the 3D LUT
|
|
|
|
* Working around these could require offsetting and scaling of values
|
|
|
|
* before and after the 3D LUT, and even that may not always be possible.
|
|
|
|
*
|
|
|
|
* DToB/BToD tags do not have most of these problems, because there pipelines
|
|
|
|
* use float32 representation throughout. We have much more precision, and
|
|
|
|
* we can mostly use negative and greater than 1.0 values. LUT elements
|
|
|
|
* still clamp their input to [0.0, 1.0] before applying the LUT. This type of
|
|
|
|
* pipeline is called multiProcessElement (MPE).
|
|
|
|
*
|
|
|
|
* MPE also allows us to represent curves in a few analytical forms. These are
|
|
|
|
* just enough to represent the EOTF curves we have and their inverses, but
|
|
|
|
* they do not allow encoding extended EOTF curves or their inverses
|
|
|
|
* (defined for all real numbers by extrapolation, and mirroring for negative
|
|
|
|
* inputs). Using MPE curves we avoid the precision problems that arise from
|
|
|
|
* attempting to represent an inverse-EOTF as a LUT. For the precision issue,
|
|
|
|
* see: https://gitlab.freedesktop.org/pq/color-and-hdr/-/merge_requests/9
|
|
|
|
*
|
|
|
|
* MPE is not a complete remedy, because 3D LUT inputs are still always clamped
|
|
|
|
* to [0.0, 1.0]. Therefore a 3D LUT cannot represent the inverse of a matrix
|
|
|
|
* that can produce negative or greater than 1.0 values without further tricks
|
|
|
|
* (scaling and offsetting) in the pipeline. Rather than implementing that
|
|
|
|
* complication, we decided to just not test with such matrices. Therefore
|
|
|
|
* BT.2020 color space is not used in the cLUT test. AdobeRGB is enough.
|
|
|
|
*/
|
|
|
|
static cmsHPROFILE
|
2024-01-16 02:58:05 +00:00
|
|
|
build_lcms_profile_output(const struct setup_args *arg)
|
2022-05-01 17:57:10 +00:00
|
|
|
{
|
|
|
|
switch (arg->type) {
|
|
|
|
case PTYPE_MATRIX_SHAPER:
|
2024-01-16 02:58:05 +00:00
|
|
|
return build_lcms_matrix_shaper_profile_output(NULL,
|
|
|
|
arg->pipeline,
|
|
|
|
arg->vcgt_exponents);
|
2022-05-01 17:57:10 +00:00
|
|
|
case PTYPE_CLUT:
|
2024-01-16 02:58:05 +00:00
|
|
|
return build_lcms_clut_profile_output(NULL,
|
|
|
|
arg->pipeline,
|
|
|
|
arg->vcgt_exponents,
|
|
|
|
arg->dim_size,
|
|
|
|
arg->clut_roundtrip_tolerance);
|
2022-05-01 17:57:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2024-02-07 21:29:31 +00:00
|
|
|
static void
|
|
|
|
build_output_icc_profile(const struct setup_args *arg, const char *filename)
|
2022-01-26 03:26:03 +00:00
|
|
|
{
|
|
|
|
cmsHPROFILE profile = NULL;
|
|
|
|
bool saved;
|
|
|
|
|
2024-01-16 02:58:05 +00:00
|
|
|
profile = build_lcms_profile_output(arg);
|
2022-01-26 03:26:03 +00:00
|
|
|
assert(profile);
|
|
|
|
|
2024-02-07 21:29:31 +00:00
|
|
|
saved = cmsSaveProfileToFile(profile, filename);
|
2022-01-26 03:26:03 +00:00
|
|
|
assert(saved);
|
|
|
|
|
|
|
|
cmsCloseProfile(profile);
|
|
|
|
}
|
|
|
|
|
2022-05-04 23:27:16 +00:00
|
|
|
static void
|
|
|
|
test_lcms_error_logger(cmsContext context_id,
|
|
|
|
cmsUInt32Number error_code,
|
|
|
|
const char *text)
|
|
|
|
{
|
|
|
|
testlog("LittleCMS error: %s\n", text);
|
|
|
|
}
|
|
|
|
|
2022-01-26 03:26:03 +00:00
|
|
|
static enum test_result_code
|
|
|
|
fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg)
|
|
|
|
{
|
|
|
|
struct compositor_setup setup;
|
|
|
|
char *file_name;
|
|
|
|
|
2022-05-04 23:27:16 +00:00
|
|
|
cmsSetLogErrorHandler(test_lcms_error_logger);
|
|
|
|
|
2022-01-26 03:26:03 +00:00
|
|
|
compositor_setup_defaults(&setup);
|
2022-12-05 16:05:21 +00:00
|
|
|
setup.renderer = WESTON_RENDERER_GL;
|
2022-01-26 03:26:03 +00:00
|
|
|
setup.backend = WESTON_BACKEND_HEADLESS;
|
|
|
|
setup.width = WINDOW_WIDTH;
|
|
|
|
setup.height = WINDOW_HEIGHT;
|
|
|
|
setup.shell = SHELL_TEST_DESKTOP;
|
2023-04-06 14:09:17 +00:00
|
|
|
setup.logging_scopes = "log,color-lcms-profiles,color-lcms-transformations,color-lcms-optimizer";
|
2022-01-26 03:26:03 +00:00
|
|
|
|
2024-02-07 21:29:31 +00:00
|
|
|
file_name = output_filename_for_fixture(THIS_TEST_NAME, harness,
|
|
|
|
arg->meta.name, "icm");
|
|
|
|
build_output_icc_profile(arg, file_name);
|
2022-01-26 03:26:03 +00:00
|
|
|
|
|
|
|
weston_ini_setup(&setup,
|
|
|
|
cfgln("[core]"),
|
2022-10-10 13:19:22 +00:00
|
|
|
cfgln("output-decorations=true"),
|
2022-01-26 03:26:03 +00:00
|
|
|
cfgln("color-management=true"),
|
|
|
|
cfgln("[output]"),
|
|
|
|
cfgln("name=headless"),
|
|
|
|
cfgln("icc_profile=%s", file_name));
|
|
|
|
|
|
|
|
free(file_name);
|
|
|
|
|
|
|
|
return weston_test_harness_execute_as_client(harness, &setup);
|
|
|
|
}
|
2022-05-01 17:57:10 +00:00
|
|
|
DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta);
|
2022-01-26 03:26:03 +00:00
|
|
|
|
2022-06-08 12:30:42 +00:00
|
|
|
static void
|
2022-06-13 10:45:58 +00:00
|
|
|
gen_ramp_rgb(pixman_image_t *image, int bitwidth, int width_bar)
|
2022-06-08 12:30:42 +00:00
|
|
|
{
|
|
|
|
static const int hue[][COLOR_CHAN_NUM] = {
|
|
|
|
{ 1, 1, 1 }, /* White */
|
|
|
|
{ 1, 1, 0 }, /* Yellow */
|
|
|
|
{ 0, 1, 1 }, /* Cyan */
|
|
|
|
{ 0, 1, 0 }, /* Green */
|
|
|
|
{ 1, 0, 1 }, /* Magenta */
|
|
|
|
{ 1, 0, 0 }, /* Red */
|
|
|
|
{ 0, 0, 1 }, /* Blue */
|
|
|
|
};
|
|
|
|
const int num_hues = ARRAY_LENGTH(hue);
|
|
|
|
|
2022-06-13 10:45:58 +00:00
|
|
|
struct image_header ih = image_header_from(image);
|
2022-06-08 12:30:42 +00:00
|
|
|
float val_max;
|
|
|
|
int x, y;
|
|
|
|
int hue_index;
|
|
|
|
int chan;
|
|
|
|
float value;
|
|
|
|
unsigned char r, g, b;
|
|
|
|
uint32_t *pixel;
|
|
|
|
|
|
|
|
float n_steps = width_bar - 1;
|
|
|
|
|
|
|
|
val_max = (1 << bitwidth) - 1;
|
|
|
|
|
2022-06-13 10:45:58 +00:00
|
|
|
for (y = 0; y < ih.height; y++) {
|
|
|
|
hue_index = (y * num_hues) / (ih.height - 1);
|
2022-06-08 12:30:42 +00:00
|
|
|
hue_index = MIN(hue_index, num_hues - 1);
|
|
|
|
|
2022-06-13 10:45:58 +00:00
|
|
|
pixel = image_header_get_row_u32(&ih, y);
|
|
|
|
for (x = 0; x < ih.width; x++, pixel++) {
|
2022-06-08 12:30:42 +00:00
|
|
|
struct color_float rgb = { .rgb = { 0, 0, 0 } };
|
|
|
|
|
2022-06-13 10:45:58 +00:00
|
|
|
value = (float)x / (float)(ih.width - 1);
|
2022-06-08 12:30:42 +00:00
|
|
|
|
|
|
|
if (width_bar > 1)
|
|
|
|
value = floor(value * n_steps) / n_steps;
|
|
|
|
|
|
|
|
for (chan = 0; chan < COLOR_CHAN_NUM; chan++) {
|
|
|
|
if (hue[hue_index][chan])
|
|
|
|
rgb.rgb[chan] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
sRGB_delinearize(&rgb);
|
|
|
|
|
|
|
|
r = round(rgb.r * val_max);
|
|
|
|
g = round(rgb.g * val_max);
|
|
|
|
b = round(rgb.b * val_max);
|
|
|
|
|
|
|
|
*pixel = (255U << 24) | (r << 16) | (g << 8) | b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-26 03:26:03 +00:00
|
|
|
static bool
|
2022-06-17 14:06:18 +00:00
|
|
|
process_pipeline_comparison(const struct buffer *src_buf,
|
|
|
|
const struct buffer *shot_buf,
|
|
|
|
const struct setup_args * arg)
|
2022-01-26 03:26:03 +00:00
|
|
|
{
|
2022-06-17 14:06:18 +00:00
|
|
|
FILE *dump = NULL;
|
2022-01-26 03:26:03 +00:00
|
|
|
#if 0
|
|
|
|
/*
|
2022-06-17 14:06:18 +00:00
|
|
|
* This file can be loaded in Octave for visualization. Find the script
|
|
|
|
* in tests/visualization/weston_plot_rgb_diff_stat.m and call it with
|
2022-01-26 03:26:03 +00:00
|
|
|
*
|
2022-06-17 14:06:18 +00:00
|
|
|
* weston_plot_rgb_diff_stat('opaque_pixel_conversion-f05-dump.txt')
|
2022-01-26 03:26:03 +00:00
|
|
|
*/
|
2022-09-26 21:37:27 +00:00
|
|
|
dump = fopen_dump_file(arg->meta.name);
|
2022-01-26 03:26:03 +00:00
|
|
|
#endif
|
|
|
|
|
2022-06-13 10:45:58 +00:00
|
|
|
struct image_header ih_src = image_header_from(src_buf->image);
|
|
|
|
struct image_header ih_shot = image_header_from(shot_buf->image);
|
2022-01-26 03:26:03 +00:00
|
|
|
int y, x;
|
|
|
|
struct color_float pix_src;
|
|
|
|
struct color_float pix_src_pipeline;
|
|
|
|
struct color_float pix_shot;
|
2022-06-17 14:06:18 +00:00
|
|
|
struct rgb_diff_stat diffstat = { .dump = dump };
|
2022-06-17 14:22:55 +00:00
|
|
|
bool ok;
|
2022-01-26 03:26:03 +00:00
|
|
|
|
2022-06-13 10:45:58 +00:00
|
|
|
/* no point to compare different images */
|
|
|
|
assert(ih_src.width == ih_shot.width);
|
|
|
|
assert(ih_src.height == ih_shot.height);
|
|
|
|
|
|
|
|
for (y = 0; y < ih_src.height; y++) {
|
|
|
|
uint32_t *row_ptr = image_header_get_row_u32(&ih_src, y);
|
|
|
|
uint32_t *row_ptr_shot = image_header_get_row_u32(&ih_shot, y);
|
2022-01-26 03:26:03 +00:00
|
|
|
|
2022-06-13 10:45:58 +00:00
|
|
|
for (x = 0; x < ih_src.width; x++) {
|
2022-01-26 03:26:03 +00:00
|
|
|
pix_src = a8r8g8b8_to_float(row_ptr[x]);
|
|
|
|
pix_shot = a8r8g8b8_to_float(row_ptr_shot[x]);
|
2022-06-17 14:06:18 +00:00
|
|
|
|
2022-05-01 17:57:10 +00:00
|
|
|
process_pixel_using_pipeline(arg->pipeline->pre_fn,
|
|
|
|
&arg->pipeline->mat,
|
|
|
|
arg->pipeline->post_fn,
|
2023-04-21 18:27:30 +00:00
|
|
|
arg->vcgt_exponents,
|
2022-01-26 03:26:03 +00:00
|
|
|
&pix_src, &pix_src_pipeline);
|
2022-02-15 12:51:26 +00:00
|
|
|
|
2022-06-17 14:06:18 +00:00
|
|
|
rgb_diff_stat_update(&diffstat,
|
|
|
|
&pix_src_pipeline, &pix_shot,
|
|
|
|
&pix_src);
|
2022-01-26 03:26:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-17 14:22:55 +00:00
|
|
|
ok = diffstat.two_norm.max <= arg->tolerance / 255.0f;
|
2022-01-26 03:26:03 +00:00
|
|
|
|
2022-06-17 14:22:55 +00:00
|
|
|
testlog("%s %s %s tolerance %f %s\n", __func__,
|
2022-06-17 14:06:18 +00:00
|
|
|
ok ? "SUCCESS" : "FAILURE",
|
2022-05-01 17:57:10 +00:00
|
|
|
arg->meta.name, arg->tolerance,
|
|
|
|
arg->type == PTYPE_MATRIX_SHAPER ? "matrix-shaper" : "cLUT");
|
2022-01-26 03:26:03 +00:00
|
|
|
|
2022-06-17 14:06:18 +00:00
|
|
|
rgb_diff_stat_print(&diffstat, __func__, 8);
|
|
|
|
|
|
|
|
if (dump)
|
|
|
|
fclose(dump);
|
|
|
|
|
2022-01-26 03:26:03 +00:00
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-06-08 12:26:10 +00:00
|
|
|
* Test that opaque client pixels produce the expected output when converted
|
|
|
|
* from the implicit sRGB input to ICC profile described output.
|
|
|
|
*
|
|
|
|
* The groundtruth conversion comes from the struct lcms_pipeline definitions.
|
|
|
|
* The first error source is converting those to ICC files. The second error
|
|
|
|
* source is Weston.
|
2022-10-10 13:19:22 +00:00
|
|
|
*
|
|
|
|
* This tests particularly the chain of input-to-blend followed by
|
|
|
|
* blend-to-output categories of color transformations.
|
2022-01-26 03:26:03 +00:00
|
|
|
*/
|
2022-06-08 12:26:10 +00:00
|
|
|
TEST(opaque_pixel_conversion)
|
2022-01-26 03:26:03 +00:00
|
|
|
{
|
2022-05-23 11:39:06 +00:00
|
|
|
int seq_no = get_test_fixture_index();
|
|
|
|
const struct setup_args *arg = &my_setup_args[seq_no];
|
2022-01-26 03:26:03 +00:00
|
|
|
const int width = WINDOW_WIDTH;
|
|
|
|
const int height = WINDOW_HEIGHT;
|
|
|
|
const int bitwidth = 8;
|
|
|
|
const int width_bar = 32;
|
|
|
|
|
|
|
|
struct client *client;
|
|
|
|
struct buffer *buf;
|
|
|
|
struct buffer *shot;
|
|
|
|
struct wl_surface *surface;
|
|
|
|
bool match;
|
|
|
|
|
|
|
|
client = create_client_and_test_surface(0, 0, width, height);
|
|
|
|
assert(client);
|
|
|
|
surface = client->surface->wl_surface;
|
|
|
|
|
|
|
|
buf = create_shm_buffer_a8r8g8b8(client, width, height);
|
2022-06-13 10:45:58 +00:00
|
|
|
gen_ramp_rgb(buf->image, bitwidth, width_bar);
|
2022-01-26 03:26:03 +00:00
|
|
|
|
|
|
|
wl_surface_attach(surface, buf->proxy, 0, 0);
|
|
|
|
wl_surface_damage(surface, 0, 0, width, height);
|
|
|
|
wl_surface_commit(surface);
|
|
|
|
|
2022-11-30 15:54:16 +00:00
|
|
|
shot = capture_screenshot_of_output(client, NULL);
|
2022-01-26 03:26:03 +00:00
|
|
|
assert(shot);
|
|
|
|
|
2022-10-10 12:09:43 +00:00
|
|
|
match = verify_image(shot->image, "shaper_matrix", arg->ref_image_index,
|
2022-05-23 11:39:06 +00:00
|
|
|
NULL, seq_no);
|
2022-06-13 10:45:58 +00:00
|
|
|
assert(process_pipeline_comparison(buf, shot, arg));
|
2022-01-26 03:26:03 +00:00
|
|
|
assert(match);
|
|
|
|
buffer_destroy(shot);
|
|
|
|
buffer_destroy(buf);
|
|
|
|
client_destroy(client);
|
|
|
|
}
|
2022-06-13 11:34:19 +00:00
|
|
|
|
|
|
|
static struct color_float
|
|
|
|
convert_to_blending_space(const struct lcms_pipeline *pip,
|
|
|
|
struct color_float cf)
|
|
|
|
{
|
|
|
|
/* Blending space is the linearized output space,
|
|
|
|
* or simply output space without the non-linear encoding
|
|
|
|
*/
|
|
|
|
cf = color_float_apply_curve(pip->pre_fn, cf);
|
|
|
|
return color_float_apply_matrix(&pip->mat, cf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
compare_blend(const struct lcms_pipeline *pip,
|
2023-04-21 18:27:30 +00:00
|
|
|
const double vcgt_exponents[COLOR_CHAN_NUM],
|
2022-06-13 11:34:19 +00:00
|
|
|
struct color_float bg,
|
|
|
|
struct color_float fg,
|
|
|
|
const struct color_float *shot,
|
|
|
|
struct rgb_diff_stat *diffstat)
|
|
|
|
{
|
|
|
|
struct color_float ref;
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
/* convert sources to straight alpha */
|
|
|
|
assert(bg.a == 1.0f);
|
|
|
|
fg = color_float_unpremult(fg);
|
|
|
|
|
|
|
|
bg = convert_to_blending_space(pip, bg);
|
|
|
|
fg = convert_to_blending_space(pip, fg);
|
|
|
|
|
|
|
|
/* blend */
|
|
|
|
for (i = 0; i < COLOR_CHAN_NUM; i++)
|
|
|
|
ref.rgb[i] = (1.0f - fg.a) * bg.rgb[i] + fg.a * fg.rgb[i];
|
|
|
|
|
|
|
|
/* non-linear encoding for output */
|
|
|
|
ref = color_float_apply_curve(pip->post_fn, ref);
|
|
|
|
|
2023-04-21 18:27:30 +00:00
|
|
|
if (should_include_vcgt(vcgt_exponents))
|
|
|
|
for (i = 0; i < COLOR_CHAN_NUM; i++)
|
|
|
|
ref.rgb[i] = pow(ref.rgb[i], vcgt_exponents[i]);
|
|
|
|
|
2022-06-13 11:34:19 +00:00
|
|
|
rgb_diff_stat_update(diffstat, &ref, shot, &fg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Alpha blending test pattern parameters */
|
|
|
|
static const int ALPHA_STEPS = 256;
|
|
|
|
static const int BLOCK_WIDTH = 1;
|
|
|
|
|
|
|
|
static void *
|
|
|
|
get_middle_row(struct buffer *buf)
|
|
|
|
{
|
|
|
|
struct image_header ih = image_header_from(buf->image);
|
|
|
|
|
|
|
|
assert(ih.width >= BLOCK_WIDTH * ALPHA_STEPS);
|
|
|
|
assert(ih.height >= BLOCK_WIDTH);
|
|
|
|
|
|
|
|
return image_header_get_row_u32(&ih, (BLOCK_WIDTH - 1) / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
check_blend_pattern(struct buffer *bg_buf,
|
|
|
|
struct buffer *fg_buf,
|
|
|
|
struct buffer *shot_buf,
|
|
|
|
const struct setup_args *arg)
|
|
|
|
{
|
|
|
|
FILE *dump = NULL;
|
|
|
|
#if 0
|
|
|
|
/*
|
|
|
|
* This file can be loaded in Octave for visualization. Find the script
|
|
|
|
* in tests/visualization/weston_plot_rgb_diff_stat.m and call it with
|
|
|
|
*
|
|
|
|
* weston_plot_rgb_diff_stat('output_icc_alpha_blend-f01-dump.txt', 255, 8)
|
|
|
|
*/
|
2022-09-26 21:37:27 +00:00
|
|
|
dump = fopen_dump_file(arg->meta.name);
|
2022-06-13 11:34:19 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
uint32_t *bg_row = get_middle_row(bg_buf);
|
|
|
|
uint32_t *fg_row = get_middle_row(fg_buf);
|
|
|
|
uint32_t *shot_row = get_middle_row(shot_buf);
|
|
|
|
struct rgb_diff_stat diffstat = { .dump = dump };
|
|
|
|
int x;
|
|
|
|
|
|
|
|
for (x = 0; x < BLOCK_WIDTH * ALPHA_STEPS; x++) {
|
|
|
|
struct color_float bg = a8r8g8b8_to_float(bg_row[x]);
|
|
|
|
struct color_float fg = a8r8g8b8_to_float(fg_row[x]);
|
|
|
|
struct color_float shot = a8r8g8b8_to_float(shot_row[x]);
|
|
|
|
|
2023-04-21 18:27:30 +00:00
|
|
|
compare_blend(arg->pipeline, arg->vcgt_exponents, bg, fg, &shot, &diffstat);
|
2022-06-13 11:34:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rgb_diff_stat_print(&diffstat, "Blending", 8);
|
|
|
|
|
|
|
|
if (dump)
|
|
|
|
fclose(dump);
|
|
|
|
|
|
|
|
/* Test success condition: */
|
|
|
|
return diffstat.two_norm.max < 1.5f / 255.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t
|
|
|
|
premult_color(uint32_t a, uint32_t r, uint32_t g, uint32_t b)
|
|
|
|
{
|
|
|
|
uint32_t c = 0;
|
|
|
|
|
|
|
|
c |= a << 24;
|
|
|
|
c |= (a * r / 255) << 16;
|
|
|
|
c |= (a * g / 255) << 8;
|
|
|
|
c |= a * b / 255;
|
|
|
|
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fill_alpha_pattern(struct buffer *buf)
|
|
|
|
{
|
|
|
|
struct image_header ih = image_header_from(buf->image);
|
|
|
|
int y;
|
|
|
|
|
|
|
|
assert(ih.pixman_format == PIXMAN_a8r8g8b8);
|
|
|
|
assert(ih.width == BLOCK_WIDTH * ALPHA_STEPS);
|
|
|
|
|
|
|
|
for (y = 0; y < ih.height; y++) {
|
|
|
|
uint32_t *row = image_header_get_row_u32(&ih, y);
|
|
|
|
uint32_t step;
|
|
|
|
|
|
|
|
for (step = 0; step < (uint32_t)ALPHA_STEPS; step++) {
|
|
|
|
uint32_t alpha = step * 255 / (ALPHA_STEPS - 1);
|
|
|
|
uint32_t color;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
color = premult_color(alpha, 0, 255 - alpha, 255);
|
|
|
|
for (i = 0; i < BLOCK_WIDTH; i++)
|
|
|
|
*row++ = color;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Test that alpha blending is correct when an output ICC profile is installed.
|
|
|
|
*
|
|
|
|
* The background is a constant color. On top of that, there is an
|
|
|
|
* alpha-blended gradient with ramps in both alpha and color. Sub-surface
|
|
|
|
* ensures the correct positioning and stacking.
|
|
|
|
*
|
|
|
|
* The gradient consists of ALPHA_STEPS number of blocks. Block size is
|
|
|
|
* BLOCK_WIDTH x BLOCK_WIDTH and a block has a uniform color.
|
|
|
|
*
|
|
|
|
* In the blending result over x axis:
|
|
|
|
* - red goes from 1.0 to 0.0, monotonic
|
|
|
|
* - green is not monotonic
|
|
|
|
* - blue goes from 0.0 to 1.0, monotonic
|
|
|
|
*
|
|
|
|
* The test has sRGB encoded input pixels (non-linear). These are converted to
|
|
|
|
* linear light (optical) values in output color space, blended, and converted
|
|
|
|
* to non-linear (electrical) values according to the output ICC profile.
|
|
|
|
*
|
|
|
|
* Specifically, this test exercises the linearization of output ICC profiles,
|
|
|
|
* retrieve_eotf_and_output_inv_eotf().
|
|
|
|
*/
|
|
|
|
TEST(output_icc_alpha_blend)
|
|
|
|
{
|
|
|
|
const int width = BLOCK_WIDTH * ALPHA_STEPS;
|
|
|
|
const int height = BLOCK_WIDTH;
|
|
|
|
const pixman_color_t background_color = {
|
|
|
|
.red = 0xffff,
|
|
|
|
.green = 0x8080,
|
|
|
|
.blue = 0x0000,
|
|
|
|
.alpha = 0xffff
|
|
|
|
};
|
|
|
|
int seq_no = get_test_fixture_index();
|
|
|
|
const struct setup_args *arg = &my_setup_args[seq_no];
|
|
|
|
struct client *client;
|
|
|
|
struct buffer *bg;
|
|
|
|
struct buffer *fg;
|
|
|
|
struct wl_subcompositor *subco;
|
|
|
|
struct wl_surface *surf;
|
|
|
|
struct wl_subsurface *sub;
|
|
|
|
struct buffer *shot;
|
|
|
|
bool match;
|
|
|
|
|
|
|
|
client = create_client();
|
|
|
|
subco = bind_to_singleton_global(client, &wl_subcompositor_interface, 1);
|
|
|
|
|
|
|
|
/* background window content */
|
|
|
|
bg = create_shm_buffer_a8r8g8b8(client, width, height);
|
|
|
|
fill_image_with_color(bg->image, &background_color);
|
|
|
|
|
|
|
|
/* background window, main surface */
|
|
|
|
client->surface = create_test_surface(client);
|
|
|
|
client->surface->width = width;
|
|
|
|
client->surface->height = height;
|
|
|
|
client->surface->buffer = bg; /* pass ownership */
|
|
|
|
surface_set_opaque_rect(client->surface,
|
|
|
|
&(struct rectangle){ 0, 0, width, height });
|
|
|
|
|
|
|
|
/* foreground blended content */
|
|
|
|
fg = create_shm_buffer_a8r8g8b8(client, width, height);
|
|
|
|
fill_alpha_pattern(fg);
|
|
|
|
|
|
|
|
/* foreground window, sub-surface */
|
|
|
|
surf = wl_compositor_create_surface(client->wl_compositor);
|
|
|
|
sub = wl_subcompositor_get_subsurface(subco, surf, client->surface->wl_surface);
|
|
|
|
/* sub-surface defaults to position 0, 0, top-most, synchronized */
|
|
|
|
wl_surface_attach(surf, fg->proxy, 0, 0);
|
|
|
|
wl_surface_damage(surf, 0, 0, width, height);
|
|
|
|
wl_surface_commit(surf);
|
|
|
|
|
|
|
|
/* attach, damage, commit background window */
|
|
|
|
move_client(client, 0, 0);
|
|
|
|
|
2022-11-30 15:54:16 +00:00
|
|
|
shot = capture_screenshot_of_output(client, NULL);
|
2022-06-13 11:34:19 +00:00
|
|
|
assert(shot);
|
2022-10-10 12:09:43 +00:00
|
|
|
match = verify_image(shot->image, "output_icc_alpha_blend", arg->ref_image_index,
|
2022-06-13 11:34:19 +00:00
|
|
|
NULL, seq_no);
|
|
|
|
assert(check_blend_pattern(bg, fg, shot, arg));
|
|
|
|
assert(match);
|
|
|
|
|
|
|
|
buffer_destroy(shot);
|
|
|
|
|
|
|
|
wl_subsurface_destroy(sub);
|
|
|
|
wl_surface_destroy(surf);
|
|
|
|
buffer_destroy(fg);
|
|
|
|
wl_subcompositor_destroy(subco);
|
|
|
|
client_destroy(client); /* destroys bg */
|
|
|
|
}
|
2022-10-10 13:19:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Test that output decorations have the expected colors.
|
|
|
|
*
|
|
|
|
* This is the only way to test input-to-output category of color
|
|
|
|
* transformations. They are used only for output decorations and some other
|
|
|
|
* debug-like features. The input color space is hardcoded to sRGB in the
|
|
|
|
* compositor.
|
|
|
|
*
|
|
|
|
* Because the output decorations are drawn with Cairo, we do not have an
|
|
|
|
* easy access to the ground-truth image and so do not check the results
|
|
|
|
* against a reference formula.
|
|
|
|
*/
|
|
|
|
TEST(output_icc_decorations)
|
|
|
|
{
|
|
|
|
int seq_no = get_test_fixture_index();
|
|
|
|
const struct setup_args *arg = &my_setup_args[seq_no];
|
|
|
|
struct client *client;
|
|
|
|
struct buffer *shot;
|
|
|
|
pixman_image_t *img;
|
|
|
|
bool match;
|
|
|
|
|
|
|
|
client = create_client();
|
|
|
|
|
|
|
|
shot = client_capture_output(client, client->output,
|
|
|
|
WESTON_CAPTURE_V1_SOURCE_FULL_FRAMEBUFFER);
|
|
|
|
img = image_convert_to_a8r8g8b8(shot->image);
|
|
|
|
|
|
|
|
match = verify_image(img, "output-icc-decorations",
|
|
|
|
arg->ref_image_index, NULL, seq_no);
|
|
|
|
assert(match);
|
|
|
|
|
|
|
|
pixman_image_unref(img);
|
|
|
|
buffer_destroy(shot);
|
|
|
|
client_destroy(client);
|
|
|
|
}
|