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