Clip invariance issue

[email protected] (Søren Sandmann)
Newsgroups gmane.comp.lib.cairo
Message-ID <[email protected]>
Hi,

The attached program demonstrates an issue with the image surface (it
likely exists with other surface types as well, but I haven't tested).

The issue is that rendering a polygon incrementally through a set of
clip boxes that combine to fill the entire surface produces different
results than rendering the same polygon without clipping. This can lead
to artefacts in cases where the application relies on partial repaints,
such as when receiving an expose event from the X server or when
scrolling.

The program generates a complex polygon and a complex clip region, and
then it renders the polygon once through the clip and then once again
through the inverse of the clip. The output is then compared to the same
polygon rendered without clipping, and if they differ, it reports the
maximum difference. The two images are saved as image{1,2}.png for easy
comparison.

A typical max difference is around 30, which seems quite bad even if it
is decided that cairo will not guarantee pixel-exact matches for this
usecase.


Søren

--
cairo mailing list
[email protected]
http://lists.cairographics.org/mailman/listinfo/cairo
clip-invariance.c (text/x-csrc, 6.9 KB)
/*
 * Copyright © 2006 M Joonas Pihlaja
 * Copyright © 2011 Chris Wilson
 * Copyright © 2012 Soren Sandmann
 *
 * 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.
 *
 * Authors:
 *   M Joonas Pihlaja <[email protected]>
 *   Chris Wilson <[email protected]>
 *   Soren Sandmann <[email protected]>
 */
#include <cairo.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define SIZE 512
#define STEP (512+2)
#define NUM_SEGMENTS 128
#define NUM_RECTANGLES 25

#define TRUE 1
#define FALSE 0

static uint32_t state;

static double
uniform_random (double minval, double maxval)
{
    static uint32_t const poly = 0x9a795537U;
    uint32_t n = 32;
    while (n-->0)
	state = 2*state < state ? (2*state ^ poly) : 2*state;
    return minval + state * (maxval - minval) / 4294967296.0;
}

static uint32_t *
copy_image_data (cairo_surface_t *image)
{
    uint32_t *result;
    int height, stride;

    height = cairo_image_surface_get_height (image);
    stride = cairo_image_surface_get_stride (image);

    result = malloc (height * stride);

    memcpy (result, cairo_image_surface_get_data (image), height * stride);

    return result;
}

static cairo_bool_t
compare_image_to_data (cairo_surface_t *image, uint32_t *data)
{
    int w = cairo_image_surface_get_width (image);
    int h = cairo_image_surface_get_height (image);
    int stride = cairo_image_surface_get_stride (image);
    uint32_t *data2 = (uint32_t *)cairo_image_surface_get_data (image);
    int i, j;
    int maxdiff = 0;
    int max_x, max_y;
    int error = 0;
    uint32_t max1, max2;

    for (i = 0; i < h; ++i)
    {
	for (j = 0; j < w; ++j)
	{
	    uint32_t pixel1 = data[i * stride/4 + j];
	    uint32_t pixel2 = data2[i * stride/4 + j];
	    int a1, r1, g1, b1;
	    int a2, r2, g2, b2;

	    if (pixel1 != pixel2)
	    {
		a1 = (pixel1 >> 24) & 0xff;
		r1 = (pixel1 >> 16) & 0xff;
		g1 = (pixel1 >>  8) & 0xff;
		b1 = (pixel1 >>  0) & 0xff;

		a2 = (pixel2 >> 24) & 0xff;
		r2 = (pixel2 >> 16) & 0xff;
		g2 = (pixel2 >>  8) & 0xff;
		b2 = (pixel2 >>  0) & 0xff;

#define CHECK(c1, c2)							\
		do {							\
		    if (abs (c1 - c2) > maxdiff)			\
		    {							\
			maxdiff = abs (c1 - c2);			\
			max_x = j;					\
			max_y = i;					\
			max1 = pixel1;					\
			max2 = pixel2;					\
		    }							\
		} while (0)

		CHECK (a1, a2);
		CHECK (r1, r2);
		CHECK (g1, g2);
		CHECK (b1, b2);

		if (!error)
		{
		    printf ("== Error detected: %x != %x, at (%d, %d)\n",
			    pixel1, pixel2, j, i);

		    error = 1;
		}
	    }
	}
    }

    if (maxdiff)
    {
	printf ("== Maximum difference: %d, at (%d, %d). Pixels: %x %x\n", maxdiff, max_x, max_y, max1, max2);
	return FALSE;
    }

    return TRUE;
}

static cairo_region_t *
random_region (int size, uint32_t s)
{
    int i;
    cairo_region_t *region = cairo_region_create ();

    state = s;

    for (i = 0; i < NUM_RECTANGLES; ++i)
    {
	cairo_rectangle_int_t rect;

	rect.x = uniform_random (0, size);
	rect.y = uniform_random (0, size);
	rect.width = uniform_random (1, 100);
	rect.height = uniform_random (1, 100);

	cairo_region_union_rectangle (region, &rect);
    }

    return region;
}

static cairo_region_t *
region_invert (cairo_region_t *region, int size)
{
    cairo_rectangle_int_t rect = { 0, 0, size, size };
    cairo_region_t *inverted = cairo_region_create_rectangle (&rect);
    cairo_region_subtract (inverted, region);
    return inverted;
}

static void
clip_to_region (cairo_t *cr, cairo_region_t *region)
{
    int n;

    n = cairo_region_num_rectangles (region);
    while (n--)
    {
	cairo_rectangle_int_t rect;

	cairo_region_get_rectangle (region, n, &rect);

	cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height);
    }

    cairo_clip (cr);
}

