diff options
author | Adrian Johnson <ajohnson@redneon.com> | 2023-01-16 08:59:32 +0000 |
---|---|---|
committer | Adrian Johnson <ajohnson@redneon.com> | 2023-01-16 08:59:32 +0000 |
commit | 745c3717aa8e91237bf90de1b2e908c72499aa0f (patch) | |
tree | fc50f38d44fdb67c751bcc4ac43668a38925c515 /test | |
parent | 6abc8076c9cf06c1e2b92fccf57210442d471f5c (diff) | |
parent | 7146358250975ec0f29b8ba80e80a26c52526bdc (diff) | |
download | cairo-745c3717aa8e91237bf90de1b2e908c72499aa0f.tar.gz |
Merge branch 'fix-shared-recording-surface' into 'master'
Fix shared use of recording surfaces
See merge request cairo/cairo!391
Diffstat (limited to 'test')
-rw-r--r-- | test/create-regions.c | 435 | ||||
-rw-r--r-- | test/meson.build | 1 | ||||
-rw-r--r-- | test/pdf-tagged-text.c | 5 | ||||
-rw-r--r-- | test/reference/create-regions.pdf.ref.png | bin | 0 -> 3227 bytes | |||
-rw-r--r-- | test/reference/create-regions.ps.ref.png | bin | 0 -> 2524 bytes | |||
-rw-r--r-- | test/reference/create-regions.svg.ref.png | bin | 0 -> 4514 bytes |
6 files changed, 441 insertions, 0 deletions
diff --git a/test/create-regions.c b/test/create-regions.c new file mode 100644 index 000000000..749595a44 --- /dev/null +++ b/test/create-regions.c @@ -0,0 +1,435 @@ +/* + * Copyright © 2023 Adrian Johnson + * + * 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 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. + * + * Author: Adrian Johnson <ajohnson@redneon.com> + */ + +#include "config.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <cairo.h> + +#if CAIRO_HAS_PDF_SURFACE && CAIRO_HAS_PS_SURFACE && CAIRO_HAS_SVG_SURFACE + +#include <cairo-pdf.h> +#include <cairo-ps.h> +#include <cairo-svg.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#include <errno.h> +#endif + +#include "cairo-test.h" +#include "buffer-diff.h" + +/* This test is to ensure that _cairo_recording_surface_replay_and_create_regions() + * works with a recording surface source re-used for multiple paginated surfaces. + * The prior use of the source with a paginated surface should not affect the region + * analysis on subsequent surfaces. + * + * If test output should only contain fallback images for unsupported + * operations. If the recording surface is incorrectly re-using the + * analysis from a different target, some operations may be missing + * from the ouput (recording surface marked the operation as supported + * when it is not) or some operations may be have fallbacks for + * natively suported opetions (recording surface marked a supported + * operation as unsupprted). + * + * To create ref images, run the test for one target at a time to + * prevent re-use of the recording surface for different targets. + */ + + +#define SIZE 40 +#define PAD 25 +#define PAGE_SIZE (SIZE*3 + PAD*4) + +/* Apply a slight rotation and use a very low fallback resolution to + * ensure fallback images are apparent in the output. */ +#define ROTATE 5 +#define FALLBACK_PPI 18 + +static void +create_recordings (cairo_operator_t op, + cairo_surface_t **recording, + cairo_surface_t **recording_group) +{ + *recording = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL); + cairo_t *cr = cairo_create (*recording); + + cairo_rotate (cr, ROTATE*M_PI/180.0); + + cairo_rectangle (cr, 0, 0, SIZE*3.0/4.0, SIZE*3.0/4.0); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_fill (cr); + + cairo_set_operator (cr, op); + + cairo_rectangle (cr, SIZE/4.0, SIZE/4.0, SIZE*3.0/4.0, SIZE*3.0/4.0); + cairo_set_source_rgb (cr, 0, 1, 0); + cairo_fill (cr); + + cairo_destroy (cr); + + *recording_group = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL); + cr = cairo_create (*recording_group); + + cairo_set_source_surface (cr, *recording, 0, 0); + cairo_paint (cr); + + cairo_destroy (cr); +} + +static void +_xunlink (const cairo_test_context_t *ctx, const char *pathname) +{ + if (unlink (pathname) < 0 && errno != ENOENT) { + cairo_test_log (ctx, "Error: Cannot remove %s: %s\n", + pathname, strerror (errno)); + exit (1); + } +} + +static cairo_bool_t +check_result (cairo_test_context_t *ctx, + const cairo_boilerplate_target_t *target, + const char *test_name, + const char *base_name, + cairo_surface_t *surface) +{ + const char *format; + char *ref_name; + char *png_name; + char *diff_name; + cairo_surface_t *test_image, *ref_image, *diff_image; + buffer_diff_result_t result; + cairo_status_t status; + cairo_bool_t ret; + + if (target->finish_surface != NULL) { + status = target->finish_surface (surface); + if (status) { + cairo_test_log (ctx, "Error: Failed to finish surface: %s\n", + cairo_status_to_string (status)); + cairo_surface_destroy (surface); + return FALSE; + } + } + + xasprintf (&png_name, "%s.out.png", base_name); + xasprintf (&diff_name, "%s.diff.png", base_name); + + test_image = target->get_image_surface (surface, 0, PAGE_SIZE, PAGE_SIZE); + if (cairo_surface_status (test_image)) { + cairo_test_log (ctx, "Error: Failed to extract page: %s\n", + cairo_status_to_string (cairo_surface_status (test_image))); + cairo_surface_destroy (test_image); + free (png_name); + free (diff_name); + return FALSE; + } + + _xunlink (ctx, png_name); + status = cairo_surface_write_to_png (test_image, png_name); + if (status) { + cairo_test_log (ctx, "Error: Failed to write output image: %s\n", + cairo_status_to_string (status)); + cairo_surface_destroy (test_image); + free (png_name); + free (diff_name); + return FALSE; + } + + format = cairo_boilerplate_content_name (target->content); + ref_name = cairo_test_reference_filename (ctx, + base_name, + test_name, + target->name, + target->basename, + format, + CAIRO_TEST_REF_SUFFIX, + CAIRO_TEST_PNG_EXTENSION); + if (ref_name == NULL) { + cairo_test_log (ctx, "Error: Cannot find reference image for %s\n", + base_name); + cairo_surface_destroy (test_image); + free (png_name); + free (diff_name); + return FALSE; + } + + ref_image = cairo_test_get_reference_image (ctx, ref_name, + target->content == CAIRO_TEST_CONTENT_COLOR_ALPHA_FLATTENED); + if (cairo_surface_status (ref_image)) { + cairo_test_log (ctx, "Error: Cannot open reference image for %s: %s\n", + ref_name, + cairo_status_to_string (cairo_surface_status (ref_image))); + cairo_surface_destroy (ref_image); + cairo_surface_destroy (test_image); + free (png_name); + free (diff_name); + free (ref_name); + return FALSE; + } + + diff_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + PAGE_SIZE, PAGE_SIZE); + + ret = TRUE; + status = image_diff (ctx, + test_image, ref_image, diff_image, + &result); + _xunlink (ctx, diff_name); + if (status) { + cairo_test_log (ctx, "Error: Failed to compare images: %s\n", + cairo_status_to_string (status)); + ret = FALSE; + } else if (image_diff_is_failure (&result, target->error_tolerance)) + { + ret = FALSE; + + status = cairo_surface_write_to_png (diff_image, diff_name); + if (status) { + cairo_test_log (ctx, "Error: Failed to write differences image: %s\n", + cairo_status_to_string (status)); + } + } + + cairo_surface_destroy (test_image); + cairo_surface_destroy (diff_image); + free (png_name); + free (diff_name); + free (ref_name); + + return ret; +} + +#define TEST_ROWS 2 +#define TEST_COLS 3 + +static void +draw (cairo_t *cr, cairo_surface_t *recordings[TEST_ROWS][TEST_COLS]) +{ + cairo_translate (cr, PAD, PAD); + for (int row = 0; row < TEST_ROWS; row++) { + cairo_save (cr); + for (int col = 0; col < TEST_COLS; col++) { + cairo_save (cr); + cairo_set_source_surface (cr, recordings[row][col], 0, 0); + cairo_paint (cr); + cairo_restore (cr); + cairo_translate (cr, SIZE + PAD, 0); + } + cairo_restore (cr); + cairo_translate (cr, 0, SIZE + PAD); + } +} + +#define NUM_TEST_SURFACES 3 + +typedef struct _test_surface { + const cairo_boilerplate_target_t *target; + cairo_surface_t *surface; + char *base_name; + void *closure; +} test_surface_t; + +static test_surface_t test_surfaces[NUM_TEST_SURFACES]; + +static void +init_test_surfaces() +{ + memset (test_surfaces, 0, sizeof (*test_surfaces)); + test_surfaces[0].surface = cairo_pdf_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE); + test_surfaces[1].surface = cairo_ps_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE); + test_surfaces[2].surface = cairo_svg_surface_create_for_stream (NULL, NULL, PAGE_SIZE, PAGE_SIZE); +} + +static void +add_test_surface (const cairo_boilerplate_target_t *target, + cairo_surface_t *surface, + char *base_name, + void *closure) +{ + for (int i = 0; i < NUM_TEST_SURFACES; i++) { + if (cairo_surface_get_type (test_surfaces[i].surface) == cairo_surface_get_type (surface)) { + cairo_surface_destroy (test_surfaces[i].surface); + test_surfaces[i].target = target; + test_surfaces[i].surface = surface; + test_surfaces[i].base_name = base_name; + test_surfaces[i].closure = closure; + break; + } + } +} + +static void +destroy_test_surfaces() +{ + for (int i = 0; i < NUM_TEST_SURFACES; i++) { + cairo_surface_destroy (test_surfaces[i].surface); + if (test_surfaces[i].target && test_surfaces[i].target->cleanup) + test_surfaces[i].target->cleanup (test_surfaces[i].closure); + if (test_surfaces[i].base_name) + free (test_surfaces[i].base_name); + } +} + + +static cairo_test_status_t +preamble (cairo_test_context_t *ctx) +{ + cairo_t *cr; + cairo_test_status_t ret = CAIRO_TEST_UNTESTED; + unsigned int i; + const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : "."; + cairo_surface_t *recordings[TEST_ROWS][TEST_COLS]; + const char *test_name = "create-regions"; + char *base_name; + + /* Each row displays three recordings. One with operations + * supported by all paginated surfaces (OVER), one with operations + * supported by PDF but not PS or SVG surfaces (DIFFERENCE), and + * one with operations supported by SVG but not PDF or PS + * (DEST_XOR). + * + * The recordings for the first row is a single recording. The + * recordings for the second row contains the first row recording + * inside another recording to test the use of cloned recording + * surfaces. + * + * We are looking to see that fallback images are only used for + * unsupported operations. + */ + create_recordings (CAIRO_OPERATOR_OVER, &recordings[0][0], &recordings[1][0]); + create_recordings (CAIRO_OPERATOR_DIFFERENCE, &recordings[0][1], &recordings[1][1]); + create_recordings (CAIRO_OPERATOR_XOR, &recordings[0][2], &recordings[1][2]); + + init_test_surfaces(); + for (i = 0; i < ctx->num_targets; i++) { + const cairo_boilerplate_target_t *target = ctx->targets_to_test[i]; + cairo_surface_t *surface = NULL; + void *closure; + const char *format; + + /* This test only works on surfaces that support fine grained fallbacks */ + if (! (target->expected_type == CAIRO_SURFACE_TYPE_PDF || + target->expected_type == CAIRO_SURFACE_TYPE_PS || + target->expected_type ==CAIRO_SURFACE_TYPE_SVG)) + continue; + + if (! cairo_test_is_target_enabled (ctx, target->name)) + continue; + + if (ret == CAIRO_TEST_UNTESTED) + ret = CAIRO_TEST_SUCCESS; + + format = cairo_boilerplate_content_name (target->content); + base_name = NULL; + xasprintf (&base_name, "%s/%s.%s.%s", + path, test_name, + target->name, + format); + + surface = (target->create_surface) (base_name, + target->content, + PAGE_SIZE, PAGE_SIZE, + PAGE_SIZE, PAGE_SIZE, + CAIRO_BOILERPLATE_MODE_TEST, + &closure); + if (surface == NULL || cairo_surface_status (surface)) { + cairo_test_log (ctx, "Failed to generate surface: %s.%s\n", + target->name, + format); + ret = CAIRO_TEST_FAILURE; + break; + } + + cairo_surface_set_fallback_resolution (surface, FALLBACK_PPI, FALLBACK_PPI); + add_test_surface (target, surface, base_name, closure); + } + + for (int i = 0; i < NUM_TEST_SURFACES; i++) { + cairo_status_t status; + + if (test_surfaces[i].target != NULL) { + cairo_test_log (ctx, + "Testing create-regions with %s target\n", + test_surfaces[i].target->name); + printf ("%s:\t", test_surfaces[i].base_name); + fflush (stdout); + } + + cr = cairo_create (test_surfaces[i].surface); + + draw (cr, recordings); + + status = cairo_status (cr); + cairo_destroy (cr); + cairo_surface_finish (test_surfaces[i].surface); + + if (test_surfaces[i].target) { + cairo_bool_t pass = FALSE; + if (status) { + cairo_test_log (ctx, "Error: Failed to create target surface: %s\n", + cairo_status_to_string (status)); + ret = CAIRO_TEST_FAILURE; + } else { + /* extract the image and compare it to our reference */ + if (! check_result (ctx, test_surfaces[i].target, test_name, test_surfaces[i].base_name, test_surfaces[i].surface)) + ret = CAIRO_TEST_FAILURE; + else + pass = TRUE; + } + + if (pass) { + printf ("PASS\n"); + } else { + printf ("FAIL\n"); + } + fflush (stdout); + } + } + + destroy_test_surfaces(); + + for (int row = 0; row < TEST_ROWS; row++) { + for (int col = 0; col < TEST_COLS; col++) { + cairo_surface_destroy (recordings[row][col]); + } + } + + return ret; +} + +CAIRO_TEST (create_regions, + "Check region analysis when re-used with different surfaces", + "fallback", /* keywords */ + NULL, /* requirements */ + 0, 0, + preamble, NULL) + +#endif /* CAIRO_HAS_PDF_SURFACE && CAIRO_HAS_PS_SURFACE && CAIRO_HAS_SVG_SURFACE */ diff --git a/test/meson.build b/test/meson.build index e670a3937..de6b30c4f 100644 --- a/test/meson.build +++ b/test/meson.build @@ -493,6 +493,7 @@ test_multi_page_sources = [ ] test_fallback_resolution_sources = [ + 'create-regions.c', 'fallback-resolution.c', ] diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c index 378e6a920..1a2f62dac 100644 --- a/test/pdf-tagged-text.c +++ b/test/pdf-tagged-text.c @@ -42,6 +42,9 @@ #endif #include <cairo.h> + +#if CAIRO_HAS_PDF_SURFACE + #include <cairo-pdf.h> /* This test checks PDF with @@ -604,3 +607,5 @@ CAIRO_TEST (pdf_tagged_text, NULL, /* requirements */ 0, 0, preamble, NULL) + +#endif /* CAIRO_HAS_PDF_SURFACE */ diff --git a/test/reference/create-regions.pdf.ref.png b/test/reference/create-regions.pdf.ref.png Binary files differnew file mode 100644 index 000000000..5762fbbc7 --- /dev/null +++ b/test/reference/create-regions.pdf.ref.png diff --git a/test/reference/create-regions.ps.ref.png b/test/reference/create-regions.ps.ref.png Binary files differnew file mode 100644 index 000000000..0478e249d --- /dev/null +++ b/test/reference/create-regions.ps.ref.png diff --git a/test/reference/create-regions.svg.ref.png b/test/reference/create-regions.svg.ref.png Binary files differnew file mode 100644 index 000000000..a89dfb044 --- /dev/null +++ b/test/reference/create-regions.svg.ref.png |