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