/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *====================================================================*/ /*! * \file gifio.c *
 *
 *    Reading gif
 *          PIX            *pixReadStreamGif()
 *          PIX            *pixReadMemGif()
 *          static l_int32  gifReadFunc()
 *          static PIX     *gifToPix()
 *
 *    Writing gif
 *          l_int32         pixWriteStreamGif()
 *          l_int32         pixWriteMemGif()
 *          static l_int32  gifWriteFunc()
 *          static l_int32  pixToGif()
 *
 *    The initial version of this module was generously contribued by
 *    Antony Dovgal.
 *
 *    The functions that read and write from pix to gif-compressed memory,
 *    using gif internal functions DGifOpen() and EGifOpen() that are
 *    available in 5.1 and later, were contributed by Tobias Peirick.
 *
 *    Version information:
 *
 *    (1) This supports the gif library, version 5.1 or later, for which
 *        gif read-from-mem and write-to-mem allow these operations
 *        without writing temporary files.
 *    (2) There has never been a gif stream interface.  For versions
 *        before 5.1, it was necessary to use a file descriptor, and to
 *        generate a file stream from the low-level descriptor.  With the
 *        memory interface in 5.1 that can be used on all platforms, it
 *        is no longer necessary to use any API code with file descriptors.
 *    (3) The public interface changed with 5.0 and with 5.1, and we
 *        no longer support 4.6.1 and 5.0.
 *    (4) Version 5.1.2 came out on Jan 7, 2016.  Leptonica cannot
 *        successfully read gif files that it writes with this version;
 *        DGifSlurp() gets an internal error from an uninitialized array
 *        and returns failure.  The problem was fixed in 5.1.3.
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include "allheaders.h" /* --------------------------------------------------------------------*/ #if HAVE_LIBGIF || HAVE_LIBUNGIF /* defined in environ.h */ /* --------------------------------------------------------------------*/ #include "gif_lib.h" /* Interface that enables low-level GIF support for reading from memory */ static PIX * gifToPix(GifFileType *gif); /* Interface that enables low-level GIF support for writing to memory */ static l_int32 pixToGif(PIX *pix, GifFileType *gif); /*! For in-memory decoding of GIF; 5.1+ */ typedef struct GifReadBuffer { size_t size; /*!< size of buffer */ size_t pos; /*!< position relative to beginning of buffer */ const l_uint8 *cdata; /*!< data in the buffer */ } GifReadBuffer; /*! Low-level callback for in-memory decoding */ static l_int32 gifReadFunc(GifFileType *gif, GifByteType *dest, l_int32 bytesToRead); /*! Low-level callback for in-memory encoding */ static l_int32 gifWriteFunc(GifFileType *gif, const GifByteType *src, l_int32 bytesToWrite); /*---------------------------------------------------------------------* * Reading gif * *---------------------------------------------------------------------*/ /*! * \brief pixReadStreamGif() * * \param[in] fp file stream opened for reading * \return pix, or NULL on error */ PIX * pixReadStreamGif(FILE *fp) { l_uint8 *filedata; size_t filesize; PIX *pix; PROCNAME("pixReadStreamGif"); if (!fp) return (PIX *)ERROR_PTR("fp not defined", procName, NULL); /* Read data into memory from file */ rewind(fp); if ((filedata = l_binaryReadStream(fp, &filesize)) == NULL) return (PIX *)ERROR_PTR("filedata not read", procName, NULL); /* Uncompress from memory */ pix = pixReadMemGif(filedata, filesize); LEPT_FREE(filedata); if (!pix) L_ERROR("failed to read gif from file data\n", procName); return pix; } /*! * \brief pixReadMemGif() * * \param[in] cdata const; gif-encoded * \param[in] size bytes data * \return pix, or NULL on error * *
 * Notes:
 *     (1) For libgif version >= 5.1, this uses the DGifOpen() buffer
 *         interface.  No temp files are required.
 *     (2) For libgif version < 5.1, it was necessary to write the compressed
 *         data to file and read it back, and we couldn't use the GNU
 *         runtime extension fmemopen() because libgif doesn't have a file
 *         stream interface.
 * 
