1 /**
2 * Fuzzy comparison utility. Copyright 2001-2012 Artifex Software, Inc.
3 **/
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <memory.h>
9 #include <sys/stat.h>
10 #include <math.h>
11
12 typedef unsigned char uchar;
13 typedef int bool;
14 #define FALSE 0
15 #define TRUE 1
16
17 #define BMP_FILE_HEADER_SIZE 14
18 #define BMP_INFO_HEADER_SIZE 40
19 #define BMP_HEADER_SIZE ((BMP_FILE_HEADER_SIZE) + \
20 (BMP_INFO_HEADER_SIZE))
21
22 #define MIN(x,y) ((x)>(y)?(y):(x))
23 #define MAX(x,y) ((x)>(y)?(x):(y))
24
25 typedef struct _Image Image;
26
27 struct _Image {
28 int (*close) (Image *self);
29 int (*get_scan_line) (Image *self, uchar *buf);
30 int (*seek) (Image *self, int y);
31 int (*feof_) (Image *self);
32 int width;
33 int height;
34 int maxval;
35 int n_chan;
36 int raster;
37 int bpp; /* bits per pixel */
38 };
39
40 typedef struct _FuzzyParams FuzzyParams;
41
42 struct _FuzzyParams {
43 int tolerance; /* in pixel counts */
44 int window_size;
45 bool report_coordinates;
46 };
47
48 typedef struct _FuzzyReport FuzzyReport;
49
50 struct _FuzzyReport {
51 int n_diff;
52 int n_outof_tolerance;
53 int n_outof_window;
54 double max_color_error;
55 double avg_color_error;
56 };
57
58 static off_t
file_length(int file)59 file_length(int file)
60 {
61 struct stat st;
62
63 fstat(file, &st);
64 return st.st_size;
65 }
66
67 int
image_get_rgb_scan_line(Image * image,uchar * buf)68 image_get_rgb_scan_line (Image *image, uchar *buf)
69 {
70 uchar *image_buf;
71 int width = image->width;
72 int code;
73 int x;
74
75 if (image->n_chan == 3 && image->bpp == 8)
76 return image->get_scan_line (image, buf);
77
78 image_buf = malloc (image->raster);
79 code = image->get_scan_line (image, image_buf);
80 if (code < 0)
81 {
82 /* skip */
83 }
84 else if (image->n_chan == 1 && image->bpp == 8)
85 {
86 for (x = 0; x < width; x++)
87 {
88 uchar g = image_buf[x];
89 buf[x * 3] = g;
90 buf[x * 3 + 1] = g;
91 buf[x * 3 + 2] = g;
92 }
93 }
94 else if (image->n_chan == 1 && image->bpp == 1)
95 {
96 for (x = 0; x < width; x++)
97 {
98 uchar g = -!(image_buf[x >> 3] & (128 >> (x & 7)));
99 buf[x * 3] = g;
100 buf[x * 3 + 1] = g;
101 buf[x * 3 + 2] = g;
102 }
103 }
104 else
105 code = -1;
106 free (image_buf);
107 return code;
108 }
109 int
image_get_rgb_scan_line_with_error(Image * image,uchar * buf,int image_index,int row_index,int * rcode)110 image_get_rgb_scan_line_with_error (Image *image, uchar *buf, int image_index, int row_index, int *rcode)
111 {
112 int code = image_get_rgb_scan_line (image, buf);
113
114 if (code == 1) {
115 *rcode = 1;
116 printf("*** I/O error in file %d at y = %d\n", image_index, row_index);
117 }
118 return code;
119 }
120
121 int
image_close(Image * image)122 image_close (Image *image)
123 {
124 return image->close (image);
125 }
126
127 static int
no_seek(Image * self,int y)128 no_seek(Image *self, int y)
129 {
130 return 0;
131 }
132
133 typedef struct _ImagePnm ImagePnm;
134
135 struct _ImagePnm {
136 Image super;
137 FILE *f;
138 long file_length;
139 };
140
141 static int
image_pnm_close(Image * self)142 image_pnm_close (Image *self)
143 {
144 ImagePnm *pnm = (ImagePnm *)self;
145 int code;
146
147 code = fclose (pnm->f);
148 free (self);
149 return code;
150 }
151
152 static ImagePnm *
create_pnm_image_struct(Image * templ,const char * path)153 create_pnm_image_struct(Image *templ, const char *path)
154 {
155 FILE *f = fopen(path,"w+b");
156 ImagePnm *pnm = (ImagePnm *)malloc(sizeof(ImagePnm));
157
158 if (pnm == NULL) {
159 printf ("Insufficient RAM.\n");
160 return NULL;
161 }
162 if (f == NULL) {
163 printf ("Can't create the file %s\n", path);
164 return NULL;
165 }
166 pnm->f = f;
167 pnm->super = *templ;
168 pnm->super.seek = no_seek;
169 pnm->super.bpp = 8; /* Now support this value only. */
170 pnm->super.n_chan = 3; /* Now support this value only. */
171 return pnm;
172 }
173
174 static ImagePnm *
create_pnm_image(Image * templ,const char * path)175 create_pnm_image(Image *templ, const char *path)
176 {
177 ImagePnm *pnm = create_pnm_image_struct(templ, path);
178
179 if (pnm == NULL)
180 return NULL;
181
182 fprintf(pnm->f,"P6\n");
183 fprintf(pnm->f,"# Generated by Ghostscript's fuzzy.c\n");
184 fprintf(pnm->f,"%d %d\n", templ->width, pnm->super.height);
185 fprintf(pnm->f,"255\n");
186 return pnm;
187 }
188
189 static int
image_pnm_feof(Image * self)190 image_pnm_feof (Image *self)
191 {
192 ImagePnm *pnm = (ImagePnm *)self;
193
194 return feof (pnm->f) || ftell(pnm->f) == pnm->file_length;
195 }
196
197 static void
write_int16(short v,FILE * f)198 write_int16(short v, FILE *f)
199 {
200 uchar val[2];
201 val[0] = (uchar)(v & 0xff);
202 val[1] = (uchar)((v >> 8) & 0xff);
203 fwrite(val, 2, 1, f);
204 }
205
206 static void
write_int32(int v,FILE * f)207 write_int32(int v, FILE *f)
208 {
209 uchar val[4];
210 val[0] = (uchar)(v & 0xff);
211 val[1] = (uchar)((v >> 8) & 0xff);
212 val[2] = (uchar)((v >> 16) & 0xff);
213 val[3] = (uchar)((v >> 24) & 0xff);
214 fwrite(val, 4, 1, f);
215 }
216
217 static int
seek_bmp_image(Image * self,int y)218 seek_bmp_image(Image *self, int y)
219 {
220 ImagePnm *pnm = (ImagePnm *)self;
221 long pos = BMP_HEADER_SIZE + self->raster * (self->height - y - 1);
222 int r = fseek(pnm->f, pos, SEEK_SET);
223
224 return r;
225 }
226
227 static ImagePnm *
create_bmp_image(Image * templ,const char * path)228 create_bmp_image(Image *templ, const char *path)
229 {
230 int raster = (templ->width * 3 + 3) & ~3;
231 int nImageSize = raster * templ->height;
232 int nFileSize = BMP_HEADER_SIZE + nImageSize;
233 ImagePnm *pnm = create_pnm_image_struct(templ, path);
234
235 if (pnm == NULL)
236 return NULL;
237 pnm->super.seek = seek_bmp_image;
238 pnm->super.raster = raster;
239
240 /* BMP file header */
241 fputc('B', pnm->f);
242 fputc('M', pnm->f);
243 write_int32(nFileSize, pnm->f);
244 write_int16(0, pnm->f); /* reserved fields are zero */
245 write_int16(0, pnm->f);
246 write_int32(BMP_HEADER_SIZE, pnm->f);
247
248 /* BMP info header */
249 write_int32(BMP_INFO_HEADER_SIZE, pnm->f);
250 write_int32(templ->width, pnm->f);
251 write_int32(pnm->super.height, pnm->f);
252 write_int16(1, pnm->f); /* planes */
253 write_int16(24, pnm->f); /* bit count */
254 write_int32(0, pnm->f); /* compression */
255 write_int32(0, pnm->f); /* size image */
256 write_int32(3780, pnm->f); /* resolution in pixels per meter */
257 write_int32(3780, pnm->f); /* use a default 96 dpi */
258 write_int32(0, pnm->f); /* ClrUsed */
259 write_int32(0, pnm->f); /* ClrImportant */
260
261 /* fseek beyond file end doesn't work with MSVC so we pad
262 out to the size of the image data at file open time. */
263 {
264 uchar *linebuf = malloc(raster);
265 if (linebuf == NULL) {
266 printf ("Couldn't allocate dummy bmp line buffer; diff image may be corrupt.\n");
267 } else {
268 int k;
269
270 memset(linebuf, 0, raster);
271
272 for (k = 0; k < pnm->super.height; k++)
273 fwrite(linebuf, 1, raster, pnm->f);
274
275 free(linebuf);
276 }
277 }
278
279 return pnm;
280 }
281
282 static int
image_pnm_get_scan_line(Image * self,uchar * buf)283 image_pnm_get_scan_line (Image *self, uchar *buf)
284 {
285 ImagePnm *pnm = (ImagePnm *)self;
286 int n_bytes = self->raster;
287 int code;
288 int bppval = (1 << self->bpp) -1;
289 int maxval = self->maxval;
290
291 code = fread (buf, 1, n_bytes, pnm->f);
292 if (maxval < bppval) {
293 int i;
294 for(i = 0; i<n_bytes; i++)
295 buf[i] = buf[i] * bppval / maxval;
296 }
297 return (code < n_bytes) ? -1 : 0;
298 }
299
300 ImagePnm *
alloc_pnm_image(const char * fn)301 alloc_pnm_image (const char *fn)
302 {
303 ImagePnm *image;
304
305 FILE *f = fopen (fn, "rb");
306
307 if (f == NULL) {
308 printf("Can't open file %s\n", fn);
309 return NULL;
310 }
311 image = (ImagePnm *)malloc (sizeof(ImagePnm));
312 image->f = f;
313 image->file_length = file_length(fileno(f));
314 return image;
315 }
316
317 int
open_pnm_image(ImagePnm * image)318 open_pnm_image (ImagePnm *image)
319 {
320 int width, height;
321 int maxval = 0;
322 int n_chan, bpp;
323 char linebuf[256];
324
325 if (fgets (linebuf, sizeof(linebuf), image->f) == NULL ||
326 linebuf[0] != 'P' || linebuf[1] < '4' || linebuf[1] > '6')
327 return 1;
328 switch (linebuf[1])
329 {
330 case '4':
331 n_chan = 1;
332 bpp = 1;
333 maxval = 1;
334 break;
335 case '5':
336 n_chan = 1;
337 bpp = 8;
338 break;
339 case '6':
340 n_chan = 3;
341 bpp = 8;
342 break;
343 default:
344 return 1;
345 }
346 do
347 {
348 if (fgets (linebuf, sizeof(linebuf), image->f) == NULL)
349 return 1;
350 }
351 while (linebuf[0] == '#');
352 if (sscanf (linebuf, "%d %d", &width, &height) != 2)
353 return 1;
354 while (!maxval)
355 {
356 if (fgets (linebuf, sizeof(linebuf), image->f) == NULL)
357 return 1;
358 if (linebuf[0] == '#')
359 continue;
360 if (sscanf(linebuf, "%d", &maxval) != 1 || maxval <= 0 || maxval > 255)
361 return 1;
362 }
363 image->super.width = width;
364 image->super.height = height;
365 image->super.maxval = maxval;
366 image->super.raster = n_chan * ((width * bpp + 7) >> 3);
367 image->super.n_chan = n_chan;
368 image->super.bpp = bpp;
369 return 0;
370 }
371
372 Image *
alloc_image_file(const char * fn)373 alloc_image_file (const char *fn)
374 {
375 /* This is the place to add a dispatcher for other image types. */
376 ImagePnm *image = alloc_pnm_image (fn);
377
378 if (image == NULL)
379 return NULL;
380 image->super.close = image_pnm_close;
381 image->super.get_scan_line = image_pnm_get_scan_line;
382 image->super.seek = no_seek;
383 image->super.feof_ = image_pnm_feof;
384 return &image->super;
385 }
386
387 static int
open_image(Image * self)388 open_image (Image *self)
389 {
390 /* This is the place to add a dispatcher for other image types. */
391 ImagePnm *pnm = (ImagePnm *)self;
392
393 return open_pnm_image (pnm);
394 }
395
396 static uchar **
alloc_window(int row_bytes,int window_size)397 alloc_window (int row_bytes, int window_size)
398 {
399 uchar **buf = (uchar **)malloc (window_size * sizeof(uchar *));
400 int i;
401
402 for (i = 0; i < window_size; i++)
403 buf[i] = malloc (row_bytes);
404 return buf;
405 }
406
407 static void
free_window(uchar ** buf,int window_size)408 free_window (uchar **buf, int window_size)
409 {
410 int i;
411
412 for (i = 0; i < window_size; i++)
413 free (buf[i]);
414 free (buf);
415 }
416
417 static void
roll_window(uchar ** buf,int window_size)418 roll_window (uchar **buf, int window_size)
419 {
420 int i;
421 uchar *tmp1, *tmp2;
422
423 tmp1 = buf[window_size - 1];
424 for (i = 0; i < window_size; i++)
425 {
426 tmp2 = buf[i];
427 buf[i] = tmp1;
428 tmp1 = tmp2;
429 }
430 }
431
432 static bool
check_window(uchar ** buf1,uchar ** buf2,const FuzzyParams * fparams,int x,int y,int width,int height)433 check_window (uchar **buf1, uchar **buf2,
434 const FuzzyParams *fparams,
435 int x, int y, int width, int height)
436 {
437 int tolerance = fparams->tolerance;
438 int window_size = fparams->window_size;
439 int i, j;
440 const int half_win = window_size >> 1;
441 const uchar *rowmid1 = buf1[half_win];
442 const uchar *rowmid2 = buf2[half_win];
443 uchar r1 = rowmid1[x * 3], g1 = rowmid1[x * 3 + 1], b1 = rowmid1[x * 3 + 2];
444 uchar r2 = rowmid2[x * 3], g2 = rowmid2[x * 3 + 1], b2 = rowmid2[x * 3 + 2];
445 bool match1 = FALSE, match2 = FALSE;
446
447 for (j = -half_win; j <= half_win; j++)
448 {
449 const uchar *row1 = buf1[j + half_win];
450 const uchar *row2 = buf2[j + half_win];
451 for (i = -half_win; i <= half_win; i++)
452 if ((i != 0 || j != 0) &&
453 x + i >= 0 && x + i < width &&
454 y + j >= 0 && y + j < height)
455 {
456 if (abs (r1 - row2[(x + i) * 3]) <= tolerance &&
457 abs (g1 - row2[(x + i) * 3 + 1]) <= tolerance &&
458 abs (b1 - row2[(x + i) * 3 + 2]) <= tolerance)
459 match1 = TRUE;
460 if (abs (r2 - row1[(x + i) * 3]) <= tolerance &&
461 abs (g2 - row1[(x + i) * 3 + 1]) <= tolerance &&
462 abs (b2 - row1[(x + i) * 3 + 2]) <= tolerance)
463 match2 = TRUE;
464 }
465 }
466 return !(match1 && match2);
467 }
468
469 static int
fuzzy_diff_images(Image * image1,Image * image2,const FuzzyParams * fparams,FuzzyReport * freport,ImagePnm * image_out)470 fuzzy_diff_images (Image *image1, Image *image2, const FuzzyParams *fparams,
471 FuzzyReport *freport, ImagePnm *image_out)
472 {
473 int width = MIN(image1->width, image2->width);
474 int height = MIN(image1->height, image2->height);
475 int max_height = MAX(image1->height, image2->height);
476 int tolerance = fparams->tolerance;
477 int window_size = fparams->window_size;
478 int row_bytes = width * 3;
479 unsigned int out_buffer_size = (image_out ? row_bytes : 0);
480 int half_win = window_size >> 1;
481 uchar **buf1 = alloc_window (row_bytes*2, window_size);
482 uchar **buf2 = alloc_window (row_bytes*2, window_size);
483 uchar *out_buf = NULL;
484 int x0 = -2, y0 = -2, mc = 0, mmax = 10;
485 int y;
486 int rcode = 0;
487 double diff[3];
488 double color_diff;
489
490 if (image_out != NULL)
491 {
492 out_buf = malloc(out_buffer_size*2);
493 if (out_buf == NULL)
494 printf ("Can't allocate output buffer.\n");
495 }
496
497 /* Read rows ahead for half window : */
498 for (y = 0; y < MIN(half_win, height); y++)
499 {
500 if (image_get_rgb_scan_line_with_error (image1, buf1[half_win - y], 1, y, &rcode))
501 goto ex;
502 if (image_get_rgb_scan_line_with_error (image2, buf2[half_win - y], 2, y, &rcode))
503 goto ex;
504 }
505 /* Initialie remaining scanlines if height < half_win */
506 for (; y < half_win; y++) {
507 memcpy(buf1[half_win - y], buf1[half_win], width * 3);
508 memcpy(buf2[half_win - y], buf2[half_win], width * 3);
509 }
510
511 /* Initialie the rest of the buffer that would be uninitialised */
512 /* before half_win lines is read, because check_window() always */
513 /* peeks into the whole window */
514 for (y = 0; y < half_win; y++) {
515 memcpy(buf1[half_win + y + 1], buf1[half_win], width * 3);
516 memcpy(buf2[half_win + y + 1], buf2[half_win], width * 3);
517 }
518
519 /* Do compare : */
520 for (y = 0; y < height; y++)
521 {
522 int x;
523 uchar *row1 = buf1[0];
524 uchar *row2 = buf2[0];
525 uchar *rowmid1 = buf1[half_win];
526 uchar *rowmid2 = buf2[half_win];
527
528 if (y < height - half_win)
529 {
530 if (image_get_rgb_scan_line_with_error (image1, row1, 1, y + half_win, &rcode))
531 goto ex;
532 if (image_get_rgb_scan_line_with_error (image2, row2, 2, y + half_win, &rcode))
533 goto ex;
534 }
535 if (out_buf)
536 memset(out_buf, 0, out_buffer_size*2);
537
538 if (memcmp(rowmid1, rowmid2, width * 3))
539 {
540 for (x = 0; x < width; x++)
541 {
542 if (rowmid1[x * 3] != rowmid2[x * 3] ||
543 rowmid1[x * 3 + 1] != rowmid2[x * 3 + 1] ||
544 rowmid1[x * 3 + 2] != rowmid2[x * 3 + 2])
545 {
546 freport->n_diff++;
547 /* If max error is the largest it can be in a 3 band
548 unsigned char image, then stop collecting this
549 information since we are likely dealing with a half
550 tone image and the max color error is not of interest */
551 if ( freport->max_color_error < 440) {
552 diff[0] = rowmid1[x * 3] - rowmid2[x * 3];
553 diff[1] = rowmid1[x * 3 + 1] - rowmid2[x * 3 + 1];
554 diff[2] = rowmid1[x * 3 + 2] - rowmid2[x * 3 + 2];
555 color_diff = sqrt(diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2]);
556 if (color_diff > freport->max_color_error) {
557 freport->max_color_error = color_diff;
558 }
559 if( freport->n_diff == 1) {
560 freport->avg_color_error = color_diff;
561 } else {
562 freport->avg_color_error =
563 ( freport->avg_color_error*(freport->n_diff-1) +
564 color_diff) / freport->n_diff;
565 }
566 }
567 if (abs (rowmid1[x * 3] - rowmid2[x * 3]) > tolerance ||
568 abs (rowmid1[x * 3 + 1] - rowmid2[x * 3 + 1]) > tolerance ||
569 abs (rowmid1[x * 3 + 2] - rowmid2[x * 3 + 2]) > tolerance)
570 {
571 freport->n_outof_tolerance++;
572 if (check_window (buf1, buf2, fparams, x, y, width, height)) {
573 freport->n_outof_window++;
574 if (out_buf && x < image1->width) {
575 out_buf[x * 3 + 0] = abs(rowmid1[x * 3 + 0]- rowmid2[x * 3 + 0]);
576 out_buf[x * 3 + 1] = abs(rowmid1[x * 3 + 1]- rowmid2[x * 3 + 1]);
577 out_buf[x * 3 + 2] = abs(rowmid1[x * 3 + 2]- rowmid2[x * 3 + 2]);
578 }
579 if (fparams->report_coordinates &&
580 (abs(x - x0) > 1 && y == y0 || y - y0 > 1))
581 {
582 /* fixme : a contiguity test wanted. */
583 x0 = x; y0 = y;
584 mc++;
585 if (mc < mmax) {
586 printf("diff: x=%d y=%d c1=%02X%02X%02X c2=%02X%02X%02X\n", x, y,
587 rowmid1[x * 3], rowmid1[x * 3 + 1], rowmid1[x * 3 + 2],
588 rowmid2[x * 3], rowmid2[x * 3 + 1], rowmid2[x * 3 + 2]);
589 } else if (mc == mmax)
590 printf("Won't report more differences.\n");
591 }
592 }
593 }
594 }
595 }
596 }
597
598 roll_window (buf1, window_size);
599 roll_window (buf2, window_size);
600 if (out_buf) {
601 if (image_out->super.seek(&image_out->super, y))
602 {
603 printf ("I/O Error seeking to the output image position.\n");
604 free(out_buf);
605 out_buf = NULL;
606 }
607 else if (fwrite(out_buf, 1, out_buffer_size, image_out->f) != out_buffer_size)
608 {
609 printf ("I/O Error writing the output image.\n");
610 free(out_buf);
611 out_buf = NULL;
612 }
613 }
614 }
615 y += half_win;
616 for (; y < max_height; y++) {
617 if (y < image1->height) {
618 if (image_get_rgb_scan_line_with_error(image1, buf1[0], 1, y, &rcode))
619 goto ex;
620 }
621 if (y < image2->height) {
622 if (image_get_rgb_scan_line_with_error(image2, buf2[0], 2, y, &rcode))
623 goto ex;
624 }
625 }
626 ex:
627 free_window (buf1, window_size);
628 free_window (buf2, window_size);
629 if (out_buf)
630 free(out_buf);
631 return rcode;
632 }
633
634 static const char *
get_arg(int argc,char ** argv,int * pi,const char * arg)635 get_arg (int argc, char **argv, int *pi, const char *arg)
636 {
637 if (arg[0] != 0)
638 return arg;
639 else
640 {
641 (*pi)++;
642 if (*pi == argc)
643 return NULL;
644 else
645 return argv[*pi];
646 }
647 }
648
649 int
usage(void)650 usage (void)
651 {
652 printf ("Usage: fuzzy [-w window_size] [-t tolerance] [-c] a.ppm b.ppm [diff.ppm | diff.bmp]\n");
653 return 1;
654 }
655
656 int
main(int argc,char ** argv)657 main (int argc, char **argv)
658 {
659 Image *image1, *image2;
660 ImagePnm *image_out = NULL;
661 FuzzyParams fparams;
662 FuzzyReport freport;
663 const char *fn[3] = {0, 0, 0};
664 int fn_idx = 0;
665 int i, page = 1, rcode = 0;
666 char *out_fn = NULL;
667
668 fparams.tolerance = 2;
669 fparams.window_size = 3;
670 fparams.report_coordinates = FALSE;
671
672 for (i = 1; i < argc; i++)
673 {
674 const char *arg = argv[i];
675
676 if (arg[0] == '-')
677 {
678 switch (arg[1])
679 {
680 case 'w':
681 fparams.window_size = atoi (get_arg (argc, argv, &i, arg + 2));
682 if ((fparams.window_size & 1) == 0)
683 {
684 printf ("window size must be odd\n");
685 return 1;
686 }
687 break;
688 case 't':
689 fparams.tolerance = atoi (get_arg (argc, argv, &i, arg + 2));
690 break;
691 case 'c':
692 fparams.report_coordinates = TRUE;
693 break;
694 default:
695 return usage ();
696 }
697 }
698 else if (fn_idx < sizeof(fn)/sizeof(fn[0]))
699 fn[fn_idx++] = argv[i];
700 else
701 return usage ();
702 }
703
704 if (fn_idx < 2)
705 return usage ();
706
707 image1 = alloc_image_file (fn[0]);
708 if (image1 == NULL)
709 return 1;
710
711 image2 = alloc_image_file (fn[1]);
712 if (image2 == NULL)
713 {
714 image_close (image1);
715 return 1;
716 }
717
718 if (fn[2]) {
719 out_fn = malloc(strlen(fn[2]) + 5);
720 if (out_fn == NULL) {
721 printf("Can't alloc the output file name.\n");
722 return 1;
723 }
724 }
725
726 while (!image1->feof_(image1) || !image2->feof_(image2))
727 {
728 if (image1->feof_(image1))
729 {
730 printf ("Extra data (maybe pages) in the image file 2.\n");
731 return 1;
732 }
733 if (image2->feof_(image2))
734 {
735 printf ("Extra data (maybe pages) in the image file 1.\n");
736 return 1;
737 }
738 if (open_image(image1))
739 {
740 printf ("Error parsing the page %d header in file %s\n", page, fn[0]);
741 return 1;
742 }
743
744 if (open_image(image2))
745 {
746 printf ("Error parsing the page %d header in file %s\n", page, fn[1]);
747 return 1;
748 }
749 if (image1->width != image2->width) {
750 printf("Different image width for page %d (%d vs %d)\n", page,image1->width,image2->width);
751 rcode = MAX(rcode, 1);
752 if (image1->width*2<image2->width)
753 return(rcode);
754 }
755 if (image1->height != image2->height) {
756 printf("Different image height for page %d (%d vs %d)\n", page,image1->height,image2->height);
757 rcode = MAX(rcode, 1);
758 }
759 if (out_fn != NULL) {
760 int l = strlen(fn[2]);
761
762 strcpy(out_fn, fn[2]);
763 for (i = l; i >= 0; i--) {
764 if (out_fn[i] == '\\' || out_fn[i] == '/' || out_fn[i] == '.')
765 break;
766 }
767 if (out_fn[i] == '.') {
768 char c;
769
770 memmove(out_fn + i + 4, out_fn + i, l + 1 - i);
771 c = out_fn[i + 4];
772 sprintf(out_fn + i, "-%03d", page);
773 out_fn[i + 4] = c;
774 } else
775 sprintf(out_fn + l, "-%03d", page);
776 image_out =
777 (!strcmp(fn[2]+ strlen(fn[2]) - 4, ".bmp") ? create_bmp_image
778 : create_pnm_image)
779 (image1, out_fn);
780 } else
781 image_out = NULL;
782
783 freport.n_diff = 0;
784 freport.n_outof_tolerance = 0;
785 freport.n_outof_window = 0;
786 freport.avg_color_error = 0;
787 freport.max_color_error = 0;
788 if (fuzzy_diff_images (image1, image2, &fparams, &freport, image_out))
789 return 1;
790 if (image_out)
791 image_pnm_close (&image_out->super);
792 image_out = NULL;
793 if (freport.n_diff > 0)
794 {
795 printf ("%s: page %d: %d diff., %d out of tol., %d out of win., %3.2lf avg diff, %3.2lf max diff\n",
796 fn[0], page, freport.n_diff, freport.n_outof_tolerance,
797 freport.n_outof_window,freport.avg_color_error,
798 freport.max_color_error);
799 rcode = MAX(rcode, 1);
800 }
801 if (freport.n_outof_window > 0)
802 rcode = MAX(rcode, 2);
803 page++;
804 }
805 image_close (image1);
806 image_close (image2);
807 return rcode;
808 }
809