580 lines
12 KiB
C
580 lines
12 KiB
C
/****************************************************************************
|
|
|
|
giftool.c - GIF transformation tool.
|
|
|
|
SPDX-License-Identifier: MIT
|
|
|
|
****************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "getopt.h"
|
|
#include "gif_lib.h"
|
|
#include "getarg.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;
|
|
|
|
for (sp = boolnames; sp->name; sp++)
|
|
if (strcmp(sp->name, from) == 0)
|
|
return sp->val;
|
|
|
|
(void)fprintf(stderr,
|
|
"giftool: %s is not a valid boolean argument.\n",
|
|
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 */
|