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