/**************************************************************************** giftool.c - GIF transformation tool. SPDX-License-Identifier: MIT ****************************************************************************/ #include #include #include #include #include #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 */