xref: /aosp_15_r20/external/giflib/gif2rgb.c (revision 324bb76b8d05e2a05aa88511fff61cf3f9ca5892)
1 /*****************************************************************************
2 
3 gif2rgb - convert GIF to 24-bit RGB pixel triples or vice-versa
4 
5 SPDX-License-Identifier: MIT
6 
7 *****************************************************************************/
8 
9 /***************************************************************************
10 
11 Toshio Kuratomi had written this in a comment about the rgb2gif code:
12 
13   Besides fixing bugs, what's really needed is for someone to work out how to
14   calculate a colormap for writing GIFs from rgb sources.  Right now, an rgb
15   source that has only two colors (b/w) is being converted into an 8 bit GIF....
16   Which is horrendously wasteful without compression.
17 
18 I (ESR) took this off the main to-do list in 2012 because I don't think
19 the GIFLIB project actually needs to be in the converters-and-tools business.
20 Plenty of hackers do that; our job is to supply stable library capability
21 with our utilities mainly interesting as test tools.
22 
23 ***************************************************************************/
24 
25 #include <ctype.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 
33 #ifdef _WIN32
34 #include <io.h>
35 #endif /* _WIN32 */
36 
37 #include "getarg.h"
38 #include "gif_lib.h"
39 
40 #define PROGRAM_NAME "gif2rgb"
41 
42 static char *VersionStr = PROGRAM_NAME VERSION_COOKIE
43     "	Gershon Elber,	" __DATE__ ",   " __TIME__ "\n"
44     "(C) Copyright 1989 Gershon Elber.\n";
45 static char *CtrlStr = PROGRAM_NAME
46     " v%- c%-#Colors!d s%-Width|Height!d!d 1%- o%-OutFileName!s h%- GifFile!*s";
47 
48 static void LoadRGB(char *FileName, int OneFileFlag, GifByteType **RedBuffer,
49                     GifByteType **GreenBuffer, GifByteType **BlueBuffer,
50                     int Width, int Height);
51 static void SaveGif(GifByteType *OutputBuffer, int Width, int Height,
52                     int ExpColorMapSize, ColorMapObject *OutputColorMap);
53 
54 /******************************************************************************
55  Load RGB file into internal frame buffer.
56 ******************************************************************************/
LoadRGB(char * FileName,int OneFileFlag,GifByteType ** RedBuffer,GifByteType ** GreenBuffer,GifByteType ** BlueBuffer,int Width,int Height)57 static void LoadRGB(char *FileName, int OneFileFlag, GifByteType **RedBuffer,
58                     GifByteType **GreenBuffer, GifByteType **BlueBuffer,
59                     int Width, int Height) {
60 	int i;
61 	unsigned long Size;
62 	GifByteType *RedP, *GreenP, *BlueP;
63 	FILE *rgbfp[3];
64 
65 	Size = ((long)Width) * Height * sizeof(GifByteType);
66 
67 	if ((*RedBuffer = (GifByteType *)malloc((unsigned int)Size)) == NULL ||
68 	    (*GreenBuffer = (GifByteType *)malloc((unsigned int)Size)) ==
69 	        NULL ||
70 	    (*BlueBuffer = (GifByteType *)malloc((unsigned int)Size)) == NULL) {
71 		GIF_EXIT("Failed to allocate memory required, aborted.");
72 	}
73 
74 	RedP = *RedBuffer;
75 	GreenP = *GreenBuffer;
76 	BlueP = *BlueBuffer;
77 
78 	if (FileName != NULL) {
79 		if (OneFileFlag) {
80 			if ((rgbfp[0] = fopen(FileName, "rb")) == NULL) {
81 				GIF_EXIT("Can't open input file name.");
82 			}
83 		} else {
84 			static const char *Postfixes[] = {".R", ".G", ".B"};
85 			char OneFileName[80];
86 
87 			for (i = 0; i < 3; i++) {
88 				strncpy(OneFileName, FileName,
89 				        sizeof(OneFileName) - 1);
90 				strncat(OneFileName, Postfixes[i],
91 				        sizeof(OneFileName) - 1 -
92 				            strlen(OneFileName));
93 
94 				if ((rgbfp[i] = fopen(OneFileName, "rb")) ==
95 				    NULL) {
96 					GIF_EXIT("Can't open input file name.");
97 				}
98 			}
99 		}
100 	} else {
101 		OneFileFlag = true;
102 
103 #ifdef _WIN32
104 		_setmode(0, O_BINARY);
105 #endif /* _WIN32 */
106 
107 		rgbfp[0] = stdin;
108 	}
109 
110 	GifQprintf("\n%s: RGB image:     ", PROGRAM_NAME);
111 
112 	if (OneFileFlag) {
113 		GifByteType *Buffer, *BufferP;
114 
115 		if ((Buffer = (GifByteType *)malloc(Width * 3)) == NULL) {
116 			GIF_EXIT(
117 			    "Failed to allocate memory required, aborted.");
118 		}
119 
120 		for (i = 0; i < Height; i++) {
121 			int j;
122 			GifQprintf("\b\b\b\b%-4d", i);
123 			if (fread(Buffer, Width * 3, 1, rgbfp[0]) != 1) {
124 				GIF_EXIT(
125 				    "Input file(s) terminated prematurly.");
126 			}
127 			for (j = 0, BufferP = Buffer; j < Width; j++) {
128 				*RedP++ = *BufferP++;
129 				*GreenP++ = *BufferP++;
130 				*BlueP++ = *BufferP++;
131 			}
132 		}
133 
134 		free((char *)Buffer);
135 		fclose(rgbfp[0]);
136 	} else {
137 		for (i = 0; i < Height; i++) {
138 			GifQprintf("\b\b\b\b%-4d", i);
139 			if (fread(RedP, Width, 1, rgbfp[0]) != 1 ||
140 			    fread(GreenP, Width, 1, rgbfp[1]) != 1 ||
141 			    fread(BlueP, Width, 1, rgbfp[2]) != 1) {
142 				GIF_EXIT(
143 				    "Input file(s) terminated prematurly.");
144 			}
145 			RedP += Width;
146 			GreenP += Width;
147 			BlueP += Width;
148 		}
149 
150 		fclose(rgbfp[0]);
151 		fclose(rgbfp[1]);
152 		fclose(rgbfp[2]);
153 	}
154 }
155 
156 /******************************************************************************
157  Save the GIF resulting image.
158 ******************************************************************************/
SaveGif(GifByteType * OutputBuffer,int Width,int Height,int ExpColorMapSize,ColorMapObject * OutputColorMap)159 static void SaveGif(GifByteType *OutputBuffer, int Width, int Height,
160                     int ExpColorMapSize, ColorMapObject *OutputColorMap) {
161 	int i, Error;
162 	GifFileType *GifFile;
163 	GifByteType *Ptr = OutputBuffer;
164 
165 	/* Open stdout for the output file: */
166 	if ((GifFile = EGifOpenFileHandle(1, &Error)) == NULL) {
167 		PrintGifError(Error);
168 		exit(EXIT_FAILURE);
169 	}
170 
171 	if (EGifPutScreenDesc(GifFile, Width, Height, ExpColorMapSize, 0,
172 	                      OutputColorMap) == GIF_ERROR ||
173 	    EGifPutImageDesc(GifFile, 0, 0, Width, Height, false, NULL) ==
174 	        GIF_ERROR) {
175 		PrintGifError(Error);
176 		exit(EXIT_FAILURE);
177 	}
178 
179 	GifQprintf("\n%s: Image 1 at (%d, %d) [%dx%d]:     ", PROGRAM_NAME,
180 	           GifFile->Image.Left, GifFile->Image.Top,
181 	           GifFile->Image.Width, GifFile->Image.Height);
182 
183 	for (i = 0; i < Height; i++) {
184 		if (EGifPutLine(GifFile, Ptr, Width) == GIF_ERROR) {
185 			exit(EXIT_FAILURE);
186 		}
187 		GifQprintf("\b\b\b\b%-4d", Height - i - 1);
188 
189 		Ptr += Width;
190 	}
191 
192 	if (EGifCloseFile(GifFile, &Error) == GIF_ERROR) {
193 		PrintGifError(Error);
194 		exit(EXIT_FAILURE);
195 	}
196 }
197 
198 /******************************************************************************
199  Close output file (if open), and exit.
200 ******************************************************************************/
RGB2GIF(bool OneFileFlag,int NumFiles,char * FileName,int ExpNumOfColors,int Width,int Height)201 static void RGB2GIF(bool OneFileFlag, int NumFiles, char *FileName,
202                     int ExpNumOfColors, int Width, int Height) {
203 	int ColorMapSize;
204 
205 	GifByteType *RedBuffer = NULL, *GreenBuffer = NULL, *BlueBuffer = NULL,
206 	            *OutputBuffer = NULL;
207 	ColorMapObject *OutputColorMap = NULL;
208 
209 	ColorMapSize = 1 << ExpNumOfColors;
210 
211 	if (NumFiles == 1) {
212 		LoadRGB(FileName, OneFileFlag, &RedBuffer, &GreenBuffer,
213 		        &BlueBuffer, Width, Height);
214 	} else {
215 		LoadRGB(NULL, OneFileFlag, &RedBuffer, &GreenBuffer,
216 		        &BlueBuffer, Width, Height);
217 	}
218 
219 	if ((OutputColorMap = GifMakeMapObject(ColorMapSize, NULL)) == NULL ||
220 	    (OutputBuffer = (GifByteType *)malloc(
221 	         Width * Height * sizeof(GifByteType))) == NULL) {
222 		GIF_EXIT("Failed to allocate memory required, aborted.");
223 	}
224 
225 	if (GifQuantizeBuffer(Width, Height, &ColorMapSize, RedBuffer,
226 	                      GreenBuffer, BlueBuffer, OutputBuffer,
227 	                      OutputColorMap->Colors) == GIF_ERROR) {
228 		exit(EXIT_FAILURE);
229 	}
230 	free((char *)RedBuffer);
231 	free((char *)GreenBuffer);
232 	free((char *)BlueBuffer);
233 
234 	SaveGif(OutputBuffer, Width, Height, ExpNumOfColors, OutputColorMap);
235 }
236 
237 /******************************************************************************
238  The real screen dumping routine.
239 ******************************************************************************/
DumpScreen2RGB(char * FileName,int OneFileFlag,ColorMapObject * ColorMap,const GifRowType * ScreenBuffer,int ScreenWidth,int ScreenHeight)240 static void DumpScreen2RGB(char *FileName, int OneFileFlag,
241                            ColorMapObject *ColorMap,
242                            const GifRowType *ScreenBuffer, int ScreenWidth,
243                            int ScreenHeight) {
244 	int i, j;
245 	GifRowType GifRow;
246 	GifColorType *ColorMapEntry;
247 	FILE *rgbfp[3];
248 
249 	if (FileName != NULL) {
250 		if (OneFileFlag) {
251 			if ((rgbfp[0] = fopen(FileName, "wb")) == NULL) {
252 				GIF_EXIT("Can't open input file name.");
253 			}
254 		} else {
255 			static char *Postfixes[] = {".R", ".G", ".B"};
256 			char OneFileName[80];
257 
258 			for (i = 0; i < 3; i++) {
259 				strncpy(OneFileName, FileName,
260 				        sizeof(OneFileName) - 1);
261 				strncat(OneFileName, Postfixes[i],
262 				        sizeof(OneFileName) - 1 -
263 				            strlen(OneFileName));
264 
265 				if ((rgbfp[i] = fopen(OneFileName, "wb")) ==
266 				    NULL) {
267 					GIF_EXIT("Can't open input file name.");
268 				}
269 			}
270 		}
271 	} else {
272 		OneFileFlag = true;
273 
274 #ifdef _WIN32
275 		_setmode(1, O_BINARY);
276 #endif /* _WIN32 */
277 
278 		rgbfp[0] = stdout;
279 	}
280 
281 	if (ColorMap == NULL) {
282 		fprintf(stderr, "Color map pointer is NULL.\n");
283 		exit(EXIT_FAILURE);
284 	}
285 
286 	if (OneFileFlag) {
287 		unsigned char *Buffer, *BufferP;
288 
289 		if ((Buffer = (unsigned char *)malloc(ScreenWidth * 3)) ==
290 		    NULL) {
291 			GIF_EXIT(
292 			    "Failed to allocate memory required, aborted.");
293 		}
294 		for (i = 0; i < ScreenHeight; i++) {
295 			GifRow = ScreenBuffer[i];
296 			GifQprintf("\b\b\b\b%-4d", ScreenHeight - i);
297 			for (j = 0, BufferP = Buffer; j < ScreenWidth; j++) {
298 				/* Check if color is within color palete */
299 				if (GifRow[j] >= ColorMap->ColorCount) {
300 					GIF_EXIT(GifErrorString(
301 					    D_GIF_ERR_IMAGE_DEFECT));
302 				}
303 				ColorMapEntry = &ColorMap->Colors[GifRow[j]];
304 				*BufferP++ = ColorMapEntry->Red;
305 				*BufferP++ = ColorMapEntry->Green;
306 				*BufferP++ = ColorMapEntry->Blue;
307 			}
308 			if (fwrite(Buffer, ScreenWidth * 3, 1, rgbfp[0]) != 1) {
309 				GIF_EXIT("Write to file(s) failed.");
310 			}
311 		}
312 
313 		free((char *)Buffer);
314 		fclose(rgbfp[0]);
315 	} else {
316 		unsigned char *Buffers[3];
317 
318 		if ((Buffers[0] = (unsigned char *)malloc(ScreenWidth)) ==
319 		        NULL ||
320 		    (Buffers[1] = (unsigned char *)malloc(ScreenWidth)) ==
321 		        NULL ||
322 		    (Buffers[2] = (unsigned char *)malloc(ScreenWidth)) ==
323 		        NULL) {
324 			GIF_EXIT(
325 			    "Failed to allocate memory required, aborted.");
326 		}
327 
328 		for (i = 0; i < ScreenHeight; i++) {
329 			GifRow = ScreenBuffer[i];
330 			GifQprintf("\b\b\b\b%-4d", ScreenHeight - i);
331 			for (j = 0; j < ScreenWidth; j++) {
332 				ColorMapEntry = &ColorMap->Colors[GifRow[j]];
333 				Buffers[0][j] = ColorMapEntry->Red;
334 				Buffers[1][j] = ColorMapEntry->Green;
335 				Buffers[2][j] = ColorMapEntry->Blue;
336 			}
337 			if (fwrite(Buffers[0], ScreenWidth, 1, rgbfp[0]) != 1 ||
338 			    fwrite(Buffers[1], ScreenWidth, 1, rgbfp[1]) != 1 ||
339 			    fwrite(Buffers[2], ScreenWidth, 1, rgbfp[2]) != 1) {
340 				GIF_EXIT("Write to file(s) failed.");
341 			}
342 		}
343 
344 		free((char *)Buffers[0]);
345 		free((char *)Buffers[1]);
346 		free((char *)Buffers[2]);
347 		fclose(rgbfp[0]);
348 		fclose(rgbfp[1]);
349 		fclose(rgbfp[2]);
350 	}
351 }
352 
GIF2RGB(int NumFiles,char * FileName,bool OneFileFlag,char * OutFileName)353 static void GIF2RGB(int NumFiles, char *FileName, bool OneFileFlag,
354                     char *OutFileName) {
355 	int i, j, Size, Row, Col, Width, Height, ExtCode, Count;
356 	GifRecordType RecordType;
357 	GifByteType *Extension;
358 	GifRowType *ScreenBuffer;
359 	GifFileType *GifFile;
360 	static const int InterlacedOffset[] = {
361 	    0, 4, 2, 1}; /* The way Interlaced image should. */
362 	static const int InterlacedJumps[] = {
363 	    8, 8, 4, 2}; /* be read - offsets and jumps... */
364 	int ImageNum = 0;
365 	ColorMapObject *ColorMap;
366 
367 	if (NumFiles == 1) {
368 		int Error;
369 		if ((GifFile = DGifOpenFileName(FileName, &Error)) == NULL) {
370 			PrintGifError(Error);
371 			exit(EXIT_FAILURE);
372 		}
373 	} else {
374 		int Error;
375 		/* Use stdin instead: */
376 		if ((GifFile = DGifOpenFileHandle(0, &Error)) == NULL) {
377 			PrintGifError(Error);
378 			exit(EXIT_FAILURE);
379 		}
380 	}
381 
382 	if (GifFile->SHeight == 0 || GifFile->SWidth == 0) {
383 		fprintf(stderr, "Image of width or height 0\n");
384 		exit(EXIT_FAILURE);
385 	}
386 
387 	/*
388 	 * Allocate the screen as vector of column of rows. Note this
389 	 * screen is device independent - it's the screen defined by the
390 	 * GIF file parameters.
391 	 */
392 	if ((ScreenBuffer = (GifRowType *)malloc(GifFile->SHeight *
393 	                                         sizeof(GifRowType))) == NULL) {
394 		GIF_EXIT("Failed to allocate memory required, aborted.");
395 	}
396 
397 	Size =
398 	    GifFile->SWidth * sizeof(GifPixelType); /* Size in bytes one row.*/
399 	if ((ScreenBuffer[0] = (GifRowType)malloc(Size)) ==
400 	    NULL) { /* First row. */
401 		GIF_EXIT("Failed to allocate memory required, aborted.");
402 	}
403 
404 	for (i = 0; i < GifFile->SWidth;
405 	     i++) { /* Set its color to BackGround. */
406 		ScreenBuffer[0][i] = GifFile->SBackGroundColor;
407 	}
408 	for (i = 1; i < GifFile->SHeight; i++) {
409 		/* Allocate the other rows, and set their color to background
410 		 * too: */
411 		if ((ScreenBuffer[i] = (GifRowType)malloc(Size)) == NULL) {
412 			GIF_EXIT(
413 			    "Failed to allocate memory required, aborted.");
414 		}
415 
416 		memcpy(ScreenBuffer[i], ScreenBuffer[0], Size);
417 	}
418 
419 	/* Scan the content of the GIF file and load the image(s) in: */
420 	do {
421 		if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
422 			PrintGifError(GifFile->Error);
423 			exit(EXIT_FAILURE);
424 		}
425 		switch (RecordType) {
426 		case IMAGE_DESC_RECORD_TYPE:
427 			if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
428 				PrintGifError(GifFile->Error);
429 				exit(EXIT_FAILURE);
430 			}
431 			Row = GifFile->Image
432 			          .Top; /* Image Position relative to Screen. */
433 			Col = GifFile->Image.Left;
434 			Width = GifFile->Image.Width;
435 			Height = GifFile->Image.Height;
436 			GifQprintf("\n%s: Image %d at (%d, %d) [%dx%d]:     ",
437 			           PROGRAM_NAME, ++ImageNum, Col, Row, Width,
438 			           Height);
439 			if (GifFile->Image.Left + GifFile->Image.Width >
440 			        GifFile->SWidth ||
441 			    GifFile->Image.Top + GifFile->Image.Height >
442 			        GifFile->SHeight) {
443 				fprintf(stderr,
444 				        "Image %d is not confined to screen "
445 				        "dimension, aborted.\n",
446 				        ImageNum);
447 				exit(EXIT_FAILURE);
448 			}
449 			if (GifFile->Image.Interlace) {
450 				/* Need to perform 4 passes on the images: */
451 				for (Count = i = 0; i < 4; i++) {
452 					for (j = Row + InterlacedOffset[i];
453 					     j < Row + Height;
454 					     j += InterlacedJumps[i]) {
455 						GifQprintf("\b\b\b\b%-4d",
456 						           Count++);
457 						if (DGifGetLine(
458 						        GifFile,
459 						        &ScreenBuffer[j][Col],
460 						        Width) == GIF_ERROR) {
461 							PrintGifError(
462 							    GifFile->Error);
463 							exit(EXIT_FAILURE);
464 						}
465 					}
466 				}
467 			} else {
468 				for (i = 0; i < Height; i++) {
469 					GifQprintf("\b\b\b\b%-4d", i);
470 					if (DGifGetLine(
471 					        GifFile,
472 					        &ScreenBuffer[Row++][Col],
473 					        Width) == GIF_ERROR) {
474 						PrintGifError(GifFile->Error);
475 						exit(EXIT_FAILURE);
476 					}
477 				}
478 			}
479 			break;
480 		case EXTENSION_RECORD_TYPE:
481 			/* Skip any extension blocks in file: */
482 			if (DGifGetExtension(GifFile, &ExtCode, &Extension) ==
483 			    GIF_ERROR) {
484 				PrintGifError(GifFile->Error);
485 				exit(EXIT_FAILURE);
486 			}
487 			while (Extension != NULL) {
488 				if (DGifGetExtensionNext(GifFile, &Extension) ==
489 				    GIF_ERROR) {
490 					PrintGifError(GifFile->Error);
491 					exit(EXIT_FAILURE);
492 				}
493 			}
494 			break;
495 		case TERMINATE_RECORD_TYPE:
496 			break;
497 		default: /* Should be trapped by DGifGetRecordType. */
498 			break;
499 		}
500 	} while (RecordType != TERMINATE_RECORD_TYPE);
501 
502 	/* Lets dump it - set the global variables required and do it: */
503 	ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap
504 	                                    : GifFile->SColorMap);
505 	if (ColorMap == NULL) {
506 		fprintf(stderr, "Gif Image does not have a colormap\n");
507 		exit(EXIT_FAILURE);
508 	}
509 
510 	/* check that the background color isn't garbage (SF bug #87) */
511 	if (GifFile->SBackGroundColor < 0 ||
512 	    GifFile->SBackGroundColor >= ColorMap->ColorCount) {
513 		fprintf(stderr, "Background color out of range for colormap\n");
514 		exit(EXIT_FAILURE);
515 	}
516 
517 	DumpScreen2RGB(OutFileName, OneFileFlag, ColorMap, ScreenBuffer,
518 	               GifFile->SWidth, GifFile->SHeight);
519 
520 	(void)free(ScreenBuffer);
521 
522 	{
523 		int Error;
524 		if (DGifCloseFile(GifFile, &Error) == GIF_ERROR) {
525 			PrintGifError(Error);
526 			exit(EXIT_FAILURE);
527 		}
528 	}
529 }
530 
531 /******************************************************************************
532  * Interpret the command line and scan the given GIF file.
533  ******************************************************************************/
main(int argc,char ** argv)534 int main(int argc, char **argv) {
535 	bool Error, OutFileFlag = false, ColorFlag = false, SizeFlag = false,
536 	            GifNoisyPrint = false;
537 	int NumFiles, Width = 0, Height = 0, ExpNumOfColors = 8;
538 	char *OutFileName, **FileName = NULL;
539 	static bool OneFileFlag = false, HelpFlag = false;
540 
541 	if ((Error = GAGetArgs(argc, argv, CtrlStr, &GifNoisyPrint, &ColorFlag,
542 	                       &ExpNumOfColors, &SizeFlag, &Width, &Height,
543 	                       &OneFileFlag, &OutFileFlag, &OutFileName,
544 	                       &HelpFlag, &NumFiles, &FileName)) != false ||
545 	    (NumFiles > 1 && !HelpFlag)) {
546 		if (Error) {
547 			GAPrintErrMsg(Error);
548 		} else if (NumFiles > 1) {
549 			GIF_MESSAGE("Error in command line parsing - one input "
550 			            "file please.");
551 		}
552 		GAPrintHowTo(CtrlStr);
553 		exit(EXIT_FAILURE);
554 	}
555 
556 	if (HelpFlag) {
557 		(void)fprintf(stderr, VersionStr, GIFLIB_MAJOR, GIFLIB_MINOR);
558 		GAPrintHowTo(CtrlStr);
559 		exit(EXIT_SUCCESS);
560 	}
561 	if (!OutFileFlag) {
562 		OutFileName = NULL;
563 	}
564 
565 	if (SizeFlag) {
566 		if ((Width <= 0 || Height <= 0) || (Height > INT_MAX / Width)) {
567 			GIF_MESSAGE(
568 			    "Image size would be overflow, zero or negative");
569 			exit(EXIT_FAILURE);
570 		}
571 		RGB2GIF(OneFileFlag, NumFiles, *FileName, ExpNumOfColors, Width,
572 		        Height);
573 	} else {
574 		GIF2RGB(NumFiles, *FileName, OneFileFlag, OutFileName);
575 	}
576 
577 	return 0;
578 }
579 
580 /* end */
581