*/ PIX * pixReadMemGif(const l_uint8 *cdata, size_t size) { GifFileType *gif; GifReadBuffer buffer; PROCNAME("pixReadMemGif"); /* 5.1+ and not 5.1.2 */ #if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)) L_ERROR("Require giflib-5.1 or later\n", procName); return NULL; #endif /* < 5.1 */ #if GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2 /* 5.1.2 */ L_ERROR("Can't use giflib-5.1.2; suggest 5.1.3 or later\n", procName); return NULL; #endif /* 5.1.2 */ if (!cdata) return (PIX *)ERROR_PTR("cdata not defined", procName, NULL); buffer.cdata = cdata; buffer.size = size; buffer.pos = 0; if ((gif = DGifOpen((void*)&buffer, gifReadFunc, NULL)) == NULL) return (PIX *)ERROR_PTR("could not open gif stream from memory", procName, NULL); return gifToPix(gif); } static l_int32 gifReadFunc(GifFileType *gif, GifByteType *dest, l_int32 bytesToRead) { GifReadBuffer *buffer; l_int32 bytesRead; PROCNAME("gifReadFunc"); if ((buffer = (GifReadBuffer*)gif->UserData) == NULL) return ERROR_INT("UserData not set", procName, -1); if(buffer->pos >= buffer->size || bytesToRead > buffer->size) return -1; bytesRead = (buffer->pos < buffer->size - bytesToRead) ? bytesToRead : buffer->size - buffer->pos; memcpy(dest, buffer->cdata + buffer->pos, bytesRead); buffer->pos += bytesRead; return bytesRead; } /*! * \brief gifToPix() * * \param[in] gif opened gif stream * \return pix, or NULL on error * *
 * Notes:
 *      (1) This decodes the pix from the compressed gif stream and
 *          closes the stream.
 *      (2) It is static so that the stream is not exposed to clients.
 * 
*/ static PIX * gifToPix(GifFileType *gif) { l_int32 wpl, i, j, w, h, d, cindex, ncolors, valid; l_int32 rval, gval, bval; l_uint32 *data, *line; PIX *pixd; PIXCMAP *cmap; ColorMapObject *gif_cmap; SavedImage si; int giferr; PROCNAME("gifToPix"); /* Read all the data, but use only the first image found */ if (DGifSlurp(gif) != GIF_OK) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("failed to read GIF data", procName, NULL); } if (gif->SavedImages == NULL) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("no images found in GIF", procName, NULL); } si = gif->SavedImages[0]; w = si.ImageDesc.Width; h = si.ImageDesc.Height; if (w <= 0 || h <= 0) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("invalid image dimensions", procName, NULL); } if (si.RasterBits == NULL) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("no raster data in GIF", procName, NULL); } if (si.ImageDesc.ColorMap) { /* private cmap for this image */ gif_cmap = si.ImageDesc.ColorMap; } else if (gif->SColorMap) { /* global cmap for whole picture */ gif_cmap = gif->SColorMap; } else { /* don't know where to take cmap from */ DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("color map is missing", procName, NULL); } ncolors = gif_cmap->ColorCount; if (ncolors <= 0 || ncolors > 256) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("ncolors is invalid", procName, NULL); } if (ncolors <= 2) d = 1; else if (ncolors <= 4) d = 2; else if (ncolors <= 16) d = 4; else /* [17 ... 256] */ d = 8; cmap = pixcmapCreate(d); for (cindex = 0; cindex < ncolors; cindex++) { rval = gif_cmap->Colors[cindex].Red; gval = gif_cmap->Colors[cindex].Green; bval = gif_cmap->Colors[cindex].Blue; pixcmapAddColor(cmap, rval, gval, bval); } if ((pixd = pixCreate(w, h, d)) == NULL) { DGifCloseFile(gif, &giferr); pixcmapDestroy(&cmap); return (PIX *)ERROR_PTR("failed to allocate pixd", procName, NULL); } pixSetInputFormat(pixd, IFF_GIF); pixSetColormap(pixd, cmap); pixcmapIsValid(cmap, pixd, &valid); if (!valid) { DGifCloseFile(gif, &giferr); pixDestroy(&pixd); pixcmapDestroy(&cmap); return (PIX *)ERROR_PTR("colormap is invalid", procName, NULL); } wpl = pixGetWpl(pixd); data = pixGetData(pixd); for (i = 0; i < h; i++) { line = data + i * wpl; if (d == 1) { for (j = 0; j < w; j++) { if (si.RasterBits[i * w + j]) SET_DATA_BIT(line, j); } } else if (d == 2) { for (j = 0; j < w; j++) SET_DATA_DIBIT(line, j, si.RasterBits[i * w + j]); } else if (d == 4) { for (j = 0; j < w; j++) SET_DATA_QBIT(line, j, si.RasterBits[i * w + j]); } else { /* d == 8 */ for (j = 0; j < w; j++) SET_DATA_BYTE(line, j, si.RasterBits[i * w + j]); } } /* Versions before 5.0 required un-interlacing to restore * the raster lines to normal order if the image * had been interlaced (for viewing in a browser): if (gif->Image.Interlace) { PIX *pixdi = pixUninterlaceGIF(pixd); pixTransferAllData(pixd, &pixdi, 0, 0); } * This is no longer required. */ DGifCloseFile(gif, &giferr); return pixd; } /*---------------------------------------------------------------------* * Writing gif * *---------------------------------------------------------------------*/ /*! * \brief pixWriteStreamGif() * * \param[in] fp file stream opened for writing * \param[in] pix 1, 2, 4, 8, 16 or 32 bpp * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) All output gif have colormaps.  If the pix is 32 bpp rgb,
 *          this quantizes the colors and writes out 8 bpp.
 *          If the pix is 16 bpp grayscale, it converts to 8 bpp first.
 * 
*/ l_ok pixWriteStreamGif(FILE *fp, PIX *pix) { l_uint8 *filedata; size_t filebytes, nbytes; PROCNAME("pixWriteStreamGif"); if (!fp) return ERROR_INT("stream not open", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); pixSetPadBits(pix, 0); if (pixWriteMemGif(&filedata, &filebytes, pix) != 0) { LEPT_FREE(filedata); return ERROR_INT("failure to gif encode pix", procName, 1); } rewind(fp); nbytes = fwrite(filedata, 1, filebytes, fp); LEPT_FREE(filedata); if (nbytes != filebytes) return ERROR_INT("write error", procName, 1); return 0; } /*! * \brief pixWriteMemGif() * * \param[out] pdata data of gif compressed image * \param[out] psize size of returned data * \param[in] pix * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) See comments in pixReadMemGif()
 * 
*/ l_ok pixWriteMemGif(l_uint8 **pdata, size_t *psize, PIX *pix) { int giferr; l_int32 result; GifFileType *gif; L_BBUFFER *buffer; PROCNAME("pixWriteMemGif"); /* 5.1+ and not 5.1.2 */ #if (GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)) L_ERROR("Require giflib-5.1 or later\n", procName); return 1; #endif /* < 5.1 */ #if GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2 /* 5.1.2 */ L_ERROR("Can't use giflib-5.1.2; suggest 5.1.3 or later\n", procName); return 1; #endif /* 5.1.2 */ if (!pdata) return ERROR_INT("&data not defined", procName, 1 ); *pdata = NULL; if (!psize) return ERROR_INT("&size not defined", procName, 1 ); *psize = 0; if (!pix) return ERROR_INT("&pix not defined", procName, 1 ); if ((buffer = bbufferCreate(NULL, 0)) == NULL) return ERROR_INT("failed to create buffer", procName, 1); if ((gif = EGifOpen((void*)buffer, gifWriteFunc, NULL)) == NULL) { bbufferDestroy(&buffer); return ERROR_INT("failed to create GIF image handle", procName, 1); } result = pixToGif(pix, gif); EGifCloseFile(gif, &giferr); if (result == 0) { *pdata = bbufferDestroyAndSaveData(&buffer, psize); } else { bbufferDestroy(&buffer); } return result; } static l_int32 gifWriteFunc(GifFileType *gif, const GifByteType *src, l_int32 bytesToWrite) { L_BBUFFER *buffer; PROCNAME("gifWriteFunc"); if ((buffer = (L_BBUFFER*)gif->UserData) == NULL) return ERROR_INT("UserData not set", procName, -1); if(bbufferRead(buffer, (l_uint8*)src, bytesToWrite) == 0) return bytesToWrite; return 0; } /*! * \brief pixToGif() * * \param[in] pix 1, 2, 4, 8, 16 or 32 bpp * \param[in] gif opened gif stream * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This encodes the pix to the gif stream. The stream is not
 *          closed by this function.
 *      (2) It is static to make this function private.
 * 
*/ static l_int32 pixToGif(PIX *pix, GifFileType *gif) { char *text; l_int32 wpl, i, j, w, h, d, ncolor, rval, gval, bval, valid; l_int32 gif_ncolor = 0; l_uint32 *data, *line; PIX *pixd; PIXCMAP *cmap; ColorMapObject *gif_cmap; GifByteType *gif_line; PROCNAME("pixToGif"); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (!gif) return ERROR_INT("gif not defined", procName, 1); d = pixGetDepth(pix); if (d == 32) { pixd = pixConvertRGBToColormap(pix, 1); } else if (d > 1) { pixd = pixConvertTo8(pix, TRUE); } else { /* d == 1; make sure there's a colormap */ pixd = pixClone(pix); if (!pixGetColormap(pixd)) { cmap = pixcmapCreate(1); pixcmapAddColor(cmap, 255, 255, 255); pixcmapAddColor(cmap, 0, 0, 0); pixSetColormap(pixd, cmap); } } if (!pixd) return ERROR_INT("failed to convert to colormapped pix", procName, 1); d = pixGetDepth(pixd); cmap = pixGetColormap(pixd); if (!cmap) { pixDestroy(&pixd); return ERROR_INT("cmap is missing", procName, 1); } pixcmapIsValid(cmap, pixd, &valid); if (!valid) { pixDestroy(&pixd); return ERROR_INT("colormap is not valid", procName, 1); } /* 'Round' the number of gif colors up to a power of 2 */ ncolor = pixcmapGetCount(cmap); for (i = 0; i <= 8; i++) { if ((1 << i) >= ncolor) { gif_ncolor = (1 << i); break; } } if (gif_ncolor < 1) { pixDestroy(&pixd); return ERROR_INT("number of colors is invalid", procName, 1); } /* Save the cmap colors in a gif_cmap */ if ((gif_cmap = GifMakeMapObject(gif_ncolor, NULL)) == NULL) { pixDestroy(&pixd); return ERROR_INT("failed to create GIF color map", procName, 1); } for (i = 0; i < gif_ncolor; i++) { rval = gval = bval = 0; if (ncolor > 0) { if (pixcmapGetColor(cmap, i, &rval, &gval, &bval) != 0) { pixDestroy(&pixd); GifFreeMapObject(gif_cmap); return ERROR_INT("failed to get color from color map", procName, 1); } ncolor--; } gif_cmap->Colors[i].Red = rval; gif_cmap->Colors[i].Green = gval; gif_cmap->Colors[i].Blue = bval; } pixGetDimensions(pixd, &w, &h, NULL); if (EGifPutScreenDesc(gif, w, h, gif_cmap->BitsPerPixel, 0, gif_cmap) != GIF_OK) { pixDestroy(&pixd); GifFreeMapObject(gif_cmap); return ERROR_INT("failed to write screen description", procName, 1); } GifFreeMapObject(gif_cmap); /* not needed after this point */ if (EGifPutImageDesc(gif, 0, 0, w, h, FALSE, NULL) != GIF_OK) { pixDestroy(&pixd); return ERROR_INT("failed to image screen description", procName, 1); } data = pixGetData(pixd); wpl = pixGetWpl(pixd); if (d != 1 && d != 2 && d != 4 && d != 8) { pixDestroy(&pixd); return ERROR_INT("image depth is not in {1, 2, 4, 8}", procName, 1); } if ((gif_line = (GifByteType *)LEPT_CALLOC(sizeof(GifByteType), w)) == NULL) { pixDestroy(&pixd); return ERROR_INT("mem alloc fail for data line", procName, 1); } for (i = 0; i < h; i++) { line = data + i * wpl; /* Gif's way of setting the raster line up for compression */ for (j = 0; j < w; j++) { switch(d) { case 8: gif_line[j] = GET_DATA_BYTE(line, j); break; case 4: gif_line[j] = GET_DATA_QBIT(line, j); break; case 2: gif_line[j] = GET_DATA_DIBIT(line, j); break; case 1: gif_line[j] = GET_DATA_BIT(line, j); break; } } /* Compress and save the line */ if (EGifPutLine(gif, gif_line, w) != GIF_OK) { LEPT_FREE(gif_line); pixDestroy(&pixd); return ERROR_INT("failed to write data line into GIF", procName, 1); } } /* Write a text comment. This must be placed after writing the * data (!!) Note that because libgif does not provide a function * for reading comments from file, you will need another way * to read comments. */ if ((text = pixGetText(pix)) != NULL) { if (EGifPutComment(gif, text) != GIF_OK) L_WARNING("gif comment not written\n", procName); } LEPT_FREE(gif_line); pixDestroy(&pixd); return 0; } #if 0 /*---------------------------------------------------------------------* * Removing interlacing (reference only; not used) * *---------------------------------------------------------------------*/ /* GIF supports 4-way interlacing by raster lines. * Before 5.0, it was necessary for leptonica to restore interlaced * data to normal raster order when reading to a pix. With 5.0, * the de-interlacing is done by the library read function. * It is here only as a reference. */ static const l_int32 InterlacedOffset[] = {0, 4, 2, 1}; static const l_int32 InterlacedJumps[] = {8, 8, 4, 2}; static PIX * pixUninterlaceGIF(PIX *pixs) { l_int32 w, h, d, wpl, j, k, srow, drow; l_uint32 *datas, *datad, *lines, *lined; PIX *pixd; PROCNAME("pixUninterlaceGIF"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); wpl = pixGetWpl(pixs); pixd = pixCreateTemplate(pixs); datas = pixGetData(pixs); datad = pixGetData(pixd); for (k = 0, srow = 0; k < 4; k++) { for (drow = InterlacedOffset[k]; drow < h; drow += InterlacedJumps[k], srow++) { lines = datas + srow * wpl; lined = datad + drow * wpl; for (j = 0; j < w; j++) memcpy(lined, lines, 4 * wpl); } } return pixd; } #endif /* -----------------------------------------------------------------*/ #endif /* HAVE_LIBGIF || HAVE_LIBUNGIF */