1 /*====================================================================*
2  -  Copyright (C) 2001 Leptonica.  All rights reserved.
3  -
4  -  Redistribution and use in source and binary forms, with or without
5  -  modification, are permitted provided that the following conditions
6  -  are met:
7  -  1. Redistributions of source code must retain the above copyright
8  -     notice, this list of conditions and the following disclaimer.
9  -  2. Redistributions in binary form must reproduce the above
10  -     copyright notice, this list of conditions and the following
11  -     disclaimer in the documentation and/or other materials
12  -     provided with the distribution.
13  -
14  -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
18  -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *====================================================================*/
26 
27 /*!
28  * \file gifio.c
29  * <pre>
30  *
31  *    Reading gif
32  *          PIX            *pixReadStreamGif()
33  *          PIX            *pixReadMemGif()
34  *          static l_int32  gifReadFunc()
35  *          static PIX     *gifToPix()
36  *
37  *    Writing gif
38  *          l_int32         pixWriteStreamGif()
39  *          l_int32         pixWriteMemGif()
40  *          static l_int32  gifWriteFunc()
41  *          static l_int32  pixToGif()
42  *
43  *    Removing interlacing
44  *          static PIX     *pixUninterlaceGIF()
45  *
46  *    The initial version of this module was generously contribued by
47  *    Antony Dovgal.
48  *
49  *    The functions that read and write from pix to gif-compressed memory,
50  *    using gif internal functions DGifOpen() and EGifOpen() that are
51  *    available in 5.1 and later, were contributed by Tobias Peirick.
52  *
53  *    Version information:
54  *
55  *    (1) This supports the gif library, version 5.1 or later, for which
56  *        gif read-from-mem and write-to-mem allow these operations
57  *        without writing temporary files.
58  *    (2) There has never been a gif stream interface.  For versions
59  *        before 5.1, it was necessary to use a file descriptor, and to
60  *        generate a file stream from the low-level descriptor.  With the
61  *        memory interface in 5.1 that can be used on all platforms, it
62  *        is no longer necessary to use any API code with file descriptors.
63  *    (3) The public interface changed with 5.0 and with 5.1, and we
64  *        no longer support 4.6.1 and 5.0.
65  *    (4) Version 5.1.2 came out on Jan 7, 2016.  Leptonica cannot
66  *        successfully read gif files that it writes with this version;
67  *        DGifSlurp() gets an internal error from an uninitialized array
68  *        and returns failure.  The problem was fixed in 5.1.3.
69  * </pre>
70  */
71 
72 #ifdef HAVE_CONFIG_H
73 #include "config_auto.h"
74 #endif  /* HAVE_CONFIG_H */
75 
76 #include <string.h>
77 #include "allheaders.h"
78 
79 /* --------------------------------------------------------------------*/
80 #if  HAVE_LIBGIF  || HAVE_LIBUNGIF             /* defined in environ.h */
81 /* --------------------------------------------------------------------*/
82 
83 #include "gif_lib.h"
84 
85     /* Interface that enables low-level GIF support for reading from memory */
86 static PIX * gifToPix(GifFileType *gif);
87     /* Interface that enables low-level GIF support for writing to memory */
88 static l_int32 pixToGif(PIX *pix, GifFileType *gif);
89 
90     /*! For in-memory decoding of GIF; 5.1+ */
91 typedef struct GifReadBuffer
92 {
93     size_t            size;    /*!< size of buffer                           */
94     size_t            pos;     /*!< position relative to beginning of buffer */
95     const l_uint8    *cdata;   /*!< data in the buffer                       */
96 } GifReadBuffer;
97 
98     /*! Low-level callback for in-memory decoding */
99 static l_int32  gifReadFunc(GifFileType *gif, GifByteType *dest,
100                             l_int32 bytesToRead);
101     /*! Low-level callback for in-memory encoding */
102 static l_int32  gifWriteFunc(GifFileType *gif, const GifByteType *src,
103                              l_int32 bytesToWrite);
104 
105     /* GIF supports 4-way interlacing by raster lines.
106      * Before 5.0, it was necessary for leptonica to restore interlaced
107      * data to normal raster order when reading to a pix. With 5.0,
108      * the de-interlacing is done by the library read function. */
109 static PIX * pixUninterlaceGIF(PIX  *pixs);
110 static const l_int32 InterlacedOffset[] = {0, 4, 2, 1};
111 static const l_int32 InterlacedJumps[] = {8, 8, 4, 2};
112 
113 
114 /*---------------------------------------------------------------------*
115  *                            Reading gif                              *
116  *---------------------------------------------------------------------*/
117 /*!
118  * \brief   pixReadStreamGif()
119  *
120  * \param[in]  fp   file stream opened for reading
121  * \return  pix, or NULL on error
122  */
123 PIX *
pixReadStreamGif(FILE * fp)124 pixReadStreamGif(FILE  *fp)
125 {
126 l_uint8  *filedata;
127 size_t    filesize;
128 PIX      *pix;
129 
130     PROCNAME("pixReadStreamGif");
131 
132     if (!fp)
133         return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
134 
135         /* Read data into memory from file */
136     rewind(fp);
137     if ((filedata = l_binaryReadStream(fp, &filesize)) == NULL)
138         return (PIX *)ERROR_PTR("filedata not read", procName, NULL);
139 
140         /* Uncompress from memory */
141     pix = pixReadMemGif(filedata, filesize);
142     LEPT_FREE(filedata);
143     if (!pix)
144         L_ERROR("failed to read gif from file data\n", procName);
145     return pix;
146 }
147 
148 
149 /*!
150  * \brief   pixReadMemGif()
151  *
152  * \param[in]  cdata    const; gif-encoded
153  * \param[in]  size     bytes data
154  * \return  pix, or NULL on error
155  *
156  * <pre>
157  * Notes:
158  *     (1) For libgif version >= 5.1, this uses the DGifOpen() buffer
159  *         interface.  No temp files are required.
160  *     (2) For libgif version < 5.1, it was necessary to write the compressed
161  *         data to file and read it back, and we couldn't use the GNU
162  *         runtime extension fmemopen() because libgif doesn't have a file
163  *         stream interface.
164  * </pre>
165  */
166 PIX *
pixReadMemGif(const l_uint8 * cdata,size_t size)167 pixReadMemGif(const l_uint8  *cdata,
168               size_t          size)
169 {
170 GifFileType   *gif;
171 GifReadBuffer  buffer;
172 
173     PROCNAME("pixReadMemGif");
174 
175         /* 5.1+ and not 5.1.2 */
176 #if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0))
177     L_ERROR("Require giflib-5.1 or later\n", procName);
178     return NULL;
179 #endif  /* < 5.1 */
180 #if GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2  /* 5.1.2 */
181     L_ERROR("Can't use giflib-5.1.2; suggest 5.1.3 or later\n", procName);
182     return NULL;
183 #endif  /* 5.1.2 */
184 
185     if (!cdata)
186         return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
187 
188     buffer.cdata = cdata;
189     buffer.size = size;
190     buffer.pos = 0;
191     if ((gif = DGifOpen((void*)&buffer, gifReadFunc, NULL)) == NULL)
192         return (PIX *)ERROR_PTR("could not open gif stream from memory",
193                                 procName, NULL);
194 
195     return gifToPix(gif);
196 }
197 
198 
199 static l_int32
gifReadFunc(GifFileType * gif,GifByteType * dest,l_int32 bytesToRead)200 gifReadFunc(GifFileType  *gif,
201             GifByteType  *dest,
202             l_int32       bytesToRead)
203 {
204 GifReadBuffer  *buffer;
205 l_int32         bytesRead;
206 
207     PROCNAME("gifReadFunc");
208 
209     if ((buffer = (GifReadBuffer*)gif->UserData) == NULL)
210         return ERROR_INT("UserData not set", procName, -1);
211 
212     if(buffer->pos >= buffer->size)
213         return -1;
214 
215     bytesRead = (buffer->pos < buffer->size - bytesToRead)
216               ? bytesToRead : buffer->size - buffer->pos;
217     memcpy(dest, buffer->cdata + buffer->pos, bytesRead);
218     buffer->pos += bytesRead;
219     return bytesRead;
220 }
221 
222 
223 /*!
224  * \brief   gifToPix()
225  *
226  * \param[in]  gif   opened gif stream
227  * \return  pix, or NULL on error
228  *
229  * <pre>
230  * Notes:
231  *      (1) This decodes the pix from the compressed gif stream and
232  *          closes the stream.
233  *      (2) It is static so that the stream is not exposed to clients.
234  * </pre>
235  */
236 static PIX *
gifToPix(GifFileType * gif)237 gifToPix(GifFileType  *gif)
238 {
239 l_int32          wpl, i, j, w, h, d, cindex, ncolors;
240 l_int32          rval, gval, bval;
241 l_uint32        *data, *line;
242 PIX             *pixd;
243 PIXCMAP         *cmap;
244 ColorMapObject  *gif_cmap;
245 SavedImage       si;
246 int              giferr;
247 
248     PROCNAME("gifToPix");
249 
250         /* Read all the data, but use only the first image found */
251     if (DGifSlurp(gif) != GIF_OK) {
252         DGifCloseFile(gif, &giferr);
253         return (PIX *)ERROR_PTR("failed to read GIF data", procName, NULL);
254     }
255 
256     if (gif->SavedImages == NULL) {
257         DGifCloseFile(gif, &giferr);
258         return (PIX *)ERROR_PTR("no images found in GIF", procName, NULL);
259     }
260 
261     si = gif->SavedImages[0];
262     w = si.ImageDesc.Width;
263     h = si.ImageDesc.Height;
264     if (w <= 0 || h <= 0) {
265         DGifCloseFile(gif, &giferr);
266         return (PIX *)ERROR_PTR("invalid image dimensions", procName, NULL);
267     }
268 
269     if (si.RasterBits == NULL) {
270         DGifCloseFile(gif, &giferr);
271         return (PIX *)ERROR_PTR("no raster data in GIF", procName, NULL);
272     }
273 
274     if (si.ImageDesc.ColorMap) {
275             /* private cmap for this image */
276         gif_cmap = si.ImageDesc.ColorMap;
277     } else if (gif->SColorMap) {
278             /* global cmap for whole picture */
279         gif_cmap = gif->SColorMap;
280     } else {
281             /* don't know where to take cmap from */
282         DGifCloseFile(gif, &giferr);
283         return (PIX *)ERROR_PTR("color map is missing", procName, NULL);
284     }
285 
286     ncolors = gif_cmap->ColorCount;
287     if (ncolors <= 2)
288         d = 1;
289     else if (ncolors <= 4)
290         d = 2;
291     else if (ncolors <= 16)
292         d = 4;
293     else
294         d = 8;
295     if ((cmap = pixcmapCreate(d)) == NULL) {
296         DGifCloseFile(gif, &giferr);
297         return (PIX *)ERROR_PTR("cmap creation failed", procName, NULL);
298     }
299 
300     for (cindex = 0; cindex < ncolors; cindex++) {
301         rval = gif_cmap->Colors[cindex].Red;
302         gval = gif_cmap->Colors[cindex].Green;
303         bval = gif_cmap->Colors[cindex].Blue;
304         pixcmapAddColor(cmap, rval, gval, bval);
305     }
306 
307     if ((pixd = pixCreate(w, h, d)) == NULL) {
308         DGifCloseFile(gif, &giferr);
309         pixcmapDestroy(&cmap);
310         return (PIX *)ERROR_PTR("failed to allocate pixd", procName, NULL);
311     }
312     pixSetInputFormat(pixd, IFF_GIF);
313     pixSetColormap(pixd, cmap);
314 
315     wpl = pixGetWpl(pixd);
316     data = pixGetData(pixd);
317     for (i = 0; i < h; i++) {
318         line = data + i * wpl;
319         if (d == 1) {
320             for (j = 0; j < w; j++) {
321                 if (si.RasterBits[i * w + j])
322                     SET_DATA_BIT(line, j);
323             }
324         } else if (d == 2) {
325             for (j = 0; j < w; j++)
326                 SET_DATA_DIBIT(line, j, si.RasterBits[i * w + j]);
327         } else if (d == 4) {
328             for (j = 0; j < w; j++)
329                 SET_DATA_QBIT(line, j, si.RasterBits[i * w + j]);
330         } else {  /* d == 8 */
331             for (j = 0; j < w; j++)
332                 SET_DATA_BYTE(line, j, si.RasterBits[i * w + j]);
333         }
334     }
335 
336     /* Versions before 5.0 required un-interlacing to restore
337      * the raster lines to normal order if the image
338      * had been interlaced (for viewing in a browser):
339          if (gif->Image.Interlace) {
340              PIX *pixdi = pixUninterlaceGIF(pixd);
341              pixTransferAllData(pixd, &pixdi, 0, 0);
342          }
343      * This is no longer required. */
344 
345     DGifCloseFile(gif, &giferr);
346     return pixd;
347 }
348 
349 
350 /*---------------------------------------------------------------------*
351  *                            Writing gif                              *
352  *---------------------------------------------------------------------*/
353 /*!
354  * \brief   pixWriteStreamGif()
355  *
356  * \param[in]  fp    file stream opened for writing
357  * \param[in]  pix   1, 2, 4, 8, 16 or 32 bpp
358  * \return  0 if OK, 1 on error
359  *
360  * <pre>
361  * Notes:
362  *      (1) All output gif have colormaps.  If the pix is 32 bpp rgb,
363  *          this quantizes the colors and writes out 8 bpp.
364  *          If the pix is 16 bpp grayscale, it converts to 8 bpp first.
365  * </pre>
366  */
367 l_int32
pixWriteStreamGif(FILE * fp,PIX * pix)368 pixWriteStreamGif(FILE  *fp,
369                   PIX   *pix)
370 {
371 l_uint8  *filedata;
372 size_t    filebytes, nbytes;
373 
374     PROCNAME("pixWriteStreamGif");
375 
376     if (!fp)
377         return ERROR_INT("stream not open", procName, 1);
378     if (!pix)
379         return ERROR_INT("pix not defined", procName, 1);
380 
381     pixSetPadBits(pix, 0);
382     if (pixWriteMemGif(&filedata, &filebytes, pix) != 0) {
383         LEPT_FREE(filedata);
384         return ERROR_INT("failure to gif encode pix", procName, 1);
385     }
386 
387     rewind(fp);
388     nbytes = fwrite(filedata, 1, filebytes, fp);
389     LEPT_FREE(filedata);
390     if (nbytes != filebytes)
391         return ERROR_INT("write error", procName, 1);
392     return 0;
393 }
394 
395 
396 /*!
397  * \brief   pixWriteMemGif()
398  *
399  * \param[out]   pdata data of gif compressed image
400  * \param[out]   psize size of returned data
401  * \param[in]    pix
402  * \return  0 if OK, 1 on error
403  *
404  * <pre>
405  * Notes:
406  *      (1) See comments in pixReadMemGif()
407  * </pre>
408  */
409 l_int32
pixWriteMemGif(l_uint8 ** pdata,size_t * psize,PIX * pix)410 pixWriteMemGif(l_uint8  **pdata,
411                size_t    *psize,
412                PIX       *pix)
413 {
414 int           giferr;
415 l_int32       result;
416 GifFileType  *gif;
417 L_BBUFFER    *buffer;
418 
419     PROCNAME("pixWriteMemGif");
420 
421         /* 5.1+ and not 5.1.2 */
422 #if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0))
423     L_ERROR("Require giflib-5.1 or later\n", procName);
424     return 1;
425 #endif  /* < 5.1 */
426 #if GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2  /* 5.1.2 */
427     L_ERROR("Can't use giflib-5.1.2; suggest 5.1.3 or later\n", procName);
428     return 1;
429 #endif  /* 5.1.2 */
430 
431     if (!pdata)
432         return ERROR_INT("&data not defined", procName, 1 );
433     *pdata = NULL;
434     if (!psize)
435         return ERROR_INT("&size not defined", procName, 1 );
436     *psize = 0;
437     if (!pix)
438         return ERROR_INT("&pix not defined", procName, 1 );
439 
440     if ((buffer = bbufferCreate(NULL, 0)) == NULL)
441         return ERROR_INT("failed to create buffer", procName, 1);
442 
443     if ((gif = EGifOpen((void*)buffer, gifWriteFunc, NULL)) == NULL) {
444         bbufferDestroy(&buffer);
445         return ERROR_INT("failed to create GIF image handle", procName, 1);
446     }
447 
448     result = pixToGif(pix, gif);
449     EGifCloseFile(gif, &giferr);
450 
451     if (result == 0) {
452         *pdata = bbufferDestroyAndSaveData(&buffer, psize);
453     } else {
454         bbufferDestroy(&buffer);
455     }
456     return result;
457 }
458 
459 
460 static l_int32
gifWriteFunc(GifFileType * gif,const GifByteType * src,l_int32 bytesToWrite)461 gifWriteFunc(GifFileType        *gif,
462              const GifByteType  *src,
463              l_int32             bytesToWrite)
464 {
465 L_BBUFFER  *buffer;
466 
467     PROCNAME("gifWriteFunc");
468 
469     if ((buffer = (L_BBUFFER*)gif->UserData) == NULL)
470         return ERROR_INT("UserData not set", procName, -1);
471 
472     if(bbufferRead(buffer, (l_uint8*)src, bytesToWrite) == 0)
473         return bytesToWrite;
474     return 0;
475 }
476 
477 
478 /*!
479  * \brief   pixToGif()
480  *
481  * \param[in]  pix    1, 2, 4, 8, 16 or 32 bpp
482  * \param[in]  gif    opened gif stream
483  * \return  0 if OK, 1 on error
484  *
485  * <pre>
486  * Notes:
487  *      (1) This encodes the pix to the gif stream. The stream is not
488  *          closed by this function.
489  *      (2) It is static to make this function private.
490  * </pre>
491  */
492 static l_int32
pixToGif(PIX * pix,GifFileType * gif)493 pixToGif(PIX          *pix,
494          GifFileType  *gif)
495 {
496 char            *text;
497 l_int32          wpl, i, j, w, h, d, ncolor, rval, gval, bval;
498 l_int32          gif_ncolor = 0;
499 l_uint32        *data, *line;
500 PIX             *pixd;
501 PIXCMAP         *cmap;
502 ColorMapObject  *gif_cmap;
503 GifByteType     *gif_line;
504 
505     PROCNAME("pixToGif");
506 
507     if (!pix)
508         return ERROR_INT("pix not defined", procName, 1);
509     if (!gif)
510         return ERROR_INT("gif not defined", procName, 1);
511 
512     d = pixGetDepth(pix);
513     if (d == 32) {
514         pixd = pixConvertRGBToColormap(pix, 1);
515     } else if (d > 1) {
516         pixd = pixConvertTo8(pix, TRUE);
517     } else {  /* d == 1; make sure there's a colormap */
518         pixd = pixClone(pix);
519         if (!pixGetColormap(pixd)) {
520             cmap = pixcmapCreate(1);
521             pixcmapAddColor(cmap, 255, 255, 255);
522             pixcmapAddColor(cmap, 0, 0, 0);
523             pixSetColormap(pixd, cmap);
524         }
525     }
526 
527     if (!pixd)
528         return ERROR_INT("failed to convert image to indexed", procName, 1);
529     d = pixGetDepth(pixd);
530 
531     if ((cmap = pixGetColormap(pixd)) == NULL) {
532         pixDestroy(&pixd);
533         return ERROR_INT("cmap is missing", procName, 1);
534     }
535 
536         /* 'Round' the number of gif colors up to a power of 2 */
537     ncolor = pixcmapGetCount(cmap);
538     for (i = 0; i <= 8; i++) {
539         if ((1 << i) >= ncolor) {
540             gif_ncolor = (1 << i);
541             break;
542         }
543     }
544     if (gif_ncolor < 1) {
545         pixDestroy(&pixd);
546         return ERROR_INT("number of colors is invalid", procName, 1);
547     }
548 
549         /* Save the cmap colors in a gif_cmap */
550     if ((gif_cmap = GifMakeMapObject(gif_ncolor, NULL)) == NULL) {
551         pixDestroy(&pixd);
552         return ERROR_INT("failed to create GIF color map", procName, 1);
553     }
554     for (i = 0; i < gif_ncolor; i++) {
555         rval = gval = bval = 0;
556         if (ncolor > 0) {
557             if (pixcmapGetColor(cmap, i, &rval, &gval, &bval) != 0) {
558                 pixDestroy(&pixd);
559                 GifFreeMapObject(gif_cmap);
560                 return ERROR_INT("failed to get color from color map",
561                                  procName, 1);
562             }
563             ncolor--;
564         }
565         gif_cmap->Colors[i].Red = rval;
566         gif_cmap->Colors[i].Green = gval;
567         gif_cmap->Colors[i].Blue = bval;
568     }
569 
570     pixGetDimensions(pixd, &w, &h, NULL);
571     if (EGifPutScreenDesc(gif, w, h, gif_cmap->BitsPerPixel, 0, gif_cmap)
572         != GIF_OK) {
573         pixDestroy(&pixd);
574         GifFreeMapObject(gif_cmap);
575         return ERROR_INT("failed to write screen description", procName, 1);
576     }
577     GifFreeMapObject(gif_cmap); /* not needed after this point */
578 
579     if (EGifPutImageDesc(gif, 0, 0, w, h, FALSE, NULL) != GIF_OK) {
580         pixDestroy(&pixd);
581         return ERROR_INT("failed to image screen description", procName, 1);
582     }
583 
584     data = pixGetData(pixd);
585     wpl = pixGetWpl(pixd);
586     if (d != 1 && d != 2 && d != 4 && d != 8) {
587         pixDestroy(&pixd);
588         return ERROR_INT("image depth is not in {1, 2, 4, 8}", procName, 1);
589     }
590 
591     if ((gif_line = (GifByteType *)LEPT_CALLOC(sizeof(GifByteType), w))
592         == NULL) {
593         pixDestroy(&pixd);
594         return ERROR_INT("mem alloc fail for data line", procName, 1);
595     }
596 
597     for (i = 0; i < h; i++) {
598         line = data + i * wpl;
599             /* Gif's way of setting the raster line up for compression */
600         for (j = 0; j < w; j++) {
601             switch(d)
602             {
603             case 8:
604                 gif_line[j] = GET_DATA_BYTE(line, j);
605                 break;
606             case 4:
607                 gif_line[j] = GET_DATA_QBIT(line, j);
608                 break;
609             case 2:
610                 gif_line[j] = GET_DATA_DIBIT(line, j);
611                 break;
612             case 1:
613                 gif_line[j] = GET_DATA_BIT(line, j);
614                 break;
615             }
616         }
617 
618             /* Compress and save the line */
619         if (EGifPutLine(gif, gif_line, w) != GIF_OK) {
620             LEPT_FREE(gif_line);
621             pixDestroy(&pixd);
622             return ERROR_INT("failed to write data line into GIF", procName, 1);
623         }
624     }
625 
626         /* Write a text comment.  This must be placed after writing the
627          * data (!!)  Note that because libgif does not provide a function
628          * for reading comments from file, you will need another way
629          * to read comments. */
630     if ((text = pixGetText(pix)) != NULL) {
631         if (EGifPutComment(gif, text) != GIF_OK)
632             L_WARNING("gif comment not written\n", procName);
633     }
634 
635     LEPT_FREE(gif_line);
636     pixDestroy(&pixd);
637     return 0;
638 }
639 
640 
641 /*---------------------------------------------------------------------*
642  *                        Removing interlacing                         *
643  *---------------------------------------------------------------------*/
644 static PIX *
pixUninterlaceGIF(PIX * pixs)645 pixUninterlaceGIF(PIX  *pixs)
646 {
647 l_int32    w, h, d, wpl, j, k, srow, drow;
648 l_uint32  *datas, *datad, *lines, *lined;
649 PIX       *pixd;
650 
651     PROCNAME("pixUninterlaceGIF");
652 
653     if (!pixs)
654         return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
655 
656     pixGetDimensions(pixs, &w, &h, &d);
657     wpl = pixGetWpl(pixs);
658     pixd = pixCreateTemplate(pixs);
659     datas = pixGetData(pixs);
660     datad = pixGetData(pixd);
661     for (k = 0, srow = 0; k < 4; k++) {
662         for (drow = InterlacedOffset[k]; drow < h;
663              drow += InterlacedJumps[k], srow++) {
664             lines = datas + srow * wpl;
665             lined = datad + drow * wpl;
666             for (j = 0; j < w; j++)
667                 memcpy(lined, lines, 4 * wpl);
668         }
669     }
670 
671     return pixd;
672 }
673 
674 
675 /* -----------------------------------------------------------------*/
676 #endif    /* HAVE_LIBGIF || HAVE_LIBUNGIF  */
677