xref: /aosp_15_r20/external/giflib/giftool.c (revision 324bb76b8d05e2a05aa88511fff61cf3f9ca5892)
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