~ubuntu-branches/ubuntu/lucid/cairo/lucid

« back to all changes in this revision

Viewing changes to test/buffer-diff.c

  • Committer: Bazaar Package Importer
  • Author(s): Sebastien Bacher
  • Date: 2008-01-17 13:00:59 UTC
  • Revision ID: james.westby@ubuntu.com-20080117130059-3gbudaudr2w8bl4w
Tags: upstream-1.5.6
ImportĀ upstreamĀ versionĀ 1.5.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* imagediff - Compare two images
 
2
 *
 
3
 * Copyright Ā© 2004 Richard D. Worth
 
4
 *
 
5
 * Permission to use, copy, modify, distribute, and sell this software
 
6
 * and its documentation for any purpose is hereby granted without
 
7
 * fee, provided that the above copyright notice appear in all copies
 
8
 * and that both that copyright notice and this permission notice
 
9
 * appear in supporting documentation, and that the name of Richard Worth
 
10
 * not be used in advertising or publicity pertaining to distribution
 
11
 * of the software without specific, written prior permission.
 
12
 * Richard Worth makes no representations about the suitability of this
 
13
 * software for any purpose.  It is provided "as is" without express
 
14
 * or implied warranty.
 
15
 *
 
16
 * RICHARD WORTH DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 
17
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 
18
 * NO EVENT SHALL RICHARD WORTH BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 
19
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 
20
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 
21
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 
22
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
23
 *
 
24
 * Author: Richard D. Worth <richard@theworths.org> */
 
25
 
 
26
#if HAVE_CONFIG_H
 
27
#include "config.h"
 
28
#endif
 
29
 
 
30
#include <stdio.h>
 
31
#include <stdlib.h>
 
32
#ifdef HAVE_UNISTD_H
 
33
#include <unistd.h>
 
34
#endif
 
35
#include <errno.h>
 
36
#include <string.h>
 
37
#include <pixman.h>
 
38
 
 
39
#include "cairo-test.h"
 
40
 
 
41
#include "pdiff.h"
 
42
#include "buffer-diff.h"
 
43
#include "xmalloc.h"
 
44
 
 
45
static void
 
46
xunlink (const char *pathname)
 
47
{
 
48
    if (unlink (pathname) < 0 && errno != ENOENT) {
 
49
        cairo_test_log ("  Error: Cannot remove %s: %s\n",
 
50
                        pathname, strerror (errno));
 
51
        exit (1);
 
52
    }
 
53
}
 
54
 
 
55
/* Compare two buffers, returning the number of pixels that are
 
56
 * different and the maximum difference of any single color channel in
 
57
 * result_ret.
 
58
 *
 
59
 * This function should be rewritten to compare all formats supported by
 
60
 * cairo_format_t instead of taking a mask as a parameter.
 
61
 */
 
62
static void
 
63
buffer_diff_core (unsigned char *_buf_a,
 
64
                  unsigned char *_buf_b,
 
65
                  unsigned char *_buf_diff,
 
66
                  int           width,
 
67
                  int           height,
 
68
                  int           stride,
 
69
                  uint32_t mask,
 
70
                  buffer_diff_result_t *result_ret)
 
71
{
 
72
    int x, y;
 
73
    uint32_t *row_a, *row_b, *row;
 
74
    buffer_diff_result_t result = {0, 0};
 
75
    uint32_t *buf_a = (uint32_t*)_buf_a;
 
76
    uint32_t *buf_b = (uint32_t*)_buf_b;
 
77
    uint32_t *buf_diff = (uint32_t*)_buf_diff;
 
78
 
 
79
    stride /= sizeof(uint32_t);
 
80
    for (y = 0; y < height; y++)
 
81
    {
 
82
        row_a = buf_a + y * stride;
 
83
        row_b = buf_b + y * stride;
 
84
        row = buf_diff + y * stride;
 
85
        for (x = 0; x < width; x++)
 
86
        {
 
87
            /* check if the pixels are the same */
 
88
            if ((row_a[x] & mask) != (row_b[x] & mask)) {
 
89
                int channel;
 
90
                uint32_t diff_pixel = 0;
 
91
 
 
92
                /* calculate a difference value for all 4 channels */
 
93
                for (channel = 0; channel < 4; channel++) {
 
94
                    int value_a = (row_a[x] >> (channel*8)) & 0xff;
 
95
                    int value_b = (row_b[x] >> (channel*8)) & 0xff;
 
96
                    unsigned int diff;
 
97
                    diff = abs (value_a - value_b);
 
98
                    if (diff > result.max_diff)
 
99
                        result.max_diff = diff;
 
100
                    diff *= 4;  /* emphasize */
 
101
                    if (diff)
 
102
                        diff += 128; /* make sure it's visible */
 
103
                    if (diff > 255)
 
104
                        diff = 255;
 
105
                    diff_pixel |= diff << (channel*8);
 
106
                }
 
107
 
 
108
                result.pixels_changed++;
 
109
                row[x] = diff_pixel;
 
110
            } else {
 
111
                row[x] = 0;
 
112
            }
 
113
            row[x] |= 0xff000000; /* Set ALPHA to 100% (opaque) */
 
114
        }
 
115
    }
 
116
 
 
117
    *result_ret = result;
 
118
}
 
