00001
00002
00003
00004
00005
00006
00007
00008
00009
00010 #include "config.h"
00011 #include "gifsicle.h"
00012 #include <stdio.h>
00013 #include <stdarg.h>
00014 #include <string.h>
00015 #include <ctype.h>
00016 #include <assert.h>
00017 #include <errno.h>
00018
00019 const char *program_name = "gifsicle";
00020 static int verbose_pos = 0;
00021 int error_count = 0;
00022 int no_warnings = 0;
00023
00024
00025 static void
00026 verror(int seriousness, char *message, va_list val)
00027 {
00028 char pattern[BUFSIZ];
00029 char buffer[BUFSIZ];
00030 verbose_endline();
00031
00032 if (seriousness > 2)
00033 sprintf(pattern, "%s: fatal error: %%s\n", program_name);
00034 else if (seriousness == 1)
00035 sprintf(pattern, "%s: warning: %%s\n", program_name);
00036 else
00037 sprintf(pattern, "%s: %%s\n", program_name);
00038
00039 if (seriousness > 1)
00040 error_count++;
00041 else if (no_warnings)
00042 return;
00043
00044
00045
00046
00047 if (strlen(message) + strlen(pattern) < BUFSIZ) {
00048 sprintf(buffer, pattern, message);
00049 vfprintf(stderr, buffer, val);
00050 } else {
00051 fwrite(pattern, 1, strlen(pattern) - 3, stderr);
00052 vfprintf(stderr, message, val);
00053 putc('\n', stderr);
00054 }
00055 }
00056
00057 void
00058 fatal_error(char *message, ...)
00059 {
00060 va_list val;
00061 va_start(val, message);
00062 verror(3, message, val);
00063 va_end(val);
00064 exit(EXIT_USER_ERR);
00065 }
00066
00067 void
00068 error(char *message, ...)
00069 {
00070 va_list val;
00071 va_start(val, message);
00072 verror(2, message, val);
00073 va_end(val);
00074 }
00075
00076 void
00077 warning(char *message, ...)
00078 {
00079 va_list val;
00080 va_start(val, message);
00081 verror(1, message, val);
00082 va_end(val);
00083 }
00084
00085 void
00086 warncontext(char *message, ...)
00087 {
00088 va_list val;
00089 va_start(val, message);
00090 verror(0, message, val);
00091 va_end(val);
00092 }
00093
00094 void
00095 clp_error_handler(char *message)
00096 {
00097 verbose_endline();
00098 fputs(message, stderr);
00099 }
00100
00101
00102 void
00103 short_usage(void)
00104 {
00105 fprintf(stderr, "Usage: %s [OPTION | FILE | FRAME]...\n\
00106 Try `%s --help' for more information.\n",
00107 program_name, program_name);
00108 }
00109
00110
00111 void
00112 usage(void)
00113 {
00114 printf("\
00115 `Gifsicle' manipulates GIF images. Its most common uses include combining\n\
00116 single images into animations, adding transparency, optimizing animations for\n\
00117 space, and printing information about GIFs.\n\
00118 \n\
00119 Usage: %s [OPTION | FILE | FRAME]...\n\
00120 \n\
00121 Mode options: at most one, before any filenames.\n\
00122 -m, --merge Merge mode: combine inputs, write stdout.\n\
00123 -b, --batch Batch mode: modify inputs, write back to\n\
00124 same filenames.\n\
00125 -e, --explode Explode mode: write N files for each input,\n\
00126 one per frame, to `input.frame-number'.\n\
00127 -E, --explode-by-name Explode mode, but write `input.name'.\n\
00128 \n\
00129 General options: Also --no-OPTION for info and verbose.\n\
00130 -I, --info Print info about input GIFs. Two -I's means\n\
00131 normal output is not suppressed.\n\
00132 --color-info, --cinfo --info plus colormap details.\n\
00133 --extension-info, --xinfo --info plus extension details.\n\
00134 -v, --verbose Prints progress information.\n\
00135 -h, --help Print this message and exit.\n\
00136 --version Print version number and exit.\n\
00137 -o, --output FILE Write output to FILE.\n\
00138 -w, --no-warnings Don't report warnings.\n\
00139 \n", program_name);
00140 printf("\
00141 Frame selections: #num, #num1-num2, #num1-, #name\n\
00142 \n\
00143 Frame change options:\n\
00144 --delete FRAMES Delete FRAMES from input.\n\
00145 --insert-before FRAME GIFS Insert GIFS before FRAMES in input.\n\
00146 --append GIFS Append GIFS to input.\n\
00147 --replace FRAMES GIFS Replace FRAMES with GIFS in input.\n\
00148 --done Done with frame changes.\n\
00149 \n\
00150 Image options: Also --no-OPTION and --same-OPTION.\n\
00151 -B, --background COL Makes COL the background color.\n\
00152 --crop X,Y+WxH, --crop X,Y-X2,Y2\n\
00153 Crops the image.\n\
00154 --flip-horizontal, --flip-vertical\n\
00155 Flips the image.\n\
00156 -i, --interlace Turns on interlacing.\n\
00157 -S, --logical-screen WxH Sets logical screen to WxH.\n\
00158 -p, --position X,Y Sets frame position to (X,Y).\n\
00159 --rotate-90, --rotate-180, --rotate-270, --no-rotate\n\
00160 Rotates the image.\n\
00161 -t, --transparent COL Makes COL transparent.\n\
00162 \n");
00163 printf("\
00164 Extension options: Also --no-OPTION and --same-OPTION.\n\
00165 -x, --app-extension N D Adds an app extension named N with data D.\n\
00166 -c, --comment TEXT Adds a comment before the next frame.\n\
00167 --extension N D Adds an extension number N with data D.\n\
00168 -n, --name TEXT Sets next frame's name.\n\
00169 \n\
00170 Animation options: Also --no-OPTION and --same-OPTION.\n\
00171 -d, --delay TIME Sets frame delay to TIME (in 1/100sec).\n\
00172 -D, --disposal METHOD Sets frame disposal to METHOD.\n\
00173 -l, --loopcount[=N] Sets loop extension to N (default forever).\n\
00174 -O, --optimize[=LEV] Optimize output GIFs.\n\
00175 -U, --unoptimize Unoptimize input GIFs.\n\
00176 \n");
00177 printf("\
00178 Whole-GIF options: Also --no-OPTION.\n\
00179 --careful Write larger GIFs that avoid bugs in other\n\
00180 programs.\n\
00181 --change-color COL1 COL2 Changes COL1 to COL2 throughout.\n\
00182 -k, --colors N Reduces the number of colors to N.\n\
00183 --color-method METHOD Set method for choosing reduced colors.\n\
00184 -f, --dither Dither image after changing colormap.\n\
00185 --resize WxH Resizes the output GIF to WxH.\n\
00186 --resize-width W Resizes to width W and proportional height.\n\
00187 --resize-height H Resizes to height H and proportional width.\n\
00188 --scale XFACTOR[xYFACTOR] Scales the output GIF by XFACTORxYFACTOR.\n\
00189 --transform-colormap CMD Transform each output colormap by shell CMD.\n\
00190 --use-colormap CMAP Set output GIF's colormap to CMAP, which can\n\
00191 be `web', `gray', `bw', or a GIF file.\n\
00192 \n\
00193 Report bugs to <eddietwo@lcs.mit.edu>.\n\
00194 Too much information? Try `%s --help | more'.\n", program_name);
00195 #ifdef GIF_UNGIF
00196 printf("\
00197 This version of Gifsicle writes uncompressed GIFs, which can be far larger\n\
00198 than compressed GIFs. See http://www.lcdf.org/gifsicle for more information.\n");
00199 #endif
00200 }
00201
00202
00203 void
00204 verbose_open(char open, const char *name)
00205 {
00206 int l = strlen(name);
00207 if (verbose_pos && verbose_pos + 3 + l > 79) {
00208 fputc('\n', stderr);
00209 verbose_pos = 0;
00210 }
00211 if (verbose_pos) {
00212 fputc(' ', stderr);
00213 verbose_pos++;
00214 }
00215 fputc(open, stderr);
00216 fputs(name, stderr);
00217 verbose_pos += 1 + l;
00218 }
00219
00220
00221 void
00222 verbose_close(char close)
00223 {
00224 fputc(close, stderr);
00225 verbose_pos++;
00226 }
00227
00228
00229 void
00230 verbose_endline(void)
00231 {
00232 if (verbose_pos) {
00233 fputc('\n', stderr);
00234 fflush(stderr);
00235 verbose_pos = 0;
00236 }
00237 }
00238
00239
00240
00241
00242
00243
00244
00245 static void
00246 safe_puts(const char *s, u_int32_t len, FILE *f)
00247 {
00248 const char *last_safe = s;
00249 for (; len > 0; len--, s++)
00250 if (*s < ' ' || *s >= 0x7F || *s == '\\') {
00251 if (last_safe != s)
00252 fwrite(last_safe, 1, s - last_safe, f);
00253 last_safe = s + 1;
00254 switch (*s) {
00255 case '\a': fputs("\\a", f); break;
00256 case '\b': fputs("\\b", f); break;
00257 case '\f': fputs("\\f", f); break;
00258 case '\n': fputs("\\n", f); break;
00259 case '\r': fputs("\\r", f); break;
00260 case '\t': fputs("\\t", f); break;
00261 case '\v': fputs("\\v", f); break;
00262 case '\\': fputs("\\\\", f); break;
00263 case 0: if (len > 1) fputs("\\000", f); break;
00264 default: fprintf(f, "\\%03o", *s); break;
00265 }
00266 }
00267 if (last_safe != s)
00268 fwrite(last_safe, 1, s - last_safe, f);
00269 }
00270
00271
00272 static void
00273 comment_info(FILE *where, Gif_Comment *gfcom, char *prefix)
00274 {
00275 int i;
00276 for (i = 0; i < gfcom->count; i++) {
00277 fputs(prefix, where);
00278 safe_puts(gfcom->str[i], gfcom->len[i], where);
00279 fputc('\n', where);
00280 }
00281 }
00282
00283
00284 #define COLORMAP_COLS 4
00285
00286 static void
00287 colormap_info(FILE *where, Gif_Colormap *gfcm, char *prefix)
00288 {
00289 int i, j;
00290 int nrows = ((gfcm->ncol - 1) / COLORMAP_COLS) + 1;
00291
00292 for (j = 0; j < nrows; j++) {
00293 int which = j;
00294 fputs(prefix, where);
00295 for (i = 0; i < COLORMAP_COLS && which < gfcm->ncol; i++, which += nrows) {
00296 if (i) fputs(" ", where);
00297 fprintf(where, " %3d: #%02X%02X%02X", which, gfcm->col[which].red,
00298 gfcm->col[which].green, gfcm->col[which].blue);
00299 }
00300 fputc('\n', where);
00301 }
00302 }
00303
00304
00305 static void
00306 extension_info(FILE *where, Gif_Stream *gfs, Gif_Extension *gfex, int count)
00307 {
00308 byte *data = gfex->data;
00309 u_int32_t pos = 0;
00310 u_int32_t len = gfex->length;
00311
00312 fprintf(where, " extension %d: ", count);
00313 if (gfex->kind == 255) {
00314 fprintf(where, "app `");
00315 safe_puts(gfex->application, strlen(gfex->application), where);
00316 fprintf(where, "'");
00317 } else {
00318 if (gfex->kind >= 32 && gfex->kind < 127)
00319 fprintf(where, "`%c' (0x%02X)", gfex->kind, gfex->kind);
00320 else
00321 fprintf(where, "0x%02X", gfex->kind);
00322 }
00323 if (gfex->position >= gfs->nimages)
00324 fprintf(where, " at end\n");
00325 else
00326 fprintf(where, " before #%d\n", gfex->position);
00327
00328
00329 while (len > 0) {
00330 u_int32_t row = 16;
00331 u_int32_t i;
00332 if (row > len) row = len;
00333 fprintf(where, " %08x: ", pos);
00334
00335 for (i = 0; i < row; i += 2) {
00336 if (i + 1 >= row)
00337 fprintf(where, "%02x ", data[i]);
00338 else
00339 fprintf(where, "%02x%02x ", data[i], data[i+1]);
00340 }
00341 for (; i < 16; i += 2)
00342 fputs(" ", where);
00343
00344 putc(' ', where);
00345 for (i = 0; i < row; i++, data++)
00346 putc((*data >= ' ' && *data < 127 ? *data : '.'), where);
00347 putc('\n', where);
00348
00349 pos += row;
00350 len -= row;
00351 }
00352 }
00353
00354
00355 void
00356 stream_info(FILE *where, Gif_Stream *gfs, const char *filename,
00357 int colormaps, int extensions)
00358 {
00359 Gif_Extension *gfex;
00360 int n;
00361
00362 if (!gfs) return;
00363
00364 verbose_endline();
00365 fprintf(where, "* %s %d image%s\n", (filename ? filename : "<stdin>"),
00366 gfs->nimages, gfs->nimages == 1 ? "" : "s");
00367 fprintf(where, " logical screen %dx%d\n",
00368 gfs->screen_width, gfs->screen_height);
00369
00370 if (gfs->global) {
00371 fprintf(where, " global color table [%d]\n", gfs->global->ncol);
00372 if (colormaps) colormap_info(where, gfs->global, " |");
00373 fprintf(where, " background %d\n", gfs->background);
00374 }
00375
00376 if (gfs->comment)
00377 comment_info(where, gfs->comment, " end comment ");
00378
00379 if (gfs->loopcount == 0)
00380 fprintf(where, " loop forever\n");
00381 else if (gfs->loopcount > 0)
00382 fprintf(where, " loop count %u\n", (unsigned)gfs->loopcount);
00383
00384 for (n = 0, gfex = gfs->extensions; gfex; gfex = gfex->next, n++)
00385 if (extensions)
00386 extension_info(where, gfs, gfex, n);
00387 if (n && !extensions)
00388 fprintf(where, " extensions %d\n", n);
00389 }
00390
00391
00392 static char *disposal_names[] = {
00393 "none", "asis", "background", "previous", "4", "5", "6", "7"
00394 };
00395
00396 void
00397 image_info(FILE *where, Gif_Stream *gfs, Gif_Image *gfi, int colormaps)
00398 {
00399 int num;
00400 if (!gfs || !gfi) return;
00401 num = Gif_ImageNumber(gfs, gfi);
00402
00403 verbose_endline();
00404 fprintf(where, " + image #%d ", num);
00405 if (gfi->identifier)
00406 fprintf(where, "#%s ", gfi->identifier);
00407
00408 fprintf(where, "%dx%d", gfi->width, gfi->height);
00409 if (gfi->left || gfi->top)
00410 fprintf(where, " at %d,%d", gfi->left, gfi->top);
00411
00412 if (gfi->interlace)
00413 fprintf(where, " interlaced");
00414
00415 if (gfi->transparent >= 0)
00416 fprintf(where, " transparent %d", gfi->transparent);
00417
00418 #if defined(PRINT_SIZE)
00419 if (gfi->compressed)
00420 fprintf(where, " compressed size %u min_code_size %d", gfi->compressed_len, *gfi->compressed);
00421 #endif
00422
00423 fprintf(where, "\n");
00424
00425 if (gfi->comment)
00426 comment_info(where, gfi->comment, " comment ");
00427
00428 if (gfi->local) {
00429 fprintf(where, " local color table [%d]\n", gfi->local->ncol);
00430 if (colormaps) colormap_info(where, gfi->local, " |");
00431 }
00432
00433 if (gfi->disposal || gfi->delay) {
00434 fprintf(where, " ");
00435 if (gfi->disposal)
00436 fprintf(where, " disposal %s", disposal_names[gfi->disposal]);
00437 if (gfi->delay)
00438 fprintf(where, " delay %d.%02ds",
00439 gfi->delay / 100, gfi->delay % 100);
00440 fprintf(where, "\n");
00441 }
00442 }
00443
00444
00445 char *
00446 explode_filename(char *filename, int number, char *name, int max_nimages)
00447 {
00448 static char *s;
00449 int l = strlen(filename);
00450 l += name ? strlen(name) : 10;
00451
00452 Gif_Delete(s);
00453 s = Gif_NewArray(char, l + 3);
00454 if (name)
00455 sprintf(s, "%s.%s", filename, name);
00456 else if (max_nimages <= 1000)
00457 sprintf(s, "%s.%03d", filename, number);
00458 else {
00459 int digits;
00460 unsigned j;
00461 unsigned max = (max_nimages < 0 ? 0 : max_nimages);
00462 for (digits = 4, j = 10000; max > j; digits++)
00463 j *= 10;
00464 sprintf(s, "%s.%0*d", filename, digits, number);
00465 }
00466
00467 return s;
00468 }
00469
00470
00471
00472
00473
00474
00475 int frame_spec_1;
00476 int frame_spec_2;
00477 char *frame_spec_name;
00478 int dimensions_x;
00479 int dimensions_y;
00480 int position_x;
00481 int position_y;
00482 Gif_Color parsed_color;
00483 Gif_Color parsed_color2;
00484 double parsed_scale_factor_x;
00485 double parsed_scale_factor_y;
00486
00487 int
00488 parse_frame_spec(Clp_Parser *clp, const char *arg, int complain, void *thunk)
00489 {
00490 char *c;
00491
00492 frame_spec_1 = 0;
00493 frame_spec_2 = -1;
00494 frame_spec_name = 0;
00495
00496 if (!input && !input_name)
00497 input_stream(0);
00498 if (!input)
00499 return 0;
00500
00501 if (arg[0] != '#') {
00502 if (complain)
00503 return Clp_OptionError(clp, "frame specifications must start with #");
00504 else
00505 return 0;
00506 }
00507 arg++;
00508 c = (char *)arg;
00509
00510
00511 if (isdigit(c[0]))
00512 frame_spec_1 = frame_spec_2 = strtol(c, &c, 10);
00513 else if (c[0] == '-' && isdigit(c[1])) {
00514 frame_spec_1 = frame_spec_2 = Gif_ImageCount(input) + strtol(c, &c, 10);
00515 if (frame_spec_1 < 0)
00516 return complain ? Clp_OptionError(clp, "there are only %d frames",
00517 Gif_ImageCount(input)) : 0;
00518 }
00519
00520
00521
00522 if (c[0] == '-' && (frame_spec_2 > 0 || c[1] != 0)) {
00523 c++;
00524 if (isdigit(c[0]))
00525 frame_spec_2 = strtol(c, &c, 10);
00526 else if (c[0] == '-' && isdigit(c[1]))
00527 frame_spec_2 = Gif_ImageCount(input) + strtol(c, &c, 10);
00528 else
00529 frame_spec_2 = Gif_ImageCount(input) - 1;
00530 }
00531
00532
00533
00534 if (c[0] != 0) {
00535 Gif_Image *gfi = Gif_GetNamedImage(input, arg);
00536 if (gfi) {
00537 frame_spec_name = (char *)arg;
00538 frame_spec_1 = frame_spec_2 = Gif_ImageNumber(input, gfi);
00539 return 1;
00540 } else if (complain < 0)
00541
00542
00543 return -97;
00544 else if (complain)
00545 return Clp_OptionError(clp, "no frame named `#%s'", arg);
00546 else
00547 return 0;
00548
00549 } else {
00550 if (frame_spec_1 >= 0 && frame_spec_1 <= frame_spec_2
00551 && frame_spec_2 < Gif_ImageCount(input))
00552 return 1;
00553 else if (!complain)
00554 return 0;
00555
00556 if (frame_spec_1 == frame_spec_2)
00557 return Clp_OptionError(clp, "no frame number #%d", frame_spec_1);
00558 else if (frame_spec_1 < 0)
00559 return Clp_OptionError(clp, "frame numbers can't be negative");
00560 else if (frame_spec_1 > frame_spec_2)
00561 return Clp_OptionError(clp, "empty frame range");
00562 else
00563 return Clp_OptionError(clp, "there are only %d frames",
00564 Gif_ImageCount(input));
00565 }
00566 }
00567
00568 int
00569 parse_dimensions(Clp_Parser *clp, const char *arg, int complain, void *thunk)
00570 {
00571 char *val;
00572
00573 if (*arg == '_' && arg[1] == 'x') {
00574 dimensions_x = 0;
00575 val = (char *)(arg + 1);
00576 } else
00577 dimensions_x = strtol(arg, &val, 10);
00578 if (*val == 'x') {
00579 if (val[1] == '_' && val[2] == 0) {
00580 dimensions_y = 0;
00581 val = val + 2;
00582 } else
00583 dimensions_y = strtol(val + 1, &val, 10);
00584 if (*val == 0)
00585 return 1;
00586 }
00587
00588 if (complain)
00589 return Clp_OptionError(clp, "invalid dimensions `%s' (want WxH)", arg);
00590 else
00591 return 0;
00592 }
00593
00594 int
00595 parse_position(Clp_Parser *clp, const char *arg, int complain, void *thunk)
00596 {
00597 char *val;
00598
00599 position_x = strtol(arg, &val, 10);
00600 if (*val == ',') {
00601 position_y = strtol(val + 1, &val, 10);
00602 if (*val == 0)
00603 return 1;
00604 }
00605
00606 if (complain)
00607 return Clp_OptionError(clp, "invalid position `%s' (want `X,Y')", arg);
00608 else
00609 return 0;
00610 }
00611
00612 int
00613 parse_scale_factor(Clp_Parser *clp, const char *arg, int complain, void *thunk)
00614 {
00615 char *val;
00616
00617 parsed_scale_factor_x = strtod(arg, &val);
00618 if (*val == 'x') {
00619 parsed_scale_factor_y = strtod(val + 1, &val);
00620 if (*val == 0)
00621 return 1;
00622 } else if (*val == 0) {
00623 parsed_scale_factor_y = parsed_scale_factor_x;
00624 return 1;
00625 }
00626
00627 if (complain)
00628 return Clp_OptionError(clp, "invalid scale factor `%s' (want XxY)", arg);
00629 else
00630 return 0;
00631 }
00632
00633 int
00634 parse_rectangle(Clp_Parser *clp, const char *arg, int complain, void *thunk)
00635 {
00636 const char *input_arg = arg;
00637 char *val;
00638
00639 int x = position_x = strtol(arg, &val, 10);
00640 if (*val == ',') {
00641 int y = position_y = strtol(val + 1, &val, 10);
00642 if (*val == '-' && parse_position(clp, val + 1, 0, 0)) {
00643 if (x >= 0 && y >= 0 && x < position_x && y < position_y) {
00644 dimensions_x = position_x - x;
00645 dimensions_y = position_y - y;
00646 position_x = x;
00647 position_y = y;
00648 return 1;
00649 }
00650 } else if (*val == '+' && parse_dimensions(clp, val + 1, 0, 0))
00651 return 1;
00652 } else if (*val == 'x') {
00653 dimensions_x = position_x;
00654 dimensions_y = strtol(val + 1, &val, 10);
00655 if (*val == 0) {
00656 position_x = position_y = 0;
00657 return 1;
00658 }
00659 }
00660
00661 if (complain)
00662 return Clp_OptionError(clp, "invalid rectangle `%s' (want `X1,Y1-X2,Y2' or `X1,Y1+WxH'", input_arg);
00663 else
00664 return 0;
00665 }
00666
00667 static int
00668 xvalue(char c)
00669 {
00670 switch (c) {
00671 case '0': case '1': case '2': case '3': case '4':
00672 case '5': case '6': case '7': case '8': case '9':
00673 return c - '0';
00674 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
00675 return c - 'A' + 10;
00676 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
00677 return c - 'a' + 10;
00678 default:
00679 return -1;
00680 }
00681 }
00682
00683 static int
00684 parse_hex_color_channel(const char *s, int ndigits)
00685 {
00686 int val1 = xvalue(s[0]);
00687 if (val1 < 0) return -1;
00688 if (ndigits == 1)
00689 return val1 * 16 + val1;
00690 else {
00691 int val2 = xvalue(s[1]);
00692 if (val2 < 0) return -1;
00693 return val1 * 16 + val2;
00694 }
00695 }
00696
00697 int
00698 parse_color(Clp_Parser *clp, const char *arg, int complain, void *thunk)
00699 {
00700 const char *input_arg = arg;
00701 char *str;
00702 int red, green, blue;
00703
00704 if (*arg == '#') {
00705 int len = strlen(++arg);
00706 if (!len || len % 3 != 0 || strspn(arg, "0123456789ABCDEFabcdef") != len) {
00707 if (complain)
00708 Clp_OptionError(clp, "invalid color `%s' (want `#RGB' or `#RRGGBB')",
00709 input_arg);
00710 return 0;
00711 }
00712
00713 len /= 3;
00714 red = parse_hex_color_channel(&arg[ 0 * len ], len);
00715 green = parse_hex_color_channel(&arg[ 1 * len ], len);
00716 blue = parse_hex_color_channel(&arg[ 2 * len ], len);
00717 goto gotrgb;
00718
00719 } else if (!isdigit(*arg))
00720 goto error;
00721
00722 red = strtol(arg, &str, 10);
00723 if (*str == 0) {
00724 if (red < 0 || red > 255)
00725 goto error;
00726 parsed_color.haspixel = 1;
00727 parsed_color.pixel = red;
00728 return 1;
00729
00730 } else if (*str != ',' && *str != '/')
00731 goto error;
00732
00733 if (*++str == 0) goto error;
00734 green = strtol(str, &str, 10);
00735 if (*str != ',' && *str != '/') goto error;
00736
00737 if (*++str == 0) goto error;
00738 blue = strtol(str, &str, 10);
00739 if (*str != 0) goto error;
00740
00741 gotrgb:
00742 if (red < 0 || green < 0 || blue < 0
00743 || red > 255 || green > 255 || blue > 255)
00744 goto error;
00745 parsed_color.red = red;
00746 parsed_color.green = green;
00747 parsed_color.blue = blue;
00748 parsed_color.haspixel = 0;
00749 return 1;
00750
00751 error:
00752 if (complain)
00753 return Clp_OptionError(clp, "invalid color `%s'", input_arg);
00754 else
00755 return 0;
00756 }
00757
00758 int
00759 parse_two_colors(Clp_Parser *clp, const char *arg, int complain, void *thunk)
00760 {
00761 Gif_Color old_color;
00762 if (parse_color(clp, arg, complain, thunk) <= 0)
00763 return 0;
00764 old_color = parsed_color;
00765
00766 arg = Clp_Shift(clp, 0);
00767 if (!arg && complain)
00768 return Clp_OptionError(clp, "`%O' takes two color arguments");
00769 else if (!arg)
00770 return 0;
00771
00772 if (parse_color(clp, arg, complain, thunk) <= 0)
00773 return 0;
00774
00775 parsed_color2 = parsed_color;
00776 parsed_color = old_color;
00777 return 1;
00778 }
00779
00780
00781
00782
00783
00784
00785 static Gif_Colormap *
00786 read_text_colormap(FILE *f, char *name)
00787 {
00788 char buf[BUFSIZ];
00789 Gif_Colormap *cm = Gif_NewFullColormap(0, 256);
00790 Gif_Color *col = cm->col;
00791 int ncol = 0;
00792 unsigned red, green, blue;
00793 float fred, fgreen, fblue;
00794
00795 while (fgets(buf, BUFSIZ, f)) {
00796
00797 if (sscanf(buf, "%g %g %g", &fred, &fgreen, &fblue) == 3) {
00798 if (fred < 0) fred = 0;
00799 if (fgreen < 0) fgreen = 0;
00800 if (fblue < 0) fblue = 0;
00801 red = (unsigned)(fred + .5);
00802 green = (unsigned)(fgreen + .5);
00803 blue = (unsigned)(fblue + .5);
00804 goto found;
00805
00806 } else if (sscanf(buf, "#%2x%2x%2x", &red, &green, &blue) == 3) {
00807 found:
00808 if (red > 255) red = 255;
00809 if (green > 255) green = 255;
00810 if (blue > 255) blue = 255;
00811 if (ncol >= 256) {
00812 error("%s: maximum 256 colors allowed in colormap", name);
00813 break;
00814 } else {
00815 col[ncol].red = red;
00816 col[ncol].green = green;
00817 col[ncol].blue = blue;
00818 ncol++;
00819 }
00820 }
00821
00822
00823 if (strchr(buf, '\n') == 0) {
00824 int c;
00825 for (c = getc(f); c != '\n' && c != EOF; c = getc(f))
00826 ;
00827 }
00828 }
00829
00830 if (ncol == 0) {
00831 error("`%s' doesn't seem to contain a colormap", name);
00832 Gif_DeleteColormap(cm);
00833 return 0;
00834 } else {
00835 cm->ncol = ncol;
00836 return cm;
00837 }
00838 }
00839
00840 Gif_Colormap *
00841 read_colormap_file(char *name, FILE *f)
00842 {
00843 Gif_Colormap *cm = 0;
00844 int c;
00845 int my_file = 0;
00846
00847 if (name && strcmp(name, "-") == 0)
00848 name = 0;
00849 if (!f) {
00850 my_file = 1;
00851 if (!name)
00852 f = stdin;
00853 else
00854 f = fopen(name, "rb");
00855 if (!f) {
00856 error("%s: %s", name, strerror(errno));
00857 return 0;
00858 }
00859 }
00860
00861 name = name ? name : "<stdin>";
00862 if (verbosing) verbose_open('<', name);
00863
00864 c = getc(f);
00865 ungetc(c, f);
00866 if (c == 'G') {
00867 Gif_Stream *gfs = Gif_ReadFile(f);
00868 if (!gfs)
00869 error("`%s' doesn't seem to contain a GIF", name);
00870 else if (!gfs->global)
00871 error("can't use `%s' as a palette (no global color table)", name);
00872 else {
00873 if (gfs->errors)
00874 warning("there were errors reading `%s'", name);
00875 cm = Gif_CopyColormap(gfs->global);
00876 }
00877
00878 Gif_DeleteStream(gfs);
00879 } else
00880 cm = read_text_colormap(f, name);
00881
00882 if (my_file) fclose(f);
00883 if (verbosing) verbose_close('>');
00884 return cm;
00885 }
00886
00887
00888
00889
00890
00891
00892
00893 Gt_Frameset *
00894 new_frameset(int initial_cap)
00895 {
00896 Gt_Frameset *fs = Gif_New(Gt_Frameset);
00897 if (initial_cap < 0) initial_cap = 0;
00898 fs->cap = initial_cap;
00899 fs->count = 0;
00900 fs->f = Gif_NewArray(Gt_Frame, initial_cap);
00901 return fs;
00902 }
00903
00904
00905 void
00906 clear_def_frame_once_options(void)
00907 {
00908
00909
00910
00911
00912
00913
00914
00915
00916
00917
00918
00919
00920
00921
00922 def_frame.name = 0;
00923 def_frame.comment = 0;
00924 def_frame.extensions = 0;
00925 }
00926
00927
00928 Gt_Frame *
00929 add_frame(Gt_Frameset *fset, int number, Gif_Stream *gfs, Gif_Image *gfi)
00930 {
00931 if (number < 0) {
00932 while (fset->count >= fset->cap) {
00933 fset->cap *= 2;
00934 Gif_ReArray(fset->f, Gt_Frame, fset->cap);
00935 }
00936 number = fset->count++;
00937 } else {
00938 assert(number < fset->count);
00939 blank_frameset(fset, number, number, 0);
00940 }
00941
00942
00943 gfs->refcount++;
00944 gfi->refcount++;
00945 fset->f[number] = def_frame;
00946 fset->f[number].stream = gfs;
00947 fset->f[number].image = gfi;
00948
00949 clear_def_frame_once_options();
00950
00951 return &fset->f[number];
00952 }
00953
00954
00955 static Gif_Extension *
00956 copy_extension(Gif_Extension *src)
00957 {
00958 Gif_Extension *dest = Gif_NewExtension(src->kind, src->application);
00959 if (!dest) return 0;
00960 dest->data = Gif_NewArray(byte, src->length);
00961 dest->length = src->length;
00962 dest->free_data = Gif_DeleteArrayFunc;
00963 if (!dest->data) {
00964 Gif_DeleteExtension(dest);
00965 return 0;
00966 }
00967 memcpy(dest->data, src->data, src->length);
00968 return dest;
00969 }
00970
00971
00972 static Gt_Frame **merger = 0;
00973 static int nmerger = 0;
00974 static int mergercap = 0;
00975
00976 static void
00977 merger_add(Gt_Frame *fp)
00978 {
00979 while (nmerger >= mergercap)
00980 if (mergercap) {
00981 mergercap *= 2;
00982 Gif_ReArray(merger, Gt_Frame *, mergercap);
00983 } else {
00984 mergercap = 16;
00985 merger = Gif_NewArray(Gt_Frame *, mergercap);
00986 }
00987 merger[ nmerger++ ] = fp;
00988 }
00989
00990
00991 static void
00992 merger_flatten(Gt_Frameset *fset, int f1, int f2)
00993 {
00994 int i;
00995 assert(f1 >= 0 && f2 < fset->count);
00996 for (i = f1; i <= f2; i++) {
00997 Gt_Frameset *nest = FRAME(fset, i).nest;
00998
00999 if (nest && nest->count > 0) {
01000 if (FRAME(fset, i).use < 0 && nest->count == 1) {
01001
01002
01003 if (FRAME(nest, 0).delay < 0)
01004 FRAME(nest, 0).delay = FRAME(fset, i).image->delay;
01005 if (FRAME(nest, 0).disposal < 0)
01006 FRAME(nest, 0).disposal = FRAME(fset, i).image->disposal;
01007 if (FRAME(nest, 0).name == 0 && FRAME(nest, 0).no_name == 0)
01008 FRAME(nest, 0).name =
01009 Gif_CopyString(FRAME(fset, i).image->identifier);
01010 }
01011 merger_flatten(nest, 0, nest->count - 1);
01012 }
01013
01014 if (FRAME(fset, i).use > 0)
01015 merger_add(&FRAME(fset, i));
01016 }
01017 }
01018
01019
01020 static void
01021 find_background(Gif_Colormap *dest_global, Gif_Color *background)
01022 {
01023 int i;
01024
01025
01026
01027 if (background->haspixel) {
01028 int first_img_transp = 0;
01029 if (merger[0]->transparent.haspixel < 255)
01030 first_img_transp = (merger[0]->image->transparent >= 0
01031 || merger[0]->transparent.haspixel);
01032 if (first_img_transp) {
01033 static int context = 0;
01034 warning("irrelevant background color");
01035 if (!context) {
01036 warncontext("(The background will appear transparent because");
01037 warncontext("the first image contains transparency.)");
01038 context = 1;
01039 }
01040 }
01041 }
01042
01043
01044
01045 if (background->haspixel == 2) {
01046 Gif_Stream *gfs = merger[0]->stream;
01047 if (gfs->global && background->pixel < gfs->global->ncol) {
01048 *background = gfs->global->col[ background->pixel ];
01049 background->haspixel = 1;
01050 } else {
01051 error("background color index `%d' out of range", background->pixel);
01052 background->haspixel = 0;
01053 }
01054 }
01055
01056
01057
01058
01059 if (background->haspixel == 0) {
01060 int relevance = 0;
01061 int saved_bg_transparent = 0;
01062 for (i = 0; i < nmerger; i++) {
01063 Gif_Stream *gfs = merger[i]->stream;
01064 int bg_disposal = merger[i]->image->disposal == GIF_DISPOSAL_BACKGROUND;
01065 int bg_transparent = gfs->images[0]->transparent >= 0;
01066 int bg_exists = gfs->global && gfs->background < gfs->global->ncol;
01067
01068 if ((!bg_exists && !bg_transparent) || (bg_disposal + 1 < relevance))
01069 continue;
01070 else if (bg_disposal + 1 > relevance) {
01071 if (bg_exists)
01072 *background = gfs->global->col[gfs->background];
01073 else
01074 background->red = background->green = background->blue = 0;
01075 saved_bg_transparent = bg_transparent;
01076 relevance = bg_disposal + 1;
01077 continue;
01078 }
01079
01080
01081 if (bg_transparent != saved_bg_transparent
01082 || (!saved_bg_transparent &&
01083 !GIF_COLOREQ(background, &gfs->global->col[gfs->background]))) {
01084 static int context = 0;
01085 warning("input images have conflicting background colors");
01086 if (!context) {
01087 warncontext("(This means some animation frames may appear incorrect.)");
01088 context = 1;
01089 }
01090 break;
01091 }
01092 }
01093 background->haspixel = relevance != 0;
01094 }
01095
01096
01097
01098
01099 if (background->haspixel) {
01100 dest_global->userflags |= COLORMAP_ENSURE_SLOT_255;
01101 dest_global->col[255] = *background;
01102 }
01103 }
01104
01105
01106 static int
01107 find_color_or_error(Gif_Color *color, Gif_Stream *gfs, Gif_Image *gfi,
01108 char *color_context)
01109 {
01110 Gif_Colormap *gfcm = gfs->global;
01111 int index;
01112 if (gfi && gfi->local) gfcm = gfi->local;
01113
01114 if (color->haspixel == 2) {
01115 if (color->pixel < gfcm->ncol)
01116 return color->pixel;
01117 else {
01118 if (color_context) error("%s color out of range", color_context);
01119 return -1;
01120 }
01121 }
01122
01123 index = Gif_FindColor(gfcm, color);
01124 if (index < 0 && color_context)
01125 error("%s color not in colormap", color_context);
01126 return index;
01127 }
01128
01129
01130 static void
01131 fix_total_crop(Gif_Stream *dest, Gif_Image *srci, int merger_index)
01132 {
01133
01134
01135 Gt_Frame *fr = merger[merger_index];
01136 Gt_Frame *next_fr = 0;
01137 Gif_Image *prev_image = 0;
01138 if (dest->nimages > 0) prev_image = dest->images[dest->nimages - 1];
01139 if (merger_index < nmerger - 1) next_fr = merger[merger_index + 1];
01140
01141
01142
01143 if (!fr->no_comments && srci->comment && next_fr) {
01144 if (!next_fr->comment) next_fr->comment = Gif_NewComment();
01145 merge_comments(next_fr->comment, srci->comment);
01146 }
01147 if (fr->comment && next_fr) {
01148 if (!next_fr->comment) next_fr->comment = Gif_NewComment();
01149 merge_comments(next_fr->comment, fr->comment);
01150 Gif_DeleteComment(fr->comment);
01151 fr->comment = 0;
01152 }
01153
01154
01155 if (fr->delay < 0)
01156 fr->delay = srci->delay;
01157 prev_image->delay += fr->delay;
01158 }
01159
01160
01161 static void
01162 handle_screen(Gif_Stream *dest, u_int16_t width, u_int16_t height)
01163 {
01164
01165
01166 if (dest->screen_width < width)
01167 dest->screen_width = width;
01168 if (dest->screen_height < height)
01169 dest->screen_height = height;
01170 }
01171
01172 static void
01173 handle_flip_and_screen(Gif_Stream *dest, Gif_Image *desti,
01174 Gt_Frame *fr, int first_image)
01175 {
01176 Gif_Stream *gfs = fr->stream;
01177
01178 u_int16_t screen_width = gfs->screen_width;
01179 u_int16_t screen_height = gfs->screen_height;
01180
01181 if (fr->flip_horizontal)
01182 flip_image(desti, screen_width, screen_height, 0);
01183 if (fr->flip_vertical)
01184 flip_image(desti, screen_width, screen_height, 1);
01185
01186 if (fr->rotation == 1)
01187 rotate_image(desti, screen_width, screen_height, 1);
01188 else if (fr->rotation == 2) {
01189 flip_image(desti, screen_width, screen_height, 0);
01190 flip_image(desti, screen_width, screen_height, 1);
01191 } else if (fr->rotation == 3)
01192 rotate_image(desti, screen_width, screen_height, 3);
01193
01194
01195 if (fr->rotation == 1 || fr->rotation == 3)
01196 handle_screen(dest, screen_height, screen_width);
01197 else
01198 handle_screen(dest, screen_width, screen_height);
01199 }
01200
01201
01202 Gif_Stream *
01203 merge_frame_interval(Gt_Frameset *fset, int f1, int f2,
01204 Gt_OutputData *output_data, int compress_immediately)
01205 {
01206 Gif_Stream *dest = Gif_NewStream();
01207 Gif_Colormap *global = Gif_NewFullColormap(256, 256);
01208 Gif_Color dest_background;
01209 int i;
01210
01211 global->ncol = 0;
01212 dest->global = global;
01213
01214
01215
01216 if (f2 < 0) f2 = fset->count - 1;
01217 nmerger = 0;
01218 merger_flatten(fset, f1, f2);
01219 if (nmerger == 0) {
01220 error("empty output GIF not written");
01221 return 0;
01222 }
01223
01224
01225 for (i = 0; i < nmerger; i++)
01226 merger[i]->stream->userflags = 1;
01227 for (i = 0; i < nmerger; i++) {
01228 if (merger[i]->stream->userflags) {
01229 Gif_Stream *src = merger[i]->stream;
01230 Gif_CalculateScreenSize(src, 0);
01231
01232 merge_stream(dest, src, merger[i]->no_comments);
01233 src->userflags = 0;
01234 }
01235 if (merger[i]->image->local)
01236 unmark_colors_2(merger[i]->image->local);
01237 }
01238
01239
01240 dest_background = output_data->background;
01241 find_background(global, &dest_background);
01242
01243
01244 for (i = 0; i < nmerger; i++)
01245 if (merger[i]->crop)
01246 merger[i]->crop->whole_stream = 0;
01247 if (merger[0]->crop) {
01248 merger[0]->crop->whole_stream = 1;
01249 for (i = 1; i < nmerger; i++)
01250 if (merger[i]->crop != merger[0]->crop)
01251 merger[0]->crop->whole_stream = 0;
01252 }
01253
01254
01255 if (output_data->loopcount > -2)
01256 dest->loopcount = output_data->loopcount;
01257 dest->screen_width = dest->screen_height = 0;
01258
01259
01260 for (i = 0; i < nmerger; i++) {
01261 Gt_Frame *fr = merger[i];
01262 Gif_Image *srci;
01263 Gif_Image *desti;
01264 int old_transparent;
01265
01266
01267 {
01268 int j;
01269 Gif_Extension *gfex = fr->stream->extensions;
01270 for (j = 0; fr->stream->images[j] != fr->image; j++) ;
01271 while (gfex && gfex->position < j)
01272 gfex = gfex->next;
01273 while (!fr->no_extensions && gfex && gfex->position == j) {
01274 Gif_AddExtension(dest, copy_extension(gfex), i);
01275 gfex = gfex->next;
01276 }
01277 gfex = fr->extensions;
01278 while (gfex) {
01279 Gif_Extension *next = gfex->next;
01280 Gif_AddExtension(dest, gfex, i);
01281 gfex = next;
01282 }
01283 }
01284
01285
01286 if (fr->crop) {
01287 srci = Gif_CopyImage(fr->image);
01288 Gif_UncompressImage(srci);
01289 if (!crop_image(srci, fr->crop)) {
01290
01291
01292 fix_total_crop(dest, srci, i);
01293 goto merge_frame_done;
01294 }
01295 } else {
01296 srci = fr->image;
01297 Gif_UncompressImage(srci);
01298 }
01299
01300
01301
01302 old_transparent = srci->transparent;
01303 if (fr->transparent.haspixel == 255)
01304 srci->transparent = -1;
01305 else if (fr->transparent.haspixel)
01306 srci->transparent =
01307 find_color_or_error(&fr->transparent, fr->stream, srci, "transparent");
01308
01309 desti = merge_image(dest, fr->stream, srci);
01310
01311 srci->transparent = old_transparent;
01312
01313
01314 if (fr->flip_horizontal || fr->flip_vertical || fr->rotation)
01315 handle_flip_and_screen(dest, desti, fr, i == 0);
01316 else
01317 handle_screen(dest, fr->stream->screen_width, fr->stream->screen_height);
01318
01319
01320 if (fr->name || fr->no_name) {
01321 Gif_DeleteArray(desti->identifier);
01322 desti->identifier = Gif_CopyString(fr->name);
01323 }
01324 if (fr->no_comments && desti->comment) {
01325 Gif_DeleteComment(desti->comment);
01326 desti->comment = 0;
01327 }
01328 if (fr->comment) {
01329 if (!desti->comment) desti->comment = Gif_NewComment();
01330 merge_comments(desti->comment, fr->comment);
01331
01332
01333 Gif_DeleteComment(fr->comment);
01334 fr->comment = 0;
01335 }
01336
01337 if (fr->interlacing >= 0)
01338 desti->interlace = fr->interlacing;
01339 if (fr->left >= 0)
01340 desti->left = fr->left + (fr->position_is_offset ? desti->left : 0);
01341 if (fr->top >= 0)
01342 desti->top = fr->top + (fr->position_is_offset ? desti->top : 0);
01343
01344 if (fr->delay >= 0)
01345 desti->delay = fr->delay;
01346 if (fr->disposal >= 0)
01347 desti->disposal = fr->disposal;
01348
01349
01350 if (compress_immediately) {
01351 Gif_FullCompressImage(dest, desti, gif_write_flags);
01352 Gif_ReleaseUncompressedImage(desti);
01353 }
01354
01355 merge_frame_done:
01356
01357 if (fr->crop)
01358 Gif_DeleteImage(srci);
01359
01360
01361 srci = fr->image;
01362 assert(srci->refcount > 1);
01363 if (--srci->refcount == 1) {
01364
01365 Gif_ReleaseUncompressedImage(srci);
01366 Gif_ReleaseCompressedImage(srci);
01367 fr->image = 0;
01368 }
01369
01370
01371
01372
01373 Gif_DeleteStream(fr->stream);
01374 fr->stream = 0;
01375 }
01376
01377
01378
01379 if (merger[0]->crop && merger[0]->crop == merger[nmerger-1]->crop)
01380 dest->screen_width = dest->screen_height = 0;
01381
01382 if (output_data->screen_width >= 0)
01383 dest->screen_width = output_data->screen_width;
01384 if (output_data->screen_height >= 0)
01385 dest->screen_height = output_data->screen_height;
01386
01387
01388 {
01389 int bg = find_color_or_error(&dest_background, dest, 0, 0);
01390 if (bg < 0 && dest->images[0]->transparent >= 0)
01391 dest->background = dest->images[0]->transparent;
01392 else if (bg < 0 && global->ncol < 256) {
01393 dest->background = global->ncol;
01394 global->col[ global->ncol ] = dest_background;
01395 global->ncol++;
01396 } else
01397 dest->background = bg;
01398 }
01399
01400 return dest;
01401 }
01402
01403
01404 void
01405 blank_frameset(Gt_Frameset *fset, int f1, int f2, int delete_object)
01406 {
01407 int i;
01408 if (delete_object) f1 = 0, f2 = -1;
01409 if (f2 < 0) f2 = fset->count - 1;
01410 for (i = f1; i <= f2; i++) {
01411
01412
01413 if (FRAME(fset, i).image && FRAME(fset, i).image->refcount > 1)
01414 FRAME(fset, i).image->refcount--;
01415 Gif_DeleteStream(FRAME(fset, i).stream);
01416 Gif_DeleteComment(FRAME(fset, i).comment);
01417 if (FRAME(fset, i).nest)
01418 blank_frameset(FRAME(fset, i).nest, 0, 0, 1);
01419 }
01420 if (delete_object) {
01421 Gif_DeleteArray(fset->f);
01422 Gif_Delete(fset);
01423 }
01424 }
01425
01426
01427 void
01428 clear_frameset(Gt_Frameset *fset, int f1)
01429 {
01430 blank_frameset(fset, f1, -1, 0);
01431 fset->count = f1;
01432 }