[test] Add composite test from rendercheck
authorChris Wilson <chris@chris-wilson.co.uk>
Sun, 13 Sep 2009 14:07:08 +0000 (15:07 +0100)
committerChris Wilson <chris@chris-wilson.co.uk>
Sun, 13 Sep 2009 15:33:01 +0000 (16:33 +0100)
Iterate over all destination formats for dst, src and composite and
compare the result of all oprators with a selection of colours.

test/Makefile.am
test/composite.c [new file with mode: 0644]

index c56f62d..784abe8 100644 (file)
@@ -5,6 +5,7 @@ TESTPROGRAMS =                  \
        region-test             \
        scaling-test            \
        blitters-test           \
+       composite               \
        fetch-test              \
        oob-test                \
        window-test             \
@@ -14,6 +15,7 @@ fetch_test_LDADD = $(TEST_LDADD)
 region_test_LDADD = $(TEST_LDADD)
 scaling_test_LDADD = $(TEST_LDADD)
 blitters_test_LDADD = $(TEST_LDADD)
+composite_LDADD = $(TEST_LDADD)
 trap_crasher_LDADD = $(TEST_LDADD)
 oob_test_LDADD = $(TEST_LDADD)
 window_test_LDADD = $(TEST_LDADD)
diff --git a/test/composite.c b/test/composite.c
new file mode 100644 (file)
index 0000000..f1df0c8
--- /dev/null
@@ -0,0 +1,798 @@
+/*
+ * Copyright © 2005 Eric Anholt
+ * Copyright © 2009 Chris Wilson
+ *
+ * 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 Eric Anholt not be used in
+ * advertising or publicity pertaining to distribution of the software without
+ * specific, written prior permission.  Eric Anholt makes no
+ * representations about the suitability of this software for any purpose.  It
+ * is provided "as is" without express or implied warranty.
+ *
+ * ERIC ANHOLT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL ERIC ANHOLT 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.
+ */
+
+#include <pixman.h>
+#include <stdio.h>
+#include <stdlib.h> /* abort() */
+#include <math.h>
+
+#define FALSE 0
+#define TRUE !FALSE
+
+#define ARRAY_LENGTH(A) ((int) (sizeof (A) / sizeof ((A) [0])))
+#define min(a,b) ((a) <= (b) ? (a) : (b))
+#define max(a,b) ((a) >= (b) ? (a) : (b))
+
+static struct color {
+    double r,g,b,a;
+} colors[] = {
+    /* these are premultiplied in main() */
+    {1.0, 1.0, 1.0, 1.0},
+    {1.0, 0.0, 0.0, 1.0},
+    {0.0, 1.0, 0.0, 1.0},
+    {0.0, 0.0, 1.0, 1.0},
+    {0.0, 0.0, 0.0, 1.0},
+    {0.5, 0.0, 0.0, 0.5},
+};
+
+static uint16_t
+_color_double_to_short (double d)
+{
+    uint32_t i;
+
+    i = (uint32_t) (d * 65536);
+    i -= (i >> 16);
+
+    return i;
+}
+
+static void
+compute_pixman_color (const struct color *color,
+                     pixman_color_t *out)
+{
+    out->red   = _color_double_to_short (color->r);
+    out->green = _color_double_to_short (color->g);
+    out->blue  = _color_double_to_short (color->b);
+    out->alpha = _color_double_to_short (color->a);
+}
+
+static const struct format {
+    pixman_format_code_t format;
+    const char *name;
+} formats[] = {
+#define P(x) { PIXMAN_##x, #x }
+    P(a8),
+
+    /* 32bpp formats */
+    P(a8r8g8b8),
+    P(x8r8g8b8),
+    P(a8b8g8r8),
+    P(x8b8g8r8),
+    P(b8g8r8a8),
+    P(b8g8r8x8),
+
+    /* XXX: and here the errors begin! */
+#if 0
+    P(x2r10g10b10),
+    P(a2r10g10b10),
+    P(x2b10g10r10),
+    P(a2b10g10r10),
+
+    /* 24bpp formats */
+    P(r8g8b8),
+    P(b8g8r8),
+
+    /* 16bpp formats */
+    P(r5g6b5),
+    P(b5g6r5),
+
+    P(a1r5g5b5),
+    P(x1r5g5b5),
+    P(a1b5g5r5),
+    P(x1b5g5r5),
+    P(a4r4g4b4),
+    P(x4r4g4b4),
+    P(a4b4g4r4),
+    P(x4b4g4r4),
+
+    /* 8bpp formats */
+    P(a8),
+    P(r3g3b2),
+    P(b2g3r3),
+    P(a2r2g2b2),
+    P(a2b2g2r2),
+
+    P(x4a4),
+
+    /* 4bpp formats */
+    P(a4),
+    P(r1g2b1),
+    P(b1g2r1),
+    P(a1r1g1b1),
+    P(a1b1g1r1),
+
+    /* 1bpp formats */
+    P(a1)
+#endif
+#undef P
+};
+
+struct image {
+    pixman_image_t *image;
+    const struct format *format;
+    const struct color *color;
+    int size;
+};
+
+static const struct operator {
+    pixman_op_t op;
+    const char *name;
+} operators[] = {
+#define P(x) { PIXMAN_OP_##x, #x }
+    P(CLEAR),
+    P(SRC),
+    P(DST),
+    P(OVER),
+    P(OVER_REVERSE),
+    P(IN),
+    P(IN_REVERSE),
+    P(OUT),
+    P(OUT_REVERSE),
+    P(ATOP),
+    P(ATOP_REVERSE),
+    P(XOR),
+    P(ADD),
+    P(SATURATE),
+
+    P(DISJOINT_CLEAR),
+    P(DISJOINT_SRC),
+    P(DISJOINT_DST),
+    P(DISJOINT_OVER),
+    P(DISJOINT_OVER_REVERSE),
+    P(DISJOINT_IN),
+    P(DISJOINT_IN_REVERSE),
+    P(DISJOINT_OUT),
+    P(DISJOINT_OUT_REVERSE),
+    P(DISJOINT_ATOP),
+    P(DISJOINT_ATOP_REVERSE),
+    P(DISJOINT_XOR),
+
+    P(CONJOINT_CLEAR),
+    P(CONJOINT_SRC),
+    P(CONJOINT_DST),
+    P(CONJOINT_OVER),
+    P(CONJOINT_OVER_REVERSE),
+    P(CONJOINT_IN),
+    P(CONJOINT_IN_REVERSE),
+    P(CONJOINT_OUT),
+    P(CONJOINT_OUT_REVERSE),
+    P(CONJOINT_ATOP),
+    P(CONJOINT_ATOP_REVERSE),
+    P(CONJOINT_XOR),
+#undef P
+};
+
+static double
+calc_op (pixman_op_t op, double src, double dst, double srca, double dsta)
+{
+#define mult_chan(src, dst, Fa, Fb) min ((src) * (Fa) + (dst) * (Fb), 1.0)
+
+    double Fa, Fb;
+
+    switch (op) {
+    case PIXMAN_OP_CLEAR:
+    case PIXMAN_OP_DISJOINT_CLEAR:
+    case PIXMAN_OP_CONJOINT_CLEAR:
+       return mult_chan (src, dst, 0.0, 0.0);
+
+    case PIXMAN_OP_SRC:
+    case PIXMAN_OP_DISJOINT_SRC:
+    case PIXMAN_OP_CONJOINT_SRC:
+       return mult_chan (src, dst, 1.0, 0.0);
+
+    case PIXMAN_OP_DST:
+    case PIXMAN_OP_DISJOINT_DST:
+    case PIXMAN_OP_CONJOINT_DST:
+       return mult_chan (src, dst, 0.0, 1.0);
+
+    case PIXMAN_OP_OVER:
+       return mult_chan (src, dst, 1.0, 1.0 - srca);
+
+    case PIXMAN_OP_OVER_REVERSE:
+       return mult_chan (src, dst, 1.0 - dsta, 1.0);
+
+    case PIXMAN_OP_IN:
+       return mult_chan (src, dst, dsta, 0.0);
+
+    case PIXMAN_OP_IN_REVERSE:
+       return mult_chan (src, dst, 0.0, srca);
+
+    case PIXMAN_OP_OUT:
+       return mult_chan (src, dst, 1.0 - dsta, 0.0);
+
+    case PIXMAN_OP_OUT_REVERSE:
+       return mult_chan (src, dst, 0.0, 1.0 - srca);
+
+    case PIXMAN_OP_ATOP:
+       return mult_chan (src, dst, dsta, 1.0 - srca);
+
+    case PIXMAN_OP_ATOP_REVERSE:
+       return mult_chan (src, dst, 1.0 - dsta,  srca);
+
+    case PIXMAN_OP_XOR:
+       return mult_chan (src, dst, 1.0 - dsta, 1.0 - srca);
+
+    case PIXMAN_OP_ADD:
+       return mult_chan (src, dst, 1.0, 1.0);
+
+    case PIXMAN_OP_SATURATE:
+    case PIXMAN_OP_DISJOINT_OVER_REVERSE:
+       if (srca == 0.0)
+           Fa = 1.0;
+       else
+           Fa = min (1.0, (1.0 - dsta) / srca);
+       return mult_chan (src, dst, Fa, 1.0);
+
+    case PIXMAN_OP_DISJOINT_OVER:
+       if (dsta == 0.0)
+           Fb = 1.0;
+       else
+           Fb = min (1.0, (1.0 - srca) / dsta);
+       return mult_chan (src, dst, 1.0, Fb);
+
+    case PIXMAN_OP_DISJOINT_IN:
+       if (srca == 0.0)
+           Fa = 0.0;
+       else
+           Fa = max (0.0, 1.0 - (1.0 - dsta) / srca);
+       return mult_chan (src, dst, Fa, 0.0);
+
+    case PIXMAN_OP_DISJOINT_IN_REVERSE:
+       if (dsta == 0.0)
+           Fb = 0.0;
+       else
+           Fb = max (0.0, 1.0 - (1.0 - srca) / dsta);
+       return mult_chan (src, dst, 0.0, Fb);
+
+    case PIXMAN_OP_DISJOINT_OUT:
+       if (srca == 0.0)
+           Fa = 1.0;
+       else
+           Fa = min (1.0, (1.0 - dsta) / srca);
+       return mult_chan (src, dst, Fa, 0.0);
+
+    case PIXMAN_OP_DISJOINT_OUT_REVERSE:
+       if (dsta == 0.0)
+           Fb = 1.0;
+       else
+           Fb = min (1.0, (1.0 - srca) / dsta);
+       return mult_chan (src, dst, 0.0, Fb);
+
+    case PIXMAN_OP_DISJOINT_ATOP:
+       if (srca == 0.0)
+           Fa = 0.0;
+       else
+           Fa = max (0.0, 1.0 - (1.0 - dsta) / srca);
+       if (dsta == 0.0)
+           Fb = 1.0;
+       else
+           Fb = min (1.0, (1.0 - srca) / dsta);
+       return mult_chan (src, dst, Fa, Fb);
+
+    case PIXMAN_OP_DISJOINT_ATOP_REVERSE:
+       if (srca == 0.0)
+           Fa = 1.0;
+       else
+           Fa = min (1.0, (1.0 - dsta) / srca);
+       if (dsta == 0.0)
+           Fb = 0.0;
+       else
+           Fb = max (0.0, 1.0 - (1.0 - srca) / dsta);
+       return mult_chan (src, dst, Fa, Fb);
+
+    case PIXMAN_OP_DISJOINT_XOR:
+       if (srca == 0.0)
+           Fa = 1.0;
+       else
+           Fa = min (1.0, (1.0 - dsta) / srca);
+       if (dsta == 0.0)
+           Fb = 1.0;
+       else
+           Fb = min (1.0, (1.0 - srca) / dsta);
+       return mult_chan (src, dst, Fa, Fb);
+
+    case PIXMAN_OP_CONJOINT_OVER:
+       if (dsta == 0.0)
+           Fb = 0.0;
+       else
+           Fb = max (0.0, 1.0 - srca / dsta);
+       return mult_chan (src, dst, 1.0, Fb);
+
+    case PIXMAN_OP_CONJOINT_OVER_REVERSE:
+       if (srca == 0.0)
+           Fa = 0.0;
+       else
+           Fa = max (0.0, 1.0 - dsta / srca);
+       return mult_chan (src, dst, Fa, 1.0);
+
+    case PIXMAN_OP_CONJOINT_IN:
+       if (srca == 0.0)
+           Fa = 1.0;
+       else
+           Fa = min (1.0, dsta / srca);
+       return mult_chan (src, dst, Fa, 0.0);
+
+    case PIXMAN_OP_CONJOINT_IN_REVERSE:
+       if (dsta == 0.0)
+           Fb = 1.0;
+       else
+           Fb = min (1.0, srca / dsta);
+       return mult_chan (src, dst, 0.0, Fb);
+
+    case PIXMAN_OP_CONJOINT_OUT:
+       if (srca == 0.0)
+           Fa = 0.0;
+       else
+           Fa = max (0.0, 1.0 - dsta / srca);
+       return mult_chan (src, dst, Fa, 0.0);
+
+    case PIXMAN_OP_CONJOINT_OUT_REVERSE:
+       if (dsta == 0.0)
+           Fb = 0.0;
+       else
+           Fb = max (0.0, 1.0 - srca / dsta);
+       return mult_chan (src, dst, 0.0, Fb);
+
+    case PIXMAN_OP_CONJOINT_ATOP:
+       if (srca == 0.0)
+           Fa = 1.0;
+       else
+           Fa = min (1.0, dsta / srca);
+       if (dsta == 0.0)
+           Fb = 0.0;
+       else
+           Fb = max (0.0, 1.0 - srca / dsta);
+       return mult_chan (src, dst, Fa, Fb);
+
+    case PIXMAN_OP_CONJOINT_ATOP_REVERSE:
+       if (srca == 0.0)
+           Fa = 0.0;
+       else
+           Fa = max (0.0, 1.0 - dsta / srca);
+       if (dsta == 0.0)
+           Fb = 1.0;
+       else
+           Fb = min (1.0, srca / dsta);
+       return mult_chan (src, dst, Fa, Fb);
+
+    case PIXMAN_OP_CONJOINT_XOR:
+       if (srca == 0.0)
+           Fa = 0.0;
+       else
+           Fa = max (0.0, 1.0 - dsta / srca);
+       if (dsta == 0.0)
+           Fb = 0.0;
+       else
+           Fb = max (0.0, 1.0 - srca / dsta);
+       return mult_chan (src, dst, Fa, Fb);
+
+    default:
+       abort();
+    }
+#undef mult_chan
+}
+
+static void
+do_composite (pixman_op_t op,
+             const struct color *src,
+             const struct color *mask,
+             const struct color *dst,
+             struct color *result,
+             pixman_bool_t componentAlpha)
+{
+    struct color srcval, srcalpha;
+
+    if (mask == NULL) {
+       srcval = *src;
+
+       srcalpha.r = src->a;
+       srcalpha.g = src->a;
+       srcalpha.b = src->a;
+       srcalpha.a = src->a;
+    } else if (componentAlpha) {
+       srcval.r = src->r * mask->r;
+       srcval.g = src->g * mask->g;
+       srcval.b = src->b * mask->b;
+       srcval.a = src->a * mask->a;
+
+       srcalpha.r = src->a * mask->r;
+       srcalpha.g = src->a * mask->g;
+       srcalpha.b = src->a * mask->b;
+       srcalpha.a = src->a * mask->a;
+    } else {
+       srcval.r = src->r * mask->a;
+       srcval.g = src->g * mask->a;
+       srcval.b = src->b * mask->a;
+       srcval.a = src->a * mask->a;
+
+       srcalpha.r = src->a * mask->a;
+       srcalpha.g = src->a * mask->a;
+       srcalpha.b = src->a * mask->a;
+       srcalpha.a = src->a * mask->a;
+    }
+
+    result->r = calc_op (op, srcval.r, dst->r, srcalpha.r, dst->a);
+    result->g = calc_op (op, srcval.g, dst->g, srcalpha.g, dst->a);
+    result->b = calc_op (op, srcval.b, dst->b, srcalpha.b, dst->a);
+    result->a = calc_op (op, srcval.a, dst->a, srcalpha.a, dst->a);
+}
+
+static void
+color_correct (pixman_format_code_t format,
+              struct color *color)
+{
+#define round_pix(pix, mask) \
+    ((int)((pix) * (mask) + .5) / (double) (mask))
+
+    if (PIXMAN_FORMAT_R (format) == 0) {
+       color->r = 0.0;
+       color->g = 0.0;
+       color->b = 0.0;
+    } else {
+       color->r = round_pix (color->r, PIXMAN_FORMAT_R (format));
+       color->g = round_pix (color->g, PIXMAN_FORMAT_G (format));
+       color->b = round_pix (color->b, PIXMAN_FORMAT_B (format));
+    }
+
+    if (PIXMAN_FORMAT_A (format) == 0)
+       color->a = 1.0;
+    else
+       color->a = round_pix (color->a, PIXMAN_FORMAT_A (format));
+
+#undef round_pix
+}
+
+static void
+get_pixel (pixman_image_t *image,
+          pixman_format_code_t format,
+          struct color *color)
+{
+#define MASK(N) ((1UL << (N))-1)
+
+    unsigned long rs, gs, bs, as;
+    int a, r, g, b;
+    unsigned long val;
+
+    val = *(unsigned long *) pixman_image_get_data (image);
+
+    /* Number of bits in each channel */
+    a = PIXMAN_FORMAT_A (format);
+    r = PIXMAN_FORMAT_R (format);
+    g = PIXMAN_FORMAT_G (format);
+    b = PIXMAN_FORMAT_B (format);
+
+    switch (PIXMAN_FORMAT_TYPE (format)) {
+    case PIXMAN_TYPE_ARGB:
+        bs = 0;
+        gs = b + bs;
+        rs = g + gs;
+        as = r + rs;
+       break;
+
+    case PIXMAN_TYPE_ABGR:
+        rs = 0;
+        gs = r + rs;
+        bs = g + gs;
+        as = b + bs;
+       break;
+
+    case PIXMAN_TYPE_BGRA:
+        as = 0;
+       rs = PIXMAN_FORMAT_BPP (format) - (b + g + r);
+        gs = r + rs;
+        bs = g + gs;
+       break;
+
+    case PIXMAN_TYPE_A:
+        as = 0;
+        rs = 0;
+        gs = 0;
+        bs = 0;
+       break;
+
+    case PIXMAN_TYPE_OTHER:
+    case PIXMAN_TYPE_COLOR:
+    case PIXMAN_TYPE_GRAY:
+    case PIXMAN_TYPE_YUY2:
+    case PIXMAN_TYPE_YV12:
+    default:
+       abort ();
+        as = 0;
+        rs = 0;
+        gs = 0;
+        bs = 0;
+       break;
+    }
+
+    if (MASK (a) != 0)
+       color->a = ((val >> as) & MASK (a)) / (double) MASK (a);
+    else
+       color->a = 1.0;
+
+    if (MASK (r) != 0) {
+       color->r = ((val >> rs) & MASK (r)) / (double) MASK (r);
+       color->g = ((val >> gs) & MASK (g)) / (double) MASK (g);
+       color->b = ((val >> bs) & MASK (b)) / (double) MASK (b);
+    } else {
+       color->r = 0.0;
+       color->g = 0.0;
+       color->b = 0.0;
+    }
+
+#undef MASK
+}
+
+static double
+eval_diff (struct color *expected, struct color *test)
+{
+    double rscale, gscale, bscale, ascale;
+    double rdiff, gdiff, bdiff, adiff;
+
+    /* XXX: Need to be provided mask shifts so we can produce useful error
+     * values.
+     */
+    rscale = 1.0 * (1 << 5);
+    gscale = 1.0 * (1 << 6);
+    bscale = 1.0 * (1 << 5);
+    ascale = 1.0 * 32;
+
+    rdiff = fabs (test->r - expected->r) * rscale;
+    bdiff = fabs (test->g - expected->g) * gscale;
+    gdiff = fabs (test->b - expected->b) * bscale;
+    adiff = fabs (test->a - expected->a) * ascale;
+
+    return max (max (max (rdiff, gdiff), bdiff), adiff);
+}
+
+static char *
+describe_image (struct image *info, char *buf, int buflen)
+{
+    if (info->size) {
+       snprintf (buf, buflen, "%s %dx%d",
+                 info->format->name,
+                 info->size, info->size);
+    } else {
+       snprintf (buf, buflen, "solid");
+    }
+
+    return buf;
+}
+
+/* Test a composite of a given operation, source, mask, and destination picture.
+ * Fills the window, and samples from the 0,0 pixel corner.
+ */
+static pixman_bool_t
+composite_test (struct image *dst,
+               const struct operator *op,
+               struct image *src,
+               struct image *mask,
+               pixman_bool_t componentAlpha)
+{
+    pixman_color_t fill;
+    pixman_rectangle16_t rect;
+    struct color expected, result, tdst, tsrc, tmsk;
+    double diff;
+    pixman_bool_t success = TRUE;
+
+    compute_pixman_color (dst->color, &fill);
+    rect.x = rect.y = 0;
+    rect.width = rect.height = dst->size;
+    pixman_image_fill_rectangles (PIXMAN_OP_SRC, dst->image,
+                                 &fill, 1, &rect);
+
+    if (mask != NULL) {
+       pixman_image_set_component_alpha (mask->image, componentAlpha);
+       pixman_image_composite (op->op, src->image, mask->image, dst->image,
+                               0, 0,
+                               0, 0,
+                               0, 0,
+                               dst->size, dst->size);
+
+       tmsk = *mask->color;
+       if (mask->size) {
+           color_correct (mask->format->format, &tmsk);
+           if (componentAlpha &&
+               PIXMAN_FORMAT_R (mask->format->format) == 0)
+           {
+               /* Ax component-alpha masks expand alpha into all color channels. */
+               tmsk.r = tmsk.g = tmsk.b = tmsk.a;
+           }
+       }
+    } else {
+       pixman_image_composite (op->op, src->image, NULL, dst->image,
+                               0, 0,
+                               0, 0,
+                               0, 0,
+                               dst->size, dst->size);
+    }
+    get_pixel (dst->image, dst->format->format, &result);
+
+    tdst = *dst->color;
+    color_correct (dst->format->format, &tdst);
+    tsrc = *src->color;
+    if (src->size)
+       color_correct (src->format->format, &tsrc);
+    do_composite (op->op, &tsrc, mask ? &tmsk : NULL, &tdst,
+                 &expected, componentAlpha);
+    color_correct (dst->format->format, &expected);
+
+    diff = eval_diff (&expected, &result);
+    if (diff > 3.0) {
+       char buf[40];
+
+       snprintf (buf, sizeof (buf),
+                 "%s %scomposite",
+                 op->name,
+                 componentAlpha ? "CA " : "");
+
+       printf("%s test error of %.4f --\n"
+              "           R    G    B    A\n"
+              "got:       %.2f %.2f %.2f %.2f [%08lx]\n"
+              "expected:  %.2f %.2f %.2f %.2f\n",
+              buf, diff,
+              result.r, result.g, result.b, result.a,
+              *(unsigned long *) pixman_image_get_data (dst->image),
+              expected.r, expected.g, expected.b, expected.a);
+
+       if (mask != NULL) {
+           printf("src color: %.2f %.2f %.2f %.2f\n"
+                  "msk color: %.2f %.2f %.2f %.2f\n"
+                  "dst color: %.2f %.2f %.2f %.2f\n",
+                  src->color->r, src->color->g,
+                  src->color->b, src->color->a,
+                  mask->color->r, mask->color->g,
+                  mask->color->b, mask->color->a,
+                  dst->color->r, dst->color->g,
+                  dst->color->b, dst->color->a);
+           printf ("src: %s, ", describe_image (src, buf, sizeof (buf)));
+           printf ("mask: %s, ", describe_image (mask, buf, sizeof (buf)));
+           printf ("dst: %s\n\n", describe_image (dst, buf, sizeof (buf)));
+       } else {
+           printf("src color: %.2f %.2f %.2f %.2f\n"
+                  "dst color: %.2f %.2f %.2f %.2f\n",
+                  src->color->r, src->color->g,
+                  src->color->b, src->color->a,
+                  dst->color->r, dst->color->g,
+                  dst->color->b, dst->color->a);
+           printf ("src: %s, ", describe_image (src, buf, sizeof (buf)));
+           printf ("dst: %s\n\n", describe_image (dst, buf, sizeof (buf)));
+       }
+
+       success = FALSE;
+    }
+
+    return success;
+}
+
+static void
+image_init (struct image *info,
+           int color,
+           int format,
+           int size)
+{
+    pixman_color_t fill;
+
+    info->color = &colors[color];
+    compute_pixman_color (info->color, &fill);
+
+    info->format = &formats[format];
+    if (size) {
+       pixman_rectangle16_t rect;
+
+       info->image = pixman_image_create_bits (info->format->format,
+                                               size, size, NULL, 0);
+
+       rect.x = rect.y = 0;
+       rect.width = rect.height = size;
+       pixman_image_fill_rectangles (PIXMAN_OP_SRC, info->image, &fill,
+                                     1, &rect);
+    } else {
+       info->image = pixman_image_create_solid_fill (&fill);
+    }
+
+    info->size = size;
+}
+
+static void
+image_fini (struct image *info)
+{
+    pixman_image_unref (info->image);
+}
+
+int
+main (void)
+{
+    pixman_bool_t ok, group_ok = TRUE, ca;
+    int i, d, m, s;
+    int tests_passed = 0, tests_total = 0;
+    int sizes[] = { 1, 10 };
+    int num_tests;
+
+    for (i = 0; i < ARRAY_LENGTH (colors); i++) {
+       colors[i].r *= colors[i].a;
+       colors[i].g *= colors[i].a;
+       colors[i].b *= colors[i].a;
+    }
+
+    num_tests = ARRAY_LENGTH (colors) * ARRAY_LENGTH (formats);
+
+    for (d = 0; d < num_tests; d++) {
+       struct image dst;
+
+       image_init (&dst,
+                   d / ARRAY_LENGTH (formats),
+                   d % ARRAY_LENGTH (formats),
+                   1);
+
+
+       for (s = -ARRAY_LENGTH (colors); s < ARRAY_LENGTH (sizes) * num_tests; s++) {
+           struct image src;
+
+           if (s < 0) {
+               image_init (&src, -s - 1, 0, 0);
+           } else {
+               image_init (&src,
+                           s / ARRAY_LENGTH (sizes) / ARRAY_LENGTH (formats),
+                           s / ARRAY_LENGTH (sizes) % ARRAY_LENGTH (formats),
+                           sizes[s % ARRAY_LENGTH (sizes)]);
+           }
+
+           for (m = -ARRAY_LENGTH (colors); m < ARRAY_LENGTH (sizes) * num_tests; m++) {
+               struct image mask;
+
+               if (m < 0) {
+                   image_init (&mask, -m - 1, 0, 0);
+               } else {
+                   image_init (&mask,
+                               m / ARRAY_LENGTH (sizes) / ARRAY_LENGTH (formats),
+                               m / ARRAY_LENGTH (sizes) % ARRAY_LENGTH (formats),
+                               sizes[m % ARRAY_LENGTH (sizes)]);
+               }
+
+               for (ca = -1; ca <= 1; ca++) {
+                   for (i = 0; i < ARRAY_LENGTH (operators); i++) {
+                       const struct operator *op = &operators[i];
+
+                       switch (ca) {
+                       case -1:
+                           ok = composite_test (&dst, op, &src, NULL, ca);
+                           break;
+                       default:
+                           ok = composite_test (&dst, op, &src, &mask, ca);
+                           break;
+                       }
+                       group_ok = group_ok && ok;
+                       tests_passed += ok;
+                       tests_total++;
+                   }
+               }
+
+               image_fini (&mask);
+           }
+           image_fini (&src);
+       }
+       image_fini (&dst);
+    }
+
+    return group_ok == FALSE;
+}