119
 
 
120
void
 
121
compare_surfaces (cairo_surface_t       *surface_a,
 
122
                  cairo_surface_t       *surface_b,
 
123
                  cairo_surface_t       *surface_diff,
 
124
                  buffer_diff_result_t  *result)
 
125
{
 
126
    /* These default values were taken straight from the
 
127
     * perceptualdiff program. We'll probably want to tune these as
 
128
     * necessary. */
 
129
    double gamma = 2.2;
 
130
    double luminance = 100.0;
 
131
    double field_of_view = 45.0;
 
132
    int discernible_pixels_changed;
 
133
 
 
134
    /* First, we run cairo's old buffer_diff algorithm which looks for
 
135
     * pixel-perfect images, (we do this first since the test suite
 
136
     * runs about 3x slower if we run pdiff_compare first).
 
137
     */
 
138
    buffer_diff_core (cairo_image_surface_get_data (surface_a),
 
139
                      cairo_image_surface_get_data (surface_b),
 
140
                      cairo_image_surface_get_data (surface_diff),
 
141
                      cairo_image_surface_get_width (surface_a),
 
142
                      cairo_image_surface_get_height (surface_a),
 
143
                      cairo_image_surface_get_stride (surface_a),
 
144
                      0xffffffff,
 
145
                      result);
 
146
    if (result->pixels_changed == 0)
 
147
        return;
 
148
 
 
149
    cairo_test_log ("%d pixels differ (with maximum difference of %d) from reference image\n",
 
150
                    result->pixels_changed, result->max_diff);
 
151
 
 
152
    /* Then, if there are any different pixels, we give the pdiff code
 
153
     * a crack at the images. If it decides that there are no visually
 
154
     * discernible differences in any pixels, then we accept this
 
155
     * result as good enough. */
 
156
    discernible_pixels_changed = pdiff_compare (surface_a, surface_b,
 
157
                                                gamma, luminance, field_of_view);
 
158
    if (discernible_pixels_changed == 0) {
 
159
        result->pixels_changed = 0;
 
160
        cairo_test_log ("But perceptual diff finds no visually discernible difference.\n"
 
161
                        "Accepting result.\n");
 
162
    }
 
163
}
 
164
 
 
165
void
 
166
buffer_diff_noalpha (unsigned char *buf_a,
 
167
                     unsigned char *buf_b,
 
168
                     unsigned char *buf_diff,
 
169
                     int           width,
 
170
                     int           height,
 
171
                     int           stride,
 
172
                     buffer_diff_result_t *result)
 
173
{
 
174
    buffer_diff_core(buf_a, buf_b, buf_diff,
 
175
                     width, height, stride, 0x00ffffff,
 
176
                     result);
 
177
}
 
178
 
 
179
static cairo_status_t
 
180
stdio_write_func (void *closure, const unsigned char *data, unsigned int length)
 
181
{
 
182
    FILE *file = closure;
 
183
 
 
184
    if (fwrite (data, 1, length, file) != length)
 
185
        return CAIRO_STATUS_WRITE_ERROR;
 
186
 
 
187
    return CAIRO_STATUS_SUCCESS;
 
188
}
 
