diff options
Diffstat (limited to 'tests/weston-test-client-helper.c')
-rw-r--r-- | tests/weston-test-client-helper.c | 865 |
1 files changed, 679 insertions, 186 deletions
diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index fda0ce7b..ea202c0c 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -1,52 +1,48 @@ /* * Copyright © 2012 Intel Corporation * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting documentation, and - * that the name of the copyright holders not be used in advertising or - * publicity pertaining to distribution of the software without specific, - * written prior permission. The copyright holders make no representations - * about the suitability of this software for any purpose. It is provided "as - * is" without express or implied warranty. + * 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 COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO - * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE - * OF THIS SOFTWARE. + * 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 <stdlib.h> +#include <stdint.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/mman.h> +#include <cairo.h> -#include "../shared/os-compatibility.h" +#include "shared/os-compatibility.h" +#include "shared/xalloc.h" +#include "shared/zalloc.h" #include "weston-test-client-helper.h" #define max(a, b) (((a) > (b)) ? (a) : (b)) #define min(a, b) (((a) > (b)) ? (b) : (a)) #define clip(x, a, b) min(max(x, a), b) -void * -fail_on_null(void *p) -{ - if (p == NULL) { - fprintf(stderr, "out of memory\n"); - exit(EXIT_FAILURE); - } - return p; -} - - int surface_contains(struct surface *surface, int x, int y) { @@ -108,7 +104,7 @@ move_client(struct client *client, int x, int y) /* The attach here is necessary because commit() will call configure * only on surfaces newly attached, and the one that sets the surface * position is the configure. */ - wl_surface_attach(surface->wl_surface, surface->wl_buffer, 0, 0); + wl_surface_attach(surface->wl_surface, surface->buffer->proxy, 0, 0); wl_surface_damage(surface->wl_surface, 0, 0, surface->width, surface->height); @@ -119,17 +115,6 @@ move_client(struct client *client, int x, int y) frame_callback_wait(client, &done); } -int -get_n_egl_buffers(struct client *client) -{ - client->test->n_egl_buffers = -1; - - weston_test_get_n_egl_buffers(client->test->weston_test); - wl_display_roundtrip(client->wl_display); - - return client->test->n_egl_buffers; -} - static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface, @@ -137,7 +122,11 @@ pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, { struct pointer *pointer = data; - pointer->focus = wl_surface_get_user_data(wl_surface); + if (wl_surface) + pointer->focus = wl_surface_get_user_data(wl_surface); + else + pointer->focus = NULL; + pointer->x = wl_fixed_to_int(x); pointer->y = wl_fixed_to_int(y); @@ -154,17 +143,20 @@ pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, pointer->focus = NULL; fprintf(stderr, "test-client: got pointer leave, surface %p\n", - wl_surface_get_user_data(wl_surface)); + wl_surface ? wl_surface_get_user_data(wl_surface) : NULL); } static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, - uint32_t time, wl_fixed_t x, wl_fixed_t y) + uint32_t time_msec, wl_fixed_t x, wl_fixed_t y) { struct pointer *pointer = data; pointer->x = wl_fixed_to_int(x); pointer->y = wl_fixed_to_int(y); + pointer->motion_time_msec = time_msec; + pointer->motion_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got pointer motion %d %d\n", pointer->x, pointer->y); @@ -172,13 +164,16 @@ pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, - uint32_t serial, uint32_t time, uint32_t button, + uint32_t serial, uint32_t time_msec, uint32_t button, uint32_t state) { struct pointer *pointer = data; pointer->button = button; pointer->state = state; + pointer->button_time_msec = time_msec; + pointer->button_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got pointer button %u %u\n", button, state); @@ -186,18 +181,65 @@ pointer_handle_button(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, - uint32_t time, uint32_t axis, wl_fixed_t value) + uint32_t time_msec, uint32_t axis, wl_fixed_t value) { + struct pointer *pointer = data; + + pointer->axis = axis; + pointer->axis_value = wl_fixed_to_double(value); + pointer->axis_time_msec = time_msec; + pointer->axis_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; + fprintf(stderr, "test-client: got pointer axis %u %f\n", axis, wl_fixed_to_double(value)); } +static void +pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) +{ + fprintf(stderr, "test-client: got pointer frame\n"); +} + +static void +pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t source) +{ + fprintf(stderr, "test-client: got pointer axis source %u\n", source); +} + +static void +pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time_msec, uint32_t axis) +{ + struct pointer *pointer = data; + + pointer->axis = axis; + pointer->axis_stop_time_msec = time_msec; + pointer->axis_stop_time_timespec = pointer->input_timestamp; + pointer->input_timestamp = (struct timespec) { 0 }; + + fprintf(stderr, "test-client: got pointer axis stop %u\n", axis); +} + +static void +pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t value) +{ + fprintf(stderr, "test-client: got pointer axis discrete %u %d\n", + axis, value); +} + static const struct wl_pointer_listener pointer_listener = { pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, + pointer_handle_frame, + pointer_handle_axis_source, + pointer_handle_axis_stop, + pointer_handle_axis_discrete, }; static void @@ -216,7 +258,10 @@ keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, { struct keyboard *keyboard = data; - keyboard->focus = wl_surface_get_user_data(wl_surface); + if (wl_surface) + keyboard->focus = wl_surface_get_user_data(wl_surface); + else + keyboard->focus = NULL; fprintf(stderr, "test-client: got keyboard enter, surface %p\n", keyboard->focus); @@ -231,18 +276,21 @@ keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, keyboard->focus = NULL; fprintf(stderr, "test-client: got keyboard leave, surface %p\n", - wl_surface_get_user_data(wl_surface)); + wl_surface ? wl_surface_get_user_data(wl_surface) : NULL); } static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, - uint32_t serial, uint32_t time, uint32_t key, + uint32_t serial, uint32_t time_msec, uint32_t key, uint32_t state) { struct keyboard *keyboard = data; keyboard->key = key; keyboard->state = state; + keyboard->key_time_msec = time_msec; + keyboard->key_time_timespec = keyboard->input_timestamp; + keyboard->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got keyboard key %u %u\n", key, state); } @@ -288,14 +336,18 @@ static const struct wl_keyboard_listener keyboard_listener = { static void touch_handle_down(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time, struct wl_surface *surface, - int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) + uint32_t serial, uint32_t time_msec, + struct wl_surface *surface, int32_t id, + wl_fixed_t x_w, wl_fixed_t y_w) { struct touch *touch = data; touch->down_x = wl_fixed_to_int(x_w); touch->down_y = wl_fixed_to_int(y_w); touch->id = id; + touch->down_time_msec = time_msec; + touch->down_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got touch down %d %d, surf: %p, id: %d\n", touch->down_x, touch->down_y, surface, id); @@ -303,21 +355,28 @@ touch_handle_down(void *data, struct wl_touch *wl_touch, static void touch_handle_up(void *data, struct wl_touch *wl_touch, - uint32_t serial, uint32_t time, int32_t id) + uint32_t serial, uint32_t time_msec, int32_t id) { struct touch *touch = data; touch->up_id = id; + touch->up_time_msec = time_msec; + touch->up_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got touch up, id: %d\n", id); } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, - uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) + uint32_t time_msec, int32_t id, + wl_fixed_t x_w, wl_fixed_t y_w) { struct touch *touch = data; touch->x = wl_fixed_to_int(x_w); touch->y = wl_fixed_to_int(y_w); + touch->motion_time_msec = time_msec; + touch->motion_time_timespec = touch->input_timestamp; + touch->input_timestamp = (struct timespec) { 0 }; fprintf(stderr, "test-client: got touch motion, %d %d, id: %d\n", touch->x, touch->y, id); @@ -380,37 +439,80 @@ static const struct wl_surface_listener surface_listener = { surface_leave }; -struct wl_buffer * -create_shm_buffer(struct client *client, int width, int height, void **pixels) +static struct buffer * +create_shm_buffer(struct client *client, int width, int height, + pixman_format_code_t format, uint32_t wlfmt) { struct wl_shm *shm = client->wl_shm; - int stride = width * 4; - int size = stride * height; + struct buffer *buf; + size_t stride_bytes; struct wl_shm_pool *pool; - struct wl_buffer *buffer; int fd; void *data; + size_t bytes_pp; + + assert(width > 0); + assert(height > 0); - fd = os_create_anonymous_file(size); + buf = xzalloc(sizeof *buf); + + bytes_pp = PIXMAN_FORMAT_BPP(format) / 8; + stride_bytes = width * bytes_pp; + /* round up to multiple of 4 bytes for Pixman */ + stride_bytes = (stride_bytes + 3) & ~3u; + assert(stride_bytes / bytes_pp >= (unsigned)width); + + buf->len = stride_bytes * height; + assert(buf->len / stride_bytes == (unsigned)height); + + fd = os_create_anonymous_file(buf->len); assert(fd >= 0); - data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + data = mmap(NULL, buf->len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { close(fd); assert(data != MAP_FAILED); } - pool = wl_shm_create_pool(shm, fd, size); - buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, - WL_SHM_FORMAT_ARGB8888); + pool = wl_shm_create_pool(shm, fd, buf->len); + buf->proxy = wl_shm_pool_create_buffer(pool, 0, width, height, + stride_bytes, wlfmt); wl_shm_pool_destroy(pool); - close(fd); - if (pixels) - *pixels = data; + buf->image = pixman_image_create_bits(format, width, height, + data, stride_bytes); - return buffer; + assert(buf->proxy); + assert(buf->image); + + return buf; +} + +struct buffer * +create_shm_buffer_a8r8g8b8(struct client *client, int width, int height) +{ + assert(client->has_argb); + + return create_shm_buffer(client, width, height, + PIXMAN_a8r8g8b8, WL_SHM_FORMAT_ARGB8888); +} + +void +buffer_destroy(struct buffer *buf) +{ + void *pixels; + + pixels = pixman_image_get_data(buf->image); + + if (buf->proxy) { + wl_buffer_destroy(buf->proxy); + assert(munmap(pixels, buf->len) == 0); + } + + assert(pixman_image_unref(buf->image)); + + free(buf); } static void @@ -439,14 +541,6 @@ test_handle_pointer_position(void *data, struct weston_test *weston_test, } static void -test_handle_n_egl_buffers(void *data, struct weston_test *weston_test, uint32_t n) -{ - struct test *test = data; - - test->n_egl_buffers = n; -} - -static void test_handle_capture_screenshot_done(void *data, struct weston_test *weston_test) { struct test *test = data; @@ -457,11 +551,31 @@ test_handle_capture_screenshot_done(void *data, struct weston_test *weston_test) static const struct weston_test_listener test_listener = { test_handle_pointer_position, - test_handle_n_egl_buffers, test_handle_capture_screenshot_done, }; static void +input_destroy(struct input *inp) +{ + if (inp->pointer) { + wl_pointer_release(inp->pointer->wl_pointer); + free(inp->pointer); + } + if (inp->keyboard) { + wl_keyboard_release(inp->keyboard->wl_keyboard); + free(inp->keyboard); + } + if (inp->touch) { + wl_touch_release(inp->touch->wl_touch); + free(inp->touch); + } + wl_list_remove(&inp->link); + wl_seat_release(inp->wl_seat); + free(inp->seat_name); + free(inp); +} + +static void input_update_devices(struct input *input) { struct pointer *pointer; @@ -522,7 +636,7 @@ seat_handle_capabilities(void *data, struct wl_seat *seat, /* we will create/update the devices only with the right (test) seat. * If we haven't discovered which seat is the test seat, just * store capabilities and bail out */ - if(input->seat_name && strcmp(input->seat_name, "test-seat") == 0) + if (input->seat_name && strcmp(input->seat_name, "test-seat") == 0) input_update_devices(input); fprintf(stderr, "test-client: got seat %p capabilities: %x\n", @@ -537,6 +651,15 @@ seat_handle_name(void *data, struct wl_seat *seat, const char *name) input->seat_name = strdup(name); assert(input->seat_name && "No memory"); + /* We only update the devices and set client input for the test seat */ + if (strcmp(name, "test-seat") == 0) { + assert(!input->client->input && + "Multiple test seats detected!"); + + input_update_devices(input); + input->client->input = input; + } + fprintf(stderr, "test-client: got seat %p name: \'%s\'\n", input, name); } @@ -628,6 +751,8 @@ handle_global(void *data, struct wl_registry *registry, &wl_compositor_interface, version); } else if (strcmp(interface, "wl_seat") == 0) { input = xzalloc(sizeof *input); + input->client = client; + input->global_name = global->name; input->wl_seat = wl_registry_bind(registry, id, &wl_seat_interface, version); @@ -658,8 +783,59 @@ handle_global(void *data, struct wl_registry *registry, } } +static struct global * +client_find_global_with_name(struct client *client, uint32_t name) +{ + struct global *global; + + wl_list_for_each(global, &client->global_list, link) { + if (global->name == name) + return global; + } + + return NULL; +} + +static struct input * +client_find_input_with_name(struct client *client, uint32_t name) +{ + struct input *input; + + wl_list_for_each(input, &client->inputs, link) { + if (input->global_name == name) + return input; + } + + return NULL; +} + +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + struct client *client = data; + struct global *global; + struct input *input; + + global = client_find_global_with_name(client, name); + assert(global && "Request to remove unknown global"); + + if (strcmp(global->interface, "wl_seat") == 0) { + input = client_find_input_with_name(client, name); + if (input) { + if (client->input == input) + client->input = NULL; + input_destroy(input); + } + } + + wl_list_remove(&global->link); + free(global->interface); + free(global); +} + static const struct wl_registry_listener registry_listener = { - handle_global + handle_global, + handle_global_remove, }; void @@ -732,34 +908,6 @@ log_handler(const char *fmt, va_list args) vfprintf(stderr, fmt, args); } -static void -input_destroy(struct input *inp) -{ - wl_list_remove(&inp->link); - wl_seat_destroy(inp->wl_seat); - free(inp); -} - -/* find the test-seat and set it in client. - * Destroy other inputs */ -static void -client_set_input(struct client *cl) -{ - struct input *inp, *inptmp; - wl_list_for_each_safe(inp, inptmp, &cl->inputs, link) { - assert(inp->seat_name && "BUG: input with no name"); - if (strcmp(inp->seat_name, "test-seat") == 0) { - cl->input = inp; - input_update_devices(inp); - } else { - input_destroy(inp); - } - } - - /* we keep only one input */ - assert(wl_list_length(&cl->inputs) == 1); -} - struct client * create_client(void) { @@ -785,9 +933,6 @@ create_client(void) * events */ client_roundtrip(client); - /* find the right input for us */ - client_set_input(client); - /* must have WL_SHM_FORMAT_ARGB32 */ assert(client->has_argb); @@ -806,16 +951,13 @@ create_client(void) return client; } -struct client * -create_client_and_test_surface(int x, int y, int width, int height) +struct surface * +create_test_surface(struct client *client) { - struct client *client; struct surface *surface; - client = create_client(); - - /* initialize the client surface */ surface = xzalloc(sizeof *surface); + surface->wl_surface = wl_compositor_create_surface(client->wl_compositor); assert(surface->wl_surface); @@ -823,15 +965,39 @@ create_client_and_test_surface(int x, int y, int width, int height) wl_surface_add_listener(surface->wl_surface, &surface_listener, surface); - client->surface = surface; wl_surface_set_user_data(surface->wl_surface, surface); + return surface; +} + +struct client * +create_client_and_test_surface(int x, int y, int width, int height) +{ + struct client *client; + struct surface *surface; + pixman_color_t color = { 16384, 16384, 16384, 16384 }; /* uint16_t */ + pixman_image_t *solid; + + client = create_client(); + + /* initialize the client surface */ + surface = create_test_surface(client); + client->surface = surface; + surface->width = width; surface->height = height; - surface->wl_buffer = create_shm_buffer(client, width, height, - &surface->data); - - memset(surface->data, 64, width * height * 4); + surface->buffer = create_shm_buffer_a8r8g8b8(client, width, height); + + solid = pixman_image_create_solid_fill(&color); + pixman_image_composite32(PIXMAN_OP_SRC, + solid, /* src */ + NULL, /* mask */ + surface->buffer->image, /* dst */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + 0, 0, /* dst x,y */ + width, height); + pixman_image_unref(solid); move_client(client, x, y); @@ -844,9 +1010,10 @@ output_path(void) char *path = getenv("WESTON_TEST_OUTPUT_PATH"); if (!path) - return "."; + return "./logs"; + return path; - } +} char* screenshot_output_filename(const char *basename, uint32_t seq) @@ -880,99 +1047,425 @@ screenshot_reference_filename(const char *basename, uint32_t seq) return filename; } +struct format_map_entry { + cairo_format_t cairo; + pixman_format_code_t pixman; +}; + +static const struct format_map_entry format_map[] = { + { CAIRO_FORMAT_ARGB32, PIXMAN_a8r8g8b8 }, + { CAIRO_FORMAT_RGB24, PIXMAN_x8r8g8b8 }, + { CAIRO_FORMAT_A8, PIXMAN_a8 }, + { CAIRO_FORMAT_RGB16_565, PIXMAN_r5g6b5 }, +}; + +static pixman_format_code_t +format_cairo2pixman(cairo_format_t fmt) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(format_map); i++) + if (format_map[i].cairo == fmt) + return format_map[i].pixman; + + assert(0 && "unknown Cairo pixel format"); +} + +static cairo_format_t +format_pixman2cairo(pixman_format_code_t fmt) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(format_map); i++) + if (format_map[i].pixman == fmt) + return format_map[i].cairo; + + assert(0 && "unknown Pixman pixel format"); +} + /** - * check_surfaces_geometry() - verifies two surfaces are same size + * Compute the ROI for image comparisons + * + * \param img_a An image. + * \param img_b Another image. + * \param clip_rect Explicit ROI, or NULL for using the whole + * image area. + * + * \return The region of interest (ROI) that is guaranteed to be inside both + * images. * - * @returns true if surfaces have the same width and height, or false - * if not, or if there is no actual data. + * If clip_rect is given, it must fall inside of both images. + * If clip_rect is NULL, the images must be of the same size. + * If any precondition is violated, this function aborts with an error. + * + * The ROI is given as pixman_box32_t, where x2,y2 are non-inclusive. */ -bool -check_surfaces_geometry(const struct surface *a, const struct surface *b) +static pixman_box32_t +image_check_get_roi(pixman_image_t *img_a, pixman_image_t *img_b, + const struct rectangle *clip_rect) { - if (a == NULL || b == NULL) { - printf("Undefined surfaces\n"); - return false; + int width_a; + int width_b; + int height_a; + int height_b; + pixman_box32_t box; + + width_a = pixman_image_get_width(img_a); + height_a = pixman_image_get_height(img_a); + + width_b = pixman_image_get_width(img_b); + height_b = pixman_image_get_height(img_b); + + if (clip_rect) { + box.x1 = clip_rect->x; + box.y1 = clip_rect->y; + box.x2 = clip_rect->x + clip_rect->width; + box.y2 = clip_rect->y + clip_rect->height; + } else { + box.x1 = 0; + box.y1 = 0; + box.x2 = max(width_a, width_b); + box.y2 = max(height_a, height_b); } - else if (a->data == NULL || b->data == NULL) { - printf("Undefined data\n"); - return false; - } - else if (a->width != b->width || a->height != b->height) { - printf("Mismatched dimensions: %d,%d != %d,%d\n", - a->width, a->height, b->width, b->height); - return false; + + assert(box.x1 >= 0); + assert(box.y1 >= 0); + assert(box.x2 > box.x1); + assert(box.y2 > box.y1); + assert(box.x2 <= width_a); + assert(box.x2 <= width_b); + assert(box.y2 <= height_a); + assert(box.y2 <= height_b); + + return box; +} + +struct image_iterator { + char *data; + int stride; /* bytes */ +}; + +static void +image_iter_init(struct image_iterator *it, pixman_image_t *image) +{ + pixman_format_code_t fmt; + + it->stride = pixman_image_get_stride(image); + it->data = (void *)pixman_image_get_data(image); + + fmt = pixman_image_get_format(image); + assert(PIXMAN_FORMAT_BPP(fmt) == 32); +} + +static uint32_t * +image_iter_get_row(struct image_iterator *it, int y) +{ + return (uint32_t *)(it->data + y * it->stride); +} + +/** + * Test if a given region within two images are pixel-identical. + * + * Returns true if the two images pixel-wise identical, and false otherwise. + * + * \param img_a First image. + * \param img_b Second image. + * \param clip_rect The region of interest, or NULL for comparing the whole + * images. + * + * This function hard-fails if clip_rect is not inside both images. If clip_rect + * is given, the images do not have to match in size, otherwise size mismatch + * will be a hard failure. + */ +bool +check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, + const struct rectangle *clip_rect) +{ + struct image_iterator it_a; + struct image_iterator it_b; + pixman_box32_t box; + int x, y; + uint32_t *pix_a; + uint32_t *pix_b; + + box = image_check_get_roi(img_a, img_b, clip_rect); + + image_iter_init(&it_a, img_a); + image_iter_init(&it_b, img_b); + + for (y = box.y1; y < box.y2; y++) { + pix_a = image_iter_get_row(&it_a, y) + box.x1; + pix_b = image_iter_get_row(&it_b, y) + box.x1; + + for (x = box.x1; x < box.x2; x++) { + if (*pix_a != *pix_b) + return false; + + pix_a++; + pix_b++; + } } + return true; } /** - * check_surfaces_equal() - tests if two surfaces are pixel-identical + * Tint a color + * + * \param src Source pixel as x8r8g8b8. + * \param add The tint as x8r8g8b8, x8 must be zero; r8, g8 and b8 must be + * no greater than 0xc0 to avoid overflow to another channel. + * \return The tinted pixel color as x8r8g8b8, x8 guaranteed to be 0xff. * - * Returns true if surface buffers have all the same byte values, - * false if the surfaces don't match or can't be compared due to - * different dimensions. + * The source pixel RGB values are divided by 4, and then the tint is added. + * To achieve colors outside of the range of src, a tint color channel must be + * at least 0x40. (0xff / 4 = 0x3f, 0xff - 0x3f = 0xc0) */ -bool -check_surfaces_equal(const struct surface *a, const struct surface *b) +static uint32_t +tint(uint32_t src, uint32_t add) { - int bpp = 4; /* Assumes ARGB */ + uint32_t v; - if (!check_surfaces_geometry(a, b)) - return false; + v = ((src & 0xfcfcfcfc) >> 2) | 0xff000000; + + return v + add; +} + +/** + * Create a visualization of image differences. + * + * \param img_a First image, which is used as the basis for the output. + * \param img_b Second image. + * \param clip_rect The region of interest, or NULL for comparing the whole + * images. + * \return A new image with the differences highlighted. + * + * Regions outside of the region of interest are shaded with black, matching + * pixels are shaded with green, and differing pixels are shaded with + * bright red. + * + * This function hard-fails if clip_rect is not inside both images. If clip_rect + * is given, the images do not have to match in size, otherwise size mismatch + * will be a hard failure. + */ +pixman_image_t * +visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, + const struct rectangle *clip_rect) +{ + pixman_image_t *diffimg; + pixman_image_t *shade; + struct image_iterator it_a; + struct image_iterator it_b; + struct image_iterator it_d; + int width; + int height; + pixman_box32_t box; + int x, y; + uint32_t *pix_a; + uint32_t *pix_b; + uint32_t *pix_d; + pixman_color_t shade_color = { 0, 0, 0, 32768 }; + + width = pixman_image_get_width(img_a); + height = pixman_image_get_height(img_a); + box = image_check_get_roi(img_a, img_b, clip_rect); + + diffimg = pixman_image_create_bits_no_clear(PIXMAN_x8r8g8b8, + width, height, NULL, 0); + + /* Fill diffimg with a black-shaded copy of img_a, and then fill + * the clip_rect area with original img_a. + */ + shade = pixman_image_create_solid_fill(&shade_color); + pixman_image_composite32(PIXMAN_OP_SRC, img_a, shade, diffimg, + 0, 0, 0, 0, 0, 0, width, height); + pixman_image_unref(shade); + pixman_image_composite32(PIXMAN_OP_SRC, img_a, NULL, diffimg, + box.x1, box.y1, 0, 0, box.x1, box.y1, + box.x2 - box.x1, box.y2 - box.y1); + + image_iter_init(&it_a, img_a); + image_iter_init(&it_b, img_b); + image_iter_init(&it_d, diffimg); + + for (y = box.y1; y < box.y2; y++) { + pix_a = image_iter_get_row(&it_a, y) + box.x1; + pix_b = image_iter_get_row(&it_b, y) + box.x1; + pix_d = image_iter_get_row(&it_d, y) + box.x1; + + for (x = box.x1; x < box.x2; x++) { + if (*pix_a == *pix_b) + *pix_d = tint(*pix_d, 0x00008000); /* green */ + else + *pix_d = tint(*pix_d, 0x00c00000); /* red */ + + pix_a++; + pix_b++; + pix_d++; + } + } - return (memcmp(a->data, b->data, bpp * a->width * a->height) == 0); + return diffimg; } /** - * check_surfaces_match_in_clip() - tests if a given region within two - * surfaces are pixel-identical. + * Write an image into a PNG file. + * + * \param image The image. + * \param fname The name and path for the file. * - * Returns true if the two surfaces have the same byte values within the - * given clipping region, or false if they don't match or the surfaces - * can't be compared. + * \returns true if successfully saved file; false otherwise. + * + * \note Only image formats directly supported by Cairo are accepted, not all + * Pixman formats. */ bool -check_surfaces_match_in_clip(const struct surface *a, const struct surface *b, const struct rectangle *clip_rect) +write_image_as_png(pixman_image_t *image, const char *fname) { - int i, j; - int x0, y0, x1, y1; - void *p, *q; - int bpp = 4; /* Assumes ARGB */ + cairo_surface_t *cairo_surface; + cairo_status_t status; + cairo_format_t fmt; - if (!check_surfaces_geometry(a, b) || clip_rect == NULL) - return false; + fmt = format_pixman2cairo(pixman_image_get_format(image)); - if (clip_rect->x > a->width || clip_rect->y > a->height) { - printf("Clip outside image boundaries\n"); - return true; - } + cairo_surface = cairo_image_surface_create_for_data( + (void *)pixman_image_get_data(image), + fmt, + pixman_image_get_width(image), + pixman_image_get_height(image), + pixman_image_get_stride(image)); - x0 = max(0, clip_rect->x); - y0 = max(0, clip_rect->y); - x1 = min(a->width, clip_rect->x + clip_rect->width); - y1 = min(a->height, clip_rect->y + clip_rect->height); + status = cairo_surface_write_to_png(cairo_surface, fname); + if (status != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "Failed to save image '%s': %s\n", fname, + cairo_status_to_string(status)); - if (x0 == x1 || y0 == y1) { - printf("Degenerate comparison\n"); - return true; + return false; } - printf("Bytewise comparison inside clip\n"); - for (i=y0; i<y1; i++) { - p = a->data + i * a->width * bpp + x0 * bpp; - q = b->data + i * b->width * bpp + x0 * bpp; - if (memcmp(p, q, (x1-x0)*bpp) != 0) { - /* Dump the bad row */ - printf("Mismatched image on row %d\n", i); - for (j=0; j<(x1-x0)*bpp; j++) { - char a_char = *((char*)(p+j*bpp)); - char b_char = *((char*)(q+j*bpp)); - printf("%d,%d: %8x %8x %s\n", i, j, a_char, b_char, - (a_char != b_char)? " <---": ""); - } - return false; - } - } + cairo_surface_destroy(cairo_surface); return true; } + +static pixman_image_t * +image_convert_to_a8r8g8b8(pixman_image_t *image) +{ + pixman_image_t *ret; + int width; + int height; + + if (pixman_image_get_format(image) == PIXMAN_a8r8g8b8) + return pixman_image_ref(image); + + width = pixman_image_get_width(image); + height = pixman_image_get_height(image); + + ret = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, width, height, + NULL, 0); + assert(ret); + + pixman_image_composite32(PIXMAN_OP_SRC, image, NULL, ret, + 0, 0, 0, 0, 0, 0, width, height); + + return ret; +} + +static void +destroy_cairo_surface(pixman_image_t *image, void *data) +{ + cairo_surface_t *surface = data; + + cairo_surface_destroy(surface); +} + +/** + * Load an image from a PNG file + * + * Reads a PNG image from disk using the given filename (and path) + * and returns as a Pixman image. Use pixman_image_unref() to free it. + * + * The returned image is always in PIXMAN_a8r8g8b8 format. + * + * @returns Pixman image, or NULL in case of error. + */ +pixman_image_t * +load_image_from_png(const char *fname) +{ + pixman_image_t *image; + pixman_image_t *converted; + cairo_format_t cairo_fmt; + pixman_format_code_t pixman_fmt; + cairo_surface_t *reference_cairo_surface; + cairo_status_t status; + int width; + int height; + int stride; + void *data; + + reference_cairo_surface = cairo_image_surface_create_from_png(fname); + cairo_surface_flush(reference_cairo_surface); + status = cairo_surface_status(reference_cairo_surface); + if (status != CAIRO_STATUS_SUCCESS) { + printf("Could not open %s: %s\n", fname, cairo_status_to_string(status)); + cairo_surface_destroy(reference_cairo_surface); + return NULL; + } + + cairo_fmt = cairo_image_surface_get_format(reference_cairo_surface); + pixman_fmt = format_cairo2pixman(cairo_fmt); + + width = cairo_image_surface_get_width(reference_cairo_surface); + height = cairo_image_surface_get_height(reference_cairo_surface); + stride = cairo_image_surface_get_stride(reference_cairo_surface); + data = cairo_image_surface_get_data(reference_cairo_surface); + + /* The Cairo surface will own the data, so we keep it around. */ + image = pixman_image_create_bits_no_clear(pixman_fmt, + width, height, data, stride); + assert(image); + + pixman_image_set_destroy_function(image, destroy_cairo_surface, + reference_cairo_surface); + + converted = image_convert_to_a8r8g8b8(image); + pixman_image_unref(image); + + return converted; +} + +/** + * Take screenshot of a single output + * + * Requests a screenshot from the server of the output that the + * client appears on. This implies that the compositor goes through an output + * repaint to provide the screenshot before this function returns. This + * function is therefore both a server roundtrip and a wait for a repaint. + * + * @returns A new buffer object, that should be freed with buffer_destroy(). + */ +struct buffer * +capture_screenshot_of_output(struct client *client) +{ + struct buffer *buffer; + + buffer = create_shm_buffer_a8r8g8b8(client, + client->output->width, + client->output->height); + + client->test->buffer_copy_done = 0; + weston_test_capture_screenshot(client->test->weston_test, + client->output->wl_output, + buffer->proxy); + while (client->test->buffer_copy_done == 0) + if (wl_display_dispatch(client->wl_display) < 0) + break; + + /* FIXME: Document somewhere the orientation the screenshot is taken + * and how the clip coords are interpreted, in case of scaling/transform. + * If we're using read_pixels() just make sure it is documented somewhere. + * Protocol docs in the XML, comparison function docs in Doxygen style. + */ + + return buffer; +} |