static void
nz_path (cairo_t *cr, uint32_t s)
{
    int i;

    state = s;
    cairo_move_to (cr, 0, 0);
    for (i = 0; i < NUM_SEGMENTS; i++) {
	double x = uniform_random (0, SIZE);
	double y = uniform_random (0, SIZE);
	cairo_line_to (cr, x, y);
    }
    cairo_close_path (cr);
}

static void
nz_fill_stroke (cairo_t *cr, uint32_t state)
{
    nz_path (cr, state);

    cairo_set_source_rgb (cr, 1, 0, 0);
    cairo_fill_preserve (cr);
    cairo_set_source_rgb (cr, 0, 1, 0);
    cairo_set_line_width (cr, 1.0);
    cairo_stroke (cr);
}

#define NUM_TESTS 2000

static void
paint_region (cairo_t *cr, cairo_region_t *region)
{
    int n = cairo_region_num_rectangles (region);
    while (n--)
    {
	cairo_rectangle_int_t rect;

	cairo_region_get_rectangle (region, n, &rect);

	cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height);

	cairo_set_source_rgba (cr, 0, 0, 0.4, 0.2);
	cairo_fill (cr);
    }
}

static void
run_test (cairo_t *cr, int width, int height)
{
    cairo_surface_t *surface;
    int i;

    cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);

    surface = cairo_get_target (cr);

    for (i = 0; i < NUM_TESTS; ++i)
    {
	double x, y;
	cairo_region_t *region, *inverted;
	uint32_t *data;

	printf ("Test %d\n", i);
	cairo_set_source_rgb (cr, 0, 0, 0.2);
	cairo_paint (cr);

	cairo_reset_clip (cr);
	region = random_region (width, i + 1);
	clip_to_region (cr, region);
	nz_fill_stroke (cr, i + 1);

	cairo_reset_clip (cr);
	inverted = region_invert (region, width);
	clip_to_region (cr, inverted);
	nz_fill_stroke (cr, i + 1);

	paint_region (cr, region);

	cairo_surface_write_to_png (surface, "image1.png");

	data = copy_image_data (surface);

	cairo_reset_clip (cr);
	cairo_set_source_rgb (cr, 0, 0, 0.2);
	cairo_paint (cr);

	nz_fill_stroke (cr, i + 1);

	cairo_surface_write_to_png (surface, "image2.png");

	if (!compare_image_to_data (surface, data))
	{
#if 0
	    printf ("Error detected. Images in \"image1.png\" and \"image2.png\"\n");

	    exit (-1);
#endif
	}

	cairo_region_destroy (inverted);
	cairo_region_destroy (region);
	free (data);
    }
}

int
main ()
{
    cairo_surface_t *surface =
	cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 512, 512);
    cairo_t *cr = cairo_create (surface);

    run_test (cr, SIZE, SIZE);
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.