189
 
 
190
/* Flatten an ARGB surface by blending it over white. The resulting
 
191
 * surface, (still in ARGB32 format, but with only alpha==1.0
 
192
 * everywhere) is returned in the same surface pointer.
 
193
 *
 
194
 * The original surface will be destroyed.
 
195
 *
 
196
 * The (x,y) value specify an origin of interest for the original
 
197
 * image. The flattened image will be generated only from the box
 
198
 * extending from (x,y) to (width,height).
 
199
 */
 
200
static void
 
201
flatten_surface (cairo_surface_t **surface, int x, int y)
 
202
{
 
203
    cairo_surface_t *flat;
 
204
    cairo_t *cr;
 
205
 
 
206
    flat = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
 
207
                                       cairo_image_surface_get_width (*surface) - x,
 
208
                                       cairo_image_surface_get_height (*surface) - y);
 
209
    cairo_surface_set_device_offset (flat, -x, -y);
 
210
 
 
211
    cr = cairo_create (flat);
 
212
    cairo_set_source_rgb (cr, 1, 1, 1);
 
213
    cairo_paint (cr);
 
214
    cairo_set_source_surface (cr, *surface, 0, 0);
 
215
    cairo_paint (cr);
 
216
    cairo_destroy (cr);
 
217
 
 
218
    cairo_surface_destroy (*surface);
 
219
    *surface = flat;
 
220
}
 
221
 
 
222
/* Given an image surface, create a new surface that has the same
 
223
 * contents as the sub-surface with its origin at x,y.
 
224
 *
 
225
 * The original surface will be destroyed.
 
226
 */
 
227
static void
 
228
extract_sub_surface (cairo_surface_t **surface, int x, int y)
 
229
{
 
230
    cairo_surface_t *sub;
 
231
    cairo_t *cr;
 
232
 
 
233
    sub = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
 
234
                                      cairo_image_surface_get_width (*surface) - x,
 
235
                                      cairo_image_surface_get_height (*surface) - y);
 
236
 
 
237
    /* We don't use a device offset like flatten_surface. That's not
 
238
     * for any important reason, (the results should be
 
239
     * identical). This style just seemed more natural to me this
 
240
     * time, so I'm leaving both here so I can look at both to see
 
241
     * which I like better. */
 
242
    cr = cairo_create (sub);
 
243
    cairo_set_source_surface (cr, *surface, -x, -y);
 
244
    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
 
245
    cairo_paint (cr);
 
246
    cairo_destroy (cr);
 
247
 
 
248
    cairo_surface_destroy (*surface);
 
249
    *surface = sub;
 
250
}
 
251
 
 
252
/* Image comparison code courtesy of Richard Worth <richard@theworths.org>
 
253
 * Returns number of pixels changed, (or -1 on error).
 
254
 * Also saves a "diff" image intended to visually show where the
 
255
 * images differ.
 
256
 *
 
257
 * The return value simply indicates whether a check was successfully
 
258
 * made, (as opposed to a file-not-found condition or similar). It
 
259
 * does not indicate anything about how much the images differ. For
 
260
 * that, see result.
 
261
 *
 
262
 * One failure mode is if the two images provided do not have the same
 
263
 * dimensions. In this case, this function will return
 
264
 * CAIRO_STATUS_SURFACE_TYPE_MISMATCH (which is a bit of an abuse, but
 
265
 * oh well).
 
266
 */
 
267
static cairo_status_t
 
268
image_diff_core (const char *filename_a,
 
269
                 const char *filename_b,
 
270
                 const char *filename_diff,
 
271
                 int            ax,
 
272
                 int            ay,
 
273
                 int            bx,
 
274
                 int            by,
 
275
                 buffer_diff_result_t *result,
 
276
                 cairo_bool_t   flatten)
 
