Added checks if there is no global colormap and a local colormap to avoid
[imager.git] / gif.c
CommitLineData
02d1d628
AMH
1#include "image.h"
2#include <gif_lib.h>
3
a3923855
TC
4/*
5=head1 NAME
6
7gif.c - read and write gif files for Imager
8
9=head1 SYNOPSIS
10
11 i_img *img;
12 i_img *imgs[count];
13 int fd;
14 int *colour_table,
15 int colours;
16 int max_colours; // number of bits per colour
17 int pixdev; // how much noise to add
18 i_color fixed[N]; // fixed palette entries
19 int fixedlen; // number of fixed colours
20 int success; // non-zero on success
3bb1c1f3 21 char *data; // a GIF file in memory
a3923855
TC
22 int length; // how big data is
23 int reader(char *, char *, int, int);
24 int writer(char *, char *, int);
25 char *userdata; // user's data, whatever it is
26 i_quantize quant;
27 i_gif_opts opts;
28
29 img = i_readgif(fd, &colour_table, &colours);
30 success = i_writegif(img, fd, max_colours, pixdev, fixedlen, fixed);
31 success = i_writegifmc(img, fd, max_colours);
32 img = i_readgif_scalar(data, length, &colour_table, &colours);
33 img = i_readgif_callback(cb, userdata, &colour_table, &colours);
34 success = i_writegif_gen(&quant, fd, imgs, count, &opts);
35 success = i_writegif_callback(&quant, writer, userdata, maxlength,
36 imgs, count, &opts);
37
38=head1 DESCRIPTION
39
40This source file provides the C level interface to reading and writing
41GIF files for Imager.
42
43This has been tested with giflib 3 and 4, though you lose the callback
44functionality with giflib3.
45
46=head1 REFERENCE
47
48=over
49
50=cut
51*/
02d1d628 52
c48818b1
TC
53static char const *gif_error_msg(int code);
54static void gif_push_error();
55
02d1d628
AMH
56#if IM_GIFMAJOR >= 4
57
a3923855
TC
58/*
59=item gif_scalar_info
60
61Internal. A structure passed to the reader function used for reading
62GIFs from scalars.
63
64Used with giflib 4 and later.
65
66=cut
67*/
68
02d1d628
AMH
69struct gif_scalar_info {
70 char *data;
71 int length;
72 int cpos;
73};
74
a3923855
TC
75/*
76=item my_gif_inputfunc(GifFileType *gft, GifByteType *buf, int length)
77
78Internal. The reader callback passed to giflib.
79
80Used with giflib 4 and later.
81
82=cut
83*/
84
02d1d628
AMH
85int
86my_gif_inputfunc(GifFileType* gft, GifByteType *buf,int length) {
87 struct gif_scalar_info *gsi=(struct gif_scalar_info *)gft->UserData;
88 /* fprintf(stderr,"my_gif_inputfunc: length=%d cpos=%d tlength=%d\n",length,gsi->cpos,gsi->length); */
89
90 if (gsi->cpos == gsi->length) return 0;
91 if (gsi->cpos+length > gsi->length) length=gsi->length-gsi->cpos; /* Don't read too much */
92 memcpy(buf,gsi->data+gsi->cpos,length);
93 gsi->cpos+=length;
94 return length;
95}
96
97#endif
98
99/*
100 This file needs a complete rewrite
101
102 This file needs a complete rewrite
103
104 Maybe not anymore, though reading still needs to support reading
105 all those gif properties.
106*/
107
108/* Make some variables global, so we could access them faster: */
109
110static int
111 ImageNum = 0,
112 BackGround = 0,
113 ColorMapSize = 0,
114 InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
115 InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
7828ca98 116
02d1d628
AMH
117static ColorMapObject *ColorMap;
118
7828ca98
AMH
119
120static
121void
122i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colormap) {
123 GifColorType *mapentry;
124 int q;
125 int colormapsize = colormap->ColorCount;
126
127 if(colours) *colours = colormapsize;
128 if(!colour_table) return;
129
130 *colour_table = mymalloc(sizeof(int *) * colormapsize * 3);
131 memset(*colour_table, 0, sizeof(int *) * colormapsize * 3);
132
133 for(q=0; q<ColorMapSize; q++) {
134 mapentry = &colormap->Colors[q];
135 (*colour_table)[q*3 + 0] = mapentry->Red;
136 (*colour_table)[q*3 + 1] = mapentry->Green;
137 (*colour_table)[q*3 + 2] = mapentry->Blue;
138 }
139}
140
141
a3923855
TC
142/*
143=item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours)
144
145Internal. Low-level function for reading a GIF file. The caller must
146create the appropriate GifFileType object and pass it in.
147
148=cut
149*/
7828ca98 150
02d1d628
AMH
151i_img *
152i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
153 i_img *im;
154 int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
7828ca98
AMH
155 int cmapcnt = 0;
156
02d1d628
AMH
157 GifRecordType RecordType;
158 GifByteType *Extension;
159
160 GifRowType GifRow;
161 static GifColorType *ColorMapEntry;
162 i_color col;
163
02d1d628
AMH
164 mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours));
165
166 BackGround = GifFile->SBackGroundColor;
167 ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
02d1d628 168
7828ca98
AMH
169 if (ColorMap) {
170 ColorMapSize = ColorMap->ColorCount;
171 i_colortable_copy(colour_table, colours, ColorMap);
172 cmapcnt++;
02d1d628 173 }
02d1d628 174
7828ca98
AMH
175
176 im = i_img_empty_ch(NULL,GifFile->SWidth,GifFile->SHeight,3);
177
02d1d628
AMH
178 Size = GifFile->SWidth * sizeof(GifPixelType);
179
180 if ((GifRow = (GifRowType) mymalloc(Size)) == NULL)
181 m_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */
182
183 for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor;
184
185 /* Scan the content of the GIF file and load the image(s) in: */
186 do {
187 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
77fc6afd
TC
188 gif_push_error();
189 i_push_error(0, "Unable to get record type");
7828ca98 190 if (colour_table) free(colour_table); /* FIXME: Isn't this an error? */
77fc6afd
TC
191 i_img_destroy(im);
192 DGifCloseFile(GifFile);
193 return NULL;
02d1d628
AMH
194 }
195
196 switch (RecordType) {
197 case IMAGE_DESC_RECORD_TYPE:
198 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
77fc6afd
TC
199 gif_push_error();
200 i_push_error(0, "Unable to get image descriptor");
7828ca98 201 if (colour_table) free(colour_table); /* FIXME: Isn't this an error? */
77fc6afd
TC
202 i_img_destroy(im);
203 DGifCloseFile(GifFile);
204 return NULL;
02d1d628 205 }
7828ca98
AMH
206
207 if ( cmapcnt == 0) {
208 if (ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) ) {
209 mm_log((1, "Adding local colormap\n"));
210 ColorMapSize = ColorMap->ColorCount;
211 i_colortable_copy(colour_table, colours, ColorMap);
212 cmapcnt++;
213 } else {
214 /* No colormap and we are about to read in the image - abandon for now */
215 mm_log((1, "Going in with no colormap\n"));
216 i_push_error(0, "Image does not have a local or a global color map");
217 if (colour_table) free(colour_table); /* FIXME: Isn't this an error? */
218 i_img_destroy(im);
219 DGifCloseFile(GifFile);
220 return NULL;
221 }
222 }
02d1d628
AMH
223 Row = GifFile->Image.Top; /* Image Position relative to Screen. */
224 Col = GifFile->Image.Left;
225 Width = GifFile->Image.Width;
226 Height = GifFile->Image.Height;
227 ImageNum++;
228 mm_log((1,"i_readgif: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
229
230 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
231 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
77fc6afd 232 i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
7828ca98 233 if (colour_table) free(colour_table); /* FIXME: Yet again */
77fc6afd
TC
234 i_img_destroy(im);
235 DGifCloseFile(GifFile);
02d1d628
AMH
236 return(0);
237 }
238 if (GifFile->Image.Interlace) {
239
240 for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
241 Count++;
242 if (DGifGetLine(GifFile, &GifRow[Col], Width) == GIF_ERROR) {
77fc6afd
TC
243 gif_push_error();
244 i_push_error(0, "Reading GIF line");
245 if (colour_table)
246 free(colour_table);
247 i_img_destroy(im);
248 DGifCloseFile(GifFile);
249 return NULL;
02d1d628
AMH
250 }
251
252 for (x = 0; x < GifFile->SWidth; x++) {
253 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
254 col.rgb.r = ColorMapEntry->Red;
255 col.rgb.g = ColorMapEntry->Green;
256 col.rgb.b = ColorMapEntry->Blue;
257 i_ppix(im,x,j,&col);
258 }
259
260 }
261 }
262 else {
263 for (i = 0; i < Height; i++) {
264 if (DGifGetLine(GifFile, &GifRow[Col], Width) == GIF_ERROR) {
77fc6afd
TC
265 gif_push_error();
266 i_push_error(0, "Reading GIF line");
267 if (colour_table)
268 free(colour_table);
269 i_img_destroy(im);
270 DGifCloseFile(GifFile);
271 return NULL;
02d1d628
AMH
272 }
273
274 for (x = 0; x < GifFile->SWidth; x++) {
275 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
276 col.rgb.r = ColorMapEntry->Red;
277 col.rgb.g = ColorMapEntry->Green;
278 col.rgb.b = ColorMapEntry->Blue;
279 i_ppix(im,x,Row,&col);
280 }
281 Row++;
282 }
283 }
284 break;
285 case EXTENSION_RECORD_TYPE:
286 /* Skip any extension blocks in file: */
287 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
77fc6afd
TC
288 gif_push_error();
289 i_push_error(0, "Reading extension record");
290 if (colour_table)
291 free(colour_table);
292 i_img_destroy(im);
293 DGifCloseFile(GifFile);
294 return NULL;
02d1d628
AMH
295 }
296 while (Extension != NULL) {
297 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
77fc6afd
TC
298 gif_push_error();
299 i_push_error(0, "reading next block of extension");
300 if (colour_table)
301 free(colour_table);
302 i_img_destroy(im);
303 DGifCloseFile(GifFile);
304 return NULL;
02d1d628
AMH
305 }
306 }
307 break;
308 case TERMINATE_RECORD_TYPE:
309 break;
310 default: /* Should be traps by DGifGetRecordType. */
311 break;
312 }
313 } while (RecordType != TERMINATE_RECORD_TYPE);
314
315 myfree(GifRow);
316
317 if (DGifCloseFile(GifFile) == GIF_ERROR) {
77fc6afd
TC
318 gif_push_error();
319 i_push_error(0, "Closing GIF file object");
320 if (colour_table)
321 free(colour_table);
322 i_img_destroy(im);
323 return NULL;
02d1d628
AMH
324 }
325 return im;
326}
327
a3923855
TC
328/*
329=item i_readgif(int fd, int **colour_table, int *colours)
330
331Reads in a GIF file from a file handle and converts it to an Imager
332RGB image object.
333
334Returns the palette for the object in colour_table for colours
335colours.
336
337Returns NULL on failure.
338
339=cut
340*/
02d1d628
AMH
341
342i_img *
343i_readgif(int fd, int **colour_table, int *colours) {
344 GifFileType *GifFile;
77fc6afd
TC
345
346 i_clear_error();
02d1d628
AMH
347
348 mm_log((1,"i_readgif(fd %d, colour_table %p, colours %p)\n", fd, colour_table, colours));
349
350 if ((GifFile = DGifOpenFileHandle(fd)) == NULL) {
77fc6afd
TC
351 gif_push_error();
352 i_push_error(0, "Cannot create giflib file object");
02d1d628
AMH
353 mm_log((1,"i_readgif: Unable to open file\n"));
354 return NULL;
355 }
356
357 return i_readgif_low(GifFile, colour_table, colours);
358}
359
a3923855
TC
360/*
361=item i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[])
362
363Write I<img> to the file handle I<fd>. The resulting GIF will use a
364maximum of 1<<I<max_colours> colours, with the first I<fixedlen>
365colours taken from I<fixed>.
366
367Returns non-zero on success.
368
369=cut
370*/
02d1d628
AMH
371
372undef_int
373i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[])
374{
375 i_color colors[256];
376 i_quantize quant;
377 i_gif_opts opts;
378
379 memset(&quant, 0, sizeof(quant));
380 memset(&opts, 0, sizeof(opts));
381 quant.make_colors = mc_addi;
382 quant.mc_colors = colors;
383 quant.mc_size = 1<<max_colors;
384 quant.mc_count = fixedlen;
385 memcpy(colors, fixed, fixedlen * sizeof(i_color));
386 quant.translate = pt_perturb;
387 quant.perturb = pixdev;
388 return i_writegif_gen(&quant, fd, &im, 1, &opts);
389}
390
a3923855
TC
391/*
392=item i_writegifmc(i_img *im, int fd, int max_colors)
393
394Write I<img> to the file handle I<fd>. The resulting GIF will use a
395maximum of 1<<I<max_colours> colours.
396
397Returns non-zero on success.
398
399=cut
400*/
401
02d1d628
AMH
402undef_int
403i_writegifmc(i_img *im, int fd, int max_colors) {
404 i_color colors[256];
405 i_quantize quant;
406 i_gif_opts opts;
407
408 memset(&quant, 0, sizeof(quant));
409 memset(&opts, 0, sizeof(opts));
410 quant.make_colors = mc_none; /* ignored for pt_giflib */
411 quant.mc_colors = colors;
412 quant.mc_size = 1 << max_colors;
413 quant.mc_count = 0;
414 quant.translate = pt_giflib;
415 return i_writegif_gen(&quant, fd, &im, 1, &opts);
416}
417
418#if 1
419
420undef_int
421i_writegifex(i_img *im, int fd) {
422 return 0;
423}
424
425#else
426
427/* I don't think this works
428 note that RedBuffer is never allocated - TC
429*/
430
431undef_int
432i_writegifex(i_img *im,int fd) {
433 int colors, xsize, ysize, channels;
434 int x,y,ColorMapSize;
435 unsigned long Size;
436
437 struct octt *ct;
438
439 GifByteType *RedBuffer = NULL, *GreenBuffer = NULL, *BlueBuffer = NULL,*OutputBuffer = NULL;
440 ColorMapObject *OutputColorMap = NULL;
441 GifFileType *GifFile;
442 GifByteType *Ptr;
443
444 i_color val;
445
446 mm_log((1,"i_writegif(0x%x,fd %d)\n",im,fd));
447
448 if (!(im->channels==1 || im->channels==3)) { fprintf(stderr,"Unable to write gif, improper colorspace.\n"); exit(3); }
449
450 xsize=im->xsize;
451 ysize=im->ysize;
452 channels=im->channels;
453
454 colors=0;
455 ct=octt_new();
456
457 colors=0;
458 for(x=0;x<xsize;x++) for(y=0;y<ysize;y++) {
459 i_gpix(im,x,y,&val);
460 colors+=octt_add(ct,val.rgb.r,val.rgb.g,val.rgb.b);
461 /* if (colors > maxc) { octt_delete(ct); }
462 We'll just bite the bullet */
463 }
464
465 ColorMapSize = (colors > 256) ? 256 : colors;
466
467 Size = ((long) im->xsize) * im->ysize * sizeof(GifByteType);
468
469 if ((OutputColorMap = MakeMapObject(ColorMapSize, NULL)) == NULL)
470 m_fatal(0,"Failed to allocate memory for Output colormap.");
471 if ((OutputBuffer = (GifByteType *) mymalloc(im->xsize * im->ysize * sizeof(GifByteType))) == NULL)
472 m_fatal(0,"Failed to allocate memory for output buffer.");
473
474 if (QuantizeBuffer(im->xsize, im->ysize, &ColorMapSize, RedBuffer, GreenBuffer, BlueBuffer,
475 OutputBuffer, OutputColorMap->Colors) == GIF_ERROR) {
476 mm_log((1,"Error in QuantizeBuffer, unable to write image.\n"));
477 return(0);
478 }
479
480
481 myfree(RedBuffer);
482 if (im->channels == 3) { myfree(GreenBuffer); myfree(BlueBuffer); }
483
484 if ((GifFile = EGifOpenFileHandle(fd)) == NULL) {
485 mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
486 return(0);
487 }
488
489 if (EGifPutScreenDesc(GifFile,im->xsize, im->ysize, colors, 0,OutputColorMap) == GIF_ERROR ||
490 EGifPutImageDesc(GifFile,0, 0, im->xsize, im->ysize, FALSE, NULL) == GIF_ERROR) {
491 mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
492 if (GifFile != NULL) EGifCloseFile(GifFile);
493 return(0);
494 }
495
496 Ptr = OutputBuffer;
497
498 for (y = 0; y < im->ysize; y++) {
499 if (EGifPutLine(GifFile, Ptr, im->xsize) == GIF_ERROR) {
500 mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
501 if (GifFile != NULL) EGifCloseFile(GifFile);
502 return(0);
503 }
504
505 Ptr += im->xsize;
506 }
507
508 if (EGifCloseFile(GifFile) == GIF_ERROR) {
509 mm_log((1,"Error in EGifCloseFile, unable to write image.\n"));
510 return(0);
511 }
512 return(1);
513}
514
515#endif
516
a3923855
TC
517/*
518=item i_readgif_scalar(char *data, int length, int **colour_table, int *colours)
519
520Reads a GIF file from an in memory copy of the file. This can be used
521if you get the 'file' from some source other than an actual file (or
522some other file handle).
523
524This function is only available with giflib 4 and higher.
525
526=cut
527*/
02d1d628
AMH
528i_img*
529i_readgif_scalar(char *data, int length, int **colour_table, int *colours) {
530#if IM_GIFMAJOR >= 4
531 GifFileType *GifFile;
02d1d628
AMH
532 struct gif_scalar_info gsi;
533
77fc6afd
TC
534 i_clear_error();
535
02d1d628
AMH
536 gsi.cpos=0;
537 gsi.length=length;
538 gsi.data=data;
539
540 mm_log((1,"i_readgif_scalar(char* data, int length, colour_table %p, colours %p)\n", data, length, colour_table, colours));
541 if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) {
77fc6afd
TC
542 gif_push_error();
543 i_push_error(0, "Cannot create giflib callback object");
02d1d628
AMH
544 mm_log((1,"i_readgif_scalar: Unable to open scalar datasource.\n"));
545 return NULL;
546 }
547
548 return i_readgif_low(GifFile, colour_table, colours);
549#else
550 return NULL;
551#endif
552}
553
554#if IM_GIFMAJOR >= 4
555
a3923855
TC
556/*
557=item gif_read_callback(GifFileType *gft, GifByteType *buf, int length)
558
559Internal. The reader callback wrapper passed to giflib.
560
561This function is only used with giflib 4 and higher.
562
563=cut
564*/
565
02d1d628
AMH
566static int
567gif_read_callback(GifFileType *gft, GifByteType *buf, int length) {
568 return i_gen_reader((i_gen_read_data *)gft->UserData, buf, length);
569}
570
571#endif
572
a3923855
TC
573
574/*
575=item i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours)
576
577Read a GIF file into an Imager RGB file, the data of the GIF file is
578retreived by callin the user supplied callback function.
579
580This function is only used with giflib 4 and higher.
581
582=cut
583*/
584
02d1d628
AMH
585i_img*
586i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours) {
587#if IM_GIFMAJOR >= 4
588 GifFileType *GifFile;
589 i_img *result;
77fc6afd 590
02d1d628
AMH
591 i_gen_read_data *gci = i_gen_read_data_new(cb, userdata);
592
9d0f387b
TC
593 i_clear_error();
594
02d1d628
AMH
595 mm_log((1,"i_readgif_callback(callback %p, userdata %p, colour_table %p, colours %p)\n", cb, userdata, colour_table, colours));
596 if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) {
77fc6afd
TC
597 gif_push_error();
598 i_push_error(0, "Cannot create giflib callback object");
02d1d628
AMH
599 mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n"));
600 myfree(gci);
601 return NULL;
602 }
603
604 result = i_readgif_low(GifFile, colour_table, colours);
605 free_gen_read_data(gci);
606
607 return result;
608#else
609 return NULL;
610#endif
611}
612
a3923855
TC
613/*
614=item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
615
616Internal. Low level image write function. Writes in interlace if
617that was requested in the GIF options.
618
619Returns non-zero on success.
620
621=cut
622*/
02d1d628
AMH
623static undef_int
624do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) {
625 if (opts->interlace) {
626 int i, j;
627 for (i = 0; i < 4; ++i) {
628 for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
629 if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) {
c48818b1
TC
630 gif_push_error();
631 i_push_error(0, "Could not save image data:");
02d1d628
AMH
632 mm_log((1, "Error in EGifPutLine\n"));
633 EGifCloseFile(gf);
634 return 0;
635 }
636 }
637 }
638 }
639 else {
640 int y;
641 for (y = 0; y < img->ysize; ++y) {
642 if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
c48818b1
TC
643 gif_push_error();
644 i_push_error(0, "Could not save image data:");
02d1d628
AMH
645 mm_log((1, "Error in EGifPutLine\n"));
646 EGifCloseFile(gf);
647 return 0;
648 }
649 data += img->xsize;
650 }
651 }
652
653 return 1;
654}
655
a3923855
TC
656/*
657=item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
658
659Internal. Writes the GIF graphics control extension, if necessary.
660
661Returns non-zero on success.
662
663=cut
664*/
02d1d628
AMH
665static int do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
666{
667 unsigned char gce[4] = {0};
668 int want_gce = 0;
669 if (want_trans) {
670 gce[0] |= 1;
671 gce[3] = trans_index;
672 ++want_gce;
673 }
674 if (index < opts->delay_count) {
675 gce[1] = opts->delays[index] % 256;
676 gce[2] = opts->delays[index] / 256;
677 ++want_gce;
678 }
679 if (index < opts->user_input_count) {
680 if (opts->user_input_flags[index])
681 gce[0] |= 2;
682 ++want_gce;
683 }
684 if (index < opts->disposal_count) {
685 gce[0] |= (opts->disposal[index] & 3) << 2;
686 ++want_gce;
687 }
688 if (want_gce) {
c48818b1
TC
689 if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
690 gif_push_error();
691 i_push_error(0, "Could not save GCE");
692 }
02d1d628
AMH
693 }
694 return 1;
695}
696
a3923855
TC
697/*
698=item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
699
700Internal. Add the Netscape2.0 loop extension block, if requested.
701
702The code for this function is currently "#if 0"ed out since the giflib
703extension writing code currently doesn't seem to support writing
704application extension blocks.
705
706=cut
707*/
02d1d628
AMH
708static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
709{
02d1d628
AMH
710 /* EGifPutExtension() doesn't appear to handle application
711 extension blocks in any way
712 Since giflib wraps the fd with a FILE * (and puts that in its
713 private data), we can't do an end-run and write the data
714 directly to the fd.
715 There's no open interface that takes a FILE * either, so we
716 can't workaround it that way either.
717 If giflib's callback interface wasn't broken by default, I'd
718 force file writes to use callbacks, but it is broken by default.
719 */
28a9109d
TC
720#if 0
721 /* yes this was another attempt at supporting the loop extension */
02d1d628 722 if (opts->loop_count) {
28a9109d
TC
723 unsigned char nsle[12] = "NETSCAPE2.0";
724 unsigned char subblock[3];
725 if (EGifPutExtension(gf, 0xFF, 11, nsle) == GIF_ERROR) {
726 gif_push_error();
727 i_push_error(0, "writing loop extension");
728 return 0;
729 }
730 subblock[0] = 1;
731 subblock[1] = opts->loop_count % 256;
732 subblock[2] = opts->loop_count / 256;
733 if (EGifPutExtension(gf, 0, 3, subblock) == GIF_ERROR) {
734 gif_push_error();
735 i_push_error(0, "writing loop extention sub-block");
736 return 0;
737 }
738 if (EGifPutExtension(gf, 0, 0, subblock) == GIF_ERROR) {
739 gif_push_error();
740 i_push_error(0, "writing loop extension terminator");
741 return 0;
742 }
02d1d628
AMH
743 }
744#endif
745 return 1;
746}
747
a3923855
TC
748/*
749=item make_gif_map(i_quantize *quant, i_gif_opts *opts, int want_trans)
750
751Create a giflib color map object from an Imager color map.
752
753=cut
754*/
755
02d1d628
AMH
756static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
757 int want_trans) {
758 GifColorType colors[256];
759 int i;
760 int size = quant->mc_count;
761 int map_size;
c48818b1 762 ColorMapObject *map;
02d1d628
AMH
763
764 for (i = 0; i < quant->mc_count; ++i) {
765 colors[i].Red = quant->mc_colors[i].rgb.r;
766 colors[i].Green = quant->mc_colors[i].rgb.g;
767 colors[i].Blue = quant->mc_colors[i].rgb.b;
768 }
769 if (want_trans) {
770 colors[size].Red = opts->tran_color.rgb.r;
771 colors[size].Green = opts->tran_color.rgb.g;
772 colors[size].Blue = opts->tran_color.rgb.b;
773 ++size;
774 }
775 map_size = 1;
776 while (map_size < size)
777 map_size <<= 1;
85363ac2
TC
778 /* giflib spews for 1 colour maps, reasonable, I suppose */
779 if (map_size == 1)
780 map_size = 2;
c48818b1
TC
781 map = MakeMapObject(map_size, colors);
782 if (!map) {
783 gif_push_error();
784 i_push_error(0, "Could not create color map object");
785 return NULL;
786 }
787 return map;
02d1d628
AMH
788}
789
a3923855
TC
790/*
791=item gif_set_version(i_quantize *quant, i_gif_opts *opts)
792
793We need to call EGifSetGifVersion() before opening the file - put that
794common code here.
795
796Unfortunately giflib 4.1.0 crashes when we use this. Internally
797giflib 4.1.0 has code:
798
799 static char *GifVersionPrefix = GIF87_STAMP;
800
801and the code that sets the version internally does:
802
803 strncpy(&GifVersionPrefix[3], Version, 3);
804
805which is very broken.
806
807Failing to set the correct GIF version doesn't seem to cause a problem
808with readers.
809
810=cut
02d1d628 811*/
a3923855 812
02d1d628
AMH
813static void gif_set_version(i_quantize *quant, i_gif_opts *opts) {
814 /* the following crashed giflib
815 the EGifSetGifVersion() is seriously borked in giflib
816 it's less borked in the ungiflib beta, but we don't have a mechanism
817 to distinguish them
818 if (opts->delay_count
819 || opts->user_input_count
820 || opts->disposal_count
821 || opts->loop_count
822 || quant->transp != tr_none)
823 EGifSetGifVersion("89a");
824 else
825 EGifSetGifVersion("87a");
826 */
827}
828
a3923855
TC
829/*
830=item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
831
832Internal. Low-level function that does the high-level GIF processing
833:)
834
835Returns non-zero on success.
836
837=cut
838*/
839
02d1d628
AMH
840static undef_int
841i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
842 i_gif_opts *opts) {
843 unsigned char *result;
844 int color_bits;
845 ColorMapObject *map;
846 int scrw = 0, scrh = 0;
847 int imgn, orig_count, orig_size;
848 int posx, posy;
849
850 mm_log((1, "i_writegif_low(quant %p, gf %p, imgs %p, count %d, opts %p)\n",
851 quant, gf, imgs, count, opts));
852
853 /**((char *)0) = 1;*/
854 /* sanity is nice */
855 if (quant->mc_size > 256)
856 quant->mc_size = 256;
857 if (quant->mc_count > quant->mc_size)
858 quant->mc_count = quant->mc_size;
859
860 for (imgn = 0; imgn < count; ++imgn) {
861 if (imgn < opts->position_count) {
862 if (imgs[imgn]->xsize + opts->positions[imgn].x > scrw)
863 scrw = imgs[imgn]->xsize + opts->positions[imgn].x;
864 if (imgs[imgn]->ysize + opts->positions[imgn].y > scrw)
865 scrh = imgs[imgn]->ysize + opts->positions[imgn].y;
866 }
867 else {
868 if (imgs[imgn]->xsize > scrw)
869 scrw = imgs[imgn]->xsize;
870 if (imgs[imgn]->ysize > scrh)
871 scrh = imgs[imgn]->ysize;
872 }
873 }
874
c48818b1
TC
875 if (count <= 0) {
876 i_push_error(0, "No images provided to write");
02d1d628 877 return 0; /* what are you smoking? */
c48818b1 878 }
02d1d628
AMH
879
880 orig_count = quant->mc_count;
881 orig_size = quant->mc_size;
882
883 if (opts->each_palette) {
5dcf9039
TC
884 int want_trans = quant->transp != tr_none
885 && imgs[0]->channels == 4;
886
887 /* if the caller gives us too many colours we can't do transparency */
888 if (want_trans && quant->mc_count == 256)
889 want_trans = 0;
890 /* if they want transparency but give us a big size, make it smaller
891 to give room for a transparency colour */
892 if (want_trans && quant->mc_size == 256)
893 --quant->mc_size;
02d1d628
AMH
894
895 /* we always generate a global palette - this lets systems with a
896 broken giflib work */
897 quant_makemap(quant, imgs, 1);
898 result = quant_translate(quant, imgs[0]);
899
02d1d628
AMH
900 if (want_trans)
901 quant_transparent(quant, result, imgs[0], quant->mc_count);
902
903 if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
904 myfree(result);
905 EGifCloseFile(gf);
906 mm_log((1, "Error in MakeMapObject."));
907 return 0;
908 }
909
910 color_bits = 1;
911 while (quant->mc_size > (1 << color_bits))
912 ++color_bits;
913
914 if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 0, map) == GIF_ERROR) {
c48818b1
TC
915 gif_push_error();
916 i_push_error(0, "Could not save screen descriptor");
02d1d628
AMH
917 FreeMapObject(map);
918 myfree(result);
919 EGifCloseFile(gf);
920 mm_log((1, "Error in EGifPutScreenDesc."));
921 return 0;
922 }
923 FreeMapObject(map);
924
925 if (!do_ns_loop(gf, opts))
926 return 0;
927
928 if (!do_gce(gf, 0, opts, want_trans, quant->mc_count)) {
929 myfree(result);
930 EGifCloseFile(gf);
931 return 0;
932 }
933 if (opts->position_count) {
934 posx = opts->positions[0].x;
935 posy = opts->positions[0].y;
936 }
937 else
938 posx = posy = 0;
939 if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize,
940 opts->interlace, NULL) == GIF_ERROR) {
c48818b1
TC
941 gif_push_error();
942 i_push_error(0, "Could not save image descriptor");
02d1d628
AMH
943 EGifCloseFile(gf);
944 mm_log((1, "Error in EGifPutImageDesc."));
945 return 0;
946 }
947 if (!do_write(gf, opts, imgs[0], result)) {
948 EGifCloseFile(gf);
949 myfree(result);
950 return 0;
951 }
952 for (imgn = 1; imgn < count; ++imgn) {
953 quant->mc_count = orig_count;
954 quant->mc_size = orig_size;
5dcf9039
TC
955 want_trans = quant->transp != tr_none
956 && imgs[0]->channels == 4;
957 /* if the caller gives us too many colours we can't do transparency */
958 if (want_trans && quant->mc_count == 256)
959 want_trans = 0;
960 /* if they want transparency but give us a big size, make it smaller
961 to give room for a transparency colour */
962 if (want_trans && quant->mc_size == 256)
963 --quant->mc_size;
964
02d1d628
AMH
965 quant_makemap(quant, imgs+imgn, 1);
966 result = quant_translate(quant, imgs[imgn]);
02d1d628
AMH
967 if (want_trans)
968 quant_transparent(quant, result, imgs[imgn], quant->mc_count);
969
970 if (!do_gce(gf, imgn, opts, want_trans, quant->mc_count)) {
971 myfree(result);
972 EGifCloseFile(gf);
973 return 0;
974 }
975 if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
976 myfree(result);
977 EGifCloseFile(gf);
978 mm_log((1, "Error in MakeMapObject."));
979 return 0;
980 }
981 if (imgn < opts->position_count) {
982 posx = opts->positions[imgn].x;
983 posy = opts->positions[imgn].y;
984 }
985 else
986 posx = posy = 0;
987 if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize,
988 imgs[imgn]->ysize, opts->interlace,
989 map) == GIF_ERROR) {
c48818b1
TC
990 gif_push_error();
991 i_push_error(0, "Could not save image descriptor");
02d1d628
AMH
992 myfree(result);
993 FreeMapObject(map);
994 EGifCloseFile(gf);
995 mm_log((1, "Error in EGifPutImageDesc."));
996 return 0;
997 }
998 FreeMapObject(map);
999
1000 if (!do_write(gf, opts, imgs[imgn], result)) {
1001 EGifCloseFile(gf);
1002 myfree(result);
1003 return 0;
1004 }
1005 myfree(result);
1006 }
1007 }
1008 else {
1009 int want_trans;
1010
02d1d628
AMH
1011 /* get a palette entry for the transparency iff we have an image
1012 with an alpha channel */
1013 want_trans = 0;
1014 for (imgn = 0; imgn < count; ++imgn) {
1015 if (imgs[imgn]->channels == 4) {
1016 ++want_trans;
1017 break;
1018 }
1019 }
5dcf9039
TC
1020 want_trans = want_trans && quant->transp != tr_none
1021 && quant->mc_count < 256;
1022 if (want_trans && quant->mc_size == 256)
1023 --quant->mc_size;
1024
1025 /* handle the first image separately - since we allow giflib
1026 conversion and giflib doesn't give us a separate function to build
1027 the colormap. */
1028
1029 /* produce a colour map */
1030 quant_makemap(quant, imgs, count);
1031 result = quant_translate(quant, imgs[0]);
1032
02d1d628
AMH
1033 if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
1034 myfree(result);
1035 EGifCloseFile(gf);
1036 mm_log((1, "Error in MakeMapObject"));
1037 return 0;
1038 }
1039 color_bits = 1;
1040 while (quant->mc_count > (1 << color_bits))
1041 ++color_bits;
1042
1043 if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 0, map) == GIF_ERROR) {
c48818b1
TC
1044 gif_push_error();
1045 i_push_error(0, "Could not save screen descriptor");
02d1d628
AMH
1046 FreeMapObject(map);
1047 myfree(result);
1048 EGifCloseFile(gf);
1049 mm_log((1, "Error in EGifPutScreenDesc."));
1050 return 0;
1051 }
1052 FreeMapObject(map);
1053
1054 if (!do_ns_loop(gf, opts))
1055 return 0;
1056
1057 if (!do_gce(gf, 0, opts, want_trans, quant->mc_count)) {
1058 myfree(result);
1059 EGifCloseFile(gf);
1060 return 0;
1061 }
1062 if (opts->position_count) {
1063 posx = opts->positions[0].x;
1064 posy = opts->positions[0].y;
1065 }
1066 else
1067 posx = posy = 0;
1068 if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize,
1069 opts->interlace, NULL) == GIF_ERROR) {
c48818b1
TC
1070 gif_push_error();
1071 i_push_error(0, "Could not save image descriptor");
02d1d628
AMH
1072 EGifCloseFile(gf);
1073 mm_log((1, "Error in EGifPutImageDesc."));
1074 return 0;
1075 }
1076 if (want_trans && imgs[0]->channels == 4)
1077 quant_transparent(quant, result, imgs[0], quant->mc_count);
1078
1079 if (!do_write(gf, opts, imgs[0], result)) {
1080 EGifCloseFile(gf);
1081 myfree(result);
1082 return 0;
1083 }
1084 myfree(result);
1085
1086 for (imgn = 1; imgn < count; ++imgn) {
1087 int local_trans;
1088 result = quant_translate(quant, imgs[imgn]);
1089 local_trans = want_trans && imgs[imgn]->channels == 4;
1090 if (local_trans)
1091 quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1092 if (!do_gce(gf, imgn, opts, local_trans, quant->mc_count)) {
1093 myfree(result);
1094 EGifCloseFile(gf);
1095 return 0;
1096 }
1097 if (imgn < opts->position_count) {
1098 posx = opts->positions[imgn].x;
1099 posy = opts->positions[imgn].y;
1100 }
1101 else
1102 posx = posy = 0;
1103 if (EGifPutImageDesc(gf, posx, posy,
1104 imgs[imgn]->xsize, imgs[imgn]->ysize,
1105 opts->interlace, NULL) == GIF_ERROR) {
c48818b1
TC
1106 gif_push_error();
1107 i_push_error(0, "Could not save image descriptor");
02d1d628
AMH
1108 myfree(result);
1109 EGifCloseFile(gf);
1110 mm_log((1, "Error in EGifPutImageDesc."));
1111 return 0;
1112 }
1113 if (!do_write(gf, opts, imgs[imgn], result)) {
1114 EGifCloseFile(gf);
1115 myfree(result);
1116 return 0;
1117 }
1118 myfree(result);
1119 }
1120 }
1121 if (EGifCloseFile(gf) == GIF_ERROR) {
c48818b1
TC
1122 gif_push_error();
1123 i_push_error(0, "Could not close GIF file");
02d1d628
AMH
1124 mm_log((1, "Error in EGifCloseFile\n"));
1125 return 0;
1126 }
1127
1128 return 1;
1129}
1130
a3923855
TC
1131/*
1132=item i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, i_gif_opts *opts)
1133
1134General high-level function to write a GIF to a file.
1135
1136Writes the GIF images to the specified file handle using the options
1137in quant and opts. See L<image.h/i_quantize> and
1138L<image.h/i_gif_opts>.
1139
1140Returns non-zero on success.
1141
1142=cut
1143*/
1144
02d1d628
AMH
1145undef_int
1146i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count,
1147 i_gif_opts *opts) {
1148 GifFileType *gf;
1149
c48818b1 1150 i_clear_error();
02d1d628
AMH
1151 mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d, opts %p)\n",
1152 quant, fd, imgs, count, opts));
1153
1154 gif_set_version(quant, opts);
1155
02d1d628 1156 if ((gf = EGifOpenFileHandle(fd)) == NULL) {
c48818b1
TC
1157 gif_push_error();
1158 i_push_error(0, "Cannot create GIF file object");
02d1d628
AMH
1159 mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
1160 return 0;
1161 }
1162
1163 return i_writegif_low(quant, gf, imgs, count, opts);
1164}
1165
1166#if IM_GIFMAJOR >= 4
1167
a3923855
TC
1168/*
1169=item gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
1170
1171Internal. Wrapper for the user write callback function.
1172
1173=cut
1174*/
1175
02d1d628
AMH
1176static int gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
1177{
1178 i_gen_write_data *gwd = (i_gen_write_data *)gf->UserData;
1179
1180 return i_gen_writer(gwd, data, size) ? size : 0;
1181}
1182
1183#endif
1184
a3923855
TC
1185/*
1186=item i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, int maxlength, i_img **imgs, int count, i_gif_opts *opts)
1187
1188General high-level function to write a GIF using callbacks to send
1189back the data.
1190
1191Returns non-zero on success.
1192
1193=cut
1194*/
1195
02d1d628
AMH
1196undef_int
1197i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
1198 int maxlength, i_img **imgs, int count, i_gif_opts *opts)
1199{
1200#if IM_GIFMAJOR >= 4
1201 GifFileType *gf;
1202 i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength);
1203 /* giflib declares this incorrectly as EgifOpen */
1204 extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc);
1205 int result;
1206
c48818b1
TC
1207 i_clear_error();
1208
02d1d628
AMH
1209 mm_log((1, "i_writegif_callback(quant %p, i_write_callback_t %p, userdata $p, maxlength %d, imgs %p, count %d, opts %p)\n",
1210 quant, cb, userdata, maxlength, imgs, count, opts));
1211
1212 if ((gf = EGifOpen(gwd, &gif_writer_callback)) == NULL) {
c48818b1
TC
1213 gif_push_error();
1214 i_push_error(0, "Cannot create GIF file object");
02d1d628
AMH
1215 mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
1216 free_gen_write_data(gwd, 0);
1217 return 0;
1218 }
1219
1220 result = i_writegif_low(quant, gf, imgs, count, opts);
1221 return free_gen_write_data(gwd, result);
1222#else
1223 return 0;
1224#endif
1225}
a3923855 1226
c48818b1
TC
1227/*
1228=item gif_error_msg(int code)
1229
1230Grabs the most recent giflib error code from GifLastError() and
1231returns a string that describes that error.
1232
1233The returned pointer points to a static buffer, either from a literal
1234C string or a static buffer.
1235
1236=cut */
1237
1238static char const *gif_error_msg(int code) {
1239 static char msg[80];
1240
1241 switch (code) {
1242 case E_GIF_ERR_OPEN_FAILED: /* should not see this */
1243 return "Failed to open given file";
1244
1245 case E_GIF_ERR_WRITE_FAILED:
1246 return "Write failed";
1247
1248 case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
1249 return "Screen descriptor already passed to giflib";
1250
1251 case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
1252 return "Image descriptor already passed to giflib";
1253
1254 case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
1255 return "Neither global nor local color map set";
1256
1257 case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
1258 return "Too much pixel data passed to giflib";
1259
1260 case E_GIF_ERR_NOT_ENOUGH_MEM:
1261 return "Out of memory";
1262
1263 case E_GIF_ERR_DISK_IS_FULL:
1264 return "Disk is full";
1265
1266 case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
1267 return "File close failed";
1268
1269 case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
1270 return "File not writable";
1271
1272 case D_GIF_ERR_OPEN_FAILED:
1273 return "Failed to open file";
1274
1275 case D_GIF_ERR_READ_FAILED:
1276 return "Failed to read from file";
1277
1278 case D_GIF_ERR_NOT_GIF_FILE:
1279 return "File is not a GIF file";
1280
1281 case D_GIF_ERR_NO_SCRN_DSCR:
1282 return "No screen descriptor detected - invalid file";
1283
1284 case D_GIF_ERR_NO_IMAG_DSCR:
1285 return "No image descriptor detected - invalid file";
1286
1287 case D_GIF_ERR_NO_COLOR_MAP:
1288 return "No global or local color map found";
1289
1290 case D_GIF_ERR_WRONG_RECORD:
1291 return "Wrong record type detected - invalid file?";
1292
1293 case D_GIF_ERR_DATA_TOO_BIG:
1294 return "Data in file too big for image";
1295
1296 case D_GIF_ERR_NOT_ENOUGH_MEM:
1297 return "Out of memory";
1298
1299 case D_GIF_ERR_CLOSE_FAILED:
1300 return "Close failed";
1301
1302 case D_GIF_ERR_NOT_READABLE:
1303 return "File not opened for read";
1304
1305 case D_GIF_ERR_IMAGE_DEFECT:
1306 return "Defective image";
1307
1308 case D_GIF_ERR_EOF_TOO_SOON:
1309 return "Unexpected EOF - invalid file";
1310
1311 default:
1312 sprintf(msg, "Unknown giflib error code %d", code);
1313 return msg;
1314 }
1315}
1316
1317/*
1318=item gif_push_error()
1319
1320Utility function that takes the current GIF error code, converts it to
1321an error message and pushes it on the error stack.
1322
1323=cut
1324*/
1325
1326static void gif_push_error() {
1327 int code = GifLastError(); /* clears saved error */
1328
1329 i_push_error(code, gif_error_msg(code));
1330}
1331
a3923855
TC
1332/*
1333=head1 BUGS
1334
1335The Netscape loop extension isn't implemented. Giflib's extension
1336writing code doesn't seem to support writing named extensions in this
1337form.
1338
1339A bug in giflib is tickled by the i_writegif_callback(). This isn't a
1340problem on ungiflib, but causes a SEGV on giflib. A patch is provided
1341in t/t10formats.t
1342
1343The GIF file tag (GIF87a vs GIF89a) currently isn't set. Using the
1344supplied interface in giflib 4.1.0 causes a SEGV in
1345EGifSetGifVersion(). See L<gif_set_version> for an explanation.
1346
1347=head1 AUTHOR
1348
1349Arnar M. Hrafnkelsson, addi@umich.edu
1350
1351=head1 SEE ALSO
1352
1353perl(1), Imager(3)
1354
1355=cut
1356
1357*/