1 /* gifdiff.c - Gifdiff compares GIF images for identical appearance.
2    Copyright (C) 1998-2001 Eddie Kohler, eddietwo@lcs.mit.edu
3    This file is part of gifdiff, in the gifsicle package.
4 
5    Gifdiff is free software. It is distributed under the GNU Public License,
6    version 2 or later; you can copy, distribute, or alter it at will, as long
7    as this notice is kept intact and this source code is made available. There
8    is no warranty, express or implied. */
9 
10 #include "config.h"
11 #include "gif.h"
12 #include "clp.h"
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <errno.h>
17 
18 #define QUIET_OPT		300
19 #define HELP_OPT		301
20 #define VERSION_OPT		302
21 
22 Clp_Option options[] = {
23   { "help", 'h', HELP_OPT, 0, 0 },
24   { "brief", 'q', QUIET_OPT, 0, Clp_Negate },
25   { "version", 'v', VERSION_OPT, 0, 0 },
26 };
27 
28 static const char *program_name;
29 
30 static const char *filename1;
31 static const char *filename2;
32 
33 static int screen_width, screen_height;
34 #define TRANSP (0)
35 
36 static u_int16_t *data1;
37 static u_int16_t *data2;
38 static u_int16_t *last1;
39 static u_int16_t *last2;
40 
41 static int brief;
42 
43 
44 static void
combine_colormaps(Gif_Colormap * gfcm,Gif_Colormap * newcm)45 combine_colormaps(Gif_Colormap *gfcm, Gif_Colormap *newcm)
46 {
47   int i;
48   if (!gfcm) return;
49   for (i = 0; i < gfcm->ncol; i++) {
50     Gif_Color *c = &gfcm->col[i];
51     c->pixel = Gif_AddColor(newcm, c, 1);
52   }
53 }
54 
55 static void
fill_area(u_int16_t * data,int l,int t,int w,int h,u_int16_t val)56 fill_area(u_int16_t *data, int l, int t, int w, int h, u_int16_t val)
57 {
58   int x, y;
59   for (y = t; y < t+h; y++) {
60     u_int16_t *d = data + screen_width * y + l;
61     for (x = 0; x < w; x++)
62       *d++ = val;
63   }
64 }
65 
66 
67 static void
apply_image(int is_second,Gif_Stream * gfs,Gif_Image * gfi)68 apply_image(int is_second, Gif_Stream *gfs, Gif_Image *gfi)
69 {
70   int i, x, y;
71   int width = gfi->width;
72   u_int16_t map[256];
73   u_int16_t *data = (is_second ? data2 : data1);
74   u_int16_t *last = (is_second ? last2 : last1);
75   Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global;
76 
77   /* set up colormap */
78   for (i = 0; i < 256; i++)
79     map[i] = 1;
80   if (gfs)
81     for (i = 0; i < gfcm->ncol; i++)
82       map[i] = gfcm->col[i].pixel;
83   if (gfi->transparent >= 0 && gfi->transparent < 256)
84     map[gfi->transparent] = TRANSP;
85 
86   /* copy image over */
87   Gif_UncompressImage(gfi);
88   Gif_ClipImage(gfi, 0, 0, screen_width, screen_height);
89   for (y = 0; y < gfi->height; y++) {
90     u_int16_t *outd = data + screen_width * (y + gfi->top) + gfi->left;
91     byte *ind = gfi->img[y];
92     if (gfi->disposal == GIF_DISPOSAL_PREVIOUS)
93       memcpy(last + screen_width * y + gfi->left, outd,
94 	     sizeof(u_int16_t) * width);
95     for (x = 0; x < width; x++, outd++, ind++)
96       if (map[*ind] != TRANSP)
97 	*outd = map[*ind];
98   }
99   Gif_ReleaseUncompressedImage(gfi);
100   Gif_ReleaseCompressedImage(gfi);
101 }
102 
103 static void
apply_image_disposal(int is_second,Gif_Stream * gfs,Gif_Image * gfi,u_int16_t background)104 apply_image_disposal(int is_second, Gif_Stream *gfs, Gif_Image *gfi,
105 		     u_int16_t background)
106 {
107   int x, y, width = gfi->width;
108   u_int16_t *data = (is_second ? data2 : data1);
109   u_int16_t *last = (is_second ? last2 : last1);
110 
111   if (gfi->disposal == GIF_DISPOSAL_PREVIOUS)
112     for (y = gfi->top; y < gfi->top + gfi->height; y++)
113       memcpy(data + screen_width * y + gfi->left,
114 	     last + screen_width * y + gfi->left,
115 	     sizeof(u_int16_t) * width);
116   else if (gfi->disposal == GIF_DISPOSAL_BACKGROUND)
117     for (y = gfi->top; y < gfi->top + gfi->height; y++) {
118       u_int16_t *d = data + screen_width * y + gfi->left;
119       for (x = 0; x < gfi->width; x++)
120 	*d++ = background;
121     }
122 }
123 
124 
125 #define SAME 0
126 #define DIFFERENT 1
127 static int was_different;
128 
129 static void
different(const char * format,...)130 different(const char *format, ...)
131 {
132   va_list val;
133   va_start(val, format);
134   if (!brief) {
135     vfprintf(stderr, format, val);
136     fputc('\n', stderr);
137   }
138   va_end(val);
139   was_different = 1;
140 }
141 
142 
143 static void
name_loopcount(int loopcount,char * buf)144 name_loopcount(int loopcount, char *buf)
145 {
146   if (loopcount < 0)
147     strcpy(buf, "none");
148   else if (loopcount == 0)
149     strcpy(buf, "forever");
150   else
151     sprintf(buf, "%d", loopcount);
152 }
153 
154 static void
name_delay(int delay,char * buf)155 name_delay(int delay, char *buf)
156 {
157   if (delay == 0)
158     strcpy(buf, "none");
159   else
160     sprintf(buf, "%d.%02ds", delay / 100, delay % 100);
161 }
162 
163 static void
name_color(int color,Gif_Colormap * gfcm,char * buf)164 name_color(int color, Gif_Colormap *gfcm, char *buf)
165 {
166   if (color == TRANSP)
167     strcpy(buf, "transparent");
168   else {
169     Gif_Color *c = &gfcm->col[color];
170     sprintf(buf, "#%02X%02X%02X", c->red, c->green, c->blue);
171   }
172 }
173 
174 
175 int
compare(Gif_Stream * s1,Gif_Stream * s2)176 compare(Gif_Stream *s1, Gif_Stream *s2)
177 {
178   Gif_Colormap *newcm;
179   int imageno, background1, background2;
180   char buf1[256], buf2[256];
181 
182   was_different = 0;
183 
184   /* Compare image counts and screen sizes. If either of these differs, quit
185      early. */
186   Gif_CalculateScreenSize(s1, 0);
187   Gif_CalculateScreenSize(s2, 0);
188 
189   if (s1->nimages != s2->nimages)
190     different("frame counts differ: <%d >%d", s1->nimages, s2->nimages);
191   if (s1->screen_width != s2->screen_width
192       || s1->screen_height != s2->screen_height)
193     different("screen sizes differ: <%dx%d >%dx%d", s1->screen_width,
194 	      s1->screen_height, s2->screen_width, s2->screen_height);
195 
196   if (was_different)
197     return DIFFERENT;
198   else if (s1->nimages == 0)
199     return SAME;
200 
201   /* Create arrays for the image data */
202   screen_width = s1->screen_width;
203   screen_height = s1->screen_height;
204 
205   data1 = Gif_NewArray(u_int16_t, screen_width * screen_height);
206   data2 = Gif_NewArray(u_int16_t, screen_width * screen_height);
207   last1 = Gif_NewArray(u_int16_t, screen_width * screen_height);
208   last2 = Gif_NewArray(u_int16_t, screen_width * screen_height);
209 
210   /* Merge all distinct colors from the two images into one colormap, setting
211      the `pixel' slots in the images' colormaps to the corresponding values
212      in the merged colormap. Don't forget transparency */
213   newcm = Gif_NewFullColormap(1, 256);
214   combine_colormaps(s1->global, newcm);
215   combine_colormaps(s2->global, newcm);
216   for (imageno = 0; imageno < s1->nimages; imageno++) {
217     combine_colormaps(s1->images[imageno]->local, newcm);
218     combine_colormaps(s2->images[imageno]->local, newcm);
219   }
220 
221   /* Choose the background values and clear the image data arrays */
222   if (s1->images[0]->transparent >= 0 || !s1->global)
223     background1 = TRANSP;
224   else
225     background1 = s1->global->col[ s1->background ].pixel;
226 
227   if (s2->images[0]->transparent >= 0 || !s2->global)
228     background2 = TRANSP;
229   else
230     background2 = s2->global->col[ s2->background ].pixel;
231 
232   fill_area(data1, 0, 0, screen_width, screen_height, background1);
233   fill_area(data2, 0, 0, screen_width, screen_height, background2);
234 
235   /* Loopcounts differ? */
236   if (s1->loopcount != s2->loopcount) {
237     name_loopcount(s1->loopcount, buf1);
238     name_loopcount(s2->loopcount, buf2);
239     different("loop counts differ: <%s >%s", buf1, buf2);
240   }
241 
242   /* Loop over frames, comparing image data and delays */
243   for (imageno = 0; imageno < s1->nimages; imageno++) {
244     Gif_Image *gfi1 = s1->images[imageno], *gfi2 = s2->images[imageno];
245     apply_image(0, s1, gfi1);
246     apply_image(1, s2, gfi2);
247 
248     if (memcmp(data1, data2, screen_width * screen_height * sizeof(u_int16_t))
249 	!= 0) {
250       int d, c = screen_width * screen_height;
251       u_int16_t *d1 = data1, *d2 = data2;
252       for (d = 0; d < c; d++, d1++, d2++)
253 	if (*d1 != *d2) {
254 	  name_color(*d1, newcm, buf1);
255 	  name_color(*d2, newcm, buf2);
256 	  different("frame #%d pixels differ: %d,%d <%s >%s",
257 		    imageno, d % screen_width, d / screen_width, buf1, buf2);
258 	  break;
259 	}
260     }
261 
262     if (gfi1->delay != gfi2->delay) {
263       name_delay(gfi1->delay, buf1);
264       name_delay(gfi2->delay, buf2);
265       different("frame #%d delays differ: <%s >%s", imageno, buf1, buf2);
266     }
267 
268     apply_image_disposal(0, s1, gfi1, background1);
269     apply_image_disposal(1, s2, gfi2, background2);
270   }
271 
272   /* That's it! */
273   Gif_DeleteColormap(newcm);
274   Gif_DeleteArray(data1);
275   Gif_DeleteArray(data2);
276   Gif_DeleteArray(last1);
277   Gif_DeleteArray(last2);
278 
279   return was_different ? DIFFERENT : SAME;
280 }
281 
282 
283 void
short_usage(void)284 short_usage(void)
285 {
286   fprintf(stderr, "Usage: %s [OPTION]... FILE1 FILE2\n\
287 Try `%s --help' for more information.\n",
288 	  program_name, program_name);
289 }
290 
291 void
usage(void)292 usage(void)
293 {
294   printf("\
295 `Gifdiff' compares two GIF files (either images or animations) for identical\n\
296 visual appearance. An animation and an optimized version of the same animation\n\
297 should compare as the same. Gifdiff exits with status 0 if the images are\n\
298 the same, 1 if they're different, and 2 if there was some error.\n\
299 \n\
300 Usage: %s [OPTION]... FILE1 FILE2\n\
301 \n\
302 Options:\n\
303   -q, --brief                   Don't report detailed differences.\n\
304   -h, --help                    Print this message and exit.\n\
305   -v, --version                 Print version number and exit.\n\
306 \n\
307 Report bugs to <eddietwo@lcs.mit.edu>.\n", program_name);
308 }
309 
310 
311 void
fatal_error(char * message,...)312 fatal_error(char *message, ...)
313 {
314   va_list val;
315   va_start(val, message);
316   fprintf(stderr, "%s: ", program_name);
317   vfprintf(stderr, message, val);
318   fputc('\n', stderr);
319   exit(2);			/* exit(2) for trouble */
320 }
321 
322 void
error(char * message,...)323 error(char *message, ...)
324 {
325   va_list val;
326   va_start(val, message);
327   fprintf(stderr, "%s: ", program_name);
328   vfprintf(stderr, message, val);
329   fputc('\n', stderr);
330 }
331 
332 void
warning(char * message,...)333 warning(char *message, ...)
334 {
335   va_list val;
336   va_start(val, message);
337   fprintf(stderr, "%s: warning: ", program_name);
338   vfprintf(stderr, message, val);
339   fputc('\n', stderr);
340 }
341 
342 static int gifread_error_count;
343 
344 static void
gifread_error(const char * message,int which_image,void * thunk)345 gifread_error(const char *message, int which_image, void *thunk)
346 {
347   static int last_which_image = 0;
348   static const char *last_message = 0;
349   static int different_error_count = 0;
350   static int same_error_count = 0;
351   const char *filename = (const char *)thunk;
352 
353   if (gifread_error_count == 0) {
354     last_which_image = -1;
355     different_error_count = 0;
356   }
357 
358   gifread_error_count++;
359   if (last_message && different_error_count <= 10
360       && (message != last_message || last_which_image != which_image)) {
361     if (same_error_count == 1)
362       error("  %s", last_message);
363     else if (same_error_count > 0)
364       error("  %s (%d times)", last_message, same_error_count);
365     same_error_count = 0;
366   }
367 
368   if (message != last_message)
369     different_error_count++;
370 
371   same_error_count++;
372   last_message = message;
373   if (last_which_image != which_image && different_error_count <= 10
374       && message) {
375     error("Error while reading `%s' frame #%d:", filename, which_image);
376     last_which_image = which_image;
377   }
378 
379   if (different_error_count == 11 && message)
380     error("(more errors while reading `%s')", filename);
381 }
382 
383 
384 int
main(int argc,char ** argv)385 main(int argc, char **argv)
386 {
387   int how_many_inputs = 0;
388   int status;
389   const char **inputp;
390   FILE *f1, *f2;
391   Gif_Stream *gfs1, *gfs2;
392 
393   Clp_Parser *clp =
394     Clp_NewParser(argc, argv, sizeof(options) / sizeof(options[0]), options);
395 
396   program_name = Clp_ProgramName(clp);
397   brief = 0;
398 
399   while (1) {
400     int opt = Clp_Next(clp);
401     switch (opt) {
402 
403      case HELP_OPT:
404       usage();
405       exit(0);
406       break;
407 
408      case VERSION_OPT:
409       printf("gifdiff (LCDF Gifsicle) %s\n", VERSION);
410       printf("Copyright (C) 1998-2001 Eddie Kohler\n\
411 This is free software; see the source for copying conditions.\n\
412 There is NO warranty, not even for merchantability or fitness for a\n\
413 particular purpose.\n");
414       exit(0);
415       break;
416 
417      case QUIET_OPT:
418       brief = !clp->negated;
419       break;
420 
421      case Clp_NotOption:
422       if (how_many_inputs == 2) {
423 	error("too many file arguments");
424 	goto bad_option;
425       }
426       inputp = (how_many_inputs == 0 ? &filename1 : &filename2);
427       how_many_inputs++;
428       if (strcmp(clp->arg, "-") == 0)
429 	*inputp = 0;
430       else
431 	*inputp = clp->arg;
432       break;
433 
434      bad_option:
435      case Clp_BadOption:
436       short_usage();
437       exit(1);
438       break;
439 
440      case Clp_Done:
441       goto done;
442 
443     }
444   }
445 
446  done:
447 
448   if (how_many_inputs < 2)
449     fatal_error("need exactly 2 file arguments");
450   if (filename1 == 0 && filename2 == 0)
451     fatal_error("can't read both files from stdin");
452 
453   if (filename1 == 0) {
454     f1 = stdin;
455     filename1 = "<stdin>";
456   } else {
457     f1 = fopen(filename1, "rb");
458     if (!f1)
459       fatal_error("%s: %s", filename1, strerror(errno));
460   }
461   gifread_error_count = 0;
462   gfs1 = Gif_FullReadFile(f1, GIF_READ_COMPRESSED, gifread_error, (void *)filename1);
463   gifread_error(0, -1, (void *)filename1); /* print out last error message */
464   if (!gfs1)
465     fatal_error("`%s' doesn't seem to contain a GIF", filename1);
466 
467   if (filename2 == 0) {
468     f2 = stdin;
469     filename2 = "<stdin>";
470   } else {
471     f2 = fopen(filename2, "rb");
472     if (!f2)
473       fatal_error("%s: %s", filename2, strerror(errno));
474   }
475   gifread_error_count = 0;
476   gfs2 = Gif_FullReadFile(f2, GIF_READ_COMPRESSED, gifread_error, (void *)filename2);
477   gifread_error(0, -1, (void *)filename2); /* print out last error message */
478   if (!gfs2) fatal_error("`%s' doesn't seem to contain a GIF", filename2);
479 
480   status = (compare(gfs1, gfs2) == DIFFERENT);
481   if (status == 1 && brief)
482     printf("GIF files %s and %s differ\n", filename1, filename2);
483 
484   Gif_DeleteStream(gfs1);
485   Gif_DeleteStream(gfs2);
486 #ifdef DMALLOC
487   dmalloc_report();
488 #endif
489   return status;
490 }
491