277
{
 
278
    cairo_status_t status;
 
279
    unsigned int width_a, height_a, stride_a;
 
280
    unsigned int width_b, height_b, stride_b;
 
281
    cairo_surface_t *surface_a, *surface_b, *surface_diff;
 
282
 
 
283
    surface_a = cairo_image_surface_create_from_png (filename_a);
 
284
    status = cairo_surface_status (surface_a);
 
285
    if (status) {
 
286
        cairo_test_log ("Error: Failed to create surface from %s: %s\n",
 
287
                        filename_a, cairo_status_to_string (status));
 
288
        return status;
 
289
    }
 
290
 
 
291
    surface_b = cairo_image_surface_create_from_png (filename_b);
 
292
    status = cairo_surface_status (surface_b);
 
293
    if (status) {
 
294
        cairo_test_log ("Error: Failed to create surface from %s: %s\n",
 
295
                        filename_b, cairo_status_to_string (status));
 
296
        cairo_surface_destroy (surface_a);
 
297
        return status;
 
298
    }
 
299
 
 
300
    if (flatten) {
 
301
        flatten_surface (&surface_a, ax, ay);
 
302
        flatten_surface (&surface_b, bx, by);
 
303
        ax = ay = bx = by = 0;
 
304
    }
 
305
 
 
306
    if (ax || ay) {
 
307
        extract_sub_surface (&surface_a, ax, ay);
 
308
        ax = ay = 0;
 
309
    }
 
310
 
 
311
    if (bx || by) {
 
312
        extract_sub_surface (&surface_b, bx, by);
 
313
        bx = by = 0;
 
314
    }
 
315
 
 
316
    width_a = cairo_image_surface_get_width (surface_a);
 
317
    height_a = cairo_image_surface_get_height (surface_a);
 
318
    stride_a = cairo_image_surface_get_stride (surface_a);
 
319
    width_b = cairo_image_surface_get_width (surface_b);
 
320
    height_b = cairo_image_surface_get_height (surface_b);
 
321
    stride_b = cairo_image_surface_get_stride (surface_b);
 
322
 
 
323
    if (width_a  != width_b  ||
 
324
        height_a != height_b ||
 
325
        stride_a != stride_b)
 
326
    {
 
327
        cairo_test_log ("Error: Image size mismatch: (%dx%d) vs. (%dx%d)\n"
 
328
                        "       for %s vs. %s\n",
 
329
                        width_a, height_a,
 
330
                        width_b, height_b,
 
331
                        filename_a, filename_b);
 
332
        cairo_surface_destroy (surface_a);
 
333
        cairo_surface_destroy (surface_b);
 
334
        return CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
 
335
    }
 
336
 
 
337
    surface_diff = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
 
338
                                               width_a, height_a);
 
339
 
 
340
    compare_surfaces (surface_a, surface_b, surface_diff, result);
 
341
 
 
342
    status = CAIRO_STATUS_SUCCESS;
 
343
    if (result->pixels_changed) {
 
344
        FILE *png_file;
 
345
 
 
346
        if (filename_diff)
 
347
            png_file = fopen (filename_diff, "wb");
 
348
        else
 
349
            png_file = stdout;
 
350
 
 
351
        status = cairo_surface_write_to_png_stream (surface_diff, stdio_write_func, png_file);
 
352
 
 
353
        if (png_file != stdout)
 
354
            fclose (png_file);
 
355
    } else {
 
356
        if (filename_diff)
 
357
            xunlink (filename_diff);
 
358
    }
 
359
 
 
360
    cairo_surface_destroy (surface_a);
 
361
    cairo_surface_destroy (surface_b);
 
362
    cairo_surface_destroy (surface_diff);
 
363
 
 
364
    return status;
 
365
}
 
366
 
 
367
cairo_status_t
 
368
image_diff (const char *filename_a,
 
369
            const char *filename_b,
 
370
            const char *filename_diff,
 
371
            int         ax,
 
372
            int         ay,
 
373
            int         bx,
 
374
            int         by,
 
375
            buffer_diff_result_t *result)
 
376
{
 
377
    return image_diff_core (filename_a, filename_b, filename_diff,
 
378
                            ax, ay, bx, by,
 
379
                            result, FALSE);
 
380
}
 
381
 
 
382
cairo_status_t
 
383
image_diff_flattened (const char *filename_a,
 
384
                      const char *filename_b,
 
385
                      const char *filename_diff,
 
386
                      int         ax,
 
387
                      int         ay,
 
388
                      int         bx,
 
389
                      int         by,
 
390
                      buffer_diff_result_t *result)
 
391
{
 
392
    return image_diff_core (filename_a, filename_b, filename_diff,
 
393
                            ax, ay, bx, by,
 
394
                            result, TRUE);
 
395
}