rsvg-test.c 12.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/* vim: set sw=4 sts=4: -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 8 -*-
 *
 * rsvg-test - Regression test utility for librsvg
 *
 * Copyright © 2004 Richard D. Worth
 * Copyright © 2006 Red Hat, Inc.
 * Copyright © 2007 Emmanuel Pacaud
 *
 * 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 authors
 * not be used in advertising or publicity pertaining to distribution
 * of the software without specific, written prior permission.
 * The authors make no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 *
 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL THE AUTHORS 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.
 *
 * Authors: Emmanuel Pacaud <emmanuel.pacaud@lapp.in2p3.fr>
 *	    Richard D. Worth <richard@theworths.org>
 *	    Carl Worth <cworth@cworth.org>
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
37
#include <glib/gstdio.h>
38

39
#include "librsvg/rsvg.h"
40

41
#include "test-utils.h"
42

43 44
static char *_output_dir;

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
typedef struct _buffer_diff_result {
    unsigned int pixels_changed;
    unsigned int max_diff;
} buffer_diff_result_t;

/* Compare two buffers, returning the number of pixels that are
 * different and the maximum difference of any single color channel in
 * result_ret.
 *
 * This function should be rewritten to compare all formats supported by
 * cairo_format_t instead of taking a mask as a parameter.
 */
static void
buffer_diff_core (unsigned char *_buf_a,
		  unsigned char *_buf_b,
		  unsigned char *_buf_diff,
		  int		width,
		  int		height,
		  int		stride,
64
		  guint32       mask,
65 66 67
		  buffer_diff_result_t *result_ret)
{
    int x, y;
68
    guint32 *row_a, *row_b, *row;
69
    buffer_diff_result_t result = {0, 0};
70 71 72
    guint32 *buf_a = (guint32 *) _buf_a;
    guint32 *buf_b = (guint32 *) _buf_b;
    guint32 *buf_diff = (guint32 *) _buf_diff;
73

74
    stride /= sizeof(guint32);
75 76 77 78 79 80 81 82 83 84
    for (y = 0; y < height; y++)
    {
	row_a = buf_a + y * stride;
	row_b = buf_b + y * stride;
	row = buf_diff + y * stride;
	for (x = 0; x < width; x++)
	{
	    /* check if the pixels are the same */
	    if ((row_a[x] & mask) != (row_b[x] & mask)) {
		int channel;
85
		guint32 diff_pixel = 0;
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

		/* calculate a difference value for all 4 channels */
		for (channel = 0; channel < 4; channel++) {
		    int value_a = (row_a[x] >> (channel*8)) & 0xff;
		    int value_b = (row_b[x] >> (channel*8)) & 0xff;
		    unsigned int diff;
		    diff = abs (value_a - value_b);
		    if (diff > result.max_diff)
			result.max_diff = diff;
		    diff *= 4;  /* emphasize */
		    if (diff)
		        diff += 128; /* make sure it's visible */
		    if (diff > 255)
		        diff = 255;
		    diff_pixel |= diff << (channel*8);
		}

		result.pixels_changed++;
104 105 106 107 108
		if ((diff_pixel & 0x00ffffff) == 0) {
		    /* alpha only difference, convert to luminance */
		    guint8 alpha = diff_pixel >> 24;
		    diff_pixel = alpha * 0x010101;
		}
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
		row[x] = diff_pixel;
	    } else {
		row[x] = 0;
	    }
	    row[x] |= 0xff000000; /* Set ALPHA to 100% (opaque) */
	}
    }

    *result_ret = result;
}

static void
compare_surfaces (cairo_surface_t	*surface_a,
		  cairo_surface_t	*surface_b,
		  cairo_surface_t	*surface_diff,
		  buffer_diff_result_t	*result)
{
126 127
    /* Here, we run cairo's old buffer_diff algorithm which looks for
     * pixel-perfect images.
128 129 130 131 132 133 134 135 136 137 138 139
     */
    buffer_diff_core (cairo_image_surface_get_data (surface_a),
		      cairo_image_surface_get_data (surface_b),
		      cairo_image_surface_get_data (surface_diff),
		      cairo_image_surface_get_width (surface_a),
		      cairo_image_surface_get_height (surface_a),
		      cairo_image_surface_get_stride (surface_a),
		      0xffffffff,
		      result);
    if (result->pixels_changed == 0)
	return;

140 141
    g_test_message ("%d pixels differ (with maximum difference of %d) from reference image\n",
		    result->pixels_changed, result->max_diff);
142 143
}

144 145 146
static char *
get_output_dir (void) {
    if (_output_dir == NULL) {
147 148
	char *cwd = g_get_current_dir ();
        _output_dir = g_strconcat (cwd, G_DIR_SEPARATOR_S, "output", NULL);
149
        g_mkdir (_output_dir, 0777);
150
	g_free (cwd);
151 152 153 154
    }
    return _output_dir;
}

