tests/color-icc-output: add blending test

This is adding basically a copy of alpha-blending-test.c. The difference
is that here we use ICC files to set up the output color profile, and
then test light-linear blending only. BLOCK_WIDTH is set to 1 to fit
inside the output size used by the fixture setup, which is smaller than
in the original, but does not change the results.

The test is aimed at testing how color-lcms module succeeds in
linearizing the output of different ICC output profiles. Incorrect
linearization should cause changes in blending results.

The tolerance is taken from the currently achieved error statistics
(1.40908) and rounded up a little to achieve a suitable margin.

Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
This commit is contained in:
Pekka Paalanen 2022-06-13 14:34:19 +03:00 committed by Pekka Paalanen
parent 2c0ff9a3b4
commit aa4f7d3a63
4 changed files with 221 additions and 0 deletions

View File

@ -1,5 +1,6 @@
/*
* Copyright 2021 Advanced Micro Devices, Inc.
* Copyright 2020, 2022 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -624,3 +625,223 @@ TEST(opaque_pixel_conversion)
buffer_destroy(buf);
client_destroy(client);
}
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,
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);
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)
*/
dump = fopen_dump_file("dump");
#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]);
compare_blend(arg->pipeline, bg, fg, &shot, &diffstat);
}
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);
shot = capture_screenshot_of_output(client);
assert(shot);
match = verify_image(shot, "output_icc_alpha_blend", arg->ref_image_index,
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 */
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B