cairo/test/create-regions.c

436 lines
14 KiB
C
Raw Normal View History

/*
* 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 */