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