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