/****************************************************************************
giftool.c - GIF transformation tool.
****************************************************************************/
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright (C) Eric S. Raymond <esr@thyrsus.com>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "getarg.h"
#include "getopt.h"
#include "gif_lib.h"
#define PROGRAM_NAME "giftool"
#define MAX_OPERATIONS 256
#define MAX_IMAGES 2048
enum boolmode { numeric, onoff, tf, yesno };
char *putbool(bool flag, enum boolmode mode) {
if (flag) {
switch (mode) {
case numeric:
return "1";
break;
case onoff:
return "on";
break;
case tf:
return "true";
break;
case yesno:
return "yes";
break;
}
} else {
switch (mode) {
case numeric:
return "0";
break;
case onoff:
return "off";
break;
case tf:
return "false";
break;
case yesno:
return "no";
break;
}
}
return "FAIL"; /* should never happen */
}
bool getbool(char *from) {
struct valmap {
char *name;
bool val;
} boolnames[] =
{
{"yes", true}, {"on", true}, {"1", true},
{"t", true}, {"no", false}, {"off", false},
{"0", false}, {"f", false}, {NULL, false},
},
*sp;
// cppcheck-suppress nullPointerRedundantCheck
for (sp = boolnames; sp->name; sp++) {
if (strcmp(sp->name, from) == 0) {
return sp->val;
}
}
if (sp == NULL) {
(void)fprintf(stderr,
"giftool: %s is not a valid boolean argument.\n",
// cppcheck-suppress nullPointerRedundantCheck
sp->name);
}
exit(EXIT_FAILURE);
}
struct operation {
enum {
aspect,
delaytime,
background,
info,
interlace,
position,
screensize,
transparent,
userinput,
disposal,
} mode;
union {
GifByteType numerator;
int delay;
int color;
int dispose;
char *format;
bool flag;
struct {
int x, y;
} p;
};
};
int main(int argc, char **argv) {
extern char *optarg; /* set by getopt */
extern int optind; /* set by getopt */
struct operation operations[MAX_OPERATIONS];
struct operation *top = operations;
int selected[MAX_IMAGES], nselected = 0;
bool have_selection = false;
char *cp;
int i, status, ErrorCode;
GifFileType *GifFileIn, *GifFileOut = (GifFileType *)NULL;
struct operation *op;
/*
* Gather operations from the command line. We use regular
* getopt(3) here rather than Gershom's argument getter because
* preserving the order of operations is important.
*/
while ((status = getopt(argc, argv, "a:b:d:f:i:n:p:s:u:x:")) != EOF) {
if (top >= operations + MAX_OPERATIONS) {
(void)fprintf(stderr, "giftool: too many operations.");
exit(EXIT_FAILURE);
}
switch (status) {
case 'a':
top->mode = aspect;
top->numerator = (GifByteType)atoi(optarg);
break;
case 'b':
top->mode = background;
top->color = atoi(optarg);
break;
case 'd':
top->mode = delaytime;
top->delay = atoi(optarg);
break;
case 'f':
top->mode = info;
top->format = optarg;
break;
case 'i':
top->mode = interlace;
top->flag = getbool(optarg);
break;
case 'n':
have_selection = true;
nselected = 0;
cp = optarg;
for (;;) {
size_t span = strspn(cp, "0123456789");
if (span > 0) {
selected[nselected++] = atoi(cp) - 1;
cp += span;
if (*cp == '\0') {
break;
} else if (*cp == ',') {
continue;
}
}
(void)fprintf(stderr,
"giftool: bad selection.\n");
exit(EXIT_FAILURE);
}
break;
case 'p':
case 's':
if (status == 'p') {
top->mode = position;
} else {
top->mode = screensize;
}
cp = strchr(optarg, ',');
if (cp == NULL) {
(void)fprintf(stderr, "giftool: missing comma "
"in coordinate pair.\n");
exit(EXIT_FAILURE);
}
top->p.x = atoi(optarg);
top->p.y = atoi(cp + 1);
if (top->p.x < 0 || top->p.y < 0) {
(void)fprintf(
stderr, "giftool: negative coordinate.\n");
exit(EXIT_FAILURE);
}
break;
case 'u':
top->mode = userinput;
top->flag = getbool(optarg);
break;
case 'x':
top->mode = disposal;
top->dispose = atoi(optarg);
break;
default:
fprintf(stderr,
"usage: giftool [-b color] [-d delay] [-iI] "
"[-t color] -[uU] [-x disposal]\n");
break;
}
++top;
}
/* read in a GIF */
if ((GifFileIn = DGifOpenFileHandle(0, &ErrorCode)) == NULL) {
PrintGifError(ErrorCode);
exit(EXIT_FAILURE);
}
if (DGifSlurp(GifFileIn) == GIF_ERROR) {
PrintGifError(GifFileIn->Error);
exit(EXIT_FAILURE);
}
if ((GifFileOut = EGifOpenFileHandle(1, &ErrorCode)) == NULL) {
PrintGifError(ErrorCode);
exit(EXIT_FAILURE);
}
/* if the selection is defaulted, compute it; otherwise bounds-check it
*/
if (!have_selection) {
for (i = nselected = 0; i < GifFileIn->ImageCount; i++) {
selected[nselected++] = i;
}
} else {
for (i = 0; i < nselected; i++) {
if (selected[i] >= GifFileIn->ImageCount ||
selected[i] < 0) {
(void)fprintf(stderr, "giftool: selection "
"index out of bounds.\n");
exit(EXIT_FAILURE);
}
}
}
/* perform the operations we've gathered */
for (op = operations; op < top; op++) {
switch (op->mode) {
case background:
GifFileIn->SBackGroundColor = op->color;
break;
case delaytime:
for (i = 0; i < nselected; i++) {
GraphicsControlBlock gcb;
DGifSavedExtensionToGCB(GifFileIn, selected[i],
&gcb);
gcb.DelayTime = op->delay;
EGifGCBToSavedExtension(&gcb, GifFileIn,
selected[i]);
}
break;
case info:
for (i = 0; i < nselected; i++) {
SavedImage *ip =
&GifFileIn->SavedImages[selected[i]];
GraphicsControlBlock gcb;
for (cp = op->format; *cp; cp++) {
if (*cp == '\\') {
char c;
switch (*++cp) {
case 'b':
(void)putchar('\b');
break;
case 'e':
(void)putchar(0x1b);
break;
case 'f':
(void)putchar('\f');
break;
case 'n':
(void)putchar('\n');
break;
case 'r':
(void)putchar('\r');
break;
case 't':
(void)putchar('\t');
break;
case 'v':
(void)putchar('\v');
break;
case 'x':
switch (*++cp) {
case '0':
c = (char)0x00;
break;
case '1':
c = (char)0x10;
break;
case '2':
c = (char)0x20;
break;
case '3':
c = (char)0x30;
break;
case '4':
c = (char)0x40;
break;
case '5':
c = (char)0x50;
break;
case '6':
c = (char)0x60;
break;
case '7':
c = (char)0x70;
break;
case '8':
c = (char)0x80;
break;
case '9':
c = (char)0x90;
break;
case 'A':
case 'a':
c = (char)0xa0;
break;
case 'B':
case 'b':
c = (char)0xb0;
break;
case 'C':
case 'c':
c = (char)0xc0;
break;
case 'D':
case 'd':
c = (char)0xd0;
break;
case 'E':
case 'e':
c = (char)0xe0;
break;
case 'F':
case 'f':
c = (char)0xf0;
break;
default:
return -1;
}
switch (*++cp) {
case '0':
c += 0x00;
break;
case '1':
c += 0x01;
break;
case '2':
c += 0x02;
break;
case '3':
c += 0x03;
break;
case '4':
c += 0x04;
break;
case '5':
c += 0x05;
break;
case '6':
c += 0x06;
break;
case '7':
c += 0x07;
break;
case '8':
c += 0x08;
break;
case '9':
c += 0x09;
break;
case 'A':
case 'a':
c += 0x0a;
break;
case 'B':
case 'b':
c += 0x0b;
break;
case 'C':
case 'c':
c += 0x0c;
break;
case 'D':
case 'd':
c += 0x0d;
break;
case 'E':
case 'e':
c += 0x0e;
break;
case 'F':
case 'f':
c += 0x0f;
break;
default:
return -2;
}
putchar(c);
break;
default:
putchar(*cp);
break;
}
} else if (*cp == '%') {
enum boolmode boolfmt;
SavedImage *sp =
&GifFileIn->SavedImages[i];
if (cp[1] == 't') {
boolfmt = tf;
++cp;
} else if (cp[1] == 'o') {
boolfmt = onoff;
++cp;
} else if (cp[1] == 'y') {
boolfmt = yesno;
++cp;
} else if (cp[1] == '1') {
boolfmt = numeric;
++cp;
} else {
boolfmt = numeric;
}
switch (*++cp) {
case '%':
putchar('%');
break;
case 'a':
(void)printf(
"%d",
GifFileIn
->AspectByte);
break;
case 'b':
(void)printf(
"%d",
GifFileIn
->SBackGroundColor);
break;
case 'd':
DGifSavedExtensionToGCB(
GifFileIn,
selected[i], &gcb);
(void)printf(
"%d",
gcb.DelayTime);
break;
case 'h':
(void)printf(
"%d", ip->ImageDesc
.Height);
break;
case 'n':
(void)printf(
"%d",
selected[i] + 1);
break;
case 'p':
(void)printf(
"%d,%d",
ip->ImageDesc.Left,
ip->ImageDesc.Top);
break;
case 's':
(void)printf(
"%d,%d",
GifFileIn->SWidth,
GifFileIn->SHeight);
break;
case 'w':
(void)printf(
"%d", ip->ImageDesc
.Width);
break;
case 't':
DGifSavedExtensionToGCB(
GifFileIn,
selected[i], &gcb);
(void)printf(
"%d",
gcb.TransparentColor);
break;
case 'u':
DGifSavedExtensionToGCB(
GifFileIn,
selected[i], &gcb);
(void)printf(
"%s",
putbool(
gcb.UserInputFlag,
boolfmt));
break;
case 'v':
fputs(EGifGetGifVersion(
GifFileIn),
stdout);
break;
case 'x':
DGifSavedExtensionToGCB(
GifFileIn,
selected[i], &gcb);
(void)printf(
"%d",
gcb.DisposalMode);
break;
case 'z':
(void)printf(
"%s",
putbool(
sp->ImageDesc
.ColorMap &&
sp->ImageDesc
.ColorMap
->SortFlag,
boolfmt));
break;
default:
(void)fprintf(
stderr,
"giftool: bad "
"format %%%c\n",
*cp);
}
} else {
(void)putchar(*cp);
}
}
}
exit(EXIT_SUCCESS);
break;
case interlace:
for (i = 0; i < nselected; i++) {
GifFileIn->SavedImages[selected[i]]
.ImageDesc.Interlace = op->flag;
}
break;
case position:
for (i = 0; i < nselected; i++) {
GifFileIn->SavedImages[selected[i]]
.ImageDesc.Left = op->p.x;
GifFileIn->SavedImages[selected[i]]
.ImageDesc.Top = op->p.y;
}
break;
case screensize:
GifFileIn->SWidth = op->p.x;
GifFileIn->SHeight = op->p.y;
break;
case transparent:
for (i = 0; i < nselected; i++) {
GraphicsControlBlock gcb;
DGifSavedExtensionToGCB(GifFileIn, selected[i],
&gcb);
gcb.TransparentColor = op->color;
EGifGCBToSavedExtension(&gcb, GifFileIn,
selected[i]);
}
break;
case userinput:
for (i = 0; i < nselected; i++) {
GraphicsControlBlock gcb;
DGifSavedExtensionToGCB(GifFileIn, selected[i],
&gcb);
gcb.UserInputFlag = op->flag;
EGifGCBToSavedExtension(&gcb, GifFileIn,
selected[i]);
}
break;
case disposal:
for (i = 0; i < nselected; i++) {
GraphicsControlBlock gcb;
DGifSavedExtensionToGCB(GifFileIn, selected[i],
&gcb);
gcb.DisposalMode = op->dispose;
EGifGCBToSavedExtension(&gcb, GifFileIn,
selected[i]);
}
break;
default:
(void)fprintf(stderr,
"giftool: unknown operation mode\n");
exit(EXIT_FAILURE);
}
}
/* write out the results */
GifFileOut->SWidth = GifFileIn->SWidth;
GifFileOut->SHeight = GifFileIn->SHeight;
GifFileOut->SColorResolution = GifFileIn->SColorResolution;
GifFileOut->SBackGroundColor = GifFileIn->SBackGroundColor;
if (GifFileIn->SColorMap != NULL) {
GifFileOut->SColorMap =
GifMakeMapObject(GifFileIn->SColorMap->ColorCount,
GifFileIn->SColorMap->Colors);
}
for (i = 0; i < GifFileIn->ImageCount; i++) {
(void)GifMakeSavedImage(GifFileOut, &GifFileIn->SavedImages[i]);
}
if (EGifSpew(GifFileOut) == GIF_ERROR) {
PrintGifError(GifFileOut->Error);
} else if (DGifCloseFile(GifFileIn, &ErrorCode) == GIF_ERROR) {
PrintGifError(ErrorCode);
}
return 0;
}
/* end */