1 /****************************************************************************
2 gif.c - read and write gif images using libgif/libungif.
3 Distributed with Xplanet.
4 Copyright (C) 2002 Hari Nair <hari@alumni.caltech.edu>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 ****************************************************************************/
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <gif_lib.h>
26
27 /*
28 Based on the gif2rgb and rgb2gif codes in the libgif distribution.
29 */
30
31 int
read_gif(const char * filename,int * width,int * height,unsigned char ** rgb)32 read_gif(const char *filename, int *width, int *height, unsigned char **rgb)
33 {
34 int BackGround = 0;
35 /* The way Interlaced image should be read - offsets and
36 jumps... */
37 int InterlacedOffset[] = { 0, 4, 2, 1 };
38 int InterlacedJumps[] = { 8, 8, 4, 2 };
39 ColorMapObject *ColorMap;
40
41 int i, j, Size, Row, Col, Width, Height, ExtCode, Count;
42 GifRecordType RecordType;
43 GifByteType *Extension;
44 GifRowType *ScreenBuffer;
45
46 GifRowType GifRow;
47 GifColorType *ColorMapEntry;
48 unsigned char *BufferP;
49
50 GifFileType *GifFile;
51 #if GIFLIB_MAJOR >= 5
52 GifFile = DGifOpenFileName(filename, NULL);
53 #else
54 GifFile = DGifOpenFileName(filename);
55 #endif
56 if (GifFile == NULL) {
57 fprintf(stderr, "Can't open GIF file %s\n", filename);
58 return(0);
59 }
60 /* (Following comment was probably written on a 286 running under DOS.) */
61 /* Allocate the screen as vector of column of rows. We can't allocate the*/
62 /* whole screen at once, as this broken minded CPU can allocate up to */
63 /* 64k at a time and our image can be bigger than that: */
64 /* Note this screen is device independent - it's the screen defined by */
65 /* the GIF file parameters. */
66 if ((ScreenBuffer = (GifRowType *)
67 malloc(GifFile->SHeight * sizeof(GifRowType))) == NULL)
68 {
69 fprintf(stderr, "Failed to allocate memory required, aborted.");
70 return(0);
71 }
72
73 Size = GifFile->SWidth * sizeof(GifPixelType);/* Size in bytes one row.*/
74 if ((ScreenBuffer[0] = (GifRowType) malloc(Size)) == NULL) /* First row. */
75 {
76 fprintf(stderr, "Failed to allocate memory required, aborted.");
77 return(0);
78 }
79
80 for (i = 0; i < GifFile->SWidth; i++) /* Set its color to BackGround. */
81 ScreenBuffer[0][i] = GifFile->SBackGroundColor;
82 for (i = 1; i < GifFile->SHeight; i++) {
83 /* Allocate the other rows, and set their color to background too: */
84 if ((ScreenBuffer[i] = (GifRowType) malloc(Size)) == NULL)
85 {
86 fprintf(stderr, "Failed to allocate memory required, aborted.\n");
87 return(0);
88 }
89
90 memcpy(ScreenBuffer[i], ScreenBuffer[0], Size);
91 }
92
93 /* Scan the content of the GIF file and load the image(s) in: */
94 do {
95 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
96 fprintf(stderr, "Can't read GIF file\n");
97 return(0);
98 }
99 switch (RecordType) {
100 case IMAGE_DESC_RECORD_TYPE:
101 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
102 return(0);
103 }
104 Row = GifFile->Image.Top; /* Image Position relative to Screen. */
105 Col = GifFile->Image.Left;
106 Width = GifFile->Image.Width;
107 Height = GifFile->Image.Height;
108 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
109 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
110 return(0);
111 }
112 if (GifFile->Image.Interlace) {
113 /* Need to perform 4 passes on the images: */
114 for (Count = i = 0; i < 4; i++)
115 for (j = Row + InterlacedOffset[i]; j < Row + Height;
116 j += InterlacedJumps[i]) {
117 if (DGifGetLine(GifFile, &ScreenBuffer[j][Col],
118 Width) == GIF_ERROR) {
119 return(0);
120 }
121 }
122 }
123 else {
124 for (i = 0; i < Height; i++) {
125 if (DGifGetLine(GifFile, &ScreenBuffer[Row++][Col],
126 Width) == GIF_ERROR) {
127 return(0);
128 }
129 }
130 }
131 break;
132 case EXTENSION_RECORD_TYPE:
133 /* Skip any extension blocks in file: */
134 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
135 return(0);
136 }
137 while (Extension != NULL) {
138 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
139 return(0);
140 }
141 }
142 break;
143 case TERMINATE_RECORD_TYPE:
144 break;
145 default: /* Should be traps by DGifGetRecordType. */
146 break;
147 }
148 } while (RecordType != TERMINATE_RECORD_TYPE);
149
150 /* Lets dump it - set the global variables required and do it: */
151 BackGround = GifFile->SBackGroundColor;
152 ColorMap = (GifFile->Image.ColorMap
153 ? GifFile->Image.ColorMap
154 : GifFile->SColorMap);
155 if (ColorMap == NULL) {
156 fprintf(stderr, "Gif Image does not have a colormap\n");
157 return(0);
158 }
159
160 *width = GifFile->SWidth;
161 *height = GifFile->SHeight;
162
163 rgb[0] = malloc(3 * *width * *height);
164 if (rgb[0] == NULL)
165 {
166 fprintf(stderr, "Can't allocate memory for GIF file.\n");
167 return(0);
168 }
169
170 BufferP = rgb[0];
171
172 for (i = 0; i < *height; i++) {
173 GifRow = ScreenBuffer[i];
174 for (j = 0; j < *width; j++) {
175 ColorMapEntry = &ColorMap->Colors[GifRow[j]];
176 *BufferP++ = ColorMapEntry->Red;
177 *BufferP++ = ColorMapEntry->Green;
178 *BufferP++ = ColorMapEntry->Blue;
179 }
180 }
181
182 if (DGifCloseFile(GifFile, NULL) == GIF_ERROR) {
183 return(0);
184 }
185
186 return(1);
187 }
188
189 /* Make some variables global, so we could access them faster: */
190 static int
191 ExpNumOfColors = 8,
192 ColorMapSize = 256;
193
194 /******************************************************************************
195 * Code to quantize high resolution image into lower one. You may want to
196 * peek into the following article this code is based on:
197 * "Color Image Quantization for frame buffer Display", by Paul Heckbert
198 * SIGGRAPH 1982 page 297-307.
199 ******************************************************************************
200 * History:
201 * 5 Jan 90 - Version 1.0 by Gershon Elber.
202 *****************************************************************************/
203
204 #define ABS(x) ((x) > 0 ? (x) : (-(x)))
205
206 #define COLOR_ARRAY_SIZE 32768
207 #define BITS_PER_PRIM_COLOR 5
208 #define MAX_PRIM_COLOR 0x1f
209
210 static int SortRGBAxis;
211
212 typedef struct QuantizedColorType {
213 GifByteType RGB[3];
214 GifByteType NewColorIndex;
215 long Count;
216 struct QuantizedColorType *Pnext;
217 } QuantizedColorType;
218
219 typedef struct NewColorMapType {
220 GifByteType RGBMin[3], RGBWidth[3];
221 unsigned int NumEntries; /* # of QuantizedColorType in linked list below */
222 unsigned long Count; /* Total number of pixels in all the entries */
223 QuantizedColorType *QuantizedColors;
224 } NewColorMapType;
225
226 static int SubdivColorMap(NewColorMapType * NewColorSubdiv,
227 unsigned int ColorMapSize,
228 unsigned int *NewColorMapSize);
229 static int SortCmpRtn(const void *Entry1, const void *Entry2);
230
231 /******************************************************************************
232 * Quantize high resolution image into lower one. Input image consists of a
233 * 2D array for each of the RGB colors with size Width by Height. There is no
234 * Color map for the input. Output is a quantized image with 2D array of
235 * indexes into the output color map.
236 * Note input image can be 24 bits at the most (8 for red/green/blue) and
237 * the output has 256 colors at the most (256 entries in the color map.).
238 * ColorMapSize specifies size of color map up to 256 and will be updated to
239 * real size before returning.
240 * Also non of the parameter are allocated by this routine.
241 * This function returns GIF_OK if succesfull, GIF_ERROR otherwise.
242 ******************************************************************************/
243 static int
QuantizeBuffer(unsigned int Width,unsigned int Height,int * ColorMapSize,GifByteType * RedInput,GifByteType * GreenInput,GifByteType * BlueInput,GifByteType * OutputBuffer,GifColorType * OutputColorMap)244 QuantizeBuffer(unsigned int Width,
245 unsigned int Height,
246 int *ColorMapSize,
247 GifByteType * RedInput,
248 GifByteType * GreenInput,
249 GifByteType * BlueInput,
250 GifByteType * OutputBuffer,
251 GifColorType * OutputColorMap) {
252
253 unsigned int Index, NumOfEntries;
254 int i, j, MaxRGBError[3];
255 unsigned int NewColorMapSize;
256 long Red, Green, Blue;
257 NewColorMapType NewColorSubdiv[256];
258 QuantizedColorType *ColorArrayEntries, *QuantizedColor;
259
260 ColorArrayEntries = (QuantizedColorType *)malloc(
261 sizeof(QuantizedColorType) * COLOR_ARRAY_SIZE);
262 if (ColorArrayEntries == NULL) {
263 return GIF_ERROR;
264 }
265
266 for (i = 0; i < COLOR_ARRAY_SIZE; i++) {
267 ColorArrayEntries[i].RGB[0] = i >> (2 * BITS_PER_PRIM_COLOR);
268 ColorArrayEntries[i].RGB[1] = (i >> BITS_PER_PRIM_COLOR) &
269 MAX_PRIM_COLOR;
270 ColorArrayEntries[i].RGB[2] = i & MAX_PRIM_COLOR;
271 ColorArrayEntries[i].Count = 0;
272 }
273
274 /* Sample the colors and their distribution: */
275 for (i = 0; i < (int)(Width * Height); i++) {
276 Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR)) <<
277 (2 * BITS_PER_PRIM_COLOR)) +
278 ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR)) <<
279 BITS_PER_PRIM_COLOR) +
280 (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR));
281 ColorArrayEntries[Index].Count++;
282 }
283
284 /* Put all the colors in the first entry of the color map, and call the
285 * recursive subdivision process. */
286 for (i = 0; i < 256; i++) {
287 NewColorSubdiv[i].QuantizedColors = NULL;
288 NewColorSubdiv[i].Count = NewColorSubdiv[i].NumEntries = 0;
289 for (j = 0; j < 3; j++) {
290 NewColorSubdiv[i].RGBMin[j] = 0;
291 NewColorSubdiv[i].RGBWidth[j] = 255;
292 }
293 }
294
295 /* Find the non empty entries in the color table and chain them: */
296 for (i = 0; i < COLOR_ARRAY_SIZE; i++)
297 if (ColorArrayEntries[i].Count > 0)
298 break;
299 QuantizedColor = NewColorSubdiv[0].QuantizedColors = &ColorArrayEntries[i];
300 NumOfEntries = 1;
301 while (++i < COLOR_ARRAY_SIZE)
302 if (ColorArrayEntries[i].Count > 0) {
303 QuantizedColor->Pnext = &ColorArrayEntries[i];
304 QuantizedColor = &ColorArrayEntries[i];
305 NumOfEntries++;
306 }
307 QuantizedColor->Pnext = NULL;
308
309 NewColorSubdiv[0].NumEntries = NumOfEntries; /* Different sampled colors */
310 NewColorSubdiv[0].Count = ((long)Width) * Height; /* Pixels */
311 NewColorMapSize = 1;
312 if (SubdivColorMap(NewColorSubdiv, *ColorMapSize, &NewColorMapSize) !=
313 GIF_OK) {
314 free((char *)ColorArrayEntries);
315 return GIF_ERROR;
316 }
317 if (NewColorMapSize < *ColorMapSize) {
318 /* And clear rest of color map: */
319 for (i = NewColorMapSize; i < *ColorMapSize; i++)
320 OutputColorMap[i].Red = OutputColorMap[i].Green =
321 OutputColorMap[i].Blue = 0;
322 }
323
324 /* Average the colors in each entry to be the color to be used in the
325 * output color map, and plug it into the output color map itself. */
326 for (i = 0; i < NewColorMapSize; i++) {
327 if ((j = NewColorSubdiv[i].NumEntries) > 0) {
328 QuantizedColor = NewColorSubdiv[i].QuantizedColors;
329 Red = Green = Blue = 0;
330 while (QuantizedColor) {
331 QuantizedColor->NewColorIndex = i;
332 Red += QuantizedColor->RGB[0];
333 Green += QuantizedColor->RGB[1];
334 Blue += QuantizedColor->RGB[2];
335 QuantizedColor = QuantizedColor->Pnext;
336 }
337 OutputColorMap[i].Red = (Red << (8 - BITS_PER_PRIM_COLOR)) / j;
338 OutputColorMap[i].Green = (Green << (8 - BITS_PER_PRIM_COLOR)) / j;
339 OutputColorMap[i].Blue = (Blue << (8 - BITS_PER_PRIM_COLOR)) / j;
340 } else
341 fprintf(stderr,
342 "Null entry in quantized color map - that's weird.\n");
343 }
344
345 /* Finally scan the input buffer again and put the mapped index in the
346 * output buffer. */
347 MaxRGBError[0] = MaxRGBError[1] = MaxRGBError[2] = 0;
348 for (i = 0; i < (int)(Width * Height); i++) {
349 Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR)) <<
350 (2 * BITS_PER_PRIM_COLOR)) +
351 ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR)) <<
352 BITS_PER_PRIM_COLOR) +
353 (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR));
354 Index = ColorArrayEntries[Index].NewColorIndex;
355 OutputBuffer[i] = Index;
356 if (MaxRGBError[0] < ABS(OutputColorMap[Index].Red - RedInput[i]))
357 MaxRGBError[0] = ABS(OutputColorMap[Index].Red - RedInput[i]);
358 if (MaxRGBError[1] < ABS(OutputColorMap[Index].Green - GreenInput[i]))
359 MaxRGBError[1] = ABS(OutputColorMap[Index].Green - GreenInput[i]);
360 if (MaxRGBError[2] < ABS(OutputColorMap[Index].Blue - BlueInput[i]))
361 MaxRGBError[2] = ABS(OutputColorMap[Index].Blue - BlueInput[i]);
362 }
363
364 free((char *)ColorArrayEntries);
365
366 *ColorMapSize = NewColorMapSize;
367
368 return GIF_OK;
369 }
370
371 /******************************************************************************
372 * Routine to subdivide the RGB space recursively using median cut in each
373 * axes alternatingly until ColorMapSize different cubes exists.
374 * The biggest cube in one dimension is subdivide unless it has only one entry.
375 * Returns GIF_ERROR if failed, otherwise GIF_OK.
376 ******************************************************************************/
377 static int
SubdivColorMap(NewColorMapType * NewColorSubdiv,unsigned int ColorMapSize,unsigned int * NewColorMapSize)378 SubdivColorMap(NewColorMapType * NewColorSubdiv,
379 unsigned int ColorMapSize,
380 unsigned int *NewColorMapSize) {
381
382 int MaxSize;
383 unsigned int i, j, Index = 0, NumEntries, MinColor, MaxColor;
384 long Sum, Count;
385 QuantizedColorType *QuantizedColor, **SortArray;
386
387 while (ColorMapSize > *NewColorMapSize) {
388 /* Find candidate for subdivision: */
389 MaxSize = -1;
390 for (i = 0; i < *NewColorMapSize; i++) {
391 for (j = 0; j < 3; j++) {
392 if ((((int)NewColorSubdiv[i].RGBWidth[j]) > MaxSize) &&
393 (NewColorSubdiv[i].NumEntries > 1)) {
394 MaxSize = NewColorSubdiv[i].RGBWidth[j];
395 Index = i;
396 SortRGBAxis = j;
397 }
398 }
399 }
400
401 if (MaxSize == -1)
402 return GIF_OK;
403
404 /* Split the entry Index into two along the axis SortRGBAxis: */
405
406 /* Sort all elements in that entry along the given axis and split at
407 * the median. */
408 SortArray = (QuantizedColorType **)malloc(
409 sizeof(QuantizedColorType *) *
410 NewColorSubdiv[Index].NumEntries);
411 if (SortArray == NULL)
412 return GIF_ERROR;
413 for (j = 0, QuantizedColor = NewColorSubdiv[Index].QuantizedColors;
414 j < NewColorSubdiv[Index].NumEntries && QuantizedColor != NULL;
415 j++, QuantizedColor = QuantizedColor->Pnext)
416 SortArray[j] = QuantizedColor;
417
418 qsort(SortArray, NewColorSubdiv[Index].NumEntries,
419 sizeof(QuantizedColorType *), SortCmpRtn);
420
421 /* Relink the sorted list into one: */
422 for (j = 0; j < NewColorSubdiv[Index].NumEntries - 1; j++)
423 SortArray[j]->Pnext = SortArray[j + 1];
424 SortArray[NewColorSubdiv[Index].NumEntries - 1]->Pnext = NULL;
425 NewColorSubdiv[Index].QuantizedColors = QuantizedColor = SortArray[0];
426 free((char *)SortArray);
427
428 /* Now simply add the Counts until we have half of the Count: */
429 Sum = NewColorSubdiv[Index].Count / 2 - QuantizedColor->Count;
430 NumEntries = 1;
431 Count = QuantizedColor->Count;
432 while (QuantizedColor->Pnext != NULL &&
433 (Sum -= QuantizedColor->Pnext->Count) >= 0 &&
434 QuantizedColor->Pnext->Pnext != NULL) {
435 QuantizedColor = QuantizedColor->Pnext;
436 NumEntries++;
437 Count += QuantizedColor->Count;
438 }
439 /* Save the values of the last color of the first half, and first
440 * of the second half so we can update the Bounding Boxes later.
441 * Also as the colors are quantized and the BBoxes are full 0..255,
442 * they need to be rescaled.
443 */
444 MaxColor = QuantizedColor->RGB[SortRGBAxis]; /* Max. of first half */
445 /* coverity[var_deref_op] */
446 MinColor = QuantizedColor->Pnext->RGB[SortRGBAxis]; /* of second */
447 MaxColor <<= (8 - BITS_PER_PRIM_COLOR);
448 MinColor <<= (8 - BITS_PER_PRIM_COLOR);
449
450 /* Partition right here: */
451 NewColorSubdiv[*NewColorMapSize].QuantizedColors =
452 QuantizedColor->Pnext;
453 QuantizedColor->Pnext = NULL;
454 NewColorSubdiv[*NewColorMapSize].Count = Count;
455 NewColorSubdiv[Index].Count -= Count;
456 NewColorSubdiv[*NewColorMapSize].NumEntries =
457 NewColorSubdiv[Index].NumEntries - NumEntries;
458 NewColorSubdiv[Index].NumEntries = NumEntries;
459 for (j = 0; j < 3; j++) {
460 NewColorSubdiv[*NewColorMapSize].RGBMin[j] =
461 NewColorSubdiv[Index].RGBMin[j];
462 NewColorSubdiv[*NewColorMapSize].RGBWidth[j] =
463 NewColorSubdiv[Index].RGBWidth[j];
464 }
465 NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] =
466 NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] +
467 NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] - MinColor;
468 NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] = MinColor;
469
470 NewColorSubdiv[Index].RGBWidth[SortRGBAxis] =
471 MaxColor - NewColorSubdiv[Index].RGBMin[SortRGBAxis];
472
473 (*NewColorMapSize)++;
474 }
475
476 return GIF_OK;
477 }
478
479 /****************************************************************************
480 * Routine called by qsort to compare two entries.
481 ****************************************************************************/
482 static int
SortCmpRtn(const void * Entry1,const void * Entry2)483 SortCmpRtn(const void *Entry1,
484 const void *Entry2) {
485
486 return (*((QuantizedColorType **) Entry1))->RGB[SortRGBAxis] -
487 (*((QuantizedColorType **) Entry2))->RGB[SortRGBAxis];
488 }
489
490 /******************************************************************************
491 * Close output file (if open), and exit.
492 ******************************************************************************/
QuitGifError(GifFileType * GifFile)493 static void QuitGifError(GifFileType *GifFile)
494 {
495 fprintf(stderr, "Error writing GIF file\n");
496 if (GifFile != NULL) EGifCloseFile(GifFile, NULL);
497 }
498
499 int
write_gif(const char * filename,int width,int height,char * rgb)500 write_gif(const char *filename, int width, int height, char *rgb)
501 {
502 int i;
503 GifByteType *red, *green, *blue;
504
505 GifByteType *OutputBuffer = NULL;
506 ColorMapObject *OutputColorMap = NULL;
507
508 GifFileType *GifFile;
509 GifByteType *Ptr;
510
511 bool GifTestExistance = false;
512
513 ColorMapSize = 1 << ExpNumOfColors;
514
515 #if GIFLIB_MAJOR >= 5
516 OutputColorMap = GifMakeMapObject(ColorMapSize, NULL);
517 #else
518 OutputColorMap = MakeMapObject(ColorMapSize, NULL);
519 #endif
520
521 if (OutputColorMap == NULL ||
522 (OutputBuffer = (GifByteType *) malloc(width * height *
523 sizeof(GifByteType))) == NULL)
524 {
525 fprintf(stderr,"Failed to allocate memory required, aborted.");
526 return(0);
527 }
528
529 red = malloc(width * height * sizeof(GifByteType));
530 green = malloc(width * height * sizeof(GifByteType));
531 blue = malloc(width * height * sizeof(GifByteType));
532 if (red == NULL || green == NULL || blue == NULL)
533 {
534 fprintf(stderr, "Can't allocate memory for GIF file.\n");
535 return(0);
536 }
537 for (i = 0; i < width * height; i++)
538 {
539 red[i] = (GifByteType) rgb[3*i ];
540 green[i] = (GifByteType) rgb[3*i+1];
541 blue[i] = (GifByteType) rgb[3*i+2];
542 }
543
544 if (QuantizeBuffer(width, height, &ColorMapSize,
545 red, green, blue,
546 OutputBuffer, OutputColorMap->Colors) == GIF_ERROR)
547
548 {
549 QuitGifError(NULL);
550 return(0);
551 }
552
553 free(red);
554 free(green);
555 free(blue);
556
557 Ptr = OutputBuffer;
558
559 #if GIFLIB_MAJOR >= 5
560 GifFile = EGifOpenFileName(filename, GifTestExistance, NULL);
561 #else
562 GifFile = EGifOpenFileName(filename, GifTestExistance);
563 #endif
564 /* Open stdout for the output file: */
565 if (GifFile == NULL)
566 {
567 QuitGifError(GifFile);
568 return(0);
569 }
570
571 if (EGifPutScreenDesc(GifFile,
572 width, height, ExpNumOfColors, 0,
573 OutputColorMap) == GIF_ERROR ||
574 EGifPutImageDesc(GifFile,
575 0, 0, width, height, false, NULL) == GIF_ERROR)
576
577 {
578 QuitGifError(GifFile);
579 return(0);
580 }
581
582 for (i = 0; i < height; i++) {
583 if (EGifPutLine(GifFile, Ptr, width) == GIF_ERROR)
584
585 {
586 QuitGifError(GifFile);
587 return(0);
588 }
589 Ptr += width;
590 }
591
592 if (EGifCloseFile(GifFile, NULL) == GIF_ERROR)
593
594 {
595 QuitGifError(GifFile);
596 return(0);
597 }
598
599 return(1);
600 }
601