/* gifsicle.c - gifsicle's main loop. Copyright (C) 1997-2001 Eddie Kohler, eddietwo@lcs.mit.edu This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2 or later; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include "config.h" #include "gifsicle.h" #include #include #include #include #include #include /* Need _setmode under MS-DOS, to set stdin/stdout to binary mode */ /* Need _fsetmode under OS/2 for the same reason */ #if defined(_MSDOS) || defined(_WIN32) || defined(__EMX__) # include # include #endif Gt_Frame def_frame; Gt_Frameset *frames = 0; int first_input_frame = 0; Gt_Frameset *nested_frames = 0; Gif_Stream *input = 0; char *input_name = 0; static int unoptimizing = 0; int gif_read_flags = 0; int gif_write_flags = 0; static int frames_done = 0; static int files_given = 0; int warn_local_colormaps = 1; static Gt_ColorTransform *input_transforms; static Gt_ColorTransform *output_transforms; #define BLANK_MODE 0 #define MERGING 1 #define BATCHING 2 #define EXPLODING 3 #define DELETING 4 #define INSERTING 5 static int mode = BLANK_MODE; static int nested_mode = 0; static int infoing = 0; int verbosing = 0; #define CHANGED(next, flag) (((next) & 1<<(flag)) != 0) #define UNCHECKED_MARK_CH(where, what) \ next_##where |= 1<explode_by_name = 1; break; case BATCHING: add_frame(frames, first_input_frame + imagenumber, input, gfi); break; case DELETING: frame = &FRAME(frames, first_input_frame + imagenumber); frame->use = 0; break; } next_frame = 0; frames_done = 1; } /***** * input a stream **/ static int gifread_error_count; static void gifread_error(const char *message, int which_image, void *thunk) { static int last_which_image = 0; static char last_message[256]; static int different_error_count = 0; static int same_error_count = 0; const char *filename = (const char *)thunk; if (gifread_error_count == 0) { last_which_image = -1; last_message[0] = 0; different_error_count = 0; } gifread_error_count++; if (last_message[0] && different_error_count <= 10 && (last_which_image != which_image || message == 0 || strcmp(message, last_message) != 0)) { if (same_error_count == 1) error(" %s", last_message); else if (same_error_count > 0) error(" %s (%d times)", last_message, same_error_count); same_error_count = 0; last_message[0] = 0; } if (last_message[0] == 0) different_error_count++; same_error_count++; if (message) strcpy(last_message, message); else last_message[0] = 0; if (last_which_image != which_image && different_error_count <= 10 && message) { error("Error while reading `%s' frame #%d:", filename, which_image); last_which_image = which_image; } if (different_error_count == 11 && message) { error("(more errors while reading `%s')", filename); different_error_count++; } } void input_stream(char *name) { FILE *f; Gif_Stream *gfs; int i; int saved_next_frame = next_frame; Gt_Frame old_def_frame; input = 0; input_name = name; frames_done = 0; next_frame = 0; next_input = 0; if (next_output) combine_output_options(); files_given++; if (name == 0 || strcmp(name, "-") == 0) { #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdin), _O_BINARY); #elif defined(__EMX__) _fsetmode(stdin, "b"); #endif f = stdin; name = ""; } else f = fopen(name, "rb"); if (!f) { error("%s: %s", name, strerror(errno)); return; } /* special error message for empty files */ i = getc(f); if (i == EOF) { error("%s: empty file", name); return; } ungetc(i, f); if (verbosing) verbose_open('<', name); gifread_error_count = 0; gfs = Gif_FullReadFile(f, gif_read_flags | GIF_READ_COMPRESSED, gifread_error, (void *)name); fclose(f); gifread_error(0, -1, (void *)name); /* print out last error message */ if (!gfs || (Gif_ImageCount(gfs) == 0 && gfs->errors > 0)) { error("%s: not a GIF", name); Gif_DeleteStream(gfs); if (verbosing) verbose_close('>'); return; } input = gfs; /* Processing when we've got a new input frame */ if (mode == BLANK_MODE) set_mode(MERGING); if (active_output_data.output_name == 0) { /* Don't override explicit output names. This code works 'cause output_name is reset to 0 after each output. */ if (mode == BATCHING) active_output_data.output_name = input_name; else if (mode == EXPLODING) { /* Explode into current directory. */ char *explode_name = (input_name ? input_name : "#stdin#"); char *slash = strrchr(explode_name, PATHNAME_SEPARATOR); if (slash) active_output_data.output_name = slash + 1; else active_output_data.output_name = explode_name; } } /* This code rather sucks. Here's the problem: Since we consider options strictly sequentially, one at a time, we can't tell the difference between these: --name=X g.gif h.gif // name on g.gif #0 --name=X g.gif #2 h.gif // name on g.gif #2 g.gif --name=X #2 h.gif // name on g.gif #2 g.gif --name=X h.gif // name on h.gif #0 !!! Here's the solution. Mark when we CHANGE an option. After processing an input GIF, mark all the options as `unchanged' -- but leave the VALUES as is. Then when we read the next frame, CLEAR the unchanged options. So it's like so: (* means changed, . means not.) [-.] --name=X [X*] g.gif [X.] #2 [-.] h.gif == name on g.gif #2 [-.] g.gif [-.] --name=X [X*] #2 [-.] h.gif == name on g.gif #2 [-.] --name=X [X*] g.gif [X.|-.] h.gif == name on g.gif #0 [-.] g.gif [-.] --name=X [X*] h.gif == name on h.gif #0 */ /* Clear old options from the last input stream */ if (!CHANGED(saved_next_frame, CH_NAME)) def_frame.name = 0; if (!CHANGED(saved_next_frame, CH_COMMENT)) def_frame.comment = 0; if (!CHANGED(saved_next_frame, CH_EXTENSION)) def_frame.extensions = 0; def_frame.input_filename = input_name; old_def_frame = def_frame; first_input_frame = frames->count; if (gfs->nimages > 1) def_frame.position_is_offset = 1; for (i = 0; i < gfs->nimages; i++) add_frame(frames, -1, gfs, gfs->images[i]); def_frame = old_def_frame; if (unoptimizing) if (!Gif_Unoptimize(gfs)) { static int context = 0; warning("`%s' is too complex to unoptimize", name); if (!context) { warncontext("(The reason was local color tables or complex transparency."); warncontext("Try running the GIF through `gifsicle --colors=255' first.)"); } context = 1; } apply_color_transforms(input_transforms, gfs); gfs->refcount++; } void input_done(void) { if (!input) return; if (verbosing) verbose_close('>'); /*if (infoing) { int i; if (input->userflags == 97) stream_info(infoing, input, input_name, colormap_infoing, extension_infoing); for (i = first_input_frame; i < frames->count; i++) if (FRAME(frames, i).stream == input && FRAME(frames, i).use) image_info(infoing, input, FRAME(frames, i).image, colormap_infoing); }*/ Gif_DeleteStream(input); input = 0; if (mode == DELETING) frame_change_done(); if (mode == BATCHING || mode == EXPLODING) output_frames(); } /***** * colormap stuff **/ static void set_new_fixed_colormap(char *name) { int i; if (name && strcmp(name, "web") == 0) { Gif_Colormap *cm = Gif_NewFullColormap(216, 256); Gif_Color *col = cm->col; for (i = 0; i < 216; i++) { col[i].red = (i / 36) * 0x33; col[i].green = ((i / 6) % 6) * 0x33; col[i].blue = (i % 6) * 0x33; } def_output_data.colormap_fixed = cm; } else if (name && (strcmp(name, "gray") == 0 || strcmp(name, "grey") == 0)) { Gif_Colormap *cm = Gif_NewFullColormap(256, 256); Gif_Color *col = cm->col; for (i = 0; i < 256; i++) col[i].red = col[i].green = col[i].blue = i; def_output_data.colormap_fixed = cm; } else if (name && strcmp(name, "bw") == 0) { Gif_Colormap *cm = Gif_NewFullColormap(2, 256); cm->col[0].red = cm->col[0].green = cm->col[0].blue = 0; cm->col[1].red = cm->col[1].green = cm->col[1].blue = 255; def_output_data.colormap_fixed = cm; } else def_output_data.colormap_fixed = read_colormap_file(name, 0); } static void do_set_colormap(Gif_Stream *gfs, Gif_Colormap *gfcm) { colormap_image_func image_func; if (active_output_data.colormap_dither) image_func = colormap_image_floyd_steinberg; else image_func = colormap_image_posterize; colormap_stream(gfs, gfcm, image_func); } static void do_colormap_change(Gif_Stream *gfs) { if (active_output_data.colormap_fixed) do_set_colormap(gfs, active_output_data.colormap_fixed); if (active_output_data.colormap_size > 0) { int nhist; Gif_Color *hist; Gif_Colormap *(*adapt_func)(Gif_Color *, int, int); Gif_Colormap *new_cm; /* set up the histogram */ { int i, any_locals = 0; for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->local) any_locals = 1; hist = histogram(gfs, &nhist); if (nhist <= active_output_data.colormap_size && !any_locals) { warncontext("trivial adaptive palette (only %d colors in source)", nhist); return; } } switch (active_output_data.colormap_algorithm) { case COLORMAP_DIVERSITY: adapt_func = &colormap_flat_diversity; break; case COLORMAP_BLEND_DIVERSITY: adapt_func = &colormap_blend_diversity; break; case COLORMAP_MEDIAN_CUT: adapt_func = &colormap_median_cut; break; default: fatal_error("can't happen"); } new_cm = (*adapt_func)(hist, nhist, active_output_data.colormap_size); do_set_colormap(gfs, new_cm); Gif_DeleteArray(hist); Gif_DeleteColormap(new_cm); } } /***** * output GIF images **/ static void write_stream(char *output_name, Gif_Stream *gfs) { FILE *f; if (output_name) f = fopen(output_name, "wb"); else { #ifndef OUTPUT_GIF_TO_TERMINAL extern int isatty(int); if (isatty(fileno(stdout))) { error("not writing to : it's a terminal"); return; } #endif #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdout), _O_BINARY); #elif defined(__EMX__) _fsetmode(stdout, "b"); #endif f = stdout; output_name = ""; } if (f) { Gif_FullWriteFile(gfs, gif_write_flags, f); fclose(f); any_output_successful = 1; } else error("%s: %s", output_name, strerror(errno)); } static void merge_and_write_frames(char *outfile, int f1, int f2) { Gif_Stream *out; int compress_immediately; int colormap_change; assert(!nested_mode); if (verbosing) verbose_open('[', outfile ? outfile : "#stdout#"); colormap_change = active_output_data.colormap_size > 0 || active_output_data.colormap_fixed; compress_immediately = !colormap_change && active_output_data.scaling == 0 && active_output_data.optimizing <= 0; warn_local_colormaps = !colormap_change; out = merge_frame_interval(frames, f1, f2, &active_output_data, compress_immediately); if (out) { if (active_output_data.scaling == 1) resize_stream(out, active_output_data.resize_width, active_output_data.resize_height); else if (active_output_data.scaling == 2) resize_stream(out, active_output_data.scale_x * out->screen_width, active_output_data.scale_y * out->screen_height); if (colormap_change) do_colormap_change(out); if (output_transforms) apply_color_transforms(output_transforms, out); if (active_output_data.optimizing > 0) optimize_fragments(out, active_output_data.optimizing); write_stream(outfile, out); Gif_DeleteStream(out); } if (verbosing) verbose_close(']'); } static void output_information(const char *outfile) { FILE *f; int i, j; Gt_Frame *fr; Gif_Stream *gfs; if (infoing == 2) f = stderr; else if (outfile == 0) f = stdout; else { f = fopen(outfile, "w"); if (!f) { error("%s: %s", outfile, strerror(errno)); return; } } for (i = 0; i < frames->count; i++) FRAME(frames, i).stream->userflags = 97; for (i = 0; i < frames->count; i++) if (FRAME(frames, i).stream->userflags == 97) { fr = &FRAME(frames, i); gfs = fr->stream; gfs->userflags = 0; stream_info(f, gfs, fr->input_filename, fr->colormap_info, fr->extensions_info); for (j = i; j < frames->count; j++) if (FRAME(frames, j).stream == gfs) { fr = &FRAME(frames, j); image_info(f, gfs, fr->image, fr->colormap_info); } } if (f != stderr && f != stdout) fclose(f); } void output_frames(void) { /* Use the current output name, not the stored output name. This supports `gifsicle a.gif -o xxx'. It's not like any other option, but seems right: it fits the natural order -- input, then output. */ int i; char *outfile = active_output_data.output_name; active_output_data.output_name = 0; /* Output information only now. */ if (infoing) output_information(outfile); if (infoing != 1 && frames->count > 0) switch (mode) { case MERGING: case BATCHING: merge_and_write_frames(outfile, 0, -1); break; case EXPLODING: { /* Use the current output name for consistency, even though that means we can't explode different frames to different names. Not a big deal anyway; they can always repeat the gif on the cmd line. */ int max_nimages = 0; for (i = 0; i < frames->count; i++) { Gt_Frame *fr = &FRAME(frames, i); if (fr->stream->nimages > max_nimages) max_nimages = fr->stream->nimages; } if (!outfile) /* Watch out! */ outfile = "-"; for (i = 0; i < frames->count; i++) { Gt_Frame *fr = &FRAME(frames, i); int imagenumber = Gif_ImageNumber(fr->stream, fr->image); char *explodename; char *imagename = 0; if (fr->explode_by_name) imagename = fr->name ? fr->name : fr->image->identifier; explodename = explode_filename(outfile, imagenumber, imagename, max_nimages); merge_and_write_frames(explodename, i, i); } break; } case INSERTING: /* do nothing */ break; } active_next_output = 0; clear_frameset(frames, 0); /* cropping: clear the `crop->ready' information, which depended on the last input image. */ if (def_frame.crop) def_frame.crop->ready = 0; } /***** * parsing arguments **/ int frame_argument(Clp_Parser *clp, char *arg) { /* Returns 0 iff you should try a file named `arg'. */ int val = parse_frame_spec(clp, arg, -1, 0); if (val == -97) return 0; else if (val > 0) { int i; for (i = frame_spec_1; i <= frame_spec_2; i++) show_frame(i, frame_spec_name != 0); if (next_output) combine_output_options(); return 1; } else return 1; } static int handle_extension(Clp_Parser *clp, int is_app) { Gif_Extension *gfex; char *extension_type = clp->arg; char *extension_body = Clp_Shift(clp, 1); if (!extension_body) { Clp_OptionError(clp, "%O requires two arguments"); return 0; } UNCHECKED_MARK_CH(frame, CH_EXTENSION); if (is_app) gfex = Gif_NewExtension(255, extension_type); else if (!isdigit(extension_type[0]) && extension_type[1] == 0) gfex = Gif_NewExtension(extension_type[0], 0); else { long l = strtol(extension_type, &extension_type, 0); if (*extension_type != 0 || l < 0 || l >= 256) fatal_error("bad extension type: must be a number between 0 and 255"); gfex = Gif_NewExtension(l, 0); } gfex->data = (byte *)extension_body; gfex->length = strlen(extension_body); gfex->next = def_frame.extensions; def_frame.extensions = gfex; return 1; } /***** * option processing **/ static void initialize_def_frame(void) { /* frame defaults */ def_frame.stream = 0; def_frame.image = 0; def_frame.use = 1; def_frame.name = 0; def_frame.no_name = 0; def_frame.comment = 0; def_frame.no_comments = 0; def_frame.interlacing = -1; def_frame.transparent.haspixel = 0; def_frame.left = -1; def_frame.top = -1; def_frame.position_is_offset = 0; def_frame.crop = 0; def_frame.delay = -1; def_frame.disposal = -1; def_frame.nest = 0; def_frame.explode_by_name = 0; def_frame.no_extensions = 0; def_frame.extensions = 0; def_frame.flip_horizontal = 0; def_frame.flip_vertical = 0; /* output defaults */ def_output_data.output_name = 0; def_output_data.screen_width = -1; def_output_data.screen_height = -1; def_output_data.background.haspixel = 0; def_output_data.loopcount = -2; def_output_data.colormap_size = 0; def_output_data.colormap_fixed = 0; def_output_data.colormap_algorithm = COLORMAP_DIVERSITY; def_output_data.colormap_dither = 0; def_output_data.optimizing = 0; def_output_data.scaling = 0; active_output_data = def_output_data; } static void combine_output_options(void) { int recent = next_output; next_output = active_next_output; #define COMBINE_ONE_OUTPUT_OPTION(value, field) \ if (CHANGED(recent, value)) { \ MARK_CH(output, value); \ active_output_data.field = def_output_data.field; \ } COMBINE_ONE_OUTPUT_OPTION(CH_OUTPUT, output_name); if (CHANGED(recent, CH_LOGICAL_SCREEN)) { MARK_CH(output, CH_LOGICAL_SCREEN); active_output_data.screen_width = def_output_data.screen_width; active_output_data.screen_height = def_output_data.screen_height; } COMBINE_ONE_OUTPUT_OPTION(CH_BACKGROUND, background); COMBINE_ONE_OUTPUT_OPTION(CH_LOOPCOUNT, loopcount); COMBINE_ONE_OUTPUT_OPTION(CH_OPTIMIZE, optimizing); COMBINE_ONE_OUTPUT_OPTION(CH_COLORMAP, colormap_size); COMBINE_ONE_OUTPUT_OPTION(CH_COLORMAP_METHOD, colormap_algorithm); if (CHANGED(recent, CH_USE_COLORMAP)) { MARK_CH(output, CH_USE_COLORMAP); if (def_output_data.colormap_fixed) def_output_data.colormap_fixed->refcount++; Gif_DeleteColormap(active_output_data.colormap_fixed); active_output_data.colormap_fixed = def_output_data.colormap_fixed; } COMBINE_ONE_OUTPUT_OPTION(CH_DITHER, colormap_dither); if (CHANGED(recent, CH_RESIZE)) { MARK_CH(output, CH_RESIZE); active_output_data.scaling = def_output_data.scaling; active_output_data.resize_width = def_output_data.resize_width; active_output_data.resize_height = def_output_data.resize_height; active_output_data.scale_x = def_output_data.scale_x; active_output_data.scale_y = def_output_data.scale_y; } def_output_data.colormap_fixed = 0; def_output_data.output_name = 0; active_next_output |= next_output; next_output = 0; } static void redundant_option_warning(const char *option_type) { static int context = 0; warning("redundant %s option", option_type); if (!context) { warncontext("(The %s option was overridden by another %s option", option_type, option_type); warncontext("before it had any effect.)"); } context = 1; } static void print_useless_options(const char *type_name, int value, const char *names[]) { int explanation_printed = 0; int i; if (!value) return; for (i = 0; i < 32; i++) if (CHANGED(value, i)) { warning("useless %s-related %s option", names[i], type_name); if (!explanation_printed) warncontext("(It didn't affect any %s.)", type_name); explanation_printed = 1; } } /***** * main **/ int main(int argc, char **argv) { Clp_Parser *clp = Clp_NewParser(argc, argv, sizeof(options) / sizeof(options[0]), options); Clp_AddStringListType (clp, LOOP_TYPE, Clp_AllowNumbers, "infinite", 0, "forever", 0, 0); Clp_AddStringListType (clp, DISPOSAL_TYPE, Clp_AllowNumbers, "none", GIF_DISPOSAL_NONE, "asis", GIF_DISPOSAL_ASIS, "background", GIF_DISPOSAL_BACKGROUND, "bg", GIF_DISPOSAL_BACKGROUND, "previous", GIF_DISPOSAL_ASIS, 0); Clp_AddStringListType (clp, COLORMAP_ALG_TYPE, 0, "diversity", COLORMAP_DIVERSITY, "blend-diversity", COLORMAP_BLEND_DIVERSITY, "median-cut", COLORMAP_MEDIAN_CUT, 0); Clp_AddType(clp, DIMENSIONS_TYPE, 0, parse_dimensions, 0); Clp_AddType(clp, POSITION_TYPE, 0, parse_position, 0); Clp_AddType(clp, SCALE_FACTOR_TYPE, 0, parse_scale_factor, 0); Clp_AddType(clp, FRAME_SPEC_TYPE, 0, parse_frame_spec, 0); Clp_AddType(clp, COLOR_TYPE, Clp_DisallowOptions, parse_color, 0); Clp_AddType(clp, RECTANGLE_TYPE, 0, parse_rectangle, 0); Clp_AddType(clp, TWO_COLORS_TYPE, Clp_DisallowOptions, parse_two_colors, 0); Clp_SetOptionChar(clp, '+', Clp_ShortNegated); Clp_SetErrorHandler(clp, clp_error_handler); program_name = Clp_ProgramName(clp); frames = new_frameset(16); initialize_def_frame(); #ifdef DMALLOC dmalloc_verbose("fudge"); #endif /* Yep, I'm an idiot. GIF dimensions are unsigned 16-bit integers. I assume that these numbers will fit in an `int'. This assertion tests that assumption. Really I should go through & change everything over, but it doesn't seem worth my time. */ { u_int16_t m = 0xFFFFU; int i = m; assert(i > 0 && "configuration/lameness failure! bug the author!"); } while (1) { int opt = Clp_Next(clp); switch (opt) { /* MODE OPTIONS */ case 'b': set_mode(BATCHING); break; case 'm': set_mode(MERGING); break; case 'e': set_mode(EXPLODING); def_frame.explode_by_name = 0; break; case 'E': set_mode(EXPLODING); def_frame.explode_by_name = 1; break; /* INFORMATION OPTIONS */ case INFO_OPT: if (clp->negated) infoing = 0; else /* switch between infoing == 1 (suppress regular output) and 2 (don't suppress) */ infoing = (infoing == 1 ? 2 : 1); break; case COLOR_INFO_OPT: if (clp->negated) def_frame.colormap_info = 0; else { def_frame.colormap_info = 1; if (!infoing) infoing = 1; } break; case EXTENSION_INFO_OPT: if (clp->negated) def_frame.extensions_info = 0; else { def_frame.extensions_info = 1; if (!infoing) infoing = 1; } break; case VERBOSE_OPT: verbosing = clp->negated ? 0 : 1; break; /* FRAME CHANGE OPTIONS */ case DELETE_OPT: case REPLACE_OPT: case INSERT_OPT: case APPEND_OPT: frame_change_done(); set_frame_change(opt); break; case ALTER_DONE_OPT: frame_change_done(); break; /* IMAGE OPTIONS */ case NAME_OPT: if (clp->negated) goto no_names; MARK_CH(frame, CH_NAME); def_frame.name = clp->arg; break; no_names: case NO_NAME_OPT: MARK_CH(frame, CH_NAME); def_frame.no_name = 1; def_frame.name = 0; break; case SAME_NAME_OPT: def_frame.no_name = 0; def_frame.name = 0; break; case COMMENT_OPT: if (clp->negated) goto no_comments; MARK_CH(frame, CH_COMMENT); if (!def_frame.comment) def_frame.comment = Gif_NewComment(); Gif_AddComment(def_frame.comment, clp->arg, -1); break; no_comments: case NO_COMMENTS_OPT: Gif_DeleteComment(def_frame.comment); def_frame.comment = 0; def_frame.no_comments = 1; break; case SAME_COMMENTS_OPT: def_frame.no_comments = 0; break; case 'i': MARK_CH(frame, CH_INTERLACE); def_frame.interlacing = clp->negated ? 0 : 1; break; case SAME_INTERLACE_OPT: def_frame.interlacing = -1; break; case POSITION_OPT: MARK_CH(frame, CH_POSITION); def_frame.left = clp->negated ? 0 : position_x; def_frame.top = clp->negated ? 0 : position_y; break; case SAME_POSITION_OPT: def_frame.left = -1; def_frame.top = -1; break; case 't': MARK_CH(frame, CH_TRANSPARENT); if (clp->negated) def_frame.transparent.haspixel = 255; else { def_frame.transparent = parsed_color; def_frame.transparent.haspixel = parsed_color.haspixel ? 2 : 1; } break; case SAME_TRANSPARENT_OPT: def_frame.transparent.haspixel = 0; break; case BACKGROUND_OPT: MARK_CH(output, CH_BACKGROUND); if (clp->negated) { def_output_data.background.haspixel = 2; def_output_data.background.pixel = 0; } else { def_output_data.background = parsed_color; def_output_data.background.haspixel = parsed_color.haspixel ? 2 : 1; } break; case SAME_BACKGROUND_OPT: MARK_CH(output, CH_BACKGROUND); def_output_data.background.haspixel = 0; break; case LOGICAL_SCREEN_OPT: MARK_CH(output, CH_LOGICAL_SCREEN); if (clp->negated) def_output_data.screen_width = def_output_data.screen_height = 0; else { def_output_data.screen_width = dimensions_x; def_output_data.screen_height = dimensions_y; } break; case SAME_LOGICAL_SCREEN_OPT: MARK_CH(output, CH_LOGICAL_SCREEN); def_output_data.screen_width = def_output_data.screen_height = -1; break; case CROP_OPT: if (clp->negated) goto no_crop; MARK_CH(frame, CH_CROP); { Gt_Crop *crop = Gif_New(Gt_Crop); /* Memory leak on crops, but this just is NOT a problem. */ crop->ready = 0; crop->whole_stream = 0; crop->spec_x = position_x; crop->spec_y = position_y; crop->spec_w = dimensions_x; crop->spec_h = dimensions_y; def_frame.crop = crop; } break; no_crop: case SAME_CROP_OPT: def_frame.crop = 0; break; /* extensions options */ case NO_EXTENSIONS_OPT: def_frame.no_extensions = 1; break; case SAME_EXTENSIONS_OPT: def_frame.no_extensions = 0; break; case EXTENSION_OPT: if (!handle_extension(clp, 0)) goto bad_option; break; case APP_EXTENSION_OPT: if (!handle_extension(clp, 1)) goto bad_option; break; /* IMAGE DATA OPTIONS */ case FLIP_HORIZ_OPT: MARK_CH(frame, CH_FLIP); def_frame.flip_horizontal = !clp->negated; break; case FLIP_VERT_OPT: MARK_CH(frame, CH_FLIP); def_frame.flip_vertical = !clp->negated; break; case NO_FLIP_OPT: def_frame.flip_horizontal = def_frame.flip_vertical = 0; break; case NO_ROTATE_OPT: def_frame.rotation = 0; break; case ROTATE_90_OPT: MARK_CH(frame, CH_ROTATE); def_frame.rotation = 1; break; case ROTATE_180_OPT: MARK_CH(frame, CH_ROTATE); def_frame.rotation = 2; break; case ROTATE_270_OPT: MARK_CH(frame, CH_ROTATE); def_frame.rotation = 3; break; /* ANIMATION OPTIONS */ case 'd': MARK_CH(frame, CH_DELAY); def_frame.delay = clp->negated ? 0 : clp->val.i; break; case SAME_DELAY_OPT: def_frame.delay = -1; break; case DISPOSAL_OPT: MARK_CH(frame, CH_DISPOSAL); if (clp->negated) def_frame.disposal = GIF_DISPOSAL_NONE; else if (clp->val.i < 0 || clp->val.i > 7) error("disposal must be between 0 and 7"); else def_frame.disposal = clp->val.i; break; case SAME_DISPOSAL_OPT: def_frame.disposal = -1; break; case 'l': MARK_CH(output, CH_LOOPCOUNT); if (clp->negated) def_output_data.loopcount = -1; else def_output_data.loopcount = (clp->have_arg ? clp->val.i : 0); break; case SAME_LOOPCOUNT_OPT: MARK_CH(output, CH_LOOPCOUNT); def_output_data.loopcount = -2; break; case OPTIMIZE_OPT: MARK_CH(output, CH_OPTIMIZE); if (clp->negated) def_output_data.optimizing = 0; else def_output_data.optimizing = (clp->have_arg ? clp->val.i : 1); break; case UNOPTIMIZE_OPT: UNCHECKED_MARK_CH(input, CH_UNOPTIMIZE); unoptimizing = clp->negated ? 0 : 1; break; /* WHOLE-GIF OPTIONS */ case CAREFUL_OPT: { if (clp->negated) gif_read_flags = gif_write_flags = 0; else { gif_read_flags = 0; gif_write_flags = GIF_WRITE_CAREFUL_MIN_CODE_SIZE; } break; } case CHANGE_COLOR_OPT: { next_input |= CH_CHANGE_COLOR; if (clp->negated) input_transforms = delete_color_transforms (input_transforms, &color_change_transformer); else if (parsed_color2.haspixel) error("COLOR2 must be in RGB format in `--change-color COLOR1 COLOR2'"); else input_transforms = append_color_change (input_transforms, parsed_color, parsed_color2); break; } case COLOR_TRANSFORM_OPT: next_output |= CH_COLOR_TRANSFORM; if (clp->negated) output_transforms = delete_color_transforms (output_transforms, &pipe_color_transformer); else output_transforms = append_color_transform (output_transforms, &pipe_color_transformer, clp->arg); break; case COLORMAP_OPT: MARK_CH(output, CH_COLORMAP); if (clp->negated) def_output_data.colormap_size = 0; else { def_output_data.colormap_size = clp->val.i; if (def_output_data.colormap_size < 2 || def_output_data.colormap_size > 256) { Clp_OptionError(clp, "argument to `%O' must be between 2 and 256"); def_output_data.colormap_size = 0; } } break; case USE_COLORMAP_OPT: MARK_CH(output, CH_USE_COLORMAP); Gif_DeleteColormap(def_output_data.colormap_fixed); if (clp->negated) def_output_data.colormap_fixed = 0; else set_new_fixed_colormap(clp->arg); break; case COLORMAP_ALGORITHM_OPT: MARK_CH(output, CH_COLORMAP_METHOD); def_output_data.colormap_algorithm = clp->val.i; break; case DITHER_OPT: MARK_CH(output, CH_DITHER); def_output_data.colormap_dither = !clp->negated; break; case RESIZE_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = 0; else if (dimensions_x <= 0 && dimensions_y <= 0) { error("one of W and H must be positive in `--resize WxH'"); def_output_data.scaling = 0; } else { def_output_data.scaling = 1; /* use resize dimensions */ def_output_data.resize_width = dimensions_x; def_output_data.resize_height = dimensions_y; } break; case RESIZE_WIDTH_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = 0; else if (clp->val.u == 0) { error("`--resize-width' argument must be positive"); def_output_data.scaling = 0; } else { def_output_data.scaling = 1; /* use resize dimensions */ def_output_data.resize_width = clp->val.u; def_output_data.resize_height = 0; } break; case RESIZE_HEIGHT_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = 0; else if (clp->val.u == 0) { error("`--resize-height' argument must be positive"); def_output_data.scaling = 0; } else { def_output_data.scaling = 1; /* use resize dimensions */ def_output_data.resize_width = 0; def_output_data.resize_height = clp->val.u; } break; case SCALE_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = 0; else if (parsed_scale_factor_x <= 0 || parsed_scale_factor_y <= 0) { error("`--scale' X and Y factors must be positive"); def_output_data.scaling = 0; } else { def_output_data.scaling = 2; /* use scale factor */ def_output_data.scale_x = parsed_scale_factor_x; def_output_data.scale_y = parsed_scale_factor_y; } break; /* RANDOM OPTIONS */ case NO_WARNINGS_OPT: no_warnings = !clp->negated; break; case WARNINGS_OPT: no_warnings = clp->negated; break; case VERSION_OPT: #ifdef GIF_UNGIF printf("LCDF Gifsicle %s (ungif)\n", VERSION); #else printf("LCDF Gifsicle %s\n", VERSION); #endif printf("Copyright (C) 1997-2001 Eddie Kohler\n\ This is free software; see the source for copying conditions.\n\ There is NO warranty, not even for merchantability or fitness for a\n\ particular purpose.\n"); exit(EXIT_OK); break; case HELP_OPT: usage(); exit(EXIT_OK); break; case OUTPUT_OPT: MARK_CH(output, CH_OUTPUT); if (strcmp(clp->arg, "-") == 0) def_output_data.output_name = 0; else def_output_data.output_name = clp->arg; break; /* NONOPTIONS */ case Clp_NotOption: if (clp->arg[0] != '#' || !frame_argument(clp, clp->arg)) { input_done(); input_stream(clp->arg); } break; case Clp_Done: goto done; bad_option: case Clp_BadOption: short_usage(); exit(EXIT_USER_ERR); break; default: break; } } done: if (next_output) combine_output_options(); if (!files_given) input_stream(0); frame_change_done(); input_done(); if (mode == MERGING) output_frames(); verbose_endline(); print_useless_options("frame", next_frame, frame_option_types); print_useless_options("input", next_input, input_option_types); if (any_output_successful) print_useless_options("output", active_next_output, output_option_types); blank_frameset(frames, 0, 0, 1); #ifdef DMALLOC dmalloc_report(); #endif return (error_count ? EXIT_ERR : EXIT_OK); }