155 156 157 158
static char *
get_output_file (const char *test_file,
                 const char *extension)
{
159
  const char *output_dir = get_output_dir ();
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
  char *result, *base;

  base = g_path_get_basename (test_file);

  if (g_str_has_suffix (base, ".svg"))
    base[strlen (base) - strlen (".svg")] = '\0';

  result = g_strconcat (output_dir, G_DIR_SEPARATOR_S, base, extension, NULL);
  g_free (base);

  return result;
}

static void
save_image (cairo_surface_t *surface,
            const char      *test_name,
            const char      *extension)
{
  char *filename = get_output_file (test_name, extension);

  g_test_message ("Storing test result image at %s", filename);
  g_assert (cairo_surface_write_to_png (surface, filename) == CAIRO_STATUS_SUCCESS);

  g_free (filename);
}

186 187 188
static gboolean
is_svg_or_subdir (GFile *file)
{
189 190 191
    char *basename;
    gboolean ignore;
    gboolean result;
192

193
    result = FALSE;
194

195
    basename = g_file_get_basename (file);
196
    ignore = g_str_has_prefix (basename, "ignore") || strcmp (basename, "resources") == 0;
197

198 199 200 201 202 203 204 205 206 207 208 209 210 211
    if (ignore)
	goto out;

    if (g_file_query_file_type (file, 0, NULL) == G_FILE_TYPE_DIRECTORY) {
	result = TRUE;
	goto out;
    }

    result = g_str_has_suffix (basename, ".svg");

out:
    g_free (basename);

    return result;
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
}

static cairo_status_t
read_from_stream (void          *stream,
                  unsigned char *data,
                  unsigned int   length)

{
  gssize result;
  GError *error = NULL;

  result = g_input_stream_read (stream, data, length, NULL, &error);
  g_assert_no_error (error);
  g_assert (result == length);

  return CAIRO_STATUS_SUCCESS;
}

