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