651 lines
13 KiB
C
651 lines
13 KiB
C
/****************************************************************************
|
|
|
|
giftool.c - GIF transformation tool.
|
|
|
|
SPDX-License-Identifier: MIT
|
|
|
|
****************************************************************************/
|
|
|
|
#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 */
|