Initial revision
[imager.git] / gif.c
1 #include "image.h"
2 #include <gif_lib.h>
3
4
5 #if IM_GIFMAJOR >= 4
6
7 struct gif_scalar_info {
8   char *data;
9   int length;
10   int cpos;
11 };
12
13 int
14 my_gif_inputfunc(GifFileType* gft, GifByteType *buf,int length) {
15   struct gif_scalar_info *gsi=(struct gif_scalar_info *)gft->UserData;
16   /*   fprintf(stderr,"my_gif_inputfunc: length=%d cpos=%d tlength=%d\n",length,gsi->cpos,gsi->length); */
17
18   if (gsi->cpos == gsi->length) return 0;
19   if (gsi->cpos+length > gsi->length) length=gsi->length-gsi->cpos; /* Don't read too much */
20   memcpy(buf,gsi->data+gsi->cpos,length);
21   gsi->cpos+=length;
22   return length;
23 }
24
25 #endif
26
27 /*
28   This file needs a complete rewrite 
29
30   This file needs a complete rewrite 
31
32   Maybe not anymore, though reading still needs to support reading
33   all those gif properties.
34 */
35
36 /* Make some variables global, so we could access them faster: */
37
38 static int
39     ImageNum = 0,
40     BackGround = 0,
41     ColorMapSize = 0,
42     InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
43     InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */
44 static ColorMapObject *ColorMap;
45
46 i_img *
47 i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
48   i_img *im;
49   int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
50   GifRecordType RecordType;
51   GifByteType *Extension;
52   
53   GifRowType GifRow;
54   static GifColorType *ColorMapEntry;
55   i_color col;
56
57   /*  unsigned char *Buffer, *BufferP; */
58
59   mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours));
60
61   BackGround = GifFile->SBackGroundColor;
62   ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
63   ColorMapSize = ColorMap->ColorCount;
64
65   /* **************************************** */
66   if(colour_table != NULL) {
67     int q;
68     *colour_table=mymalloc(sizeof(int *) * ColorMapSize * 3);
69     if(*colour_table == NULL) 
70       m_fatal(0,"Failed to allocate memory for GIF colour table, aborted."); 
71     
72     memset(*colour_table, 0, sizeof(int *) * ColorMapSize * 3);
73
74     for(q=0; q<ColorMapSize; q++) {
75       ColorMapEntry = &ColorMap->Colors[q];
76       (*colour_table)[q*3 + 0]=ColorMapEntry->Red;
77       (*colour_table)[q*3 + 1]=ColorMapEntry->Green;
78       (*colour_table)[q*3 + 2]=ColorMapEntry->Blue;
79     }
80   }
81
82   if(colours != NULL) {
83     *colours = ColorMapSize;
84   }
85   
86   /* **************************************** */
87   im=i_img_empty_ch(NULL,GifFile->SWidth,GifFile->SHeight,3);
88   
89   Size = GifFile->SWidth * sizeof(GifPixelType); 
90   
91   if ((GifRow = (GifRowType) mymalloc(Size)) == NULL)
92     m_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */
93
94   for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor;
95   
96   /* Scan the content of the GIF file and load the image(s) in: */
97   do {
98     if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
99       PrintGifError();
100       exit(-1);
101     }
102     
103     switch (RecordType) {
104     case IMAGE_DESC_RECORD_TYPE:
105       if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
106         PrintGifError();
107         exit(-1);
108       }
109       Row = GifFile->Image.Top; /* Image Position relative to Screen. */
110       Col = GifFile->Image.Left;
111       Width = GifFile->Image.Width;
112       Height = GifFile->Image.Height;
113       ImageNum++;
114       mm_log((1,"i_readgif: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
115
116       if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
117           GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
118         fprintf(stderr, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
119         return(0);
120       }
121       if (GifFile->Image.Interlace) {
122
123         for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
124           Count++;
125           if (DGifGetLine(GifFile, &GifRow[Col], Width) == GIF_ERROR) {
126             mm_log((1,"fatal"));
127             exit(-1);
128           }
129           
130           for (x = 0; x < GifFile->SWidth; x++) {
131             ColorMapEntry = &ColorMap->Colors[GifRow[x]];
132             col.rgb.r = ColorMapEntry->Red;
133             col.rgb.g = ColorMapEntry->Green;
134             col.rgb.b = ColorMapEntry->Blue;
135             i_ppix(im,x,j,&col);
136           }
137           
138         }
139       }
140       else {
141         for (i = 0; i < Height; i++) {
142           if (DGifGetLine(GifFile, &GifRow[Col], Width) == GIF_ERROR) {
143             mm_log((1,"fatal\n"));
144             exit(-1);
145           }
146
147           for (x = 0; x < GifFile->SWidth; x++) {
148             ColorMapEntry = &ColorMap->Colors[GifRow[x]];
149             col.rgb.r = ColorMapEntry->Red;
150             col.rgb.g = ColorMapEntry->Green;
151             col.rgb.b = ColorMapEntry->Blue;
152             i_ppix(im,x,Row,&col);
153           }
154           Row++;
155         }
156       }
157       break;
158     case EXTENSION_RECORD_TYPE:
159       /* Skip any extension blocks in file: */
160       if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
161         mm_log((1,"fatal\n"));
162         exit(-1);
163       }
164       while (Extension != NULL) {
165         if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
166           mm_log((1,"fatal\n"));
167           exit(-1);
168         }
169       }
170       break;
171     case TERMINATE_RECORD_TYPE:
172       break;
173     default:                /* Should be traps by DGifGetRecordType. */
174       break;
175     }
176   } while (RecordType != TERMINATE_RECORD_TYPE);
177   
178   myfree(GifRow);
179   
180   if (DGifCloseFile(GifFile) == GIF_ERROR) {
181     PrintGifError();
182     exit(-1);
183   }
184   return im;
185 }
186
187
188 i_img *
189 i_readgif(int fd, int **colour_table, int *colours) {
190   GifFileType *GifFile;
191   
192   mm_log((1,"i_readgif(fd %d, colour_table %p, colours %p)\n", fd, colour_table, colours));
193
194   if ((GifFile = DGifOpenFileHandle(fd)) == NULL) {
195     mm_log((1,"i_readgif: Unable to open file\n"));
196     return NULL;
197   }
198
199   return i_readgif_low(GifFile, colour_table, colours);
200 }
201
202
203 undef_int
204 i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[])
205 {
206   i_color colors[256];
207   i_quantize quant;
208   i_gif_opts opts;
209   
210   memset(&quant, 0, sizeof(quant));
211   memset(&opts, 0, sizeof(opts));
212   quant.make_colors = mc_addi;
213   quant.mc_colors = colors;
214   quant.mc_size = 1<<max_colors;
215   quant.mc_count = fixedlen;
216   memcpy(colors, fixed, fixedlen * sizeof(i_color));
217   quant.translate = pt_perturb;
218   quant.perturb = pixdev;
219   return i_writegif_gen(&quant, fd, &im, 1, &opts);
220 }
221
222 undef_int
223 i_writegifmc(i_img *im, int fd, int max_colors) {
224   i_color colors[256];
225   i_quantize quant;
226   i_gif_opts opts;
227   
228   memset(&quant, 0, sizeof(quant));
229   memset(&opts, 0, sizeof(opts));
230   quant.make_colors = mc_none; /* ignored for pt_giflib */
231   quant.mc_colors = colors;
232   quant.mc_size = 1 << max_colors;
233   quant.mc_count = 0;
234   quant.translate = pt_giflib;
235   return i_writegif_gen(&quant, fd, &im, 1, &opts);
236 }
237
238 #if 1
239
240 undef_int
241 i_writegifex(i_img *im, int fd) {
242   return 0;
243 }
244
245 #else
246
247 /* I don't think this works
248    note that RedBuffer is never allocated - TC
249 */
250
251 undef_int
252 i_writegifex(i_img *im,int fd) {
253   int colors, xsize, ysize, channels;
254   int x,y,ColorMapSize;
255   unsigned long Size;
256
257   struct octt *ct;
258
259   GifByteType *RedBuffer = NULL, *GreenBuffer = NULL, *BlueBuffer = NULL,*OutputBuffer = NULL;
260   ColorMapObject *OutputColorMap = NULL;
261   GifFileType *GifFile;
262   GifByteType *Ptr;
263   
264   i_color val;
265
266   mm_log((1,"i_writegif(0x%x,fd %d)\n",im,fd));
267   
268   if (!(im->channels==1 || im->channels==3)) { fprintf(stderr,"Unable to write gif, improper colorspace.\n"); exit(3); }
269
270   xsize=im->xsize;
271   ysize=im->ysize;
272   channels=im->channels;
273
274   colors=0;
275   ct=octt_new();
276
277   colors=0;
278   for(x=0;x<xsize;x++) for(y=0;y<ysize;y++) {
279     i_gpix(im,x,y,&val);
280     colors+=octt_add(ct,val.rgb.r,val.rgb.g,val.rgb.b);
281     /*  if (colors > maxc) { octt_delete(ct); } 
282         We'll just bite the bullet */
283   }
284
285   ColorMapSize = (colors > 256) ? 256 : colors;
286   
287   Size = ((long) im->xsize) * im->ysize * sizeof(GifByteType);
288   
289   if ((OutputColorMap = MakeMapObject(ColorMapSize, NULL)) == NULL)
290     m_fatal(0,"Failed to allocate memory for Output colormap.");
291   if ((OutputBuffer = (GifByteType *) mymalloc(im->xsize * im->ysize * sizeof(GifByteType))) == NULL)
292     m_fatal(0,"Failed to allocate memory for output buffer.");
293   
294   if (QuantizeBuffer(im->xsize, im->ysize, &ColorMapSize, RedBuffer, GreenBuffer, BlueBuffer,
295                      OutputBuffer, OutputColorMap->Colors) == GIF_ERROR) {
296     mm_log((1,"Error in QuantizeBuffer, unable to write image.\n"));
297     return(0);
298   }
299
300
301   myfree(RedBuffer);
302   if (im->channels == 3) { myfree(GreenBuffer); myfree(BlueBuffer); }
303   
304   if ((GifFile = EGifOpenFileHandle(fd)) == NULL) {
305     mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
306     return(0);
307   }
308   
309   if (EGifPutScreenDesc(GifFile,im->xsize, im->ysize, colors, 0,OutputColorMap) == GIF_ERROR ||
310       EGifPutImageDesc(GifFile,0, 0, im->xsize, im->ysize, FALSE, NULL) == GIF_ERROR) {
311     mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
312     if (GifFile != NULL) EGifCloseFile(GifFile);
313     return(0);
314   }
315
316   Ptr = OutputBuffer;
317
318   for (y = 0; y < im->ysize; y++) {
319     if (EGifPutLine(GifFile, Ptr, im->xsize) == GIF_ERROR) {
320       mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
321       if (GifFile != NULL) EGifCloseFile(GifFile);
322       return(0);
323     }
324     
325     Ptr += im->xsize;
326   }
327   
328   if (EGifCloseFile(GifFile) == GIF_ERROR) {
329     mm_log((1,"Error in EGifCloseFile, unable to write image.\n"));
330     return(0);
331   }
332   return(1);
333 }
334
335 #endif
336
337 i_img*
338 i_readgif_scalar(char *data, int length, int **colour_table, int *colours) {
339 #if IM_GIFMAJOR >= 4
340   GifFileType *GifFile;
341   
342   struct gif_scalar_info gsi;
343
344   gsi.cpos=0;
345   gsi.length=length;
346   gsi.data=data;
347
348   mm_log((1,"i_readgif_scalar(char* data, int length, colour_table %p, colours %p)\n", data, length, colour_table, colours));
349   if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) {
350     mm_log((1,"i_readgif_scalar: Unable to open scalar datasource.\n"));
351     return NULL;
352   }
353
354   return i_readgif_low(GifFile, colour_table, colours);
355 #else
356   return NULL;
357 #endif
358 }
359
360 #if IM_GIFMAJOR >= 4
361
362 static int
363 gif_read_callback(GifFileType *gft, GifByteType *buf, int length) {
364   return i_gen_reader((i_gen_read_data *)gft->UserData, buf, length);
365 }
366
367 #endif
368
369 i_img*
370 i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours) {
371 #if IM_GIFMAJOR >= 4
372   GifFileType *GifFile;
373   i_img *result;
374   
375   i_gen_read_data *gci = i_gen_read_data_new(cb, userdata);
376
377   mm_log((1,"i_readgif_callback(callback %p, userdata %p, colour_table %p, colours %p)\n", cb, userdata, colour_table, colours));
378   if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) {
379     mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n"));
380     myfree(gci);
381     return NULL;
382   }
383
384   result = i_readgif_low(GifFile, colour_table, colours);
385   free_gen_read_data(gci);
386
387   return result;
388 #else
389   return NULL;
390 #endif
391 }
392
393 /* low level image write 
394    writes in interlace if that's what was requested */
395 static undef_int 
396 do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) {
397   if (opts->interlace) {
398     int i, j;
399     for (i = 0; i < 4; ++i) {
400       for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
401         if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) {
402           mm_log((1, "Error in EGifPutLine\n"));
403           EGifCloseFile(gf);
404           return 0;
405         }
406       }
407     }
408   }
409   else {
410     int y;
411     for (y = 0; y < img->ysize; ++y) {
412       if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
413         mm_log((1, "Error in EGifPutLine\n"));
414         EGifCloseFile(gf);
415         return 0;
416       }
417       data += img->xsize;
418     }
419   }
420
421   return 1;
422 }
423
424 static int do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
425 {
426   unsigned char gce[4] = {0};
427   int want_gce = 0;
428   if (want_trans) {
429     gce[0] |= 1;
430     gce[3] = trans_index;
431     ++want_gce;
432   }
433   if (index < opts->delay_count) {
434     gce[1] = opts->delays[index] % 256;
435     gce[2] = opts->delays[index] / 256;
436     ++want_gce;
437   }
438   if (index < opts->user_input_count) {
439     if (opts->user_input_flags[index])
440       gce[0] |= 2;
441     ++want_gce;
442   }
443   if (index < opts->disposal_count) {
444     gce[0] |= (opts->disposal[index] & 3) << 2;
445     ++want_gce;
446   }
447   if (want_gce) {
448     return EGifPutExtension(gf, 0xF9, sizeof(gce), gce) != GIF_ERROR;
449   }
450   return 1;
451 }
452
453 /* add the Netscape2.0 loop extension block, if requested */
454 static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
455 {
456 #if 0
457   /* EGifPutExtension() doesn't appear to handle application 
458      extension blocks in any way
459      Since giflib wraps the fd with a FILE * (and puts that in its
460      private data), we can't do an end-run and write the data 
461      directly to the fd.
462      There's no open interface that takes a FILE * either, so we 
463      can't workaround it that way either.
464      If giflib's callback interface wasn't broken by default, I'd 
465      force file writes to use callbacks, but it is broken by default.
466   */
467   if (opts->loop_count) {
468     unsigned char nsle[15] = "NETSCAPE2.0";
469     nsle[11] = 3;
470     nsle[12] = 1;
471     nsle[13] = opts->loop_count % 256;
472     nsle[14] = opts->loop_count / 256;
473     return EGifPutExtension(gf, 0xFF, sizeof(nsle), nsle) != GIF_ERROR;
474   }
475 #endif
476   return 1;
477 }
478
479 static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
480                                     int want_trans) {
481   GifColorType colors[256];
482   int i;
483   int size = quant->mc_count;
484   int map_size;
485
486   for (i = 0; i < quant->mc_count; ++i) {
487     colors[i].Red = quant->mc_colors[i].rgb.r;
488     colors[i].Green = quant->mc_colors[i].rgb.g;
489     colors[i].Blue = quant->mc_colors[i].rgb.b;
490   }
491   if (want_trans) {
492     colors[size].Red = opts->tran_color.rgb.r;
493     colors[size].Green = opts->tran_color.rgb.g;
494     colors[size].Blue = opts->tran_color.rgb.b;
495     ++size;
496   }
497   map_size = 1;
498   while (map_size < size)
499     map_size <<= 1;
500   return MakeMapObject(map_size, colors);
501 }
502
503 /* we need to call EGifSetGifVersion() before opening the file - put that
504    common code here
505 */
506 static void gif_set_version(i_quantize *quant, i_gif_opts *opts) {
507   /* the following crashed giflib
508      the EGifSetGifVersion() is seriously borked in giflib
509      it's less borked in the ungiflib beta, but we don't have a mechanism
510      to distinguish them
511      if (opts->delay_count
512      || opts->user_input_count
513      || opts->disposal_count
514      || opts->loop_count
515      || quant->transp != tr_none)
516      EGifSetGifVersion("89a");
517      else
518      EGifSetGifVersion("87a");
519   */
520 }
521
522 static undef_int
523 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
524                i_gif_opts *opts) {
525   unsigned char *result;
526   int color_bits;
527   ColorMapObject *map;
528   int scrw = 0, scrh = 0;
529   int imgn, orig_count, orig_size;
530   int posx, posy;
531
532   mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d, opts %p)\n", 
533           quant, gf, imgs, count, opts));
534
535   /**((char *)0) = 1;*/
536   /* sanity is nice */
537   if (quant->mc_size > 256) 
538     quant->mc_size = 256;
539   if (quant->mc_count > quant->mc_size)
540     quant->mc_count = quant->mc_size;
541
542   for (imgn = 0; imgn < count; ++imgn) {
543     if (imgn < opts->position_count) {
544       if (imgs[imgn]->xsize + opts->positions[imgn].x > scrw)
545         scrw = imgs[imgn]->xsize + opts->positions[imgn].x;
546       if (imgs[imgn]->ysize + opts->positions[imgn].y > scrw)
547         scrh = imgs[imgn]->ysize + opts->positions[imgn].y;
548     }
549     else {
550       if (imgs[imgn]->xsize > scrw)
551         scrw = imgs[imgn]->xsize;
552       if (imgs[imgn]->ysize > scrh)
553         scrh = imgs[imgn]->ysize;
554     }
555   }
556
557
558   if (count <= 0)
559     return 0; /* what are you smoking? */
560
561   orig_count = quant->mc_count;
562   orig_size = quant->mc_size;
563
564   if (opts->each_palette) {
565     int want_trans;
566
567     /* we always generate a global palette - this lets systems with a 
568        broken giflib work */
569     quant_makemap(quant, imgs, 1);
570     result = quant_translate(quant, imgs[0]);
571
572     want_trans = quant->transp != tr_none 
573       && imgs[0]->channels == 4 
574       && quant->mc_count < 256;
575     if (want_trans)
576       quant_transparent(quant, result, imgs[0], quant->mc_count);
577     
578     if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
579       myfree(result);
580       EGifCloseFile(gf);
581       mm_log((1, "Error in MakeMapObject."));
582       return 0;
583     }
584
585     color_bits = 1;
586     while (quant->mc_size > (1 << color_bits))
587       ++color_bits;
588   
589     if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 0, map) == GIF_ERROR) {
590       FreeMapObject(map);
591       myfree(result);
592       EGifCloseFile(gf);
593       mm_log((1, "Error in EGifPutScreenDesc."));
594       return 0;
595     }
596     FreeMapObject(map);
597
598     if (!do_ns_loop(gf, opts))
599       return 0;
600
601     if (!do_gce(gf, 0, opts, want_trans, quant->mc_count)) {
602       myfree(result);
603       EGifCloseFile(gf);
604       return 0;
605     }
606     if (opts->position_count) {
607       posx = opts->positions[0].x;
608       posy = opts->positions[0].y;
609     }
610     else
611       posx = posy = 0;
612     if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
613                          opts->interlace, NULL) == GIF_ERROR) {
614       EGifCloseFile(gf);
615       mm_log((1, "Error in EGifPutImageDesc."));
616       return 0;
617     }
618     if (!do_write(gf, opts, imgs[0], result)) {
619       EGifCloseFile(gf);
620       myfree(result);
621       return 0;
622     }
623     for (imgn = 1; imgn < count; ++imgn) {
624       quant->mc_count = orig_count;
625       quant->mc_size = orig_size;
626       quant_makemap(quant, imgs+imgn, 1);
627       result = quant_translate(quant, imgs[imgn]);
628       want_trans = quant->transp != tr_none 
629         && imgs[imgn]->channels == 4 
630         && quant->mc_count < 256;
631       if (want_trans)
632         quant_transparent(quant, result, imgs[imgn], quant->mc_count);
633       
634       if (!do_gce(gf, imgn, opts, want_trans, quant->mc_count)) {
635         myfree(result);
636         EGifCloseFile(gf);
637         return 0;
638       }
639       if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
640         myfree(result);
641         EGifCloseFile(gf);
642         mm_log((1, "Error in MakeMapObject."));
643         return 0;
644       }
645       if (imgn < opts->position_count) {
646         posx = opts->positions[imgn].x;
647         posy = opts->positions[imgn].y;
648       }
649       else
650         posx = posy = 0;
651       if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, 
652                            imgs[imgn]->ysize, opts->interlace, 
653                            map) == GIF_ERROR) {
654         myfree(result);
655         FreeMapObject(map);
656         EGifCloseFile(gf);
657         mm_log((1, "Error in EGifPutImageDesc."));
658         return 0;
659       }
660       FreeMapObject(map);
661       
662       if (!do_write(gf, opts, imgs[imgn], result)) {
663         EGifCloseFile(gf);
664         myfree(result);
665         return 0;
666       }
667       myfree(result);
668     }
669   }
670   else {
671     int want_trans;
672
673     mm_log((1, "i_writegif_low: GOT HERE\n"));
674
675     /* handle the first image separately - since we allow giflib
676        conversion and giflib doesn't give us a separate function to build
677        the colormap. */
678      
679     /* produce a colour map */
680     quant_makemap(quant, imgs, count);
681     result = quant_translate(quant, imgs[0]);
682
683     /* get a palette entry for the transparency iff we have an image
684        with an alpha channel */
685     want_trans = 0;
686     for (imgn = 0; imgn < count; ++imgn) {
687       if (imgs[imgn]->channels == 4) {
688         ++want_trans;
689         break;
690       }
691     }
692     want_trans = want_trans && quant->transp != tr_none && quant->mc_count < 256;
693     if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
694       myfree(result);
695       EGifCloseFile(gf);
696       mm_log((1, "Error in MakeMapObject"));
697       return 0;
698     }
699     color_bits = 1;
700     while (quant->mc_count > (1 << color_bits))
701       ++color_bits;
702
703     if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 0, map) == GIF_ERROR) {
704       FreeMapObject(map);
705       myfree(result);
706       EGifCloseFile(gf);
707       mm_log((1, "Error in EGifPutScreenDesc."));
708       return 0;
709     }
710     FreeMapObject(map);
711
712     if (!do_ns_loop(gf, opts))
713       return 0;
714
715     if (!do_gce(gf, 0, opts, want_trans, quant->mc_count)) {
716       myfree(result);
717       EGifCloseFile(gf);
718       return 0;
719     }
720     if (opts->position_count) {
721       posx = opts->positions[0].x;
722       posy = opts->positions[0].y;
723     }
724     else
725       posx = posy = 0;
726     if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
727                          opts->interlace, NULL) == GIF_ERROR) {
728       EGifCloseFile(gf);
729       mm_log((1, "Error in EGifPutImageDesc."));
730       return 0;
731     }
732     if (want_trans && imgs[0]->channels == 4) 
733       quant_transparent(quant, result, imgs[0], quant->mc_count);
734
735     if (!do_write(gf, opts, imgs[0], result)) {
736       EGifCloseFile(gf);
737       myfree(result);
738       return 0;
739     }
740     myfree(result);
741
742     for (imgn = 1; imgn < count; ++imgn) {
743       int local_trans;
744       result = quant_translate(quant, imgs[imgn]);
745       local_trans = want_trans && imgs[imgn]->channels == 4;
746       if (local_trans)
747         quant_transparent(quant, result, imgs[imgn], quant->mc_count);
748       if (!do_gce(gf, imgn, opts, local_trans, quant->mc_count)) {
749         myfree(result);
750         EGifCloseFile(gf);
751         return 0;
752       }
753       if (imgn < opts->position_count) {
754         posx = opts->positions[imgn].x;
755         posy = opts->positions[imgn].y;
756       }
757       else
758         posx = posy = 0;
759       if (EGifPutImageDesc(gf, posx, posy, 
760                            imgs[imgn]->xsize, imgs[imgn]->ysize, 
761                            opts->interlace, NULL) == GIF_ERROR) {
762         myfree(result);
763         EGifCloseFile(gf);
764         mm_log((1, "Error in EGifPutImageDesc."));
765         return 0;
766       }
767       if (!do_write(gf, opts, imgs[imgn], result)) {
768         EGifCloseFile(gf);
769         myfree(result);
770         return 0;
771       }
772       myfree(result);
773     }
774   }
775   if (EGifCloseFile(gf) == GIF_ERROR) {
776     mm_log((1, "Error in EGifCloseFile\n"));
777     return 0;
778   }
779
780   return 1;
781 }
782
783 undef_int
784 i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, 
785                i_gif_opts *opts) {
786   GifFileType *gf;
787
788   mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d, opts %p)\n", 
789           quant, fd, imgs, count, opts));
790
791   gif_set_version(quant, opts);
792
793   mm_log((1, "i_writegif_gen: set ops\n"));
794
795   if ((gf = EGifOpenFileHandle(fd)) == NULL) {
796     mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
797     return 0;
798   }
799
800   return i_writegif_low(quant, gf, imgs, count, opts);
801 }
802
803 #if IM_GIFMAJOR >= 4
804
805 static int gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
806 {
807   i_gen_write_data *gwd = (i_gen_write_data *)gf->UserData;
808
809   return i_gen_writer(gwd, data, size) ? size : 0;
810 }
811
812 #endif
813
814 undef_int
815 i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
816                     int maxlength, i_img **imgs, int count, i_gif_opts *opts)
817 {
818 #if IM_GIFMAJOR >= 4
819   GifFileType *gf;
820   i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength);
821   /* giflib declares this incorrectly as EgifOpen */
822   extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc);
823   int result;
824
825   mm_log((1, "i_writegif_callback(quant %p, i_write_callback_t %p, userdata $p, maxlength %d, imgs %p, count %d, opts %p)\n", 
826           quant, cb, userdata, maxlength, imgs, count, opts));
827   
828   if ((gf = EGifOpen(gwd, &gif_writer_callback)) == NULL) {
829     mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
830     free_gen_write_data(gwd, 0);
831     return 0;
832   }
833
834   result = i_writegif_low(quant, gf, imgs, count, opts);
835   return free_gen_write_data(gwd, result);
836 #else
837   return 0;
838 #endif
839 }