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