+#endif
+#if !defined(HAVE_UINTPTR_T) && defined(HAVE_CONFIG_H)
+typedef unsigned long uintptr_t;
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/** @file clp.h
+ * @brief Functions for parsing command line options.
+ *
+ * The CLP functions are used to parse command line arguments into options.
+ * It automatically handles value parsing, error messages, long options with
+ * minimum prefix matching, short options, and negated options.
+ *
+ * The CLP model works like this.
+ *
+ *
+ * - The user declares an array of Clp_Option structures that define the
+ * options their program accepts.
+ * - The user creates a Clp_Parser object using Clp_NewParser(), passing in
+ * the command line arguments to parse and the Clp_Option structures.
+ * - A loop repeatedly calls Clp_Next() to parse the arguments.
+ *
+ *
+ * Unlike many command line parsing libraries, CLP steps through all arguments
+ * one at a time, rather than slurping up all options at once. This makes it
+ * meaningful to give an option more than once.
+ *
+ * Here's an example.
+ *
+ * @code
+ * #define ANIMAL_OPT 1
+ * #define VEGETABLE_OPT 2
+ * #define MINERALS_OPT 3
+ * #define USAGE_OPT 4
+ *
+ * static const Clp_Option options[] = {
+ * { "animal", 'a', ANIMAL_OPT, Clp_ValString, 0 },
+ * { "vegetable", 'v', VEGETABLE_OPT, Clp_ValString, Clp_Negate | Clp_Optional },
+ * { "minerals", 'm', MINERALS_OPT, Clp_ValInt, 0 },
+ * { "usage", 0, USAGE_OPT, 0, 0 }
+ * };
+ *
+ * int main(int argc, char *argv[]) {
+ * Clp_Parser *clp = Clp_NewParser(argc, argv,
+ * sizeof(options) / sizeof(options[0]), options);
+ * int opt;
+ * while ((opt = Clp_Next(clp)) != Clp_Done)
+ * switch (opt) {
+ * case ANIMAL_OPT:
+ * fprintf(stderr, "animal is %s\n", clp->val.s);
+ * break;
+ * case VEGETABLE_OPT:
+ * if (clp->negated)
+ * fprintf(stderr, "no vegetables!\n");
+ * else if (clp->have_val)
+ * fprintf(stderr, "vegetable is %s\n", clp->val.s);
+ * else
+ * fprintf(stderr, "vegetables OK\n");
+ * break;
+ * case MINERALS_OPT:
+ * fprintf(stderr, "%d minerals\n", clp->val.i);
+ * break;
+ * case USAGE_OPT:
+ * fprintf(stderr, "Usage: 20q [--animal=ANIMAL] [--vegetable[=VEGETABLE]] [--minerals=N]\n");
+ * break;
+ * case Clp_NotOption:
+ * fprintf(stderr, "non-option %s\n", clp->vstr);
+ * break;
+ * }
+ * }
+ * }
+ * @endcode
+ *
+ * Here are a couple of executions.
+ *
+ *
+ * % ./20q --animal=cat
+ * animal is cat
+ * % ./20q --animal=cat -a dog -afish --animal bird --an=snake
+ * animal is cat
+ * animal is dog
+ * animal is fish
+ * animal is bird
+ * animal is snake
+ * % ./20q --no-vegetables
+ * no vegetables!
+ * % ./20q -v
+ * vegetables OK
+ * % ./20q -vkale
+ * vegetable is kale
+ * % ./20q -m10
+ * 10 minerals
+ * % ./20q -m foo
+ * '-m' expects an integer, not 'foo'
+ *
+ */
+
+
+/* Option types for Clp_SetOptionChar */
+#define Clp_DoubledLong (Clp_LongImplicit * 2)
+
+#define Clp_InitialValType 8
+#define MAX_AMBIGUOUS_VALUES 4
+
+typedef struct {
+ int val_type;
+ Clp_ValParseFunc func;
+ int flags;
+ void *user_data;
+} Clp_ValType;
+
+typedef struct {
+ unsigned ilong : 1;
+ unsigned ishort : 1;
+ unsigned imandatory : 1;
+ unsigned ioptional : 1;
+ unsigned ipos : 1;
+ unsigned ineg : 1;
+ unsigned iprefmatch : 1;
+ unsigned lmmpos_short : 1;
+ unsigned lmmneg_short : 1;
+ unsigned unquoted : 1;
+ unsigned char ilongoff;
+ int lmmpos;
+ int lmmneg;
+} Clp_InternOption;
+
+
+#define Clp_OptionCharsSize 5
+
+typedef struct {
+ int c;
+ int type;
+} Clp_Oclass;
+#define Clp_OclassSize 10
+
+typedef struct Clp_Internal {
+ const Clp_Option *opt;
+ Clp_InternOption *iopt;
+ int nopt;
+ unsigned opt_generation;
+
+ Clp_ValType *valtype;
+ int nvaltype;
+
+ const char * const *argv;
+ int argc;
+
+ Clp_Oclass oclass[Clp_OclassSize];
+ int noclass;
+ int long1pos;
+ int long1neg;
+ int utf8;
+
+ char option_chars[Clp_OptionCharsSize];
+ const char *xtext;
+
+ const char *program_name;
+ void (*error_handler)(Clp_Parser *, const char *);
+
+ int option_processing;
+ int current_option;
+
+ unsigned char is_short;
+ unsigned char whole_negated; /* true if negated by an option character */
+ unsigned char could_be_short;
+ unsigned char current_short;
+ unsigned char negated_by_no;
+
+ int ambiguous;
+ int ambiguous_values[MAX_AMBIGUOUS_VALUES];
+} Clp_Internal;
+
+
+struct Clp_ParserState {
+ const char * const *argv;
+ int argc;
+
+ char option_chars[Clp_OptionCharsSize];
+ const char *xtext;
+
+ int option_processing;
+
+ unsigned opt_generation;
+ int current_option;
+ unsigned char is_short;
+ unsigned char whole_negated;
+ unsigned char current_short;
+ unsigned char negated_by_no;
+};
+
+
+typedef struct Clp_StringList {
+ Clp_Option *items;
+ Clp_InternOption *iopt;
+ int nitems;
+
+ unsigned char allow_int;
+ unsigned char val_long;
+ int nitems_invalid_report;
+} Clp_StringList;
+
+
+static const Clp_Option clp_option_sentinel[] = {
+ {"", 0, Clp_NotOption, 0, 0},
+ {"", 0, Clp_Done, 0, 0},
+ {"", 0, Clp_BadOption, 0, 0},
+ {"", 0, Clp_Error, 0, 0}
+};
+
+
+static int parse_string(Clp_Parser *, const char *, int, void *);
+static int parse_int(Clp_Parser *, const char *, int, void *);
+static int parse_bool(Clp_Parser *, const char *, int, void *);
+static int parse_double(Clp_Parser *, const char *, int, void *);
+static int parse_string_list(Clp_Parser *, const char *, int, void *);
+
+static int ambiguity_error(Clp_Parser *, int, int *, const Clp_Option *,
+ const Clp_InternOption *, const char *, const char *,
+ ...);
+
+
+/*******
+ * utf8
+ **/
+
+#define U_REPLACEMENT 0xFFFD
+
+static char *
+encode_utf8(char *s, int n, int c)
+{
+ if (c < 0 || c >= 0x110000 || (c >= 0xD800 && c <= 0xDFFF))
+ c = U_REPLACEMENT;
+ if (c <= 0x7F && n >= 1)
+ *s++ = c;
+ else if (c <= 0x7FF && n >= 2) {
+ *s++ = 0xC0 | (c >> 6);
+ goto char1;
+ } else if (c <= 0xFFFF && n >= 3) {
+ *s++ = 0xE0 | (c >> 12);
+ goto char2;
+ } else if (n >= 4) {
+ *s++ = 0xF0 | (c >> 18);
+ *s++ = 0x80 | ((c >> 12) & 0x3F);
+ char2:
+ *s++ = 0x80 | ((c >> 6) & 0x3F);
+ char1:
+ *s++ = 0x80 | (c & 0x3F);
+ }
+ return s;
+}
+
+static int
+decode_utf8(const char *s, const char **cp)
+{
+ int c;
+ if ((unsigned char) *s <= 0x7F) /* 1 byte: 0x000000-0x00007F */
+ c = *s++;
+ else if ((unsigned char) *s <= 0xC1) /* bad/overlong encoding */
+ goto replacement;
+ else if ((unsigned char) *s <= 0xDF) { /* 2 bytes: 0x000080-0x0007FF */
+ if ((s[1] & 0xC0) != 0x80) /* bad encoding */
+ goto replacement;
+ c = (*s++ & 0x1F) << 6;
+ goto char1;
+ } else if ((unsigned char) *s <= 0xEF) { /* 3 bytes: 0x000800-0x00FFFF */
+ if ((s[1] & 0xC0) != 0x80 /* bad encoding */
+ || (s[2] & 0xC0) != 0x80 /* bad encoding */
+ || ((unsigned char) *s == 0xE0 /* overlong encoding */
+ && (s[1] & 0xE0) == 0x80)
+ || ((unsigned char) *s == 0xED /* encoded surrogate */
+ && (s[1] & 0xE0) == 0xA0))
+ goto replacement;
+ c = (*s++ & 0x0F) << 12;
+ goto char2;
+ } else if ((unsigned char) *s <= 0xF4) { /* 4 bytes: 0x010000-0x10FFFF */
+ if ((s[1] & 0xC0) != 0x80 /* bad encoding */
+ || (s[2] & 0xC0) != 0x80 /* bad encoding */
+ || (s[3] & 0xC0) != 0x80 /* bad encoding */
+ || ((unsigned char) *s == 0xF0 /* overlong encoding */
+ && (s[1] & 0xF0) == 0x80)
+ || ((unsigned char) *s == 0xF4 /* encoded value > 0x10FFFF */
+ && (unsigned char) s[1] >= 0x90))
+ goto replacement;
+ c = (*s++ & 0x07) << 18;
+ c += (*s++ & 0x3F) << 12;
+ char2:
+ c += (*s++ & 0x3F) << 6;
+ char1:
+ c += (*s++ & 0x3F);
+ } else {
+ replacement:
+ c = U_REPLACEMENT;
+ for (s++; (*s & 0xC0) == 0x80; s++)
+ /* nothing */;
+ }
+ if (cp)
+ *cp = s;
+ return c;
+}
+
+static int
+utf8_charlen(const char *s)
+{
+ const char *sout;
+ (void) decode_utf8(s, &sout);
+ return sout - s;
+}
+
+static int
+clp_utf8_charlen(const Clp_Internal *cli, const char *s)
+{
+ return (cli->utf8 ? utf8_charlen(s) : 1);
+}
+
+
+/*******
+ * Clp_NewParser, etc.
+ **/
+
+static int
+min_different_chars(const char *s, const char *t)
+ /* Returns the minimum number of bytes required to distinguish
+ s from t.
+ If s is shorter than t, returns strlen(s). */
+{
+ const char *sfirst = s;
+ while (*s && *t && *s == *t)
+ s++, t++;
+ if (!*s)
+ return s - sfirst;
+ else
+ return s - sfirst + 1;
+}
+
+static int
+long_as_short(const Clp_Internal *cli, const Clp_Option *o,
+ Clp_InternOption *io, int failure)
+{
+ if ((cli->long1pos || cli->long1neg) && io->ilong) {
+ const char *name = o->long_name + io->ilongoff;
+ if (cli->utf8) {
+ int c = decode_utf8(name, &name);
+ if (!*name && c && c != U_REPLACEMENT)
+ return c;
+ } else if (name[0] && !name[1])
+ return (unsigned char) name[0];
+ }
+ return failure;
+}
+
+static void
+compare_options(Clp_Parser *clp, const Clp_Option *o1, Clp_InternOption *io1,
+ const Clp_Option *o2, Clp_InternOption *io2)
+{
+ Clp_Internal *cli = clp->internal;
+ int short1, shortx1;
+
+ /* ignore meaningless combinations */
+ if ((!io1->ishort && !io1->ilong) || (!io2->ishort && !io2->ilong)
+ || !((io1->ipos && io2->ipos) || (io1->ineg && io2->ineg))
+ || o1->option_id == o2->option_id)
+ return;
+
+ /* look for duplication of short options */
+ short1 = (io1->ishort ? o1->short_name : -1);
+ shortx1 = long_as_short(cli, o1, io1, -2);
+ if (short1 >= 0 || shortx1 >= 0) {
+ int short2 = (io2->ishort ? o2->short_name : -3);
+ int shortx2 = long_as_short(cli, o2, io2, -4);
+ if (short1 == short2)
+ Clp_OptionError(clp, "CLP internal error: more than 1 option has short name %<%c%>", short1);
+ else if ((short1 == shortx2 || shortx1 == short2 || shortx1 == shortx2)
+ && ((io1->ipos && io2->ipos && cli->long1pos)
+ || (io1->ineg && io2->ineg && cli->long1neg)))
+ Clp_OptionError(clp, "CLP internal error: 1-char long name conflicts with short name %<%c%>", (short1 == shortx2 ? shortx2 : shortx1));
+ }
+
+ /* analyze longest minimum match */
+ if (io1->ilong) {
+ const char *name1 = o1->long_name + io1->ilongoff;
+
+ /* long name's first character matches short name */
+ if (io2->ishort && !io1->iprefmatch) {
+ int name1char = (cli->utf8 ? decode_utf8(name1, 0) : (unsigned char) *name1);
+ if (name1char == o2->short_name) {
+ if (io1->ipos && io2->ipos)
+ io1->lmmpos_short = 1;
+ if (io1->ineg && io2->ineg)
+ io1->lmmneg_short = 1;
+ }
+ }
+
+ /* match long name to long name */
+ if (io2->ilong) {
+ const char *name2 = o2->long_name + io2->ilongoff;
+ if (strcmp(name1, name2) == 0)
+ Clp_OptionError(clp, "CLP internal error: duplicate long name %<%s%>", name1);
+ if (io1->ipos && io2->ipos && !strncmp(name1, name2, io1->lmmpos)
+ && (!io1->iprefmatch || strncmp(name1, name2, strlen(name1))))
+ io1->lmmpos = min_different_chars(name1, name2);
+ if (io1->ineg && io2->ineg && !strncmp(name1, name2, io1->lmmneg)
+ && (!io1->iprefmatch || strncmp(name1, name2, strlen(name1))))
+ io1->lmmneg = min_different_chars(name1, name2);
+ }
+ }
+}
+
+static void
+calculate_lmm(Clp_Parser *clp, const Clp_Option *opt, Clp_InternOption *iopt, int nopt)
+{
+ int i, j;
+ for (i = 0; i < nopt; ++i) {
+ iopt[i].lmmpos = iopt[i].lmmneg = 1;
+ iopt[i].lmmpos_short = iopt[i].lmmneg_short = 0;
+ for (j = 0; j < nopt; ++j)
+ compare_options(clp, &opt[i], &iopt[i], &opt[j], &iopt[j]);
+ }
+}
+
+/** @param argc number of arguments
+ * @param argv argument array
+ * @param nopt number of option definitions
+ * @param opt option definition array
+ * @return the parser
+ *
+ * The new Clp_Parser that will parse the arguments in @a argv according to
+ * the option definitions in @a opt.
+ *
+ * The Clp_Parser is created with the following characteristics:
+ *
+ *
+ * - The "-" character introduces short options (Clp_SetOptionChar(clp,
+ * '-', Clp_Short)).
+ * - Clp_ProgramName is set from the first argument in @a argv, if any. The
+ * first argument returned by Clp_Next() will be the second argument in @a
+ * argv. Note that this behavior differs from Clp_SetArguments.
+ * - UTF-8 support is on iff the LANG environment variable contains
+ * one of the substrings "UTF-8", "UTF8", or "utf8". Override this with
+ * Clp_SetUTF8().
+ * - The Clp_ValString, Clp_ValStringNotOption, Clp_ValInt, Clp_ValUnsigned,
+ * Clp_ValLong, Clp_ValUnsignedLong, Clp_ValBool, and Clp_ValDouble types are
+ * installed.
+ * - Errors are reported to standard error.
+ *
+ *
+ * You may also create a Clp_Parser with no arguments or options
+ * (Clp_NewParser(0, 0, 0, 0)) and set the arguments and options
+ * later.
+ *
+ * Returns NULL if there isn't enough memory to construct the parser.
+ *
+ * @note The CLP library will not modify the contents of @a argv or @a opt.
+ * The calling program must not modify @a opt. It may modify @a argv in
+ * limited cases.
+ */
+Clp_Parser *
+Clp_NewParser(int argc, const char * const *argv, int nopt, const Clp_Option *opt)
+{
+ Clp_Parser *clp = (Clp_Parser *)malloc(sizeof(Clp_Parser));
+ Clp_Internal *cli = (Clp_Internal *)malloc(sizeof(Clp_Internal));
+ Clp_InternOption *iopt = (Clp_InternOption *)malloc(sizeof(Clp_InternOption) * nopt);
+ if (cli)
+ cli->valtype = (Clp_ValType *)malloc(sizeof(Clp_ValType) * Clp_InitialValType);
+ if (!clp || !cli || !iopt || !cli->valtype)
+ goto failed;
+
+ clp->option = &clp_option_sentinel[-Clp_Done];
+ clp->negated = 0;
+ clp->have_val = 0;
+ clp->vstr = 0;
+ clp->user_data = 0;
+ clp->internal = cli;
+
+ cli->opt = opt;
+ cli->nopt = nopt;
+ cli->iopt = iopt;
+ cli->opt_generation = 0;
+ cli->error_handler = 0;
+
+ /* Assign program name (now so we can call Clp_OptionError) */
+ if (argc > 0) {
+ const char *slash = strrchr(argv[0], '/');
+ cli->program_name = slash ? slash + 1 : argv[0];
+ } else
+ cli->program_name = 0;
+
+ /* Assign arguments, skipping program name */
+ Clp_SetArguments(clp, argc - 1, argv + 1);
+
+ /* Initialize UTF-8 status and option classes */
+ {
+ char *s = getenv("LANG");
+ cli->utf8 = (s && (strstr(s, "UTF-8") != 0 || strstr(s, "UTF8") != 0
+ || strstr(s, "utf8") != 0));
+ }
+ cli->oclass[0].c = '-';
+ cli->oclass[0].type = Clp_Short;
+ cli->noclass = 1;
+ cli->long1pos = cli->long1neg = 0;
+
+ /* Add default type parsers */
+ cli->nvaltype = 0;
+ Clp_AddType(clp, Clp_ValString, 0, parse_string, 0);
+ Clp_AddType(clp, Clp_ValStringNotOption, Clp_DisallowOptions, parse_string, 0);
+ Clp_AddType(clp, Clp_ValInt, 0, parse_int, (void*) (uintptr_t) 0);
+ Clp_AddType(clp, Clp_ValUnsigned, 0, parse_int, (void*) (uintptr_t) 1);
+ Clp_AddType(clp, Clp_ValLong, 0, parse_int, (void*) (uintptr_t) 2);
+ Clp_AddType(clp, Clp_ValUnsignedLong, 0, parse_int, (void*) (uintptr_t) 3);
+ Clp_AddType(clp, Clp_ValBool, 0, parse_bool, 0);
+ Clp_AddType(clp, Clp_ValDouble, 0, parse_double, 0);
+
+ /* Set options */
+ Clp_SetOptions(clp, nopt, opt);
+
+ return clp;
+
+ failed:
+ if (cli && cli->valtype)
+ free(cli->valtype);
+ if (cli)
+ free(cli);
+ if (clp)
+ free(clp);
+ if (iopt)
+ free(iopt);
+ return 0;
+}
+
+/** @param clp the parser
+ *
+ * All memory associated with @a clp is freed. */
+void
+Clp_DeleteParser(Clp_Parser *clp)
+{
+ int i;
+ Clp_Internal *cli;
+ if (!clp)
+ return;
+
+ cli = clp->internal;
+
+ /* get rid of any string list types */
+ for (i = 0; i < cli->nvaltype; i++)
+ if (cli->valtype[i].func == parse_string_list) {
+ Clp_StringList *clsl = (Clp_StringList *)cli->valtype[i].user_data;
+ free(clsl->items);
+ free(clsl->iopt);
+ free(clsl);
+ }
+
+ free(cli->valtype);
+ free(cli->iopt);
+ free(cli);
+ free(clp);
+}
+
+
+/** @param clp the parser
+ * @param errh error handler function
+ * @return previous error handler function
+ *
+ * The error handler function is called when CLP encounters an error while
+ * parsing the command line. It is called with the arguments "(*errh)(@a
+ * clp, s)", where s is a description of the error terminated by
+ * a newline. The s descriptions produced by CLP itself are prefixed
+ * by the program name, if any. */
+Clp_ErrorHandler
+Clp_SetErrorHandler(Clp_Parser *clp, void (*errh)(Clp_Parser *, const char *))
+{
+ Clp_Internal *cli = clp->internal;
+ Clp_ErrorHandler old = cli->error_handler;
+ cli->error_handler = errh;
+ return old;
+}
+
+/** @param clp the parser
+ * @param utf8 does the parser support UTF-8?
+ * @return previous UTF-8 mode
+ *
+ * In UTF-8 mode, all input strings (arguments and long names for options) are
+ * assumed to be encoded via UTF-8, and all character names
+ * (Clp_SetOptionChar() and short names for options) may cover the whole
+ * Unicode range. Out of UTF-8 mode, all input strings are treated as binary,
+ * and all character names must be unsigned char values.
+ *
+ * Furthermore, error messages in UTF-8 mode may contain Unicode quote
+ * characters. */
+int
+Clp_SetUTF8(Clp_Parser *clp, int utf8)
+{
+ Clp_Internal *cli = clp->internal;
+ int old_utf8 = cli->utf8;
+ cli->utf8 = utf8;
+ calculate_lmm(clp, cli->opt, cli->iopt, cli->nopt);
+ return old_utf8;
+}
+
+/** @param clp the parser
+ * @param c character
+ * @return option character treatment
+ *
+ * Returns an integer specifying how CLP treats arguments that begin
+ * with character @a c. See Clp_SetOptionChar for possibilities.
+ */
+int
+Clp_OptionChar(Clp_Parser *clp, int c)
+{
+ Clp_Internal *cli = clp->internal;
+ int i, oclass = 0;
+ if (cli->noclass > 0 && cli->oclass[0].c == 0)
+ oclass = cli->oclass[0].type;
+ for (i = 0; i < cli->noclass; ++i)
+ if (cli->oclass[i].c == c)
+ oclass = cli->oclass[i].type;
+ return oclass;
+}
+
+/** @param clp the parser
+ * @param c character
+ * @param type option character treatment
+ * @return previous option character treatment, or -1 on error
+ *
+ * @a type specifies how CLP treats arguments that begin with character @a c.
+ * Possibilities are:
+ *
+ *
+ * - Clp_NotOption (or 0)
+ * - The argument cannot be an option.
+ * - Clp_Long
+ * - The argument is a long option.
+ * - Clp_Short
+ * - The argument is a set of short options.
+ * - Clp_Short|Clp_Long
+ * - The argument is either a long option or, if no matching long option is
+ * found, a set of short options.
+ * - Clp_LongNegated
+ * - The argument is a negated long option. For example, after
+ * Clp_SetOptionChar(@a clp, '^', Clp_LongNegated), the argument "^foo" is
+ * equivalent to "--no-foo".
+ * - Clp_ShortNegated
+ * - The argument is a set of negated short options.
+ * - Clp_ShortNegated|Clp_LongNegated
+ * - The argument is either a negated long option or, if no matching long
+ * option is found, a set of negated short options.
+ * - Clp_LongImplicit
+ * - The argument may be a long option, where the character @a c is actually
+ * part of the long option name. For example, after Clp_SetOptionChar(@a clp,
+ * 'f', Clp_LongImplicit), the argument "foo" may be equivalent to
+ * "--foo".
+ *
+ *
+ * In UTF-8 mode, @a c may be any Unicode character. Otherwise, @a c must be
+ * an unsigned char value. The special character 0 assigns @a type to @em
+ * every character.
+ *
+ * It is an error if @a c is out of range, @a type is illegal, or there are
+ * too many character definitions stored in @a clp already. The function
+ * returns -1 on error.
+ *
+ * A double hyphen "--" always introduces a long option. This behavior cannot
+ * currently be changed with Clp_SetOptionChar().
+ */
+int
+Clp_SetOptionChar(Clp_Parser *clp, int c, int type)
+{
+ int i, long1pos, long1neg;
+ int old = Clp_OptionChar(clp, c);
+ Clp_Internal *cli = clp->internal;
+
+ if (type != Clp_NotOption && type != Clp_Short && type != Clp_Long
+ && type != Clp_ShortNegated && type != Clp_LongNegated
+ && type != Clp_LongImplicit && type != (Clp_Short | Clp_Long)
+ && type != (Clp_ShortNegated | Clp_LongNegated))
+ return -1;
+ if (c < 0 || c >= (cli->utf8 ? 0x110000 : 256))
+ return -1;
+
+ if (c == 0)
+ cli->noclass = 0;
+ for (i = 0; i < cli->noclass; ++i)
+ if (cli->oclass[i].c == c)
+ break;
+ if (i == Clp_OclassSize)
+ return -1;
+
+ cli->oclass[i].c = c;
+ cli->oclass[i].type = type;
+ if (cli->noclass == i)
+ cli->noclass = i + 1;
+
+ long1pos = long1neg = 0;
+ for (i = 0; i < cli->noclass; ++i) {
+ if ((cli->oclass[i].type & Clp_Short)
+ && (cli->oclass[i].type & Clp_Long))
+ long1pos = 1;
+ if ((cli->oclass[i].type & Clp_ShortNegated)
+ && (cli->oclass[i].type & Clp_LongNegated))
+ long1neg = 1;
+ }
+
+ if (long1pos != cli->long1pos || long1neg != cli->long1neg) {
+ /* Must recheck option set */
+ cli->long1pos = long1pos;
+ cli->long1neg = long1neg;
+ calculate_lmm(clp, cli->opt, cli->iopt, cli->nopt);
+ }
+
+ return old;
+}
+
+/** @param clp the parser
+ * @param nopt number of option definitions
+ * @param opt option definition array
+ * @return 0 on success, -1 on failure
+ *
+ * Installs the option definitions in @a opt. Future option parsing will
+ * use @a opt to search for options.
+ *
+ * Also checks @a opt's option definitions for validity. "CLP internal
+ * errors" are reported via Clp_OptionError() if:
+ *
+ *
+ * - An option has a negative ID.
+ * - Two different short options have the same name.
+ * - Two different long options have the same name.
+ * - A short and a long option are ambiguous, in that some option character
+ * might introduce either a short or a long option (e.g., Clp_SetOptionChar(@a
+ * clp, '-', Clp_Long|Clp_Short)), and a short name equals a long name.
+ *
+ *
+ * If necessary memory cannot be allocated, this function returns -1 without
+ * modifying the parser.
+ *
+ * @note The CLP library will not modify the contents of @a argv or @a opt.
+ * The calling program must not modify @a opt either until another call to
+ * Clp_SetOptions() or the parser is destroyed.
+ */
+int
+Clp_SetOptions(Clp_Parser *clp, int nopt, const Clp_Option *opt)
+{
+ Clp_Internal *cli = clp->internal;
+ Clp_InternOption *iopt;
+ int i;
+ static unsigned opt_generation = 0;
+
+ if (nopt > cli->nopt) {
+ iopt = (Clp_InternOption *)malloc(sizeof(Clp_InternOption) * nopt);
+ if (!iopt)
+ return -1;
+ free(cli->iopt);
+ cli->iopt = iopt;
+ }
+
+ cli->opt = opt;
+ cli->nopt = nopt;
+ cli->opt_generation = ++opt_generation;
+ iopt = cli->iopt;
+ cli->current_option = -1;
+
+ /* Massage the options to make them usable */
+ for (i = 0; i < nopt; ++i) {
+ memset(&iopt[i], 0, sizeof(iopt[i]));
+
+ /* Ignore negative option_ids, which are internal to CLP */
+ if (opt[i].option_id < 0) {
+ Clp_OptionError(clp, "CLP internal error: option %d has negative option_id", i);
+ iopt[i].ilong = iopt[i].ishort = iopt[i].ipos = iopt[i].ineg = iopt[i].unquoted = 0;
+ continue;
+ }
+
+ /* Set flags based on input flags */
+ iopt[i].ilong = (opt[i].long_name != 0 && opt[i].long_name[0] != 0);
+ iopt[i].ishort = (opt[i].short_name > 0
+ && opt[i].short_name < (cli->utf8 ? 0x110000 : 256));
+ iopt[i].ipos = 1;
+ iopt[i].ineg = (opt[i].flags & Clp_Negate) != 0;
+ iopt[i].imandatory = (opt[i].flags & Clp_Mandatory) != 0;
+ iopt[i].ioptional = (opt[i].flags & Clp_Optional) != 0;
+ iopt[i].iprefmatch = (opt[i].flags & Clp_PreferredMatch) != 0;
+ iopt[i].unquoted = 0;
+ iopt[i].ilongoff = 0;
+
+ /* Enforce invariants */
+ if (opt[i].val_type <= 0)
+ iopt[i].imandatory = iopt[i].ioptional = 0;
+ if (opt[i].val_type > 0 && !iopt[i].ioptional)
+ iopt[i].imandatory = 1;
+
+ /* Options that start with 'no-' should be changed to OnlyNegated */
+ if (iopt[i].ilong && strncmp(opt[i].long_name, "no-", 3) == 0) {
+ iopt[i].ipos = 0;
+ iopt[i].ineg = 1;
+ iopt[i].ilongoff = 3;
+ if (strncmp(opt[i].long_name + 3, "no-", 3) == 0)
+ Clp_OptionError(clp, "CLP internal error: option %d begins with \"no-no-\"", i);
+ } else if (opt[i].flags & Clp_OnlyNegated) {
+ iopt[i].ipos = 0;
+ iopt[i].ineg = 1;
+ }
+ }
+
+ /* Check option set */
+ calculate_lmm(clp, opt, iopt, nopt);
+
+ return 0;
+}
+
+/** @param clp the parser
+ * @param argc number of arguments
+ * @param argv argument array
+ *
+ * Installs the arguments in @a argv for parsing. Future option parsing will
+ * analyze @a argv.
+ *
+ * Unlike Clp_NewParser(), this function does not treat @a argv[0] specially.
+ * The first subsequent call to Clp_Next() will analyze @a argv[0].
+ *
+ * This function also sets option processing to on, as by
+ * Clp_SetOptionProcessing(@a clp, 1).
+ *
+ * @note The CLP library will not modify the contents of @a argv. The calling
+ * program should not generally modify the element of @a argv that CLP is
+ * currently analyzing.
+ */
+void
+Clp_SetArguments(Clp_Parser *clp, int argc, const char * const *argv)
+{
+ Clp_Internal *cli = clp->internal;
+
+ cli->argc = argc + 1;
+ cli->argv = argv - 1;
+
+ cli->is_short = 0;
+ cli->whole_negated = 0;
+ cli->option_processing = 1;
+ cli->current_option = -1;
+}
+
+
+/** @param clp the parser
+ * @param on whether to search for options
+ * @return previous option processing setting
+ *
+ * When option processing is off, every call to Clp_Next() returns
+ * Clp_NotOption. By default the option "--" turns off option
+ * processing and is otherwise ignored.
+ */
+int
+Clp_SetOptionProcessing(Clp_Parser *clp, int on)
+{
+ Clp_Internal *cli = clp->internal;
+ int old = cli->option_processing;
+ cli->option_processing = on;
+ return old;
+}
+
+
+/*******
+ * functions for Clp_Option lists
+ **/
+
+/* the ever-glorious argcmp */
+
+static int
+argcmp(const char *ref, const char *arg, int min_match, int fewer_dashes)
+ /* Returns 0 if ref and arg don't match.
+ Returns -1 if ref and arg match, but fewer than min_match characters.
+ Returns len if ref and arg match min_match or more characters;
+ len is the number of characters that matched in arg.
+ Allows arg to contain fewer dashes than ref iff fewer_dashes != 0.
+
+ Examples:
+ argcmp("x", "y", 1, 0) --> 0 / just plain wrong
+ argcmp("a", "ax", 1, 0) --> 0 / ...even though min_match == 1
+ and the 1st chars match
+ argcmp("box", "bo", 3, 0) --> -1 / ambiguous
+ argcmp("cat", "c=3", 1, 0) --> 1 / handles = arguments
+ */
+{
+ const char *refstart = ref;
+ const char *argstart = arg;
+ assert(min_match > 0);
+
+ compare:
+ while (*ref && *arg && *arg != '=' && *ref == *arg)
+ ref++, arg++;
+
+ /* Allow arg to contain fewer dashes than ref */
+ if (fewer_dashes && *ref == '-' && ref[1] && ref[1] == *arg) {
+ ref++;
+ goto compare;
+ }
+
+ if (*arg && *arg != '=')
+ return 0;
+ else if (ref - refstart < min_match)
+ return -1;
+ else
+ return arg - argstart;
+}
+
+static int
+find_prefix_opt(Clp_Parser *clp, const char *arg,
+ int nopt, const Clp_Option *opt,
+ const Clp_InternOption *iopt,
+ int *ambiguous, int *ambiguous_values)
+ /* Looks for an unambiguous match of 'arg' against one of the long
+ options in 'opt'. Returns positive if it finds one; otherwise, returns
+ -1 and possibly changes 'ambiguous' and 'ambiguous_values' to keep
+ track of at most MAX_AMBIGUOUS_VALUES possibilities. */
+{
+ int i, fewer_dashes = 0, first_ambiguous = *ambiguous;
+ int negated = clp && clp->negated;
+ int first_charlen = (clp ? clp_utf8_charlen(clp->internal, arg) : 1);
+
+ retry:
+ for (i = 0; i < nopt; i++) {
+ int len, lmm;
+ if (!iopt[i].ilong || (negated ? !iopt[i].ineg : !iopt[i].ipos))
+ continue;
+
+ lmm = (negated ? iopt[i].lmmneg : iopt[i].lmmpos);
+ if (clp && clp->internal->could_be_short
+ && (negated ? iopt[i].lmmneg_short : iopt[i].lmmpos_short))
+ lmm = (first_charlen >= lmm ? first_charlen + 1 : lmm);
+ len = argcmp(opt[i].long_name + iopt[i].ilongoff, arg, lmm, fewer_dashes);
+ if (len > 0)
+ return i;
+ else if (len < 0) {
+ if (*ambiguous < MAX_AMBIGUOUS_VALUES)
+ ambiguous_values[*ambiguous] = i;
+ ++(*ambiguous);
+ }
+ }
+
+ /* If there were no partial matches, try again with fewer_dashes true */
+ if (*ambiguous == first_ambiguous && !fewer_dashes) {
+ fewer_dashes = 1;
+ goto retry;
+ }
+
+ return -1;
+}
+
+
+/*****
+ * Argument parsing
+ **/
+
+static int
+val_type_binsearch(Clp_Internal *cli, int val_type)
+{
+ unsigned l = 0, r = cli->nvaltype;
+ while (l < r) {
+ unsigned m = l + (r - l) / 2;
+ if (cli->valtype[m].val_type == val_type)
+ return m;
+ else if (cli->valtype[m].val_type < val_type)
+ l = m + 1;
+ else
+ r = m;
+ }
+ return l;
+}
+
+/** @param clp the parser
+ * @param val_type value type ID
+ * @param flags value type flags
+ * @param parser parser function
+ * @param user_data user data for @a parser function
+ * @return 0 on success, -1 on failure
+ *
+ * Defines argument type @a val_type in parser @a clp. The parsing function
+ * @a parser will be passed argument values for type @a val_type. It should
+ * parse the argument into values (usually in @a clp->val, but sometimes
+ * elsewhere), report errors if necessary, and return whether the parse was
+ * successful.
+ *
+ * Any prior argument parser match @a val_type is removed. @a val_type must
+ * be greater than zero.
+ *
+ * @a flags specifies additional parsing flags. At the moment the only
+ * relevant flag is Clp_DisallowOptions, which means that separated values
+ * must not look like options. For example, assume argument
+ * --a/-a has mandatory value type Clp_ValStringNotOption
+ * (which has Clp_DisallowOptions). Then:
+ *
+ *
+ * - --a=--b will parse with value --b.
+ * - -a--b will parse with value --b.
+ * - --a --b will not parse, since the mandatory value looks like
+ * an option.
+ * - -a --b will not parse, since the mandatory value looks like
+ * an option.
+ *
+ */
+int
+Clp_AddType(Clp_Parser *clp, int val_type, int flags,
+ Clp_ValParseFunc parser, void *user_data)
+{
+ Clp_Internal *cli = clp->internal;
+ int vtpos;
+
+ if (val_type <= 0 || !parser)
+ return -1;
+
+ vtpos = val_type_binsearch(cli, val_type);
+
+ if (vtpos == cli->nvaltype || cli->valtype[vtpos].val_type != val_type) {
+ if (cli->nvaltype != 0 && (cli->nvaltype % Clp_InitialValType) == 0) {
+ Clp_ValType *new_valtype =
+ (Clp_ValType *) realloc(cli->valtype, sizeof(Clp_ValType) * (cli->nvaltype + Clp_InitialValType));
+ if (!new_valtype)
+ return -1;
+ cli->valtype = new_valtype;
+ }
+ memmove(&cli->valtype[vtpos + 1], &cli->valtype[vtpos],
+ sizeof(Clp_ValType) * (cli->nvaltype - vtpos));
+ cli->nvaltype++;
+ cli->valtype[vtpos].func = 0;
+ }
+
+ if (cli->valtype[vtpos].func == parse_string_list) {
+ Clp_StringList *clsl = (Clp_StringList *) cli->valtype[vtpos].user_data;
+ free(clsl->items);
+ free(clsl->iopt);
+ free(clsl);
+ }
+
+ cli->valtype[vtpos].val_type = val_type;
+ cli->valtype[vtpos].func = parser;
+ cli->valtype[vtpos].flags = flags;
+ cli->valtype[vtpos].user_data = user_data;
+ return 0;
+}
+
+
+/*******
+ * Default argument parsers
+ **/
+
+static int
+parse_string(Clp_Parser *clp, const char *arg, int complain, void *user_data)
+{
+ (void)complain, (void)user_data;
+ clp->val.s = arg;
+ return 1;
+}
+
+static int
+parse_int(Clp_Parser* clp, const char* arg, int complain, void* user_data)
+{
+ const char *val;
+ uintptr_t type = (uintptr_t) user_data;
+ if (*arg == 0 || isspace((unsigned char) *arg)
+ || ((type & 1) && *arg == '-'))
+ val = arg;
+ else if (type & 1) { /* unsigned */
+#if HAVE_STRTOUL
+ clp->val.ul = strtoul(arg, (char **) &val, 0);
+#else
+ /* don't bother really trying to do it right */
+ if (arg[0] == '-')
+ val = arg;
+ else
+ clp->val.l = strtol(arg, (char **) &val, 0);
+#endif
+ } else
+ clp->val.l = strtol(arg, (char **) &val, 0);
+ if (type <= 1)
+ clp->val.u = (unsigned) clp->val.ul;
+ if (*arg != 0 && *val == 0)
+ return 1;
+ else {
+ if (complain) {
+ const char *message = (type & 1)
+ ? "%<%O%> expects a nonnegative integer, not %<%s%>"
+ : "%<%O%> expects an integer, not %<%s%>";
+ Clp_OptionError(clp, message, arg);
+ }
+ return 0;
+ }
+}
+
+static int
+parse_double(Clp_Parser *clp, const char *arg, int complain, void *user_data)
+{
+ const char *val;
+ (void)user_data;
+ if (*arg == 0 || isspace((unsigned char) *arg))
+ val = arg;
+ else
+ clp->val.d = strtod(arg, (char **) &val);
+ if (*arg != 0 && *val == 0)
+ return 1;
+ else {
+ if (complain)
+ Clp_OptionError(clp, "%<%O%> expects a real number, not %<%s%>", arg);
+ return 0;
+ }
+}
+
+static int
+parse_bool(Clp_Parser *clp, const char *arg, int complain, void *user_data)
+{
+ int i;
+ char lcarg[6];
+ (void)user_data;
+ if (strlen(arg) > 5 || strchr(arg, '=') != 0)
+ goto error;
+
+ for (i = 0; arg[i] != 0; i++)
+ lcarg[i] = tolower((unsigned char) arg[i]);
+ lcarg[i] = 0;
+
+ if (argcmp("yes", lcarg, 1, 0) > 0
+ || argcmp("true", lcarg, 1, 0) > 0
+ || argcmp("1", lcarg, 1, 0) > 0) {
+ clp->val.i = 1;
+ return 1;
+ } else if (argcmp("no", lcarg, 1, 0) > 0
+ || argcmp("false", lcarg, 1, 0) > 0
+ || argcmp("1", lcarg, 1, 0) > 0) {
+ clp->val.i = 0;
+ return 1;
+ }
+
+ error:
+ if (complain)
+ Clp_OptionError(clp, "%<%O%> expects a true-or-false value, not %<%s%>", arg);
+ return 0;
+}
+
+
+/*****
+ * Clp_AddStringListType
+ **/
+
+static int
+parse_string_list(Clp_Parser *clp, const char *arg, int complain, void *user_data)
+{
+ Clp_StringList *sl = (Clp_StringList *)user_data;
+ int idx, ambiguous = 0;
+ int ambiguous_values[MAX_AMBIGUOUS_VALUES + 1];
+
+ /* actually look for a string value */
+ idx = find_prefix_opt
+ (0, arg, sl->nitems, sl->items, sl->iopt,
+ &ambiguous, ambiguous_values);
+ if (idx >= 0) {
+ clp->val.i = sl->items[idx].option_id;
+ if (sl->val_long)
+ clp->val.l = clp->val.i;
+ return 1;
+ }
+
+ if (sl->allow_int) {
+ if (parse_int(clp, arg, 0, (void*) (uintptr_t) (sl->val_long ? 2 : 0)))
+ return 1;
+ }
+
+ if (complain) {
+ const char *complaint = (ambiguous ? "ambiguous" : "invalid");
+ if (!ambiguous) {
+ ambiguous = sl->nitems_invalid_report;
+ for (idx = 0; idx < ambiguous; idx++)
+ ambiguous_values[idx] = idx;
+ }
+ return ambiguity_error
+ (clp, ambiguous, ambiguous_values, sl->items, sl->iopt,
+ "", "option %<%V%> is %s", complaint);
+ } else
+ return 0;
+}
+
+
+static int
+finish_string_list(Clp_Parser *clp, int val_type, int flags,
+ Clp_Option *items, int nitems, int itemscap)
+{
+ int i, niitems = nitems + (flags & Clp_AllowNumbers ? 1 : 0);
+ Clp_StringList *clsl = (Clp_StringList *)malloc(sizeof(Clp_StringList));
+ Clp_InternOption *iopt = (Clp_InternOption *)malloc(sizeof(Clp_InternOption) * niitems);
+ if (!clsl || !iopt)
+ goto error;
+
+ clsl->items = items;
+ clsl->iopt = iopt;
+ clsl->nitems = nitems;
+ clsl->allow_int = (flags & Clp_AllowNumbers) != 0;
+ clsl->val_long = (flags & Clp_StringListLong) != 0;
+
+ memset(iopt, 0, sizeof(Clp_InternOption) * niitems);
+ for (i = 0; i < niitems; i++) {
+ iopt[i].ilong = iopt[i].ipos = 1;
+ iopt[i].ishort = iopt[i].ineg = iopt[i].ilongoff = iopt[i].iprefmatch = iopt[i].unquoted = 0;
+ }
+ calculate_lmm(clp, items, iopt, nitems);
+
+ if (nitems < MAX_AMBIGUOUS_VALUES && nitems < itemscap && clsl->allow_int) {
+ items[nitems].long_name = "any integer";
+ iopt[nitems].unquoted = 1;
+ clsl->nitems_invalid_report = nitems + 1;
+ } else if (nitems > MAX_AMBIGUOUS_VALUES + 1)
+ clsl->nitems_invalid_report = MAX_AMBIGUOUS_VALUES + 1;
+ else
+ clsl->nitems_invalid_report = nitems;
+
+ if (Clp_AddType(clp, val_type, 0, parse_string_list, clsl) >= 0)
+ return 0;
+
+ error:
+ if (clsl)
+ free(clsl);
+ if (iopt)
+ free(iopt);
+ return -1;
+}
+
+/** @param clp the parser
+ * @param val_type value type ID
+ * @param flags string list flags
+ * @return 0 on success, -1 on failure
+ *
+ * Defines argument type @a val_type in parser @a clp. The parsing function
+ * sets @a clp->val.i to an integer. The value string is matched against
+ * strings provided in the ellipsis arguments. For example, the
+ * Clp_AddStringListType() call below has the same effect as the
+ * Clp_AddStringListTypeVec() call:
+ *
+ * For example:
+ * @code
+ * Clp_AddStringListType(clp, 100, Clp_AllowNumbers, "cat", 1,
+ * "cattle", 2, "dog", 3, (const char *) NULL);
+ *
+ * const char * const strs[] = { "cat", "cattle", "dog" };
+ * const int vals[] = { 1, 2, 3 };
+ * Clp_AddStringListTypeVec(clp, 100, Clp_AllowNumbers, 3, strs, vals);
+ * @endcode
+ *
+ * @note The CLP library will not modify any of the passed-in strings. The
+ * calling program must not modify or free them either until the parser is
+ * destroyed.
+ */
+int
+Clp_AddStringListType(Clp_Parser *clp, int val_type, int flags, ...)
+{
+ int nitems = 0;
+ int itemscap = 5;
+ Clp_Option *items = (Clp_Option *)malloc(sizeof(Clp_Option) * itemscap);
+
+ va_list val;
+ va_start(val, flags);
+
+ if (!items)
+ goto error;
+
+ /* slurp up the arguments */
+ while (1) {
+ int value;
+ char *name = va_arg(val, char *);
+ if (!name)
+ break;
+ if (flags & Clp_StringListLong) {
+ long lvalue = va_arg(val, long);
+ value = (int) lvalue;
+ assert(value == lvalue);
+ } else
+ value = va_arg(val, int);
+
+ if (nitems >= itemscap) {
+ Clp_Option *new_items;
+ itemscap *= 2;
+ new_items = (Clp_Option *)realloc(items, sizeof(Clp_Option) * itemscap);
+ if (!new_items)
+ goto error;
+ items = new_items;
+ }
+
+ items[nitems].long_name = name;
+ items[nitems].option_id = value;
+ items[nitems].flags = 0;
+ nitems++;
+ }
+
+ va_end(val);
+ if (finish_string_list(clp, val_type, flags, items, nitems, itemscap) >= 0)
+ return 0;
+
+ error:
+ va_end(val);
+ if (items)
+ free(items);
+ return -1;
+}
+
+/** @param clp the parser
+ * @param val_type value type ID
+ * @param flags string list flags
+ * @param nstrs number of strings in list
+ * @param strs array of strings
+ * @param vals array of values
+ * @return 0 on success, -1 on failure
+ *
+ * Defines argument type @a val_type in parser @a clp. The parsing function
+ * sets @a clp->val.i to an integer. The value string is matched against the
+ * @a strs. If there's a unique match, the corresponding entry from @a vals
+ * is returned. Unique prefix matches also work. Finally, if @a flags
+ * contains the Clp_AllowNumbers flag, then integers are also accepted.
+ *
+ * For example:
+ * @code
+ * const char * const strs[] = { "cat", "cattle", "dog" };
+ * const int vals[] = { 1, 2, 3 };
+ * Clp_AddStringListTypeVec(clp, 100, Clp_AllowNumbers, 3, strs, vals);
+ * @endcode
+ *
+ * Say that option --animal takes value type 100. Then:
+ *
+ *
+ * - --animal=cat will succeed and set @a clp->val.i = 1.
+ * - --animal=cattle will succeed and set @a clp->val.i = 2.
+ * - --animal=dog will succeed and set @a clp->val.i = 3.
+ * - --animal=d will succeed and set @a clp->val.i = 3.
+ * - --animal=c will fail, since c is ambiguous.
+ * - --animal=4 will succeed and set @a clp->val.i = 4.
+ *
+ *
+ * @note The CLP library will not modify the contents of @a strs or @a vals.
+ * The calling program can modify the @a strs array, but the actual strings
+ * (for instance, @a strs[0] and @a strs[1]) must not be modified or freed
+ * until the parser is destroyed.
+ */
+int
+Clp_AddStringListTypeVec(Clp_Parser *clp, int val_type, int flags,
+ int nstrs, const char * const *strs,
+ const int *vals)
+ /* An alternate way to make a string list type. See Clp_AddStringListType
+ for the basics; this coalesces the strings and values into two arrays,
+ rather than spreading them out into a variable argument list. */
+{
+ int i;
+ int itemscap = (nstrs < 5 ? 5 : nstrs);
+ Clp_Option *items = (Clp_Option *)malloc(sizeof(Clp_Option) * itemscap);
+ if (!items)
+ return -1;
+
+ /* copy over items */
+ for (i = 0; i < nstrs; i++) {
+ items[i].long_name = strs[i];
+ items[i].option_id = vals[i];
+ items[i].flags = 0;
+ }
+
+ if (finish_string_list(clp, val_type, flags, items, nstrs, itemscap) >= 0)
+ return 0;
+ else {
+ free(items);
+ return -1;
+ }
+}
+
+
+/*******
+ * Returning information
+ **/
+
+const char *
+Clp_ProgramName(Clp_Parser *clp)
+{
+ return clp->internal->program_name;
+}
+
+/** @param clp the parser
+ * @param name new program name
+ * @return previous program name
+ *
+ * The calling program should not modify or free @a name until @a clp itself
+ * is destroyed. */
+const char *
+Clp_SetProgramName(Clp_Parser *clp, const char *name)
+{
+ const char *old = clp->internal->program_name;
+ clp->internal->program_name = name;
+ return old;
+}
+
+
+/******
+ * Clp_ParserStates
+ **/
+
+/** @return the parser state
+ *
+ * A Clp_ParserState object can store a parsing state of a Clp_Parser object.
+ * This state specifies exactly how far the Clp_Parser has gotten in parsing
+ * an argument list. The Clp_SaveParser() and Clp_RestoreParser() functions
+ * can be used to save this state and then restore it later, allowing a
+ * Clp_Parser to switch among argument lists.
+ *
+ * The initial state is empty, in that after Clp_RestoreParser(clp, state),
+ * Clp_Next(clp) would return Clp_Done.
+ *
+ * Parser states can be saved and restored among different parser objects.
+ *
+ * @sa Clp_DeleteParserState, Clp_SaveParser, Clp_RestoreParser
+ */
+Clp_ParserState *
+Clp_NewParserState(void)
+{
+ Clp_ParserState *state = (Clp_ParserState *)malloc(sizeof(Clp_ParserState));
+ if (state) {
+ state->argv = 0;
+ state->argc = 0;
+ state->option_chars[0] = 0;
+ state->xtext = 0;
+ state->option_processing = 0;
+ state->opt_generation = 0;
+ state->current_option = -1;
+ state->is_short = 0;
+ state->whole_negated = 0;
+ state->current_short = 0;
+ state->negated_by_no = 0;
+ }
+ return state;
+}
+
+/** @param state parser state
+ *
+ * The memory associated with @a state is freed.
+ */
+void
+Clp_DeleteParserState(Clp_ParserState *state)
+{
+ free(state);
+}
+
+/** @param clp the parser
+ * @param state parser state
+ * @sa Clp_NewParserState, Clp_RestoreParser
+ */
+void
+Clp_SaveParser(const Clp_Parser *clp, Clp_ParserState *state)
+{
+ Clp_Internal *cli = clp->internal;
+ state->argv = cli->argv;
+ state->argc = cli->argc;
+ memcpy(state->option_chars, cli->option_chars, Clp_OptionCharsSize);
+ state->xtext = cli->xtext;
+
+ state->option_processing = cli->option_processing;
+ state->opt_generation = cli->opt_generation;
+ state->current_option = cli->current_option;
+ state->is_short = cli->is_short;
+ state->whole_negated = cli->whole_negated;
+ state->current_short = cli->current_short;
+ state->negated_by_no = cli->negated_by_no;
+}
+
+
+/** @param clp the parser
+ * @param state parser state
+ *
+ * The parser state in @a state is restored into @a clp. The next call to
+ * Clp_Next() will return the same result as it would have at the time @a
+ * state was saved (probably by Clp_SaveParser(@a clp, @a state)).
+ *
+ * A parser state contains information about arguments (argc and argv; see
+ * Clp_SetArguments()) and option processing (Clp_SetOptionProcessing()), but
+ * not about options (Clp_SetOptions()). Changes to options and value types
+ * are preserved across Clp_RestoreParser().
+ *
+ * @sa Clp_NewParserState, Clp_SaveParser
+ */
+void
+Clp_RestoreParser(Clp_Parser *clp, const Clp_ParserState *state)
+{
+ Clp_Internal *cli = clp->internal;
+ cli->argv = state->argv;
+ cli->argc = state->argc;
+ memcpy(cli->option_chars, state->option_chars, Clp_OptionCharsSize);
+ cli->xtext = state->xtext;
+ cli->option_processing = state->option_processing;
+ cli->is_short = state->is_short;
+ cli->whole_negated = state->whole_negated;
+ cli->current_short = state->current_short;
+ cli->negated_by_no = state->negated_by_no;
+ if (cli->opt_generation == state->opt_generation)
+ cli->current_option = state->current_option;
+ else
+ cli->current_option = -1;
+}
+
+
+/*******
+ * Clp_Next and its helpers
+ **/
+
+static void
+set_option_text(Clp_Internal *cli, const char *text, int n_option_chars)
+{
+ assert(n_option_chars < Clp_OptionCharsSize);
+ memcpy(cli->option_chars, text, n_option_chars);
+ cli->option_chars[n_option_chars] = 0;
+ cli->xtext = text + n_option_chars;
+}
+
+static int
+get_oclass(Clp_Parser *clp, const char *text, int *ocharskip)
+{
+ int c;
+ if (clp->internal->utf8) {
+ const char *s;
+ c = decode_utf8(text, &s);
+ *ocharskip = s - text;
+ } else {
+ c = (unsigned char) text[0];
+ *ocharskip = 1;
+ }
+ return Clp_OptionChar(clp, c);
+}
+
+static int
+next_argument(Clp_Parser *clp, int want_argument)
+ /* Moves clp to the next argument.
+ Returns 1 if it finds another option.
+ Returns 0 if there aren't any more arguments.
+ Returns 0, sets clp->have_val = 1, and sets clp->vstr to the argument
+ if the next argument isn't an option.
+ If want_argument > 0, it'll look for an argument.
+ want_argument == 1: Accept arguments that start with Clp_NotOption
+ or Clp_LongImplicit.
+ want_argument == 2: Accept ALL arguments.
+
+ Where is the option stored when this returns?
+ Well, cli->argv[0] holds the whole of the next command line argument.
+ cli->option_chars holds a string: what characters began the option?
+ It is generally "-" or "--".
+ cli->text holds the text of the option:
+ for short options, cli->text[0] is the relevant character;
+ for long options, cli->text holds the rest of the option. */
+{
+ Clp_Internal *cli = clp->internal;
+ const char *text;
+ int oclass, ocharskip;
+
+ /* clear relevant flags */
+ clp->have_val = 0;
+ clp->vstr = 0;
+ cli->could_be_short = 0;
+
+ /* if we're in a string of short options, move up one char in the string */
+ if (cli->is_short) {
+ cli->xtext += clp_utf8_charlen(cli, cli->xtext);
+ if (cli->xtext[0] == 0)
+ cli->is_short = 0;
+ else if (want_argument > 0) {
+ /* handle -O[=]argument case */
+ clp->have_val = 1;
+ if (cli->xtext[0] == '=')
+ clp->vstr = cli->xtext + 1;
+ else
+ clp->vstr = cli->xtext;
+ cli->is_short = 0;
+ return 0;
+ }
+ }
+
+ /* if in short options, we're all set */
+ if (cli->is_short)
+ return 1;
+
+ /** if not in short options, move to the next argument **/
+ cli->whole_negated = 0;
+ cli->xtext = 0;
+
+ if (cli->argc <= 1)
+ return 0;
+
+ cli->argc--;
+ cli->argv++;
+ text = cli->argv[0];
+
+ if (want_argument > 1)
+ goto not_option;
+
+ if (text[0] == '-' && text[1] == '-') {
+ oclass = Clp_DoubledLong;
+ ocharskip = 2;
+ } else
+ oclass = get_oclass(clp, text, &ocharskip);
+
+ /* If this character could introduce either a short or a long option,
+ try a long option first, but remember that short's a possibility for
+ later. */
+ if ((oclass & (Clp_Short | Clp_ShortNegated))
+ && (oclass & (Clp_Long | Clp_LongNegated))) {
+ oclass &= ~(Clp_Short | Clp_ShortNegated);
+ if (text[ocharskip])
+ cli->could_be_short = 1;
+ }
+
+ switch (oclass) {
+
+ case Clp_Short:
+ cli->is_short = 1;
+ goto check_singleton;
+
+ case Clp_ShortNegated:
+ cli->is_short = 1;
+ cli->whole_negated = 1;
+ goto check_singleton;
+
+ case Clp_Long:
+ goto check_singleton;
+
+ case Clp_LongNegated:
+ cli->whole_negated = 1;
+ goto check_singleton;
+
+ check_singleton:
+ /* For options introduced with one character, option-char,
+ '[option-char]' alone is NOT an option. */
+ if (!text[ocharskip])
+ goto not_option;
+ set_option_text(cli, text, ocharskip);
+ break;
+
+ case Clp_LongImplicit:
+ /* LongImplict: option_chars == "" (since all chars are part of the
+ option); restore head -> text of option */
+ if (want_argument > 0)
+ goto not_option;
+ set_option_text(cli, text, 0);
+ break;
+
+ case Clp_DoubledLong:
+ set_option_text(cli, text, ocharskip);
+ break;
+
+ not_option:
+ case Clp_NotOption:
+ cli->is_short = 0;
+ clp->have_val = 1;
+ clp->vstr = text;
+ return 0;
+
+ default:
+ assert(0 /* CLP misconfiguration: bad option type */);
+
+ }
+
+ return 1;
+}
+
+
+static void
+switch_to_short_argument(Clp_Parser *clp)
+{
+ Clp_Internal *cli = clp->internal;
+ const char *text = cli->argv[0];
+ int ocharskip, oclass = get_oclass(clp, text, &ocharskip);
+ assert(cli->could_be_short);
+ cli->is_short = 1;
+ cli->whole_negated = !!(oclass & Clp_ShortNegated);
+ set_option_text(cli, cli->argv[0], ocharskip);
+}
+
+
+static int
+find_long(Clp_Parser *clp, const char *arg)
+ /* If arg corresponds to one of clp's options, finds that option &
+ returns it. If any argument is given after an = sign in arg, sets
+ clp->have_val = 1 and clp->vstr to that argument. Sets cli->ambiguous
+ to 1 iff there was no match because the argument was ambiguous. */
+{
+ Clp_Internal *cli = clp->internal;
+ int optno, len, lmm;
+ const Clp_Option *opt = cli->opt;
+ const Clp_InternOption *iopt;
+ int first_negative_ambiguous;
+
+ /* Look for a normal option. */
+ optno = find_prefix_opt
+ (clp, arg, cli->nopt, opt, cli->iopt,
+ &cli->ambiguous, cli->ambiguous_values);
+ if (optno >= 0)
+ goto worked;
+
+ /* If we can't find it, look for a negated option. */
+ /* I know this is silly, but it makes me happy to accept
+ --no-no-option as a double negative synonym for --option. :) */
+ first_negative_ambiguous = cli->ambiguous;
+ while (arg[0] == 'n' && arg[1] == 'o' && arg[2] == '-') {
+ arg += 3;
+ clp->negated = !clp->negated;
+ optno = find_prefix_opt
+ (clp, arg, cli->nopt, opt, cli->iopt,
+ &cli->ambiguous, cli->ambiguous_values);
+ if (optno >= 0)
+ goto worked;
+ }
+
+ /* No valid option was found; return 0. Mark the ambiguous values found
+ through '--no' by making them negative. */
+ {
+ int i, max = cli->ambiguous;
+ if (max > MAX_AMBIGUOUS_VALUES) max = MAX_AMBIGUOUS_VALUES;
+ for (i = first_negative_ambiguous; i < max; i++)
+ cli->ambiguous_values[i] = -cli->ambiguous_values[i] - 1;
+ }
+ return -1;
+
+ worked:
+ iopt = &cli->iopt[optno];
+ lmm = (clp->negated ? iopt->lmmneg : iopt->lmmpos);
+ if (cli->could_be_short
+ && (clp->negated ? iopt->lmmneg_short : iopt->lmmpos_short)) {
+ int first_charlen = clp_utf8_charlen(cli, arg);
+ lmm = (first_charlen >= lmm ? first_charlen + 1 : lmm);
+ }
+ len = argcmp(opt[optno].long_name + iopt->ilongoff, arg, lmm, 1);
+ assert(len > 0);
+ if (arg[len] == '=') {
+ clp->have_val = 1;
+ clp->vstr = arg + len + 1;
+ }
+ return optno;
+}
+
+
+static int
+find_short(Clp_Parser *clp, const char *text)
+ /* If short_name corresponds to one of clp's options, returns it. */
+{
+ Clp_Internal *cli = clp->internal;
+ const Clp_Option *opt = cli->opt;
+ const Clp_InternOption *iopt = cli->iopt;
+ int i, c;
+ if (clp->internal->utf8)
+ c = decode_utf8(text, 0);
+ else
+ c = (unsigned char) *text;
+
+ for (i = 0; i < cli->nopt; i++)
+ if (iopt[i].ishort && opt[i].short_name == c
+ && (!clp->negated || iopt[i].ineg)) {
+ clp->negated = clp->negated || !iopt[i].ipos;
+ return i;
+ }
+
+ return -1;
+}
+
+
+/** @param clp the parser
+ * @return option ID of next option
+ *
+ * Parse the next argument from the argument list, store information about
+ * that argument in the fields of @a clp, and return the option's ID.
+ *
+ * If an argument was successfully parsed, that option's ID is returned.
+ * Other possible return values are:
+ *
+ *
+ * - Clp_Done
+ * - There are no more arguments.
+ * - Clp_NotOption
+ * - The next argument was not an option. The argument's text is @a
+ * clp->vstr (and @a clp->val.s).
+ * - Clp_BadOption
+ * - The next argument was a bad option: either an option that wasn't
+ * understood, or an option lacking a required value, or an option whose value
+ * couldn't be parsed. The option has been skipped.
+ * - Clp_Error
+ * - There was an internal error. This should never occur unless a user
+ * messes with, for example, a Clp_Option array.
+ *
+ *
+ * The fields of @a clp are set as follows.
+ *
+ *
+ * - negated
+ * - 1 if the option was negated, 0 if it wasn't.
+ * - have_val
+ * - 1 if the option had a value, 0 if it didn't. Note that negated options
+ * are not allowed to have values.
+ * - vstr
+ * - The value string, if any. NULL if there was no value.
+ * - val
+ * - An option's value type will parse the value string into this
+ * union.
+ *
+ *
+ * The parsed argument is shifted off the argument list, so that sequential
+ * calls to Clp_Next() step through the argument list.
+ */
+int
+Clp_Next(Clp_Parser *clp)
+{
+ Clp_Internal *cli = clp->internal;
+ int optno;
+ const Clp_Option *opt;
+ Clp_ParserState clpsave;
+ int vtpos, complain;
+
+ /* Set up clp */
+ cli->current_option = -1;
+ cli->ambiguous = 0;
+
+ /* Get the next argument or option */
+ if (!next_argument(clp, cli->option_processing ? 0 : 2)) {
+ clp->val.s = clp->vstr;
+ optno = clp->have_val ? Clp_NotOption : Clp_Done;
+ clp->option = &clp_option_sentinel[-optno];
+ return optno;
+ }
+
+ clp->negated = cli->whole_negated;
+ if (cli->is_short)
+ optno = find_short(clp, cli->xtext);
+ else
+ optno = find_long(clp, cli->xtext);
+
+ /* If there's ambiguity between long & short options, and we couldn't
+ find a long option, look for a short option */
+ if (optno < 0 && cli->could_be_short) {
+ switch_to_short_argument(clp);
+ optno = find_short(clp, cli->xtext);
+ }
+
+ /* If we didn't find an option... */
+ if (optno < 0 || (clp->negated && !cli->iopt[optno].ineg)) {
+ /* default processing for the "--" option: turn off option processing
+ and return the next argument */
+ if (strcmp(cli->argv[0], "--") == 0) {
+ Clp_SetOptionProcessing(clp, 0);
+ return Clp_Next(clp);
+ }
+
+ /* otherwise, report some error or other */
+ if (cli->ambiguous)
+ ambiguity_error(clp, cli->ambiguous, cli->ambiguous_values,
+ cli->opt, cli->iopt, cli->option_chars,
+ "option %<%s%s%> is ambiguous",
+ cli->option_chars, cli->xtext);
+ else if (cli->is_short && !cli->could_be_short)
+ Clp_OptionError(clp, "unrecognized option %<%s%C%>",
+ cli->option_chars, cli->xtext);
+ else
+ Clp_OptionError(clp, "unrecognized option %<%s%s%>",
+ cli->option_chars, cli->xtext);
+
+ clp->option = &clp_option_sentinel[-Clp_BadOption];
+ return Clp_BadOption;
+ }
+
+ /* Set the current option */
+ cli->current_option = optno;
+ cli->current_short = cli->is_short;
+ cli->negated_by_no = clp->negated && !cli->whole_negated;
+
+ /* The no-argument (or should-have-no-argument) case */
+ if (clp->negated
+ || (!cli->iopt[optno].imandatory && !cli->iopt[optno].ioptional)) {
+ if (clp->have_val) {
+ Clp_OptionError(clp, "%<%O%> can%,t take an argument");
+ clp->option = &clp_option_sentinel[-Clp_BadOption];
+ return Clp_BadOption;
+ } else {
+ clp->option = &cli->opt[optno];
+ return cli->opt[optno].option_id;
+ }
+ }
+
+ /* Get an argument if we need one, or if it's optional */
+ /* Sanity-check the argument type. */
+ opt = &cli->opt[optno];
+ if (opt->val_type <= 0) {
+ clp->option = &clp_option_sentinel[-Clp_Error];
+ return Clp_Error;
+ }
+ vtpos = val_type_binsearch(cli, opt->val_type);
+ if (vtpos == cli->nvaltype
+ || cli->valtype[vtpos].val_type != opt->val_type) {
+ clp->option = &clp_option_sentinel[-Clp_Error];
+ return Clp_Error;
+ }
+
+ /* complain == 1 only if the argument was explicitly given,
+ or it is mandatory. */
+ complain = (clp->have_val != 0) || cli->iopt[optno].imandatory;
+ Clp_SaveParser(clp, &clpsave);
+
+ if (cli->iopt[optno].imandatory && !clp->have_val) {
+ /* Mandatory argument case */
+ /* Allow arguments to options to start with a dash, but only if the
+ argument type allows it by not setting Clp_DisallowOptions */
+ int disallow = (cli->valtype[vtpos].flags & Clp_DisallowOptions) != 0;
+ next_argument(clp, disallow ? 1 : 2);
+ if (!clp->have_val) {
+ int got_option = cli->xtext != 0;
+ Clp_RestoreParser(clp, &clpsave);
+ if (got_option)
+ Clp_OptionError(clp, "%<%O%> requires a non-option argument");
+ else
+ Clp_OptionError(clp, "%<%O%> requires an argument");
+ clp->option = &clp_option_sentinel[-Clp_BadOption];
+ return Clp_BadOption;
+ }
+
+ } else if (cli->is_short && !clp->have_val
+ && cli->xtext[clp_utf8_charlen(cli, cli->xtext)])
+ /* The -[option]argument case:
+ Assume that the rest of the current string is the argument. */
+ next_argument(clp, 1);
+
+ /* Parse the argument */
+ clp->option = opt;
+ if (clp->have_val) {
+ Clp_ValType *atr = &cli->valtype[vtpos];
+ if (atr->func(clp, clp->vstr, complain, atr->user_data) <= 0) {
+ /* parser failed */
+ clp->have_val = 0;
+ if (complain) {
+ clp->option = &clp_option_sentinel[-Clp_BadOption];
+ return Clp_BadOption;
+ } else {
+ Clp_RestoreParser(clp, &clpsave);
+ clp->option = opt;
+ }
+ }
+ }
+
+ return opt->option_id;
+}
+
+
+/** @param clp the parser
+ * @param allow_options whether options will be allowed
+ *
+ * Remove and return the next argument from @a clp's argument array. If there
+ * are no arguments left, or if the next argument is an option and @a
+ * allow_options != 0, then returns null.
+ */
+const char *
+Clp_Shift(Clp_Parser *clp, int allow_options)
+ /* Returns the next argument from the argument list without parsing it.
+ If there are no more arguments, returns 0. */
+{
+ Clp_ParserState clpsave;
+ Clp_SaveParser(clp, &clpsave);
+ next_argument(clp, allow_options ? 2 : 1);
+ if (!clp->have_val)
+ Clp_RestoreParser(clp, &clpsave);
+ return clp->vstr;
+}
+
+
+/*******
+ * Clp_OptionError
+ **/
+
+typedef struct Clp_BuildString {
+ char* data;
+ char* pos;
+ char* end_data;
+ char buf[256];
+} Clp_BuildString;
+
+static void build_string_program_prefix(Clp_BuildString* bs,
+ const Clp_Parser* clp);
+
+static void build_string_init(Clp_BuildString* bs, Clp_Parser* clp) {
+ bs->data = bs->pos = bs->buf;
+ bs->end_data = &bs->buf[sizeof(bs->buf)];
+ if (clp)
+ build_string_program_prefix(bs, clp);
+}
+
+static void build_string_cleanup(Clp_BuildString* bs) {
+ if (bs->data != bs->buf)
+ free(bs->data);
+}
+
+static int build_string_grow(Clp_BuildString* bs, size_t want) {
+ size_t ipos = bs->pos - bs->data, ncap;
+ if (!bs->pos)
+ return 0;
+ for (ncap = (bs->end_data - bs->data) << 1; ncap < want; ncap *= 2)
+ /* nada */;
+ if (bs->data == bs->buf) {
+ if ((bs->data = (char*) malloc(ncap)))
+ memcpy(bs->data, bs->buf, bs->pos - bs->buf);
+ } else
+ bs->data = (char*) realloc(bs->data, ncap);
+ if (!bs->data) {
+ bs->pos = bs->end_data = bs->data;
+ return 0;
+ } else {
+ bs->pos = bs->data + ipos;
+ bs->end_data = bs->data + ncap;
+ return 1;
+ }
+}
+
+#define ENSURE_BUILD_STRING(bs, space) \
+ ((((bs)->end_data - (bs)->pos) >= (space)) \
+ || build_string_grow((bs), (bs)->pos - (bs)->data + (space)))
+
+static void
+append_build_string(Clp_BuildString *bs, const char *s, int l)
+{
+ if (l < 0)
+ l = strlen(s);
+ if (ENSURE_BUILD_STRING(bs, l)) {
+ memcpy(bs->pos, s, l);
+ bs->pos += l;
+ }
+}
+
+static void
+build_string_program_prefix(Clp_BuildString* bs, const Clp_Parser* clp)
+{
+ const Clp_Internal* cli = clp->internal;
+ if (cli->program_name && cli->program_name[0]) {
+ append_build_string(bs, cli->program_name, -1);
+ append_build_string(bs, ": ", 2);
+ }
+}
+
+
+const char*
+Clp_QuoteChar(const Clp_Parser* clp, int isclose)
+{
+ if (!clp->internal->utf8) {
+ return "'";
+ } else if (isclose) {
+ return "\342\200\231";
+ } else {
+ return "\342\200\230";
+ }
+}
+
+static void
+Clp_vbsprintf(Clp_Parser *clp, Clp_BuildString *bs,
+ const char *fmt, va_list val)
+{
+ Clp_Internal *cli = clp->internal;
+ const char *percent;
+ int c;
+
+ for (percent = strchr(fmt, '%'); percent; percent = strchr(fmt, '%')) {
+ append_build_string(bs, fmt, percent - fmt);
+ switch (*++percent) {
+
+ case 's': {
+ const char *s = va_arg(val, const char *);
+ append_build_string(bs, s ? s : "(null)", -1);
+ break;
+ }
+
+ case 'C': {
+ const char *s = va_arg(val, const char *);
+ if (cli->utf8)
+ c = decode_utf8(s, 0);
+ else
+ c = (unsigned char) *s;
+ goto char_c;
+ }
+
+ case 'c':
+ c = va_arg(val, int);
+ goto char_c;
+
+ char_c:
+ if (ENSURE_BUILD_STRING(bs, 5)) {
+ if (c >= 32 && c <= 126)
+ *bs->pos++ = c;
+ else if (c < 32) {
+ *bs->pos++ = '^';
+ *bs->pos++ = c + 64;
+ } else if (cli->utf8 && c >= 127 && c < 0x110000) {
+ bs->pos = encode_utf8(bs->pos, 4, c);
+ } else if (c >= 127 && c <= 255) {
+ snprintf(bs->pos, 5, "\\%03o", c & 0xFF);
+ bs->pos += 4;
+ } else {
+ *bs->pos++ = '\\';
+ *bs->pos++ = '?';
+ }
+ }
+ break;
+
+ case 'd': {
+ int d = va_arg(val, int);
+ if (ENSURE_BUILD_STRING(bs, 32)) {
+ bs->pos += snprintf(bs->pos, 32, "%d", d);
+ }
+ break;
+ }
+
+ case 'O':
+ case 'V': {
+ int optno = cli->current_option;
+ const Clp_Option *opt = &cli->opt[optno];
+ if (optno < 0)
+ append_build_string(bs, "(no current option!)", -1);
+ else if (cli->current_short) {
+ append_build_string(bs, cli->option_chars, -1);
+ if (ENSURE_BUILD_STRING(bs, 5)) {
+ if (cli->utf8)
+ bs->pos = encode_utf8(bs->pos, 5, opt->short_name);
+ else
+ *bs->pos++ = opt->short_name;
+ }
+ } else if (cli->negated_by_no) {
+ append_build_string(bs, cli->option_chars, -1);
+ append_build_string(bs, "no-", 3);
+ append_build_string(bs, opt->long_name + cli->iopt[optno].ilongoff, -1);
+ } else {
+ append_build_string(bs, cli->option_chars, -1);
+ append_build_string(bs, opt->long_name + cli->iopt[optno].ilongoff, -1);
+ }
+ if (optno >= 0 && clp->have_val && *percent == 'V') {
+ if (cli->current_short && !cli->iopt[optno].ioptional)
+ append_build_string(bs, " ", 1);
+ else if (!cli->current_short)
+ append_build_string(bs, "=", 1);
+ append_build_string(bs, clp->vstr, -1);
+ }
+ break;
+ }
+
+ case '%':
+ if (ENSURE_BUILD_STRING(bs, 1))
+ *bs->pos++ = '%';
+ break;
+
+ case '<':
+ append_build_string(bs, Clp_QuoteChar(clp, 0), -1);
+ break;
+
+ case ',':
+ case '>':
+ append_build_string(bs, Clp_QuoteChar(clp, 1), -1);
+ break;
+
+ case 0:
+ append_build_string(bs, "%", 1);
+ goto done;
+
+ default:
+ if (ENSURE_BUILD_STRING(bs, 2)) {
+ *bs->pos++ = '%';
+ *bs->pos++ = *percent;
+ }
+ break;
+
+ }
+ fmt = ++percent;
+ }
+
+ done:
+ append_build_string(bs, fmt, -1);
+}
+
+static const char* build_string_text(Clp_BuildString* bs, int report_oom) {
+ if (bs->pos) {
+ *bs->pos = 0;
+ return bs->data;
+ } else if (report_oom)
+ return "out of memory\n";
+ else
+ return NULL;
+}
+
+static void
+do_error(Clp_Parser *clp, Clp_BuildString *bs)
+{
+ const char *text = build_string_text(bs, 1);
+ if (clp->internal->error_handler != 0)
+ (*clp->internal->error_handler)(clp, text);
+ else
+ fputs(text, stderr);
+}
+
+/** @param clp the parser
+ * @param format error format
+ *
+ * Format an error message from @a format and any additional arguments in
+ * the ellipsis. The resulting error string is then printed to standard
+ * error (or passed to the error handler specified by Clp_SetErrorHandler).
+ * Returns the number of characters printed.
+ *
+ * The following format characters are accepted:
+ *
+ *
+ * - %c
+ * - A character (type int). Control characters are printed in
+ * caret notation. If the parser is in UTF-8 mode, the character is formatted
+ * in UTF-8. Otherwise, special characters are printed with backslashes and
+ * octal notation.
+ * - %s
+ * - A string (type const char *).
+ * - %C
+ * - The argument is a string (type const char *). The first
+ * character in this string is printed. If the parser is in UTF-8 mode, this
+ * may involve multiple bytes.
+ * - %d
+ * - An integer (type int). Printed in decimal.
+ * - %O
+ * - The current option. No values are read from the argument list; the
+ * current option is defined in the Clp_Parser object itself.
+ * - %V
+ * - Like %O, but also includes the current value,
+ * if any.
+ * - %%
+ * - Prints a percent character.
+ * - %<
+ * - Prints an open quote string. In UTF-8 mode, prints a left single
+ * quote. Otherwise prints a single quote.
+ * - %>
+ * - Prints a closing quote string. In UTF-8 mode, prints a right single
+ * quote. Otherwise prints a single quote.
+ * - %,
+ * - Prints an apostrophe. In UTF-8 mode, prints a right single quote.
+ * Otherwise prints a single quote.
+ *
+ *
+ * Note that no flag characters, precision, or field width characters are
+ * currently supported.
+ *
+ * @sa Clp_SetErrorHandler
+ */
+int
+Clp_OptionError(Clp_Parser *clp, const char *format, ...)
+{
+ Clp_BuildString bs;
+ va_list val;
+ va_start(val, format);
+ build_string_init(&bs, clp);
+ Clp_vbsprintf(clp, &bs, format, val);
+ append_build_string(&bs, "\n", 1);
+ va_end(val);
+ do_error(clp, &bs);
+ build_string_cleanup(&bs);
+ return bs.pos - bs.data;
+}
+
+/** @param clp the parser
+ * @param f output file
+ * @param format error format
+ *
+ * Format an error message using @a format and additional arguments in the
+ * ellipsis, according to the Clp_OptionError formatting conventions. The
+ * resulting message is written to @a f.
+ *
+ * @sa Clp_OptionError */
+int
+Clp_fprintf(Clp_Parser* clp, FILE* f, const char* format, ...)
+{
+ Clp_BuildString bs;
+ va_list val;
+ va_start(val, format);
+ build_string_init(&bs, NULL);
+ Clp_vbsprintf(clp, &bs, format, val);
+ va_end(val);
+ if (bs.pos != bs.data)
+ fwrite(bs.data, 1, bs.pos - bs.data, f);
+ build_string_cleanup(&bs);
+ return bs.pos - bs.data;
+}
+
+/** @param clp the parser
+ * @param f output file
+ * @param format error format
+ * @param val arguments
+ *
+ * Format an error message using @a format and @a val, according to the
+ * Clp_OptionError formatting conventions. The resulting message is written
+ * to @a f.
+ *
+ * @sa Clp_OptionError */
+int
+Clp_vfprintf(Clp_Parser* clp, FILE* f, const char* format, va_list val)
+{
+ Clp_BuildString bs;
+ build_string_init(&bs, NULL);
+ Clp_vbsprintf(clp, &bs, format, val);
+ if (bs.pos != bs.data)
+ fwrite(bs.data, 1, bs.pos - bs.data, f);
+ build_string_cleanup(&bs);
+ return bs.pos - bs.data;
+}
+
+/** @param clp the parser
+ * @param str output string
+ * @param size size of output string
+ * @param format error format
+ *
+ * Format an error message from @a format and any additional arguments in
+ * the ellipsis, according to the Clp_OptionError formatting conventions.
+ * The resulting string is written to @a str. At most @a size characters are
+ * written to @a str, including a terminating null byte. The return value is
+ * the number of characters that would have been written (excluding the
+ * terminating null byte) if @a size were large enough to contain the entire
+ * string.
+ *
+ * @sa Clp_OptionError */
+int
+Clp_vsnprintf(Clp_Parser* clp, char* str, size_t size,
+ const char* format, va_list val)
+{
+ Clp_BuildString bs;
+ build_string_init(&bs, NULL);
+ Clp_vbsprintf(clp, &bs, format, val);
+ if ((size_t) (bs.pos - bs.data) < size) {
+ memcpy(str, bs.data, bs.pos - bs.data);
+ str[bs.pos - bs.data] = 0;
+ } else {
+ memcpy(str, bs.data, size - 1);
+ str[size - 1] = 0;
+ }
+ build_string_cleanup(&bs);
+ return bs.pos - bs.data;
+}
+
+static int
+ambiguity_error(Clp_Parser *clp, int ambiguous, int *ambiguous_values,
+ const Clp_Option *opt, const Clp_InternOption *iopt,
+ const char *prefix, const char *fmt, ...)
+{
+ Clp_Internal *cli = clp->internal;
+ Clp_BuildString bs;
+ int i;
+ va_list val;
+
+ va_start(val, fmt);
+ build_string_init(&bs, clp);
+ Clp_vbsprintf(clp, &bs, fmt, val);
+ append_build_string(&bs, "\n", 1);
+
+ build_string_program_prefix(&bs, clp);
+ append_build_string(&bs, "(Possibilities are", -1);
+
+ for (i = 0; i < ambiguous && i < MAX_AMBIGUOUS_VALUES; i++) {
+ int value = ambiguous_values[i];
+ const char *no_dash = "";
+ if (value < 0)
+ value = -(value + 1), no_dash = "no-";
+ if (i == 0)
+ append_build_string(&bs, " ", 1);
+ else if (i == ambiguous - 1)
+ append_build_string(&bs, (i == 1 ? " and " : ", and "), -1);
+ else
+ append_build_string(&bs, ", ", 2);
+ if (!iopt[value].unquoted)
+ append_build_string(&bs, (cli->utf8 ? "\342\200\230" : "'"), -1);
+ append_build_string(&bs, prefix, -1);
+ append_build_string(&bs, no_dash, -1);
+ append_build_string(&bs, opt[value].long_name + iopt[value].ilongoff, -1);
+ if (!iopt[value].unquoted)
+ append_build_string(&bs, (cli->utf8 ? "\342\200\231" : "'"), -1);
+ }
+
+ if (ambiguous > MAX_AMBIGUOUS_VALUES)
+ append_build_string(&bs, ", and others", -1);
+ append_build_string(&bs, ".)\n", -1);
+ va_end(val);
+
+ do_error(clp, &bs);
+ build_string_cleanup(&bs);
+ return 0;
+}
+
+static int
+copy_string(char *buf, int buflen, int bufpos, const char *what)
+{
+ int l = strlen(what);
+ if (l > buflen - bufpos - 1)
+ l = buflen - bufpos - 1;
+ memcpy(buf + bufpos, what, l);
+ return l;
+}
+
+/** @param clp the parser
+ * @param buf output buffer
+ * @param len length of output buffer
+ * @return number of characters written to the buffer, not including the terminating NUL
+ *
+ * A string that looks like the last option parsed by @a clp is extracted into
+ * @a buf. The correct option characters are put into the string first,
+ * followed by the option text. The output buffer is null-terminated unless
+ * @a len == 0.
+ *
+ * @sa Clp_CurOptionName
+ */
+int
+Clp_CurOptionNameBuf(Clp_Parser *clp, char *buf, int len)
+{
+ Clp_Internal *cli = clp->internal;
+ int optno = cli->current_option;
+ int pos = 0;
+ if (optno < 0)
+ pos += copy_string(buf, len, pos, "(no current option!)");
+ else if (cli->current_short) {
+ pos += copy_string(buf, len, pos, cli->option_chars);
+ if (cli->utf8)
+ pos = (encode_utf8(buf + pos, len - pos - 1, cli->opt[optno].short_name) - buf);
+ else if (pos < len - 1)
+ buf[pos++] = cli->opt[optno].short_name;
+ } else if (cli->negated_by_no) {
+ pos += copy_string(buf, len, pos, cli->option_chars);
+ pos += copy_string(buf, len, pos, "no-");
+ pos += copy_string(buf, len, pos, cli->opt[optno].long_name + cli->iopt[optno].ilongoff);
+ } else {
+ pos += copy_string(buf, len, pos, cli->option_chars);
+ pos += copy_string(buf, len, pos, cli->opt[optno].long_name + cli->iopt[optno].ilongoff);
+ }
+ if (pos < len)
+ buf[pos] = 0;
+ return pos;
+}
+
+/** @param clp the parser
+ * @return string describing the current option
+ *
+ * This function acts like Clp_CurOptionNameBuf(), but returns a pointer into
+ * a static buffer that will be rewritten on the next call to
+ * Clp_CurOptionName().
+ *
+ * @note This function is not thread safe.
+ *
+ * @sa Clp_CurOptionName
+ */
+const char *
+Clp_CurOptionName(Clp_Parser *clp)
+{
+ static char buf[256];
+ Clp_CurOptionNameBuf(clp, buf, 256);
+ return buf;
+}
+
+int
+Clp_IsLong(Clp_Parser *clp, const char *long_name)
+{
+ Clp_Internal *cli = clp->internal;
+ int optno = cli->current_option;
+ return optno >= 0 && strcmp(cli->opt[optno].long_name, long_name) == 0;
+}
+
+int
+Clp_IsShort(Clp_Parser *clp, int short_name)
+{
+ Clp_Internal *cli = clp->internal;
+ int optno = cli->current_option;
+ return optno >= 0 && cli->opt[optno].short_name == short_name;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/fmalloc.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/fmalloc.c
new file mode 100644
index 0000000..bddfca6
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/fmalloc.c
@@ -0,0 +1,39 @@
+#if HAVE_CONFIG_H
+# include
+#endif
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern const char* program_name;
+
+void* Gif_Realloc(void* p, size_t s, size_t n, const char* file, int line) {
+ (void) file, (void) line;
+ if (s == 0 || n == 0) {
+ Gif_Free(p);
+ return (void*) 0;
+ } else if (s == 1 || n == 1 || s <= ((size_t) -1) / n) {
+ p = realloc(p, s * n);
+ if (!p) {
+ fprintf(stderr, "%s: Out of memory, giving up\n", program_name);
+ exit(1);
+ }
+ return p;
+ } else {
+ fprintf(stderr, "%s: Out of memory, giving up (huge allocation)\n", program_name);
+ exit(1);
+ return (void*) 0;
+ }
+}
+
+#undef Gif_Free
+void Gif_Free(void* p) {
+ free(p);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/gifdiff.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifdiff.c
new file mode 100644
index 0000000..ef7ac5e
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifdiff.c
@@ -0,0 +1,632 @@
+/* gifdiff.c - Gifdiff compares GIF images for identical appearance.
+ Copyright (C) 1998-2025 Eddie Kohler, ekohler@gmail.com
+ This file is part of gifdiff, in the gifsicle package.
+
+ Gifdiff 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
+#include
+#include
+#include
+#include
+#include
+#if HAVE_UNISTD_H
+# include
+#endif
+
+#define QUIET_OPT 300
+#define HELP_OPT 301
+#define VERSION_OPT 302
+#define IGNORE_REDUNDANCY_OPT 303
+#define REDUNDANCY_OPT 304
+#define IGNORE_BACKGROUND_OPT 305
+#define BACKGROUND_OPT 306
+
+const Clp_Option options[] = {
+ { "help", 'h', HELP_OPT, 0, 0 },
+ { "brief", 'q', QUIET_OPT, 0, Clp_Negate },
+ { "redundancy", 0, REDUNDANCY_OPT, 0, Clp_Negate },
+ { "ignore-redundancy", 'w', IGNORE_REDUNDANCY_OPT, 0, Clp_Negate },
+ { "bg", 0, BACKGROUND_OPT, 0, Clp_Negate },
+ { "ignore-bg", 0, IGNORE_BACKGROUND_OPT, 0, Clp_Negate },
+ { "background", 0, BACKGROUND_OPT, 0, Clp_Negate },
+ { "ignore-background", 'B', IGNORE_BACKGROUND_OPT, 0, Clp_Negate },
+ { "version", 'v', VERSION_OPT, 0, 0 }
+};
+
+const char *program_name;
+
+static const char *filename1;
+static const char *filename2;
+
+static unsigned screen_width, screen_height;
+#define TRANSP (0)
+
+static uint16_t *gdata[2];
+static uint16_t *glast[2];
+static uint16_t *scratch;
+static uint16_t *line;
+
+static int brief;
+static int ignore_redundancy;
+static int ignore_background;
+
+static Clp_Parser* clp;
+
+
+static void
+combine_colormaps(Gif_Colormap *gfcm, Gif_Colormap *newcm)
+{
+ int i, gfcm_ncol = gfcm ? gfcm->ncol : 0;
+ for (i = 0; i < gfcm_ncol; i++) {
+ Gif_Color *c = &gfcm->col[i];
+ c->pixel = Gif_AddColor(newcm, c, 1);
+ }
+}
+
+static void
+fill_area(uint16_t *data, int l, int t, int w, int h, uint16_t val)
+{
+ int x;
+ data += screen_width * t + l;
+ for (; h > 0; --h) {
+ for (x = w; x > 0; --x)
+ *data++ = val;
+ data += screen_width - w;
+ }
+}
+
+static void
+copy_area(uint16_t *dst, const uint16_t *src, int l, int t, int w, int h)
+{
+ dst += screen_width * t + l;
+ src += screen_width * t + l;
+ for (; h > 0; --h, dst += screen_width, src += screen_width)
+ memcpy(dst, src, sizeof(uint16_t) * w);
+}
+
+static void
+expand_bounds(int *lf, int *tp, int *rt, int *bt, const Gif_Image *gfi)
+{
+ int empty = (*lf >= *rt || *tp >= *bt);
+ if (empty || gfi->left < *lf)
+ *lf = gfi->left;
+ if (empty || gfi->top < *tp)
+ *tp = gfi->top;
+ if (empty || gfi->left + gfi->width > *rt)
+ *rt = gfi->left + gfi->width;
+ if (empty || gfi->top + gfi->height > *bt)
+ *bt = gfi->top + gfi->height;
+}
+
+
+static int
+apply_image(int is_second, Gif_Stream *gfs, int imageno, uint16_t background)
+{
+ int i, x, y, any_change;
+ Gif_Image *gfi = gfs->images[imageno];
+ Gif_Image *pgfi = imageno ? gfs->images[imageno - 1] : 0;
+ int width = gfi->width;
+ uint16_t map[256];
+ uint16_t *data = gdata[is_second];
+ uint16_t *last = glast[is_second];
+ Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global;
+ int gfcm_ncol = gfcm ? gfcm->ncol : 0;
+
+ /* set up colormap */
+ for (i = 0; i < gfcm_ncol; ++i)
+ map[i] = gfcm->col[i].pixel;
+ for (i = gfcm_ncol; i < 256; ++i)
+ map[i] = 1;
+ if (gfi->transparent >= 0 && gfi->transparent < 256)
+ map[gfi->transparent] = TRANSP;
+
+ /* if this image's disposal is 'previous', save the post-disposal version in
+ 'scratch' */
+ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
+ copy_area(scratch, data, gfi->left, gfi->top, gfi->width, gfi->height);
+ if (pgfi && pgfi->disposal == GIF_DISPOSAL_PREVIOUS)
+ copy_area(scratch, last, pgfi->left, pgfi->top, pgfi->width, pgfi->height);
+ else if (pgfi && pgfi->disposal == GIF_DISPOSAL_BACKGROUND)
+ fill_area(scratch, pgfi->left, pgfi->top, pgfi->width, pgfi->height, background);
+ }
+
+ /* uncompress and clip */
+ Gif_UncompressImage(gfs, gfi);
+ Gif_ClipImage(gfi, 0, 0, screen_width, screen_height);
+
+ any_change = imageno == 0;
+ {
+ int lf = 0, tp = 0, rt = 0, bt = 0;
+ expand_bounds(&lf, &tp, &rt, &bt, gfi);
+ if (pgfi && pgfi->disposal == GIF_DISPOSAL_PREVIOUS)
+ expand_bounds(&lf, &tp, &rt, &bt, pgfi);
+ else if (pgfi && pgfi->disposal == GIF_DISPOSAL_BACKGROUND) {
+ expand_bounds(&lf, &tp, &rt, &bt, pgfi);
+ fill_area(last, pgfi->left, pgfi->top, pgfi->width, pgfi->height, background);
+ } else
+ pgfi = 0;
+ for (y = tp; y < bt; ++y) {
+ uint16_t *outd = data + screen_width * y + lf;
+ if (!any_change)
+ memcpy(line, outd, (rt - lf) * sizeof(uint16_t));
+ if (pgfi && y >= pgfi->top && y < pgfi->top + pgfi->height)
+ memcpy(outd + pgfi->left - lf,
+ last + screen_width * y + pgfi->left,
+ pgfi->width * sizeof(uint16_t));
+ if (y >= gfi->top && y < gfi->top + gfi->height) {
+ uint16_t *xoutd = outd + gfi->left - lf;
+ const uint8_t *ind = gfi->img[y - gfi->top];
+ for (x = 0; x < width; ++x, ++ind, ++xoutd)
+ if (map[*ind] != TRANSP)
+ *xoutd = map[*ind];
+ }
+ if (!any_change && memcmp(line, outd, (rt - lf) * sizeof(uint16_t)) != 0)
+ any_change = 1;
+ }
+ }
+
+ Gif_ReleaseUncompressedImage(gfi);
+ Gif_ReleaseCompressedImage(gfi);
+
+ /* switch 'glast' with 'scratch' if necessary */
+ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
+ uint16_t *x = scratch;
+ scratch = glast[is_second];
+ glast[is_second] = x;
+ }
+
+ return any_change;
+}
+
+
+#define SAME 0
+#define DIFFERENT 1
+static int was_different;
+
+static void
+different(const char *format, ...)
+{
+ va_list val;
+ va_start(val, format);
+ if (!brief) {
+ vfprintf(stdout, format, val);
+ fputc('\n', stdout);
+ }
+ va_end(val);
+ was_different = 1;
+}
+
+
+static void
+name_loopcount(int loopcount, char *buf, size_t bufsz)
+{
+ if (loopcount < 0)
+ strcpy(buf, "none");
+ else if (loopcount == 0)
+ strcpy(buf, "forever");
+ else
+ snprintf(buf, bufsz, "%d", loopcount);
+}
+
+static void
+name_delay(int delay, char *buf, size_t bufsz)
+{
+ if (delay == 0)
+ strcpy(buf, "none");
+ else
+ snprintf(buf, bufsz, "%d.%02ds", delay / 100, delay % 100);
+}
+
+static void
+name_color(int color, Gif_Colormap *gfcm, char *buf, size_t bufsz)
+{
+ if (color == TRANSP)
+ strcpy(buf, "transparent");
+ else {
+ Gif_Color *c = &gfcm->col[color];
+ snprintf(buf, bufsz, "#%02X%02X%02X", c->gfc_red, c->gfc_green, c->gfc_blue);
+ }
+}
+
+
+int
+compare(Gif_Stream *s1, Gif_Stream *s2)
+{
+ Gif_Colormap *newcm;
+ int imageno1, imageno2, background1, background2;
+ char buf1[256], buf2[256], fbuf[256];
+
+ was_different = 0;
+
+ /* Compare image counts and screen sizes. If either of these differs, quit
+ early. */
+ Gif_CalculateScreenSize(s1, 0);
+ Gif_CalculateScreenSize(s2, 0);
+
+ if (s1->screen_width != s2->screen_width
+ || s1->screen_height != s2->screen_height) {
+ different("screen sizes differ: <%dx%d >%dx%d", s1->screen_width,
+ s1->screen_height, s2->screen_width, s2->screen_height);
+ return DIFFERENT;
+ }
+
+ if (s1->screen_width == 0 || s1->screen_height == 0
+ || s2->screen_width == 0 || s2->screen_height == 0) {
+ /* paranoia -- don't think this can happen */
+ different("zero screen sizes");
+ return DIFFERENT;
+ }
+
+ if (s1->nimages == 0 || s2->nimages == 0) {
+ if (s1->nimages != s2->nimages) {
+ different("frame counts differ: <#%d >#%d", s1->nimages, s2->nimages);
+ return DIFFERENT;
+ } else
+ return SAME;
+ }
+
+ /* Create arrays for the image data */
+ screen_width = s1->screen_width;
+ screen_height = s1->screen_height;
+
+ gdata[0] = Gif_NewArray(uint16_t, screen_width * screen_height);
+ gdata[1] = Gif_NewArray(uint16_t, screen_width * screen_height);
+ glast[0] = Gif_NewArray(uint16_t, screen_width * screen_height);
+ glast[1] = Gif_NewArray(uint16_t, screen_width * screen_height);
+ scratch = Gif_NewArray(uint16_t, screen_width * screen_height);
+ line = Gif_NewArray(uint16_t, screen_width);
+
+ /* Merge all distinct colors from the two images into one colormap, setting
+ the 'pixel' slots in the images' colormaps to the corresponding values
+ in the merged colormap. Don't forget transparency */
+ newcm = Gif_NewFullColormap(1, 256);
+ combine_colormaps(s1->global, newcm);
+ combine_colormaps(s2->global, newcm);
+ for (imageno1 = 0; imageno1 < s1->nimages; ++imageno1)
+ combine_colormaps(s1->images[imageno1]->local, newcm);
+ for (imageno2 = 0; imageno2 < s2->nimages; ++imageno2)
+ combine_colormaps(s2->images[imageno2]->local, newcm);
+
+ /* Choose the background values */
+ background1 = background2 = TRANSP;
+ if ((s1->nimages == 0 || s1->images[0]->transparent < 0)
+ && s1->global && s1->background < s1->global->ncol)
+ background1 = s1->global->col[ s1->background ].pixel;
+ if ((s2->nimages == 0 || s2->images[0]->transparent < 0)
+ && s2->global && s2->background < s2->global->ncol)
+ background2 = s2->global->col[ s2->background ].pixel;
+
+ /* Clear screens */
+ fill_area(gdata[0], 0, 0, screen_width, screen_height, TRANSP);
+ fill_area(gdata[1], 0, 0, screen_width, screen_height, TRANSP);
+
+ /* Loopcounts differ? */
+ if (s1->loopcount != s2->loopcount) {
+ name_loopcount(s1->loopcount, buf1, sizeof(buf1));
+ name_loopcount(s2->loopcount, buf2, sizeof(buf2));
+ different("loop counts differ: <%s >%s", buf1, buf2);
+ }
+
+ /* Loop over frames, comparing image data and delays */
+ apply_image(0, s1, 0, background1);
+ apply_image(1, s2, 0, background2);
+ imageno1 = imageno2 = 0;
+ while (imageno1 != s1->nimages && imageno2 != s2->nimages) {
+ int fi1 = imageno1, fi2 = imageno2,
+ delay1 = s1->images[fi1]->delay, delay2 = s2->images[fi2]->delay;
+
+ /* get message right */
+ if (imageno1 == imageno2)
+ snprintf(fbuf, sizeof(fbuf), "#%d", imageno1);
+ else
+ snprintf(fbuf, sizeof(fbuf), "<#%d >#%d", imageno1, imageno2);
+
+ /* compare pixels */
+ if (memcmp(gdata[0], gdata[1],
+ screen_width * screen_height * sizeof(uint16_t)) != 0) {
+ unsigned d, c = screen_width * screen_height;
+ uint16_t *d1 = gdata[0], *d2 = gdata[1];
+ for (d = 0; d < c; d++, d1++, d2++)
+ if (*d1 != *d2) {
+ name_color(*d1, newcm, buf1, sizeof(buf1));
+ name_color(*d2, newcm, buf2, sizeof(buf2));
+ different("frame %s pixels differ: %d,%d <%s >%s",
+ fbuf, d % screen_width, d / screen_width, buf1, buf2);
+ break;
+ }
+ }
+
+ /* compare background */
+ if (!ignore_background && background1 != background2
+ && (imageno1 == 0 || s1->images[imageno1 - 1]->disposal == GIF_DISPOSAL_BACKGROUND)
+ && (imageno2 == 0 || s2->images[imageno2 - 1]->disposal == GIF_DISPOSAL_BACKGROUND)) {
+ unsigned d, c = screen_width * screen_height;
+ uint16_t *d1 = gdata[0], *d2 = gdata[1];
+ for (d = 0; d < c; ++d, ++d1, ++d2)
+ if (*d1 == TRANSP || *d2 == TRANSP) {
+ name_color(background1, newcm, buf1, sizeof(buf1));
+ name_color(background2, newcm, buf2, sizeof(buf2));
+ different("frame %s background pixels differ: %d,%d <%s >%s",
+ fbuf, d % screen_width, d / screen_width, buf1, buf2);
+ background1 = background2 = TRANSP;
+ break;
+ }
+ }
+
+ /* move to next images, skipping redundancy */
+ for (++imageno1;
+ imageno1 < s1->nimages && !apply_image(0, s1, imageno1, background1);
+ ++imageno1)
+ delay1 += s1->images[imageno1]->delay;
+ for (++imageno2;
+ imageno2 < s2->nimages && !apply_image(1, s2, imageno2, background2);
+ ++imageno2)
+ delay2 += s2->images[imageno2]->delay;
+
+ if (!ignore_redundancy) {
+ fi1 = (imageno1 - fi1) - (imageno2 - fi2);
+ for (; fi1 > 0; --fi1)
+ different("extra redundant frame: <#%d", imageno1 - fi1);
+ for (; fi1 < 0; ++fi1)
+ different("extra redundant frame: >#%d", imageno2 + fi1);
+ }
+
+ if (delay1 != delay2) {
+ name_delay(delay1, buf1, sizeof(buf1));
+ name_delay(delay2, buf2, sizeof(buf2));
+ different("frame %s delays differ: <%s >%s", fbuf, buf1, buf2);
+ }
+ }
+
+ if (imageno1 != s1->nimages || imageno2 != s2->nimages)
+ different("frame counts differ: <#%d >#%d", s1->nimages, s2->nimages);
+
+ /* That's it! */
+ Gif_DeleteColormap(newcm);
+ Gif_DeleteArray(gdata[0]);
+ Gif_DeleteArray(gdata[1]);
+ Gif_DeleteArray(glast[0]);
+ Gif_DeleteArray(glast[1]);
+ Gif_DeleteArray(scratch);
+ Gif_DeleteArray(line);
+
+ return was_different ? DIFFERENT : SAME;
+}
+
+
+void short_usage(void) {
+ Clp_fprintf(clp, stderr, "Usage: %s [OPTION]... FILE1 FILE2\n\
+Try %<%s --help%> for more information.\n",
+ program_name, program_name);
+}
+
+void usage(void) {
+ Clp_fprintf(clp, stdout, "\
+% compares two GIF files (either images or animations) for identical\n\
+visual appearance. An animation and an optimized version of the same animation\n\
+should compare as the same. Gifdiff exits with status 0 if the images are\n\
+the same, 1 if they%,re different, and 2 if there was some error.\n\
+\n\
+Usage: %s [OPTION]... FILE1 FILE2\n\n", program_name);
+ Clp_fprintf(clp, stdout, "\
+Options:\n\
+ -q, --brief Don%,t report detailed differences.\n\
+ -w, --ignore-redundancy Ignore differences in redundant frames.\n\
+ -B, --ignore-background Ignore differences in background colors.\n\
+ -h, --help Print this message and exit.\n\
+ -v, --version Print version number and exit.\n\
+\n\
+Report bugs to .\n");
+}
+
+
+void fatal_error(const char* format, ...) {
+ char buf[BUFSIZ];
+ int n = snprintf(buf, BUFSIZ, "%s: ", program_name);
+ va_list val;
+ va_start(val, format);
+ Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
+ va_end(val);
+ fputs(buf, stderr);
+ exit(2); /* exit(2) for trouble */
+}
+
+void error(const char* format, ...) {
+ char buf[BUFSIZ];
+ int n = snprintf(buf, BUFSIZ, "%s: ", program_name);
+ va_list val;
+ va_start(val, format);
+ Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
+ va_end(val);
+ fputs(buf, stderr);
+}
+
+static int gifread_error_count;
+
+static void
+gifread_error(Gif_Stream* gfs, Gif_Image* gfi,
+ int is_error, const char *message)
+{
+ static int last_is_error = 0;
+ static int last_which_image = 0;
+ static char last_message[256];
+ static int different_error_count = 0;
+ static int same_error_count = 0;
+ int which_image = Gif_ImageNumber(gfs, gfi);
+ const char *filename = gfs->landmark;
+ if (which_image < 0)
+ which_image = gfs->nimages;
+
+ 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)) {
+ const char *etype = last_is_error ? "error" : "warning";
+ error("While reading %<%s%> frame #%d:\n", filename, last_which_image);
+ if (same_error_count == 1)
+ error(" %s: %s\n", etype, last_message);
+ else if (same_error_count > 0)
+ error(" %s: %s (%d times)\n", 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++;
+ strcpy(last_message, message);
+ last_which_image = which_image;
+ last_is_error = is_error;
+ } else
+ last_message[0] = 0;
+
+ if (different_error_count == 11 && message) {
+ error("(more errors while reading %<%s%>)\n", filename);
+ different_error_count++;
+ }
+}
+
+static Gif_Stream *
+read_stream(const char **filename)
+{
+ FILE *f;
+ Gif_Stream *gfs;
+ if (*filename == 0) {
+#if 0
+ /* Since gifdiff always takes explicit filename arguments,
+ allow explicit reads from terminal. */
+#ifndef OUTPUT_GIF_TO_TERMINAL
+ if (isatty(fileno(stdin))) {
+ fatal_error(": is a terminal\n");
+ return NULL;
+ }
+#endif
+#endif
+ f = stdin;
+#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
+ *filename = "";
+ } else {
+ f = fopen(*filename, "rb");
+ if (!f)
+ fatal_error("%s: %s\n", *filename, strerror(errno));
+ }
+ gifread_error_count = 0;
+ gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, *filename, gifread_error);
+ if (!gfs)
+ fatal_error("%s: file not in GIF format\n", *filename);
+ return gfs;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int how_many_inputs = 0;
+ int status;
+ const char **inputp;
+ Gif_Stream *gfs1, *gfs2;
+
+ clp = Clp_NewParser(argc, (const char * const *)argv,
+ sizeof(options) / sizeof(options[0]), options);
+
+ program_name = Clp_ProgramName(clp);
+ brief = 0;
+
+ while (1) {
+ int opt = Clp_Next(clp);
+ switch (opt) {
+
+ case HELP_OPT:
+ usage();
+ exit(0);
+ break;
+
+ case VERSION_OPT:
+ printf("gifdiff (LCDF Gifsicle) %s\n", VERSION);
+ printf("Copyright (C) 1998-2025 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(0);
+ break;
+
+ case QUIET_OPT:
+ brief = !clp->negated;
+ break;
+
+ case IGNORE_REDUNDANCY_OPT:
+ ignore_redundancy = !clp->negated;
+ break;
+
+ case REDUNDANCY_OPT:
+ ignore_redundancy = !!clp->negated;
+ break;
+
+ case IGNORE_BACKGROUND_OPT:
+ ignore_background = !clp->negated;
+ break;
+
+ case BACKGROUND_OPT:
+ ignore_background = !!clp->negated;
+ break;
+
+ case Clp_NotOption:
+ if (how_many_inputs == 2) {
+ error("too many file arguments\n");
+ goto bad_option;
+ }
+ inputp = (how_many_inputs == 0 ? &filename1 : &filename2);
+ how_many_inputs++;
+ if (strcmp(clp->vstr, "-") == 0)
+ *inputp = 0;
+ else
+ *inputp = clp->vstr;
+ break;
+
+ bad_option:
+ case Clp_BadOption:
+ short_usage();
+ exit(1);
+ break;
+
+ case Clp_Done:
+ goto done;
+
+ }
+ }
+
+ done:
+
+ if (how_many_inputs < 2)
+ fatal_error("need exactly 2 file arguments\n");
+ if (filename1 == 0 && filename2 == 0)
+ fatal_error("can%,t read both files from stdin\n");
+
+ gfs1 = read_stream(&filename1);
+ gfs2 = read_stream(&filename2);
+
+ status = (compare(gfs1, gfs2) == DIFFERENT);
+ if (status == 1 && brief)
+ printf("GIF files %s and %s differ\n", filename1, filename2);
+
+ Gif_DeleteStream(gfs1);
+ Gif_DeleteStream(gfs2);
+ return status;
+}
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/giffunc.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/giffunc.c
new file mode 100644
index 0000000..3ddf2df
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/giffunc.c
@@ -0,0 +1,844 @@
+/* giffunc.c - General functions for the GIF library.
+ Copyright (C) 1997-2021 Eddie Kohler, ekohler@gmail.com
+ This file is part of the LCDF GIF library.
+
+ The LCDF GIF library is free software. It is distributed under the GNU
+ General 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. */
+
+#if HAVE_CONFIG_H
+# include
+#endif
+#include
+#include
+#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+Gif_Stream *
+Gif_NewStream(void)
+{
+ Gif_Stream *gfs = Gif_New(Gif_Stream);
+ if (!gfs)
+ return 0;
+ gfs->images = 0;
+ gfs->nimages = gfs->imagescap = 0;
+ gfs->global = 0;
+ gfs->background = 256;
+ gfs->screen_width = gfs->screen_height = 0;
+ gfs->loopcount = -1;
+ gfs->end_comment = 0;
+ gfs->end_extension_list = 0;
+ gfs->errors = 0;
+ gfs->user_flags = 0;
+ gfs->refcount = 0;
+ gfs->landmark = 0;
+ return gfs;
+}
+
+
+Gif_Image *
+Gif_NewImage(void)
+{
+ Gif_Image *gfi = Gif_New(Gif_Image);
+ if (!gfi)
+ return 0;
+ gfi->width = gfi->height = 0;
+ gfi->img = 0;
+ gfi->image_data = 0;
+ gfi->left = gfi->top = 0;
+ gfi->delay = 0;
+ gfi->disposal = GIF_DISPOSAL_NONE;
+ gfi->interlace = 0;
+ gfi->local = 0;
+ gfi->transparent = -1;
+ gfi->user_flags = 0;
+ gfi->identifier = 0;
+ gfi->comment = 0;
+ gfi->extension_list = 0;
+ gfi->free_image_data = Gif_Free;
+ gfi->compressed_len = 0;
+ gfi->compressed_errors = 0;
+ gfi->compressed = 0;
+ gfi->free_compressed = 0;
+ gfi->user_data = 0;
+ gfi->free_user_data = 0;
+ gfi->refcount = 0;
+ return gfi;
+}
+
+
+Gif_Colormap *
+Gif_NewColormap(void)
+{
+ return Gif_NewFullColormap(0, 256);
+}
+
+
+Gif_Colormap *
+Gif_NewFullColormap(int count, int capacity)
+{
+ Gif_Colormap *gfcm = Gif_New(Gif_Colormap);
+ if (!gfcm || count < 0) {
+ Gif_Delete(gfcm);
+ return 0;
+ }
+ if (count > capacity)
+ capacity = count;
+ if (capacity < 256)
+ capacity = 256;
+ gfcm->ncol = count;
+ gfcm->capacity = capacity;
+ gfcm->col = Gif_NewArray(Gif_Color, capacity);
+ gfcm->refcount = 0;
+ gfcm->user_flags = 0;
+ if (!gfcm->col) {
+ Gif_Delete(gfcm);
+ return 0;
+ }
+ return gfcm;
+}
+
+
+Gif_Comment *
+Gif_NewComment(void)
+{
+ Gif_Comment *gfcom = Gif_New(Gif_Comment);
+ if (!gfcom)
+ return 0;
+ gfcom->str = 0;
+ gfcom->len = 0;
+ gfcom->count = gfcom->cap = 0;
+ return gfcom;
+}
+
+
+Gif_Extension *
+Gif_NewExtension(int kind, const char* appname, int applength)
+{
+ Gif_Extension *gfex = Gif_New(Gif_Extension);
+ if (!gfex)
+ return 0;
+ gfex->kind = kind;
+ if (appname) {
+ gfex->appname = (char*) Gif_NewArray(char, applength + 1);
+ if (!gfex->appname) {
+ Gif_Delete(gfex);
+ return 0;
+ }
+ memcpy(gfex->appname, appname, applength);
+ gfex->appname[applength] = 0;
+ gfex->applength = applength;
+ } else {
+ gfex->appname = 0;
+ gfex->applength = 0;
+ }
+ gfex->data = 0;
+ gfex->stream = 0;
+ gfex->image = 0;
+ gfex->next = 0;
+ gfex->free_data = 0;
+ gfex->packetized = 0;
+ return gfex;
+}
+
+Gif_Extension*
+Gif_CopyExtension(Gif_Extension* src)
+{
+ Gif_Extension* dst = Gif_NewExtension(src->kind, src->appname, src->applength);
+ if (!dst)
+ return NULL;
+ if (!src->data || !src->free_data) {
+ dst->data = src->data;
+ dst->length = src->length;
+ } else {
+ dst->data = Gif_NewArray(uint8_t, src->length);
+ if (!dst->data) {
+ Gif_DeleteExtension(dst);
+ return NULL;
+ }
+ memcpy(dst->data, src->data, src->length);
+ dst->length = src->length;
+ dst->free_data = Gif_Free;
+ }
+ dst->packetized = src->packetized;
+ return dst;
+}
+
+
+char *
+Gif_CopyString(const char *s)
+{
+ int l;
+ char *copy;
+ if (!s)
+ return 0;
+ l = strlen(s);
+ copy = Gif_NewArray(char, l + 1);
+ if (!copy)
+ return 0;
+ memcpy(copy, s, l + 1);
+ return copy;
+}
+
+
+int
+Gif_AddImage(Gif_Stream *gfs, Gif_Image *gfi)
+{
+ if (gfs->nimages >= gfs->imagescap) {
+ if (gfs->imagescap)
+ gfs->imagescap *= 2;
+ else
+ gfs->imagescap = 2;
+ Gif_ReArray(gfs->images, Gif_Image *, gfs->imagescap);
+ if (!gfs->images)
+ return 0;
+ }
+ gfs->images[gfs->nimages] = gfi;
+ gfs->nimages++;
+ gfi->refcount++;
+ return 1;
+}
+
+
+void
+Gif_RemoveImage(Gif_Stream *gfs, int inum)
+{
+ int j;
+ if (inum < 0 || inum >= gfs->nimages)
+ return;
+ Gif_DeleteImage(gfs->images[inum]);
+ for (j = inum; j < gfs->nimages - 1; j++)
+ gfs->images[j] = gfs->images[j+1];
+ gfs->nimages--;
+}
+
+
+int
+Gif_ImageColorBound(const Gif_Image* gfi)
+{
+ if (gfi->compressed && gfi->compressed[0] > 0 && gfi->compressed[0] < 8)
+ return 1 << gfi->compressed[0];
+ else
+ return 256;
+}
+
+
+int
+Gif_AddCommentTake(Gif_Comment *gfcom, char *x, int xlen)
+{
+ if (gfcom->count >= gfcom->cap) {
+ if (gfcom->cap)
+ gfcom->cap *= 2;
+ else
+ gfcom->cap = 2;
+ Gif_ReArray(gfcom->str, char *, gfcom->cap);
+ Gif_ReArray(gfcom->len, int, gfcom->cap);
+ if (!gfcom->str || !gfcom->len)
+ return 0;
+ }
+ if (xlen < 0)
+ xlen = strlen(x);
+ gfcom->str[ gfcom->count ] = x;
+ gfcom->len[ gfcom->count ] = xlen;
+ gfcom->count++;
+ return 1;
+}
+
+
+int
+Gif_AddComment(Gif_Comment *gfcom, const char *x, int xlen)
+{
+ char *new_x;
+ if (xlen < 0)
+ xlen = strlen(x);
+ new_x = Gif_NewArray(char, xlen);
+ if (!new_x)
+ return 0;
+ memcpy(new_x, x, xlen);
+ if (Gif_AddCommentTake(gfcom, new_x, xlen) == 0) {
+ Gif_DeleteArray(new_x);
+ return 0;
+ } else
+ return 1;
+}
+
+
+int
+Gif_AddExtension(Gif_Stream* gfs, Gif_Image* gfi, Gif_Extension* gfex)
+{
+ Gif_Extension **pprev;
+ if (gfex->stream || gfex->image)
+ return 0;
+ pprev = gfi ? &gfi->extension_list : &gfs->end_extension_list;
+ while (*pprev)
+ pprev = &(*pprev)->next;
+ *pprev = gfex;
+ gfex->stream = gfs;
+ gfex->image = gfi;
+ gfex->next = 0;
+ return 1;
+}
+
+
+int
+Gif_ImageNumber(Gif_Stream *gfs, Gif_Image *gfi)
+{
+ int i;
+ if (gfs && gfi)
+ for (i = 0; i != gfs->nimages; ++i)
+ if (gfs->images[i] == gfi)
+ return i;
+ return -1;
+}
+
+
+void
+Gif_CalculateScreenSize(Gif_Stream *gfs, int force)
+{
+ int i;
+ int screen_width = force ? 0 : gfs->screen_width;
+ int screen_height = force ? 0 : gfs->screen_height;
+
+ for (i = 0; i < gfs->nimages && (force || i == 0); ++i) {
+ Gif_Image *gfi = gfs->images[i];
+ if (screen_width < gfi->left + gfi->width)
+ screen_width = gfi->left + gfi->width;
+ if (screen_height < gfi->top + gfi->height)
+ screen_height = gfi->top + gfi->height;
+ }
+
+ if (gfs->screen_width < screen_width || force)
+ gfs->screen_width = screen_width;
+ if (gfs->screen_height < screen_height || force)
+ gfs->screen_height = screen_height;
+}
+
+
+Gif_Stream *
+Gif_CopyStreamSkeleton(Gif_Stream *gfs)
+{
+ Gif_Stream *ngfs = Gif_NewStream();
+ if (!ngfs)
+ return 0;
+ ngfs->global = Gif_CopyColormap(gfs->global);
+ ngfs->background = gfs->background;
+ ngfs->screen_width = gfs->screen_width;
+ ngfs->screen_height = gfs->screen_height;
+ ngfs->loopcount = gfs->loopcount;
+ if (gfs->global && !ngfs->global) {
+ Gif_DeleteStream(ngfs);
+ return 0;
+ } else
+ return ngfs;
+}
+
+
+Gif_Stream *
+Gif_CopyStreamImages(Gif_Stream *gfs)
+{
+ Gif_Stream *ngfs = Gif_CopyStreamSkeleton(gfs);
+ int i;
+ if (!ngfs)
+ return 0;
+ for (i = 0; i < gfs->nimages; i++) {
+ Gif_Image *gfi = Gif_CopyImage(gfs->images[i]);
+ if (!gfi || !Gif_AddImage(ngfs, gfi)) {
+ Gif_DeleteStream(ngfs);
+ return 0;
+ }
+ }
+ return ngfs;
+}
+
+
+Gif_Colormap *
+Gif_CopyColormap(Gif_Colormap *src)
+{
+ Gif_Colormap *dest;
+ if (!src)
+ return 0;
+
+ dest = Gif_NewFullColormap(src->ncol, src->capacity);
+ if (!dest)
+ return 0;
+
+ memcpy(dest->col, src->col, sizeof(src->col[0]) * src->ncol);
+ return dest;
+}
+
+
+Gif_Image *
+Gif_CopyImage(Gif_Image *src)
+{
+ Gif_Image *dest;
+ uint8_t *data;
+ int i;
+ if (!src)
+ return 0;
+
+ dest = Gif_NewImage();
+ if (!dest)
+ return 0;
+
+ dest->identifier = Gif_CopyString(src->identifier);
+ if (!dest->identifier && src->identifier)
+ goto failure;
+ if (src->comment) {
+ dest->comment = Gif_NewComment();
+ if (!dest->comment)
+ goto failure;
+ for (i = 0; i < src->comment->count; i++)
+ if (!Gif_AddComment(dest->comment, src->comment->str[i],
+ src->comment->len[i]))
+ goto failure;
+ }
+ if (src->extension_list) {
+ Gif_Extension* gfex = src->extension_list;
+ while (gfex) {
+ Gif_Extension* dstex = Gif_CopyExtension(gfex);
+ if (!dstex)
+ goto failure;
+ Gif_AddExtension(NULL, dest, dstex);
+ gfex = gfex->next;
+ }
+ }
+
+ dest->local = Gif_CopyColormap(src->local);
+ if (!dest->local && src->local)
+ goto failure;
+ dest->transparent = src->transparent;
+
+ dest->delay = src->delay;
+ dest->disposal = src->disposal;
+ dest->left = src->left;
+ dest->top = src->top;
+
+ dest->width = src->width;
+ dest->height = src->height;
+
+ dest->interlace = src->interlace;
+ if (src->img) {
+ dest->img = Gif_NewArray(uint8_t *, dest->height + 1);
+ dest->image_data = Gif_NewArray(uint8_t, (size_t) dest->width * (size_t) dest->height);
+ dest->free_image_data = Gif_Free;
+ if (!dest->img || !dest->image_data)
+ goto failure;
+ for (i = 0, data = dest->image_data; i < dest->height; i++) {
+ memcpy(data, src->img[i], dest->width);
+ dest->img[i] = data;
+ data += dest->width;
+ }
+ dest->img[dest->height] = 0;
+ }
+ if (src->compressed) {
+ if (src->free_compressed == 0)
+ dest->compressed = src->compressed;
+ else {
+ dest->compressed = Gif_NewArray(uint8_t, src->compressed_len);
+ dest->free_compressed = Gif_Free;
+ memcpy(dest->compressed, src->compressed, src->compressed_len);
+ }
+ dest->compressed_len = src->compressed_len;
+ dest->compressed_errors = src->compressed_errors;
+ }
+
+ return dest;
+
+ failure:
+ Gif_DeleteImage(dest);
+ return 0;
+}
+
+
+void Gif_MakeImageEmpty(Gif_Image* gfi) {
+ Gif_ReleaseUncompressedImage(gfi);
+ Gif_ReleaseCompressedImage(gfi);
+ gfi->left = gfi->left < 0xFFFE ? gfi->left : 0xFFFE;
+ gfi->top = gfi->top < 0xFFFE ? gfi->top : 0xFFFE;
+ gfi->width = 1;
+ gfi->height = 1;
+ gfi->transparent = 0;
+ Gif_CreateUncompressedImage(gfi, 0);
+ gfi->img[0][0] = 0;
+}
+
+
+/** DELETION **/
+
+typedef struct Gif_DeletionHook {
+ int kind;
+ Gif_DeletionHookFunc func;
+ void *callback_data;
+ struct Gif_DeletionHook *next;
+} Gif_DeletionHook;
+
+static Gif_DeletionHook *all_hooks;
+
+void
+Gif_DeleteStream(Gif_Stream *gfs)
+{
+ Gif_DeletionHook *hook;
+ int i;
+ if (!gfs || --gfs->refcount > 0)
+ return;
+
+ for (i = 0; i < gfs->nimages; i++)
+ Gif_DeleteImage(gfs->images[i]);
+ Gif_DeleteArray(gfs->images);
+
+ Gif_DeleteColormap(gfs->global);
+
+ Gif_DeleteComment(gfs->end_comment);
+ while (gfs->end_extension_list)
+ Gif_DeleteExtension(gfs->end_extension_list);
+
+ for (hook = all_hooks; hook; hook = hook->next)
+ if (hook->kind == GIF_T_STREAM)
+ (*hook->func)(GIF_T_STREAM, gfs, hook->callback_data);
+ Gif_Delete(gfs);
+}
+
+
+void
+Gif_DeleteImage(Gif_Image *gfi)
+{
+ Gif_DeletionHook *hook;
+ if (!gfi || --gfi->refcount > 0)
+ return;
+
+ for (hook = all_hooks; hook; hook = hook->next)
+ if (hook->kind == GIF_T_IMAGE)
+ (*hook->func)(GIF_T_IMAGE, gfi, hook->callback_data);
+
+ Gif_DeleteArray(gfi->identifier);
+ Gif_DeleteComment(gfi->comment);
+ while (gfi->extension_list)
+ Gif_DeleteExtension(gfi->extension_list);
+ Gif_DeleteColormap(gfi->local);
+ if (gfi->image_data && gfi->free_image_data)
+ (*gfi->free_image_data)((void *)gfi->image_data);
+ Gif_DeleteArray(gfi->img);
+ if (gfi->compressed && gfi->free_compressed)
+ (*gfi->free_compressed)((void *)gfi->compressed);
+ if (gfi->user_data && gfi->free_user_data)
+ (*gfi->free_user_data)(gfi->user_data);
+ Gif_Delete(gfi);
+}
+
+
+void
+Gif_DeleteColormap(Gif_Colormap *gfcm)
+{
+ Gif_DeletionHook *hook;
+ if (!gfcm || --gfcm->refcount > 0)
+ return;
+
+ for (hook = all_hooks; hook; hook = hook->next)
+ if (hook->kind == GIF_T_COLORMAP)
+ (*hook->func)(GIF_T_COLORMAP, gfcm, hook->callback_data);
+
+ Gif_DeleteArray(gfcm->col);
+ Gif_Delete(gfcm);
+}
+
+
+void
+Gif_DeleteComment(Gif_Comment *gfcom)
+{
+ int i;
+ if (!gfcom)
+ return;
+ for (i = 0; i < gfcom->count; i++)
+ Gif_DeleteArray(gfcom->str[i]);
+ Gif_DeleteArray(gfcom->str);
+ Gif_DeleteArray(gfcom->len);
+ Gif_Delete(gfcom);
+}
+
+
+void
+Gif_DeleteExtension(Gif_Extension *gfex)
+{
+ if (!gfex)
+ return;
+ if (gfex->data && gfex->free_data)
+ (*gfex->free_data)(gfex->data);
+ Gif_DeleteArray(gfex->appname);
+ if (gfex->stream || gfex->image) {
+ Gif_Extension** pprev;
+ if (gfex->image)
+ pprev = &gfex->image->extension_list;
+ else
+ pprev = &gfex->stream->end_extension_list;
+ while (*pprev && *pprev != gfex)
+ pprev = &(*pprev)->next;
+ if (*pprev)
+ *pprev = gfex->next;
+ }
+ Gif_Delete(gfex);
+}
+
+
+/** DELETION HOOKS **/
+
+int
+Gif_AddDeletionHook(int kind, void (*func)(int, void *, void *), void *cb)
+{
+ Gif_DeletionHook *hook = Gif_New(Gif_DeletionHook);
+ if (!hook)
+ return 0;
+ Gif_RemoveDeletionHook(kind, func, cb);
+ hook->kind = kind;
+ hook->func = func;
+ hook->callback_data = cb;
+ hook->next = all_hooks;
+ all_hooks = hook;
+ return 1;
+}
+
+void
+Gif_RemoveDeletionHook(int kind, void (*func)(int, void *, void *), void *cb)
+{
+ Gif_DeletionHook *hook = all_hooks, *prev = 0;
+ while (hook) {
+ if (hook->kind == kind && hook->func == func
+ && hook->callback_data == cb) {
+ if (prev)
+ prev->next = hook->next;
+ else
+ all_hooks = hook->next;
+ Gif_Delete(hook);
+ return;
+ }
+ prev = hook;
+ hook = hook->next;
+ }
+}
+
+
+int
+Gif_ColorEq(Gif_Color *c1, Gif_Color *c2)
+{
+ return GIF_COLOREQ(c1, c2);
+}
+
+
+int
+Gif_FindColor(Gif_Colormap *gfcm, Gif_Color *c)
+{
+ int i;
+ for (i = 0; i < gfcm->ncol; i++)
+ if (GIF_COLOREQ(&gfcm->col[i], c))
+ return i;
+ return -1;
+}
+
+
+int
+Gif_AddColor(Gif_Colormap *gfcm, Gif_Color *c, int look_from)
+{
+ int i;
+ if (look_from >= 0)
+ for (i = look_from; i < gfcm->ncol; i++)
+ if (GIF_COLOREQ(&gfcm->col[i], c))
+ return i;
+ if (gfcm->ncol >= gfcm->capacity) {
+ gfcm->capacity *= 2;
+ Gif_ReArray(gfcm->col, Gif_Color, gfcm->capacity);
+ if (gfcm->col == 0)
+ return -1;
+ }
+ i = gfcm->ncol;
+ gfcm->ncol++;
+ gfcm->col[i] = *c;
+ return i;
+}
+
+
+Gif_Image *
+Gif_GetImage(Gif_Stream *gfs, int imagenumber)
+{
+ if (imagenumber >= 0 && imagenumber < gfs->nimages)
+ return gfs->images[imagenumber];
+ else
+ return 0;
+}
+
+
+Gif_Image *
+Gif_GetNamedImage(Gif_Stream *gfs, const char *name)
+{
+ int i;
+
+ if (!name)
+ return gfs->nimages ? gfs->images[0] : 0;
+
+ for (i = 0; i < gfs->nimages; i++)
+ if (gfs->images[i]->identifier &&
+ strcmp(gfs->images[i]->identifier, name) == 0)
+ return gfs->images[i];
+
+ return 0;
+}
+
+
+void
+Gif_ReleaseCompressedImage(Gif_Image *gfi)
+{
+ if (gfi->compressed && gfi->free_compressed)
+ (*gfi->free_compressed)(gfi->compressed);
+ gfi->compressed = 0;
+ gfi->compressed_len = 0;
+ gfi->compressed_errors = 0;
+ gfi->free_compressed = 0;
+}
+
+void
+Gif_ReleaseUncompressedImage(Gif_Image *gfi)
+{
+ Gif_DeleteArray(gfi->img);
+ if (gfi->image_data && gfi->free_image_data)
+ (*gfi->free_image_data)(gfi->image_data);
+ gfi->img = 0;
+ gfi->image_data = 0;
+ gfi->free_image_data = 0;
+}
+
+
+int
+Gif_ClipImage(Gif_Image *gfi, int left, int top, int width, int height)
+{
+ int new_width = gfi->width, new_height = gfi->height;
+ int y;
+
+ if (!gfi->img)
+ return 0;
+
+ if (gfi->left < left) {
+ int shift = left - gfi->left;
+ for (y = 0; y < gfi->height; y++)
+ gfi->img[y] += shift;
+ gfi->left += shift;
+ new_width -= shift;
+ }
+
+ if (gfi->top < top) {
+ int shift = top - gfi->top;
+ for (y = gfi->height - 1; y >= shift; y++)
+ gfi->img[y - shift] = gfi->img[y];
+ gfi->top += shift;
+ new_height -= shift;
+ }
+
+ if (gfi->left + new_width >= width)
+ new_width = width - gfi->left;
+
+ if (gfi->top + new_height >= height)
+ new_height = height - gfi->top;
+
+ if (new_width < 0)
+ new_width = 0;
+ if (new_height < 0)
+ new_height = 0;
+ gfi->width = new_width;
+ gfi->height = new_height;
+ return 1;
+}
+
+
+int
+Gif_InterlaceLine(int line, int height)
+{
+ height--;
+ if (line > height / 2)
+ return line * 2 - ( height | 1);
+ else if (line > height / 4)
+ return line * 4 - ((height & ~1) | 2);
+ else if (line > height / 8)
+ return line * 8 - ((height & ~3) | 4);
+ else
+ return line * 8;
+}
+
+
+int
+Gif_SetUncompressedImage(Gif_Image *gfi, uint8_t *image_data,
+ void (*free_data)(void *), int data_interlaced)
+{
+ /* NB does not affect compressed image (and must not) */
+ unsigned i;
+ unsigned width = gfi->width;
+ unsigned height = gfi->height;
+ uint8_t **img;
+
+ Gif_ReleaseUncompressedImage(gfi);
+ if (!image_data)
+ return 0;
+
+ img = Gif_NewArray(uint8_t *, height + 1);
+ if (!img)
+ return 0;
+
+ if (data_interlaced)
+ for (i = 0; i < height; i++)
+ img[ Gif_InterlaceLine(i, height) ] = image_data + width * i;
+ else
+ for (i = 0; i < height; i++)
+ img[i] = image_data + width * i;
+ img[height] = 0;
+
+ gfi->img = img;
+ gfi->image_data = image_data;
+ gfi->free_image_data = free_data;
+ return 1;
+}
+
+int
+Gif_CreateUncompressedImage(Gif_Image *gfi, int data_interlaced)
+{
+ size_t sz = (size_t) gfi->width * (size_t) gfi->height;
+ uint8_t *data = Gif_NewArray(uint8_t, sz ? sz : 1);
+ return Gif_SetUncompressedImage(gfi, data, Gif_Free, data_interlaced);
+}
+
+void
+Gif_InitCompressInfo(Gif_CompressInfo *gcinfo)
+{
+ gcinfo->flags = 0;
+ gcinfo->loss = 0;
+}
+
+
+void
+Gif_Debug(char *x, ...)
+{
+ va_list val;
+ va_start(val, x);
+ vfprintf(stderr, x, val);
+ va_end(val);
+}
+
+
+#if !GIF_ALLOCATOR_DEFINED
+void* Gif_Realloc(void* p, size_t s, size_t n, const char* file, int line) {
+ (void) file, (void) line;
+ if (s == 0 || n == 0)
+ Gif_Free(p);
+ else if (s == 1 || n == 1 || s <= ((size_t) -1) / n)
+ return realloc(p, s * n);
+ return (void*) 0;
+}
+
+#undef Gif_Free
+void Gif_Free(void* p) {
+ free(p);
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/gifread.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifread.c
new file mode 100644
index 0000000..6be5e18
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifread.c
@@ -0,0 +1,979 @@
+/* gifread.c - Functions to read GIFs.
+ Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com
+ This file is part of the LCDF GIF library.
+
+ The LCDF GIF library is free software. It is distributed under the GNU
+ General 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. */
+
+#if HAVE_CONFIG_H
+# include
+#elif !defined(__cplusplus)
+/* Assume we don't have inline by default */
+# define inline
+#endif
+#include
+#include
+#include
+#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+
+ Gif_Stream *stream;
+
+ Gif_Code *prefix;
+ uint8_t *suffix;
+ uint16_t *length;
+
+ uint16_t width;
+ uint16_t height;
+
+ uint8_t *image;
+ uint8_t *maximage;
+
+ unsigned decodepos;
+
+ Gif_Image* gfi;
+ Gif_ReadErrorHandler handler;
+ int errors[2];
+
+} Gif_Context;
+
+
+typedef struct Gif_Reader {
+ FILE *f;
+ const uint8_t *v;
+ uint32_t pos;
+ uint32_t length;
+ int is_record;
+ int is_eoi;
+ uint8_t (*byte_getter)(struct Gif_Reader *);
+ uint32_t (*block_getter)(uint8_t*, uint32_t, struct Gif_Reader*);
+ int (*eofer)(struct Gif_Reader *);
+} Gif_Reader;
+
+static Gif_ReadErrorHandler default_error_handler = 0;
+
+
+#define gifgetc(grr) ((char)(*grr->byte_getter)(grr))
+#define gifgetbyte(grr) ((*grr->byte_getter)(grr))
+#define gifgetblock(ptr, size, grr) ((*grr->block_getter)(ptr, size, grr))
+#define gifeof(grr) ((*grr->eofer)(grr))
+
+static inline uint16_t
+gifgetunsigned(Gif_Reader *grr)
+{
+ uint8_t one = gifgetbyte(grr);
+ uint8_t two = gifgetbyte(grr);
+ return one | (two << 8);
+}
+
+
+static uint8_t
+file_byte_getter(Gif_Reader *grr)
+{
+ int i = getc(grr->f);
+ if (i != EOF) {
+ ++grr->pos;
+ return i;
+ } else
+ return 0;
+}
+
+static uint32_t
+file_block_getter(uint8_t *p, uint32_t s, Gif_Reader *grr)
+{
+ size_t nread = fread(p, 1, s, grr->f);
+ if (nread < s)
+ memset(p + nread, 0, s - nread);
+ grr->pos += nread;
+ return nread;
+}
+
+static int
+file_eofer(Gif_Reader *grr)
+{
+ int c = getc(grr->f);
+ if (c == EOF)
+ return 1;
+ else {
+ ungetc(c, grr->f);
+ return 0;
+ }
+}
+
+
+static uint8_t
+record_byte_getter(Gif_Reader *grr)
+{
+ if (grr->pos < grr->length)
+ return grr->v[grr->pos++];
+ else
+ return 0;
+}
+
+static uint32_t
+record_block_getter(uint8_t *p, uint32_t s, Gif_Reader *grr)
+{
+ uint32_t ncopy = (grr->pos + s <= grr->length ? s : grr->length - grr->pos);
+ memcpy(p, &grr->v[grr->pos], ncopy);
+ grr->pos += ncopy;
+ if (ncopy < s)
+ memset(p + ncopy, 0, s - ncopy);
+ return ncopy;
+}
+
+static int
+record_eofer(Gif_Reader *grr)
+{
+ return grr->pos == grr->length;
+}
+
+
+static void
+make_data_reader(Gif_Reader *grr, const uint8_t *data, uint32_t length)
+{
+ grr->v = data;
+ grr->pos = 0;
+ grr->length = length;
+ grr->is_record = 1;
+ grr->byte_getter = record_byte_getter;
+ grr->block_getter = record_block_getter;
+ grr->eofer = record_eofer;
+}
+
+
+static void
+gif_read_error(Gif_Context *gfc, int is_error, const char *text)
+{
+ Gif_ReadErrorHandler handler = gfc->handler ? gfc->handler : default_error_handler;
+ if (is_error >= 0)
+ gfc->errors[is_error > 0] += 1;
+ if (handler)
+ handler(gfc->stream, gfc->gfi, is_error, text);
+}
+
+
+static uint8_t
+one_code(Gif_Context *gfc, Gif_Code code)
+{
+ uint8_t *suffixes = gfc->suffix;
+ Gif_Code *prefixes = gfc->prefix;
+ uint8_t *ptr;
+ int lastsuffix = 0;
+ int codelength = gfc->length[code];
+
+ gfc->decodepos += codelength;
+ ptr = gfc->image + gfc->decodepos;
+ while (codelength > 0) {
+ lastsuffix = suffixes[code];
+ code = prefixes[code];
+ --ptr;
+ if (ptr < gfc->maximage)
+ *ptr = lastsuffix;
+ --codelength;
+ }
+
+ /* return the first pixel in the code, which, since we walked backwards
+ through the code, was the last suffix we processed. */
+ return lastsuffix;
+}
+
+static int
+read_image_block(Gif_Reader *grr, uint8_t *buffer, int *bit_pos_store,
+ int *bit_len_store, int bits_needed)
+{
+ int bit_position = *bit_pos_store;
+ int bit_length = *bit_len_store;
+ uint8_t block_len;
+
+ while (bit_position + bits_needed > bit_length) {
+ /* Read in the next data block. */
+ if (bit_position >= 8) {
+ /* Need to shift down the upper, unused part of 'buffer' */
+ int i = bit_position / 8;
+ buffer[0] = buffer[i];
+ buffer[1] = buffer[i+1];
+ bit_position -= i * 8;
+ bit_length -= i * 8;
+ }
+ block_len = gifgetbyte(grr);
+ GIF_DEBUG(("\nimage_block(%d) ", block_len));
+ if (block_len == 0) return 0;
+ gifgetblock(buffer + bit_length / 8, block_len, grr);
+ bit_length += block_len * 8;
+ }
+
+ *bit_pos_store = bit_position;
+ *bit_len_store = bit_length;
+ return 1;
+}
+
+
+static void
+read_image_data(Gif_Context *gfc, Gif_Reader *grr)
+{
+ /* we need a bit more than GIF_MAX_BLOCK in case a single code is split
+ across blocks */
+ uint8_t buffer[GIF_MAX_BLOCK + 5];
+ int i;
+ uint32_t accum;
+
+ int bit_position;
+ int bit_length;
+
+ Gif_Code code;
+ Gif_Code old_code;
+ Gif_Code clear_code; /* eoi_code == clear_code + 1 */
+ Gif_Code next_code;
+#define CUR_BUMP_CODE (1 << bits_needed)
+#define CUR_CODE_MASK ((1 << bits_needed) - 1)
+
+ int min_code_size;
+ int bits_needed;
+
+ gfc->decodepos = 0;
+
+ min_code_size = gifgetbyte(grr);
+ GIF_DEBUG(("\n\nmin_code_size(%d) ", min_code_size));
+ if (min_code_size >= GIF_MAX_CODE_BITS) {
+ gif_read_error(gfc, 1, "image corrupted, min_code_size too big");
+ min_code_size = GIF_MAX_CODE_BITS - 1;
+ } else if (min_code_size < 2) {
+ gif_read_error(gfc, 1, "image corrupted, min_code_size too small");
+ min_code_size = 2;
+ }
+ clear_code = 1 << min_code_size;
+ for (code = 0; code < clear_code; code++) {
+ gfc->prefix[code] = 49428;
+ gfc->suffix[code] = (uint8_t)code;
+ gfc->length[code] = 1;
+ }
+
+ next_code = clear_code + 1;
+ bits_needed = min_code_size + 1;
+
+ code = clear_code;
+
+ bit_length = bit_position = 0;
+ /* Thus the 'Read in the next data block.' code below will be invoked on the
+ first time through: exactly right! */
+
+ while (1) {
+
+ old_code = code;
+
+ /* GET A CODE INTO THE 'code' VARIABLE.
+ *
+ * 9.Dec.1998 - Rather than maintain a byte pointer and a bit offset into
+ * the current byte (and the processing associated with that), we maintain
+ * one number: the offset, in bits, from the beginning of 'buffer'. This
+ * much cleaner choice was inspired by Patrick J. Naughton
+ * 's GIF-reading code, which does the same thing.
+ * His code distributed as part of XV in xvgif.c. */
+
+ if (bit_position + bits_needed > bit_length)
+ /* Read in the next data block. */
+ if (!read_image_block(grr, buffer, &bit_position, &bit_length,
+ bits_needed))
+ goto zero_length_block;
+
+ i = bit_position / 8;
+ accum = buffer[i] + (buffer[i+1] << 8);
+ if (bits_needed >= 8)
+ accum |= (buffer[i+2]) << 16;
+ code = (Gif_Code)((accum >> (bit_position % 8)) & CUR_CODE_MASK);
+ bit_position += bits_needed;
+
+ GIF_DEBUG(("%d ", code));
+
+ /* CHECK FOR SPECIAL OR BAD CODES: clear_code, eoi_code, or a code that is
+ * too large. */
+ if (code == clear_code) {
+ GIF_DEBUG(("clear "));
+ bits_needed = min_code_size + 1;
+ next_code = clear_code + 1;
+ continue;
+
+ } else if (code == clear_code + 1)
+ break;
+
+ else if (code > next_code && next_code && next_code != clear_code) {
+ /* code > next_code: a (hopefully recoverable) error.
+
+ Bug fix, 5/27: Do this even if old_code == clear_code, and set code
+ to 0 to prevent errors later. (If we didn't zero code, we'd later set
+ old_code = code; then we had old_code >= next_code; so the prefixes
+ array got all screwed up!)
+
+ Bug fix, 4/12/2010: It is not an error if next_code == clear_code.
+ This happens at the end of a large GIF: see the next comment ("If no
+ meaningful next code should be defined...."). */
+ if (gfc->errors[1] < 20)
+ gif_read_error(gfc, 1, "image corrupted, code out of range");
+ else if (gfc->errors[1] == 20)
+ gif_read_error(gfc, 1, "(not reporting more errors)");
+ code = 0;
+ }
+
+ /* PROCESS THE CURRENT CODE and define the next code. If no meaningful
+ * next code should be defined, then we have set next_code to either
+ * 'eoi_code' or 'clear_code' -- so we'll store useless prefix/suffix data
+ * in a useless place. */
+
+ /* *First,* set up the prefix and length for the next code
+ (in case code == next_code). */
+ gfc->prefix[next_code] = old_code;
+ gfc->length[next_code] = gfc->length[old_code] + 1;
+
+ /* Use one_code to process code. It's nice that it returns the first
+ pixel in code: that's what we need. */
+ gfc->suffix[next_code] = one_code(gfc, code);
+
+ /* Special processing if code == next_code: we didn't know code's final
+ suffix when we called one_code, but we do now. */
+ /* 7.Mar.2014 -- Avoid error if image has zero width/height. */
+ if (code == next_code && gfc->image + gfc->decodepos <= gfc->maximage)
+ gfc->image[gfc->decodepos - 1] = gfc->suffix[next_code];
+
+ /* Increment next_code except for the 'clear_code' special case (that's
+ when we're reading at the end of a GIF) */
+ if (next_code != clear_code) {
+ next_code++;
+ if (next_code == CUR_BUMP_CODE) {
+ if (bits_needed < GIF_MAX_CODE_BITS)
+ bits_needed++;
+ else
+ next_code = clear_code;
+ }
+ }
+
+ }
+
+ /* read blocks until zero-length reached. */
+ i = gifgetbyte(grr);
+ GIF_DEBUG(("\nafter_image(%d)\n", i));
+ while (i > 0) {
+ gifgetblock(buffer, i, grr);
+ i = gifgetbyte(grr);
+ GIF_DEBUG(("\nafter_image(%d)\n", i));
+ }
+
+ /* zero-length block reached. */
+ zero_length_block: {
+ long delta = (long) (gfc->maximage - gfc->image) - (long) gfc->decodepos;
+ char buf[BUFSIZ];
+ if (delta > 0) {
+ snprintf(buf, sizeof(buf), "missing %ld %s of image data", delta,
+ delta == 1 ? "pixel" : "pixels");
+ gif_read_error(gfc, 1, buf);
+ memset(&gfc->image[gfc->decodepos], 0, delta);
+ } else if (delta < -1) {
+ /* One pixel of superfluous data is OK; that could be the
+ code == next_code case. */
+ snprintf(buf, sizeof(buf), "%ld superfluous pixels of image data", -delta);
+ gif_read_error(gfc, 0, buf);
+ }
+ }
+}
+
+
+static Gif_Colormap *
+read_color_table(int size, Gif_Reader *grr)
+{
+ Gif_Colormap *gfcm = Gif_NewFullColormap(size, size);
+ unsigned char buf[256 * 3], *bptr, *eptr;
+ size_t n;
+ Gif_Color *c;
+ if (!gfcm) return 0;
+ assert(gfcm->capacity >= 256 && size >= 0 && size <= 256);
+
+ GIF_DEBUG(("colormap(%d) ", size));
+ n = gifgetblock(buf, size * 3, grr);
+
+ /* always initialize 256 colors; colors not included in map are
+ initialized to black in case of bad input */
+ memset(&buf[n], 0, 256 * 3 - n);
+
+ for (bptr = buf, eptr = buf + (256 * 3), c = gfcm->col;
+ bptr != eptr;
+ bptr += 3, ++c) {
+ c->gfc_red = bptr[0];
+ c->gfc_green = bptr[1];
+ c->gfc_blue = bptr[2];
+ c->haspixel = 0;
+ }
+
+ return gfcm;
+}
+
+
+static int
+read_logical_screen_descriptor(Gif_Stream *gfs, Gif_Reader *grr)
+ /* returns 0 on memory error */
+{
+ uint8_t packed;
+
+ /* we don't care about logical screen width or height */
+ gfs->screen_width = gifgetunsigned(grr);
+ gfs->screen_height = gifgetunsigned(grr);
+
+ packed = gifgetbyte(grr);
+ gfs->background = gifgetbyte(grr);
+
+ /* don't care about pixel aspect ratio */
+ gifgetbyte(grr);
+
+ if (packed & 0x80) { /* have a global color table */
+ int ncol = 1 << ((packed & 0x07) + 1);
+ gfs->global = read_color_table(ncol, grr);
+ if (!gfs->global) return 0;
+ gfs->global->refcount = 1;
+ } else
+ gfs->background = 256;
+
+ return 1;
+}
+
+
+static int
+read_compressed_image(Gif_Image *gfi, Gif_Reader *grr, int read_flags)
+{
+ if (grr->is_record) {
+ const uint32_t image_pos = grr->pos;
+
+ /* scan over image */
+ ++grr->pos; /* skip min code size */
+ while (grr->pos < grr->length) {
+ int amt = grr->v[grr->pos];
+ grr->pos += amt + 1;
+ if (amt == 0)
+ break;
+ }
+ if (grr->pos > grr->length)
+ grr->pos = grr->length;
+
+ gfi->compressed_len = grr->pos - image_pos;
+ gfi->compressed_errors = 0;
+ if (read_flags & GIF_READ_CONST_RECORD) {
+ gfi->compressed = (uint8_t*) &grr->v[image_pos];
+ gfi->free_compressed = 0;
+ } else {
+ gfi->compressed = Gif_NewArray(uint8_t, gfi->compressed_len);
+ gfi->free_compressed = Gif_Free;
+ if (!gfi->compressed) return 0;
+ memcpy(gfi->compressed, &grr->v[image_pos], gfi->compressed_len);
+ }
+
+ } else {
+ /* non-record; have to read it block by block. */
+ uint32_t comp_cap = 1024;
+ uint32_t comp_len;
+ uint8_t *comp = Gif_NewArray(uint8_t, comp_cap);
+ int i;
+ if (!comp) return 0;
+
+ /* min code size */
+ i = gifgetbyte(grr);
+ comp[0] = i;
+ comp_len = 1;
+
+ i = gifgetbyte(grr);
+ while (i > 0) {
+ /* add 2 before check so we don't have to check after loop when appending
+ 0 block */
+ if (comp_len + i + 2 > comp_cap) {
+ comp_cap *= 2;
+ Gif_ReArray(comp, uint8_t, comp_cap);
+ if (!comp) return 0;
+ }
+ comp[comp_len] = i;
+ gifgetblock(comp + comp_len + 1, i, grr);
+ comp_len += i + 1;
+ i = gifgetbyte(grr);
+ }
+ comp[comp_len++] = 0;
+
+ gfi->compressed_len = comp_len;
+ gfi->compressed_errors = 0;
+ gfi->compressed = comp;
+ gfi->free_compressed = Gif_Free;
+ }
+
+ return 1;
+}
+
+
+static int
+uncompress_image(Gif_Context *gfc, Gif_Image *gfi, Gif_Reader *grr)
+{
+ int old_nerrors;
+ if (!Gif_CreateUncompressedImage(gfi, gfi->interlace))
+ return 0;
+ gfc->width = gfi->width;
+ gfc->height = gfi->height;
+ gfc->image = gfi->image_data;
+ gfc->maximage = gfi->image_data + (unsigned) gfi->width * (unsigned) gfi->height;
+ old_nerrors = gfc->errors[1];
+ read_image_data(gfc, grr);
+ gfi->compressed_errors = gfc->errors[1] - old_nerrors;
+ return 1;
+}
+
+
+int
+Gif_FullUncompressImage(Gif_Stream* gfs, Gif_Image* gfi,
+ Gif_ReadErrorHandler h)
+{
+ Gif_Context gfc;
+ Gif_Reader grr;
+ int ok = 0;
+
+ /* return right away if image is already uncompressed. this might screw over
+ people who expect re-uncompressing to restore the compressed version. */
+ if (gfi->img)
+ return 2;
+ if (gfi->image_data)
+ /* we have uncompressed data, but not an 'img' array;
+ this shouldn't happen */
+ return 0;
+
+ gfc.stream = gfs;
+ gfc.gfi = gfi;
+ gfc.prefix = Gif_NewArray(Gif_Code, GIF_MAX_CODE);
+ gfc.suffix = Gif_NewArray(uint8_t, GIF_MAX_CODE);
+ gfc.length = Gif_NewArray(uint16_t, GIF_MAX_CODE);
+ gfc.handler = h;
+ gfc.errors[0] = gfc.errors[1] = 0;
+
+ if (gfc.prefix && gfc.suffix && gfc.length && gfi->compressed) {
+ make_data_reader(&grr, gfi->compressed, gfi->compressed_len);
+ ok = uncompress_image(&gfc, gfi, &grr);
+ }
+
+ Gif_DeleteArray(gfc.prefix);
+ Gif_DeleteArray(gfc.suffix);
+ Gif_DeleteArray(gfc.length);
+ if (gfc.errors[0] || gfc.errors[1])
+ gif_read_error(&gfc, -1, 0);
+ return ok && !gfc.errors[1];
+}
+
+
+static int
+read_image(Gif_Reader *grr, Gif_Context *gfc, Gif_Image *gfi, int read_flags)
+ /* returns 0 on memory error */
+{
+ uint8_t packed;
+
+ gfi->left = gifgetunsigned(grr);
+ gfi->top = gifgetunsigned(grr);
+ gfi->width = gifgetunsigned(grr);
+ gfi->height = gifgetunsigned(grr);
+ /* Mainline GIF processors (Firefox, etc.) process missing width (height)
+ as screen_width (screen_height). */
+ if (gfi->width == 0)
+ gfi->width = gfc->stream->screen_width;
+ if (gfi->height == 0)
+ gfi->height = gfc->stream->screen_height;
+ /* If still zero, error. */
+ if (gfi->width == 0 || gfi->height == 0) {
+ gif_read_error(gfc, 1, "ignoring frame, zero width and/or height");
+ Gif_MakeImageEmpty(gfi);
+ read_flags = 0;
+ }
+ /* If position out of range, error. */
+ if ((unsigned) gfi->left + (unsigned) gfi->width > 0xFFFF
+ || (unsigned) gfi->top + (unsigned) gfi->height > 0xFFFF) {
+ gif_read_error(gfc, 1, "ignoring frame, violates 65535x65535 screen size limit");
+ Gif_MakeImageEmpty(gfi);
+ read_flags = 0;
+ }
+ GIF_DEBUG(("<%ux%u> ", gfi->width, gfi->height));
+
+ packed = gifgetbyte(grr);
+ if (packed & 0x80) { /* have a local color table */
+ int ncol = 1 << ((packed & 0x07) + 1);
+ gfi->local = read_color_table(ncol, grr);
+ if (!gfi->local) return 0;
+ gfi->local->refcount = 1;
+ }
+
+ gfi->interlace = (packed & 0x40) != 0;
+
+ /* Keep the compressed data if asked */
+ if (read_flags & GIF_READ_COMPRESSED) {
+ if (!read_compressed_image(gfi, grr, read_flags))
+ return 0;
+ if (read_flags & GIF_READ_UNCOMPRESSED) {
+ Gif_Reader new_grr;
+ make_data_reader(&new_grr, gfi->compressed, gfi->compressed_len);
+ if (!uncompress_image(gfc, gfi, &new_grr))
+ return 0;
+ }
+
+ } else if (read_flags & GIF_READ_UNCOMPRESSED) {
+ if (!uncompress_image(gfc, gfi, grr))
+ return 0;
+
+ } else {
+ /* skip over the image */
+ uint8_t buffer[GIF_MAX_BLOCK];
+ int i = gifgetbyte(grr);
+ while (i > 0) {
+ gifgetblock(buffer, i, grr);
+ i = gifgetbyte(grr);
+ }
+ }
+
+ return 1;
+}
+
+
+static void
+read_graphic_control_extension(Gif_Context *gfc, Gif_Image *gfi,
+ Gif_Reader *grr)
+{
+ uint8_t len;
+ uint8_t crap[GIF_MAX_BLOCK];
+
+ len = gifgetbyte(grr);
+
+ if (len == 4) {
+ uint8_t packed = gifgetbyte(grr);
+ gfi->disposal = (packed >> 2) & 0x07;
+ gfi->delay = gifgetunsigned(grr);
+ gfi->transparent = gifgetbyte(grr);
+ if (!(packed & 0x01)) /* transparent color doesn't exist */
+ gfi->transparent = -1;
+ len -= 4;
+ }
+
+ if (len > 0) {
+ gif_read_error(gfc, 1, "bad graphic extension");
+ gifgetblock(crap, len, grr);
+ }
+
+ len = gifgetbyte(grr);
+ while (len > 0) {
+ gif_read_error(gfc, 1, "bad graphic extension");
+ gifgetblock(crap, len, grr);
+ len = gifgetbyte(grr);
+ }
+}
+
+
+static char *last_name;
+
+
+static char *
+suck_data(char *data, int *store_len, Gif_Reader *grr)
+{
+ uint8_t len = gifgetbyte(grr);
+ int total_len = 0;
+
+ while (len > 0) {
+ Gif_ReArray(data, char, total_len + len + 1);
+ if (!data) return 0;
+ gifgetblock((uint8_t *)data + total_len, len, grr);
+
+ total_len += len;
+ data[total_len] = 0;
+
+ len = gifgetbyte(grr);
+ }
+
+ if (store_len) *store_len = total_len;
+ return data;
+}
+
+
+static int
+read_unknown_extension(Gif_Context* gfc, Gif_Reader* grr,
+ int kind, char* appname, int applength)
+{
+ uint8_t block_len = gifgetbyte(grr);
+ uint8_t* data = 0;
+ int data_len = 0;
+ Gif_Extension *gfex = 0;
+
+ while (block_len > 0) {
+ Gif_ReArray(data, uint8_t, data_len + block_len + 2);
+ if (!data)
+ goto done;
+ data[data_len] = block_len;
+ gifgetblock(data + data_len + 1, block_len, grr);
+ data_len += block_len + 1;
+ block_len = gifgetbyte(grr);
+ }
+
+ if (data)
+ gfex = Gif_NewExtension(kind, appname, applength);
+ if (gfex) {
+ gfex->data = data;
+ gfex->free_data = Gif_Free;
+ gfex->length = data_len;
+ gfex->packetized = 1;
+ data[data_len] = 0;
+ Gif_AddExtension(gfc->stream, gfc->gfi, gfex);
+ }
+
+ done:
+ if (!gfex)
+ Gif_DeleteArray(data);
+ while (block_len > 0) {
+ uint8_t buffer[GIF_MAX_BLOCK];
+ gifgetblock(buffer, block_len, grr);
+ block_len = gifgetbyte(grr);
+ }
+ return gfex != 0;
+}
+
+
+static int
+read_application_extension(Gif_Context *gfc, Gif_Reader *grr)
+{
+ Gif_Stream *gfs = gfc->stream;
+ uint8_t buffer[GIF_MAX_BLOCK + 1];
+ uint8_t len = gifgetbyte(grr);
+ gifgetblock(buffer, len, grr);
+
+ /* Read the Netscape loop extension. */
+ if (len == 11
+ && (memcmp(buffer, "NETSCAPE2.0", 11) == 0
+ || memcmp(buffer, "ANIMEXTS1.0", 11) == 0)) {
+
+ len = gifgetbyte(grr);
+ if (len == 3) {
+ gifgetbyte(grr); /* throw away the 1 */
+ gfs->loopcount = gifgetunsigned(grr);
+ len = gifgetbyte(grr);
+ if (len)
+ gif_read_error(gfc, 1, "bad loop extension");
+ } else
+ gif_read_error(gfc, 1, "bad loop extension");
+
+ while (len > 0) {
+ gifgetblock(buffer, len, grr);
+ len = gifgetbyte(grr);
+ }
+ return 1;
+
+ } else
+ return read_unknown_extension(gfc, grr, 0xFF, (char*)buffer, len);
+}
+
+
+static int
+read_comment_extension(Gif_Image *gfi, Gif_Reader *grr)
+{
+ int len;
+ Gif_Comment *gfcom = gfi->comment;
+ char *m = suck_data(0, &len, grr);
+ if (m) {
+ if (!gfcom)
+ gfcom = gfi->comment = Gif_NewComment();
+ if (!gfcom || !Gif_AddCommentTake(gfcom, m, len))
+ return 0;
+ }
+ return 1;
+}
+
+
+static Gif_Stream *
+read_gif(Gif_Reader *grr, int read_flags,
+ const char* landmark, Gif_ReadErrorHandler handler)
+{
+ Gif_Stream *gfs;
+ Gif_Image *gfi;
+ Gif_Context gfc;
+ int unknown_block_type = 0;
+
+ if (gifgetc(grr) != 'G' ||
+ gifgetc(grr) != 'I' ||
+ gifgetc(grr) != 'F')
+ return 0;
+ (void)gifgetc(grr);
+ (void)gifgetc(grr);
+ (void)gifgetc(grr);
+
+ gfs = Gif_NewStream();
+ gfi = Gif_NewImage();
+
+ gfc.stream = gfs;
+ gfc.prefix = Gif_NewArray(Gif_Code, GIF_MAX_CODE);
+ gfc.suffix = Gif_NewArray(uint8_t, GIF_MAX_CODE);
+ gfc.length = Gif_NewArray(uint16_t, GIF_MAX_CODE);
+ gfc.handler = handler;
+ gfc.gfi = gfi;
+ gfc.errors[0] = gfc.errors[1] = 0;
+
+ if (!gfs || !gfi || !gfc.prefix || !gfc.suffix || !gfc.length)
+ goto done;
+ gfs->landmark = landmark;
+
+ GIF_DEBUG(("\nGIF "));
+ if (!read_logical_screen_descriptor(gfs, grr))
+ goto done;
+ GIF_DEBUG(("logscrdesc "));
+
+ while (!gifeof(grr)) {
+
+ uint8_t block = gifgetbyte(grr);
+
+ switch (block) {
+
+ case ',': /* image block */
+ GIF_DEBUG(("imageread %d ", gfs->nimages));
+
+ gfi->identifier = last_name;
+ last_name = 0;
+ if (!Gif_AddImage(gfs, gfi))
+ goto done;
+ else if (!read_image(grr, &gfc, gfi, read_flags)) {
+ Gif_RemoveImage(gfs, gfs->nimages - 1);
+ gfi = 0;
+ goto done;
+ }
+
+ gfc.gfi = gfi = Gif_NewImage();
+ if (!gfi)
+ goto done;
+ break;
+
+ case ';': /* terminator */
+ GIF_DEBUG(("term\n"));
+ goto done;
+
+ case '!': /* extension */
+ block = gifgetbyte(grr);
+ GIF_DEBUG(("ext(0x%02X) ", block));
+ switch (block) {
+
+ case 0xF9:
+ read_graphic_control_extension(&gfc, gfi, grr);
+ break;
+
+ case 0xCE:
+ last_name = suck_data(last_name, 0, grr);
+ break;
+
+ case 0xFE:
+ if (!read_comment_extension(gfi, grr)) goto done;
+ break;
+
+ case 0xFF:
+ read_application_extension(&gfc, grr);
+ break;
+
+ default:
+ read_unknown_extension(&gfc, grr, block, 0, 0);
+ break;
+
+ }
+ break;
+
+ default:
+ if (!unknown_block_type) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "unknown block type %d at file offset %u",
+ block, grr->pos - 1);
+ gif_read_error(&gfc, 1, buf);
+ }
+ if (++unknown_block_type > 20)
+ goto done;
+ break;
+
+ }
+
+ }
+
+ done:
+
+ /* Move comments and extensions after last image into stream. */
+ if (gfs && gfi) {
+ Gif_Extension* gfex;
+ gfs->end_comment = gfi->comment;
+ gfi->comment = 0;
+ gfs->end_extension_list = gfi->extension_list;
+ gfi->extension_list = 0;
+ for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next)
+ gfex->image = NULL;
+ }
+
+ Gif_DeleteImage(gfi);
+ Gif_DeleteArray(last_name);
+ Gif_DeleteArray(gfc.prefix);
+ Gif_DeleteArray(gfc.suffix);
+ Gif_DeleteArray(gfc.length);
+ gfc.gfi = 0;
+ last_name = 0;
+
+ if (gfs)
+ gfs->errors = gfc.errors[1];
+ if (gfs && gfc.errors[1] == 0
+ && !(read_flags & GIF_READ_TRAILING_GARBAGE_OK)
+ && !grr->eofer(grr))
+ gif_read_error(&gfc, 0, "trailing garbage after GIF ignored");
+ /* finally, export last message */
+ gif_read_error(&gfc, -1, 0);
+
+ return gfs;
+}
+
+
+Gif_Stream *
+Gif_FullReadFile(FILE *f, int read_flags,
+ const char* landmark, Gif_ReadErrorHandler h)
+{
+ Gif_Reader grr;
+ if (!f) return 0;
+ grr.f = f;
+ grr.pos = 0;
+ grr.is_record = 0;
+ grr.byte_getter = file_byte_getter;
+ grr.block_getter = file_block_getter;
+ grr.eofer = file_eofer;
+ return read_gif(&grr, read_flags, landmark, h);
+}
+
+Gif_Stream *
+Gif_FullReadRecord(const Gif_Record *gifrec, int read_flags,
+ const char* landmark, Gif_ReadErrorHandler h)
+{
+ Gif_Reader grr;
+ if (!gifrec) return 0;
+ make_data_reader(&grr, gifrec->data, gifrec->length);
+ if (read_flags & GIF_READ_CONST_RECORD)
+ read_flags |= GIF_READ_COMPRESSED;
+ return read_gif(&grr, read_flags, landmark, h);
+}
+
+
+#undef Gif_ReadFile
+#undef Gif_ReadRecord
+
+Gif_Stream *
+Gif_ReadFile(FILE *f)
+{
+ return Gif_FullReadFile(f, GIF_READ_UNCOMPRESSED, 0, 0);
+}
+
+Gif_Stream *
+Gif_ReadRecord(const Gif_Record *gifrec)
+{
+ return Gif_FullReadRecord(gifrec, GIF_READ_UNCOMPRESSED, 0, 0);
+}
+
+void
+Gif_SetErrorHandler(Gif_ReadErrorHandler handler) {
+ default_error_handler = handler;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/gifsicle.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifsicle.c
new file mode 100644
index 0000000..6b9fe8e
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifsicle.c
@@ -0,0 +1,2236 @@
+/* -*- c-basic-offset: 2 -*- */
+/* gifsicle.c - gifsicle's main loop.
+ Copyright (C) 1997-2025 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) {
+ size_t namelen = strlen(main_name) + 10;
+ component_namebuf = (char*) malloc(namelen);
+ snprintf(component_namebuf, namelen, "%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 exact)
+{
+ 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);
+
+ def_output_data.colormap_fixed_exact = exact;
+}
+
+static void
+do_colormap_change(Gif_Stream *gfs)
+{
+ if (active_output_data.colormap_fixed)
+ colormap_stream(gfs, active_output_data.colormap_fixed,
+ &active_output_data, !active_output_data.colormap_fixed_exact);
+
+ 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, 1);
+
+ 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;
+ }
+ kc_set_gamma(active_output_data.colormap_gamma_type,
+ active_output_data.colormap_gamma);
+ 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_mode = 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_fixed_exact = 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_mode = def_output_data.screen_mode;
+ 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;
+ active_output_data.colormap_fixed_exact = def_output_data.colormap_fixed_exact;
+ }
+ 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.colormap_fixed_exact = 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_mode = -1;
+ def_output_data.screen_width = def_output_data.screen_height = 0;
+ } else {
+ def_output_data.screen_mode = 1;
+ 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_mode = 0;
+ 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", 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->vstr, 0);
+ break;
+
+ case USE_EXACT_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, 1);
+ 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 (clp->negated) {
+ MARK_CH(output, CH_GAMMA);
+ def_output_data.colormap_gamma_type = KC_GAMMA_NUMERIC;
+ def_output_data.colormap_gamma = 1.0;
+ break;
+ }
+ if (strlen(clp->val.s) == 4
+ && tolower((unsigned char) clp->val.s[0]) == 's'
+ && tolower((unsigned char) clp->val.s[1]) == 'r'
+ && tolower((unsigned char) clp->val.s[2]) == 'g'
+ && tolower((unsigned char) clp->val.s[3]) == 'b') {
+ MARK_CH(output, CH_GAMMA);
+ def_output_data.colormap_gamma_type = KC_GAMMA_SRGB;
+ break;
+ }
+#if HAVE_CBRTF
+ if (strlen(clp->val.s) == 5
+ && tolower((unsigned char) clp->val.s[0]) == 'o'
+ && tolower((unsigned char) clp->val.s[1]) == 'k'
+ && tolower((unsigned char) clp->val.s[2]) == 'l'
+ && tolower((unsigned char) clp->val.s[3]) == 'a'
+ && tolower((unsigned char) clp->val.s[4]) == 'b') {
+ MARK_CH(output, CH_GAMMA);
+ def_output_data.colormap_gamma_type = KC_GAMMA_OKLAB;
+ break;
+ }
+#endif
+#if HAVE_POW
+ {
+ char* ends;
+ double gamma = strtod(clp->val.s, &ends);
+ if (*clp->val.s && !*ends && !isspace((unsigned char) *clp->val.s)) {
+ MARK_CH(output, CH_GAMMA);
+ def_output_data.colormap_gamma_type = KC_GAMMA_NUMERIC;
+ def_output_data.colormap_gamma = gamma;
+ break;
+ }
+ }
+#endif
+ Clp_OptionError(clp, "%O not supported");
+ 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;
+
+ case LOSSY_OPT:
+ if (clp->have_val)
+ gif_write_info.loss = clp->val.i;
+ else
+ gif_write_info.loss = 20;
+ 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-2025 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(clp);
+ 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(clp);
+ 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);
+}
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/gifsicle.h b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifsicle.h
new file mode 100644
index 0000000..55b2f85
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifsicle.h
@@ -0,0 +1,350 @@
+/* gifsicle.h - Function declarations for gifsicle.
+ Copyright (C) 1997-2025 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. */
+
+#ifndef GIFSICLE_H
+#define GIFSICLE_H
+#include
+#include
+#ifdef __GNUC__
+#define NORETURN __attribute__ ((noreturn))
+#define USED_ATTR __attribute__ ((used))
+#else
+#define NORETURN
+#define USED_ATTR
+#endif
+
+typedef struct Gt_Frameset Gt_Frameset;
+typedef struct Gt_Crop Gt_Crop;
+typedef struct Gt_ColorTransform Gt_ColorTransform;
+
+#if ENABLE_THREADS
+#include
+extern pthread_mutex_t kd3_sort_lock;
+#endif
+
+typedef struct Gt_Frame {
+
+ Gif_Stream *stream;
+ Gif_Image *image;
+ int use;
+
+ const char *name;
+ int no_name;
+ Gif_Comment *comment;
+ int no_comments;
+
+ Gif_Color transparent; /* also background */
+ int interlacing;
+ int left;
+ int top;
+
+ Gt_Crop *crop;
+ int left_offset;
+ int top_offset;
+
+ int delay;
+ int disposal;
+
+ Gt_Frameset *nest;
+ int explode_by_name;
+
+ int no_extensions;
+ int no_app_extensions;
+ Gif_Extension *extensions;
+
+ unsigned flip_horizontal: 1;
+ unsigned flip_vertical: 1;
+ unsigned info_flags: 3;
+ unsigned position_is_offset: 1;
+ unsigned total_crop: 1;
+ unsigned rotation;
+
+ const char *input_filename;
+
+} Gt_Frame;
+
+
+struct Gt_Frameset {
+ int count;
+ int cap;
+ Gt_Frame *f;
+};
+
+
+struct Gt_Crop {
+ int ready;
+ int transparent_edges;
+ int spec_x;
+ int spec_y;
+ int spec_w;
+ int spec_h;
+ int x;
+ int y;
+ int w;
+ int h;
+ int left_offset;
+ int top_offset;
+};
+
+
+typedef void (*colormap_transform_func)(Gif_Colormap *, void *);
+
+struct Gt_ColorTransform {
+ Gt_ColorTransform *prev;
+ Gt_ColorTransform *next;
+ colormap_transform_func func;
+ void *data;
+};
+
+
+typedef struct {
+
+ const char *output_name;
+ const char *active_output_name;
+
+ int screen_mode;
+ int screen_width;
+ int screen_height;
+
+ Gif_Color background;
+ int loopcount;
+
+ int colormap_size;
+ Gif_Colormap *colormap_fixed;
+ int colormap_fixed_exact;
+ int colormap_algorithm;
+ int colormap_needs_transparency;
+ int dither_type;
+ const uint8_t* dither_data;
+ const char* dither_name;
+ int colormap_gamma_type;
+ double colormap_gamma;
+
+ int optimizing;
+
+ int scaling;
+ int resize_width;
+ int resize_height;
+ int resize_flags;
+ double scale_x;
+ double scale_y;
+ int scale_method;
+ int scale_colors;
+
+ int conserve_memory;
+
+} Gt_OutputData;
+
+extern Gt_OutputData active_output_data;
+extern Clp_Parser* clp;
+
+#define GT_SCALING_NONE 0
+#define GT_SCALING_RESIZE 1
+#define GT_SCALING_SCALE 2
+
+#define GT_RESIZE_FIT 1
+#define GT_RESIZE_FIT_DOWN 2
+#define GT_RESIZE_FIT_UP 4
+#define GT_RESIZE_MIN_DIMEN 8
+
+#define SCALE_METHOD_POINT 0
+#define SCALE_METHOD_BOX 1
+#define SCALE_METHOD_MIX 2
+#define SCALE_METHOD_CATROM 3
+#define SCALE_METHOD_LANCZOS2 4
+#define SCALE_METHOD_LANCZOS3 5
+#define SCALE_METHOD_MITCHELL 6
+
+#define GT_OPT_MASK 0xFFFF
+#define GT_OPT_KEEPEMPTY 0x10000
+
+
+/*****
+ * helper
+ **/
+
+static inline int
+constrain(int low, int x, int high)
+{
+ return x < low ? low : (x < high ? x : high);
+}
+
+
+/*****
+ * error & verbose
+ **/
+extern const char *program_name;
+extern int verbosing;
+extern int error_count;
+extern int no_warnings;
+extern int thread_count;
+extern Gif_CompressInfo gif_write_info;
+
+void fatal_error(const char* format, ...) NORETURN;
+void warning(int need_file, const char* format, ...);
+void lwarning(const char* landmark, const char* format, ...);
+void error(int need_file, const char* format, ...);
+void lerror(const char* landmark, const char* format, ...);
+void clp_error_handler(Clp_Parser *clp, const char *clp_message);
+void usage(Clp_Parser* clp);
+void short_usage(Clp_Parser* clp);
+
+void verbose_open(char, const char *);
+void verbose_close(char);
+void verbose_endline(void);
+
+const char* debug_color_str(const Gif_Color* gfc);
+
+#define EXIT_OK 0
+#define EXIT_ERR 1
+#define EXIT_USER_ERR 1
+
+/*****
+ * info &c
+ **/
+#define INFO_COLORMAPS 1
+#define INFO_EXTENSIONS 2
+#define INFO_SIZES 4
+void stream_info(FILE *f, Gif_Stream *gfs, const char *filename, int flags);
+void image_info(FILE *f, Gif_Stream *gfs, Gif_Image *gfi, int flags);
+
+char *explode_filename(const char *filename, int number,
+ const char *name, int max_nimg);
+
+/*****
+ * merging images
+ **/
+void unmark_colors(Gif_Colormap *);
+void unmark_colors_2(Gif_Colormap *);
+void mark_used_colors(Gif_Stream *gfs, Gif_Image *gfi, Gt_Crop *crop,
+ int compress_immediately);
+int find_color_index(Gif_Color *c, int nc, Gif_Color *);
+int merge_colormap_if_possible(Gif_Colormap *, Gif_Colormap *);
+
+extern int warn_local_colormaps;
+void merge_stream(Gif_Stream *dest, Gif_Stream *src, int no_comments);
+void merge_comments(Gif_Comment *destc, Gif_Comment *srcc);
+Gif_Image* merge_image(Gif_Stream* dest, Gif_Stream* src, Gif_Image* srci,
+ Gt_Frame* srcfr, int same_compressed_ok);
+
+void optimize_fragments(Gif_Stream *, int optimizeness, int huge_stream);
+
+/*****
+ * image/colormap transformations
+ **/
+Gif_Colormap *read_colormap_file(const char *, FILE *);
+void apply_color_transforms(Gt_ColorTransform *, Gif_Stream *);
+
+typedef void (*color_transform_func)(Gif_Colormap *, void *);
+Gt_ColorTransform *append_color_transform
+ (Gt_ColorTransform *list, color_transform_func, void *);
+Gt_ColorTransform *delete_color_transforms
+ (Gt_ColorTransform *list, color_transform_func);
+
+void color_change_transformer(Gif_Colormap *, void *);
+Gt_ColorTransform *append_color_change
+ (Gt_ColorTransform *list, Gif_Color, Gif_Color);
+
+void pipe_color_transformer(Gif_Colormap *, void *);
+
+void combine_crop(Gt_Crop *dstcrop, const Gt_Crop *srccrop, const Gif_Image *gfi);
+int crop_image(Gif_Image* gfi, Gt_Frame* fr, int preserve_total_crop);
+
+void flip_image(Gif_Image* gfi, Gt_Frame* fr, int is_vert);
+void rotate_image(Gif_Image* gfi, Gt_Frame* fr, int rotation);
+void resize_dimensions(int* w, int* h, double new_width, double new_height,
+ int flags);
+void resize_stream(Gif_Stream* gfs, double new_width, double new_height,
+ int flags, int method, int scale_colors);
+
+/*****
+ * quantization
+ **/
+#define KC_GAMMA_SRGB 0
+#define KC_GAMMA_NUMERIC 1
+#define KC_GAMMA_OKLAB 2
+void kc_set_gamma(int type, double gamma);
+
+#define COLORMAP_DIVERSITY 0
+#define COLORMAP_BLEND_DIVERSITY 1
+#define COLORMAP_MEDIAN_CUT 2
+
+enum {
+ dither_none = 0, dither_default, dither_floyd_steinberg,
+ dither_ordered, dither_ordered_new, dither_atkinson
+};
+int set_dither_type(Gt_OutputData* od, const char* name);
+void colormap_stream(Gif_Stream*, Gif_Colormap*, Gt_OutputData*, int allow_shrink);
+
+struct kchist;
+Gif_Colormap* colormap_blend_diversity(struct kchist* kch, Gt_OutputData* od);
+Gif_Colormap* colormap_flat_diversity(struct kchist* kch, Gt_OutputData* od);
+Gif_Colormap* colormap_median_cut(struct kchist* kch, Gt_OutputData* od);
+
+
+/*****
+ * parsing stuff
+ **/
+extern int frame_spec_1;
+extern int frame_spec_2;
+extern char * frame_spec_name;
+extern int dimensions_x;
+extern int dimensions_y;
+extern int position_x;
+extern int position_y;
+extern Gif_Color parsed_color;
+extern Gif_Color parsed_color2;
+extern double parsed_scale_factor_x;
+extern double parsed_scale_factor_y;
+
+int parse_frame_spec(Clp_Parser*, const char*, int, void*);
+int parse_dimensions(Clp_Parser*, const char*, int, void*);
+int parse_position(Clp_Parser*, const char*, int, void*);
+int parse_scale_factor(Clp_Parser*, const char*, int, void*);
+int parse_color(Clp_Parser*, const char*, int, void*);
+int parse_rectangle(Clp_Parser*, const char*, int, void*);
+int parse_two_colors(Clp_Parser*, const char*, int, void*);
+
+extern Gif_Stream *input;
+extern const char *input_name;
+
+void input_stream(const char*);
+void input_done(void);
+void output_frames(void);
+
+/*****
+ * stuff with frames
+ **/
+extern Gt_Frame def_frame;
+#define FRAME(fs, i) ((fs)->f[i])
+
+Gt_Frameset * new_frameset(int initial_cap);
+Gt_Frame* add_frame(Gt_Frameset*, Gif_Stream*, Gif_Image*);
+void clear_def_frame_once_options(void);
+
+Gif_Stream * merge_frame_interval(Gt_Frameset*, int f1, int f2,
+ Gt_OutputData*, int compress, int *huge);
+void clear_frameset(Gt_Frameset*, int from);
+void blank_frameset(Gt_Frameset*, int from, int to, int delete_ob);
+
+/*****
+ * mode
+ **/
+#define BLANK_MODE 0
+#define MERGING 1
+#define BATCHING 2
+#define EXPLODING 3
+#define INFOING 4
+#define DELETING 5
+#define INSERTING 6
+
+extern int mode;
+extern int nested_mode;
+
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/giftoc.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/giftoc.c
new file mode 100644
index 0000000..2f5faec
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/giftoc.c
@@ -0,0 +1,222 @@
+#if HAVE_CONFIG_H
+# include
+#endif
+#ifndef PATHNAME_SEPARATOR
+# define PATHNAME_SEPARATOR '/'
+#endif
+#include
+#include
+#include
+#include
+#include
+
+int is_static = 1;
+int is_const = 1;
+
+static void *
+fmalloc(int size)
+{
+ void *p = malloc(size);
+ if (!p) {
+ fputs("giftoc: Out of memory!\n", stderr);
+ exit(1);
+ }
+ return p;
+}
+
+void
+print_reckless(FILE *f, char *gifrecname)
+{
+ unsigned long size = 0;
+ int c;
+ int lasthex = 0;
+
+ printf("\n%sGifRecord %s = { (unsigned char *)\"",
+ is_static ? "static " : "", gifrecname);
+ size = 0;
+ c = getc(f);
+ while (c != EOF) {
+
+ if (size % 60 == 0) printf("\"\n \"");
+
+ switch (c) {
+
+ case '\\':
+ fputs("\\\\", stdout);
+ lasthex = 0;
+ break;
+
+ case '\"':
+ fputs("\\\"", stdout);
+ lasthex = 0;
+ break;
+
+ case '\b':
+ fputs("\\b", stdout);
+ lasthex = 0;
+ break;
+
+ case '\r':
+ fputs("\\r", stdout);
+ lasthex = 0;
+ break;
+
+ case '\n':
+ fputs("\\n", stdout);
+ lasthex = 0;
+ break;
+
+ case '\f':
+ fputs("\\f", stdout);
+ lasthex = 0;
+ break;
+
+ case '\t':
+ fputs("\\t", stdout);
+ lasthex = 0;
+ break;
+
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6':
+ case '7':
+ if (lasthex)
+ printf("\\%o", c);
+ else
+ putchar(c);
+ break;
+
+ default:
+ if (isprint(c)) {
+ putchar(c);
+ lasthex = 0;
+ } else {
+ printf("\\%o", c);
+ lasthex = 1;
+ }
+ break;
+
+ }
+
+ size++;
+ c = getc(f);
+ }
+
+ printf("\",\n %lu\n};\n", size);
+}
+
+void
+print_unreckless(FILE *f, char *gifrecname)
+{
+ unsigned long size = 0;
+ int c;
+
+ printf("\nstatic %sunsigned char %s_data[] = {",
+ (is_const ? "const " : ""), gifrecname);
+ size = 0;
+ c = getc(f);
+ while (c != EOF) {
+ if (size % 20 == 0) printf("\n");
+ printf("%d,", c);
+ size++;
+ c = getc(f);
+ }
+ printf("};\n%s%sGif_Record %s = { %s_data, %lu };\n",
+ (is_static ? "static " : ""),
+ (is_const ? "const " : ""),
+ gifrecname, gifrecname, size);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int reckless = 0;
+ int make_name = 0;
+ const char *directory = "";
+
+ argc--, argv++;
+
+ while (argv[0] && argv[0][0] == '-') {
+ if (!strcmp(argv[0], "-reckless"))
+ reckless = 1, argc--, argv++;
+ else if (!strcmp(argv[0], "-static"))
+ is_static = 1, argc--, argv++;
+ else if (!strcmp(argv[0], "-extern"))
+ is_static = 0, argc--, argv++;
+ else if (!strcmp(argv[0], "-makename"))
+ make_name = 1, argc--, argv++;
+ else if (!strcmp(argv[0], "-nonconst"))
+ is_const = 0, argc--, argv++;
+ else if (!strcmp(argv[0], "-const"))
+ is_const = 1, argc--, argv++;
+ else if (!strcmp(argv[0], "-dir") && argc > 1) {
+ directory = argv[1], argc -= 2, argv += 2;
+ /* make sure directory is slash-terminated */
+ if (directory[ strlen(directory) - 1 ] != PATHNAME_SEPARATOR
+ && directory[0]) {
+ size_t len = strlen(directory) + 2;
+ char *ndirectory = (char *)fmalloc(len);
+ snprintf(ndirectory, len, "%s%c", directory, PATHNAME_SEPARATOR);
+ directory = ndirectory;
+ }
+ } else
+ break;
+ }
+
+ if ((!make_name && argc % 2 != 0)
+ || argc < 1
+ || (argv[0] && argv[0][0] == '-')) {
+ fprintf(stderr, "\
+usage: giftoc [OPTIONS] FILE NAME [FILE NAME...]\n\
+or: giftoc -makename [OPTIONS] FILE [FILE...]\n\
+ OPTIONS are -reckless, -extern, -nonconst, -dir DIR\n");
+ exit(1);
+ }
+
+ if (!is_static)
+ printf("#include \"config.h\"\n#include \n\n");
+
+ for (; argc > 0; argc--, argv++) {
+ FILE *f;
+ char *rec_name = 0;
+ char *file_name = (char *)fmalloc(strlen(argv[0]) + strlen(directory) + 1);
+
+ strcpy(file_name, directory);
+ strcat(file_name, argv[0]);
+ f = fopen(file_name, "rb");
+
+ if (!f) {
+ fprintf(stderr, "%s: %s\n", file_name, strerror(errno));
+ goto done;
+ }
+
+ if (make_name) {
+ char *sin, *sout;
+ sin = strrchr(file_name, PATHNAME_SEPARATOR) + 1;
+ if (!sin) sin = file_name;
+ sout = rec_name = (char *)fmalloc(strlen(sin) + 2);
+ if (isdigit(*sin)) *sout++ = 'N';
+ for (; *sin; sin++, sout++)
+ if (isalnum(*sin))
+ *sout = *sin;
+ else
+ *sout = '_';
+ *sout = 0;
+
+ } else {
+ argv++, argc--;
+ rec_name = argv[0];
+ }
+
+ if (reckless) print_reckless(f, rec_name);
+ else print_unreckless(f, rec_name);
+
+ done:
+ if (make_name)
+ free(rec_name);
+ free(file_name);
+ if (f)
+ fclose(f);
+ }
+
+ exit(0);
+
+}
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/gifunopt.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifunopt.c
new file mode 100644
index 0000000..e8b68fb
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifunopt.c
@@ -0,0 +1,238 @@
+/* gifunopt.c - Unoptimization function for the GIF library.
+ Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com
+ This file is part of the LCDF GIF library.
+
+ The LCDF GIF library is free software. It is distributed under the GNU
+ General 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. */
+
+#if HAVE_CONFIG_H
+# include
+#endif
+#include
+#include
+#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TRANSPARENT 256
+
+static void
+put_image_in_screen(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen)
+{
+ int transparent = gfi->transparent;
+ unsigned x, y;
+ unsigned w = gfi->width;
+ unsigned h = gfi->height;
+ if (gfi->left + w > gfs->screen_width)
+ w = gfs->screen_width - gfi->left;
+ if (gfi->top + h > gfs->screen_height)
+ h = gfs->screen_height - gfi->top;
+
+ for (y = 0; y < h; y++) {
+ uint16_t *move = screen + (unsigned) gfs->screen_width * (y + gfi->top) + gfi->left;
+ uint8_t *line = gfi->img[y];
+ for (x = 0; x < w; x++, move++, line++)
+ if (*line != transparent)
+ *move = *line;
+ }
+}
+
+
+static void
+put_background_in_screen(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen)
+{
+ uint16_t solid;
+ unsigned x, y;
+ unsigned w = gfi->width;
+ unsigned h = gfi->height;
+ if (gfi->left + w > gfs->screen_width) w = gfs->screen_width - gfi->left;
+ if (gfi->top + h > gfs->screen_height) h = gfs->screen_height - gfi->top;
+
+ if (gfi->transparent < 0 && gfs->images[0]->transparent < 0
+ && gfs->global && gfs->background < gfs->global->ncol)
+ solid = gfs->background;
+ else
+ solid = TRANSPARENT;
+
+ for (y = 0; y < h; y++) {
+ uint16_t *move = screen + (unsigned) gfs->screen_width * (y + gfi->top) + gfi->left;
+ for (x = 0; x < w; x++, move++)
+ *move = solid;
+ }
+}
+
+
+static int
+create_image_data(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen,
+ uint8_t *new_data, int *used_transparent)
+{
+ int have[257];
+ int transparent = -1;
+ unsigned pos, size = (unsigned) gfs->screen_width * (unsigned) gfs->screen_height;
+ uint16_t *move;
+ int i;
+
+ /* mark colors used opaquely in the image */
+ assert(TRANSPARENT == 256);
+ for (i = 0; i < 257; i++)
+ have[i] = 0;
+ for (pos = 0, move = screen; pos != size; ++pos, move++)
+ have[*move] = 1;
+
+ /* the new transparent color is a color unused in either */
+ if (have[TRANSPARENT]) {
+ for (i = 0; i < 256 && transparent < 0; i++)
+ if (!have[i])
+ transparent = i;
+ if (transparent < 0)
+ goto error;
+ if (transparent >= gfs->global->ncol) {
+ Gif_ReArray(gfs->global->col, Gif_Color, 256);
+ if (!gfs->global->col) goto error;
+ gfs->global->ncol = transparent + 1;
+ }
+ }
+
+ /* map the wide image onto the new data */
+ *used_transparent = 0;
+ for (pos = 0, move = screen; pos != size; ++pos, move++, new_data++)
+ if (*move == TRANSPARENT) {
+ *new_data = transparent;
+ *used_transparent = 1;
+ } else
+ *new_data = *move;
+
+ gfi->transparent = transparent;
+ return 1;
+
+ error:
+ return 0;
+}
+
+
+static int
+unoptimize_image(Gif_Stream *gfs, Gif_Image *gfi, uint16_t *screen)
+{
+ unsigned size = (unsigned) gfs->screen_width * (unsigned) gfs->screen_height;
+ int used_transparent;
+ uint8_t *new_data = Gif_NewArray(uint8_t, size);
+ uint16_t *new_screen = screen;
+ if (!new_data) return 0;
+
+ /* Oops! May need to uncompress it */
+ Gif_UncompressImage(gfs, gfi);
+ Gif_ReleaseCompressedImage(gfi);
+
+ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
+ new_screen = Gif_NewArray(uint16_t, size);
+ if (!new_screen) return 0;
+ memcpy(new_screen, screen, size * sizeof(uint16_t));
+ }
+
+ put_image_in_screen(gfs, gfi, new_screen);
+ if (!create_image_data(gfs, gfi, new_screen, new_data, &used_transparent)) {
+ Gif_DeleteArray(new_data);
+ return 0;
+ }
+
+ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS)
+ Gif_DeleteArray(new_screen);
+ else if (gfi->disposal == GIF_DISPOSAL_BACKGROUND)
+ put_background_in_screen(gfs, gfi, screen);
+
+ gfi->left = 0;
+ gfi->top = 0;
+ gfi->width = gfs->screen_width;
+ gfi->height = gfs->screen_height;
+ gfi->disposal = used_transparent;
+ Gif_SetUncompressedImage(gfi, new_data, Gif_Free, 0);
+
+ return 1;
+}
+
+
+static int
+no_more_transparency(Gif_Image *gfi1, Gif_Image *gfi2)
+{
+ int t1 = gfi1->transparent, t2 = gfi2->transparent, y;
+ if (t1 < 0)
+ return 1;
+ for (y = 0; y < gfi1->height; ++y) {
+ uint8_t *d1 = gfi1->img[y], *d2 = gfi2->img[y], *ed1 = d1 + gfi1->width;
+ while (d1 < ed1) {
+ if (*d1 == t1 && *d2 != t2)
+ return 0;
+ ++d1, ++d2;
+ }
+ }
+ return 1;
+}
+
+
+int
+Gif_FullUnoptimize(Gif_Stream *gfs, int flags)
+{
+ int ok = 1;
+ int i;
+ unsigned pos, size;
+ uint16_t *screen;
+ uint16_t background;
+ Gif_Image *gfi;
+
+ if (gfs->nimages < 1) return 1;
+ for (i = 0; i < gfs->nimages; i++)
+ if (gfs->images[i]->local)
+ return 0;
+ if (!gfs->global)
+ return 0;
+
+ Gif_CalculateScreenSize(gfs, 0);
+ size = (unsigned) gfs->screen_width * (unsigned) gfs->screen_height;
+
+ screen = Gif_NewArray(uint16_t, size);
+ gfi = gfs->images[0];
+ if (gfi->transparent < 0
+ && gfs->global && gfs->background < gfs->global->ncol)
+ background = gfs->background;
+ else
+ background = TRANSPARENT;
+ for (pos = 0; pos != size; ++pos)
+ screen[pos] = background;
+
+ for (i = 0; i < gfs->nimages; i++)
+ if (!unoptimize_image(gfs, gfs->images[i], screen))
+ ok = 0;
+
+ if (ok) {
+ if (flags & GIF_UNOPTIMIZE_SIMPLEST_DISPOSAL) {
+ /* set disposal based on use of transparency.
+ If (every transparent pixel in frame i is also transparent in frame
+ i - 1), then frame i - 1 gets disposal ASIS; otherwise, disposal
+ BACKGROUND. */
+ for (i = 0; i < gfs->nimages; ++i)
+ if (i == gfs->nimages - 1
+ || no_more_transparency(gfs->images[i+1], gfs->images[i]))
+ gfs->images[i]->disposal = GIF_DISPOSAL_NONE;
+ else
+ gfs->images[i]->disposal = GIF_DISPOSAL_BACKGROUND;
+ } else
+ for (i = 0; i < gfs->nimages; ++i)
+ gfs->images[i]->disposal = GIF_DISPOSAL_BACKGROUND;
+ }
+
+ Gif_DeleteArray(screen);
+ return ok;
+}
+
+int
+Gif_Unoptimize(Gif_Stream *gfs)
+{
+ return Gif_FullUnoptimize(gfs, 0);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/gifview.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifview.c
new file mode 100644
index 0000000..ae13ec2
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifview.c
@@ -0,0 +1,1419 @@
+/* gifview.c - gifview's main loop.
+ Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com
+ This file is part of gifview, in the gifsicle package.
+
+ Gifview 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
+#ifdef X_DISPLAY_MISSING
+#error "You can't compile gifview without X."
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if HAVE_SYS_SELECT_H
+# include
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if HAVE_UNISTD_H
+# include
+#endif
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+#else
+#define EXTERN extern
+#endif
+
+/*****
+ * TIME STUFF (from xwrits)
+ **/
+
+#define MICRO_PER_SEC 1000000
+
+#define xwADDTIME(result, a, b) do { \
+ (result).tv_sec = (a).tv_sec + (b).tv_sec; \
+ if (((result).tv_usec = (a).tv_usec + (b).tv_usec) >= MICRO_PER_SEC) { \
+ (result).tv_sec++; \
+ (result).tv_usec -= MICRO_PER_SEC; \
+ } } while (0)
+
+#define xwSUBTIME(result, a, b) do { \
+ (result).tv_sec = (a).tv_sec - (b).tv_sec; \
+ if (((result).tv_usec = (a).tv_usec - (b).tv_usec) < 0) { \
+ (result).tv_sec--; \
+ (result).tv_usec += MICRO_PER_SEC; \
+ } } while (0)
+
+#define xwSETMINTIME(a, b) do { \
+ if ((b).tv_sec < (a).tv_sec || \
+ ((b).tv_sec == (a).tv_sec && (b).tv_usec < (a).tv_usec)) \
+ (a) = (b); \
+ } while (0)
+
+#define xwTIMEGEQ(a, b) ((a).tv_sec > (b).tv_sec || \
+ ((a).tv_sec == (b).tv_sec && (a).tv_usec >= (b).tv_usec))
+
+#define xwTIMEGT(a, b) ((a).tv_sec > (b).tv_sec || \
+ ((a).tv_sec == (b).tv_sec && (a).tv_usec > (b).tv_usec))
+
+#define xwTIMELEQ0(a) ((a).tv_sec < 0 || ((a).tv_sec == 0 && (a).tv_usec <= 0))
+
+#ifdef X_GETTIMEOFDAY
+# define xwGETTIMEOFDAY(a) X_GETTIMEOFDAY(a)
+#elif GETTIMEOFDAY_PROTO == 0
+EXTERN int gettimeofday(struct timeval *, struct timezone *);
+# define xwGETTIMEOFDAY(a) gettimeofday((a), 0)
+#elif GETTIMEOFDAY_PROTO == 1
+# define xwGETTIMEOFDAY(a) gettimeofday((a))
+#else
+# define xwGETTIMEOFDAY(a) gettimeofday((a), 0)
+#endif
+
+#define xwGETTIME(a) do { xwGETTIMEOFDAY(&(a)); xwSUBTIME((a), (a), genesis_time); } while (0)
+struct timeval genesis_time;
+
+
+/*****
+ * THE VIEWER STRUCTURE
+ **/
+
+static unsigned pixel_memory_limit_kb = 40000;
+static unsigned pixel_memory_kb;
+
+typedef struct Gt_Viewer {
+
+ Display *display;
+ int screen_number;
+ Visual *visual;
+ int depth;
+ Colormap colormap;
+ Gif_XContext *gfx;
+ Cursor arrow_cursor;
+ Cursor wait_cursor;
+
+ Window parent;
+ int top_level;
+
+ Window window;
+ int use_window;
+ int width;
+ int height;
+ int resizable;
+ int being_deleted;
+
+ Gif_Stream *gfs;
+ const char *name;
+ const char *title;
+
+ Gif_Image **im;
+ int nim;
+
+ Pixmap pixmap;
+ int im_pos;
+ int was_unoptimized;
+
+ Gif_XFrame *unoptimized_frames;
+ int n_unoptimized_frames;
+
+ struct Gt_Viewer *next;
+
+ int can_animate;
+ int animating;
+ int unoptimizing;
+ int scheduled;
+ int preparing;
+ struct Gt_Viewer *anim_next;
+ struct timeval timer;
+ int anim_loop;
+
+} Gt_Viewer;
+
+const char *program_name = "gifview";
+static Clp_Parser* clp;
+
+static const char *cur_display_name = 0;
+static Display *cur_display = 0;
+static const char *cur_geometry_spec = 0;
+static Cursor cur_arrow_cursor = 0;
+static Cursor cur_wait_cursor = 0;
+static const char *cur_resource_name;
+static const char *cur_window_title = 0;
+static Window cur_use_window = None;
+static int cur_use_window_new = 0;
+static const char *cur_background_color = "black";
+
+static Gt_Viewer *viewers;
+static Gt_Viewer *animations;
+static int animating = 0;
+static int unoptimizing = 0;
+static int install_colormap = 0;
+static int interactive = 1;
+static int min_delay = 0;
+static int fallback_delay = 0;
+
+static struct timeval preparation_time;
+
+
+#define DISPLAY_OPT 300
+#define UNOPTIMIZE_OPT 301
+#define VERSION_OPT 302
+#define ANIMATE_OPT 303
+#define GEOMETRY_OPT 304
+#define NAME_OPT 305
+#define HELP_OPT 306
+#define WINDOW_OPT 307
+#define INSTALL_COLORMAP_OPT 308
+#define INTERACTIVE_OPT 309
+#define BACKGROUND_OPT 310
+#define NEW_WINDOW_OPT 311
+#define TITLE_OPT 312
+#define MIN_DELAY_OPT 313
+#define FALLBACK_DELAY_OPT 314
+#define MEMORY_LIMIT_OPT 315
+
+#define WINDOW_TYPE (Clp_ValFirstUser)
+
+const Clp_Option options[] = {
+ { "animate", 'a', ANIMATE_OPT, 0, Clp_Negate },
+ { "background", 'b', BACKGROUND_OPT, Clp_ValString, 0 },
+ { "bg", 't', BACKGROUND_OPT, Clp_ValString, 0 },
+ { "display", 'd', DISPLAY_OPT, Clp_ValStringNotOption, 0 },
+ { "geometry", 'g', GEOMETRY_OPT, Clp_ValString, 0 },
+ { "install-colormap", 'i', INSTALL_COLORMAP_OPT, 0, Clp_Negate },
+ { "interactive", 'e', INTERACTIVE_OPT, 0, Clp_Negate },
+ { "help", 0, HELP_OPT, 0, 0 },
+ { "memory-limit", 0, MEMORY_LIMIT_OPT, Clp_ValUnsigned, Clp_Negate },
+ { "min-delay", 0, MIN_DELAY_OPT, Clp_ValInt, Clp_Negate },
+ { "fallback-delay", 0, FALLBACK_DELAY_OPT, Clp_ValInt, Clp_Negate },
+ { "name", 0, NAME_OPT, Clp_ValString, 0 },
+ { "title", 'T', TITLE_OPT, Clp_ValString, 0 },
+ { "unoptimize", 'U', UNOPTIMIZE_OPT, 0, Clp_Negate },
+ { "version", 0, VERSION_OPT, 0, 0 },
+ { "window", 'w', WINDOW_OPT, WINDOW_TYPE, 0 },
+ { "new-window", 0, NEW_WINDOW_OPT, WINDOW_TYPE, 0 }
+};
+
+
+/*****
+ * Diagnostics
+ **/
+
+void fatal_error(const char* format, ...) {
+ char buf[BUFSIZ];
+ int n = snprintf(buf, BUFSIZ, "%s: ", program_name);
+ va_list val;
+ va_start(val, format);
+ Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
+ va_end(val);
+ fputs(buf, stderr);
+ exit(1);
+}
+
+void error(const char* format, ...) {
+ char buf[BUFSIZ];
+ int n = snprintf(buf, BUFSIZ, "%s: ", program_name);
+ va_list val;
+ va_start(val, format);
+ Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
+ va_end(val);
+ fputs(buf, stderr);
+}
+
+void warning(const char* format, ...) {
+ char buf[BUFSIZ];
+ int n = snprintf(buf, BUFSIZ, "%s: warning: ", program_name);
+ va_list val;
+ va_start(val, format);
+ Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
+ va_end(val);
+ fputs(buf, stderr);
+}
+
+void short_usage(void) {
+ Clp_fprintf(clp, stderr, "\
+Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\
+Try %<%s --help%> for more information.\n",
+ program_name, program_name);
+}
+
+void usage(void) {
+ Clp_fprintf(clp, stdout, "\
+% is a lightweight GIF viewer for X. It can display animated GIFs as\n\
+slideshows, one frame at a time, or as animations.\n\
+\n\
+Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\n", program_name);
+ Clp_fprintf(clp, stdout, "\
+Options are:\n\
+ -a, --animate Animate multiframe GIFs.\n\
+ -U, --unoptimize Unoptimize displayed GIFs.\n\
+ -d, --display DISPLAY Set display to DISPLAY.\n\
+ --name NAME Set application resource name to NAME.\n\
+ -g, --geometry GEOMETRY Set window geometry.\n\
+ -T, --title TITLE Set window title.\n");
+ Clp_fprintf(clp, stdout, "\
+ -w, --window WINDOW Show GIF in existing WINDOW.\n\
+ --new-window WINDOW Show GIF in new child of existing WINDOW.\n\
+ -i, --install-colormap Use a private colormap.\n\
+ --bg, --background COLOR Use COLOR for transparent pixels.\n\
+ --min-delay DELAY Set minimum frame delay to DELAY/100 sec.\n\
+ --fallback-delay DELAY Set fallback frame delay to DELAY/100 sec.\n\
+ +e, --no-interactive Ignore buttons and keystrokes.\n");
+ Clp_fprintf(clp, stdout, "\
+ --memory-limit LIM Cache at most LIM megabytes of animation.\n\
+ --help Print this message and exit.\n\
+ --version Print version number and exit.\n\
+\n\
+Frame selections: #num, #num1-num2, #num1-, #name\n\n");
+ Clp_fprintf(clp, stdout, "\
+Keystrokes:\n\
+ [N]/[Space] Go to next frame. [P]/[B] Go to previous frame.\n\
+ [R]/[<] Go to first frame. [>] Go to last frame.\n\
+ [ESC] Stop animation. [S]/[A] Toggle animation.\n\
+ [U] Toggle unoptimization. [Backspace]/[W] Delete window.\n\
+ [Q] Quit.\n\
+\n\
+Left mouse button goes to next frame, right mouse button deletes window.\n\
+\n\
+Report bugs to .\n");
+}
+
+
+/*****
+ * Window creation
+ **/
+
+#if defined(__cplusplus) || defined(c_plusplus)
+#define VISUAL_CLASS c_class
+#else
+#define VISUAL_CLASS class
+#endif
+
+static void
+choose_visual(Gt_Viewer *viewer)
+{
+ Display *display = viewer->display;
+ int screen_number = viewer->screen_number;
+ VisualID default_visualid = DefaultVisual(display, screen_number)->visualid;
+
+ XVisualInfo visi_template;
+ int nv, i;
+ XVisualInfo *v, *best_v = 0;
+ Gt_Viewer *trav;
+
+ /* Look for an existing Gt_Viewer with the same display and screen number */
+ if (!install_colormap)
+ for (trav = viewers; trav; trav = trav->next)
+ if (trav != viewer && trav->display == display
+ && trav->screen_number == screen_number) {
+ viewer->visual = trav->visual;
+ viewer->depth = trav->depth;
+ viewer->colormap = trav->colormap;
+ viewer->gfx = trav->gfx;
+ viewer->gfx->refcount++;
+ return;
+ }
+
+ /* Find the default visual's XVisualInfo & put it in best_v */
+ visi_template.screen = screen_number;
+ v = XGetVisualInfo(display, VisualScreenMask, &visi_template, &nv);
+ for (i = 0; i < nv && !best_v; i++)
+ if (v[i].visualid == default_visualid)
+ best_v = &v[i];
+
+ if (!best_v) {
+
+ /* This should never happen. If we can't find the default visual's
+ XVisualInfo, we just use the default visual */
+ viewer->visual = DefaultVisual(display, screen_number);
+ viewer->depth = DefaultDepth(display, screen_number);
+ viewer->colormap = DefaultColormap(display, screen_number);
+
+ } else {
+
+ /* Which visual to choose? This isn't exactly a simple decision, since
+ we want to avoid colormap flashing while choosing a nice visual. So
+ here's the algorithm: Prefer the default visual, or take a TrueColor
+ visual with strictly greater depth. */
+ for (i = 0; i < nv; i++)
+ if (v[i].depth > best_v->depth && v[i].VISUAL_CLASS == TrueColor)
+ best_v = &v[i];
+
+ viewer->visual = best_v->visual;
+ viewer->depth = best_v->depth;
+ if (best_v->visualid != default_visualid
+ || (best_v->VISUAL_CLASS == PseudoColor && install_colormap))
+ viewer->colormap =
+ XCreateColormap(display, RootWindow(display, screen_number),
+ viewer->visual, AllocNone);
+ else
+ viewer->colormap = DefaultColormap(display, screen_number);
+
+ }
+
+ viewer->gfx = Gif_NewXContextFromVisual
+ (display, screen_number, viewer->visual, viewer->depth, viewer->colormap);
+ viewer->gfx->refcount++;
+
+ if (v) XFree(v);
+}
+
+
+Gt_Viewer *
+new_viewer(Display *display, Gif_Stream *gfs, const char *name)
+{
+ Gt_Viewer *viewer;
+ int i;
+
+ /* Make the Gt_Viewer structure */
+ viewer = Gif_New(Gt_Viewer);
+ viewer->display = display;
+
+ if (cur_use_window) {
+ XWindowAttributes attr;
+
+ if (cur_use_window == (Window)(-1)) { /* means use root window */
+ viewer->screen_number = DefaultScreen(display);
+ cur_use_window = RootWindow(display, viewer->screen_number);
+ }
+
+ XGetWindowAttributes(display, cur_use_window, &attr);
+
+ viewer->screen_number = -1;
+ for (i = 0; i < ScreenCount(display); i++)
+ if (ScreenOfDisplay(display, i) == attr.screen)
+ viewer->screen_number = i;
+ assert(viewer->screen_number >= 0);
+
+ viewer->visual = attr.visual;
+ viewer->depth = attr.depth;
+ viewer->colormap = attr.colormap;
+
+ viewer->gfx = Gif_NewXContextFromVisual
+ (display, viewer->screen_number, viewer->visual, viewer->depth,
+ viewer->colormap);
+ viewer->gfx->refcount++;
+
+ /* Before -- use root window, if that's what we were given; otherwise,
+ create a child of the window we were given */
+ /* 13.Nov.2001 - don't make a child of the window we were given! */
+ if (cur_use_window_new) {
+ viewer->window = None;
+ viewer->use_window = 0;
+ } else {
+ viewer->window = cur_use_window;
+ viewer->use_window = 1;
+ }
+ viewer->parent = cur_use_window;
+ viewer->top_level = 0;
+ viewer->resizable = 0;
+
+ } else {
+ viewer->screen_number = DefaultScreen(display);
+ choose_visual(viewer);
+ viewer->window = None;
+ viewer->parent = RootWindow(display, viewer->screen_number);
+ viewer->use_window = 0;
+ viewer->top_level = 1;
+ viewer->resizable = 1;
+ }
+
+ /* assign background color */
+ if (cur_background_color) {
+ XColor color;
+ if (!XParseColor(viewer->display, viewer->colormap, cur_background_color,
+ &color)) {
+ error("invalid background color %<%s%>\n", cur_background_color);
+ cur_background_color = 0;
+ } else if (!XAllocColor(viewer->display, viewer->colormap, &color))
+ warning("can%,t allocate background color\n");
+ else {
+ unsigned long pixel = color.pixel;
+ Gif_XContext *gfx = viewer->gfx;
+ if (pixel != gfx->transparent_pixel && gfx->refcount > 1) {
+ /* copy X context */
+ viewer->gfx = Gif_NewXContextFromVisual
+ (gfx->display, gfx->screen_number, gfx->visual, gfx->depth,
+ gfx->colormap);
+ viewer->gfx->refcount++;
+ gfx->refcount--;
+ }
+ viewer->gfx->transparent_pixel = pixel;
+ }
+ }
+
+ if (!cur_arrow_cursor) {
+ cur_arrow_cursor = XCreateFontCursor(display, XC_left_ptr);
+ cur_wait_cursor = XCreateFontCursor(display, XC_watch);
+ }
+ viewer->arrow_cursor = cur_arrow_cursor;
+ viewer->wait_cursor = cur_wait_cursor;
+
+ viewer->being_deleted = 0;
+ viewer->gfs = gfs;
+ gfs->refcount++;
+ viewer->name = name;
+ viewer->title = cur_window_title;
+ viewer->nim = Gif_ImageCount(gfs);
+ viewer->im = Gif_NewArray(Gif_Image *, viewer->nim);
+ for (i = 0; i < viewer->nim; i++)
+ viewer->im[i] = gfs->images[i];
+ viewer->pixmap = None;
+ viewer->im_pos = -1;
+ viewer->was_unoptimized = 0;
+ viewer->unoptimized_frames = Gif_NewXFrames(gfs);
+ viewer->n_unoptimized_frames = 0;
+ viewer->next = viewers;
+ viewers = viewer;
+ viewer->animating = 0;
+ viewer->unoptimizing = unoptimizing;
+ viewer->scheduled = 0;
+ viewer->preparing = 0;
+ viewer->anim_next = 0;
+ viewer->anim_loop = 0;
+ viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
+
+ return viewer;
+}
+
+
+void
+delete_viewer(Gt_Viewer *viewer)
+{
+ Gt_Viewer *prev = 0, *trav;
+ if (viewer->pixmap && !viewer->was_unoptimized)
+ XFreePixmap(viewer->display, viewer->pixmap);
+
+ for (trav = viewers; trav != viewer; prev = trav, trav = trav->next)
+ ;
+ if (prev) prev->next = viewer->next;
+ else viewers = viewer->next;
+
+ Gif_DeleteXFrames(viewer->gfx, viewer->gfs, viewer->unoptimized_frames);
+ Gif_DeleteStream(viewer->gfs);
+ Gif_DeleteArray(viewer->im);
+ Gif_DeleteXContext(viewer->gfx);
+ Gif_Delete(viewer);
+}
+
+
+static Gt_Viewer *
+next_viewer(Gif_Stream *gfs, const char *name)
+{
+ Gt_Viewer *viewer = new_viewer(cur_display, gfs, name);
+ cur_use_window = None;
+ return viewer;
+}
+
+static Gt_Viewer *
+get_input_stream(const char *name)
+{
+ FILE *f;
+ Gif_Stream *gfs = 0;
+
+ if (name == 0 || strcmp(name, "-") == 0) {
+#ifndef OUTPUT_GIF_TO_TERMINAL
+ if (isatty(fileno(stdin))) {
+ error(": is a terminal\n");
+ return NULL;
+ }
+#endif
+ f = stdin;
+#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
+ name = "";
+ } else
+ f = fopen(name, "rb");
+ if (!f) {
+ error("%s: %s\n", name, strerror(errno));
+ return 0;
+ }
+
+ gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, 0, 0);
+ fclose(f);
+
+ if (!gfs || Gif_ImageCount(gfs) == 0) {
+ error("%s: file not in GIF format\n", name);
+ Gif_DeleteStream(gfs);
+ return 0;
+ }
+
+ if (!cur_display) {
+ cur_display = XOpenDisplay(cur_display_name);
+ if (!cur_display) {
+ error("can%,t open display\n");
+ return 0;
+ }
+ }
+
+ return next_viewer(gfs, name);
+}
+
+
+/*****
+ * Schedule stuff
+ **/
+
+void
+switch_animating(Gt_Viewer *viewer, int animating)
+{
+ int i;
+ Gif_Stream *gfs = viewer->gfs;
+ if (animating == viewer->animating || !viewer->can_animate)
+ return;
+ for (i = 0; i < gfs->nimages; i++)
+ viewer->im[i] = gfs->images[i];
+ viewer->animating = animating;
+ if (!animating)
+ viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
+}
+
+
+void
+unschedule(Gt_Viewer *viewer)
+{
+ Gt_Viewer *prev, *trav;
+ if (!viewer->scheduled) return;
+ for (prev = 0, trav = animations; trav; prev = trav, trav = trav->anim_next)
+ if (trav == viewer)
+ break;
+ if (trav) {
+ if (prev) prev->anim_next = viewer->anim_next;
+ else animations = viewer->anim_next;
+ }
+ viewer->scheduled = 0;
+ viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
+}
+
+void
+schedule(Gt_Viewer *viewer)
+{
+ Gt_Viewer *prev, *trav;
+
+ if (viewer->scheduled)
+ unschedule(viewer);
+
+ prev = 0;
+ for (trav = animations; trav; prev = trav, trav = trav->anim_next)
+ if (xwTIMEGEQ(trav->timer, viewer->timer))
+ break;
+ if (prev) {
+ viewer->anim_next = trav;
+ prev->anim_next = viewer;
+ } else {
+ viewer->anim_next = animations;
+ animations = viewer;
+ }
+
+ viewer->scheduled = 1;
+}
+
+void
+schedule_next_frame(Gt_Viewer *viewer)
+{
+ struct timeval interval;
+ int delay = viewer->im[viewer->im_pos]->delay;
+ int next_pos = viewer->im_pos + 1;
+ if (delay < 1)
+ delay = fallback_delay;
+ if (delay < min_delay)
+ delay = min_delay;
+ if (next_pos == viewer->nim)
+ next_pos = 0;
+
+ if (viewer->timer.tv_sec == 0 && viewer->timer.tv_usec == 0)
+ xwGETTIME(viewer->timer);
+
+ interval.tv_sec = delay / 100;
+ interval.tv_usec = (delay % 100) * (MICRO_PER_SEC / 100);
+ if (delay == 0)
+ interval.tv_usec = 2000;
+ xwADDTIME(viewer->timer, viewer->timer, interval);
+
+ /* 1.Aug.2002 - leave some time to prepare the frame if necessary */
+ if (viewer->unoptimized_frames[next_pos].pixmap) {
+ xwSUBTIME(viewer->timer, viewer->timer, preparation_time);
+ viewer->preparing = 1;
+ }
+
+ schedule(viewer);
+}
+
+
+/*****
+ * X stuff
+ **/
+
+int
+parse_geometry(const char *const_g, XSizeHints *sh, int screen_width,
+ int screen_height)
+{
+ char *g = (char *)const_g;
+ sh->flags = 0;
+
+ if (isdigit(*g)) {
+ sh->flags |= USSize;
+ sh->width = strtol(g, &g, 10);
+ if (g[0] == 'x' && isdigit(g[1]))
+ sh->height = strtol(g + 1, &g, 10);
+ else
+ goto error;
+ } else if (!*g)
+ goto error;
+
+ if (*g == '+' || *g == '-') {
+ int x_minus, y_minus;
+ sh->flags |= USPosition | PWinGravity;
+ x_minus = *g == '-';
+ sh->x = strtol(g + 1, &g, 10);
+ if (x_minus) sh->x = screen_width - sh->x - sh->width;
+
+ y_minus = *g == '-';
+ if (*g == '-' || *g == '+')
+ sh->y = strtol(g + 1, &g, 10);
+ else
+ goto error;
+ if (y_minus) sh->y = screen_height - sh->y - sh->height;
+
+ if (x_minus)
+ sh->win_gravity = y_minus ? SouthEastGravity : NorthEastGravity;
+ else
+ sh->win_gravity = y_minus ? SouthWestGravity : NorthWestGravity;
+
+ } else if (*g)
+ goto error;
+
+ return 1;
+
+ error:
+ warning("bad geometry specification\n");
+ sh->flags = 0;
+ return 0;
+}
+
+
+static Atom wm_delete_window_atom;
+static Atom wm_protocols_atom;
+
+void
+create_viewer_window(Gt_Viewer *viewer, int w, int h)
+{
+ Display *display = viewer->display;
+ char *stringlist[2];
+ XTextProperty window_name, icon_name;
+ XClassHint classh;
+ XSizeHints *sizeh = XAllocSizeHints(); /* sets all fields to 0 */
+
+ /* Set the window's geometry */
+ sizeh->width = w ? w : 1;
+ sizeh->height = h ? h : 1;
+ if (cur_geometry_spec) {
+ int scr_width = DisplayWidth(viewer->display, viewer->screen_number);
+ int scr_height = DisplayHeight(viewer->display, viewer->screen_number);
+ parse_geometry(cur_geometry_spec, sizeh, scr_width, scr_height);
+ }
+
+ /* Open the display and create the window */
+ if (!viewer->window) {
+ XSetWindowAttributes x_set_attr;
+ unsigned long x_set_attr_mask;
+ x_set_attr.colormap = viewer->colormap;
+ x_set_attr.backing_store = NotUseful;
+ x_set_attr.save_under = False;
+ x_set_attr.border_pixel = 0;
+ x_set_attr.background_pixel = 0;
+ x_set_attr_mask = CWColormap | CWBorderPixel | CWBackPixel
+ | CWBackingStore | CWSaveUnder;
+
+ viewer->window = XCreateWindow
+ (display, viewer->parent,
+ sizeh->x, sizeh->y, sizeh->width, sizeh->height, 0,
+ viewer->depth, InputOutput, viewer->visual,
+ x_set_attr_mask, &x_set_attr);
+ XDefineCursor(display, viewer->window, viewer->arrow_cursor);
+ }
+
+ /* If user gave us geometry, don't change the size later */
+ if (sizeh->flags & USSize)
+ viewer->resizable = 0;
+ viewer->width = w;
+ viewer->height = h;
+
+ /* Set the window's title and class (for window manager resources) */
+ if (viewer->top_level) {
+ stringlist[0] = "gifview";
+ stringlist[1] = 0;
+ XStringListToTextProperty(stringlist, 1, &window_name);
+ XStringListToTextProperty(stringlist, 1, &icon_name);
+ classh.res_name = (char *)cur_resource_name;
+ classh.res_class = "Gifview";
+ XSetWMProperties(display, viewer->window, &window_name, &icon_name,
+ NULL, 0, sizeh, NULL, &classh);
+ XFree(window_name.value);
+ XFree(icon_name.value);
+
+ if (!wm_delete_window_atom) {
+ wm_delete_window_atom = XInternAtom(display, "WM_DELETE_WINDOW", False);
+ wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False);
+ }
+ XSetWMProtocols(display, viewer->window, &wm_delete_window_atom, 1);
+ }
+
+ if (interactive)
+ XSelectInput(display, viewer->window, ButtonPressMask | KeyPressMask
+ | StructureNotifyMask);
+ else
+ XSelectInput(display, viewer->window, StructureNotifyMask);
+
+ XFree(sizeh);
+}
+
+
+void
+pre_delete_viewer(Gt_Viewer *viewer)
+{
+ if (viewer->being_deleted) return;
+ viewer->being_deleted = 1;
+
+ if (viewer->scheduled) unschedule(viewer);
+
+ if (viewer->window && !viewer->use_window)
+ XDestroyWindow(viewer->display, viewer->window);
+ else
+ delete_viewer(viewer);
+}
+
+
+Gt_Viewer *
+find_viewer(Display *display, Window window)
+{
+ Gt_Viewer *v;
+ for (v = viewers; v; v = v->next)
+ if (v->display == display && v->window == window)
+ return v;
+ return 0;
+}
+
+
+void
+set_viewer_name(Gt_Viewer *viewer, int slow_number)
+{
+ Gif_Image *gfi;
+ char *strs[2];
+ char *identifier;
+ XTextProperty name_prop;
+ int im_pos = (slow_number >= 0 ? slow_number : viewer->im_pos);
+ int len;
+
+ if (!viewer->top_level || im_pos >= viewer->nim || viewer->being_deleted)
+ return;
+
+ gfi = viewer->im[im_pos];
+ len = strlen(viewer->title) + strlen(viewer->name) + 14;
+ identifier = (slow_number >= 0 ? (char *)0 : gfi->identifier);
+ if (identifier)
+ len += 2 + strlen(identifier);
+
+ strs[0] = Gif_NewArray(char, len);
+ if (strcmp(viewer->title, "gifview") != 0)
+ snprintf(strs[0], len, "%s", viewer->title);
+ else if (slow_number >= 0)
+ snprintf(strs[0], len, "gifview: %s [#%d]", viewer->name, im_pos);
+ else if (viewer->nim == 1 && identifier)
+ snprintf(strs[0], len, "gifview: %s #%s", viewer->name, identifier);
+ else if (viewer->animating || viewer->nim == 1)
+ snprintf(strs[0], len, "gifview: %s", viewer->name);
+ else if (!identifier)
+ snprintf(strs[0], len, "gifview: %s #%d", viewer->name, im_pos);
+ else
+ snprintf(strs[0], len, "gifview: %s #%d #%s", viewer->name, im_pos, identifier);
+ strs[1] = 0;
+
+ XStringListToTextProperty(strs, 1, &name_prop);
+ XSetWMName(viewer->display, viewer->window, &name_prop);
+ XSetWMIconName(viewer->display, viewer->window, &name_prop);
+
+ XFree(name_prop.value);
+ Gif_DeleteArray(strs[0]);
+}
+
+static unsigned screen_memory_kb(const Gt_Viewer* viewer) {
+ return 1 + ((unsigned) (viewer->gfs->screen_width
+ * viewer->gfs->screen_height) / 334);
+}
+
+static Pixmap
+unoptimized_frame(Gt_Viewer *viewer, int frame, int slow)
+{
+ /* create a new unoptimized frame if necessary */
+ if (!viewer->unoptimized_frames[frame].pixmap) {
+ (void) Gif_XNextImage(viewer->gfx, viewer->gfs, frame,
+ viewer->unoptimized_frames);
+ pixel_memory_kb += screen_memory_kb(viewer);
+ viewer->unoptimized_frames[viewer->n_unoptimized_frames].user_data =
+ frame;
+ ++viewer->n_unoptimized_frames;
+ if (slow) {
+ set_viewer_name(viewer, frame);
+ XFlush(viewer->display);
+ }
+ }
+
+ /* kill some old frames if over the memory limit */
+ while (pixel_memory_limit_kb != (unsigned) -1
+ && pixel_memory_limit_kb < pixel_memory_kb
+ && viewer->n_unoptimized_frames > 1) {
+ int killidx, killframe, i = 0;
+ do {
+ killidx = random() % viewer->n_unoptimized_frames;
+ killframe = viewer->unoptimized_frames[killidx].user_data;
+ ++i;
+ } while (killframe == frame
+ || (i < 10 && killframe > frame && killframe < frame + 5)
+ || (i < 10 && (killframe % 50) == 0));
+ XFreePixmap(viewer->display, viewer->unoptimized_frames[killframe].pixmap);
+ viewer->unoptimized_frames[killframe].pixmap = None;
+ --viewer->n_unoptimized_frames;
+ viewer->unoptimized_frames[killidx].user_data =
+ viewer->unoptimized_frames[viewer->n_unoptimized_frames].user_data;
+ pixel_memory_kb -= screen_memory_kb(viewer);
+ }
+
+ return viewer->unoptimized_frames[frame].pixmap;
+}
+
+void
+prepare_frame(Gt_Viewer *viewer, int frame)
+{
+ Display *display = viewer->display;
+ Window window = viewer->window;
+ int changed_cursor = 0;
+
+ if (viewer->being_deleted || !viewer->animating)
+ return;
+
+ if (frame < 0 || frame > viewer->nim - 1)
+ frame = 0;
+
+ /* Change cursor if we need to wait. */
+ if ((viewer->animating || viewer->unoptimizing)
+ && !viewer->unoptimized_frames[frame].pixmap) {
+ if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) {
+ changed_cursor = 1;
+ XDefineCursor(display, window, viewer->wait_cursor);
+ XFlush(display);
+ }
+ }
+
+ /* Prepare the frame */
+ (void) unoptimized_frame(viewer, frame, changed_cursor && !viewer->animating);
+
+ /* Restore cursor */
+ if (changed_cursor)
+ XDefineCursor(display, window, viewer->arrow_cursor);
+
+ /* schedule actual view of window */
+ xwADDTIME(viewer->timer, viewer->timer, preparation_time);
+ viewer->preparing = 0;
+ schedule(viewer);
+}
+
+void
+view_frame(Gt_Viewer *viewer, int frame)
+{
+ Display *display = viewer->display;
+ Window window = viewer->window;
+ Pixmap old_pixmap = viewer->pixmap;
+ int need_set_name = 0;
+
+ if (viewer->being_deleted)
+ return;
+
+ if (frame < 0)
+ frame = 0;
+ if (frame > viewer->nim - 1 && viewer->animating) {
+ int loopcount = viewer->gfs->loopcount;
+ if (loopcount == 0 || loopcount > viewer->anim_loop) {
+ viewer->anim_loop++;
+ frame = 0;
+ } else {
+ switch_animating(viewer, 0);
+ need_set_name = 1;
+ }
+ }
+ if (frame > viewer->nim - 1)
+ frame = viewer->nim - 1;
+
+ if (frame != viewer->im_pos) {
+ Gif_Image *gfi = viewer->im[frame];
+ int width, height, changed_cursor = 0;
+
+ /* Change cursor if we need to wait. */
+ if ((viewer->animating || viewer->unoptimizing)
+ && !viewer->unoptimized_frames[frame].pixmap) {
+ if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) {
+ changed_cursor = 1;
+ XDefineCursor(display, window, viewer->wait_cursor);
+ XFlush(display);
+ }
+ }
+
+ /* 5/26/98 Do some noodling around to try and use memory most effectively.
+ If animating, keep the uncompressed frame; otherwise, throw it away. */
+ if (viewer->animating || viewer->unoptimizing)
+ viewer->pixmap = unoptimized_frame(viewer, frame,
+ changed_cursor && !viewer->animating);
+ else
+ viewer->pixmap = Gif_XImage(viewer->gfx, viewer->gfs, gfi);
+
+ /* put the image on the window */
+ if (viewer->animating || viewer->unoptimizing)
+ width = viewer->gfs->screen_width, height = viewer->gfs->screen_height;
+ else
+ width = Gif_ImageWidth(gfi), height = Gif_ImageHeight(gfi);
+ if (!window) {
+ create_viewer_window(viewer, width, height);
+ window = viewer->window;
+ }
+ XSetWindowBackgroundPixmap(display, window, viewer->pixmap);
+ if (old_pixmap || viewer->use_window) /* clear existing window */
+ XClearWindow(display, window);
+ /* Only change size after changing pixmap. */
+ if ((viewer->width != width || viewer->height != height)
+ && viewer->resizable) {
+ XWindowChanges winch;
+ winch.width = viewer->width = width;
+ winch.height = viewer->height = height;
+ XReconfigureWMWindow
+ (display, window, viewer->screen_number,
+ CWWidth | CWHeight, &winch);
+ }
+
+ /* Get rid of old pixmaps */
+ if (!viewer->was_unoptimized && old_pixmap)
+ XFreePixmap(display, old_pixmap);
+ viewer->was_unoptimized = viewer->animating || viewer->unoptimizing;
+
+ /* Restore cursor */
+ if (changed_cursor)
+ XDefineCursor(display, window, viewer->arrow_cursor);
+
+ /* Do we need a new name? */
+ if ((!viewer->animating && Gif_ImageCount(viewer->gfs) > 1)
+ || old_pixmap == None)
+ need_set_name = 1;
+ }
+
+ viewer->im_pos = frame;
+ viewer->preparing = 0;
+
+ if (need_set_name)
+ set_viewer_name(viewer, -1);
+
+ if (!old_pixmap && !viewer->use_window)
+ /* first image; map the window */
+ XMapRaised(display, window);
+ else if (viewer->animating)
+ /* only schedule next frame if image is already mapped */
+ schedule_next_frame(viewer);
+}
+
+
+/*****
+ * Command line arguments: marking frames, being done with streams
+ **/
+
+int
+frame_argument(Gt_Viewer *viewer, const char *arg)
+{
+ const char *c = arg;
+ int n1 = 0;
+
+ /* Get a number range (#x, #x-y, #x-, or #-y). First, read x. */
+ if (isdigit(c[0]))
+ n1 = strtol(arg, (char **)&c, 10);
+
+ /* It really was a number range only if c is now at the end of the
+ argument. */
+ if (c[0] != 0) {
+ Gif_Image *gfi = Gif_GetNamedImage(viewer->gfs, c);
+ if (!gfi)
+ error("no frame named %<%s%>\n", c);
+ else
+ n1 = Gif_ImageNumber(viewer->gfs, gfi);
+ } else {
+ if (n1 < 0 || n1 >= viewer->nim) {
+ error("no frame number %d\n", n1);
+ n1 = 0;
+ }
+ }
+
+ return n1;
+}
+
+
+void
+input_stream_done(Gt_Viewer *viewer, int first_frame)
+{
+ viewer->can_animate = Gif_ImageCount(viewer->gfs) > 1;
+ switch_animating(viewer, animating && viewer->can_animate);
+ if (first_frame < 0)
+ first_frame = 0;
+ view_frame(viewer, first_frame);
+}
+
+
+void
+key_press(Gt_Viewer *viewer, XKeyEvent *e)
+{
+ char buf[32];
+ KeySym key;
+ int nbuf = XLookupString(e, buf, 32, &key, 0);
+ if (nbuf > 1) buf[0] = 0; /* ignore multikey sequences */
+
+ if (key == XK_space || key == XK_F || key == XK_f || key == XK_N || key == XK_n)
+ /* space, N or F: one frame ahead */
+ view_frame(viewer, viewer->im_pos + 1);
+
+ else if (key == XK_B || key == XK_b || key == XK_P || key == XK_p)
+ /* B or P: one frame back */
+ view_frame(viewer, viewer->im_pos - 1);
+
+ else if (key == XK_W || key == XK_w || key == XK_BackSpace)
+ /* backspace: delete viewer */
+ pre_delete_viewer(viewer);
+
+ else if (key == XK_Q || key == XK_q)
+ /* Q: quit application */
+ exit(0);
+
+ else if (key == XK_S || key == XK_s || key == XK_a || key == XK_A) {
+ /* S or A: toggle animation */
+ switch_animating(viewer, !viewer->animating);
+
+ if (viewer->animating) {
+ int pos = viewer->im_pos;
+ if (viewer->im_pos >= viewer->nim - 1) {
+ pos = 0;
+ viewer->anim_loop = 0;
+ }
+ view_frame(viewer, pos);
+
+ } else
+ unschedule(viewer);
+
+ set_viewer_name(viewer, -1);
+
+ } else if (key == XK_U || key == XK_u) {
+ /* U: toggle unoptimizing */
+ int pos = viewer->im_pos;
+ viewer->unoptimizing = !viewer->unoptimizing;
+ if (!viewer->animating) {
+ viewer->im_pos = -1;
+ view_frame(viewer, pos);
+ set_viewer_name(viewer, -1);
+ }
+
+ } else if (key == XK_R || key == XK_r
+ || (nbuf == 1 && buf[0] == '<')) {
+ /* R or <: reset to first frame */
+ unschedule(viewer);
+ viewer->anim_loop = 0;
+ view_frame(viewer, 0);
+
+ } else if (nbuf == 1 && buf[0] == '>') {
+ /* >: reset to last frame */
+ unschedule(viewer);
+ viewer->anim_loop = 0;
+ view_frame(viewer, viewer->nim - 1);
+
+ } else if (key == XK_Escape && viewer->animating) {
+ /* Escape: stop animation */
+ switch_animating(viewer, 0);
+ unschedule(viewer);
+ set_viewer_name(viewer, -1);
+
+ } else if (key == XK_Z || key == XK_z) {
+ /* Z: trigger resizability */
+ viewer->resizable = !viewer->resizable;
+ }
+}
+
+
+void
+loop(void)
+{
+ struct timeval now, stop_loop, stop_delta;
+ fd_set xfds;
+ XEvent e;
+ int pending;
+ Gt_Viewer *v;
+ Display *display = viewers->display;
+ int x_socket = ConnectionNumber(display);
+ stop_delta.tv_sec = 0;
+ stop_delta.tv_usec = 200000;
+
+ xwGETTIME(now);
+ FD_ZERO(&xfds);
+
+ while (viewers) {
+
+ /* Check for any animations */
+ /* 13.Feb.2001 - Use the 'pending' counter to avoid a tight loop
+ if all the frames in an animation have delay 0. Reported by
+ Franc,ois Petitjean. */
+ /* 1.Aug.2002 - Switch to running the loop for max 0.2s. */
+ xwADDTIME(stop_loop, now, stop_delta);
+ while (animations && xwTIMEGEQ(now, animations->timer)
+ && xwTIMEGEQ(stop_loop, now)) {
+ v = animations;
+ animations = v->anim_next;
+ v->scheduled = 0;
+ if (v->preparing)
+ prepare_frame(v, v->im_pos + 1);
+ else {
+ if (xwTIMEGEQ(now, v->timer))
+ v->timer = now;
+ view_frame(v, v->im_pos + 1);
+ }
+ xwGETTIME(now);
+ }
+
+ pending = XPending(display);
+ if (!pending) {
+ /* select() until event arrives */
+ struct timeval timeout, *timeout_ptr;
+ int retval;
+
+ if (animations) {
+ xwSUBTIME(timeout, animations->timer, now);
+ timeout_ptr = &timeout;
+ } else
+ timeout_ptr = 0;
+
+ FD_SET(x_socket, &xfds);
+ retval = select(x_socket + 1, &xfds, 0, 0, timeout_ptr);
+ pending = (retval <= 0 ? 0 : FD_ISSET(x_socket, &xfds));
+ }
+
+ if (pending)
+ while (XPending(display)) {
+ XNextEvent(display, &e);
+ v = find_viewer(e.xany.display, e.xany.window);
+ if (v) {
+ if (interactive) {
+ if (e.type == ButtonPress && e.xbutton.button == 1)
+ /* Left mouse button: go to next frame */
+ view_frame(v, v->im_pos + 1);
+
+ else if (e.type == ButtonPress && e.xbutton.button == 4)
+ /* mousewheel forward */
+ view_frame(v, v->im_pos + 1);
+
+ else if (e.type == ButtonPress && e.xbutton.button == 5)
+ /* mousewheel backward */
+ view_frame(v, v->im_pos - 1);
+
+ else if (e.type == ButtonPress && e.xbutton.button == 3)
+ /* Right mouse button: delete window */
+ pre_delete_viewer(v);
+
+ else if (e.type == KeyPress)
+ /* Key press: call function */
+ key_press(v, &e.xkey);
+ }
+
+ if (e.type == ClientMessage
+ && e.xclient.message_type == wm_protocols_atom
+ && (Atom)(e.xclient.data.l[0]) == wm_delete_window_atom)
+ /* WM_DELETE_WINDOW message: delete window */
+ pre_delete_viewer(v);
+
+ else if (e.type == MapNotify && v->animating
+ && v->scheduled == 0)
+ /* Window was just mapped; now, start animating it */
+ schedule_next_frame(v);
+
+ else if (e.type == DestroyNotify)
+ /* Once the window has been destroyed, delete related state */
+ delete_viewer(v);
+ }
+ }
+
+ xwGETTIME(now);
+ }
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ Gt_Viewer *viewer = 0;
+ int viewer_given = 0;
+ int any_errors = 0;
+ int first_frame = -1;
+
+ clp = Clp_NewParser(argc, (const char * const *)argv,
+ sizeof(options) / sizeof(options[0]), options);
+ Clp_SetOptionChar(clp, '+', Clp_ShortNegated);
+ Clp_AddStringListType
+ (clp, WINDOW_TYPE, Clp_AllowNumbers | Clp_StringListLong,
+ "root", (long) -1,
+ (const char*) 0);
+ program_name = cur_resource_name = cur_window_title = Clp_ProgramName(clp);
+
+ xwGETTIMEOFDAY(&genesis_time);
+ preparation_time.tv_sec = 0;
+ preparation_time.tv_usec = 200000;
+
+ while (1) {
+ int opt = Clp_Next(clp);
+ switch (opt) {
+
+ case DISPLAY_OPT:
+ if (cur_display)
+ fatal_error("%<--display%> must come before all other options\n");
+ cur_display_name = clp->vstr;
+ cur_display = 0;
+ cur_arrow_cursor = cur_wait_cursor = None;
+ break;
+
+ case TITLE_OPT:
+ cur_window_title = clp->vstr;
+ break;
+
+ case GEOMETRY_OPT:
+ cur_geometry_spec = clp->vstr;
+ break;
+
+ case NAME_OPT:
+ cur_resource_name = clp->vstr;
+ break;
+
+ case UNOPTIMIZE_OPT:
+ unoptimizing = clp->negated ? 0 : 1;
+ break;
+
+ case BACKGROUND_OPT:
+ cur_background_color = clp->vstr;
+ break;
+
+ case ANIMATE_OPT:
+ animating = clp->negated ? 0 : 1;
+ break;
+
+ case INSTALL_COLORMAP_OPT:
+ install_colormap = clp->negated ? 0 : 1;
+ break;
+
+ case WINDOW_OPT:
+ cur_use_window = clp->val.ul;
+ cur_use_window_new = 0;
+ break;
+
+ case NEW_WINDOW_OPT:
+ cur_use_window = clp->val.ul;
+ cur_use_window_new = 1;
+ break;
+
+ case INTERACTIVE_OPT:
+ interactive = clp->negated ? 0 : 1;
+ break;
+
+ case MIN_DELAY_OPT:
+ min_delay = clp->negated ? 0 : clp->val.i;
+ break;
+
+ case FALLBACK_DELAY_OPT:
+ fallback_delay = clp->negated ? 0 : clp->val.i;
+ break;
+
+ case MEMORY_LIMIT_OPT:
+ if (clp->negated || clp->val.u >= ((unsigned) -1 / 1000))
+ pixel_memory_limit_kb = (unsigned) -1;
+ else
+ pixel_memory_limit_kb = clp->val.u * 1000;
+ break;
+
+ case VERSION_OPT:
+ printf("gifview (LCDF Gifsicle) %s\n", VERSION);
+ printf("Copyright (C) 1997-2025 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(0);
+ break;
+
+ case HELP_OPT:
+ usage();
+ exit(0);
+ break;
+
+ case Clp_NotOption:
+ if (clp->vstr[0] == '#') {
+ if (!viewer_given) {
+ viewer = get_input_stream(0);
+ viewer_given = 1;
+ }
+ if (viewer && first_frame >= 0) {
+ /* copy viewer if 2 frame specs given */
+ input_stream_done(viewer, first_frame);
+ viewer = next_viewer(viewer->gfs, viewer->name);
+ }
+ if (viewer)
+ first_frame = frame_argument(viewer, clp->vstr + 1);
+ } else {
+ if (viewer) input_stream_done(viewer, first_frame);
+ first_frame = -1;
+ viewer = get_input_stream(clp->vstr);
+ viewer_given = 1;
+ }
+ break;
+
+ case Clp_Done:
+ goto done;
+
+ case Clp_BadOption:
+ any_errors = 1;
+ break;
+
+ default:
+ break;
+
+ }
+ }
+
+ done:
+
+ if (!viewer_given) {
+ if (any_errors) {
+ short_usage();
+ exit(1);
+ }
+ viewer = get_input_stream(0);
+ }
+ if (viewer) input_stream_done(viewer, first_frame);
+
+ if (viewers) loop();
+
+ return 0;
+}
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/gifwrite.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifwrite.c
new file mode 100644
index 0000000..a087a71
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifwrite.c
@@ -0,0 +1,1214 @@
+/* -*- mode: c; c-basic-offset: 2 -*- */
+/* gifwrite.c - Functions to write GIFs.
+ Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com
+ This file is part of the LCDF GIF library.
+
+ The LCDF GIF library is free software. It is distributed under the GNU
+ General 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. */
+
+#if HAVE_CONFIG_H
+# include
+#elif !defined(__cplusplus) && !defined(inline)
+/* Assume we don't have inline by default */
+# define inline
+#endif
+#include
+#include
+#include
+#include
+#include
+#include "kcolor.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WRITE_BUFFER_SIZE 255
+#define NODES_SIZE GIF_MAX_CODE
+#define LINKS_SIZE (GIF_MAX_CODE + 256)
+
+/* 1.Aug.1999 - Removed code hashing in favor of an adaptive tree strategy
+ based on Whirlgif-3.04, written by Hans Dinsen-Hansen . Mr.
+ Dinsen-Hansen brought the adaptive tree strategy to my attention and argued
+ at length that it was better than hashing. It runs a lot faster, though it
+ creates identical compression.
+
+ Each code is represented by a Node. The Nodes form a tree with variable
+ fan-out -- up to 'clear_code' children per Node. There are two kinds of
+ Node, TABLE and LINKS. In a TABLE node, the children are stored in a table
+ indexed by suffix -- thus, it's very efficient to determine a given child.
+ In a LINKS node, the existent children are stored in a linked list. This is
+ slightly slower to access. When a LINKS node gets more than
+ 'MAX_LINKS_TYPE-1' children, it is converted to a TABLE node. (This is why
+ it's an adaptive tree.)
+
+ Problems with this implementation: MAX_LINKS_TYPE is fixed, so GIFs with
+ very small numbers of colors (2, 4, 8) won't get the speed benefits of
+ TABLE nodes. */
+
+#define TABLE_TYPE 0
+#define LINKS_TYPE 1
+#define MAX_LINKS_TYPE 5
+typedef struct Gif_Node {
+ Gif_Code code;
+ uint8_t type;
+ uint8_t suffix;
+ uint32_t length;
+ struct Gif_Node* sibling;
+ union {
+ struct Gif_Node* s;
+ struct Gif_Node** m;
+ } child;
+} Gif_Node;
+
+typedef struct Gif_LossyNode {
+ Gif_Node node;
+ unsigned long diffaccum;
+ wkcolor error;
+} Gif_LossyNode;
+
+typedef struct Gif_CodeTable {
+ Gif_Node** links;
+ int clear_code;
+ unsigned max_loss;
+ uint32_t maxpos;
+
+ union {
+ Gif_Node* nl;
+ Gif_LossyNode* ls;
+ } nodes;
+ int links_pos;
+
+ kcolor* global_kcs;
+ kcolor* local_kcs;
+ kcolor* kcs;
+ double loss_scale;
+ unsigned* kcdiff;
+} Gif_CodeTable;
+
+
+struct Gif_Writer {
+ FILE *f;
+ uint8_t *v;
+ uint32_t pos;
+ uint32_t cap;
+ Gif_CompressInfo gcinfo;
+ int global_size;
+ int local_size;
+ int errors;
+ int cleared;
+ Gif_CodeTable code_table;
+ void (*byte_putter)(uint8_t, struct Gif_Writer *);
+ void (*block_putter)(const uint8_t *, size_t, struct Gif_Writer *);
+};
+
+
+#define gifputbyte(b, grr) ((*grr->byte_putter)(b, grr))
+#define gifputblock(b, l, grr) ((*grr->block_putter)(b, l, grr))
+
+static inline void
+gifputunsigned(uint16_t uns, Gif_Writer *grr)
+{
+ gifputbyte(uns & 0xFF, grr);
+ gifputbyte(uns >> 8, grr);
+}
+
+
+static void
+file_byte_putter(uint8_t b, Gif_Writer *grr)
+{
+ fputc(b, grr->f);
+}
+
+static void
+file_block_putter(const uint8_t *block, size_t size, Gif_Writer *grr)
+{
+ if (fwrite(block, 1, size, grr->f) != size)
+ grr->errors = 1;
+}
+
+
+static void
+memory_byte_putter(uint8_t b, Gif_Writer *grr)
+{
+ if (grr->pos >= grr->cap) {
+ grr->cap = (grr->cap ? grr->cap * 2 : 1024);
+ Gif_ReArray(grr->v, uint8_t, grr->cap);
+ }
+ if (grr->v) {
+ grr->v[grr->pos] = b;
+ grr->pos++;
+ }
+}
+
+static void
+memory_block_putter(const uint8_t *data, size_t len, Gif_Writer *grr)
+{
+ while (grr->pos + len >= grr->cap) {
+ grr->cap = (grr->cap ? grr->cap * 2 : 1024);
+ Gif_ReArray(grr->v, uint8_t, grr->cap);
+ }
+ if (grr->v) {
+ memcpy(grr->v + grr->pos, data, len);
+ grr->pos += len;
+ }
+}
+
+
+static inline int
+gfc_init(Gif_CodeTable* gfc, const Gif_CompressInfo* gcinfo)
+{
+ gfc->max_loss = 0;
+ if (gcinfo->loss > 0)
+ gfc->max_loss = gcinfo->loss * kc_distance(kc_make8g(0, 0, 0), kc_make8g(0, 0, 8));
+ if (gfc->max_loss > 0) {
+ gfc->loss_scale = 4.0 / gfc->max_loss;
+ gfc->nodes.ls = Gif_NewArray(Gif_LossyNode, NODES_SIZE);
+ } else
+ gfc->nodes.nl = Gif_NewArray(Gif_Node, NODES_SIZE);
+ gfc->links = Gif_NewArray(Gif_Node*, LINKS_SIZE);
+ gfc->global_kcs = NULL;
+ gfc->local_kcs = NULL;
+ gfc->kcs = NULL;
+ gfc->kcdiff = NULL;
+ return (gfc->max_loss > 0 ? !!gfc->nodes.ls : !!gfc->nodes.nl) && gfc->links;
+}
+
+static int
+gif_writer_init(Gif_Writer* grr, FILE* f, const Gif_CompressInfo* gcinfo)
+{
+ grr->f = f;
+ grr->v = NULL;
+ grr->pos = grr->cap = 0;
+ if (gcinfo)
+ grr->gcinfo = *gcinfo;
+ else
+ Gif_InitCompressInfo(&grr->gcinfo);
+ grr->errors = 0;
+ grr->cleared = 0;
+ if (f) {
+ grr->byte_putter = file_byte_putter;
+ grr->block_putter = file_block_putter;
+ } else {
+ grr->byte_putter = memory_byte_putter;
+ grr->block_putter = memory_block_putter;
+ }
+ return gfc_init(&grr->code_table, gcinfo);
+}
+
+static void
+gif_writer_cleanup(Gif_Writer* grr)
+{
+ Gif_DeleteArray(grr->v);
+ if (grr->code_table.max_loss > 0)
+ Gif_DeleteArray(grr->code_table.nodes.ls);
+ else
+ Gif_DeleteArray(grr->code_table.nodes.nl);
+ Gif_DeleteArray(grr->code_table.links);
+ Gif_DeleteArray(grr->code_table.global_kcs);
+ Gif_DeleteArray(grr->code_table.local_kcs);
+ Gif_DeleteArray(grr->code_table.kcdiff);
+}
+
+
+static inline void
+gfc_clear(Gif_CodeTable *gfc)
+{
+ int c;
+ /* The first clear_code nodes are reserved for single-pixel codes */
+ gfc->links_pos = gfc->clear_code;
+ for (c = 0; c < gfc->clear_code; c++) {
+ Gif_Node* n = gfc->max_loss > 0 ? &gfc->nodes.ls[c].node : &gfc->nodes.nl[c];
+ n->code = c;
+ n->type = LINKS_TYPE;
+ n->suffix = c;
+ n->length = 1;
+ n->child.s = NULL;
+ gfc->links[c] = n;
+ }
+}
+
+static inline Gif_Node *
+gfc_lookup(Gif_CodeTable *gfc, Gif_Node *node, uint8_t suffix)
+{
+ assert(suffix < gfc->clear_code);
+ if (!node)
+ return gfc->links[suffix];
+ if (node->type == TABLE_TYPE)
+ return node->child.m[suffix];
+ for (node = node->child.s; node; node = node->sibling)
+ if (node->suffix == suffix)
+ return node;
+ return NULL;
+}
+
+static void
+gfc_change_node_to_table(Gif_CodeTable *gfc, Gif_Node *work_node,
+ Gif_Node *next_node)
+{
+ /* change links node to table node */
+ Gif_Code c;
+ Gif_Node **table = &gfc->links[gfc->links_pos];
+ Gif_Node *n;
+ gfc->links_pos += gfc->clear_code;
+
+ for (c = 0; c < gfc->clear_code; c++)
+ table[c] = 0;
+ table[next_node->suffix] = next_node;
+ for (n = work_node->child.s; n; n = n->sibling)
+ table[n->suffix] = n;
+
+ work_node->type = TABLE_TYPE;
+ work_node->child.m = table;
+}
+
+static inline void
+gfc_define(Gif_CodeTable* gfc, Gif_Node* work_node, uint8_t suffix,
+ Gif_Code next_code)
+{
+ /* Add a new code to our dictionary. First reserve a node for the
+ added code. It's LINKS_TYPE at first. */
+ Gif_Node* next_node = gfc->max_loss > 0 ? &gfc->nodes.ls[next_code].node : &gfc->nodes.nl[next_code];
+ next_node->code = next_code;
+ next_node->type = LINKS_TYPE;
+ next_node->suffix = suffix;
+ next_node->length = work_node->length + 1;
+ next_node->child.s = NULL;
+
+ /* link next_node into work_node's set of children */
+ if (work_node->type == TABLE_TYPE)
+ work_node->child.m[suffix] = next_node;
+ else if (work_node->type < MAX_LINKS_TYPE
+ || gfc->links_pos + gfc->clear_code > LINKS_SIZE) {
+ next_node->sibling = work_node->child.s;
+ work_node->child.s = next_node;
+ if (work_node->type < MAX_LINKS_TYPE)
+ work_node->type++;
+ } else
+ gfc_change_node_to_table(gfc, work_node, next_node);
+}
+
+static inline const uint8_t *
+gif_imageline(Gif_Image *gfi, unsigned pos)
+{
+ unsigned y, x;
+ if (gfi->width == 0)
+ return NULL;
+ y = pos / gfi->width;
+ x = pos - y * gfi->width;
+ if (y == (unsigned) gfi->height)
+ return NULL;
+ else if (!gfi->interlace)
+ return gfi->img[y] + x;
+ else
+ return gfi->img[Gif_InterlaceLine(y, gfi->height)] + x;
+}
+
+static inline unsigned
+gif_line_endpos(Gif_Image *gfi, unsigned pos)
+{
+ unsigned y = pos / gfi->width;
+ return (y + 1) * gfi->width;
+}
+
+static inline uint8_t
+gfc_pixel(Gif_CodeTable* gfc, Gif_Image* gfi, unsigned pos)
+{
+ unsigned y = pos / gfi->width;
+ unsigned x = pos - y * gfi->width;
+ if (gfi->interlace)
+ y = Gif_InterlaceLine(y, gfi->height);
+ uint8_t p = gfi->img[y][x];
+ if (p >= gfc->clear_code)
+ /* should not happen unless GIF_WRITE_CAREFUL_MIN_CODE_BITS */
+ p = 0;
+ return p;
+}
+
+static int
+gfc_lossy_init(Gif_CodeTable* gfc, Gif_Stream* gfs, Gif_Image* gfi)
+{
+ int i, j;
+ Gif_Colormap* gfcm = gfi->local ? gfi->local : gfs->global;
+ kcolor** kcs = gfi->local ? &gfc->local_kcs : &gfc->global_kcs;
+ kcolor* old_kcs = gfc->kcs;
+ if (!*kcs) {
+ *kcs = Gif_NewArray(kcolor, 256);
+ if (!*kcs) {
+ gfc->max_loss = 0;
+ return 0;
+ }
+ for (i = 0; i != gfcm->ncol; ++i) {
+ (*kcs)[i] = kc_makegfcg(&gfcm->col[i]);
+ }
+ for (; i < 256; ++i) {
+ (*kcs)[i] = kc_make8g(0, 0, 0);
+ }
+ }
+ gfc->kcs = *kcs;
+ if (gfc->kcdiff && kcs == &gfc->global_kcs && old_kcs == gfc->global_kcs) {
+ return 1;
+ }
+ if (!gfc->kcdiff
+ && !(gfc->kcdiff = Gif_NewArray(unsigned, 256 * 256))) {
+ gfc->max_loss = 0;
+ return 0;
+ }
+ for (i = 0; i != gfcm->ncol; ++i) {
+ for (j = 0; j != i; ++j) {
+ gfc->kcdiff[(i << 8) | j] = gfc->kcdiff[(j << 8) | i] =
+ kc_distance(gfc->kcs[i], gfc->kcs[j]);
+ }
+ gfc->kcdiff[(i << 8) | i] = 0;
+ }
+ return 1;
+}
+
+/* difference between expected color a+dither and color b (used to calculate dithering required) */
+static inline wkcolor diffused_difference(const Gif_CodeTable* gfc,
+ int expected, int actual, wkcolor error)
+{
+ int i;
+ kcolor a = gfc->kcs[expected], b = gfc->kcs[actual];
+ wkcolor wkc;
+ for (i = 0; i != 3; ++i) {
+ wkc.a[i] = (int32_t) a.a[i] - (int32_t) b.a[i] + 3 * error.a[i] / 4;
+ }
+ return wkc;
+}
+
+static inline int
+gfc_lossy_candidate(Gif_CodeTable* gfc, Gif_Image* gfi,
+ Gif_LossyNode* n, Gif_LossyNode* ch,
+ uint8_t expected)
+{
+ uint8_t actual = ch->node.suffix;
+ unsigned diff;
+ /* exact match */
+ if (expected == actual) {
+ ch->diffaccum = n->diffaccum;
+ ch->error = diffused_difference(gfc, expected, expected, n->error);
+ return 1;
+ }
+ /* transparency never matches */
+ if (expected == gfi->transparent || actual == gfi->transparent)
+ return 0;
+ /* check local loss */
+ diff = gfc->kcdiff[(expected << 8) | actual];
+ if (diff > gfc->max_loss)
+ return 0;
+ /* check accumulated loss */
+ if (!wkc_iszero(n->error)
+ && kc_distance(kc_adjust(gfc->kcs[expected], n->error), gfc->kcs[actual]) > gfc->max_loss)
+ return 0;
+ /* success */
+ ch->diffaccum = n->diffaccum + diff;
+ ch->error = diffused_difference(gfc, expected, actual, n->error);
+ return 1;
+}
+
+static Gif_Node*
+gfc_lookup_lossy(Gif_CodeTable* gfc, Gif_Image* gfi, unsigned pos)
+{
+ Gif_LossyNode* best;
+ double best_score;
+ Gif_LossyNode* stack[NODES_SIZE];
+ unsigned nstack;
+
+ if (pos == gfc->maxpos)
+ return NULL;
+
+ /* initialize with first node */
+ best = (Gif_LossyNode*) gfc_lookup(gfc, NULL, gfc_pixel(gfc, gfi, pos));
+ best->diffaccum = 0;
+ best->error = wkc_zero();
+ best_score = 1.0;
+ stack[0] = best;
+ nstack = 1;
+
+ /* explore until stack is empty */
+ while (nstack > 0) {
+ Gif_LossyNode* n = stack[nstack - 1];
+ unsigned tpos = pos + n->node.length;
+ int i;
+ uint8_t expected;
+ --nstack;
+
+ /* check if this node is better than current best */
+ if (n != best) {
+ double score = n->node.length - n->diffaccum * gfc->loss_scale;
+ if (score > best_score) {
+ best = n;
+ best_score = score;
+ }
+ }
+
+ /* reset error at start of line */
+ if (tpos == gfc->maxpos)
+ continue;
+ if (tpos % gfi->width == 0)
+ n->error = wkc_zero();
+
+ expected = gfc_pixel(gfc, gfi, tpos);
+
+ /* consider all children */
+ if (n->node.type == TABLE_TYPE) {
+ for (i = 0; i < gfc->clear_code; ++i) {
+ Gif_LossyNode* ch = (Gif_LossyNode*) n->node.child.m[i];
+ if (ch && gfc_lossy_candidate(gfc, gfi, n, ch, expected))
+ stack[nstack++] = ch;
+ }
+ } else {
+ Gif_LossyNode* ch;
+ for (ch = (Gif_LossyNode*) n->node.child.s; ch; ch = (Gif_LossyNode*) ch->node.sibling) {
+ if (gfc_lossy_candidate(gfc, gfi, n, ch, expected))
+ stack[nstack++] = ch;
+ }
+ }
+ }
+
+ return &best->node;
+}
+
+static int
+write_compressed_data(Gif_Stream* gfs, Gif_Image* gfi,
+ int min_code_bits, Gif_Writer* grr)
+{
+ Gif_CodeTable* gfc = &grr->code_table;
+ uint8_t stack_buffer[512 - 24];
+ uint8_t *buf = stack_buffer;
+ unsigned bufpos = 0;
+ unsigned bufcap = sizeof(stack_buffer) * 8;
+
+ unsigned pos;
+ unsigned clear_bufpos, clear_pos;
+ unsigned line_endpos;
+ const unsigned image_endpos = (size_t) gfi->height * (size_t) gfi->width;
+ const uint8_t *imageline;
+
+ unsigned run = 0;
+#define RUN_EWMA_SHIFT 4
+#define RUN_EWMA_SCALE 19
+#define RUN_INV_THRESH ((unsigned) (1 << RUN_EWMA_SCALE) / 3000)
+ unsigned run_ewma = 0;
+ Gif_Node *work_node;
+ Gif_Node *next_node;
+ Gif_Code next_code = 0;
+ Gif_Code output_code;
+ uint8_t suffix;
+
+ int cur_code_bits;
+
+ /* Here we go! */
+ gifputbyte(min_code_bits, grr);
+#define CUR_BUMP_CODE (1 << cur_code_bits)
+ gfc->clear_code = (Gif_Code) (1 << min_code_bits);
+ gfc->maxpos = gfi->width * gfi->height;
+ grr->cleared = 0;
+
+ cur_code_bits = min_code_bits + 1;
+ /* next_code set by first runthrough of output clear_code */
+ GIF_DEBUG(("clear(%d) eoi(%d) bits(%d) ", gfc->clear_code, gfc->clear_code + 1, cur_code_bits));
+
+ work_node = NULL;
+ output_code = gfc->clear_code;
+ /* Because output_code is clear_code, we'll initialize next_code, et al.
+ below. */
+
+ pos = clear_pos = clear_bufpos = 0;
+ line_endpos = gfi->width;
+ imageline = gif_imageline(gfi, pos);
+
+ if (gfc->max_loss > 0) {
+ gfc_lossy_init(gfc, gfs, gfi);
+ }
+
+ while (1) {
+
+ /*****
+ * Output 'output_code' to the memory buffer. */
+ if (bufpos + 32 >= bufcap) {
+ unsigned ncap = bufcap * 2 + (24 << 3);
+ uint8_t *nbuf = Gif_NewArray(uint8_t, ncap >> 3);
+ if (!nbuf)
+ goto error;
+ memcpy(nbuf, buf, bufcap >> 3);
+ if (buf != stack_buffer)
+ Gif_DeleteArray(buf);
+ buf = nbuf;
+ bufcap = ncap;
+ }
+
+ {
+ unsigned endpos = bufpos + cur_code_bits;
+ do {
+ if (bufpos & 7)
+ buf[bufpos >> 3] |= output_code << (bufpos & 7);
+ else if (bufpos & 0x7FF)
+ buf[bufpos >> 3] = output_code >> (bufpos - endpos + cur_code_bits);
+ else {
+ buf[bufpos >> 3] = 255;
+ endpos += 8;
+ }
+
+ bufpos += 8 - (bufpos & 7);
+ } while (bufpos < endpos);
+ bufpos = endpos;
+ }
+
+
+ /*****
+ * Handle special codes. */
+
+ if (output_code == gfc->clear_code) {
+ /* Clear data and prepare gfc */
+ cur_code_bits = min_code_bits + 1;
+ next_code = gfc->clear_code + 2;
+ run_ewma = 1 << RUN_EWMA_SCALE;
+ run = 0;
+ gfc_clear(gfc);
+ clear_pos = clear_bufpos = 0;
+
+ GIF_DEBUG(("clear "));
+
+ } else if (output_code == gfc->clear_code + 1)
+ break;
+
+ else {
+ if (next_code > CUR_BUMP_CODE && cur_code_bits < GIF_MAX_CODE_BITS)
+ /* bump up compression size */
+ ++cur_code_bits;
+
+ /* Adjust current run length average. */
+ run = (run << RUN_EWMA_SCALE) + (1 << (RUN_EWMA_SHIFT - 1));
+ if (run < run_ewma)
+ run_ewma -= (run_ewma - run) >> RUN_EWMA_SHIFT;
+ else
+ run_ewma += (run - run_ewma) >> RUN_EWMA_SHIFT;
+
+ /* Reset run length. */
+ run = !!work_node;
+ }
+
+
+ /*****
+ * Find the next code to output. */
+ if (gfc->max_loss) {
+ work_node = gfc_lookup_lossy(gfc, gfi, pos);
+ run = work_node ? work_node->length : 0;
+ pos += run;
+
+ if (pos < image_endpos) {
+ /* Output the current code. */
+ if (next_code < GIF_MAX_CODE) {
+ gfc_define(gfc, work_node, gfc_pixel(gfc, gfi, pos), next_code);
+ next_code++;
+ } else
+ next_code = GIF_MAX_CODE + 1; /* to match "> CUR_BUMP_CODE" above */
+
+ /* Check whether to clear table. */
+ if (next_code > 4094) {
+ int do_clear = grr->gcinfo.flags & GIF_WRITE_EAGER_CLEAR;
+
+ if (!do_clear) {
+ unsigned pixels_left = image_endpos - pos - 1;
+ if (pixels_left) {
+ /* Always clear if run_ewma gets small relative to
+ min_code_bits. Otherwise, clear if #images/run is smaller
+ than an empirical threshold, meaning it will take more than
+ 3000 or so average runs to complete the image. */
+ if (run_ewma < ((36U << RUN_EWMA_SCALE) / min_code_bits)
+ || pixels_left > UINT_MAX / RUN_INV_THRESH
+ || run_ewma < pixels_left * RUN_INV_THRESH)
+ do_clear = 1;
+ }
+ }
+
+ if ((do_clear || run < 7) && !clear_pos) {
+ clear_pos = pos - run;
+ clear_bufpos = bufpos;
+ } else if (!do_clear && run > 50)
+ clear_pos = clear_bufpos = 0;
+
+ if (do_clear) {
+ GIF_DEBUG(("rewind %u pixels/%d bits", pos + 1 - clear_pos, bufpos + cur_code_bits - clear_bufpos));
+ output_code = gfc->clear_code;
+ pos = clear_pos;
+
+ bufpos = clear_bufpos;
+ buf[bufpos >> 3] &= (1 << (bufpos & 7)) - 1;
+ grr->cleared = 1;
+ continue;
+ }
+ }
+
+ /* Adjust current run length average. */
+ run = (run << RUN_EWMA_SCALE) + (1 << (RUN_EWMA_SHIFT - 1));
+ if (run < run_ewma)
+ run_ewma -= (run_ewma - run) >> RUN_EWMA_SHIFT;
+ else
+ run_ewma += (run - run_ewma) >> RUN_EWMA_SHIFT;
+ }
+
+ output_code = work_node ? work_node->code : gfc->clear_code + 1;
+ } else {
+ /* If height is 0 -- no more pixels to write -- we output work_node next
+ time around. */
+ while (imageline) {
+ suffix = *imageline;
+ if (suffix >= gfc->clear_code)
+ /* should not happen unless GIF_WRITE_CAREFUL_MIN_CODE_BITS */
+ suffix = 0;
+ next_node = gfc_lookup(gfc, work_node, suffix);
+
+ imageline++;
+ pos++;
+ if (pos == line_endpos) {
+ imageline = gif_imageline(gfi, pos);
+ line_endpos += gfi->width;
+ }
+
+ if (next_node) {
+ work_node = next_node;
+ ++run;
+ continue;
+ }
+
+ /* Output the current code. */
+ if (next_code < GIF_MAX_CODE) {
+ gfc_define(gfc, work_node, suffix, next_code);
+ next_code++;
+ } else
+ next_code = GIF_MAX_CODE + 1; /* to match "> CUR_BUMP_CODE" above */
+
+ /* Check whether to clear table. */
+ if (next_code > 4094) {
+ int do_clear = grr->gcinfo.flags & GIF_WRITE_EAGER_CLEAR;
+
+ if (!do_clear) {
+ unsigned pixels_left = image_endpos - pos;
+ if (pixels_left) {
+ /* Always clear if run_ewma gets small relative to
+ min_code_bits. Otherwise, clear if #images/run is smaller
+ than an empirical threshold, meaning it will take more than
+ 3000 or so average runs to complete the image. */
+ if (run_ewma < ((36U << RUN_EWMA_SCALE) / min_code_bits)
+ || pixels_left > UINT_MAX / RUN_INV_THRESH
+ || run_ewma < pixels_left * RUN_INV_THRESH)
+ do_clear = 1;
+ }
+ }
+
+ if ((do_clear || run < 7) && !clear_pos) {
+ clear_pos = pos - (run + 1);
+ clear_bufpos = bufpos;
+ } else if (!do_clear && run > 50)
+ clear_pos = clear_bufpos = 0;
+
+ if (do_clear) {
+ GIF_DEBUG(("rewind %u pixels/%d bits ", pos - clear_pos, bufpos + cur_code_bits - clear_bufpos));
+ output_code = gfc->clear_code;
+ pos = clear_pos;
+ imageline = gif_imageline(gfi, pos);
+ line_endpos = gif_line_endpos(gfi, pos);
+ bufpos = clear_bufpos;
+ buf[bufpos >> 3] &= (1 << (bufpos & 7)) - 1;
+ work_node = NULL;
+ grr->cleared = 1;
+ goto found_output_code;
+ }
+ }
+
+ output_code = work_node->code;
+ work_node = gfc->links[suffix];
+ goto found_output_code;
+ }
+
+ /* Ran out of data if we get here. */
+ output_code = (work_node ? work_node->code : gfc->clear_code + 1);
+ work_node = NULL;
+
+ found_output_code: ;
+ }
+ }
+
+ /* Output memory buffer to stream. */
+ bufpos = (bufpos + 7) >> 3;
+ buf[(bufpos - 1) & 0xFFFFFF00] = (bufpos - 1) & 0xFF;
+ buf[bufpos] = 0;
+ gifputblock(buf, bufpos + 1, grr);
+
+ if (buf != stack_buffer)
+ Gif_DeleteArray(buf);
+ return 1;
+
+ error:
+ if (buf != stack_buffer)
+ Gif_DeleteArray(buf);
+ return 0;
+}
+
+
+static int
+calculate_min_code_bits(Gif_Image *gfi, const Gif_Writer* grr)
+{
+ int colors_used = -1, min_code_bits, i;
+
+ if (grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) {
+ /* calculate m_c_b based on colormap */
+ if (grr->local_size > 0)
+ colors_used = grr->local_size;
+ else if (grr->global_size > 0)
+ colors_used = grr->global_size;
+
+ } else if (gfi->img) {
+ /* calculate m_c_b from uncompressed data */
+ int x, y, width = gfi->width, height = gfi->height;
+ colors_used = 0;
+ for (y = 0; y < height && colors_used < 128; y++) {
+ uint8_t *data = gfi->img[y];
+ for (x = width; x > 0; x--, data++)
+ if (*data > colors_used)
+ colors_used = *data;
+ }
+ colors_used++;
+
+ } else if (gfi->compressed) {
+ /* take m_c_b from compressed image */
+ colors_used = 1 << gfi->compressed[0];
+
+ } else {
+ /* should never happen */
+ colors_used = 256;
+ }
+
+ min_code_bits = 2; /* min_code_bits of 1 isn't allowed */
+ i = 4;
+ while (i < colors_used) {
+ min_code_bits++;
+ i *= 2;
+ }
+
+ return min_code_bits;
+}
+
+
+static int get_color_table_size(const Gif_Stream* gfs, Gif_Image* gfi,
+ Gif_Writer* grr);
+
+static void
+save_compression_result(Gif_Image* gfi, Gif_Writer* grr, int ok)
+{
+ if (!(grr->gcinfo.flags & GIF_WRITE_SHRINK)
+ || (ok && (!gfi->compressed || gfi->compressed_len > grr->pos))) {
+ if (gfi->compressed)
+ (*gfi->free_compressed)((void *) gfi->compressed);
+ if (ok) {
+ gfi->compressed_len = grr->pos;
+ gfi->compressed_errors = 0;
+ gfi->compressed = grr->v;
+ gfi->free_compressed = Gif_Free;
+ grr->v = 0;
+ grr->cap = 0;
+ } else
+ gfi->compressed = 0;
+ }
+ grr->pos = 0;
+}
+
+int
+Gif_FullCompressImage(Gif_Stream *gfs, Gif_Image *gfi,
+ const Gif_CompressInfo *gcinfo)
+{
+ int ok = 0;
+ uint8_t min_code_bits;
+ Gif_Writer grr;
+
+ if (!gif_writer_init(&grr, NULL, gcinfo)) {
+ if (!(grr.gcinfo.flags & GIF_WRITE_SHRINK))
+ Gif_ReleaseCompressedImage(gfi);
+ goto done;
+ }
+
+ grr.global_size = get_color_table_size(gfs, 0, &grr);
+ grr.local_size = get_color_table_size(gfs, gfi, &grr);
+
+ min_code_bits = calculate_min_code_bits(gfi, &grr);
+ ok = write_compressed_data(gfs, gfi, min_code_bits, &grr);
+ save_compression_result(gfi, &grr, ok);
+
+ if ((grr.gcinfo.flags & (GIF_WRITE_OPTIMIZE | GIF_WRITE_EAGER_CLEAR))
+ == GIF_WRITE_OPTIMIZE
+ && grr.cleared && ok) {
+ grr.gcinfo.flags |= GIF_WRITE_EAGER_CLEAR | GIF_WRITE_SHRINK;
+ if (write_compressed_data(gfs, gfi, min_code_bits, &grr))
+ save_compression_result(gfi, &grr, 1);
+ }
+
+ done:
+ gif_writer_cleanup(&grr);
+ return ok;
+}
+
+
+static int
+get_color_table_size(const Gif_Stream* gfs, Gif_Image* gfi, Gif_Writer* grr)
+{
+ Gif_Colormap *gfcm = gfi ? gfi->local : gfs->global;
+ int ncol, totalcol, i;
+
+ if (!gfcm || gfcm->ncol <= 0)
+ return 0;
+
+ /* Make sure ncol is reasonable */
+ ncol = gfcm->ncol;
+
+ /* Possibly bump up 'ncol' based on 'transparent' values, if
+ careful_min_code_bits */
+ if (grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE) {
+ if (gfi && gfi->transparent >= ncol)
+ ncol = gfi->transparent + 1;
+ else if (!gfi)
+ for (i = 0; i < gfs->nimages; i++)
+ if (gfs->images[i]->transparent >= ncol)
+ ncol = gfs->images[i]->transparent + 1;
+ }
+
+ /* Make sure the colormap is a power of two entries! */
+ /* GIF format doesn't allow a colormap with only 1 entry. */
+ if (ncol > 256)
+ ncol = 256;
+ for (totalcol = 2; totalcol < ncol; totalcol *= 2)
+ /* nada */;
+
+ return totalcol;
+}
+
+static void
+write_color_table(Gif_Colormap* gfcm, int totalcol, Gif_Writer* grr)
+{
+ Gif_Color *c = gfcm->col;
+ int i, ncol = gfcm->ncol;
+
+ for (i = 0; i < ncol && i < totalcol; i++, c++) {
+ gifputbyte(c->gfc_red, grr);
+ gifputbyte(c->gfc_green, grr);
+ gifputbyte(c->gfc_blue, grr);
+ }
+
+ /* Pad out colormap with black. */
+ for (; i < totalcol; i++) {
+ gifputbyte(0, grr);
+ gifputbyte(0, grr);
+ gifputbyte(0, grr);
+ }
+}
+
+
+static int
+write_image(Gif_Stream* gfs, Gif_Image* gfi, Gif_Writer* grr)
+{
+ uint8_t min_code_bits, packed = 0;
+ grr->local_size = get_color_table_size(gfs, gfi, grr);
+
+ gifputbyte(',', grr);
+ gifputunsigned(gfi->left, grr);
+ gifputunsigned(gfi->top, grr);
+ gifputunsigned(gfi->width, grr);
+ gifputunsigned(gfi->height, grr);
+
+ if (grr->local_size > 0) {
+ int size = 2;
+ packed |= 0x80;
+ while (size < grr->local_size)
+ size *= 2, packed++;
+ }
+
+ if (gfi->interlace) packed |= 0x40;
+ gifputbyte(packed, grr);
+
+ if (grr->local_size > 0)
+ write_color_table(gfi->local, grr->local_size, grr);
+
+ /* calculate min_code_bits here (because calculation may involve
+ recompression, if GIF_WRITE_CAREFUL_MIN_CODE_SIZE is true) */
+ min_code_bits = calculate_min_code_bits(gfi, grr);
+
+ /* use existing compressed data if it exists. This will tend to whip
+ people's asses who uncompress an image, keep the compressed data around,
+ but modify the uncompressed data anyway. That sucks. */
+ if (gfi->compressed
+ && (!(grr->gcinfo.flags & GIF_WRITE_CAREFUL_MIN_CODE_SIZE)
+ || gfi->compressed[0] == min_code_bits)) {
+ uint8_t *compressed = gfi->compressed;
+ uint32_t compressed_len = gfi->compressed_len;
+ while (compressed_len > 0) {
+ uint16_t amt = (compressed_len > 0x7000 ? 0x7000 : compressed_len);
+ gifputblock(compressed, amt, grr);
+ compressed += amt;
+ compressed_len -= amt;
+ }
+
+ } else if (!gfi->img) {
+ Gif_UncompressImage(gfs, gfi);
+ write_compressed_data(gfs, gfi, min_code_bits, grr);
+ Gif_ReleaseUncompressedImage(gfi);
+
+ } else
+ write_compressed_data(gfs, gfi, min_code_bits, grr);
+
+ return 1;
+}
+
+
+static void
+write_logical_screen_descriptor(Gif_Stream *gfs, Gif_Writer *grr)
+{
+ uint8_t packed = 0x70; /* high resolution colors */
+ grr->global_size = get_color_table_size(gfs, 0, grr);
+
+ Gif_CalculateScreenSize(gfs, 0);
+ gifputunsigned(gfs->screen_width, grr);
+ gifputunsigned(gfs->screen_height, grr);
+
+ if (grr->global_size > 0) {
+ uint16_t size = 2;
+ packed |= 0x80;
+ while (size < grr->global_size)
+ size *= 2, packed++;
+ }
+
+ gifputbyte(packed, grr);
+ if (gfs->background < grr->global_size)
+ gifputbyte(gfs->background, grr);
+ else
+ gifputbyte(255, grr);
+ gifputbyte(0, grr); /* no aspect ratio information */
+
+ if (grr->global_size > 0)
+ write_color_table(gfs->global, grr->global_size, grr);
+}
+
+
+/* extension byte table:
+ 0x01 plain text extension
+ 0xCE name*
+ 0xF9 graphic control extension
+ 0xFE comment extension
+ 0xFF application extension
+ */
+
+static void
+write_graphic_control_extension(Gif_Image *gfi, Gif_Writer *grr)
+{
+ uint8_t packed = 0;
+ gifputbyte('!', grr);
+ gifputbyte(0xF9, grr);
+ gifputbyte(4, grr);
+ if (gfi->transparent >= 0) packed |= 0x01;
+ packed |= (gfi->disposal & 0x07) << 2;
+ gifputbyte(packed, grr);
+ gifputunsigned(gfi->delay, grr);
+ gifputbyte((uint8_t)gfi->transparent, grr);
+ gifputbyte(0, grr);
+}
+
+
+static void
+blast_data(const uint8_t *data, int len, Gif_Writer *grr)
+{
+ while (len > 0) {
+ int s = len > 255 ? 255 : len;
+ gifputbyte(s, grr);
+ gifputblock(data, s, grr);
+ data += s;
+ len -= s;
+ }
+ gifputbyte(0, grr);
+}
+
+
+static void
+write_name_extension(char *id, Gif_Writer *grr)
+{
+ gifputbyte('!', grr);
+ gifputbyte(0xCE, grr);
+ blast_data((uint8_t *)id, strlen(id), grr);
+}
+
+
+static void
+write_comment_extensions(Gif_Comment *gfcom, Gif_Writer *grr)
+{
+ int i;
+ for (i = 0; i < gfcom->count; i++) {
+ gifputbyte('!', grr);
+ gifputbyte(0xFE, grr);
+ blast_data((const uint8_t *)gfcom->str[i], gfcom->len[i], grr);
+ }
+}
+
+
+static void
+write_netscape_loop_extension(uint16_t value, Gif_Writer *grr)
+{
+ gifputblock((const uint8_t *)"!\xFF\x0BNETSCAPE2.0\x03\x01", 16, grr);
+ gifputunsigned(value, grr);
+ gifputbyte(0, grr);
+}
+
+
+static void
+write_generic_extension(Gif_Extension *gfex, Gif_Writer *grr)
+{
+ uint32_t pos = 0;
+ if (gfex->kind < 0) return; /* ignore our private extensions */
+
+ gifputbyte('!', grr);
+ gifputbyte(gfex->kind, grr);
+ if (gfex->kind == 255) { /* an application extension */
+ if (gfex->applength) {
+ gifputbyte(gfex->applength, grr);
+ gifputblock((const uint8_t*) gfex->appname, gfex->applength, grr);
+ }
+ }
+ if (gfex->packetized)
+ gifputblock(gfex->data, gfex->length, grr);
+ else {
+ while (pos + 255 < gfex->length) {
+ gifputbyte(255, grr);
+ gifputblock(gfex->data + pos, 255, grr);
+ pos += 255;
+ }
+ if (pos < gfex->length) {
+ uint32_t len = gfex->length - pos;
+ gifputbyte(len, grr);
+ gifputblock(gfex->data + pos, len, grr);
+ }
+ }
+ gifputbyte(0, grr);
+}
+
+static int
+write_gif(Gif_Stream *gfs, Gif_Writer *grr)
+{
+ Gif_Extension* gfex;
+ int ok = 0;
+ int i;
+
+ {
+ uint8_t isgif89a = 0;
+ if (gfs->end_comment || gfs->end_extension_list || gfs->loopcount > -1)
+ isgif89a = 1;
+ for (i = 0; i < gfs->nimages && !isgif89a; i++) {
+ Gif_Image* gfi = gfs->images[i];
+ if (gfi->identifier || gfi->transparent != -1 || gfi->disposal
+ || gfi->delay || gfi->comment || gfi->extension_list)
+ isgif89a = 1;
+ }
+ if (isgif89a)
+ gifputblock((const uint8_t *)"GIF89a", 6, grr);
+ else
+ gifputblock((const uint8_t *)"GIF87a", 6, grr);
+ }
+
+ write_logical_screen_descriptor(gfs, grr);
+
+ if (gfs->loopcount > -1)
+ write_netscape_loop_extension(gfs->loopcount, grr);
+
+ for (i = 0; i < gfs->nimages; i++)
+ if (!Gif_IncrementalWriteImage(grr, gfs, gfs->images[i]))
+ goto done;
+
+ for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next)
+ write_generic_extension(gfex, grr);
+ if (gfs->end_comment)
+ write_comment_extensions(gfs->end_comment, grr);
+
+ gifputbyte(';', grr);
+ ok = 1;
+
+ done:
+ return ok;
+}
+
+
+int
+Gif_FullWriteFile(Gif_Stream *gfs, const Gif_CompressInfo *gcinfo,
+ FILE *f)
+{
+ Gif_Writer grr;
+ int ok = gif_writer_init(&grr, f, gcinfo)
+ && write_gif(gfs, &grr);
+ gif_writer_cleanup(&grr);
+ return ok;
+}
+
+
+Gif_Writer*
+Gif_IncrementalWriteFileInit(Gif_Stream* gfs, const Gif_CompressInfo* gcinfo,
+ FILE *f)
+{
+ Gif_Writer* grr = Gif_New(Gif_Writer);
+ if (!grr || !gif_writer_init(grr, f, gcinfo)) {
+ Gif_Delete(grr);
+ return NULL;
+ }
+ gifputblock((const uint8_t *)"GIF89a", 6, grr);
+ write_logical_screen_descriptor(gfs, grr);
+ if (gfs->loopcount > -1)
+ write_netscape_loop_extension(gfs->loopcount, grr);
+ return grr;
+}
+
+int
+Gif_IncrementalWriteImage(Gif_Writer* grr, Gif_Stream* gfs, Gif_Image* gfi)
+{
+ Gif_Extension *gfex;
+ for (gfex = gfi->extension_list; gfex; gfex = gfex->next)
+ write_generic_extension(gfex, grr);
+ if (gfi->comment)
+ write_comment_extensions(gfi->comment, grr);
+ if (gfi->identifier)
+ write_name_extension(gfi->identifier, grr);
+ if (gfi->transparent != -1 || gfi->disposal || gfi->delay)
+ write_graphic_control_extension(gfi, grr);
+ return write_image(gfs, gfi, grr);
+}
+
+int
+Gif_IncrementalWriteComplete(Gif_Writer* grr, Gif_Stream* gfs)
+{
+ Gif_Extension* gfex;
+ for (gfex = gfs->end_extension_list; gfex; gfex = gfex->next)
+ write_generic_extension(gfex, grr);
+ if (gfs->end_comment)
+ write_comment_extensions(gfs->end_comment, grr);
+ gifputbyte(';', grr);
+ gif_writer_cleanup(grr);
+ Gif_Delete(grr);
+ return 1;
+}
+
+
+#undef Gif_CompressImage
+#undef Gif_WriteFile
+
+int
+Gif_CompressImage(Gif_Stream *gfs, Gif_Image *gfi)
+{
+ return Gif_FullCompressImage(gfs, gfi, 0);
+}
+
+int
+Gif_WriteFile(Gif_Stream *gfs, FILE *f)
+{
+ return Gif_FullWriteFile(gfs, 0, f);
+}
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/gifx.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifx.c
new file mode 100644
index 0000000..3b12fcb
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/gifx.c
@@ -0,0 +1,852 @@
+/* gifx.c - Functions to turn GIFs in memory into X Pixmaps.
+ Copyright (C) 1997-2025 Eddie Kohler, ekohler@gmail.com
+ This file is part of the LCDF GIF library.
+
+ The LCDF GIF library is free software. It is distributed under the GNU
+ General 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. */
+
+#if HAVE_CONFIG_H
+# include
+#endif
+#include
+#include
+#include
+#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SAFELS(a,b) ((b) < 0 ? (a) >> -(b) : (a) << (b))
+
+struct Gif_XColormap {
+
+ Gif_XContext *x_context;
+ Gif_Colormap *colormap;
+
+ int allocated;
+ int claimed;
+ uint16_t npixels;
+ unsigned long *pixels;
+
+ Gif_XColormap *next;
+
+};
+
+static unsigned long crap_pixels[256];
+
+
+static void
+load_closest(Gif_XContext *gfx)
+{
+ XColor *color;
+ uint16_t ncolor;
+ uint16_t ncolormap;
+ int i;
+
+ if (gfx->closest) return;
+
+ ncolormap = ncolor = gfx->ncolormap;
+ if (ncolor > 256) ncolor = 256;
+ color = Gif_NewArray(XColor, ncolor);
+
+ if (ncolormap > 256)
+ for (i = 0; i < ncolor; i++)
+ color[i].pixel = (rand() >> 4) % ncolormap;
+ else
+ for (i = 0; i < ncolor; i++)
+ color[i].pixel = i;
+ XQueryColors(gfx->display, gfx->colormap, color, ncolor);
+
+ gfx->closest = Gif_NewArray(Gif_Color, ncolor);
+ for (i = 0; i < ncolor; i++) {
+ Gif_Color *c = &gfx->closest[i];
+ c->haspixel = 1;
+ c->gfc_red = color[i].red >> 8;
+ c->gfc_green = color[i].green >> 8;
+ c->gfc_blue = color[i].blue >> 8;
+ c->pixel = color[i].pixel;
+ }
+ gfx->nclosest = ncolor;
+
+ Gif_DeleteArray(color);
+}
+
+
+static unsigned long
+allocate_closest(Gif_XContext *gfx, Gif_Color *c)
+{
+ Gif_Color *closer;
+ Gif_Color *got = 0;
+ uint32_t distance = 0x4000000;
+ int i;
+
+ load_closest(gfx);
+
+ for (i = 0, closer = gfx->closest; i < gfx->nclosest; i++, closer++) {
+ int redd = c->gfc_red - closer->gfc_red;
+ int greend = c->gfc_green - closer->gfc_green;
+ int blued = c->gfc_blue - closer->gfc_blue;
+ uint32_t d = redd * redd + greend * greend + blued * blued;
+ if (d < distance) {
+ distance = d;
+ got = closer;
+ }
+ }
+
+ if (!got) return 0;
+ if (!got->haspixel) {
+ XColor xcol;
+ xcol.red = got->gfc_red | (got->gfc_red << 8);
+ xcol.green = got->gfc_green | (got->gfc_green << 8);
+ xcol.blue = got->gfc_blue | (got->gfc_blue << 8);
+ if (XAllocColor(gfx->display, gfx->colormap, &xcol) == 0) {
+ /* Probably was a read/write color cell. Get rid of it!! */
+ *got = gfx->closest[gfx->nclosest - 1];
+ gfx->nclosest--;
+ return allocate_closest(gfx, c);
+ }
+ got->pixel = xcol.pixel;
+ got->haspixel = 1;
+ }
+
+ return got->pixel;
+}
+
+
+static void
+allocate_colors(Gif_XColormap *gfxc)
+{
+ Gif_XContext *gfx = gfxc->x_context;
+ uint16_t size = gfxc->colormap->ncol;
+ Gif_Color *c = gfxc->colormap->col;
+ unsigned long *pixels = gfxc->pixels;
+ XColor xcol;
+ int i;
+ if (!gfxc->allocated) {
+ if (size > gfxc->npixels) size = gfxc->npixels;
+ for (i = 0; i < size; i++, c++) {
+ xcol.red = c->gfc_red | (c->gfc_red << 8);
+ xcol.green = c->gfc_green | (c->gfc_green << 8);
+ xcol.blue = c->gfc_blue | (c->gfc_blue << 8);
+ if (XAllocColor(gfx->display, gfx->colormap, &xcol))
+ pixels[i] = xcol.pixel;
+ else
+ pixels[i] = allocate_closest(gfx, c);
+ }
+ gfxc->allocated = 1;
+ gfxc->claimed = 0;
+ }
+}
+
+static void
+deallocate_colors(Gif_XColormap *gfxc)
+{
+ Gif_XContext *gfx = gfxc->x_context;
+ if (gfxc->allocated && !gfxc->claimed) {
+ XFreeColors(gfx->display, gfx->colormap, gfxc->pixels, gfxc->npixels, 0);
+ gfxc->allocated = 0;
+ }
+}
+
+
+static Gif_XColormap *
+create_x_colormap_extension(Gif_XContext *gfx, Gif_Colormap *gfcm)
+{
+ Gif_XColormap *gfxc;
+ unsigned long *pixels;
+ if (!gfcm) return 0;
+ gfxc = Gif_New(Gif_XColormap);
+ pixels = gfxc ? Gif_NewArray(unsigned long, 256) : 0;
+ if (pixels) {
+ gfxc->x_context = gfx;
+ gfxc->colormap = gfcm;
+ gfxc->allocated = 0;
+ gfxc->npixels = gfcm->ncol;
+ gfxc->pixels = pixels;
+ gfxc->next = gfx->xcolormap;
+ gfx->xcolormap = gfxc;
+ return gfxc;
+ } else {
+ Gif_Delete(gfxc);
+ Gif_DeleteArray(pixels);
+ return 0;
+ }
+}
+
+static Gif_XColormap *
+find_x_colormap_extension(Gif_XContext *gfx, Gif_Colormap *gfcm, int create)
+{
+ Gif_XColormap *gfxc = gfx->xcolormap;
+ if (!gfcm) return 0;
+ while (gfxc) {
+ if (gfxc->colormap == gfcm)
+ return gfxc;
+ gfxc = gfxc->next;
+ }
+ if (create)
+ return create_x_colormap_extension(gfx, gfcm);
+ else
+ return 0;
+}
+
+int
+Gif_XAllocateColors(Gif_XContext *gfx, Gif_Colormap *gfcm)
+{
+ Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1);
+ if (gfxc) {
+ allocate_colors(gfxc);
+ return 1;
+ } else
+ return 0;
+}
+
+void
+Gif_XDeallocateColors(Gif_XContext *gfx, Gif_Colormap *gfcm)
+{
+ Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0);
+ if (gfxc)
+ deallocate_colors(gfxc);
+}
+
+
+unsigned long *
+Gif_XClaimStreamColors(Gif_XContext *gfx, Gif_Stream *gfs, int *np_store)
+{
+ int i;
+ int npixels = 0;
+ unsigned long *pixels;
+ Gif_Colormap *global = gfs->global;
+ *np_store = 0;
+
+ for (i = 0; i < gfs->nimages; i++) {
+ Gif_Image *gfi = gfs->images[i];
+ Gif_Colormap *gfcm = (gfi->local ? gfi->local : global);
+ Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0);
+ if (gfxc && gfxc->allocated && gfxc->claimed == 0) {
+ gfxc->claimed = 2;
+ npixels += gfxc->npixels;
+ if (gfcm == global) global = 0;
+ }
+ }
+
+ if (!npixels) return 0;
+
+ pixels = Gif_NewArray(unsigned long, npixels);
+ if (!pixels) return 0;
+ *np_store = npixels;
+
+ npixels = 0;
+ global = gfs->global;
+ for (i = 0; i < gfs->nimages; i++) {
+ Gif_Image *gfi = gfs->images[i];
+ Gif_Colormap *gfcm = (gfi->local ? gfi->local : global);
+ Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0);
+ if (gfxc && gfxc->allocated && gfxc->claimed == 2) {
+ memcpy(pixels + npixels, gfxc->pixels, gfxc->npixels);
+ npixels += gfxc->npixels;
+ gfxc->claimed = 1;
+ if (gfcm == global) global = 0;
+ }
+ }
+
+ return pixels;
+}
+
+
+/* Getting pixmaps */
+
+#define BYTESIZE 8
+
+static int
+put_sub_image_colormap(Gif_XContext *gfx,
+ Gif_Stream* gfs, Gif_Image *gfi, Gif_Colormap *gfcm,
+ int left, int top, int width, int height,
+ Pixmap pixmap, int pixmap_x, int pixmap_y)
+{
+ XImage *ximage;
+ uint8_t *xdata;
+
+ int i, j, k;
+ size_t bytes_per_line;
+
+ unsigned long saved_transparent = 0;
+ int release_uncompressed = 0;
+ uint16_t nct;
+ unsigned long *pixels;
+
+ /* Find the correct image and colormap */
+ if (!gfi) return 0;
+ if (!gfx->image_gc)
+ gfx->image_gc = XCreateGC(gfx->display, pixmap, 0, 0);
+ if (!gfx->image_gc)
+ return 0;
+
+ /* Make sure the image is uncompressed */
+ if (!gfi->img && !gfi->image_data && gfi->compressed) {
+ Gif_UncompressImage(gfs, gfi);
+ release_uncompressed = 1;
+ }
+
+ /* Check subimage dimensions */
+ if (width <= 0 || height <= 0 || left < 0 || top < 0
+ || left+width <= 0 || top+height <= 0
+ || left+width > gfi->width || top+height > gfi->height)
+ return 0;
+
+ /* Allocate colors from the colormap; make sure the transparent color
+ * has the given pixel value */
+ if (gfcm) {
+ Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1);
+ if (!gfxc) return 0;
+ allocate_colors(gfxc);
+ pixels = gfxc->pixels;
+ nct = gfxc->npixels;
+ } else {
+ for (i = 0; i < 256; i++) crap_pixels[i] = gfx->foreground_pixel;
+ pixels = crap_pixels;
+ nct = 256;
+ }
+ if (gfi->transparent >= 0 && gfi->transparent < 256) {
+ saved_transparent = pixels[ gfi->transparent ];
+ pixels[ gfi->transparent ] = gfx->transparent_pixel;
+ }
+
+ /* Set up the X image */
+ if (gfx->depth <= 8) i = 8;
+ else if (gfx->depth <= 16) i = 16;
+ else i = 32;
+ ximage =
+ XCreateImage(gfx->display, gfx->visual, gfx->depth,
+ gfx->depth == 1 ? XYBitmap : ZPixmap, 0, NULL,
+ width, height, i, 0);
+
+ ximage->bitmap_bit_order = ximage->byte_order = LSBFirst;
+ bytes_per_line = ximage->bytes_per_line;
+ xdata = Gif_NewArray(uint8_t, bytes_per_line * height);
+ ximage->data = (char *)xdata;
+
+ /* The main loop */
+ if (ximage->bits_per_pixel % 8 == 0) {
+ /* Optimize for cases where a pixel is exactly one or more bytes */
+ int bytes_per_pixel = ximage->bits_per_pixel / 8;
+
+ for (j = 0; j < height; j++) {
+ uint8_t *line = gfi->img[top + j] + left;
+ uint8_t *writer = xdata + bytes_per_line * j;
+ for (i = 0; i < width; i++) {
+ unsigned long pixel;
+ if (line[i] < nct)
+ pixel = pixels[line[i]];
+ else
+ pixel = pixels[0];
+ for (k = 0; k < bytes_per_pixel; k++) {
+ *writer++ = pixel;
+ pixel >>= 8;
+ }
+ }
+ }
+
+ } else {
+ /* Other bits-per-pixel */
+ int bits_per_pixel = ximage->bits_per_pixel;
+ uint32_t bits_per_pixel_mask = (1UL << bits_per_pixel) - 1;
+
+ for (j = 0; j < height; j++) {
+ int imshift = 0;
+ uint32_t impixel = 0;
+ uint8_t *line = gfi->img[top + j] + left;
+ uint8_t *writer = xdata + bytes_per_line * j;
+
+ for (i = 0; i < width; i++) {
+ unsigned long pixel;
+ if (line[i] < nct)
+ pixel = pixels[line[i]];
+ else
+ pixel = pixels[0];
+
+ impixel |= SAFELS(pixel & bits_per_pixel_mask, imshift);
+ while (imshift + bits_per_pixel >= BYTESIZE) {
+ *writer++ = impixel;
+ imshift -= BYTESIZE;
+ impixel = SAFELS(pixel, imshift);
+ }
+ imshift += bits_per_pixel;
+ }
+
+ if (imshift)
+ *writer++ = impixel;
+ }
+ }
+
+ /* Restore saved transparent pixel value */
+ if (gfi->transparent >= 0 && gfi->transparent < 256)
+ pixels[ gfi->transparent ] = saved_transparent;
+
+ /* Put it onto the pixmap */
+ XPutImage(gfx->display, pixmap, gfx->image_gc, ximage, 0, 0,
+ pixmap_x, pixmap_y, width, height);
+
+ Gif_DeleteArray(xdata);
+ ximage->data = 0; /* avoid freeing it again in XDestroyImage */
+ XDestroyImage(ximage);
+
+ if (release_uncompressed)
+ Gif_ReleaseUncompressedImage(gfi);
+
+ return 1;
+}
+
+
+Pixmap
+Gif_XSubImageColormap(Gif_XContext *gfx,
+ Gif_Stream* gfs, Gif_Image *gfi, Gif_Colormap *gfcm,
+ int left, int top, int width, int height)
+{
+ Pixmap pixmap =
+ XCreatePixmap(gfx->display, gfx->drawable,
+ width ? width : 1, height ? height : 1, gfx->depth);
+ if (pixmap) {
+ if (put_sub_image_colormap(gfx, gfs, gfi, gfcm, left, top, width, height,
+ pixmap, 0, 0))
+ return pixmap;
+ else
+ XFreePixmap(gfx->display, pixmap);
+ }
+ return None;
+}
+
+Pixmap
+Gif_XImage(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi)
+{
+ Gif_Colormap *gfcm;
+ if (!gfi && gfs->nimages) gfi = gfs->images[0];
+ if (!gfi) return None;
+ gfcm = gfi->local;
+ if (!gfcm) gfcm = gfs->global;
+ return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm,
+ 0, 0, gfi->width, gfi->height);
+}
+
+Pixmap
+Gif_XImageColormap(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Colormap *gfcm,
+ Gif_Image *gfi)
+{
+ if (!gfi && gfs->nimages) gfi = gfs->images[0];
+ if (!gfi) return None;
+ return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm,
+ 0, 0, gfi->width, gfi->height);
+}
+
+Pixmap
+Gif_XSubImage(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi,
+ int left, int top, int width, int height)
+{
+ Gif_Colormap *gfcm;
+ if (!gfi && gfs->nimages) gfi = gfs->images[0];
+ if (!gfi) return None;
+ gfcm = gfi->local;
+ if (!gfcm) gfcm = gfs->global;
+ return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm,
+ left, top, width, height);
+}
+
+
+Pixmap
+Gif_XSubMask(Gif_XContext* gfx, Gif_Stream* gfs, Gif_Image* gfi,
+ int left, int top, int width, int height)
+{
+ Pixmap pixmap = None;
+ XImage *ximage;
+ uint8_t *xdata;
+
+ int i, j;
+ int transparent;
+ size_t bytes_per_line;
+ int release_uncompressed = 0;
+
+ /* Find the correct image */
+ if (!gfi) return None;
+
+ /* Check subimage dimensions */
+ if (width <= 0 || height <= 0 || left < 0 || top < 0
+ || left+width <= 0 || top+height <= 0
+ || left+width > gfi->width || top+height > gfi->height)
+ return None;
+
+ /* Make sure the image is uncompressed */
+ if (!gfi->img && !gfi->image_data && gfi->compressed) {
+ Gif_UncompressImage(gfs, gfi);
+ release_uncompressed = 1;
+ }
+
+ /* Create the X image */
+ ximage =
+ XCreateImage(gfx->display, gfx->visual, 1,
+ XYBitmap, 0, NULL,
+ width, height,
+ 8, 0);
+
+ ximage->bitmap_bit_order = ximage->byte_order = LSBFirst;
+ bytes_per_line = ximage->bytes_per_line;
+ xdata = Gif_NewArray(uint8_t, bytes_per_line * height);
+ ximage->data = (char *)xdata;
+
+ transparent = gfi->transparent;
+
+ /* The main loop */
+ for (j = 0; j < height; j++) {
+ int imshift = 0;
+ uint32_t impixel = 0;
+ uint8_t *line = gfi->img[top + j] + left;
+ uint8_t *writer = xdata + bytes_per_line * j;
+
+ for (i = 0; i < width; i++) {
+ if (line[i] == transparent)
+ impixel |= 1 << imshift;
+
+ if (++imshift >= BYTESIZE) {
+ *writer++ = impixel;
+ imshift = 0;
+ impixel = 0;
+ }
+ }
+
+ if (imshift)
+ *writer++ = impixel;
+ }
+
+ /* Create the pixmap */
+ pixmap =
+ XCreatePixmap(gfx->display, gfx->drawable,
+ width ? width : 1, height ? height : 1, 1);
+ if (!gfx->mask_gc)
+ gfx->mask_gc = XCreateGC(gfx->display, pixmap, 0, 0);
+
+ if (pixmap && gfx->mask_gc)
+ XPutImage(gfx->display, pixmap, gfx->mask_gc, ximage, 0, 0, 0, 0,
+ width, height);
+
+ Gif_DeleteArray(xdata);
+ ximage->data = 0; /* avoid freeing it again in XDestroyImage */
+ XDestroyImage(ximage);
+
+ if (release_uncompressed)
+ Gif_ReleaseUncompressedImage(gfi);
+
+ return pixmap;
+}
+
+
+Pixmap
+Gif_XMask(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi)
+{
+ if (!gfi && gfs->nimages) gfi = gfs->images[0];
+ if (!gfi) return None;
+ return Gif_XSubMask(gfx, gfs, gfi, 0, 0, gfi->width, gfi->height);
+}
+
+static Pixmap
+screen_pixmap(Gif_XContext *gfx, Gif_Stream *gfs)
+{
+ return XCreatePixmap(gfx->display, gfx->drawable,
+ gfs->screen_width, gfs->screen_height, gfx->depth);
+}
+
+static int
+apply_background(Gif_XContext *gfx, Gif_Stream *gfs, int i, Pixmap pixmap)
+{
+ Gif_Image *gfi = gfs->images[i >= 0 ? i : 0];
+ Gif_Colormap *gfcm = (gfi->local ? gfi->local : gfs->global);
+ unsigned long bg_pixel;
+
+ /* find bg_pixel */
+ if (gfs->global && gfs->background < gfs->global->ncol
+ && gfs->images[0]->transparent < 0) {
+ Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1);
+ if (!gfxc)
+ return -1;
+ allocate_colors(gfxc);
+ bg_pixel = gfxc->pixels[gfs->background];
+ } else
+ bg_pixel = gfx->transparent_pixel;
+
+ /* install it as the foreground color on gfx->image_gc */
+ if (!gfx->image_gc)
+ gfx->image_gc = XCreateGC(gfx->display, pixmap, 0, 0);
+ if (!gfx->image_gc)
+ return -1;
+ XSetForeground(gfx->display, gfx->image_gc, bg_pixel);
+ gfx->transparent_pixel = bg_pixel;
+
+ /* clear the image portion */
+ if (i < 0)
+ XFillRectangle(gfx->display, pixmap, gfx->image_gc,
+ 0, 0, gfs->screen_width, gfs->screen_height);
+ else /*if (gfi->transparent < 0)*/
+ XFillRectangle(gfx->display, pixmap, gfx->image_gc,
+ gfi->left, gfi->top, gfi->width, gfi->height);
+ /*else {
+ Pixmap mask = Gif_XMask(gfx, gfs, gfi);
+ if (mask == None)
+ return -1;
+ XSetClipMask(gfx->display, gfx->image_gc, mask);
+ XSetClipOrigin(gfx->display, gfx->image_gc, gfi->left, gfi->top);
+ XFillRectangle(gfx->display, pixmap, gfx->image_gc,
+ gfi->left, gfi->top, gfi->width, gfi->height);
+ XSetClipMask(gfx->display, gfx->image_gc, None);
+ XFreePixmap(gfx->display, mask);
+ }*/
+
+ return 0;
+}
+
+static int
+apply_image(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi, Pixmap pixmap)
+{
+ Pixmap image = Gif_XImage(gfx, gfs, gfi), mask;
+ if (image == None)
+ return -1;
+
+ if (gfi->transparent >= 0) {
+ mask = Gif_XMask(gfx, gfs, gfi);
+ if (mask == None) {
+ XFreePixmap(gfx->display, image);
+ return -1;
+ }
+
+ XSetClipMask(gfx->display, gfx->image_gc, mask);
+ XSetClipOrigin(gfx->display, gfx->image_gc, gfi->left, gfi->top);
+ XCopyArea(gfx->display, image, pixmap, gfx->image_gc,
+ 0, 0, gfi->width, gfi->height, gfi->left, gfi->top);
+ XSetClipMask(gfx->display, gfx->image_gc, None);
+ XFreePixmap(gfx->display, mask);
+ } else {
+ XCopyArea(gfx->display, image, pixmap, gfx->image_gc,
+ 0, 0, gfi->width, gfi->height, gfi->left, gfi->top);
+ }
+
+ XFreePixmap(gfx->display, image);
+ return 0;
+}
+
+static int
+fullscreen(Gif_Stream *gfs, Gif_Image *gfi, int require_opaque)
+{
+ return (gfi->left == 0 && gfi->top == 0 && gfi->width == gfs->screen_width
+ && gfi->height == gfs->screen_height
+ && (!require_opaque || gfi->transparent < 0));
+}
+
+Gif_XFrame *
+Gif_NewXFrames(Gif_Stream *gfs)
+{
+ int i, last_postdisposal = -1;
+ Gif_XFrame *fs = Gif_NewArray(Gif_XFrame, gfs->nimages);
+ if (!fs)
+ return 0;
+ for (i = 0; i < gfs->nimages; ++i) {
+ Gif_Image *gfi = gfs->images[i];
+ fs[i].pixmap = None;
+ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS)
+ fs[i].postdisposal = last_postdisposal;
+ else
+ fs[i].postdisposal = i;
+ last_postdisposal = fs[i].postdisposal;
+ }
+ return fs;
+}
+
+void
+Gif_DeleteXFrames(Gif_XContext *gfx, Gif_Stream *gfs, Gif_XFrame *fs)
+{
+ int i;
+ for (i = 0; i < gfs->nimages; ++i)
+ if (fs[i].pixmap)
+ XFreePixmap(gfx->display, fs[i].pixmap);
+ Gif_DeleteArray(fs);
+}
+
+Pixmap
+Gif_XNextImage(Gif_XContext *gfx, Gif_Stream *gfs, int i, Gif_XFrame *frames)
+{
+ Pixmap result = None;
+ unsigned long old_transparent = gfx->transparent_pixel;
+ Gif_Image *gfi;
+ int previ, scani;
+
+ /* return already rendered pixmap if any */
+ if (frames[i].pixmap != None)
+ return frames[i].pixmap;
+
+ /* render fullscreen image */
+ gfi = gfs->images[i];
+ if (fullscreen(gfs, gfi, 1)) {
+ frames[i].pixmap = Gif_XImage(gfx, gfs, gfi);
+ return frames[i].pixmap;
+ }
+
+ /* image is not full screen, need to find background */
+ previ = i - 1;
+ if (previ >= 0)
+ previ = frames[previ].postdisposal;
+
+ /* scan backwards for a renderable image */
+ scani = previ;
+ while (scani >= 0
+ && frames[scani].pixmap == None
+ && !fullscreen(gfs, gfs->images[scani], 1))
+ --scani;
+
+ /* create the pixmap */
+ result = screen_pixmap(gfx, gfs);
+ if (result == None)
+ return None;
+
+ /* scan forward to produce background */
+ gfi = (scani >= 0 ? gfs->images[scani] : 0);
+ if (gfi && (gfi->disposal != GIF_DISPOSAL_BACKGROUND
+ || !fullscreen(gfs, gfi, 1))) {
+ /* perhaps we need to create an image (if so, must be fullscreen) */
+ if (frames[scani].pixmap == None) {
+ frames[scani].pixmap = Gif_XImage(gfx, gfs, gfi);
+ if (frames[scani].pixmap == None)
+ goto error_exit;
+ }
+ XCopyArea(gfx->display, frames[scani].pixmap, result, gfx->image_gc,
+ 0, 0, gfs->screen_width, gfs->screen_height, 0, 0);
+ }
+ if (!gfi || gfi->disposal == GIF_DISPOSAL_BACKGROUND) {
+ if (apply_background(gfx, gfs, scani, result) < 0)
+ goto error_exit;
+ }
+
+ while (scani < previ) {
+ ++scani;
+ gfi = gfs->images[scani];
+ if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) {
+ if (apply_background(gfx, gfs, scani, result) < 0)
+ goto error_exit;
+ } else if (gfi->disposal != GIF_DISPOSAL_PREVIOUS) {
+ if (apply_image(gfx, gfs, gfs->images[scani], result) < 0)
+ goto error_exit;
+ }
+ }
+
+ /* apply image */
+ if (gfs->screen_width != 0 && gfs->screen_height != 0) {
+ if (apply_image(gfx, gfs, gfs->images[i], result) < 0)
+ goto error_exit;
+ }
+
+ frames[i].pixmap = result;
+ return frames[i].pixmap;
+
+ error_exit:
+ XFreePixmap(gfx->display, result);
+ gfx->transparent_pixel = old_transparent;
+ return None;
+}
+
+
+/** CREATING AND DESTROYING XCONTEXTS **/
+
+static void
+delete_xcolormap(Gif_XColormap *gfxc)
+{
+ Gif_XContext *gfx = gfxc->x_context;
+ Gif_XColormap *prev = 0, *trav = gfx->xcolormap;
+ while (trav != gfxc && trav) {
+ prev = trav;
+ trav = trav->next;
+ }
+ if (gfx->free_deleted_colormap_pixels)
+ deallocate_colors(gfxc);
+ if (prev)
+ prev->next = gfxc->next;
+ else
+ gfx->xcolormap = gfxc->next;
+ Gif_DeleteArray(gfxc->pixels);
+ Gif_Delete(gfxc);
+}
+
+static void
+delete_colormap_hook(int dummy, void *colormap_x, void *callback_x)
+{
+ Gif_Colormap *gfcm = (Gif_Colormap *)colormap_x;
+ Gif_XContext *gfx = (Gif_XContext *)callback_x;
+ Gif_XColormap *gfxc;
+ (void) dummy;
+ for (gfxc = gfx->xcolormap; gfxc; gfxc = gfxc->next)
+ if (gfxc->colormap == gfcm) {
+ delete_xcolormap(gfxc);
+ return;
+ }
+}
+
+
+Gif_XContext *
+Gif_NewXContextFromVisual(Display *display, int screen_number,
+ Visual *visual, int depth, Colormap colormap)
+{
+ Gif_XContext *gfx;
+
+ gfx = Gif_New(Gif_XContext);
+ gfx->display = display;
+ gfx->screen_number = screen_number;
+ gfx->drawable = RootWindow(display, screen_number);
+
+ gfx->visual = visual;
+ gfx->colormap = colormap;
+ gfx->ncolormap = visual->map_entries;
+ gfx->depth = depth;
+
+ gfx->closest = 0;
+ gfx->nclosest = 0;
+
+ gfx->free_deleted_colormap_pixels = 0;
+ gfx->xcolormap = 0;
+
+ gfx->image_gc = None;
+ gfx->mask_gc = None;
+
+ gfx->transparent_pixel = 0UL;
+ gfx->foreground_pixel = 1UL;
+ gfx->refcount = 0;
+
+ Gif_AddDeletionHook(GIF_T_COLORMAP, delete_colormap_hook, gfx);
+ return gfx;
+}
+
+
+Gif_XContext *
+Gif_NewXContext(Display *display, Window window)
+{
+ XWindowAttributes attr;
+ XGetWindowAttributes(display, window, &attr);
+ return Gif_NewXContextFromVisual(display, XScreenNumberOfScreen(attr.screen),
+ attr.visual, attr.depth, attr.colormap);
+}
+
+
+void
+Gif_DeleteXContext(Gif_XContext *gfx)
+{
+ if (!gfx) return;
+ if (--gfx->refcount > 0) return;
+ while (gfx->xcolormap)
+ delete_xcolormap(gfx->xcolormap);
+ if (gfx->image_gc)
+ XFreeGC(gfx->display, gfx->image_gc);
+ if (gfx->mask_gc)
+ XFreeGC(gfx->display, gfx->mask_gc);
+ Gif_DeleteArray(gfx->closest);
+ Gif_RemoveDeletionHook(GIF_T_COLORMAP, delete_colormap_hook, gfx);
+ Gif_Delete(gfx);
+}
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/kcolor.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/kcolor.c
new file mode 100644
index 0000000..c373f35
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/kcolor.c
@@ -0,0 +1,708 @@
+/* kcolor.c - Kcolor subsystem for gifsicle.
+ Copyright (C) 1997-2025 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
+
+/* Invariant: (0<=x<256) ==> (srgb_revgamma[srgb_gamma[x] >> 7] <= x). */
+
+static const uint16_t srgb_gamma_table_256[256] = {
+ 0, 10, 20, 30, 40, 50, 60, 70,
+ 80, 90, 99, 110, 120, 132, 144, 157,
+ 170, 184, 198, 213, 229, 246, 263, 281,
+ 299, 319, 338, 359, 380, 403, 425, 449,
+ 473, 498, 524, 551, 578, 606, 635, 665,
+ 695, 727, 759, 792, 825, 860, 895, 931,
+ 968, 1006, 1045, 1085, 1125, 1167, 1209, 1252,
+ 1296, 1341, 1386, 1433, 1481, 1529, 1578, 1629,
+ 1680, 1732, 1785, 1839, 1894, 1950, 2007, 2065,
+ 2123, 2183, 2244, 2305, 2368, 2432, 2496, 2562,
+ 2629, 2696, 2765, 2834, 2905, 2977, 3049, 3123,
+ 3198, 3273, 3350, 3428, 3507, 3587, 3668, 3750,
+ 3833, 3917, 4002, 4088, 4176, 4264, 4354, 4444,
+ 4536, 4629, 4723, 4818, 4914, 5011, 5109, 5209,
+ 5309, 5411, 5514, 5618, 5723, 5829, 5936, 6045,
+ 6154, 6265, 6377, 6490, 6604, 6720, 6836, 6954,
+ 7073, 7193, 7315, 7437, 7561, 7686, 7812, 7939,
+ 8067, 8197, 8328, 8460, 8593, 8728, 8863, 9000,
+ 9139, 9278, 9419, 9560, 9704, 9848, 9994, 10140,
+ 10288, 10438, 10588, 10740, 10893, 11048, 11204, 11360,
+ 11519, 11678, 11839, 12001, 12164, 12329, 12495, 12662,
+ 12831, 13000, 13172, 13344, 13518, 13693, 13869, 14047,
+ 14226, 14406, 14588, 14771, 14955, 15141, 15328, 15516,
+ 15706, 15897, 16089, 16283, 16478, 16675, 16872, 17071,
+ 17272, 17474, 17677, 17882, 18088, 18295, 18504, 18714,
+ 18926, 19138, 19353, 19569, 19786, 20004, 20224, 20445,
+ 20668, 20892, 21118, 21345, 21573, 21803, 22034, 22267,
+ 22501, 22736, 22973, 23211, 23451, 23692, 23935, 24179,
+ 24425, 24672, 24920, 25170, 25421, 25674, 25928, 26184,
+ 26441, 26700, 26960, 27222, 27485, 27749, 28016, 28283,
+ 28552, 28823, 29095, 29368, 29643, 29920, 30197, 30477,
+ 30758, 31040, 31324, 31610, 31897, 32185, 32475, 32767
+};
+
+static const uint16_t srgb_revgamma_table_256[256] = {
+ 0, 1628, 2776, 3619, 4309, 4904, 5434, 5914,
+ 6355, 6765, 7150, 7513, 7856, 8184, 8497, 8798,
+ 9086, 9365, 9634, 9895, 10147, 10393, 10631, 10864,
+ 11091, 11312, 11528, 11739, 11946, 12148, 12347, 12541,
+ 12732, 12920, 13104, 13285, 13463, 13639, 13811, 13981,
+ 14149, 14314, 14476, 14637, 14795, 14951, 15105, 15257,
+ 15408, 15556, 15703, 15848, 15991, 16133, 16273, 16412,
+ 16549, 16685, 16819, 16953, 17084, 17215, 17344, 17472,
+ 17599, 17725, 17849, 17973, 18095, 18217, 18337, 18457,
+ 18575, 18692, 18809, 18925, 19039, 19153, 19266, 19378,
+ 19489, 19600, 19710, 19819, 19927, 20034, 20141, 20247,
+ 20352, 20457, 20560, 20664, 20766, 20868, 20969, 21070,
+ 21170, 21269, 21368, 21466, 21564, 21661, 21758, 21854,
+ 21949, 22044, 22138, 22232, 22326, 22418, 22511, 22603,
+ 22694, 22785, 22875, 22965, 23055, 23144, 23232, 23321,
+ 23408, 23496, 23583, 23669, 23755, 23841, 23926, 24011,
+ 24095, 24180, 24263, 24347, 24430, 24512, 24595, 24676,
+ 24758, 24839, 24920, 25001, 25081, 25161, 25240, 25319,
+ 25398, 25477, 25555, 25633, 25710, 25788, 25865, 25941,
+ 26018, 26094, 26170, 26245, 26321, 26396, 26470, 26545,
+ 26619, 26693, 26766, 26840, 26913, 26986, 27058, 27130,
+ 27202, 27274, 27346, 27417, 27488, 27559, 27630, 27700,
+ 27770, 27840, 27910, 27979, 28048, 28117, 28186, 28255,
+ 28323, 28391, 28459, 28527, 28594, 28661, 28728, 28795,
+ 28862, 28928, 28995, 29061, 29127, 29192, 29258, 29323,
+ 29388, 29453, 29518, 29582, 29646, 29711, 29775, 29838,
+ 29902, 29965, 30029, 30092, 30155, 30217, 30280, 30342,
+ 30404, 30466, 30528, 30590, 30652, 30713, 30774, 30835,
+ 30896, 30957, 31017, 31078, 31138, 31198, 31258, 31318,
+ 31378, 31437, 31497, 31556, 31615, 31674, 31733, 31791,
+ 31850, 31908, 31966, 32024, 32082, 32140, 32198, 32255,
+ 32313, 32370, 32427, 32484, 32541, 32598, 32654, 32711
+};
+
+static const float linear_srgb_table_256[256] = {
+ 0.00000, 0.04984009, 0.08494473, 0.11070206, 0.13180381, 0.1500052, 0.1661857, 0.18085852,
+ 0.19435316, 0.20689574, 0.21864912, 0.22973509, 0.2402475, 0.25026038, 0.25983337, 0.26901522,
+ 0.27784654, 0.28636143, 0.29458886, 0.3025538, 0.31027776, 0.31777957, 0.32507575, 0.33218095,
+ 0.33910814, 0.34586892, 0.35247374, 0.35893196, 0.3652521, 0.3714419, 0.37750843, 0.38345808,
+ 0.38929683, 0.39503005, 0.40066284, 0.40619975, 0.41164514, 0.417003, 0.42227703, 0.42747074,
+ 0.4325873, 0.4376298, 0.44260103, 0.4475037, 0.45234028, 0.45711315, 0.46182457, 0.4664766,
+ 0.47107124, 0.4756104, 0.4800958, 0.4845292, 0.48891217, 0.49324623, 0.49753287, 0.5017734,
+ 0.5059693, 0.5101216, 0.5142317, 0.5183006, 0.5223295, 0.5263194, 0.53027135, 0.53418624,
+ 0.53806514, 0.54190874, 0.5457181, 0.54949385, 0.5532369, 0.556948, 0.5606278, 0.5642771,
+ 0.56789654, 0.5714868, 0.57504845, 0.5785821, 0.5820884, 0.58556795, 0.58902115, 0.59244865,
+ 0.59585094, 0.5992285, 0.60258186, 0.60591143, 0.60921764, 0.612501, 0.61576194, 0.6190008,
+ 0.622218, 0.62541395, 0.62858903, 0.6317436, 0.63487804, 0.6379926, 0.6410878, 0.6441637,
+ 0.64722085, 0.6502595, 0.6532799, 0.65628237, 0.65926725, 0.6622347, 0.6651851, 0.66811866,
+ 0.67103565, 0.6739363, 0.67682093, 0.6796897, 0.6825429, 0.6853807, 0.6882034, 0.69101113,
+ 0.69380414, 0.6965826, 0.69934684, 0.70209694, 0.7048331, 0.7075556, 0.7102645, 0.71296,
+ 0.7156424, 0.7183118, 0.7209683, 0.7236121, 0.7262435, 0.7288625, 0.73146933, 0.73406404,
+ 0.73664695, 0.73921806, 0.7417776, 0.74432564, 0.7468624, 0.749388, 0.75190246, 0.7544061,
+ 0.7568989, 0.759381, 0.76185256, 0.7643137, 0.7667645, 0.7692052, 0.7716358, 0.7740564,
+ 0.77646714, 0.77886814, 0.78125954, 0.78364134, 0.7860138, 0.7883768, 0.79073066, 0.7930754,
+ 0.795411, 0.7977377, 0.80005556, 0.8023647, 0.8046651, 0.80695695, 0.8092403, 0.8115152,
+ 0.8137818, 0.81604016, 0.8182903, 0.8205324, 0.8227665, 0.8249926, 0.8272109, 0.8294214,
+ 0.8316242, 0.8338194, 0.836007, 0.8381871, 0.84035975, 0.84252506, 0.8446831, 0.84683394,
+ 0.84897757, 0.85111415, 0.8532437, 0.85536623, 0.8574819, 0.8595907, 0.8616927, 0.86378807,
+ 0.8658767, 0.8679587, 0.87003416, 0.87210315, 0.87416565, 0.8762218, 0.8782716, 0.8803151,
+ 0.8823524, 0.8843835, 0.8864085, 0.8884274, 0.8904402, 0.8924471, 0.89444804, 0.8964431,
+ 0.8984324, 0.9004158, 0.90239346, 0.9043654, 0.9063318, 0.9082925, 0.91024756, 0.9121972,
+ 0.9141413, 0.91608, 0.9180133, 0.9199412, 0.92186373, 0.92378104, 0.9256931, 0.92759997,
+ 0.92950165, 0.9313982, 0.93328965, 0.9351761, 0.9370575, 0.9389339, 0.9408054, 0.9426719,
+ 0.9445336, 0.94639045, 0.9482424, 0.9500897, 0.9519322, 0.95377004, 0.9556032, 0.9574316,
+ 0.9592555, 0.9610748, 0.96288955, 0.9646998, 0.9665055, 0.9683068, 0.9701037, 0.9718961,
+ 0.9736842, 0.9754679, 0.97724736, 0.9790225, 0.9807934, 0.9825601, 0.98432255, 0.9860808,
+ 0.987835, 0.989585, 0.9913309, 0.99307275, 0.9948106, 0.99654436, 0.99827415, 1.00000
+};
+
+const uint16_t* gamma_tables[2] = {
+ srgb_gamma_table_256,
+ srgb_revgamma_table_256
+};
+
+#if ENABLE_THREADS
+pthread_mutex_t kd3_sort_lock;
+#endif
+
+
+const char* kc_debug_str(kcolor x) {
+ static int whichbuf = 0;
+ static char buf[8][64];
+ whichbuf = (whichbuf + 1) % 8;
+ if (x.a[0] >= 0 && x.a[1] >= 0 && x.a[2] >= 0) {
+ x = kc_revgamma_transform(x);
+ snprintf(buf[whichbuf], sizeof(buf[whichbuf]), "#%02X%02X%02X",
+ x.a[0] >> 7, x.a[1] >> 7, x.a[2] >> 7);
+ } else
+ snprintf(buf[whichbuf], sizeof(buf[whichbuf]), "<%d,%d,%d>",
+ x.a[0], x.a[1], x.a[2]);
+ return buf[whichbuf];
+}
+
+void kc_set_gamma(int type, double gamma) {
+ static int cur_type = KC_GAMMA_SRGB;
+ static double cur_gamma = 2.2;
+ int i, j;
+ uint16_t* g[2];
+#if !HAVE_POW
+ if (type == KC_GAMMA_NUMERIC && gamma != 1.0) {
+ type = KC_GAMMA_SRGB;
+ }
+#endif
+ if (type == cur_type && (type != KC_GAMMA_NUMERIC || gamma == cur_gamma)) {
+ return;
+ }
+ if (type != KC_GAMMA_NUMERIC) {
+ if (gamma_tables[0] != srgb_gamma_table_256) {
+ Gif_DeleteArray(gamma_tables[0]);
+ }
+ gamma_tables[0] = srgb_gamma_table_256;
+ gamma_tables[1] = srgb_revgamma_table_256;
+ } else {
+ if (gamma_tables[0] == srgb_gamma_table_256) {
+ gamma_tables[0] = Gif_NewArray(uint16_t, 512);
+ gamma_tables[1] = gamma_tables[0] + 256;
+ }
+ g[0] = (uint16_t*) gamma_tables[0];
+ g[1] = (uint16_t*) gamma_tables[1];
+ for (j = 0; j != 256; ++j) {
+#if HAVE_POW
+ g[0][j] = (int) (pow(j / 255.0, gamma) * 32767 + 0.5);
+ g[1][j] = (int) (pow(j / 256.0, 1/gamma) * 32767 + 0.5);
+#else
+ g[0][j] = (int) (j / 255.0 * 32767 + 0.5);
+ g[1][j] = (int) (j / 256.0 * 32767 + 0.5);
+#endif
+ /* The ++gamma_tables[][] ensures that round-trip gamma correction
+ always preserve the input colors. Without it, one might have,
+ for example, input values 0, 1, and 2 all mapping to
+ gamma-corrected value 0. Then a round-trip through gamma
+ correction loses information.
+ */
+ for (i = j ? 0 : 2; i != 2; ++i) {
+ while (g[i][j] <= g[i][j-1] && g[i][j] < 32767)
+ ++g[i][j];
+ }
+ }
+ }
+ cur_type = type;
+ cur_gamma = gamma;
+}
+
+kcolor kc_revgamma_transform(kcolor x) {
+ int d;
+ for (d = 0; d != 3; ++d) {
+ int c = gamma_tables[1][x.a[d] >> 7];
+ while (c < 0x7F80 && x.a[d] >= gamma_tables[0][(c + 0x80) >> 7])
+ c += 0x80;
+ x.a[d] = c;
+ }
+ return x;
+}
+
+#if 0
+static void kc_test_gamma() {
+ int x, y, z;
+ for (x = 0; x != 256; ++x)
+ for (y = 0; y != 256; ++y)
+ for (z = 0; z != 256; ++z) {
+ kcolor k = kc_make8g(x, y, z);
+ kc_revgamma_transform(&k);
+ if ((k.a[0] >> 7) != x || (k.a[1] >> 7) != y
+ || (k.a[2] >> 7) != z) {
+ kcolor kg = kc_make8g(x, y, z);
+ fprintf(stderr, "#%02X%02X%02X ->g #%04X%04X%04X ->revg #%02X%02X%02X!\n",
+ x, y, z, kg.a[0], kg.a[1], kg.a[2],
+ k.a[0] >> 7, k.a[1] >> 7, k.a[2] >> 7);
+ assert(0);
+ }
+ }
+}
+#endif
+
+kcolor kc_oklab_transform(int a0, int a1, int a2) {
+#if HAVE_CBRTF
+ float cr = linear_srgb_table_256[a0];
+ float cg = linear_srgb_table_256[a1];
+ float cb = linear_srgb_table_256[a2];
+
+ float l = 0.4122214708f * cr + 0.5363325363f * cg + 0.0514459929f * cb;
+ float m = 0.2119034982f * cr + 0.6806995451f * cg + 0.1073969566f * cb;
+ float s = 0.0883024619f * cr + 0.2817188376f * cg + 0.6299787005f * cb;
+
+ float l_ = cbrtf(l);
+ float m_ = cbrtf(m);
+ float s_ = cbrtf(s);
+
+ float okl = 0.2104542553f*l_ + 0.7936177850f*m_ - 0.0040720468f*s_;
+ float oka = 1.9779984951f*l_ - 2.4285922050f*m_ + 0.4505937099f*s_;
+ float okb = 0.0259040371f*l_ + 0.7827717662f*m_ - 0.8086757660f*s_;
+
+ // On sRGB colors, `okl` ranges from 0 to 1,
+ // `oka` ranges from -0.23388740 to +0.27621666,
+ // `okb` ranges from -0.31152815 to +0.19856972.
+
+ kcolor kc;
+ kc.a[0] = okl * 32767;
+ kc.a[1] = (oka + 0.5) * 32767;
+ kc.a[2] = (okb + 0.5) * 32767;
+ return kc;
+#else
+ /* fall back to sRGB gamma */
+ return kc_make8g(a0, a1, a2);
+#endif
+}
+
+
+static int kchist_sizes[] = {
+ 4093, 16381, 65521, 262139, 1048571, 4194301, 16777213,
+ 67108859, 268435459, 1073741839
+};
+
+static void kchist_grow(kchist* kch);
+
+void kchist_init(kchist* kch) {
+ int i;
+ kch->h = Gif_NewArray(kchistitem, kchist_sizes[0]);
+ kch->n = 0;
+ kch->capacity = kchist_sizes[0];
+ for (i = 0; i != kch->capacity; ++i)
+ kch->h[i].count = 0;
+}
+
+void kchist_cleanup(kchist* kch) {
+ Gif_DeleteArray(kch->h);
+ kch->h = NULL;
+}
+
+kchistitem* kchist_add(kchist* kch, kcolor k, kchist_count_t count) {
+ unsigned hash1, hash2 = 0;
+ kacolor ka;
+ kchistitem *khi;
+ ka.k = k;
+ ka.a[3] = 0;
+
+ if (!kch->capacity
+ || kch->n > ((kch->capacity * 3) >> 4))
+ kchist_grow(kch);
+ hash1 = (((ka.a[0] & 0x7FE0) << 15)
+ | ((ka.a[1] & 0x7FE0) << 5)
+ | ((ka.a[2] & 0x7FE0) >> 5)) % kch->capacity;
+
+ while (1) {
+ khi = &kch->h[hash1];
+ if (!khi->count
+ || memcmp(&khi->ka, &ka, sizeof(ka)) == 0)
+ break;
+ if (!hash2) {
+ hash2 = (((ka.a[0] & 0x03FF) << 20)
+ | ((ka.a[1] & 0x03FF) << 10)
+ | (ka.a[2] & 0x03FF)) % kch->capacity;
+ hash2 = hash2 ? hash2 : 1;
+ }
+ hash1 += hash2;
+ if (hash1 >= (unsigned) kch->capacity)
+ hash1 -= kch->capacity;
+ }
+
+ if (!khi->count) {
+ khi->ka = ka;
+ ++kch->n;
+ }
+ khi->count += count;
+ if (khi->count < count)
+ khi->count = (kchist_count_t) -1;
+ return khi;
+}
+
+static void kchist_grow(kchist* kch) {
+ kchistitem* oldh = kch->h;
+ int i, oldcapacity = kch->capacity ? kch->capacity : kch->n;
+ for (i = 0; kchist_sizes[i] <= oldcapacity; ++i)
+ /* do nothing */;
+ kch->capacity = kchist_sizes[i];
+ kch->h = Gif_NewArray(kchistitem, kch->capacity);
+ kch->n = 0;
+ for (i = 0; i != kch->capacity; ++i)
+ kch->h[i].count = 0;
+ for (i = 0; i != oldcapacity; ++i)
+ if (oldh[i].count)
+ kchist_add(kch, oldh[i].ka.k, oldh[i].count);
+ Gif_DeleteArray(oldh);
+}
+
+void kchist_compress(kchist* kch) {
+ int i, j;
+ for (i = 0, j = kch->n; i != kch->n; )
+ if (kch->h[i].count)
+ ++i;
+ else if (kch->h[j].count) {
+ kch->h[i] = kch->h[j];
+ ++i, ++j;
+ } else
+ ++j;
+ kch->capacity = 0;
+}
+
+void kchist_make(kchist* kch, Gif_Stream* gfs, uint32_t* ntransp_store) {
+ uint32_t gcount[256], lcount[256];
+ uint32_t nbackground = 0, ntransparent = 0;
+ int x, y, i, imagei;
+ kchist_init(kch);
+
+ for (i = 0; i != 256; ++i)
+ gcount[i] = 0;
+
+ /* Count pixels */
+ for (imagei = 0; imagei < gfs->nimages; ++imagei) {
+ Gif_Image* gfi = gfs->images[imagei];
+ Gif_Colormap* gfcm = gfi->local ? gfi->local : gfs->global;
+ uint32_t* count = gfi->local ? lcount : gcount;
+ uint32_t old_transparent_count = 0;
+ int only_compressed = (gfi->img == 0);
+ if (!gfcm)
+ continue;
+ if (count == lcount)
+ for (i = 0; i != 256; ++i)
+ count[i] = 0;
+ if (gfi->transparent >= 0)
+ old_transparent_count = count[gfi->transparent];
+
+ /* unoptimize the image if necessary */
+ if (only_compressed)
+ Gif_UncompressImage(gfs, gfi);
+
+ /* sweep over the image data, counting pixels */
+ for (y = 0; y < gfi->height; ++y) {
+ const uint8_t* data = gfi->img[y];
+ for (x = 0; x < gfi->width; ++x, ++data)
+ ++count[*data];
+ }
+
+ /* add counted colors to global histogram (local only) */
+ if (gfi->local)
+ for (i = 0; i != gfcm->ncol; ++i)
+ if (count[i] && i != gfi->transparent)
+ kchist_add(kch, kc_makegfcg(&gfcm->col[i]), count[i]);
+ if (gfi->transparent >= 0
+ && count[gfi->transparent] != old_transparent_count) {
+ ntransparent += count[gfi->transparent] - old_transparent_count;
+ count[gfi->transparent] = old_transparent_count;
+ }
+
+ /* if this image has background disposal, count its size towards the
+ background's pixel count */
+ if (gfi->disposal == GIF_DISPOSAL_BACKGROUND)
+ nbackground += (unsigned) gfi->width * (unsigned) gfi->height;
+
+ /* throw out compressed image if necessary */
+ if (only_compressed)
+ Gif_ReleaseUncompressedImage(gfi);
+ }
+
+ if (gfs->images[0]->transparent < 0
+ && gfs->global && gfs->background < gfs->global->ncol)
+ gcount[gfs->background] += nbackground;
+ else
+ ntransparent += nbackground;
+
+ if (gfs->global)
+ for (i = 0; i != gfs->global->ncol; ++i)
+ if (gcount[i])
+ kchist_add(kch, kc_makegfcg(&gfs->global->col[i]), gcount[i]);
+
+ /* now, make the linear histogram from the hashed histogram */
+ kchist_compress(kch);
+ *ntransp_store = ntransparent;
+}
+
+
+/*****
+ * kd_tree allocation and deallocation
+ **/
+
+struct kd3_treepos {
+ int pivot;
+ int offset;
+};
+
+void kd3_init(kd3_tree* kd3, kcolor (*transform)(int, int, int)) {
+ kd3->tree = NULL;
+ kd3->ks = Gif_NewArray(kcolor, 256);
+ kd3->nitems = 0;
+ kd3->items_cap = 256;
+ kd3->transform = transform ? transform : kc_make8g;
+ kd3->xradius = NULL;
+ kd3->disabled = -1;
+}
+
+void kd3_cleanup(kd3_tree* kd3) {
+ Gif_DeleteArray(kd3->tree);
+ Gif_DeleteArray(kd3->ks);
+ Gif_DeleteArray(kd3->xradius);
+}
+
+void kd3_add_transformed(kd3_tree* kd3, kcolor k) {
+ if (kd3->nitems == kd3->items_cap) {
+ kd3->items_cap *= 2;
+ Gif_ReArray(kd3->ks, kcolor, kd3->items_cap);
+ }
+ kd3->ks[kd3->nitems] = k;
+ ++kd3->nitems;
+ if (kd3->tree) {
+ Gif_DeleteArray(kd3->tree);
+ Gif_DeleteArray(kd3->xradius);
+ kd3->tree = NULL;
+ kd3->xradius = NULL;
+ }
+}
+
+static kd3_tree* kd3_sorter;
+
+static int kd3_item_compare_0(const void* a, const void* b) {
+ const int* aa = (const int*) a, *bb = (const int*) b;
+ return kd3_sorter->ks[*aa].a[0] - kd3_sorter->ks[*bb].a[0];
+}
+
+static int kd3_item_compare_1(const void* a, const void* b) {
+ const int* aa = (const int*) a, *bb = (const int*) b;
+ return kd3_sorter->ks[*aa].a[1] - kd3_sorter->ks[*bb].a[1];
+}
+
+static int kd3_item_compare_2(const void* a, const void* b) {
+ const int* aa = (const int*) a, *bb = (const int*) b;
+ return kd3_sorter->ks[*aa].a[2] - kd3_sorter->ks[*bb].a[2];
+}
+
+static int (*kd3_item_compares[])(const void*, const void*) = {
+ &kd3_item_compare_0, &kd3_item_compare_1, &kd3_item_compare_2
+};
+
+static int kd3_item_compare_all(const void* a, const void* b) {
+ const int* aa = (const int*) a, *bb = (const int*) b;
+ return memcmp(&kd3_sorter->ks[*aa], &kd3_sorter->ks[*bb], sizeof(kcolor));
+}
+
+static int kd3_build_range(int* perm, int nperm, int n, int depth) {
+ kd3_tree* kd3 = kd3_sorter;
+ int m, nl, nr, aindex = depth % 3;
+ if (depth > kd3->maxdepth)
+ kd3->maxdepth = depth;
+ while (n >= kd3->ntree) {
+ kd3->ntree *= 2;
+ Gif_ReArray(kd3->tree, kd3_treepos, kd3->ntree);
+ }
+ if (nperm <= 1) {
+ kd3->tree[n].pivot = (nperm == 0 ? -1 : perm[0]);
+ kd3->tree[n].offset = -1;
+ return 2;
+ }
+
+ qsort(perm, nperm, sizeof(int), kd3_item_compares[aindex]);
+
+ /* pick pivot: a color component to split */
+ m = nperm >> 1;
+ while (m > 0
+ && kd3->ks[perm[m]].a[aindex] == kd3->ks[perm[m-1]].a[aindex])
+ --m;
+ if (m == 0) { /* don't split entirely to the right (infinite loop) */
+ m = nperm >> 1;
+ while (m < nperm - 1 /* also, don't split entirely to the left */
+ && kd3->ks[perm[m]].a[aindex] == kd3->ks[perm[m-1]].a[aindex])
+ ++m;
+ }
+ if (m == 0)
+ kd3->tree[n].pivot = kd3->ks[perm[m]].a[aindex];
+ else
+ kd3->tree[n].pivot = kd3->ks[perm[m-1]].a[aindex]
+ + ((kd3->ks[perm[m]].a[aindex] - kd3->ks[perm[m-1]].a[aindex]) >> 1);
+
+ /* recurse */
+ nl = kd3_build_range(perm, m, n+1, depth+1);
+ kd3->tree[n].offset = 1+nl;
+ nr = kd3_build_range(&perm[m], nperm - m, n+1+nl, depth+1);
+ return 1+nl+nr;
+}
+
+#if 0
+static void kd3_print_depth(kd3_tree* kd3, int depth, kd3_treepos* p,
+ int* a, int* b) {
+ int i;
+ char x[6][10];
+ for (i = 0; i != 3; ++i) {
+ if (a[i] == INT_MIN)
+ snprintf(x[2*i], sizeof(x[2*i]), "*");
+ else
+ snprintf(x[2*i], sizeof(x[2*i]), "%d", a[i]);
+ if (b[i] == INT_MAX)
+ snprintf(x[2*i+1], sizeof(x[2*i+1]), "*");
+ else
+ snprintf(x[2*i+1], sizeof(x[2*i+1]), "%d", b[i]);
+ }
+ printf("%*s<%s:%s,%s:%s,%s:%s>", depth*3, "",
+ x[0], x[1], x[2], x[3], x[4], x[5]);
+ if (p->offset < 0) {
+ if (p->pivot >= 0) {
+ assert(kd3->ks[p->pivot].a[0] >= a[0]);
+ assert(kd3->ks[p->pivot].a[1] >= a[1]);
+ assert(kd3->ks[p->pivot].a[2] >= a[2]);
+ assert(kd3->ks[p->pivot].a[0] < b[0]);
+ assert(kd3->ks[p->pivot].a[1] < b[1]);
+ assert(kd3->ks[p->pivot].a[2] < b[2]);
+ printf(" ** @%d: <%d,%d,%d>\n", p->pivot, kd3->ks[p->pivot].a[0], kd3->ks[p->pivot].a[1], kd3->ks[p->pivot].a[2]);
+ }
+ } else {
+ int aindex = depth % 3, x[3];
+ assert(p->pivot >= a[aindex]);
+ assert(p->pivot < b[aindex]);
+ printf((aindex == 0 ? " | <%d,_,_>\n" :
+ aindex == 1 ? " | <_,%d,_>\n" : " | <_,_,%d>\n"), p->pivot);
+ memcpy(x, b, sizeof(int) * 3);
+ x[aindex] = p->pivot;
+ kd3_print_depth(kd3, depth + 1, p + 1, a, x);
+ memcpy(x, a, sizeof(int) * 3);
+ x[aindex] = p->pivot;
+ kd3_print_depth(kd3, depth + 1, p + p->offset, x, b);
+ }
+}
+
+static void kd3_print(kd3_tree* kd3) {
+ int a[3], b[3];
+ a[0] = a[1] = a[2] = INT_MIN;
+ b[0] = b[1] = b[2] = INT_MAX;
+ kd3_print_depth(kd3, 0, kd3->tree, a, b);
+}
+#endif
+
+void kd3_build_xradius(kd3_tree* kd3) {
+ int i, j;
+ /* create xradius */
+ if (kd3->xradius)
+ return;
+ kd3->xradius = Gif_NewArray(unsigned, kd3->nitems);
+ for (i = 0; i != kd3->nitems; ++i)
+ kd3->xradius[i] = (unsigned) -1;
+ for (i = 0; i != kd3->nitems; ++i)
+ for (j = i + 1; j != kd3->nitems; ++j) {
+ unsigned dist = kc_distance(kd3->ks[i], kd3->ks[j]);
+ unsigned radius = dist / 4;
+ if (radius < kd3->xradius[i])
+ kd3->xradius[i] = radius;
+ if (radius < kd3->xradius[j])
+ kd3->xradius[j] = radius;
+ }
+}
+
+void kd3_build(kd3_tree* kd3) {
+ int i, delta, *perm;
+ assert(!kd3->tree);
+
+ /* create tree */
+ kd3->tree = Gif_NewArray(kd3_treepos, 256);
+ kd3->ntree = 256;
+ kd3->maxdepth = 0;
+
+ /* create copy of items; remove duplicates */
+ perm = Gif_NewArray(int, kd3->nitems);
+ for (i = 0; i != kd3->nitems; ++i)
+ perm[i] = i;
+#if ENABLE_THREADS
+ /*
+ * Because kd3_sorter is a static global used in some
+ * sorting comparators, put a mutex around this
+ * code block to avoid an utter catastrophe.
+ */
+ pthread_mutex_lock(&kd3_sort_lock);
+#endif
+
+ kd3_sorter = kd3;
+ qsort(perm, kd3->nitems, sizeof(int), kd3_item_compare_all);
+ for (i = 0, delta = 1; i + delta < kd3->nitems; ++i)
+ if (memcmp(&kd3->ks[perm[i]], &kd3->ks[perm[i+delta]],
+ sizeof(kcolor)) == 0)
+ ++delta, --i;
+ else if (delta > 1)
+ perm[i+1] = perm[i+delta];
+
+ kd3_build_range(perm, kd3->nitems - (delta - 1), 0, 0);
+ assert(kd3->maxdepth < 32);
+
+#if ENABLE_THREADS
+ pthread_mutex_unlock(&kd3_sort_lock);
+#endif
+ Gif_DeleteArray(perm);
+}
+
+void kd3_init_build(kd3_tree* kd3, kcolor (*transform)(int, int, int),
+ const Gif_Colormap* gfcm) {
+ int i;
+ kd3_init(kd3, transform);
+ for (i = 0; i < gfcm->ncol; ++i)
+ kd3_add8g(kd3, gfcm->col[i].gfc_red, gfcm->col[i].gfc_green,
+ gfcm->col[i].gfc_blue);
+ kd3_build(kd3);
+}
+
+int kd3_closest_transformed(kd3_tree* kd3, kcolor k, unsigned* dist_store) {
+ const kd3_treepos* stack[32];
+ uint8_t state[32];
+ int stackpos = 0;
+ int result = -1;
+ unsigned mindist = (unsigned) -1;
+
+ if (!kd3->tree)
+ kd3_build(kd3);
+
+ stack[0] = kd3->tree;
+ state[0] = 0;
+
+ while (stackpos >= 0) {
+ const kd3_treepos* p;
+ assert(stackpos < 32);
+ p = stack[stackpos];
+
+ if (p->offset < 0) {
+ if (p->pivot >= 0 && kd3->disabled != p->pivot) {
+ unsigned dist = kc_distance(kd3->ks[p->pivot], k);
+ if (dist < mindist) {
+ mindist = dist;
+ result = p->pivot;
+ }
+ }
+ if (--stackpos >= 0)
+ ++state[stackpos];
+ } else if (state[stackpos] == 0) {
+ if (k.a[stackpos % 3] < p->pivot)
+ stack[stackpos + 1] = p + 1;
+ else
+ stack[stackpos + 1] = p + p->offset;
+ ++stackpos;
+ state[stackpos] = 0;
+ } else {
+ int delta = k.a[stackpos % 3] - p->pivot;
+ if (state[stackpos] == 1
+ && (unsigned) delta * (unsigned) delta < mindist) {
+ if (delta < 0)
+ stack[stackpos + 1] = p + p->offset;
+ else
+ stack[stackpos + 1] = p + 1;
+ ++stackpos;
+ state[stackpos] = 0;
+ } else if (--stackpos >= 0)
+ ++state[stackpos];
+ }
+ }
+
+ if (dist_store)
+ *dist_store = mindist;
+ return result;
+}
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/kcolor.h b/packages/optimizt-gifsicle/upstream/gifsicle/src/kcolor.h
new file mode 100644
index 0000000..b1c16df
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/kcolor.h
@@ -0,0 +1,354 @@
+/* kcolor.h - Color-oriented function declarations for gifsicle.
+ Copyright (C) 2013-2021 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. */
+
+#ifndef GIFSICLE_KCOLOR_H
+#define GIFSICLE_KCOLOR_H
+#include
+#include
+
+/* kcolor: a 3D vector, each component has 15 bits of precision */
+/* 15 bits means KC_MAX * KC_MAX always fits within a signed 32-bit
+ integer, and a 3-D squared distance always fits within an unsigned 32-bit
+ integer. */
+#define KC_MAX 0x7FFF
+#define KC_WHOLE 0x8000
+#define KC_HALF 0x4000
+#define KC_QUARTER 0x2000
+#define KC_BITS 15
+typedef struct kcolor {
+ int16_t a[3];
+} kcolor;
+
+#undef min
+#undef max
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+#define KC_CLAMPV(v) (max(0, min((v), KC_MAX)))
+
+typedef union kacolor {
+ kcolor k;
+ int16_t a[4];
+#if HAVE_INT64_T
+ int64_t q; /* to get better alignment */
+#endif
+} kacolor;
+
+
+/* gamma_tables[0]: array of 256 gamma-conversion values
+ gamma_tables[1]: array of 256 reverse gamma-conversion values */
+extern const uint16_t* gamma_tables[2];
+
+
+/* return the gamma transformation of `a0/a1/a2` [RGB] */
+static inline kcolor kc_make8g(int a0, int a1, int a2) {
+ kcolor kc;
+ kc.a[0] = gamma_tables[0][a0];
+ kc.a[1] = gamma_tables[0][a1];
+ kc.a[2] = gamma_tables[0][a2];
+ return kc;
+}
+
+/* return the gamma transformation of `*gfc` */
+static inline kcolor kc_makegfcg(const Gif_Color* gfc) {
+ return kc_make8g(gfc->gfc_red, gfc->gfc_green, gfc->gfc_blue);
+}
+
+/* return the uncorrected representation of `a0/a1/a2` [RGB] */
+static inline kcolor kc_make8ng(int a0, int a1, int a2) {
+ kcolor kc;
+ kc.a[0] = (a0 << 7) + (a0 >> 1);
+ kc.a[1] = (a1 << 7) + (a1 >> 1);
+ kc.a[2] = (a2 << 7) + (a2 >> 1);
+ return kc;
+}
+
+/* return the kcolor representation of `*gfc` (no gamma transformation) */
+static inline kcolor kc_makegfcng(const Gif_Color* gfc) {
+ return kc_make8ng(gfc->gfc_red, gfc->gfc_green, gfc->gfc_blue);
+}
+
+/* return transparency */
+static inline kacolor kac_transparent() {
+ kacolor x;
+ x.a[0] = x.a[1] = x.a[2] = x.a[3] = 0;
+ return x;
+}
+
+/* return a hex color string definition for `x` */
+const char* kc_debug_str(kcolor x);
+
+/* set `*x` to the reverse gamma transformation of `*x` */
+kcolor kc_revgamma_transform(kcolor x);
+
+/* return the reverse gramma transformation of `*x` as a Gif_Color */
+static inline Gif_Color kc_togfcg(kcolor x) {
+ Gif_Color gfc;
+ x = kc_revgamma_transform(x);
+ gfc.gfc_red = (uint8_t) (x.a[0] >> 7);
+ gfc.gfc_green = (uint8_t) (x.a[1] >> 7);
+ gfc.gfc_blue = (uint8_t) (x.a[2] >> 7);
+ gfc.haspixel = 0;
+ return gfc;
+}
+
+
+/* return the squared Euclidean distance between `*x` and `*y` */
+static inline uint32_t kc_distance(kcolor x, kcolor y) {
+ /* It’s OK to use unsigned multiplication for this: the low 32 bits
+ are the same either way. Unsigned avoids undefined behavior. */
+ uint32_t d0 = (uint32_t) x.a[0] - (uint32_t) y.a[0];
+ uint32_t d1 = (uint32_t) x.a[1] - (uint32_t) y.a[1];
+ uint32_t d2 = (uint32_t) x.a[2] - (uint32_t) y.a[2];
+ return d0 * d0 + d1 * d1 + d2 * d2;
+}
+
+/* return the luminance value for `*x`; result is between 0 and KC_MAX */
+static inline int kc_luminance(kcolor kc) {
+ return (55 * kc.a[0] + 183 * kc.a[1] + 19 * kc.a[2]) >> 8;
+}
+
+/* set `*x` to the grayscale version of `*x`, transformed by luminance */
+static inline kcolor kc_luminance_transform(int a0, int a1, int a2) {
+ /* For grayscale colormaps, use distance in luminance space instead of
+ distance in RGB space. The weights for the R,G,B components in
+ luminance space are 0.2126,0.7152,0.0722. (That's ITU primaries, which
+ are compatible with sRGB; NTSC recommended our previous values,
+ 0.299,0.587,0.114.) Using the proportional factors 55,183,19 we get a
+ scaled gray value between 0 and 255 * 257; dividing by 256 gives us
+ what we want. Thanks to Christian Kumpf, , for
+ providing a patch. */
+ kcolor kc = kc_make8g(a0, a1, a2);
+ kc.a[0] = kc.a[1] = kc.a[2] = kc_luminance(kc);
+ return kc;
+}
+
+kcolor kc_oklab_transform(int a0, int a1, int a2);
+
+
+/* wkcolor: like kcolor, but components are 32 bits instead of 16 */
+
+typedef struct wkcolor {
+ int32_t a[3];
+} wkcolor;
+
+static inline wkcolor wkc_zero(void) {
+ wkcolor wkc;
+ wkc.a[0] = wkc.a[1] = wkc.a[2] = 0;
+ return wkc;
+}
+
+static inline int wkc_iszero(wkcolor wkc) {
+ return wkc.a[0] == 0 && wkc.a[1] == 0 && wkc.a[2] == 0;
+}
+
+static inline kcolor kc_adjust(kcolor k, wkcolor delta) {
+ k.a[0] = KC_CLAMPV((int32_t) k.a[0] + delta.a[0]);
+ k.a[1] = KC_CLAMPV((int32_t) k.a[1] + delta.a[1]);
+ k.a[2] = KC_CLAMPV((int32_t) k.a[2] + delta.a[2]);
+ return k;
+}
+
+
+/* kd3_tree: kd-tree for 3 dimensions, indexing kcolors */
+
+typedef struct kd3_tree kd3_tree;
+typedef struct kd3_treepos kd3_treepos;
+
+struct kd3_tree {
+ kd3_treepos* tree;
+ int ntree;
+ int disabled;
+ kcolor* ks;
+ int nitems;
+ int items_cap;
+ int maxdepth;
+ kcolor (*transform)(int, int, int);
+ unsigned* xradius;
+};
+
+/* initialize `kd3` with the given color `transform` (may be NULL) */
+void kd3_init(kd3_tree* kd3, kcolor (*transform)(int, int, int));
+
+/* free `kd3` */
+void kd3_cleanup(kd3_tree* kd3);
+
+/* return the transformed color for 8-bit color `a0/a1/a2` (RGB) */
+static inline kcolor kd3_make8g(kd3_tree* kd3, int a0, int a1, int a2) {
+ return kd3->transform(a0, a1, a2);
+}
+
+/* return the transformed color for `*gfc` */
+static inline kcolor kd3_makegfcg(kd3_tree* kd3, const Gif_Color* gfc) {
+ return kd3_make8g(kd3, gfc->gfc_red, gfc->gfc_green, gfc->gfc_blue);
+}
+
+/* add the transformed color `k` to `*kd3` (do not apply `kd3->transform`). */
+void kd3_add_transformed(kd3_tree* kd3, kcolor k);
+
+/* given 8-bit color `a0/a1/a2` (RGB), transform it by `kd3->transform`
+ (e.g., apply gamma), and add it to `*kd3` */
+static inline void kd3_add8g(kd3_tree* kd3, int a0, int a1, int a2) {
+ kd3_add_transformed(kd3, kd3_make8g(kd3, a0, a1, a2));
+}
+
+/* set `kd3->xradius`. given color `i`, `kd3->xradius[i]` is the square of the
+ color's uniquely owned neighborhood.
+ If `kc_distance(&kd3->ks[i], &k) < kd3->xradius[i]`, then
+ `kd3_closest_transformed(kd3, &k) == i`. */
+void kd3_build_xradius(kd3_tree* kd3);
+
+/* build the actual kd-tree for `kd3`. must be called before kd3_closest. */
+void kd3_build(kd3_tree* kd3);
+
+/* kd3_init + kd3_add8g for all colors in `gfcm` + kd3_build */
+void kd3_init_build(kd3_tree* kd3, kcolor (*transform)(int, int, int),
+ const Gif_Colormap* gfcm);
+
+/* return the index of the color in `*kd3` closest to `k`.
+ if `dist!=NULL`, store the distance from `k` to that index in `*dist`. */
+int kd3_closest_transformed(kd3_tree* kd3, kcolor k, unsigned* dist);
+
+/* given 8-bit color `a0/a1/a2` (RGB), transform it by `kd3->transform`
+ (e.g., apply gamma), and return the index of the color in `*kd3`
+ closest to the result. */
+static inline int kd3_closest8g(kd3_tree* kd3, int a0, int a1, int a2) {
+ return kd3_closest_transformed(kd3, kd3_make8g(kd3, a0, a1, a2), NULL);
+}
+
+/* disable color index `i` in `*kd3`: it will never be returned by
+ `kd3_closest*` */
+static inline void kd3_disable(kd3_tree* kd3, int i) {
+ assert((unsigned) i < (unsigned) kd3->nitems);
+ assert(kd3->disabled < 0 || kd3->disabled == i);
+ kd3->disabled = i;
+}
+
+/* enable all color indexes in `*kd3` */
+static inline void kd3_enable_all(kd3_tree* kd3) {
+ kd3->disabled = -1;
+}
+
+
+typedef uint32_t kchist_count_t;
+typedef struct kchistitem {
+ kacolor ka;
+ kchist_count_t count;
+} kchistitem;
+
+typedef struct kchist {
+ kchistitem* h;
+ int n;
+ int capacity;
+} kchist;
+
+void kchist_init(kchist* kch);
+void kchist_cleanup(kchist* kch);
+void kchist_make(kchist* kch, Gif_Stream* gfs, uint32_t* ntransp);
+kchistitem* kchist_add(kchist* kch, kcolor color, kchist_count_t count);
+void kchist_compress(kchist* kch);
+
+static inline int kchistitem_compare_red(const void* va, const void* vb) {
+ const kchistitem* a = (const kchistitem*) va;
+ const kchistitem* b = (const kchistitem*) vb;
+ return a->ka.a[0] - b->ka.a[0];
+}
+
+static inline int kchistitem_compare_green(const void* va, const void* vb) {
+ const kchistitem* a = (const kchistitem*) va;
+ const kchistitem* b = (const kchistitem*) vb;
+ return a->ka.a[1] - b->ka.a[1];
+}
+
+static inline int kchistitem_compare_blue(const void* va, const void* vb) {
+ const kchistitem* a = (const kchistitem*) va;
+ const kchistitem* b = (const kchistitem*) vb;
+ return a->ka.a[2] - b->ka.a[2];
+}
+
+static inline int kchistitem_compare_popularity(const void* va, const void* vb) {
+ const kchistitem* a = (const kchistitem*) va;
+ const kchistitem* b = (const kchistitem*) vb;
+ return a->count > b->count ? -1 : a->count != b->count;
+}
+
+
+typedef struct {
+ kchist* kch;
+ int* closest;
+ uint32_t* min_dist;
+ uint32_t* min_dither_dist;
+ int* chosen;
+ int nchosen;
+} kcdiversity;
+
+void kcdiversity_init(kcdiversity* div, kchist* kch, int dodither);
+void kcdiversity_cleanup(kcdiversity* div);
+int kcdiversity_find_popular(kcdiversity* div);
+int kcdiversity_find_diverse(kcdiversity* div, double ditherweight);
+int kcdiversity_choose(kcdiversity* div, int chosen, int dodither);
+
+
+#if HAVE_SIMD && HAVE_VECTOR_SIZE_VECTOR_TYPES
+typedef float float4 __attribute__((vector_size (sizeof(float) * 4)));
+typedef int int4 __attribute__((vector_size (sizeof(int) * 4)));
+#elif HAVE_SIMD && HAVE_EXT_VECTOR_TYPE_VECTOR_TYPES
+typedef float float4 __attribute__((ext_vector_type (4)));
+#else
+typedef float float4[4];
+#endif
+
+typedef union scale_color {
+ float4 a;
+} scale_color;
+
+static inline void sc_clear(scale_color* x) {
+ x->a[0] = x->a[1] = x->a[2] = x->a[3] = 0;
+}
+
+static inline scale_color sc_makekc(kcolor k) {
+ scale_color sc;
+ sc.a[0] = k.a[0];
+ sc.a[1] = k.a[1];
+ sc.a[2] = k.a[2];
+ sc.a[3] = KC_MAX;
+ return sc;
+}
+
+static inline scale_color sc_make(float a0, float a1, float a2, float a3) {
+ scale_color sc;
+ sc.a[0] = a0;
+ sc.a[1] = a1;
+ sc.a[2] = a2;
+ sc.a[3] = a3;
+ return sc;
+}
+
+#if HAVE_SIMD
+# define SCVEC_ADDV(sc, sc2) (sc).a += (sc2).a
+# define SCVEC_MULV(sc, sc2) (sc).a *= (sc2).a
+# define SCVEC_MULF(sc, f) (sc).a *= (f)
+# define SCVEC_DIVF(sc, f) (sc).a /= (f)
+# define SCVEC_ADDVxF(sc, sc2, f) (sc).a += (sc2).a * (f)
+# if HAVE___BUILTIN_SHUFFLEVECTOR
+# define SCVEC_ROT3(out, sc) do { (out).a = __builtin_shufflevector((sc).a, (sc).a, 1, 2, 0, 3); } while (0)
+# else
+# define SCVEC_ROT3(out, sc) do { int4 shufmask__ = {1, 2, 0, 3}; (out).a = __builtin_shuffle((sc).a, shufmask__); } while (0)
+# endif
+#else
+# define SCVEC_FOREACH(t) do { int k__; for (k__ = 0; k__ != 4; ++k__) { t; } } while (0)
+# define SCVEC_ADDV(sc, sc2) SCVEC_FOREACH((sc).a[k__] += (sc2).a[k__])
+# define SCVEC_MULV(sc, sc2) SCVEC_FOREACH((sc).a[k__] *= (sc2).a[k__])
+# define SCVEC_MULF(sc, f) SCVEC_FOREACH((sc).a[k__] *= (f))
+# define SCVEC_DIVF(sc, f) SCVEC_FOREACH((sc).a[k__] /= (f))
+# define SCVEC_ADDVxF(sc, sc2, f) SCVEC_FOREACH((sc).a[k__] += (sc2).a[k__] * (f))
+# define SCVEC_ROT3(out, sc) do { float __a0 = (sc).a[0]; (out).a[0] = (sc).a[1]; (out).a[1] = (sc).a[2]; (out).a[2] = __a0; (out).a[3] = (sc).a[3]; } while (0)
+#endif
+
+#endif
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/merge.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/merge.c
new file mode 100644
index 0000000..5b6b110
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/merge.c
@@ -0,0 +1,403 @@
+/* merge.c - Functions which actually combine and manipulate GIF image data.
+ Copyright (C) 1997-2021 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
+#include
+#include
+#include
+#include
+#include
+
+/* First merging stage: Mark the used colors in all colormaps. */
+
+void
+unmark_colors(Gif_Colormap *gfcm)
+{
+ int i;
+ if (gfcm)
+ for (i = 0; i < gfcm->ncol; i++)
+ gfcm->col[i].haspixel = 0;
+}
+
+void
+unmark_colors_2(Gif_Colormap *gfcm)
+{
+ int i;
+ for (i = 0; i < gfcm->ncol; i++) {
+ gfcm->col[i].pixel = 256;
+ gfcm->col[i].haspixel = 0;
+ }
+}
+
+
+void
+mark_used_colors(Gif_Stream *gfs, Gif_Image *gfi, Gt_Crop *crop,
+ int compress_immediately)
+{
+ Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global;
+ Gif_Color *col;
+ int i, j, l, t, r, b, nleft, ncol, transp = gfi->transparent;
+
+ /* There might not be a colormap. */
+ if (!gfcm)
+ return;
+ col = gfcm->col;
+ ncol = gfcm->ncol;
+
+ /* Mark color used for transparency. */
+ if (transp >= 0 && transp < ncol)
+ col[transp].haspixel |= 2;
+
+ /* Only mark colors until we've seen all of them. The left variable keeps
+ track of how many are left. */
+ for (i = nleft = 0; i < ncol; ++i)
+ if (!(col[i].haspixel & 1) && i != transp)
+ ++nleft;
+ if (nleft == 0)
+ return;
+
+ if (gfi->img || Gif_UncompressImage(gfs, gfi) == 2)
+ compress_immediately = 0;
+
+ /* Loop over every pixel (until we've seen all colors) */
+ if (crop) {
+ Gt_Crop c;
+ combine_crop(&c, crop, gfi);
+ l = c.x;
+ t = c.y;
+ r = l + c.w;
+ b = t + c.h;
+ } else {
+ l = t = 0;
+ r = gfi->width;
+ b = gfi->height;
+ }
+
+ for (j = t; j != b; ++j) {
+ uint8_t *data = gfi->img[j] + l;
+ for (i = l; i != r; ++i, ++data)
+ if (*data < ncol && !(col[*data].haspixel & 1) && *data != transp) {
+ col[*data].haspixel |= 1;
+ --nleft;
+ if (nleft == 0)
+ goto done;
+ }
+ }
+
+ done:
+ if (compress_immediately > 0)
+ Gif_ReleaseUncompressedImage(gfi);
+}
+
+
+int
+find_color_index(Gif_Color *c, int nc, Gif_Color *color)
+{
+ int index;
+ for (index = 0; index < nc; index++)
+ if (GIF_COLOREQ(&c[index], color))
+ return index;
+ return -1;
+}
+
+
+int
+merge_colormap_if_possible(Gif_Colormap *dest, Gif_Colormap *src)
+{
+ Gif_Color *srccol;
+ Gif_Color *destcol = dest->col;
+ int destncol = dest->ncol;
+ int dest_user_flags = dest->user_flags;
+ int i, x;
+ int trivial_map = 1;
+
+ if (!src)
+ return 1;
+
+ srccol = src->col;
+ for (i = 0; i < src->ncol; i++) {
+ if (srccol[i].haspixel & 1) {
+ /* Store an image color cell's mapping to the global colormap in
+ its 'pixel' slot. This is useful caching: oftentimes many
+ input frames will share a colormap */
+ int mapto = (srccol[i].pixel < 256 ? (int)srccol[i].pixel : -1);
+
+ if (mapto == -1)
+ mapto = find_color_index(destcol, destncol, &srccol[i]);
+
+ if (mapto == -1 && destncol < 256) {
+ /* add the color */
+ mapto = destncol;
+ destcol[mapto] = srccol[i];
+ destncol++;
+ }
+
+ if (mapto == -1)
+ /* check for a pure-transparent color */
+ for (x = 0; x < destncol; x++)
+ if (destcol[x].haspixel == 2) {
+ mapto = x;
+ destcol[mapto] = srccol[i];
+ break;
+ }
+
+ if (mapto == -1)
+ /* give up and require a local colormap */
+ goto local_colormap_required;
+
+ assert(mapto >= 0 && mapto < destncol);
+ assert(GIF_COLOREQ(&destcol[mapto], &srccol[i]));
+
+ srccol[i].pixel = mapto;
+ destcol[mapto].haspixel = 1;
+ if (mapto != i)
+ trivial_map = 0;
+
+ } else if (srccol[i].haspixel & 2) {
+ /* a dedicated transparent color; if trivial_map & at end of
+ colormap insert it with haspixel == 2. (strictly not
+ necessary; we do it to try to keep the map trivial.) */
+ if (trivial_map && i == destncol) {
+ destcol[destncol] = srccol[i];
+ destncol++;
+ }
+ }
+ }
+
+ /* success! save new number of colors */
+ dest->ncol = destncol;
+ dest->user_flags = dest_user_flags;
+ return 1;
+
+ /* failure: a local colormap is required */
+ local_colormap_required:
+ if (warn_local_colormaps == 1) {
+ static int context = 0;
+ if (!context) {
+ warning(1, "too many colors, using local colormaps\n"
+ " (You may want to try %<--colors 256%>.)");
+ context = 1;
+ } else
+ warning(1, "too many colors, using local colormaps");
+ warn_local_colormaps = 2;
+ }
+
+ /* 9.Dec.1998 - This must have been a longstanding bug! We MUST clear
+ the cached mappings of any pixels in the source colormap we
+ assigned this time through, since we are throwing those colors
+ away. We assigned it this time through if the cached mapping is >=
+ dest->ncol. */
+ for (x = 0; x < i; x++)
+ if ((srccol[x].haspixel & 1) && srccol[x].pixel >= (uint32_t)dest->ncol)
+ srccol[x].pixel = 256;
+
+ return 0;
+}
+
+
+void
+merge_stream(Gif_Stream *dest, Gif_Stream *src, int no_comments)
+{
+ int i;
+ assert(dest->global);
+
+ /* unmark colors in global and local colormaps -- 12/9 */
+ if (src->global)
+ unmark_colors_2(src->global);
+ for (i = 0; i < src->nimages; i++)
+ if (src->images[i]->local)
+ unmark_colors_2(src->images[i]->local);
+
+ if (dest->loopcount < 0)
+ dest->loopcount = src->loopcount;
+
+ if (src->end_comment && !no_comments) {
+ if (!dest->end_comment)
+ dest->end_comment = Gif_NewComment();
+ merge_comments(dest->end_comment, src->end_comment);
+ }
+}
+
+
+void
+merge_comments(Gif_Comment *destc, Gif_Comment *srcc)
+{
+ int i;
+ for (i = 0; i < srcc->count; i++)
+ Gif_AddComment(destc, srcc->str[i], srcc->len[i]);
+}
+
+
+static void merge_image_input_colors(uint8_t* inused, const Gif_Image* srci) {
+ int i, x, y, nleft = Gif_ImageColorBound(srci);
+ for (i = 0; i != 256; ++i)
+ inused[i] = 0;
+ for (y = 0; y != srci->height && nleft > 0; ++y) {
+ const uint8_t* data = srci->img[y];
+ for (x = 0; x != srci->width; ++x, ++data) {
+ nleft -= 1 - inused[*data];
+ inused[*data] = 1;
+ }
+ }
+ if (srci->transparent >= 0)
+ inused[srci->transparent] = 0;
+}
+
+
+Gif_Image *
+merge_image(Gif_Stream *dest, Gif_Stream *src, Gif_Image *srci,
+ Gt_Frame* srcfr, int same_compressed_ok)
+{
+ Gif_Colormap *imagecm;
+ int imagecm_ncol;
+ int i;
+ Gif_Colormap *localcm = 0;
+ Gif_Colormap *destcm = dest->global;
+
+ uint8_t map[256]; /* map[input pixel value] == output pixval */
+ int trivial_map; /* does the map take input pixval --> the same
+ pixel value for all colors in the image? */
+ uint8_t inused[256]; /* inused[input pival] == 1 iff used */
+ uint8_t used[256]; /* used[output pixval K] == 1 iff K was used
+ in the image */
+
+
+ Gif_Image *desti;
+
+ /* mark colors that were actually used in this image */
+ imagecm = srci->local ? srci->local : src->global;
+ imagecm_ncol = imagecm ? imagecm->ncol : 0;
+ merge_image_input_colors(inused, srci);
+ for (i = imagecm_ncol; i != 256; ++i)
+ if (inused[i]) {
+ lwarning(srcfr->input_filename, "some colors undefined by colormap");
+ break;
+ }
+
+ /* map[old_pixel_value] == new_pixel_value */
+ for (i = 0; i < 256; i++)
+ map[i] = used[i] = 0;
+
+ /* Merge the colormap */
+ if (merge_colormap_if_possible(dest->global, imagecm)) {
+ /* Create 'map' and 'used' for global colormap. */
+ for (i = 0; i != imagecm_ncol; ++i)
+ if (inused[i])
+ map[i] = imagecm->col[i].pixel;
+
+ } else {
+ /* Need a local colormap. */
+ destcm = localcm = Gif_NewFullColormap(0, 256);
+ for (i = 0; i != imagecm_ncol; ++i)
+ if (inused[i]) {
+ map[i] = localcm->ncol;
+ localcm->col[localcm->ncol] = imagecm->col[i];
+ ++localcm->ncol;
+ }
+ }
+
+ trivial_map = 1;
+ for (i = 0; i != 256; ++i)
+ if (inused[i]) {
+ used[map[i]] = 1;
+ trivial_map = trivial_map && map[i] == i;
+ }
+
+ /* Decide on a transparent index */
+ if (srci->transparent >= 0) {
+ int found_transparent = -1;
+
+ /* try to keep the map trivial -- prefer same transparent index */
+ if (trivial_map && !used[srci->transparent])
+ found_transparent = srci->transparent;
+ else
+ for (i = destcm->ncol - 1; i >= 0; i--)
+ if (!used[i])
+ found_transparent = i;
+
+ /* 1.Aug.1999 - Allow for the case that the transparent index is bigger
+ than the number of colors we've created thus far. */
+ if (found_transparent < 0 || found_transparent >= destcm->ncol) {
+ Gif_Color *c;
+ found_transparent = destcm->ncol;
+ /* 1.Aug.1999 - Don't update destcm->ncol -- we want the output colormap
+ to be as small as possible. */
+ c = &destcm->col[found_transparent];
+ if (imagecm && srci->transparent < imagecm->ncol)
+ *c = imagecm->col[srci->transparent];
+ c->haspixel = 2;
+ assert(c->haspixel == 2 && found_transparent < 256);
+ }
+
+ map[srci->transparent] = found_transparent;
+ if (srci->transparent != found_transparent) trivial_map = 0;
+ }
+
+ assert(destcm->ncol <= 256);
+ /* Make the new image. */
+ desti = Gif_NewImage();
+
+ desti->identifier = Gif_CopyString(srci->identifier);
+ if (srci->transparent > -1)
+ desti->transparent = map[srci->transparent];
+ desti->delay = srci->delay;
+ desti->disposal = srci->disposal;
+ desti->left = srci->left;
+ desti->top = srci->top;
+ desti->interlace = srci->interlace;
+
+ desti->width = srci->width;
+ desti->height = srci->height;
+ desti->local = localcm;
+
+ if (trivial_map && same_compressed_ok && srci->compressed
+ && !srci->compressed_errors) {
+ desti->compressed_len = srci->compressed_len;
+ desti->compressed = Gif_NewArray(uint8_t, srci->compressed_len);
+ desti->free_compressed = Gif_Free;
+ memcpy(desti->compressed, srci->compressed, srci->compressed_len);
+ } else {
+ int i, j;
+ Gif_CreateUncompressedImage(desti, desti->interlace);
+
+ if (trivial_map)
+ for (j = 0; j < desti->height; j++)
+ memcpy(desti->img[j], srci->img[j], desti->width);
+
+ else
+ for (j = 0; j < desti->height; j++) {
+ uint8_t *srcdata = srci->img[j];
+ uint8_t *destdata = desti->img[j];
+ for (i = 0; i < desti->width; i++, srcdata++, destdata++)
+ *destdata = map[*srcdata];
+ }
+ }
+
+ /* comments and extensions */
+ if (srci->comment) {
+ desti->comment = Gif_NewComment();
+ merge_comments(desti->comment, srci->comment);
+ }
+ if (srci->extension_list && !srcfr->no_extensions) {
+ Gif_Extension* gfex;
+ for (gfex = srci->extension_list; gfex; gfex = gfex->next)
+ if (gfex->kind != 255 || !srcfr->no_app_extensions)
+ Gif_AddExtension(dest, desti, Gif_CopyExtension(gfex));
+ }
+ while (srcfr->extensions) {
+ Gif_Extension* next = srcfr->extensions->next;
+ Gif_AddExtension(dest, desti, srcfr->extensions);
+ srcfr->extensions = next;
+ }
+
+ Gif_AddImage(dest, desti);
+ return desti;
+}
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/optimize.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/optimize.c
new file mode 100644
index 0000000..e153cdb
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/optimize.c
@@ -0,0 +1,467 @@
+/* optimize.c - Functions to optimize animated GIFs.
+ Copyright (C) 1997-2021 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
+
+typedef int32_t penalty_type;
+
+typedef struct {
+ int left;
+ int top;
+ int width;
+ int height;
+} Gif_OptBounds;
+
+typedef struct {
+ uint16_t left;
+ uint16_t top;
+ uint16_t width;
+ uint16_t height;
+ uint32_t size;
+ uint8_t disposal;
+ int transparent;
+ uint8_t *needed_colors;
+ unsigned required_color_count;
+ int32_t active_penalty;
+ int32_t global_penalty;
+ int32_t colormap_penalty;
+ Gif_Image *new_gfi;
+} Gif_OptData;
+
+/* Screen width and height */
+static int screen_width;
+static int screen_height;
+
+/* Colormap containing all colors in the image. May have >256 colors */
+static Gif_Colormap *all_colormap;
+/* Histogram so we can find colors quickly */
+static kchist all_colormap_hist;
+
+/* The old global colormap, or a fake one we created if necessary */
+static Gif_Colormap *in_global_map;
+
+/* The new global colormap */
+static Gif_Colormap *out_global_map;
+
+#define TRANSP (0)
+#define NOT_IN_OUT_GLOBAL (256)
+static unsigned background;
+static int image_index;
+
+static penalty_type *permuting_sort_values;
+
+#define REQUIRED 2
+#define REPLACE_TRANSP 1
+
+
+/*****
+ * SIMPLE HELPERS
+ * new and delete optimize data; and colormap_combine; and sorting permutations
+ **/
+
+Gif_OptData *
+new_opt_data(void)
+{
+ Gif_OptData *od = Gif_New(Gif_OptData);
+ od->needed_colors = 0;
+ od->global_penalty = 1;
+ return od;
+}
+
+void
+delete_opt_data(Gif_OptData *od)
+{
+ if (!od) return;
+ Gif_DeleteArray(od->needed_colors);
+ Gif_Delete(od);
+}
+
+
+/* all_colormap_add: Ensure that each color in 'src' is represented in
+ 'all_colormap'. For each color 'i' in 'src', src->col[i].pixel == some j
+ so that GIF_COLOREQ(&src->col[i], &all_colormap->col[j]).
+ all_colormap->col[0] is reserved for transparency; no source color will
+ be mapped to it. */
+
+static void all_colormap_add(const Gif_Colormap* src) {
+ int i;
+
+ /* expand dst->col if necessary. This might change dst->col */
+ if (all_colormap->ncol + src->ncol >= all_colormap->capacity) {
+ all_colormap->capacity *= 2;
+ Gif_ReArray(all_colormap->col, Gif_Color, all_colormap->capacity);
+ }
+
+ for (i = 0; i < src->ncol; ++i) {
+ kchistitem* khi = kchist_add(&all_colormap_hist,
+ kc_makegfcng(&src->col[i]), 0);
+ if (!khi->count) {
+ all_colormap->col[all_colormap->ncol] = src->col[i];
+ all_colormap->col[all_colormap->ncol].pixel = 0;
+ khi->count = all_colormap->ncol;
+ ++all_colormap->ncol;
+ }
+ src->col[i].pixel = khi->count;
+ }
+}
+
+
+/*****
+ * MANIPULATING IMAGE AREAS
+ **/
+
+static Gif_OptBounds
+safe_bounds(Gif_Image *area)
+{
+ /* Returns bounds constrained to lie within the screen. */
+ Gif_OptBounds b;
+ b.left = constrain(0, area->left, screen_width);
+ b.top = constrain(0, area->top, screen_height);
+ b.width = constrain(0, area->left + area->width, screen_width) - b.left;
+ b.height = constrain(0, area->top + area->height, screen_height) - b.top;
+ return b;
+}
+
+
+/*****
+ * FIND THE SMALLEST BOUNDING RECTANGLE ENCLOSING ALL CHANGES
+ **/
+
+/* fix_difference_bounds: make sure the image isn't 0x0. */
+
+static void
+fix_difference_bounds(Gif_OptData *bounds)
+{
+ if (bounds->width == 0 || bounds->height == 0) {
+ bounds->top = 0;
+ bounds->left = 0;
+ bounds->width = 1;
+ bounds->height = 1;
+ }
+ /* assert that image lies completely within screen */
+ assert(bounds->top < screen_height && bounds->left < screen_width
+ && bounds->top + bounds->height <= screen_height
+ && bounds->left + bounds->width <= screen_width);
+}
+
+
+/*****
+ * CALCULATE OUTPUT GLOBAL COLORMAP
+ **/
+
+static void
+increment_penalties(Gif_OptData *opt, penalty_type *penalty, int32_t delta)
+{
+ int i;
+ int all_ncol = all_colormap->ncol;
+ uint8_t *need = opt->needed_colors;
+ for (i = 1; i < all_ncol; i++)
+ if (need[i] == REQUIRED)
+ penalty[i] += delta;
+}
+
+
+/*****
+ * CREATE COLOR MAPPING FOR A PARTICULAR IMAGE
+ **/
+
+/* sort_colormap_permutation_rgb: for canonicalizing local colormaps by
+ arranging them in RGB order */
+
+static int
+colormap_rgb_permutation_sorter(const void *v1, const void *v2)
+{
+ const Gif_Color *col1 = (const Gif_Color *)v1;
+ const Gif_Color *col2 = (const Gif_Color *)v2;
+ int value1 = (col1->gfc_red << 16) | (col1->gfc_green << 8) | col1->gfc_blue;
+ int value2 = (col2->gfc_red << 16) | (col2->gfc_green << 8) | col2->gfc_blue;
+ return value1 - value2;
+}
+
+
+/* prepare_colormap_map: Create and return an array of bytes mapping from
+ global pixel values to pixel values for this image. It may add colormap
+ cells to 'into'; if there isn't enough room in 'into', it will return 0. It
+ sets the 'transparent' field of 'gfi->optdata', but otherwise doesn't
+ change or read it at all. */
+
+static uint8_t *
+prepare_colormap_map(Gif_Image *gfi, Gif_Colormap *into, uint8_t *need)
+{
+ int i;
+ int is_global = (into == out_global_map);
+
+ int all_ncol = all_colormap->ncol;
+ Gif_Color *all_col = all_colormap->col;
+
+ int ncol = into->ncol;
+ Gif_Color *col = into->col;
+
+ uint8_t *map = Gif_NewArray(uint8_t, all_ncol);
+ uint8_t into_used[256];
+
+ /* keep track of which pixel indices in 'into' have been used; initially,
+ all unused */
+ for (i = 0; i < 256; i++)
+ into_used[i] = 0;
+
+ /* go over all non-transparent global pixels which MUST appear
+ (need[P]==REQUIRED) and place them in 'into' */
+ for (i = 1; i < all_ncol; i++) {
+ int val;
+ if (need[i] != REQUIRED)
+ continue;
+
+ /* fail if a needed pixel isn't in the global map */
+ if (is_global) {
+ val = all_col[i].pixel;
+ if (val >= ncol)
+ goto error;
+ } else {
+ /* always place colors in a local colormap */
+ if (ncol == 256)
+ goto error;
+ val = ncol;
+ col[val] = all_col[i];
+ col[val].pixel = i;
+ ncol++;
+ }
+
+ map[i] = val;
+ into_used[val] = 1;
+ }
+
+ if (!is_global) {
+ qsort(col, ncol, sizeof(Gif_Color), colormap_rgb_permutation_sorter);
+ for (i = 0; i < ncol; ++i)
+ map[col[i].pixel] = i;
+ }
+
+ /* now check for transparency */
+ gfi->transparent = -1;
+ if (need[TRANSP]) {
+ int transparent = -1;
+
+ /* first, look for an unused index in 'into'. Pick the lowest one: the
+ lower transparent index we get, the more likely we can shave a bit off
+ min_code_bits later, thus saving space */
+ for (i = 0; i < ncol; i++)
+ if (!into_used[i]) {
+ transparent = i;
+ break;
+ }
+
+ /* otherwise, [1.Aug.1999] use a fake slot for the purely transparent
+ color. Don't actually enter the transparent color into the colormap --
+ we might be able to output a smaller colormap! If there's no room for
+ it, give up */
+ if (transparent < 0) {
+ if (ncol < 256) {
+ transparent = ncol;
+ /* 1.Aug.1999 - don't increase ncol */
+ col[ncol] = all_col[TRANSP];
+ } else
+ goto error;
+ }
+
+ /* change mapping */
+ map[TRANSP] = transparent;
+ for (i = 1; i < all_ncol; i++)
+ if (need[i] == REPLACE_TRANSP)
+ map[i] = transparent;
+
+ gfi->transparent = transparent;
+ }
+
+ /* If we get here, it worked! Commit state changes (the number of color
+ cells in 'into') and return the map. */
+ into->ncol = ncol;
+ return map;
+
+ error:
+ /* If we get here, it failed! Return 0 and don't change global state. */
+ Gif_DeleteArray(map);
+ return 0;
+}
+
+
+/* prepare_colormap: make a colormap up from the image data by fitting any
+ used colors into a colormap. Returns a map from global color index to index
+ in this image's colormap. May set a local colormap on 'gfi'. */
+
+static uint8_t *
+prepare_colormap(Gif_Image *gfi, uint8_t *need)
+{
+ uint8_t *map;
+
+ /* try to map pixel values into the global colormap */
+ Gif_DeleteColormap(gfi->local);
+ gfi->local = 0;
+ map = prepare_colormap_map(gfi, out_global_map, need);
+
+ if (!map) {
+ /* that didn't work; add a local colormap. */
+ gfi->local = Gif_NewFullColormap(0, 256);
+ map = prepare_colormap_map(gfi, gfi->local, need);
+ }
+
+ return map;
+}
+
+
+/*****
+ * INITIALIZATION AND FINALIZATION
+ **/
+
+static int
+initialize_optimizer(Gif_Stream *gfs)
+{
+ int i;
+
+ if (gfs->nimages < 1)
+ return 0;
+
+ /* combine colormaps */
+ all_colormap = Gif_NewFullColormap(1, 384);
+ all_colormap->col[0].gfc_red = 255;
+ all_colormap->col[0].gfc_green = 255;
+ all_colormap->col[0].gfc_blue = 255;
+
+ in_global_map = gfs->global;
+ if (!in_global_map) {
+ Gif_Color *col;
+ in_global_map = Gif_NewFullColormap(256, 256);
+ col = in_global_map->col;
+ for (i = 0; i < 256; i++, col++)
+ col->gfc_red = col->gfc_green = col->gfc_blue = i;
+ }
+
+ {
+ int any_globals = 0;
+ int first_transparent = -1;
+
+ kchist_init(&all_colormap_hist);
+ for (i = 0; i < gfs->nimages; i++) {
+ Gif_Image *gfi = gfs->images[i];
+ if (gfi->local)
+ all_colormap_add(gfi->local);
+ else
+ any_globals = 1;
+ if (gfi->transparent >= 0 && first_transparent < 0)
+ first_transparent = i;
+ }
+ if (any_globals)
+ all_colormap_add(in_global_map);
+ kchist_cleanup(&all_colormap_hist);
+
+ /* try and maintain transparency's pixel value */
+ if (first_transparent >= 0) {
+ Gif_Image *gfi = gfs->images[first_transparent];
+ Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global;
+ all_colormap->col[TRANSP] = gfcm->col[gfi->transparent];
+ }
+ }
+
+ /* find screen_width and screen_height, and clip all images to screen */
+ Gif_CalculateScreenSize(gfs, 0);
+ screen_width = gfs->screen_width;
+ screen_height = gfs->screen_height;
+ for (i = 0; i < gfs->nimages; i++)
+ Gif_ClipImage(gfs->images[i], 0, 0, screen_width, screen_height);
+
+ /* choose background */
+ if (gfs->images[0]->transparent < 0
+ && gfs->global && gfs->background < in_global_map->ncol)
+ background = in_global_map->col[gfs->background].pixel;
+ else
+ background = TRANSP;
+
+ return 1;
+}
+
+static void
+finalize_optimizer(Gif_Stream *gfs, int optimize_flags)
+{
+ int i;
+
+ if (background == TRANSP)
+ gfs->background = (uint8_t)gfs->images[0]->transparent;
+
+ /* 11.Mar.2010 - remove entirely transparent frames. */
+ for (i = 1; i < gfs->nimages && !(optimize_flags & GT_OPT_KEEPEMPTY); ++i) {
+ Gif_Image *gfi = gfs->images[i];
+ if (gfi->width == 1 && gfi->height == 1 && gfi->transparent >= 0
+ && !gfi->identifier && !gfi->comment
+ && (gfi->disposal == GIF_DISPOSAL_ASIS
+ || gfi->disposal == GIF_DISPOSAL_NONE
+ || gfi->disposal == GIF_DISPOSAL_PREVIOUS)
+ && gfi->delay && gfs->images[i-1]->delay) {
+ Gif_UncompressImage(gfs, gfi);
+ if (gfi->img[0][0] == gfi->transparent
+ && (gfs->images[i-1]->disposal == GIF_DISPOSAL_ASIS
+ || gfs->images[i-1]->disposal == GIF_DISPOSAL_NONE)) {
+ gfs->images[i-1]->delay += gfi->delay;
+ Gif_DeleteImage(gfi);
+ memmove(&gfs->images[i], &gfs->images[i+1], sizeof(Gif_Image *) * (gfs->nimages - i - 1));
+ --gfs->nimages;
+ --i;
+ }
+ }
+ }
+
+ /* 10.Dec.1998 - prefer GIF_DISPOSAL_NONE to GIF_DISPOSAL_ASIS. This is
+ semantically "wrong" -- it's better to set the disposal explicitly than
+ rely on default behavior -- but will result in smaller GIF files, since
+ the graphic control extension can be left off in many cases. */
+ for (i = 0; i < gfs->nimages; i++)
+ if (gfs->images[i]->disposal == GIF_DISPOSAL_ASIS
+ && gfs->images[i]->delay == 0
+ && gfs->images[i]->transparent < 0)
+ gfs->images[i]->disposal = GIF_DISPOSAL_NONE;
+
+ Gif_DeleteColormap(in_global_map);
+ Gif_DeleteColormap(all_colormap);
+}
+
+
+/* two versions of the optimization template */
+#define palindex_type uint16_t
+#define X(t) t ## 16
+#include "opttemplate.c"
+#undef palindex_type
+#undef X
+
+#define palindex_type uint32_t
+#define X(t) t ## 32
+#include "opttemplate.c"
+
+/* the interface function! */
+
+void
+optimize_fragments(Gif_Stream *gfs, int optimize_flags, int huge_stream)
+{
+ if (!initialize_optimizer(gfs))
+ return;
+ if ((unsigned) all_colormap->ncol >= 0xFFFF) {
+ create_subimages32(gfs, optimize_flags, !huge_stream);
+ create_out_global_map32(gfs);
+ create_new_image_data32(gfs, optimize_flags);
+ finalize_optimizer_data32();
+ } else {
+ create_subimages16(gfs, optimize_flags, !huge_stream);
+ create_out_global_map16(gfs);
+ create_new_image_data16(gfs, optimize_flags);
+ finalize_optimizer_data16();
+ }
+ finalize_optimizer(gfs, optimize_flags);
+}
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/opttemplate.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/opttemplate.c
new file mode 100644
index 0000000..736f7e1
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/opttemplate.c
@@ -0,0 +1,960 @@
+/* opttemplate.c - Functions to optimize animated GIFs.
+ Copyright (C) 1997-2021 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. */
+
+static palindex_type *X(last_data);
+static palindex_type *X(this_data);
+static palindex_type *X(next_data);
+
+
+/* sort_permutation: sorts a given permutation 'perm' according to the
+ corresponding values in 'values'. Thus, in the output, the sequence
+ '[ values[perm[i]] | i <- 0..size-1 ]' will be monotonic, either up or
+ (if is_down != 0) down. */
+
+/* 9.Dec.1998 - Dumb idiot, it's time you stopped using C. The optimizer was
+ broken because I switched to uint32_t's for the sorting values without
+ considering the consequences; and the consequences were bad. */
+
+static int
+X(permuting_sorter_up)(const void *v1, const void *v2) {
+ const palindex_type *n1 = (const palindex_type *)v1;
+ const palindex_type *n2 = (const palindex_type *)v2;
+ if (permuting_sort_values[*n1] < permuting_sort_values[*n2])
+ return -1;
+ else if (permuting_sort_values[*n1] == permuting_sort_values[*n2])
+ return 0;
+ else
+ return 1;
+}
+
+static int
+X(permuting_sorter_down)(const void *v1, const void *v2) {
+ const palindex_type *n1 = (const palindex_type *)v1;
+ const palindex_type *n2 = (const palindex_type *)v2;
+ if (permuting_sort_values[*n1] > permuting_sort_values[*n2])
+ return -1;
+ else if (permuting_sort_values[*n1] == permuting_sort_values[*n2])
+ return 0;
+ else
+ return 1;
+}
+
+static palindex_type*
+X(sort_permutation)(palindex_type *perm, int size,
+ penalty_type *values, int is_down) {
+ permuting_sort_values = values;
+ if (is_down)
+ qsort(perm, size, sizeof(palindex_type), X(permuting_sorter_down));
+ else
+ qsort(perm, size, sizeof(palindex_type), X(permuting_sorter_up));
+ permuting_sort_values = 0;
+ return perm;
+}
+
+
+/*****
+ * MANIPULATING IMAGE AREAS
+ **/
+
+static void
+X(copy_data_area)(palindex_type *dst, palindex_type *src, Gif_Image *area)
+{
+ Gif_OptBounds ob;
+ int y;
+ if (!area)
+ return;
+ ob = safe_bounds(area);
+ dst += ob.top * (unsigned) screen_width + ob.left;
+ src += ob.top * (unsigned) screen_width + ob.left;
+ for (y = 0; y < ob.height; y++) {
+ memcpy(dst, src, sizeof(palindex_type) * ob.width);
+ dst += screen_width;
+ src += screen_width;
+ }
+}
+
+static void
+X(copy_data_area_subimage)(palindex_type *dst, palindex_type *src,
+ Gif_OptData *area)
+{
+ Gif_Image img;
+ img.left = area->left;
+ img.top = area->top;
+ img.width = area->width;
+ img.height = area->height;
+ X(copy_data_area)(dst, src, &img);
+}
+
+static void
+X(erase_data_area)(palindex_type *dst, Gif_Image *area)
+{
+ int x, y;
+ Gif_OptBounds ob = safe_bounds(area);
+ dst += ob.top * (unsigned) screen_width + ob.left;
+ for (y = 0; y < ob.height; y++) {
+ for (x = 0; x < ob.width; x++)
+ dst[x] = TRANSP;
+ dst += screen_width;
+ }
+}
+
+static void
+X(erase_data_area_subimage)(palindex_type *dst, Gif_OptData *area)
+{
+ Gif_Image img;
+ img.left = area->left;
+ img.top = area->top;
+ img.width = area->width;
+ img.height = area->height;
+ X(erase_data_area)(dst, &img);
+}
+
+static void
+X(erase_screen)(palindex_type *dst)
+{
+ uint32_t i;
+ uint32_t screen_size = (unsigned) screen_width * (unsigned) screen_height;
+ for (i = 0; i < screen_size; i++)
+ *dst++ = TRANSP;
+}
+
+/*****
+ * APPLY A GIF FRAME OR DISPOSAL TO AN IMAGE DESTINATION
+ **/
+
+static void
+X(apply_frame)(palindex_type *dst, Gif_Stream* gfs, Gif_Image* gfi,
+ int replace, int save_uncompressed)
+{
+ int i, y, was_compressed = 0;
+ palindex_type map[256];
+ Gif_Colormap *colormap = gfi->local ? gfi->local : in_global_map;
+ Gif_OptBounds ob = safe_bounds(gfi);
+
+ if (!gfi->img) {
+ was_compressed = 1;
+ Gif_UncompressImage(gfs, gfi);
+ }
+
+ /* make sure transparency maps to TRANSP */
+ for (i = 0; i < colormap->ncol; i++)
+ map[i] = colormap->col[i].pixel;
+ /* out-of-bounds colors map to 0, for the sake of argument */
+ y = colormap->ncol ? colormap->col[0].pixel : 0;
+ for (i = colormap->ncol; i < 256; i++)
+ map[i] = y;
+ if (gfi->transparent >= 0 && gfi->transparent < 256)
+ map[gfi->transparent] = TRANSP;
+ else
+ replace = 1;
+
+ /* map the image */
+ dst += ob.left + ob.top * (unsigned) screen_width;
+ for (y = 0; y < ob.height; y++) {
+ uint8_t *gfi_pointer = gfi->img[y];
+ int x;
+
+ if (replace)
+ for (x = 0; x < ob.width; x++)
+ dst[x] = map[gfi_pointer[x]];
+ else
+ for (x = 0; x < ob.width; x++) {
+ palindex_type new_pixel = map[gfi_pointer[x]];
+ if (new_pixel != TRANSP)
+ dst[x] = new_pixel;
+ }
+
+ dst += screen_width;
+ }
+
+ if (was_compressed && !save_uncompressed)
+ Gif_ReleaseUncompressedImage(gfi);
+}
+
+static void
+X(apply_frame_disposal)(palindex_type *into_data, palindex_type *from_data,
+ palindex_type *previous_data, Gif_Image *gfi)
+{
+ unsigned screen_size = (unsigned) screen_width * (unsigned) screen_height;
+ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS)
+ memcpy(into_data, previous_data, sizeof(palindex_type) * screen_size);
+ else {
+ memcpy(into_data, from_data, sizeof(palindex_type) * screen_size);
+ if (gfi->disposal == GIF_DISPOSAL_BACKGROUND)
+ X(erase_data_area)(into_data, gfi);
+ }
+}
+
+
+/*****
+ * FIND THE SMALLEST BOUNDING RECTANGLE ENCLOSING ALL CHANGES
+ **/
+
+/* find_difference_bounds: Find the smallest rectangular area containing all
+ the changes and store it in 'bounds'. */
+
+static void
+X(find_difference_bounds)(Gif_OptData *bounds, Gif_Image *gfi, Gif_Image *last)
+{
+ int lf, rt, lf_min, rt_max, tp, bt, x, y;
+
+ /* 1.Aug.99 - use current bounds if possible, since this function is a speed
+ bottleneck */
+ if (!last
+ || last->disposal == GIF_DISPOSAL_NONE
+ || last->disposal == GIF_DISPOSAL_ASIS) {
+ Gif_OptBounds ob = safe_bounds(gfi);
+ lf_min = ob.left;
+ rt_max = ob.left + ob.width - 1;
+ tp = ob.top;
+ bt = ob.top + ob.height - 1;
+ } else {
+ lf_min = 0;
+ rt_max = screen_width - 1;
+ tp = 0;
+ bt = screen_height - 1;
+ }
+
+ for (; tp < screen_height; tp++)
+ if (memcmp(X(last_data) + (unsigned) screen_width * tp,
+ X(this_data) + (unsigned) screen_width * tp,
+ screen_width * sizeof(palindex_type)) != 0)
+ break;
+ for (; bt >= tp; bt--)
+ if (memcmp(X(last_data) + (unsigned) screen_width * bt,
+ X(this_data) + (unsigned) screen_width * bt,
+ screen_width * sizeof(palindex_type)) != 0)
+ break;
+
+ lf = screen_width;
+ rt = 0;
+ for (y = tp; y <= bt; y++) {
+ palindex_type *ld = X(last_data) + (unsigned) screen_width * y;
+ palindex_type *td = X(this_data) + (unsigned) screen_width * y;
+ for (x = lf_min; x < lf; x++)
+ if (ld[x] != td[x])
+ break;
+ lf = x;
+
+ for (x = rt_max; x > rt; x--)
+ if (ld[x] != td[x])
+ break;
+ rt = x;
+ }
+
+ /* 19.Aug.1999 - handle case when there's no difference between frames */
+ if (tp > bt) {
+ tp = bt = constrain(0, gfi->top, screen_height - 1);
+ lf = rt = constrain(0, gfi->left, screen_width - 1);
+ }
+
+ bounds->left = lf;
+ bounds->top = tp;
+ bounds->width = rt + 1 - lf;
+ bounds->height = bt + 1 - tp;
+}
+
+
+/* expand_difference_bounds: If the current image has background disposal and
+ the background is transparent, we must expand the difference bounds to
+ include any blanked (newly transparent) pixels that are still transparent
+ in the next image. This function does that by comparing this_data and
+ next_data. The new bounds are passed and stored in 'bounds'; the image's
+ old bounds, which are also the maximum bounds, are passed in
+ 'this_bounds'. */
+
+static int
+X(expand_difference_bounds)(Gif_OptData *bounds, Gif_Image *this_bounds)
+{
+ int x, y, expanded = 0;
+ Gif_OptBounds ob = safe_bounds(this_bounds);
+
+ if (bounds->width <= 0 || bounds->height <= 0) {
+ bounds->left = bounds->top = 0;
+ bounds->width = screen_width;
+ bounds->height = screen_height;
+ }
+
+ /* 20.Nov.2013 - The image `bounds` might be larger than `this_bounds`
+ because of a previous frame's background disposal. Don't accidentally
+ shrink `this_bounds`. */
+ if (ob.left > bounds->left) {
+ ob.width = (ob.left + ob.width) - bounds->left;
+ ob.left = bounds->left;
+ }
+ if (ob.top > bounds->top) {
+ ob.height = (ob.top + ob.height) - bounds->top;
+ ob.top = bounds->top;
+ }
+ if (ob.left + ob.width < bounds->left + bounds->width)
+ ob.width = bounds->left + bounds->width - ob.left;
+ if (ob.top + ob.height < bounds->top + bounds->height)
+ ob.height = bounds->top + bounds->height - ob.top;
+
+ for (; ob.top < bounds->top; ++ob.top, --ob.height) {
+ palindex_type *now = X(this_data) + (unsigned) screen_width * ob.top;
+ palindex_type *next = X(next_data) + (unsigned) screen_width * ob.top;
+ for (x = ob.left; x < ob.left + ob.width; ++x)
+ if (now[x] != TRANSP && next[x] == TRANSP) {
+ expanded = 1;
+ goto found_top;
+ }
+ }
+
+ found_top:
+ for (; ob.top + ob.height > bounds->top + bounds->height; --ob.height) {
+ palindex_type *now = X(this_data) + (unsigned) screen_width * (ob.top + ob.height - 1);
+ palindex_type *next = X(next_data) + (unsigned) screen_width * (ob.top + ob.height - 1);
+ for (x = ob.left; x < ob.left + ob.width; ++x)
+ if (now[x] != TRANSP && next[x] == TRANSP) {
+ expanded = 1;
+ goto found_bottom;
+ }
+ }
+
+ found_bottom:
+ for (; ob.left < bounds->left; ++ob.left, --ob.width) {
+ palindex_type *now = X(this_data) + ob.left;
+ palindex_type *next = X(next_data) + ob.left;
+ for (y = ob.top; y < ob.top + ob.height; ++y)
+ if (now[y * (unsigned) screen_width] != TRANSP
+ && next[y * (unsigned) screen_width] == TRANSP) {
+ expanded = 1;
+ goto found_left;
+ }
+ }
+
+ found_left:
+ for (; ob.left + ob.width > bounds->left + bounds->width; --ob.width) {
+ palindex_type *now = X(this_data) + ob.left + ob.width - 1;
+ palindex_type *next = X(next_data) + ob.left + ob.width - 1;
+ for (y = ob.top; y < ob.top + ob.height; ++y)
+ if (now[y * (unsigned) screen_width] != TRANSP
+ && next[y * (unsigned) screen_width] == TRANSP) {
+ expanded = 1;
+ goto found_right;
+ }
+ }
+
+ found_right:
+ if (!expanded)
+ for (y = ob.top; y < ob.top + ob.height; ++y) {
+ palindex_type *now = X(this_data) + y * (unsigned) screen_width;
+ palindex_type *next = X(next_data) + y * (unsigned) screen_width;
+ for (x = ob.left; x < ob.left + ob.width; ++x)
+ if (now[x] != TRANSP && next[x] == TRANSP) {
+ expanded = 1;
+ break;
+ }
+ }
+
+ bounds->left = ob.left;
+ bounds->top = ob.top;
+ bounds->width = ob.width;
+ bounds->height = ob.height;
+ return expanded;
+}
+
+
+/*****
+ * DETERMINE WHICH COLORS ARE USED
+ **/
+
+/* get_used_colors: mark which colors are needed by a given image. Returns a
+ need array so that need[j] == REQUIRED if the output colormap must
+ include all_color j; REPLACE_TRANSP if it should be replaced by
+ transparency; and 0 if it's not in the image at all.
+
+ If use_transparency > 0, then a pixel which was the same in the last frame
+ may be replaced with transparency. If use_transparency == 2, transparency
+ MUST be set. (This happens on the first image if the background should be
+ transparent.) */
+
+static void
+X(get_used_colors)(Gif_OptData *bounds, int use_transparency)
+{
+ int top = bounds->top, width = bounds->width, height = bounds->height;
+ int i, x, y;
+ int all_ncol = all_colormap->ncol;
+ uint8_t *need = Gif_NewArray(uint8_t, all_ncol);
+
+ for (i = 0; i < all_ncol; i++)
+ need[i] = 0;
+
+ /* set elements that are in the image. need == 2 means the color
+ must be in the map; need == 1 means the color may be replaced by
+ transparency. */
+ for (y = top; y < top + height; y++) {
+ palindex_type *data = X(this_data) + (unsigned) screen_width * y + bounds->left;
+ palindex_type *last = X(last_data) + (unsigned) screen_width * y + bounds->left;
+ for (x = 0; x < width; x++) {
+ if (data[x] != last[x])
+ need[data[x]] = REQUIRED;
+ else if (need[data[x]] == 0)
+ need[data[x]] = REPLACE_TRANSP;
+ }
+ }
+ if (need[TRANSP])
+ need[TRANSP] = REQUIRED;
+
+ /* check for too many colors; also force transparency if needed */
+ {
+ int count[3];
+ /* Count distinct pixels in each category */
+ count[0] = count[1] = count[2] = 0;
+ for (i = 0; i < all_ncol; i++)
+ count[need[i]]++;
+ /* If use_transparency is large and there's room, add transparency */
+ if (use_transparency > 1 && !need[TRANSP] && count[REQUIRED] < 256) {
+ need[TRANSP] = REQUIRED;
+ count[REQUIRED]++;
+ }
+ /* If too many "potentially transparent" pixels, force transparency */
+ if (count[REPLACE_TRANSP] + count[REQUIRED] > 256)
+ use_transparency = 1;
+ /* Make sure transparency is marked necessary if we use it */
+ if (count[REPLACE_TRANSP] > 0 && use_transparency && !need[TRANSP]) {
+ need[TRANSP] = REQUIRED;
+ count[REQUIRED]++;
+ }
+ /* If not using transparency, change "potentially transparent" pixels to
+ "actually used" pixels */
+ if (!use_transparency) {
+ for (i = 0; i < all_ncol; i++)
+ if (need[i] == REPLACE_TRANSP)
+ need[i] = REQUIRED;
+ count[REQUIRED] += count[REPLACE_TRANSP];
+ }
+ /* If we can afford to have transparency, and we want to use it, then
+ include it */
+ if (count[REQUIRED] < 256 && use_transparency && !need[TRANSP]) {
+ need[TRANSP] = REQUIRED;
+ count[REQUIRED]++;
+ }
+ bounds->required_color_count = count[REQUIRED];
+ }
+
+ bounds->needed_colors = need;
+}
+
+
+/*****
+ * FIND SUBIMAGES AND COLORS USED
+ **/
+
+static void
+X(create_subimages)(Gif_Stream *gfs, int optimize_flags, int save_uncompressed)
+{
+ unsigned screen_size;
+ Gif_Image *last_gfi;
+ int next_data_valid;
+ palindex_type *previous_data = 0;
+ int local_color_tables = 0;
+
+ screen_size = (unsigned) screen_width * (unsigned) screen_height;
+
+ X(last_data) = Gif_NewArray(palindex_type, screen_size);
+ X(this_data) = Gif_NewArray(palindex_type, screen_size);
+ X(next_data) = Gif_NewArray(palindex_type, screen_size);
+ next_data_valid = 0;
+
+ /* do first image. Remember to uncompress it if necessary */
+ X(erase_screen)(X(last_data));
+ X(erase_screen)(X(this_data));
+ last_gfi = 0;
+
+ /* PRECONDITION:
+ previous_data -- garbage
+ last_data -- optimized image after disposal of previous optimized frame
+ this_data -- input image after disposal of previous input frame
+ next_data -- input image after application of current input frame,
+ if next_image_valid */
+ for (image_index = 0; image_index < gfs->nimages; image_index++) {
+ Gif_Image *gfi = gfs->images[image_index];
+ Gif_OptData *subimage = new_opt_data();
+ if (gfi->local)
+ local_color_tables = 1;
+
+ /* save previous data if necessary */
+ if (gfi->disposal == GIF_DISPOSAL_PREVIOUS
+ || (local_color_tables && image_index > 0
+ && last_gfi->disposal > GIF_DISPOSAL_ASIS)) {
+ if (!previous_data)
+ previous_data = Gif_NewArray(palindex_type, screen_size);
+ memcpy(previous_data, X(this_data),
+ sizeof(palindex_type) * screen_size);
+ }
+
+ /* set this_data equal to the current image */
+ if (next_data_valid) {
+ palindex_type *temp = X(this_data);
+ X(this_data) = X(next_data);
+ X(next_data) = temp;
+ next_data_valid = 0;
+ } else
+ X(apply_frame)(X(this_data), gfs, gfi, 0, save_uncompressed);
+
+ retry_frame:
+ /* find minimum area of difference between this image and last image */
+ subimage->disposal = GIF_DISPOSAL_ASIS;
+ if (image_index > 0) {
+ X(find_difference_bounds)(subimage, gfi, last_gfi);
+ } else {
+ Gif_OptBounds ob = safe_bounds(gfi);
+ subimage->left = ob.left;
+ subimage->top = ob.top;
+ subimage->width = ob.width;
+ subimage->height = ob.height;
+ }
+
+ /* might need to expand difference border on background disposal */
+ if ((gfi->disposal == GIF_DISPOSAL_BACKGROUND
+ || gfi->disposal == GIF_DISPOSAL_PREVIOUS)
+ && image_index < gfs->nimages - 1) {
+ /* set up next_data */
+ Gif_Image *next_gfi = gfs->images[image_index + 1];
+ X(apply_frame_disposal)(X(next_data), X(this_data), previous_data, gfi);
+ X(apply_frame)(X(next_data), gfs, next_gfi, 0, save_uncompressed);
+ next_data_valid = 1;
+ /* expand border as necessary */
+ if (X(expand_difference_bounds)(subimage, gfi))
+ subimage->disposal = GIF_DISPOSAL_BACKGROUND;
+ }
+
+ fix_difference_bounds(subimage);
+
+ /* set map of used colors */
+ {
+ int use_transparency = (optimize_flags & GT_OPT_MASK) > 1
+ && image_index > 0;
+ if (image_index == 0 && background == TRANSP)
+ use_transparency = 2;
+ X(get_used_colors)(subimage, use_transparency);
+ /* Gifsicle's optimization strategy normally creates frames with ASIS
+ or BACKGROUND disposal (not PREVIOUS disposal). However, there are
+ cases when PREVIOUS disposal is strictly required, or a frame would
+ require more than 256 colors. Detect this case and try to recover. */
+ if (subimage->required_color_count > 256) {
+ if (image_index > 0 && local_color_tables) {
+ Gif_OptData *subimage = (Gif_OptData*) last_gfi->user_data;
+ if ((last_gfi->disposal == GIF_DISPOSAL_PREVIOUS
+ || last_gfi->disposal == GIF_DISPOSAL_BACKGROUND)
+ && subimage->disposal != last_gfi->disposal) {
+ subimage->disposal = last_gfi->disposal;
+ memcpy(X(last_data), previous_data, sizeof(palindex_type) * screen_size);
+ goto retry_frame;
+ }
+ }
+ fatal_error("%d colors required in a frame (256 is max)",
+ subimage->required_color_count);
+ }
+ }
+
+ gfi->user_data = subimage;
+ last_gfi = gfi;
+
+ /* Apply optimized disposal to last_data and unoptimized disposal to
+ this_data. Before 9.Dec.1998 I applied unoptimized disposal uniformly
+ to both. This led to subtle bugs. After all, to determine bounds, we
+ want to compare the current image (only obtainable through unoptimized
+ disposal) with what WILL be left after the previous OPTIMIZED image's
+ disposal. This fix is repeated in create_new_image_data */
+ if (subimage->disposal == GIF_DISPOSAL_BACKGROUND)
+ X(erase_data_area_subimage)(X(last_data), subimage);
+ else
+ X(copy_data_area_subimage)(X(last_data), X(this_data), subimage);
+
+ if (last_gfi->disposal == GIF_DISPOSAL_BACKGROUND)
+ X(erase_data_area)(X(this_data), last_gfi);
+ else if (last_gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
+ palindex_type *temp = previous_data;
+ previous_data = X(this_data);
+ X(this_data) = temp;
+ }
+ }
+
+ Gif_DeleteArray(X(next_data));
+ if (previous_data)
+ Gif_DeleteArray(previous_data);
+}
+
+
+/*****
+ * CALCULATE OUTPUT GLOBAL COLORMAP
+ **/
+
+/* create_out_global_map: The interface function to this pass. It creates
+ out_global_map and sets pixel values on all_colormap appropriately.
+ Specifically:
+
+ all_colormap->col[P].pixel >= 256 ==> P is not in the global colormap.
+
+ Otherwise, all_colormap->col[P].pixel == the J so that
+ GIF_COLOREQ(&all_colormap->col[P], &out_global_map->col[J]).
+
+ On return, the 'colormap_penalty' component of an image's Gif_OptData
+ structure is <0 iff that image will need a local colormap.
+
+ 20.Aug.1999 - updated to new version that arranges the entire colormap, not
+ just the stuff above 256 colors. */
+
+static void
+X(create_out_global_map)(Gif_Stream *gfs)
+{
+ int all_ncol = all_colormap->ncol;
+ penalty_type *penalty = Gif_NewArray(penalty_type, all_ncol);
+ palindex_type *permute = Gif_NewArray(palindex_type, all_ncol);
+ palindex_type *ordering = Gif_NewArray(palindex_type, all_ncol);
+ int cur_ncol, i, imagei;
+ int nglobal_all = (all_ncol <= 257 ? all_ncol - 1 : 256);
+ int permutation_changed;
+ assert(all_ncol <= 0x7FFFFFFF);
+
+ /* initial permutation is null */
+ for (i = 0; i < all_ncol - 1; i++)
+ permute[i] = i + 1;
+
+ /* choose appropriate penalties for each image */
+ for (imagei = 0; imagei < gfs->nimages; imagei++) {
+ Gif_OptData *opt = (Gif_OptData *)gfs->images[imagei]->user_data;
+ palindex_type pi;
+ opt->global_penalty = opt->colormap_penalty = 1;
+ for (pi = 2; pi < opt->required_color_count; pi *= 2)
+ opt->colormap_penalty *= 3;
+ opt->active_penalty =
+ (all_ncol > 257 ? opt->colormap_penalty : opt->global_penalty);
+ }
+
+ /* set initial penalties for each color */
+ for (i = 1; i < all_ncol; i++)
+ penalty[i] = 0;
+ for (imagei = 0; imagei < gfs->nimages; imagei++) {
+ Gif_OptData *opt = (Gif_OptData *)gfs->images[imagei]->user_data;
+ increment_penalties(opt, penalty, opt->active_penalty);
+ }
+ permutation_changed = 1;
+
+ /* Loop, removing one color at a time. */
+ for (cur_ncol = all_ncol - 1; cur_ncol; cur_ncol--) {
+ palindex_type removed;
+
+ /* sort permutation based on penalty */
+ if (permutation_changed)
+ X(sort_permutation)(permute, cur_ncol, penalty, 1);
+ permutation_changed = 0;
+
+ /* update reverse permutation */
+ removed = permute[cur_ncol - 1];
+ ordering[removed] = cur_ncol - 1;
+
+ /* decrement penalties for colors that are out of the running */
+ for (imagei = 0; imagei < gfs->nimages; imagei++) {
+ Gif_OptData *opt = (Gif_OptData *)gfs->images[imagei]->user_data;
+ uint8_t *need = opt->needed_colors;
+ if (opt->global_penalty > 0 && need[removed] == REQUIRED) {
+ increment_penalties(opt, penalty, -opt->active_penalty);
+ opt->global_penalty = 0;
+ opt->colormap_penalty = (cur_ncol > 256 ? -1 : 0);
+ permutation_changed = 1;
+ }
+ }
+
+ /* change colormap penalties if we're no longer working w/globalmap */
+ if (cur_ncol == 257) {
+ for (i = 0; i < all_ncol; i++)
+ penalty[i] = 0;
+ for (imagei = 0; imagei < gfs->nimages; imagei++) {
+ Gif_OptData *opt = (Gif_OptData *)gfs->images[imagei]->user_data;
+ opt->active_penalty = opt->global_penalty;
+ increment_penalties(opt, penalty, opt->global_penalty);
+ }
+ permutation_changed = 1;
+ }
+ }
+
+ /* make sure background is in the global colormap */
+ if (background != TRANSP && ordering[background] >= 256) {
+ palindex_type other = permute[255];
+ ordering[other] = ordering[background];
+ ordering[background] = 255;
+ }
+
+ /* assign out_global_map based on permutation */
+ out_global_map = Gif_NewFullColormap(nglobal_all, 256);
+
+ for (i = 1; i < all_ncol; i++)
+ if (ordering[i] < 256) {
+ out_global_map->col[ordering[i]] = all_colormap->col[i];
+ all_colormap->col[i].pixel = ordering[i];
+ } else
+ all_colormap->col[i].pixel = NOT_IN_OUT_GLOBAL;
+
+ /* set the stream's background color */
+ if (background != TRANSP)
+ gfs->background = ordering[background];
+
+ /* cleanup */
+ Gif_DeleteArray(penalty);
+ Gif_DeleteArray(permute);
+ Gif_DeleteArray(ordering);
+}
+
+
+/*****
+ * CREATE OUTPUT FRAME DATA
+ **/
+
+/* simple_frame_data: just copy the data from the image into the frame data.
+ No funkiness, no transparency, nothing */
+
+static void
+X(simple_frame_data)(Gif_Image *gfi, uint8_t *map)
+{
+ Gif_OptBounds ob = safe_bounds(gfi);
+ int x, y;
+ unsigned scan_width = gfi->width;
+
+ for (y = 0; y < ob.height; y++) {
+ palindex_type *from = X(this_data) + (unsigned) screen_width * (y + ob.top) + ob.left;
+ uint8_t *into = gfi->image_data + y * scan_width;
+ for (x = 0; x < ob.width; x++)
+ *into++ = map[*from++];
+ }
+}
+
+
+/* transp_frame_data: copy the frame data into the actual image, using
+ transparency occasionally according to a heuristic described below */
+
+static void
+X(transp_frame_data)(Gif_Stream *gfs, Gif_Image *gfi, uint8_t *map,
+ int optimize_flags, Gif_CompressInfo *gcinfo)
+{
+ Gif_OptBounds ob = safe_bounds(gfi);
+ int x, y, transparent = gfi->transparent;
+ palindex_type *last = 0;
+ palindex_type *cur = 0;
+ uint8_t *data, *begin_same;
+ uint8_t *t2_data = 0, *last_for_t2;
+ int nsame;
+
+ /* First, try w/o transparency. Compare this to the result using
+ transparency and pick the better of the two. */
+ X(simple_frame_data)(gfi, map);
+ Gif_FullCompressImage(gfs, gfi, gcinfo);
+ gcinfo->flags |= GIF_WRITE_SHRINK;
+
+ /* Actually copy data to frame.
+
+ Use transparency if possible to shrink the size of the written GIF.
+
+ The written GIF will be small if patterns (sequences of pixel values)
+ recur in the image.
+ We could conceivably use transparency to produce THE OPTIMAL image,
+ with the most recurring patterns of the best kinds; but this would
+ be very hard (wouldn't it?). Instead, we settle for a heuristic:
+ we try and create RUNS. (Since we *try* to create them, they will
+ presumably recur!) A RUN is a series of adjacent pixels all with the
+ same value.
+
+ By & large, we just use the regular image's values. However, we might
+ create a transparent run *not in* the regular image, if TWO OR MORE
+ adjacent runs OF DIFFERENT COLORS *could* be made transparent.
+
+ (An area can be made transparent if the corresponding area in the previous
+ frame had the same colors as the area does now.)
+
+ Why? If only one run (say of color C) could be transparent, we get no
+ large immediate advantage from making it transparent (it'll be a run of
+ the same length regardless). Also, we might LOSE: what if the run was
+ adjacent to some more of color C, which couldn't be made transparent? If
+ we use color C (instead of the transparent color), then we get a longer
+ run.
+
+ This simple heuristic does a little better than Gifwizard's (6/97)
+ on some images, but does *worse than nothing at all* on others.
+
+ However, it DOES do better than the complicated, greedy algorithm that
+ preceded it; and now we pick either the transparency-optimized version or
+ the normal version, whichever compresses smaller, for the best of both
+ worlds. (9/98)
+
+ On several images, making SINGLE color runs transparent wins over the
+ previous heuristic, so try both at optimize level 3 or above (the cost is
+ ~30%). (2/11) */
+
+ data = begin_same = last_for_t2 = gfi->image_data;
+ nsame = 0;
+
+ for (y = 0; y < ob.height; ++y) {
+ last = X(last_data) + (unsigned) screen_width * (y + ob.top) + ob.left;
+ cur = X(this_data) + (unsigned) screen_width * (y + ob.top) + ob.left;
+ for (x = 0; x < ob.width; ++x) {
+ if (*cur != *last && map[*cur] != transparent) {
+ if (nsame == 1 && data[-1] != transparent
+ && (optimize_flags & GT_OPT_MASK) > 2) {
+ if (!t2_data)
+ t2_data = Gif_NewArray(uint8_t, (size_t) ob.width * (size_t) ob.height);
+ memcpy(t2_data + (last_for_t2 - gfi->image_data),
+ last_for_t2, begin_same - last_for_t2);
+ memset(t2_data + (begin_same - gfi->image_data),
+ transparent, data - begin_same);
+ last_for_t2 = data;
+ }
+ nsame = 0;
+ } else if (nsame == 0) {
+ begin_same = data;
+ ++nsame;
+ } else if (nsame == 1 && map[*cur] != data[-1]) {
+ memset(begin_same, transparent, data - begin_same);
+ ++nsame;
+ }
+ if (nsame > 1)
+ *data = transparent;
+ else
+ *data = map[*cur];
+ ++data, ++cur, ++last;
+ }
+ }
+
+ if (t2_data)
+ memcpy(t2_data + (last_for_t2 - gfi->image_data),
+ last_for_t2, data - last_for_t2);
+
+
+ /* Now, try compressed transparent version(s) and pick the better of the
+ two (or three). */
+ Gif_FullCompressImage(gfs, gfi, gcinfo);
+ if (t2_data) {
+ Gif_SetUncompressedImage(gfi, t2_data, Gif_Free, 0);
+ Gif_FullCompressImage(gfs, gfi, gcinfo);
+ }
+ Gif_ReleaseUncompressedImage(gfi);
+
+ gcinfo->flags &= ~GIF_WRITE_SHRINK;
+}
+
+
+/*****
+ * CREATE NEW IMAGE DATA
+ **/
+
+/* last == what last image ended up looking like
+ this == what new image should look like
+
+ last = apply O1 + dispose O1 + ... + apply On-1 + dispose On-1
+ this = apply U1 + dispose U1 + ... + apply Un-1 + dispose Un-1 + apply Un
+
+ invariant: apply O1 + dispose O1 + ... + apply Ok
+ === apply U1 + dispose U1 + ... + apply Uk */
+
+static void
+X(create_new_image_data)(Gif_Stream *gfs, int optimize_flags)
+{
+ Gif_Image cur_unopt_gfi; /* placeholder; maintains pre-optimization
+ image size so we can apply background
+ disposal */
+ unsigned screen_size = (unsigned) screen_width * (unsigned) screen_height;
+ palindex_type *previous_data = 0;
+ Gif_CompressInfo gcinfo = gif_write_info;
+ if ((optimize_flags & GT_OPT_MASK) >= 3)
+ gcinfo.flags |= GIF_WRITE_OPTIMIZE;
+
+ gfs->global = out_global_map;
+
+ /* do first image. Remember to uncompress it if necessary */
+ X(erase_screen)(X(last_data));
+ X(erase_screen)(X(this_data));
+
+ for (image_index = 0; image_index < gfs->nimages; image_index++) {
+ Gif_Image *cur_gfi = gfs->images[image_index];
+ Gif_OptData *opt = (Gif_OptData *)cur_gfi->user_data;
+ int was_compressed = (cur_gfi->img == 0);
+
+ /* save previous data if necessary */
+ if (cur_gfi->disposal == GIF_DISPOSAL_PREVIOUS) {
+ if (!previous_data)
+ previous_data = Gif_NewArray(palindex_type, screen_size);
+ X(copy_data_area)(previous_data, X(this_data), cur_gfi);
+ }
+
+ /* set up this_data to be equal to the current image */
+ X(apply_frame)(X(this_data), gfs, cur_gfi, 0, 0);
+
+ /* save actual bounds and disposal from unoptimized version so we can
+ apply the disposal correctly next time through */
+ cur_unopt_gfi = *cur_gfi;
+
+ /* set bounds and disposal from optdata */
+ Gif_ReleaseUncompressedImage(cur_gfi);
+ cur_gfi->left = opt->left;
+ cur_gfi->top = opt->top;
+ cur_gfi->width = opt->width;
+ cur_gfi->height = opt->height;
+ cur_gfi->disposal = opt->disposal;
+ if (image_index > 0)
+ cur_gfi->interlace = 0;
+
+ /* find the new image's colormap and then make new data */
+ {
+ uint8_t *map = prepare_colormap(cur_gfi, opt->needed_colors);
+ uint8_t *data = Gif_NewArray(uint8_t, (size_t) cur_gfi->width * (size_t) cur_gfi->height);
+ Gif_SetUncompressedImage(cur_gfi, data, Gif_Free, 0);
+
+ /* don't use transparency on first frame */
+ if ((optimize_flags & GT_OPT_MASK) > 1 && image_index > 0
+ && cur_gfi->transparent >= 0)
+ X(transp_frame_data)(gfs, cur_gfi, map, optimize_flags, &gcinfo);
+ else
+ X(simple_frame_data)(cur_gfi, map);
+
+ if (cur_gfi->img) {
+ if (was_compressed || (optimize_flags & GT_OPT_MASK) > 1) {
+ Gif_FullCompressImage(gfs, cur_gfi, &gcinfo);
+ Gif_ReleaseUncompressedImage(cur_gfi);
+ } else /* bug fix 22.May.2001 */
+ Gif_ReleaseCompressedImage(cur_gfi);
+ }
+
+ Gif_DeleteArray(map);
+ }
+
+ delete_opt_data(opt);
+ cur_gfi->user_data = 0;
+
+ /* Set up last_data and this_data. last_data must contain this_data + new
+ disposal. this_data must contain this_data + old disposal. */
+ if (cur_gfi->disposal == GIF_DISPOSAL_NONE
+ || cur_gfi->disposal == GIF_DISPOSAL_ASIS)
+ X(copy_data_area)(X(last_data), X(this_data), cur_gfi);
+ else if (cur_gfi->disposal == GIF_DISPOSAL_BACKGROUND)
+ X(erase_data_area)(X(last_data), cur_gfi);
+ else if (cur_gfi->disposal != GIF_DISPOSAL_PREVIOUS)
+ assert(0 && "optimized frame has strange disposal");
+
+ if (cur_unopt_gfi.disposal == GIF_DISPOSAL_BACKGROUND)
+ X(erase_data_area)(X(this_data), &cur_unopt_gfi);
+ else if (cur_unopt_gfi.disposal == GIF_DISPOSAL_PREVIOUS)
+ X(copy_data_area)(X(this_data), previous_data, &cur_unopt_gfi);
+ }
+
+ if (previous_data)
+ Gif_DeleteArray(previous_data);
+}
+
+
+/*****
+ * INITIALIZATION AND FINALIZATION
+ **/
+
+static void
+X(finalize_optimizer_data)(void) {
+ Gif_DeleteArray(X(last_data));
+ Gif_DeleteArray(X(this_data));
+}
diff --git a/packages/optimizt-gifsicle/upstream/gifsicle/src/quantize.c b/packages/optimizt-gifsicle/upstream/gifsicle/src/quantize.c
new file mode 100644
index 0000000..b3bff1a
--- /dev/null
+++ b/packages/optimizt-gifsicle/upstream/gifsicle/src/quantize.c
@@ -0,0 +1,1477 @@
+/* quantize.c - Histograms and quantization for gifsicle.
+ Copyright (C) 1997-2025 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