diff --git a/tests/color-icc-output-test.c b/tests/color-icc-output-test.c index a9b3d7c4..63fbfd1f 100644 --- a/tests/color-icc-output-test.c +++ b/tests/color-icc-output-test.c @@ -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 */ +} diff --git a/tests/reference/output_icc_alpha_blend-00.png b/tests/reference/output_icc_alpha_blend-00.png new file mode 100644 index 00000000..beec77b8 Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-00.png differ diff --git a/tests/reference/output_icc_alpha_blend-01.png b/tests/reference/output_icc_alpha_blend-01.png new file mode 100644 index 00000000..45250ecc Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-01.png differ diff --git a/tests/reference/output_icc_alpha_blend-02.png b/tests/reference/output_icc_alpha_blend-02.png new file mode 100644 index 00000000..7714b103 Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-02.png differ