1 /* imagediff - Compare two images
3 * Copyright © 2004 Richard D. Worth
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.
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.
24 * Author: Richard D. Worth <richard@theworths.org> */
37 #include "buffer-diff.h"
40 _xunlink (const char *pathname
)
42 if (unlink (pathname
) < 0 && errno
!= ENOENT
) {
43 fprintf (stderr
, " Error: Cannot remove %s: %s\n",
44 pathname
, strerror (errno
));
49 /* Flatten an ARGB surface by blending it over white. The resulting
50 * surface, (still in ARGB32 format, but with only alpha==1.0
51 * everywhere) is returned in the same surface pointer.
53 * The original surface will be destroyed.
55 * The (x,y) value specify an origin of interest for the original
56 * image. The flattened image will be generated only from the box
57 * extending from (x,y) to (width,height).
60 flatten_surface (cairo_surface_t
**surface
, int x
, int y
)
62 cairo_surface_t
*flat
;
65 flat
= cairo_image_surface_create (CAIRO_FORMAT_ARGB32
,
66 cairo_image_surface_get_width (*surface
) - x
,
67 cairo_image_surface_get_height (*surface
) - y
);
68 cairo_surface_set_device_offset (flat
, -x
, -y
);
70 cr
= cairo_create (flat
);
71 cairo_surface_destroy (flat
);
73 cairo_set_source_rgb (cr
, 1, 1, 1);
76 cairo_set_source_surface (cr
, *surface
, 0, 0);
77 cairo_surface_destroy (*surface
);
80 *surface
= cairo_surface_reference (cairo_get_target (cr
));
84 /* Given an image surface, create a new surface that has the same
85 * contents as the sub-surface with its origin at x,y.
87 * The original surface will be destroyed.
90 extract_sub_surface (cairo_surface_t
**surface
, int x
, int y
)
95 sub
= cairo_image_surface_create (CAIRO_FORMAT_ARGB32
,
96 cairo_image_surface_get_width (*surface
) - x
,
97 cairo_image_surface_get_height (*surface
) - y
);
99 /* We don't use a device offset like flatten_surface. That's not
100 * for any important reason, (the results should be
101 * identical). This style just seemed more natural to me this
102 * time, so I'm leaving both here so I can look at both to see
103 * which I like better. */
104 cr
= cairo_create (sub
);
105 cairo_surface_destroy (sub
);
107 cairo_set_source_surface (cr
, *surface
, -x
, -y
);
108 cairo_surface_destroy (*surface
);
109 cairo_set_operator (cr
, CAIRO_OPERATOR_SOURCE
);
112 *surface
= cairo_surface_reference (cairo_get_target (cr
));
116 static cairo_status_t
117 stdio_write_func (void *closure
, const unsigned char *data
, unsigned int length
)
119 FILE *file
= closure
;
121 if (fwrite (data
, 1, length
, file
) != length
)
122 return CAIRO_STATUS_WRITE_ERROR
;
124 return CAIRO_STATUS_SUCCESS
;
127 static cairo_status_t
128 write_png (cairo_surface_t
*surface
, const char *filename
)
130 cairo_status_t status
;
133 if (filename
!= NULL
) {
134 png_file
= fopen (filename
, "wb");
135 if (png_file
== NULL
) {
138 return CAIRO_STATUS_NO_MEMORY
;
140 return CAIRO_STATUS_WRITE_ERROR
;
146 status
= cairo_surface_write_to_png_stream (surface
,
150 if (png_file
!= stdout
)
156 static cairo_status_t
157 png_diff (const char *filename_a
,
158 const char *filename_b
,
159 const char *filename_diff
,
164 buffer_diff_result_t
*result
)
166 cairo_surface_t
*surface_a
;
167 cairo_surface_t
*surface_b
;
168 cairo_surface_t
*surface_diff
;
169 cairo_status_t status
;
171 surface_a
= cairo_image_surface_create_from_png (filename_a
);
172 status
= cairo_surface_status (surface_a
);
174 fprintf (stderr
, "Error: Failed to create surface from %s: %s\n",
175 filename_a
, cairo_status_to_string (status
));
179 surface_b
= cairo_image_surface_create_from_png (filename_b
);
180 status
= cairo_surface_status (surface_b
);
182 fprintf (stderr
, "Error: Failed to create surface from %s: %s\n",
183 filename_b
, cairo_status_to_string (status
));
184 cairo_surface_destroy (surface_a
);
189 extract_sub_surface (&surface_a
, ax
, ay
);
194 extract_sub_surface (&surface_b
, bx
, by
);
198 status
= cairo_surface_status (surface_a
);
200 fprintf (stderr
, "Error: Failed to extract surface from %s: %s\n",
201 filename_a
, cairo_status_to_string (status
));
202 cairo_surface_destroy (surface_a
);
203 cairo_surface_destroy (surface_b
);
206 status
= cairo_surface_status (surface_b
);
208 fprintf (stderr
, "Error: Failed to extract surface from %s: %s\n",
209 filename_b
, cairo_status_to_string (status
));
210 cairo_surface_destroy (surface_a
);
211 cairo_surface_destroy (surface_b
);
215 surface_diff
= cairo_image_surface_create (CAIRO_FORMAT_ARGB32
,
216 cairo_image_surface_get_width (surface_a
),
217 cairo_image_surface_get_height (surface_a
));
218 status
= cairo_surface_status (surface_diff
);
221 "Error: Failed to allocate surface to hold differences\n");
222 cairo_surface_destroy (surface_a
);
223 cairo_surface_destroy (surface_b
);
224 return CAIRO_STATUS_NO_MEMORY
;
227 status
= image_diff (NULL
,
228 surface_a
, surface_b
, surface_diff
,
232 _xunlink (filename_diff
);
234 if (status
== CAIRO_STATUS_SUCCESS
&&
235 result
->pixels_changed
)
237 status
= write_png (surface_diff
, filename_diff
);
240 cairo_surface_destroy (surface_a
);
241 cairo_surface_destroy (surface_b
);
242 cairo_surface_destroy (surface_diff
);
248 main (int argc
, char *argv
[])
250 buffer_diff_result_t result
;
251 cairo_status_t status
;
253 unsigned int ax
, ay
, bx
, by
;
255 if (argc
!= 3 && argc
!= 7) {
256 fprintf (stderr
, "Usage: %s image1.png image2.png [ax ay bx by]\n", argv
[0]);
257 fprintf (stderr
, "Computes an output image designed to present a \"visual diff\" such that even\n");
258 fprintf (stderr
, "small errors in single pixels are readily apparent in the output.\n");
259 fprintf (stderr
, "The output image is written on stdout.\n");
264 ax
= strtoul (argv
[3], NULL
, 0);
265 ay
= strtoul (argv
[4], NULL
, 0);
266 bx
= strtoul (argv
[5], NULL
, 0);
267 by
= strtoul (argv
[6], NULL
, 0);
269 ax
= ay
= bx
= by
= 0;
272 status
= png_diff (argv
[1], argv
[2], NULL
, ax
, ay
, bx
, by
, &result
);
275 fprintf (stderr
, "Error comparing images: %s\n",
276 cairo_status_to_string (status
));
280 if (result
.pixels_changed
)
281 fprintf (stderr
, "Total pixels changed: %d with a maximum channel difference of %d.\n",
282 result
.pixels_changed
,
285 return (result
.pixels_changed
!= 0);