static cairo_surface_t *
read_png (const char *test_name)
{
  char *reference_uri;
  GFileInputStream *stream;
  GFile *file;
  GError *error = NULL;
  cairo_surface_t *surface;

  reference_uri = g_strconcat (test_name, "-ref.png", NULL);
  file = g_file_new_for_uri (reference_uri);
241 242
  g_free (reference_uri);

243 244 245 246 247 248 249 250 251 252 253 254
  stream = g_file_read (file, NULL, &error);
  g_assert_no_error (error);
  g_assert (stream);

  surface = cairo_image_surface_create_from_png_stream (read_from_stream, stream);

  g_object_unref (stream);
  g_object_unref (file);

  return surface;
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
static cairo_surface_t *
extract_rectangle (cairo_surface_t *source,
		   int x,
		   int y,
		   int w,
		   int h)
{
    cairo_surface_t *dest;
    cairo_t *cr;

    dest = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
    cr = cairo_create (dest);
    cairo_set_source_surface (cr, source, -x, -y);

    cairo_paint (cr);
    cairo_destroy (cr);

    return dest;
}

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
/*
 * Report that a test would have failed if we used stricter criteria,
 * but that we are tolerating it for a reason given in @message.
 *
 * This is the same as g_test_incomplete(), but with a workaround for
 * GNOME/glib#1474 so that we don't fail tests on older GLib.
 */
static void
test_tolerate (const gchar *message)
{
    if (glib_check_version (2, 57, 3)) {
        /* In GLib >= 2.57.3, g_test_incomplete() behaves as intended:
         * the test result is reported as an expected failure and the
         * overall test exits 0 */
        g_test_incomplete (message);
    }
    else {
        /* In GLib < 2.57.3, g_test_incomplete() reported the wrong TAP
         * result (an unexpected success) and the overall test exited 1,
         * which would break "make check". g_test_skip() is the next
         * best thing available. */
        g_test_skip (message);
    }
}

300 301 302 303 304 305 306 307 308 309 310
// https://gitlab.gnome.org/GNOME/librsvg/issues/91
//
// We were computing some offsets incorrectly if the initial transformation matrix
// passed to rsvg_handle_render_cairo() was not the identity matrix.  So,
// we create a surface with a "frame" around the destination for the image,
// and then only consider the pixels inside the frame.  This will require us
// to have a non-identity transformation (i.e. a translation matrix), which
// will test for this bug.
//
// The frame size is meant to be a ridiculous number to simulate an arbitrary
// offset.
311
#define FRAME_SIZE 47
312

313
static void
314
rsvg_cairo_check (gconstpointer data)
315
{
316
    GFile *test_file = G_FILE (data);
317 318 319
    RsvgHandle *rsvg;
    RsvgDimensionData dimensions;
    cairo_t *cr;
320
    cairo_surface_t *render_surface;
321 322
    cairo_surface_t *surface_a, *surface_b, *surface_diff;
    buffer_diff_result_t result;
323
    char *test_file_base;
324 325
    unsigned int width_a, height_a, stride_a;
    unsigned int width_b, height_b, stride_b;
326
    GError *error = NULL;
327

328 329 330
    test_file_base = g_file_get_uri (test_file);
    if (g_str_has_suffix (test_file_base, ".svg"))
      test_file_base[strlen (test_file_base) - strlen (".svg")] = '\0';
331

332 333
    rsvg = rsvg_handle_new_from_gfile_sync (test_file, 0, NULL, &error);
    g_assert_no_error (error);
334
    g_assert (rsvg != NULL);
335

336 337
    rsvg_handle_internal_set_testing (rsvg, TRUE);

338 339 340 341
    if (g_str_has_suffix (test_file_base, "-48dpi"))
      rsvg_handle_set_dpi_x_y (rsvg, 48.0, 48.0);
    else
      rsvg_handle_set_dpi_x_y (rsvg, 72.0, 72.0);
342
    rsvg_handle_get_dimensions (rsvg, &dimensions);
343 344
    g_assert (dimensions.width > 0);
    g_assert (dimensions.height > 0);
345 346 347 348 349 350

    render_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
						 dimensions.width + 2 * FRAME_SIZE,
						 dimensions.height + 2 * FRAME_SIZE);
    cr = cairo_create (render_surface);
    cairo_translate (cr, FRAME_SIZE, FRAME_SIZE);
351
    g_assert (rsvg_handle_render_cairo (rsvg, cr));
352 353 354 355 356 357 358 359

    surface_a = extract_rectangle (render_surface,
				   FRAME_SIZE,
				   FRAME_SIZE,
				   dimensions.width,
				   dimensions.height);
    cairo_surface_destroy (render_surface);

360
    save_image (surface_a, test_file_base, "-out.png");
361

362
    surface_b = read_png (test_file_base);
363 364 365 366 367 368 369 370 371 372
    width_a = cairo_image_surface_get_width (surface_a);
    height_a = cairo_image_surface_get_height (surface_a);
    stride_a = cairo_image_surface_get_stride (surface_a);
    width_b = cairo_image_surface_get_width (surface_b);
    height_b = cairo_image_surface_get_height (surface_b);
    stride_b = cairo_image_surface_get_stride (surface_b);

    if (width_a  != width_b  ||
	height_a != height_b ||
	stride_a != stride_b) {
373 374 375
        g_test_fail ();
        g_test_message ("Image size mismatch (%dx%d != %dx%d)\n",
                        width_a, height_a, width_b, height_b); 
376 377
    }
    else {
378 379 380
#ifdef __x86_64__
	const unsigned int MAX_DIFF = 2;
#else
381 382 383
        /* https://gitlab.gnome.org/GNOME/librsvg/issues/178,
         * https://gitlab.gnome.org/GNOME/librsvg/issues/366 */
	const unsigned int MAX_DIFF = 20;
384
#endif
385
	const unsigned int WARN_DIFF = 2;
386

387 388 389 390 391
	surface_diff = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
						   dimensions.width, dimensions.height);

	compare_surfaces (surface_a, surface_b, surface_diff, &result);

392
	if (result.pixels_changed && result.max_diff > MAX_DIFF) {
393
            g_test_fail ();
394
            save_image (surface_diff, test_file_base, "-diff.png");
395
	}
396 397 398 399
        else if (result.pixels_changed && result.max_diff > WARN_DIFF) {
            test_tolerate ("not the same as x86_64, but giving it the benefit of the doubt");
            save_image (surface_diff, test_file_base, "-diff.png");
	}
400 401 402 403 404 405 406 407

	cairo_surface_destroy (surface_diff);
    }

    cairo_surface_destroy (surface_a);
    cairo_surface_destroy (surface_b);
    cairo_destroy (cr);

408
    g_object_unref (rsvg);
409
    g_free (test_file_base);
410 411 412 413 414
}

int
main (int argc, char **argv)
{
415
    int result;
416

417
    /* For systemLanguage attribute tests */
418
    g_setenv ("LC_ALL", "de:en_US:en", TRUE);
419

420
    g_test_init (&argc, &argv, NULL);
421

422 423
    if (argc < 2) {
        GFile *base, *tests;
424

425 426
        base = g_file_new_for_path (test_utils_get_test_data_path ());
        tests = g_file_get_child (base, "reftests");
427
        test_utils_add_test_for_all_files ("/rsvg-test/reftests", tests, tests, rsvg_cairo_check, is_svg_or_subdir);
428 429
        g_object_unref (tests);
        g_object_unref (base);
430
    } else {
431 432 433 434 435
        guint i;

        for (i = 1; i < argc; i++) {
            GFile *file = g_file_new_for_commandline_arg (argv[i]);

436
            test_utils_add_test_for_all_files ("/rsvg-test/reftests", NULL, file, rsvg_cairo_check, is_svg_or_subdir);
437 438 439

            g_object_unref (file);
        }
440
    }
441

442 443
    result = g_test_run ();

444 445
    rsvg_cleanup ();

446
    return result;
447 448
}