00001
00002
00003
00004
00005
00006
00007
00008
00009
00010 #include "config.h"
00011 #include "gif.h"
00012 #include "clp.h"
00013 #include <stdarg.h>
00014 #include <stdio.h>
00015 #include <string.h>
00016 #include <errno.h>
00017
00018 #define QUIET_OPT 300
00019 #define HELP_OPT 301
00020 #define VERSION_OPT 302
00021
00022 Clp_Option options[] = {
00023 { "help", 'h', HELP_OPT, 0, 0 },
00024 { "brief", 'q', QUIET_OPT, 0, Clp_Negate },
00025 { "version", 'v', VERSION_OPT, 0, 0 },
00026 };
00027
00028 static const char *program_name;
00029
00030 static const char *filename1;
00031 static const char *filename2;
00032
00033 static int screen_width, screen_height;
00034 #define TRANSP (0)
00035
00036 static u_int16_t *data1;
00037 static u_int16_t *data2;
00038 static u_int16_t *last1;
00039 static u_int16_t *last2;
00040
00041 static int brief;
00042
00043
00044 static void
00045 combine_colormaps(Gif_Colormap *gfcm, Gif_Colormap *newcm)
00046 {
00047 int i;
00048 if (!gfcm) return;
00049 for (i = 0; i < gfcm->ncol; i++) {
00050 Gif_Color *c = &gfcm->col[i];
00051 c->pixel = Gif_AddColor(newcm, c, 1);
00052 }
00053 }
00054
00055 static void
00056 fill_area(u_int16_t *data, int l, int t, int w, int h, u_int16_t val)
00057 {
00058 int x, y;
00059 for (y = t; y < t+h; y++) {
00060 u_int16_t *d = data + screen_width * y + l;
00061 for (x = 0; x < w; x++)
00062 *d++ = val;
00063 }
00064 }
00065
00066
00067 static void
00068 apply_image(int is_second, Gif_Stream *gfs, Gif_Image *gfi)
00069 {
00070 int i, x, y;
00071 int width = gfi->width;
00072 u_int16_t map[256];
00073 u_int16_t *data = (is_second ? data2 : data1);
00074 u_int16_t *last = (is_second ? last2 : last1);
00075 Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global;
00076
00077
00078 for (i = 0; i < 256; i++)
00079 map[i] = 1;
00080 if (gfs)
00081 for (i = 0; i < gfcm->ncol; i++)
00082 map[i] = gfcm->col[i].pixel;
00083 if (gfi->transparent >= 0 && gfi->transparent < 256)
00084 map[gfi->transparent] = TRANSP;
00085
00086
00087 Gif_UncompressImage(gfi);
00088 Gif_ClipImage(gfi, 0, 0, screen_width, screen_height);
00089 for (y = 0; y < gfi->height; y++) {
00090 u_int16_t *outd = data + screen_width * (y + gfi->top) + gfi->left;
00091 byte *ind = gfi->img[y];
00092 if (gfi->disposal == GIF_DISPOSAL_PREVIOUS)
00093 memcpy(last + screen_width * y + gfi->left, outd,
00094 sizeof(u_int16_t) * width);
00095 for (x = 0; x < width; x++, outd++, ind++)
00096 if (map[*ind] != TRANSP)
00097 *outd = map[*ind];
00098 }
00099 Gif_ReleaseUncompressedImage(gfi);
00100 Gif_ReleaseCompressedImage(gfi);
00101 }
00102
00103 static void
00104 apply_image_disposal(int is_second, Gif_Stream *gfs, Gif_Image *gfi,
00105 u_int16_t background)
00106 {
00107 int x, y, width = gfi->width;
00108 u_int16_t *data = (is_second ? data2 : data1);
00109 u_int16_t *last = (is_second ? last2 : last1);
00110
00111 if (gfi->disposal == GIF_DISPOSAL_PREVIOUS)
00112 for (y = gfi->top; y < gfi->top + gfi->height; y++)
00113 memcpy(data + screen_width * y + gfi->left,
00114 last + screen_width * y + gfi->left,
00115 sizeof(u_int16_t) * width);
00116 else if (gfi->disposal == GIF_DISPOSAL_BACKGROUND)
00117 for (y = gfi->top; y < gfi->top + gfi->height; y++) {
00118 u_int16_t *d = data + screen_width * y + gfi->left;
00119 for (x = 0; x < gfi->width; x++)
00120 *d++ = background;
00121 }
00122 }
00123
00124
00125 #define SAME 0
00126 #define DIFFERENT 1
00127 static int was_different;
00128
00129 static void
00130 different(const char *format, ...)
00131 {
00132 va_list val;
00133 va_start(val, format);
00134 if (!brief) {
00135 vfprintf(stderr, format, val);
00136 fputc('\n', stderr);
00137 }
00138 va_end(val);
00139 was_different = 1;
00140 }
00141
00142
00143 static void
00144 name_loopcount(int loopcount, char *buf)
00145 {
00146 if (loopcount < 0)
00147 strcpy(buf, "none");
00148 else if (loopcount == 0)
00149 strcpy(buf, "forever");
00150 else
00151 sprintf(buf, "%d", loopcount);
00152 }
00153
00154 static void
00155 name_delay(int delay, char *buf)
00156 {
00157 if (delay == 0)
00158 strcpy(buf, "none");
00159 else
00160 sprintf(buf, "%d.%02ds", delay / 100, delay % 100);
00161 }
00162
00163 static void
00164 name_color(int color, Gif_Colormap *gfcm, char *buf)
00165 {
00166 if (color == TRANSP)
00167 strcpy(buf, "transparent");
00168 else {
00169 Gif_Color *c = &gfcm->col[color];
00170 sprintf(buf, "#%02X%02X%02X", c->red, c->green, c->blue);
00171 }
00172 }
00173
00174
00175 int
00176 compare(Gif_Stream *s1, Gif_Stream *s2)
00177 {
00178 Gif_Colormap *newcm;
00179 int imageno, background1, background2;
00180 char buf1[256], buf2[256];
00181
00182 was_different = 0;
00183
00184
00185
00186 Gif_CalculateScreenSize(s1, 0);
00187 Gif_CalculateScreenSize(s2, 0);
00188
00189 if (s1->nimages != s2->nimages)
00190 different("frame counts differ: <%d >%d", s1->nimages, s2->nimages);
00191 if (s1->screen_width != s2->screen_width
00192 || s1->screen_height != s2->screen_height)
00193 different("screen sizes differ: <%dx%d >%dx%d", s1->screen_width,
00194 s1->screen_height, s2->screen_width, s2->screen_height);
00195
00196 if (was_different)
00197 return DIFFERENT;
00198 else if (s1->nimages == 0)
00199 return SAME;
00200
00201
00202 screen_width = s1->screen_width;
00203 screen_height = s1->screen_height;
00204
00205 data1 = Gif_NewArray(u_int16_t, screen_width * screen_height);
00206 data2 = Gif_NewArray(u_int16_t, screen_width * screen_height);
00207 last1 = Gif_NewArray(u_int16_t, screen_width * screen_height);
00208 last2 = Gif_NewArray(u_int16_t, screen_width * screen_height);
00209
00210
00211
00212
00213 newcm = Gif_NewFullColormap(1, 256);
00214 combine_colormaps(s1->global, newcm);
00215 combine_colormaps(s2->global, newcm);
00216 for (imageno = 0; imageno < s1->nimages; imageno++) {
00217 combine_colormaps(s1->images[imageno]->local, newcm);
00218 combine_colormaps(s2->images[imageno]->local, newcm);
00219 }
00220
00221
00222 if (s1->images[0]->transparent >= 0 || !s1->global)
00223 background1 = TRANSP;
00224 else
00225 background1 = s1->global->col[ s1->background ].pixel;
00226
00227 if (s2->images[0]->transparent >= 0 || !s2->global)
00228 background2 = TRANSP;
00229 else
00230 background2 = s2->global->col[ s2->background ].pixel;
00231
00232 fill_area(data1, 0, 0, screen_width, screen_height, background1);
00233 fill_area(data2, 0, 0, screen_width, screen_height, background2);
00234
00235
00236 if (s1->loopcount != s2->loopcount) {
00237 name_loopcount(s1->loopcount, buf1);
00238 name_loopcount(s2->loopcount, buf2);
00239 different("loop counts differ: <%s >%s", buf1, buf2);
00240 }
00241
00242
00243 for (imageno = 0; imageno < s1->nimages; imageno++) {
00244 Gif_Image *gfi1 = s1->images[imageno], *gfi2 = s2->images[imageno];
00245 apply_image(0, s1, gfi1);
00246 apply_image(1, s2, gfi2);
00247
00248 if (memcmp(data1, data2, screen_width * screen_height * sizeof(u_int16_t))
00249 != 0) {
00250 int d, c = screen_width * screen_height;
00251 u_int16_t *d1 = data1, *d2 = data2;
00252 for (d = 0; d < c; d++, d1++, d2++)
00253 if (*d1 != *d2) {
00254 name_color(*d1, newcm, buf1);
00255 name_color(*d2, newcm, buf2);
00256 different("frame #%d pixels differ: %d,%d <%s >%s",
00257 imageno, d % screen_width, d / screen_width, buf1, buf2);
00258 break;
00259 }
00260 }
00261
00262 if (gfi1->delay != gfi2->delay) {
00263 name_delay(gfi1->delay, buf1);
00264 name_delay(gfi2->delay, buf2);
00265 different("frame #%d delays differ: <%s >%s", imageno, buf1, buf2);
00266 }
00267
00268 apply_image_disposal(0, s1, gfi1, background1);
00269 apply_image_disposal(1, s2, gfi2, background2);
00270 }
00271
00272
00273 Gif_DeleteColormap(newcm);
00274 Gif_DeleteArray(data1);
00275 Gif_DeleteArray(data2);
00276 Gif_DeleteArray(last1);
00277 Gif_DeleteArray(last2);
00278
00279 return was_different ? DIFFERENT : SAME;
00280 }
00281
00282
00283 void
00284 short_usage(void)
00285 {
00286 fprintf(stderr, "Usage: %s [OPTION]... FILE1 FILE2\n\
00287 Try `%s --help' for more information.\n",
00288 program_name, program_name);
00289 }
00290
00291 void
00292 usage(void)
00293 {
00294 printf("\
00295 `Gifdiff' compares two GIF files (either images or animations) for identical\n\
00296 visual appearance. An animation and an optimized version of the same animation\n\
00297 should compare as the same. Gifdiff exits with status 0 if the images are\n\
00298 the same, 1 if they're different, and 2 if there was some error.\n\
00299 \n\
00300 Usage: %s [OPTION]... FILE1 FILE2\n\
00301 \n\
00302 Options:\n\
00303 -q, --brief Don't report detailed differences.\n\
00304 -h, --help Print this message and exit.\n\
00305 -v, --version Print version number and exit.\n\
00306 \n\
00307 Report bugs to <eddietwo@lcs.mit.edu>.\n", program_name);
00308 }
00309
00310
00311 void
00312 fatal_error(char *message, ...)
00313 {
00314 va_list val;
00315 va_start(val, message);
00316 fprintf(stderr, "%s: ", program_name);
00317 vfprintf(stderr, message, val);
00318 fputc('\n', stderr);
00319 exit(2);
00320 }
00321
00322 void
00323 error(char *message, ...)
00324 {
00325 va_list val;
00326 va_start(val, message);
00327 fprintf(stderr, "%s: ", program_name);
00328 vfprintf(stderr, message, val);
00329 fputc('\n', stderr);
00330 }
00331
00332 void
00333 warning(char *message, ...)
00334 {
00335 va_list val;
00336 va_start(val, message);
00337 fprintf(stderr, "%s: warning: ", program_name);
00338 vfprintf(stderr, message, val);
00339 fputc('\n', stderr);
00340 }
00341
00342 static int gifread_error_count;
00343
00344 static void
00345 gifread_error(const char *message, int which_image, void *thunk)
00346 {
00347 static int last_which_image = 0;
00348 static const char *last_message = 0;
00349 static int different_error_count = 0;
00350 static int same_error_count = 0;
00351 const char *filename = (const char *)thunk;
00352
00353 if (gifread_error_count == 0) {
00354 last_which_image = -1;
00355 different_error_count = 0;
00356 }
00357
00358 gifread_error_count++;
00359 if (last_message && different_error_count <= 10
00360 && (message != last_message || last_which_image != which_image)) {
00361 if (same_error_count == 1)
00362 error(" %s", last_message);
00363 else if (same_error_count > 0)
00364 error(" %s (%d times)", last_message, same_error_count);
00365 same_error_count = 0;
00366 }
00367
00368 if (message != last_message)
00369 different_error_count++;
00370
00371 same_error_count++;
00372 last_message = message;
00373 if (last_which_image != which_image && different_error_count <= 10
00374 && message) {
00375 error("Error while reading `%s' frame #%d:", filename, which_image);
00376 last_which_image = which_image;
00377 }
00378
00379 if (different_error_count == 11 && message)
00380 error("(more errors while reading `%s')", filename);
00381 }
00382
00383
00384 int
00385 main(int argc, char **argv)
00386 {
00387 int how_many_inputs = 0;
00388 int status;
00389 const char **inputp;
00390 FILE *f1, *f2;
00391 Gif_Stream *gfs1, *gfs2;
00392
00393 Clp_Parser *clp =
00394 Clp_NewParser(argc, argv, sizeof(options) / sizeof(options[0]), options);
00395
00396 program_name = Clp_ProgramName(clp);
00397 brief = 0;
00398
00399 while (1) {
00400 int opt = Clp_Next(clp);
00401 switch (opt) {
00402
00403 case HELP_OPT:
00404 usage();
00405 exit(0);
00406 break;
00407
00408 case VERSION_OPT:
00409 printf("gifdiff (LCDF Gifsicle) %s\n", VERSION);
00410 printf("Copyright (C) 1998-2001 Eddie Kohler\n\
00411 This is free software; see the source for copying conditions.\n\
00412 There is NO warranty, not even for merchantability or fitness for a\n\
00413 particular purpose.\n");
00414 exit(0);
00415 break;
00416
00417 case QUIET_OPT:
00418 brief = !clp->negated;
00419 break;
00420
00421 case Clp_NotOption:
00422 if (how_many_inputs == 2) {
00423 error("too many file arguments");
00424 goto bad_option;
00425 }
00426 inputp = (how_many_inputs == 0 ? &filename1 : &filename2);
00427 how_many_inputs++;
00428 if (strcmp(clp->arg, "-") == 0)
00429 *inputp = 0;
00430 else
00431 *inputp = clp->arg;
00432 break;
00433
00434 bad_option:
00435 case Clp_BadOption:
00436 short_usage();
00437 exit(1);
00438 break;
00439
00440 case Clp_Done:
00441 goto done;
00442
00443 }
00444 }
00445
00446 done:
00447
00448 if (how_many_inputs < 2)
00449 fatal_error("need exactly 2 file arguments");
00450 if (filename1 == 0 && filename2 == 0)
00451 fatal_error("can't read both files from stdin");
00452
00453 if (filename1 == 0) {
00454 f1 = stdin;
00455 filename1 = "<stdin>";
00456 } else {
00457 f1 = fopen(filename1, "rb");
00458 if (!f1)
00459 fatal_error("%s: %s", filename1, strerror(errno));
00460 }
00461 gifread_error_count = 0;
00462 gfs1 = Gif_FullReadFile(f1, GIF_READ_COMPRESSED, gifread_error, (void *)filename1);
00463 gifread_error(0, -1, (void *)filename1);
00464 if (!gfs1)
00465 fatal_error("`%s' doesn't seem to contain a GIF", filename1);
00466
00467 if (filename2 == 0) {
00468 f2 = stdin;
00469 filename2 = "<stdin>";
00470 } else {
00471 f2 = fopen(filename2, "rb");
00472 if (!f2)
00473 fatal_error("%s: %s", filename2, strerror(errno));
00474 }
00475 gifread_error_count = 0;
00476 gfs2 = Gif_FullReadFile(f2, GIF_READ_COMPRESSED, gifread_error, (void *)filename2);
00477 gifread_error(0, -1, (void *)filename2);
00478 if (!gfs2) fatal_error("`%s' doesn't seem to contain a GIF", filename2);
00479
00480 status = (compare(gfs1, gfs2) == DIFFERENT);
00481 if (status == 1 && brief)
00482 printf("GIF files %s and %s differ\n", filename1, filename2);
00483
00484 Gif_DeleteStream(gfs1);
00485 Gif_DeleteStream(gfs2);
00486 #ifdef DMALLOC
00487 dmalloc_report();
00488 #endif
00489 return status;
00490 }