1 /****************************************************************************
2
3 giftool.c - GIF transformation tool.
4
5 SPDX-License-Identifier: MIT
6
7 ****************************************************************************/
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <fcntl.h>
13 #include <stdbool.h>
14
15 #include "getopt.h"
16 #include "gif_lib.h"
17 #include "getarg.h"
18
19 #define PROGRAM_NAME "giftool"
20
21 #define MAX_OPERATIONS 256
22 #define MAX_IMAGES 2048
23
24 enum boolmode {numeric, onoff, tf, yesno};
25
putbool(bool flag,enum boolmode mode)26 char *putbool(bool flag, enum boolmode mode)
27 {
28 if (flag)
29 switch (mode) {
30 case numeric: return "1"; break;
31 case onoff: return "on"; break;
32 case tf: return "true"; break;
33 case yesno: return "yes"; break;
34 }
35 else
36 switch (mode) {
37 case numeric: return "0"; break;
38 case onoff: return "off"; break;
39 case tf: return "false"; break;
40 case yesno: return "no"; break;
41 }
42
43 return "FAIL"; /* should never happen */
44 }
45
getbool(char * from)46 bool getbool(char *from)
47 {
48 struct valmap {char *name; bool val;}
49 boolnames[] = {
50 {"yes", true},
51 {"on", true},
52 {"1", true},
53 {"t", true},
54 {"no", false},
55 {"off", false},
56 {"0", false},
57 {"f", false},
58 {NULL, false},
59 }, *sp;
60
61 for (sp = boolnames; sp->name; sp++)
62 if (strcmp(sp->name, from) == 0)
63 return sp->val;
64
65 (void)fprintf(stderr,
66 "giftool: %s is not a valid boolean argument.\n",
67 sp->name);
68 exit(EXIT_FAILURE);
69 }
70
71 struct operation {
72 enum {
73 aspect,
74 delaytime,
75 background,
76 info,
77 interlace,
78 position,
79 screensize,
80 transparent,
81 userinput,
82 disposal,
83 } mode;
84 union {
85 GifByteType numerator;
86 int delay;
87 int color;
88 int dispose;
89 char *format;
90 bool flag;
91 struct {
92 int x, y;
93 } p;
94 };
95 };
96
main(int argc,char ** argv)97 int main(int argc, char **argv)
98 {
99 extern char *optarg; /* set by getopt */
100 extern int optind; /* set by getopt */
101 struct operation operations[MAX_OPERATIONS];
102 struct operation *top = operations;
103 int selected[MAX_IMAGES], nselected = 0;
104 bool have_selection = false;
105 char *cp;
106 int i, status, ErrorCode;
107 GifFileType *GifFileIn, *GifFileOut = (GifFileType *)NULL;
108 struct operation *op;
109
110 /*
111 * Gather operations from the command line. We use regular
112 * getopt(3) here rather than Gershom's argument getter because
113 * preserving the order of operations is important.
114 */
115 while ((status = getopt(argc, argv, "a:b:d:f:i:n:p:s:u:x:")) != EOF)
116 {
117 if (top >= operations + MAX_OPERATIONS) {
118 (void)fprintf(stderr, "giftool: too many operations.");
119 exit(EXIT_FAILURE);
120 }
121
122 switch (status)
123 {
124 case 'a':
125 top->mode = aspect;
126 top->numerator = (GifByteType)atoi(optarg);
127 break;
128
129 case 'b':
130 top->mode = background;
131 top->color = atoi(optarg);
132 break;
133
134 case 'd':
135 top->mode = delaytime;
136 top->delay = atoi(optarg);
137 break;
138
139 case 'f':
140 top->mode = info;
141 top->format = optarg;
142 break;
143
144 case 'i':
145 top->mode = interlace;
146 top->flag = getbool(optarg);
147 break;
148
149 case 'n':
150 have_selection = true;
151 nselected = 0;
152 cp = optarg;
153 for (;;)
154 {
155 size_t span = strspn(cp, "0123456789");
156
157 if (span > 0)
158 {
159 selected[nselected++] = atoi(cp)-1;
160 cp += span;
161 if (*cp == '\0')
162 break;
163 else if (*cp == ',')
164 continue;
165 }
166
167 (void) fprintf(stderr, "giftool: bad selection.\n");
168 exit(EXIT_FAILURE);
169 }
170 break;
171
172 case 'p':
173 case 's':
174 if (status == 'p')
175 top->mode = position;
176 else
177 top->mode = screensize;
178 cp = strchr(optarg, ',');
179 if (cp == NULL)
180 {
181 (void) fprintf(stderr, "giftool: missing comma in coordinate pair.\n");
182 exit(EXIT_FAILURE);
183 }
184 top->p.x = atoi(optarg);
185 top->p.y = atoi(cp+1);
186 if (top->p.x < 0 || top->p.y < 0)
187 {
188 (void) fprintf(stderr, "giftool: negative coordinate.\n");
189 exit(EXIT_FAILURE);
190 }
191 break;
192
193 case 'u':
194 top->mode = userinput;
195 top->flag = getbool(optarg);
196 break;
197
198 case 'x':
199 top->mode = disposal;
200 top->dispose = atoi(optarg);
201 break;
202
203 default:
204 fprintf(stderr, "usage: giftool [-b color] [-d delay] [-iI] [-t color] -[uU] [-x disposal]\n");
205 break;
206 }
207
208 ++top;
209 }
210
211 /* read in a GIF */
212 if ((GifFileIn = DGifOpenFileHandle(0, &ErrorCode)) == NULL) {
213 PrintGifError(ErrorCode);
214 exit(EXIT_FAILURE);
215 }
216 if (DGifSlurp(GifFileIn) == GIF_ERROR) {
217 PrintGifError(GifFileIn->Error);
218 exit(EXIT_FAILURE);
219 }
220 if ((GifFileOut = EGifOpenFileHandle(1, &ErrorCode)) == NULL) {
221 PrintGifError(ErrorCode);
222 exit(EXIT_FAILURE);
223 }
224
225 /* if the selection is defaulted, compute it; otherwise bounds-check it */
226 if (!have_selection)
227 for (i = nselected = 0; i < GifFileIn->ImageCount; i++)
228 selected[nselected++] = i;
229 else
230 for (i = 0; i < nselected; i++)
231 if (selected[i] >= GifFileIn->ImageCount || selected[i] < 0)
232 {
233 (void) fprintf(stderr,
234 "giftool: selection index out of bounds.\n");
235 exit(EXIT_FAILURE);
236 }
237
238 /* perform the operations we've gathered */
239 for (op = operations; op < top; op++)
240 switch (op->mode)
241 {
242 case background:
243 GifFileIn->SBackGroundColor = op->color;
244 break;
245
246 case delaytime:
247 for (i = 0; i < nselected; i++)
248 {
249 GraphicsControlBlock gcb;
250
251 DGifSavedExtensionToGCB(GifFileIn, selected[i], &gcb);
252 gcb.DelayTime = op->delay;
253 EGifGCBToSavedExtension(&gcb, GifFileIn, selected[i]);
254 }
255 break;
256
257 case info:
258 for (i = 0; i < nselected; i++) {
259 SavedImage *ip = &GifFileIn->SavedImages[selected[i]];
260 GraphicsControlBlock gcb;
261 for (cp = op->format; *cp; cp++) {
262 if (*cp == '\\')
263 {
264 char c;
265 switch (*++cp)
266 {
267 case 'b':
268 (void)putchar('\b');
269 break;
270 case 'e':
271 (void)putchar(0x1b);
272 break;
273 case 'f':
274 (void)putchar('\f');
275 break;
276 case 'n':
277 (void)putchar('\n');
278 break;
279 case 'r':
280 (void)putchar('\r');
281 break;
282 case 't':
283 (void)putchar('\t');
284 break;
285 case 'v':
286 (void)putchar('\v');
287 break;
288 case 'x':
289 switch (*++cp) {
290 case '0':
291 c = (char)0x00;
292 break;
293 case '1':
294 c = (char)0x10;
295 break;
296 case '2':
297 c = (char)0x20;
298 break;
299 case '3':
300 c = (char)0x30;
301 break;
302 case '4':
303 c = (char)0x40;
304 break;
305 case '5':
306 c = (char)0x50;
307 break;
308 case '6':
309 c = (char)0x60;
310 break;
311 case '7':
312 c = (char)0x70;
313 break;
314 case '8':
315 c = (char)0x80;
316 break;
317 case '9':
318 c = (char)0x90;
319 break;
320 case 'A':
321 case 'a':
322 c = (char)0xa0;
323 break;
324 case 'B':
325 case 'b':
326 c = (char)0xb0;
327 break;
328 case 'C':
329 case 'c':
330 c = (char)0xc0;
331 break;
332 case 'D':
333 case 'd':
334 c = (char)0xd0;
335 break;
336 case 'E':
337 case 'e':
338 c = (char)0xe0;
339 break;
340 case 'F':
341 case 'f':
342 c = (char)0xf0;
343 break;
344 default:
345 return -1;
346 }
347 switch (*++cp) {
348 case '0':
349 c += 0x00;
350 break;
351 case '1':
352 c += 0x01;
353 break;
354 case '2':
355 c += 0x02;
356 break;
357 case '3':
358 c += 0x03;
359 break;
360 case '4':
361 c += 0x04;
362 break;
363 case '5':
364 c += 0x05;
365 break;
366 case '6':
367 c += 0x06;
368 break;
369 case '7':
370 c += 0x07;
371 break;
372 case '8':
373 c += 0x08;
374 break;
375 case '9':
376 c += 0x09;
377 break;
378 case 'A':
379 case 'a':
380 c += 0x0a;
381 break;
382 case 'B':
383 case 'b':
384 c += 0x0b;
385 break;
386 case 'C':
387 case 'c':
388 c += 0x0c;
389 break;
390 case 'D':
391 case 'd':
392 c += 0x0d;
393 break;
394 case 'E':
395 case 'e':
396 c += 0x0e;
397 break;
398 case 'F':
399 case 'f':
400 c += 0x0f;
401 break;
402 default:
403 return -2;
404 }
405 putchar(c);
406 break;
407 default:
408 putchar(*cp);
409 break;
410 }
411 }
412 else if (*cp == '%')
413 {
414 enum boolmode boolfmt;
415 SavedImage *sp = &GifFileIn->SavedImages[i];
416
417 if (cp[1] == 't') {
418 boolfmt = tf;
419 ++cp;
420 } else if (cp[1] == 'o') {
421 boolfmt = onoff;
422 ++cp;
423 } else if (cp[1] == 'y') {
424 boolfmt = yesno;
425 ++cp;
426 } else if (cp[1] == '1') {
427 boolfmt = numeric;
428 ++cp;
429 } else
430 boolfmt = numeric;
431
432 switch (*++cp)
433 {
434 case '%':
435 putchar('%');
436 break;
437 case 'a':
438 (void)printf("%d", GifFileIn->AspectByte);
439 break;
440 case 'b':
441 (void)printf("%d", GifFileIn->SBackGroundColor);
442 break;
443 case 'd':
444 DGifSavedExtensionToGCB(GifFileIn,
445 selected[i],
446 &gcb);
447 (void)printf("%d", gcb.DelayTime);
448 break;
449 case 'h':
450 (void)printf("%d", ip->ImageDesc.Height);
451 break;
452 case 'n':
453 (void)printf("%d", selected[i]+1);
454 break;
455 case 'p':
456 (void)printf("%d,%d",
457 ip->ImageDesc.Left, ip->ImageDesc.Top);
458 break;
459 case 's':
460 (void)printf("%d,%d",
461 GifFileIn->SWidth,
462 GifFileIn->SHeight);
463 break;
464 case 'w':
465 (void)printf("%d", ip->ImageDesc.Width);
466 break;
467 case 't':
468 DGifSavedExtensionToGCB(GifFileIn,
469 selected[i],
470 &gcb);
471 (void)printf("%d", gcb.TransparentColor);
472 break;
473 case 'u':
474 DGifSavedExtensionToGCB(GifFileIn,
475 selected[i],
476 &gcb);
477 (void)printf("%s", putbool(gcb.UserInputFlag, boolfmt));
478 break;
479 case 'v':
480 fputs(EGifGetGifVersion(GifFileIn), stdout);
481 break;
482 case 'x':
483 DGifSavedExtensionToGCB(GifFileIn,
484 selected[i],
485 &gcb);
486 (void)printf("%d", gcb.DisposalMode);
487 break;
488 case 'z':
489 (void) printf("%s", putbool(sp->ImageDesc.ColorMap && sp->ImageDesc.ColorMap->SortFlag, boolfmt));
490 break;
491 default:
492 (void)fprintf(stderr,
493 "giftool: bad format %%%c\n", *cp);
494 }
495 }
496 else
497 (void)putchar(*cp);
498 }
499 }
500 exit(EXIT_SUCCESS);
501 break;
502
503 case interlace:
504 for (i = 0; i < nselected; i++)
505 GifFileIn->SavedImages[selected[i]].ImageDesc.Interlace = op->flag;
506 break;
507
508 case position:
509 for (i = 0; i < nselected; i++) {
510 GifFileIn->SavedImages[selected[i]].ImageDesc.Left = op->p.x;
511 GifFileIn->SavedImages[selected[i]].ImageDesc.Top = op->p.y;
512 }
513 break;
514
515 case screensize:
516 GifFileIn->SWidth = op->p.x;
517 GifFileIn->SHeight = op->p.y;
518 break;
519
520 case transparent:
521 for (i = 0; i < nselected; i++)
522 {
523 GraphicsControlBlock gcb;
524
525 DGifSavedExtensionToGCB(GifFileIn, selected[i], &gcb);
526 gcb.TransparentColor = op->color;
527 EGifGCBToSavedExtension(&gcb, GifFileIn, selected[i]);
528 }
529 break;
530
531 case userinput:
532 for (i = 0; i < nselected; i++)
533 {
534 GraphicsControlBlock gcb;
535
536 DGifSavedExtensionToGCB(GifFileIn, selected[i], &gcb);
537 gcb.UserInputFlag = op->flag;
538 EGifGCBToSavedExtension(&gcb, GifFileIn, selected[i]);
539 }
540 break;
541
542 case disposal:
543 for (i = 0; i < nselected; i++)
544 {
545 GraphicsControlBlock gcb;
546
547 DGifSavedExtensionToGCB(GifFileIn, selected[i], &gcb);
548 gcb.DisposalMode = op->dispose;
549 EGifGCBToSavedExtension(&gcb, GifFileIn, selected[i]);
550 }
551 break;
552
553 default:
554 (void)fprintf(stderr, "giftool: unknown operation mode\n");
555 exit(EXIT_FAILURE);
556 }
557
558 /* write out the results */
559 GifFileOut->SWidth = GifFileIn->SWidth;
560 GifFileOut->SHeight = GifFileIn->SHeight;
561 GifFileOut->SColorResolution = GifFileIn->SColorResolution;
562 GifFileOut->SBackGroundColor = GifFileIn->SBackGroundColor;
563 if (GifFileIn->SColorMap != NULL)
564 GifFileOut->SColorMap = GifMakeMapObject(
565 GifFileIn->SColorMap->ColorCount,
566 GifFileIn->SColorMap->Colors);
567
568 for (i = 0; i < GifFileIn->ImageCount; i++)
569 (void) GifMakeSavedImage(GifFileOut, &GifFileIn->SavedImages[i]);
570
571 if (EGifSpew(GifFileOut) == GIF_ERROR)
572 PrintGifError(GifFileOut->Error);
573 else if (DGifCloseFile(GifFileIn, &ErrorCode) == GIF_ERROR)
574 PrintGifError(ErrorCode);
575
576 return 0;
577 }
578
579 /* end */
580