/* -*- c-basic-offset: 2 -*- */ /* gifsicle.c - gifsicle's main loop. Copyright (C) 1997-2017 Eddie Kohler, ekohler@gmail.com This file is part of gifsicle. Gifsicle is free software. It is distributed under the GNU Public License, version 2; 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 #include "gifsicle.h" #include "kcolor.h" #include #include #include #include #include #include #if HAVE_UNISTD_H # include #endif #ifndef static_assert #define static_assert(x, msg) switch ((int) (x)) case 0: case !!((int) (x)): #endif Gt_Frame def_frame; Gt_Frameset *frames = 0; int first_input_frame = 0; Gt_Frameset *nested_frames = 0; Gif_Stream *input = 0; const char *input_name = 0; static int unoptimizing = 0; const int GIFSICLE_DEFAULT_THREAD_COUNT = 8; int thread_count = 0; static int gif_read_flags = 0; static int nextfile = 0; Gif_CompressInfo gif_write_info; 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; int mode = BLANK_MODE; int nested_mode = 0; static int infoing = 0; int verbosing = 0; static int no_ignore_errors = 0; #define CHANGED(next, flag) (((next) & (1<<(flag))) != 0) #define UNCHECKED_MARK_CH(where, what) \ next_##where |= 1< suppresses normal output, can%,t use with an\n output mode like %<--merge%> or %<--batch%>.\n (Try %<-II%>, which doesn%,t suppress normal output.)"); if (newmode != BLANK_MODE && newmode != mode) fatal_error("too late to change modes"); } void set_frame_change(int kind) { int i; Gt_Frameset *fset; set_mode(BLANK_MODE); if (mode < DELETING && frames_done) { fatal_error("frame selection and frame changes don%,t mix"); return; } assert(!nested_mode); nested_mode = mode; if (frame_spec_1 > frame_spec_2) { i = frame_spec_1; frame_spec_1 = frame_spec_2; frame_spec_2 = i; } switch (kind) { case DELETE_OPT: mode = DELETING; break; case REPLACE_OPT: for (i = frame_spec_1; i < frame_spec_2; i++) FRAME(frames, i).use = 0; /* We want to use the last frame's delay, but nothing else about it. */ FRAME(frames, i).use = -1; /* FALLTHRU */ case INSERT_OPT: /* Define a nested frameset (or use an existing one). */ fset = FRAME(frames, frame_spec_2).nest; if (!fset) fset = new_frameset(8); FRAME(frames, frame_spec_2).nest = fset; /* Later: Merge frames at the end of the nested frameset. */ mode = INSERTING; nested_frames = frames; frames = fset; break; case APPEND_OPT: /* Just merge frames at the end of this frameset. */ mode = INSERTING; break; } } static void frame_change_done(void) { if (nested_mode) mode = nested_mode; if (nested_frames) frames = nested_frames; nested_mode = 0; nested_frames = 0; } static void show_frame(int imagenumber, int usename) { Gif_Image *gfi; Gt_Frame *frame; if (!input || !(gfi = Gif_GetImage(input, imagenumber))) return; switch (mode) { case MERGING: case INSERTING: case EXPLODING: case INFOING: case BATCHING: if (!frames_done) clear_frameset(frames, first_input_frame); frame = add_frame(frames, input, gfi); if (usename) frame->explode_by_name = 1; break; case DELETING: frame = &FRAME(frames, first_input_frame + imagenumber); frame->use = 0; break; } next_frame = 0; frames_done = 1; } /***** * input a stream **/ static void gifread_error(Gif_Stream* gfs, Gif_Image* gfi, int is_error, const char* message) { static int last_is_error = 0; static char last_landmark[256]; static char last_message[256]; static int different_error_count = 0; static int same_error_count = 0; char landmark[256]; int which_image = Gif_ImageNumber(gfs, gfi); if (gfs && which_image < 0) which_image = gfs->nimages; /* ignore warnings if "no_warning" */ if (no_warnings && is_error == 0) return; if (message) { const char *filename = gfs && gfs->landmark ? gfs->landmark : ""; if (gfi && (which_image != 0 || gfs->nimages != 1)) snprintf(landmark, sizeof(landmark), "%s:#%d", filename, which_image < 0 ? gfs->nimages : which_image); else snprintf(landmark, sizeof(landmark), "%s", filename); } if (last_message[0] && different_error_count <= 10 && (!message || strcmp(message, last_message) != 0 || strcmp(landmark, last_landmark) != 0)) { const char* etype = last_is_error ? "read error: " : ""; void (*f)(const char*, const char*, ...) = last_is_error ? lerror : lwarning; if (gfi && gfi->user_flags) /* error already reported */; else if (same_error_count == 1) f(last_landmark, "%s%s", etype, last_message); else if (same_error_count > 0) f(last_landmark, "%s%s (%d times)", etype, last_message, same_error_count); same_error_count = 0; last_message[0] = 0; } if (message) { if (last_message[0] == 0) different_error_count++; same_error_count++; strncpy(last_message, message, sizeof(last_message)); last_message[sizeof(last_message) - 1] = 0; strncpy(last_landmark, landmark, sizeof(last_landmark)); last_landmark[sizeof(last_landmark) - 1] = 0; last_is_error = is_error; if (different_error_count == 11) { if (!(gfi && gfi->user_flags)) error(0, "(plus more errors; is this GIF corrupt?)"); different_error_count++; } } else last_message[0] = 0; { unsigned long missing; if (message && sscanf(message, "missing %lu pixel", &missing) == 1 && missing > 10000 && no_ignore_errors) { gifread_error(gfs, 0, -1, 0); lerror(landmark, "fatal error: too many missing pixels, giving up"); exit(1); } } if (gfi && is_error < 0) gfi->user_flags |= 1; } struct StoredFile { FILE *f; struct StoredFile *next; char name[1]; }; static struct StoredFile *stored_files = 0; static FILE * open_giffile(const char *name) { struct StoredFile *sf; FILE *f; if (name == 0 || strcmp(name, "-") == 0) { #ifndef OUTPUT_GIF_TO_TERMINAL if (isatty(fileno(stdin))) { lerror("", "Is a terminal"); return NULL; } #endif #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdin), _O_BINARY); #elif defined(__DJGPP__) setmode(fileno(stdin), O_BINARY); #elif defined(__EMX__) _fsetmode(stdin, "b"); #endif return stdin; } if (nextfile) for (sf = stored_files; sf; sf = sf->next) if (strcmp(name, sf->name) == 0) return sf->f; f = fopen(name, "rb"); if (f && nextfile) { sf = (struct StoredFile *) malloc(sizeof(struct StoredFile) + strlen(name)); sf->f = f; sf->next = stored_files; stored_files = sf; strcpy(sf->name, name); } else if (!f) lerror(name, "%s", strerror(errno)); return f; } static void close_giffile(FILE *f, int final) { struct StoredFile **sf_pprev, *sf; if (!final && nextfile) { int c = getc(f); if (c == EOF) final = 1; else ungetc(c, f); } for (sf_pprev = &stored_files; (sf = *sf_pprev); sf_pprev = &sf->next) if (sf->f == f) { if (final) { fclose(f); *sf_pprev = sf->next; free((void *) sf); } return; } if (f != stdin) fclose(f); } void input_stream(const char *name) { char* component_namebuf; FILE *f; Gif_Stream *gfs; int i; int saved_next_frame = next_frame; int componentno = 0; const char *main_name = 0; 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++; set_mode(BLANK_MODE); f = open_giffile(name); if (!f) return; if (f == stdin) { name = ""; input_name = 0; } main_name = name; retry_file: /* change filename for component files */ componentno++; if (componentno > 1) { component_namebuf = (char*) malloc(strlen(main_name) + 10); sprintf(component_namebuf, "%s~%d", main_name, componentno); name = component_namebuf; } else component_namebuf = 0; /* check for empty file */ i = getc(f); if (i == EOF) { if (!(gif_read_flags & GIF_READ_TRAILING_GARBAGE_OK)) lerror(name, "empty file"); else if (nextfile) lerror(name, "no more images in file"); goto error; } ungetc(i, f); if (verbosing) verbose_open('<', name); /* read file */ { int old_error_count = error_count; gfs = Gif_FullReadFile(f, gif_read_flags | GIF_READ_COMPRESSED, name, gifread_error); if ((!gfs || (Gif_ImageCount(gfs) == 0 && gfs->errors > 0)) && componentno != 1) lerror(name, "trailing garbage ignored"); if (!no_ignore_errors) error_count = old_error_count; } if (!gfs || (Gif_ImageCount(gfs) == 0 && gfs->errors > 0)) { if (componentno == 1) lerror(name, "file not in GIF format"); Gif_DeleteStream(gfs); if (verbosing) verbose_close('>'); goto error; } /* special processing for components after the first */ if (componentno > 1) { if (mode == BATCHING || mode == INSERTING) fatal_error("%s: %<--multifile%> is useful only in merge mode", name); input_done(); } input = gfs; /* Processing when we've got a new input frame */ set_mode(BLANK_MODE); 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. */ const char *explode_name = (input_name ? input_name : "#stdin#"); const 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; def_frame.position_is_offset = 1; for (i = 0; i < gfs->nimages; i++) add_frame(frames, gfs, gfs->images[i]); def_frame = old_def_frame; if (unoptimizing) if (!Gif_FullUnoptimize(gfs, GIF_UNOPTIMIZE_SIMPLEST_DISPOSAL)) { static int context = 0; if (!context) { lwarning(name, "GIF too complex to unoptimize\n" " (The reason was local color tables or complex transparency.\n" " Try running the GIF through % first.)"); context = 1; } else lwarning(name, "GIF too complex to unoptimize"); } apply_color_transforms(input_transforms, gfs); gfs->refcount++; /* Read more files. */ free(component_namebuf); if ((gif_read_flags & GIF_READ_TRAILING_GARBAGE_OK) && !nextfile) goto retry_file; close_giffile(f, 0); return; error: free(component_namebuf); close_giffile(f, 1); } void input_done(void) { if (!input) return; if (verbosing) verbose_close('>'); 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(const 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].gfc_red = (i / 36) * 0x33; col[i].gfc_green = ((i / 6) % 6) * 0x33; col[i].gfc_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].gfc_red = col[i].gfc_green = col[i].gfc_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].gfc_red = cm->col[0].gfc_green = cm->col[0].gfc_blue = 0; cm->col[1].gfc_red = cm->col[1].gfc_green = cm->col[1].gfc_blue = 255; def_output_data.colormap_fixed = cm; } else def_output_data.colormap_fixed = read_colormap_file(name, 0); } static void do_colormap_change(Gif_Stream *gfs) { if (active_output_data.colormap_fixed || active_output_data.colormap_size > 0) kc_set_gamma(active_output_data.colormap_gamma_type, active_output_data.colormap_gamma); if (active_output_data.colormap_fixed) colormap_stream(gfs, active_output_data.colormap_fixed, &active_output_data); if (active_output_data.colormap_size > 0) { kchist kch; Gif_Colormap* (*adapt_func)(kchist*, Gt_OutputData*); Gif_Colormap *new_cm; /* set up the histogram */ { uint32_t ntransp; int i, any_locals = 0; for (i = 0; i < gfs->nimages; i++) if (gfs->images[i]->local) any_locals = 1; kchist_make(&kch, gfs, &ntransp); if (kch.n <= active_output_data.colormap_size && !any_locals && !active_output_data.colormap_fixed) { warning(1, "trivial adaptive palette (only %d colors in source)", kch.n); kchist_cleanup(&kch); return; } active_output_data.colormap_needs_transparency = ntransp > 0; } 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)(&kch, &active_output_data); colormap_stream(gfs, new_cm, &active_output_data); Gif_DeleteColormap(new_cm); kchist_cleanup(&kch); } } /***** * output GIF images **/ static void write_stream(const char *output_name, Gif_Stream *gfs) { FILE *f; if (output_name) f = fopen(output_name, "wb"); else { #ifndef OUTPUT_GIF_TO_TERMINAL if (isatty(fileno(stdout))) { lerror("", "Is a terminal: try `-o OUTPUTFILE`"); return; } #endif #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdout), _O_BINARY); #elif defined(__DJGPP__) setmode(fileno(stdout), O_BINARY); #elif defined(__EMX__) _fsetmode(stdout, "b"); #endif f = stdout; output_name = ""; } if (f) { Gif_FullWriteFile(gfs, &gif_write_info, f); fclose(f); any_output_successful = 1; } else lerror(output_name, "%s", strerror(errno)); } static void merge_and_write_frames(const char *outfile, int f1, int f2) { Gif_Stream *out; int compress_immediately; int colormap_change; int huge_stream; assert(!nested_mode); if (verbosing) verbose_open('[', outfile ? outfile : "#stdout#"); active_output_data.active_output_name = outfile; colormap_change = active_output_data.colormap_size > 0 || active_output_data.colormap_fixed; warn_local_colormaps = !colormap_change; if (!(active_output_data.scaling || (active_output_data.optimizing & GT_OPT_MASK) || colormap_change)) compress_immediately = 1; else compress_immediately = active_output_data.conserve_memory; out = merge_frame_interval(frames, f1, f2, &active_output_data, compress_immediately, &huge_stream); if (out) { double w, h; if (active_output_data.scaling == GT_SCALING_SCALE) { w = active_output_data.scale_x * out->screen_width; h = active_output_data.scale_y * out->screen_height; } else { w = active_output_data.resize_width; h = active_output_data.resize_height; } if (active_output_data.scaling != GT_SCALING_NONE) resize_stream(out, w, h, active_output_data.resize_flags, active_output_data.scale_method, active_output_data.scale_colors); if (colormap_change) do_colormap_change(out); if (output_transforms) apply_color_transforms(output_transforms, out); if (active_output_data.optimizing & GT_OPT_MASK) optimize_fragments(out, active_output_data.optimizing, huge_stream); write_stream(outfile, out); Gif_DeleteStream(out); } if (verbosing) verbose_close(']'); active_output_data.active_output_name = 0; } 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) { lerror(outfile, "%s", strerror(errno)); return; } } for (i = 0; i < frames->count; i++) FRAME(frames, i).stream->user_flags = 97; for (i = 0; i < frames->count; i++) if (FRAME(frames, i).stream->user_flags == 97) { fr = &FRAME(frames, i); gfs = fr->stream; gfs->user_flags = 0; stream_info(f, gfs, fr->input_filename, fr->info_flags); for (j = i; j < frames->count; j++) if (FRAME(frames, j).stream == gfs) { fr = &FRAME(frames, j); image_info(f, gfs, fr->image, fr->info_flags); } } 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; const 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: case INFOING: 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; const 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, const 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, delta = (frame_spec_1 <= frame_spec_2 ? 1 : -1); for (i = frame_spec_1; i != frame_spec_2 + delta; i += delta) 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; const char *extension_type = clp->vstr; const 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, 11); else if (!isdigit(extension_type[0]) && extension_type[1] == 0) gfex = Gif_NewExtension(extension_type[0], 0, 0); else { long l = strtol(extension_type, (char **)&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, 0); } gfex->data = (uint8_t *)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.no_app_extensions = 0; def_frame.extensions = 0; def_frame.flip_horizontal = 0; def_frame.flip_vertical = 0; def_frame.total_crop = 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.dither_type = dither_none; def_output_data.dither_name = "none"; def_output_data.colormap_gamma_type = KC_GAMMA_SRGB; def_output_data.colormap_gamma = 2.2; def_output_data.optimizing = 0; def_output_data.scaling = GT_SCALING_NONE; def_output_data.scale_method = SCALE_METHOD_MIX; def_output_data.scale_colors = 0; def_output_data.conserve_memory = 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; } if (CHANGED(recent, CH_DITHER)) { MARK_CH(output, CH_DITHER); active_output_data.dither_type = def_output_data.dither_type; active_output_data.dither_data = def_output_data.dither_data; } if (CHANGED(recent, CH_GAMMA)) { MARK_CH(output, CH_GAMMA); active_output_data.colormap_gamma_type = def_output_data.colormap_gamma_type; active_output_data.colormap_gamma = def_output_data.colormap_gamma; } 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.resize_flags = def_output_data.resize_flags; active_output_data.scale_x = def_output_data.scale_x; active_output_data.scale_y = def_output_data.scale_y; } if (CHANGED(recent, CH_RESIZE_METHOD)) { MARK_CH(output, CH_RESIZE_METHOD); active_output_data.scale_method = def_output_data.scale_method; } if (CHANGED(recent, CH_SCALE_COLORS)) { MARK_CH(output, CH_SCALE_COLORS); active_output_data.scale_colors = def_output_data.scale_colors; } COMBINE_ONE_OUTPUT_OPTION(CH_MEMORY, conserve_memory); 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* opttype) { static int context = 0; if (!context) { warning(0, "redundant %s option\n" " (The %s option was overridden by another %s option\n" " before it had any effect.)", opttype, opttype, opttype); context = 1; } else warning(0, "redundant %s option", opttype); } 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)) { if (!explanation_printed) { warning(0, "useless %s-related %s option\n" " (It didn%,t affect any %s.)", names[i], type_name, type_name); explanation_printed = 1; } else warning(0, "useless %s-related %s option", names[i], type_name); } } static Gt_Crop * copy_crop(Gt_Crop *oc) { Gt_Crop *nc = Gif_New(Gt_Crop); /* Memory leak on crops, but this just is NOT a problem. */ if (oc) *nc = *oc; else memset(nc, 0, sizeof(Gt_Crop)); nc->ready = 0; return nc; } static void parse_resize_geometry_opt(Gt_OutputData* odata, const char* str, Clp_Parser* clp) { double x, y; int flags = GT_RESIZE_FIT, scale = 0; if (*str == '_' || *str == 'x') { x = 0; str += (*str == '_'); } else if (isdigit((unsigned char) *str)) x = strtol(str, (char**) &str, 10); else goto error; if (*str == 'x') { ++str; if (*str == '_' || !isdigit((unsigned char) *str)) { y = 0; str += (*str == '_'); } else y = strtol(str, (char**) &str, 10); } else y = x; for (; *str != 0; ++str) if (*str == '%') scale = 1; else if (*str == '!') flags = 0; else if (*str == '^') flags |= GT_RESIZE_FIT | GT_RESIZE_MIN_DIMEN; else if (*str == '<') flags |= GT_RESIZE_FIT | GT_RESIZE_FIT_UP; else if (*str == '>') flags |= GT_RESIZE_FIT | GT_RESIZE_FIT_DOWN; else goto error; if (scale) { odata->scaling = GT_SCALING_SCALE; odata->scale_x = x / 100.0; odata->scale_y = y / 100.0; } else { odata->scaling = GT_SCALING_RESIZE; odata->resize_width = x; odata->resize_height = y; } odata->resize_flags = flags; return; error: Clp_OptionError(clp, "argument to %O must be a valid geometry specification"); } /***** * main **/ int main(int argc, char *argv[]) { /* Check SIZEOF constants (useful for Windows). If these assertions fail, you've used the wrong Makefile. You should've used Makefile.w32 for 32-bit Windows and Makefile.w64 for 64-bit Windows. */ static_assert(sizeof(unsigned int) == SIZEOF_UNSIGNED_INT, "unsigned int has the wrong size."); static_assert(sizeof(unsigned long) == SIZEOF_UNSIGNED_LONG, "unsigned long has the wrong size."); static_assert(sizeof(void*) == SIZEOF_VOID_P, "void* has the wrong size."); clp = Clp_NewParser(argc, (const char * const *)argv, sizeof(options) / sizeof(options[0]), options); Clp_AddStringListType (clp, LOOP_TYPE, Clp_AllowNumbers, "infinite", 0, "forever", 0, (const char*) 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_PREVIOUS, (const char*) 0); Clp_AddStringListType (clp, COLORMAP_ALG_TYPE, 0, "diversity", COLORMAP_DIVERSITY, "blend-diversity", COLORMAP_BLEND_DIVERSITY, "median-cut", COLORMAP_MEDIAN_CUT, (const char*) 0); Clp_AddStringListType (clp, OPTIMIZE_TYPE, Clp_AllowNumbers, "keep-empty", GT_OPT_KEEPEMPTY + 1, "no-keep-empty", GT_OPT_KEEPEMPTY, "drop-empty", GT_OPT_KEEPEMPTY, "no-drop-empty", GT_OPT_KEEPEMPTY + 1, (const char*) 0); Clp_AddStringListType (clp, RESIZE_METHOD_TYPE, 0, "point", SCALE_METHOD_POINT, "sample", SCALE_METHOD_POINT, "mix", SCALE_METHOD_MIX, "box", SCALE_METHOD_BOX, "catrom", SCALE_METHOD_CATROM, "lanczos", SCALE_METHOD_LANCZOS3, "lanczos2", SCALE_METHOD_LANCZOS2, "lanczos3", SCALE_METHOD_LANCZOS3, "mitchell", SCALE_METHOD_MITCHELL, "fast", SCALE_METHOD_POINT, "good", SCALE_METHOD_MIX, (const char*) 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(); Gif_InitCompressInfo(&gif_write_info); Gif_SetErrorHandler(gifread_error); #if ENABLE_THREADS pthread_mutex_init(&kd3_sort_lock, 0); #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. */ { uint16_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.info_flags &= ~INFO_COLORMAPS; else { def_frame.info_flags |= INFO_COLORMAPS; if (!infoing) infoing = 1; } break; case EXTENSION_INFO_OPT: if (clp->negated) def_frame.info_flags &= ~INFO_EXTENSIONS; else { def_frame.info_flags |= INFO_EXTENSIONS; if (!infoing) infoing = 1; } break; case SIZE_INFO_OPT: if (clp->negated) def_frame.info_flags &= ~INFO_SIZES; else { def_frame.info_flags |= INFO_SIZES; 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->vstr; 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->vstr, -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 = copy_crop(def_frame.crop); /* Memory leak on crops, but this just is NOT a problem. */ 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; case CROP_TRANSPARENCY_OPT: if (clp->negated) goto no_crop_transparency; def_frame.crop = copy_crop(def_frame.crop); def_frame.crop->transparent_edges = 1; break; no_crop_transparency: if (def_frame.crop && def_frame.crop->transparent_edges) { def_frame.crop = copy_crop(def_frame.crop); def_frame.crop->transparent_edges = 0; } break; /* extensions options */ case NO_EXTENSIONS_OPT: def_frame.no_extensions = 1; break; case NO_APP_EXTENSIONS_OPT: def_frame.no_app_extensions = 1; break; case SAME_EXTENSIONS_OPT: def_frame.no_extensions = 0; break; case SAME_APP_EXTENSIONS_OPT: def_frame.no_app_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(0, "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_val ? clp->val.i : 0); break; case SAME_LOOPCOUNT_OPT: MARK_CH(output, CH_LOOPCOUNT); def_output_data.loopcount = -2; break; case OPTIMIZE_OPT: { int o; UNCHECKED_MARK_CH(output, CH_OPTIMIZE); if (clp->negated || (clp->have_val && clp->val.i < 0)) o = 0; else o = (clp->have_val ? clp->val.i : 1); if (o > GT_OPT_MASK && (o & 1)) def_output_data.optimizing |= o - 1; else if (o > GT_OPT_MASK) def_output_data.optimizing &= ~o; else def_output_data.optimizing = (def_output_data.optimizing & ~GT_OPT_MASK) | o; break; } case UNOPTIMIZE_OPT: UNCHECKED_MARK_CH(input, CH_UNOPTIMIZE); unoptimizing = clp->negated ? 0 : 1; break; case THREADS_OPT: if (clp->negated) thread_count = 0; else if (clp->have_val) thread_count = clp->val.i; else thread_count = GIFSICLE_DEFAULT_THREAD_COUNT; break; /* WHOLE-GIF OPTIONS */ case CAREFUL_OPT: { if (clp->negated) gif_write_info.flags = 0; else gif_write_info.flags = GIF_WRITE_CAREFUL_MIN_CODE_SIZE | GIF_WRITE_EAGER_CLEAR; break; } case CHANGE_COLOR_OPT: { next_input |= 1 << CH_CHANGE_COLOR; if (clp->negated) input_transforms = delete_color_transforms (input_transforms, &color_change_transformer); else if (parsed_color2.haspixel) error(0, "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 |= 1 << 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, (void *)clp->vstr); 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 GRAY_OPT: MARK_CH(output, CH_USE_COLORMAP); Gif_DeleteColormap(def_output_data.colormap_fixed); set_new_fixed_colormap("gray"); 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->vstr); break; case COLORMAP_ALGORITHM_OPT: MARK_CH(output, CH_COLORMAP_METHOD); def_output_data.colormap_algorithm = clp->val.i; break; case DITHER_OPT: { const char* name; if (clp->negated) name = "none"; else if (!clp->have_val) name = "default"; else name = clp->val.s; if (strcmp(name, "posterize") == 0) name = "none"; if (strcmp(name, def_output_data.dither_name) != 0 && (strcmp(name, "none") == 0 || strcmp(def_output_data.dither_name, "default") != 0)) MARK_CH(output, CH_DITHER); UNCHECKED_MARK_CH(output, CH_DITHER); if (set_dither_type(&def_output_data, name) < 0) Clp_OptionError(clp, "%<%s%> is not a valid dither", name); def_output_data.dither_name = name; break; } case GAMMA_OPT: { #if HAVE_POW char* ends; MARK_CH(output, CH_GAMMA); if (clp->negated) { def_output_data.colormap_gamma_type = KC_GAMMA_NUMERIC; def_output_data.colormap_gamma = 1; } else if (strcmp(clp->val.s, "sRGB") == 0 || strcmp(clp->val.s, "srgb") == 0) def_output_data.colormap_gamma_type = KC_GAMMA_SRGB; else { double gamma = strtod(clp->val.s, &ends); if (*clp->val.s && !*ends && !isspace((unsigned char) *clp->val.s)) { def_output_data.colormap_gamma_type = KC_GAMMA_NUMERIC; def_output_data.colormap_gamma = gamma; } else Clp_OptionError(clp, "%O should be a number or %"); } #else Clp_OptionError(clp, "this version of Gifsicle does not support %O"); #endif break; } case RESIZE_OPT: case RESIZE_FIT_OPT: case RESIZE_TOUCH_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = GT_SCALING_NONE; else if (dimensions_x <= 0 && dimensions_y <= 0) { error(0, "one of W and H must be positive in %<%s WxH%>", Clp_CurOptionName(clp)); def_output_data.scaling = GT_SCALING_NONE; } else { def_output_data.scaling = GT_SCALING_RESIZE; def_output_data.resize_width = dimensions_x; def_output_data.resize_height = dimensions_y; def_output_data.resize_flags = 0; if (opt != RESIZE_OPT) def_output_data.resize_flags |= GT_RESIZE_FIT; if (opt == RESIZE_FIT_OPT) def_output_data.resize_flags |= GT_RESIZE_FIT_DOWN; } break; case RESIZE_WIDTH_OPT: case RESIZE_HEIGHT_OPT: case RESIZE_FIT_WIDTH_OPT: case RESIZE_FIT_HEIGHT_OPT: case RESIZE_TOUCH_WIDTH_OPT: case RESIZE_TOUCH_HEIGHT_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = GT_SCALING_NONE; else if (clp->val.u == 0) { error(0, "%s argument must be positive", Clp_CurOptionName(clp)); def_output_data.scaling = GT_SCALING_NONE; } else { unsigned dimen[2] = {0, 0}; dimen[(opt == RESIZE_HEIGHT_OPT || opt == RESIZE_FIT_HEIGHT_OPT)] = clp->val.u; def_output_data.scaling = GT_SCALING_RESIZE; def_output_data.resize_width = dimen[0]; def_output_data.resize_height = dimen[1]; def_output_data.resize_flags = 0; if (opt != RESIZE_WIDTH_OPT && opt != RESIZE_HEIGHT_OPT) def_output_data.resize_flags |= GT_RESIZE_FIT; if (opt == RESIZE_FIT_WIDTH_OPT || opt == RESIZE_FIT_HEIGHT_OPT) def_output_data.resize_flags |= GT_RESIZE_FIT_DOWN; } break; case SCALE_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = GT_SCALING_NONE; else if (parsed_scale_factor_x <= 0 || parsed_scale_factor_y <= 0) { error(0, "%s X and Y factors must be positive", Clp_CurOptionName(clp)); def_output_data.scaling = GT_SCALING_NONE; } else { def_output_data.scaling = GT_SCALING_SCALE; def_output_data.scale_x = parsed_scale_factor_x; def_output_data.scale_y = parsed_scale_factor_y; def_output_data.resize_flags = 0; } break; case RESIZE_GEOMETRY_OPT: MARK_CH(output, CH_RESIZE); if (clp->negated) def_output_data.scaling = GT_SCALING_NONE; else parse_resize_geometry_opt(&def_output_data, clp->val.s, clp); break; case RESIZE_METHOD_OPT: MARK_CH(output, CH_RESIZE_METHOD); def_output_data.scale_method = clp->val.i; break; case RESIZE_COLORS_OPT: MARK_CH(output, CH_SCALE_COLORS); def_output_data.scale_colors = clp->negated ? 0 : clp->val.i; if (def_output_data.scale_colors > 256) { error(0, "%s can be at most 256", Clp_CurOptionName(clp)); def_output_data.scale_colors = 256; } break; /* RANDOM OPTIONS */ case NO_WARNINGS_OPT: no_warnings = !clp->negated; break; case WARNINGS_OPT: no_warnings = clp->negated; break; case IGNORE_ERRORS_OPT: no_ignore_errors = clp->negated; break; case CONSERVE_MEMORY_OPT: MARK_CH(output, CH_MEMORY); def_output_data.conserve_memory = clp->negated ? -1 : 1; break; case MULTIFILE_OPT: if (clp->negated) gif_read_flags &= ~GIF_READ_TRAILING_GARBAGE_OK; else { gif_read_flags |= GIF_READ_TRAILING_GARBAGE_OK; nextfile = 0; } break; case NEXTFILE_OPT: if (clp->negated) gif_read_flags &= ~GIF_READ_TRAILING_GARBAGE_OK; else { gif_read_flags |= GIF_READ_TRAILING_GARBAGE_OK; nextfile = 1; } 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-2017 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->vstr, "-") == 0) def_output_data.output_name = 0; else def_output_data.output_name = clp->vstr; break; /* NONOPTIONS */ case Clp_NotOption: if (clp->vstr[0] != '#' || !frame_argument(clp, clp->vstr)) { input_done(); input_stream(clp->vstr); } 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 && !error_count) || mode == INFOING) 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); Clp_DeleteParser(clp); return (error_count ? EXIT_ERR : EXIT_OK); }