Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/tiffio.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/leptonica/src/tiffio.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,2882 @@ +/*====================================================================* + - 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 tiffio.c + * <pre> + * + * TIFFClientOpen() wrappers for FILE*: + * static tsize_t lept_read_proc() + * static tsize_t lept_write_proc() + * static toff_t lept_seek_proc() + * static int lept_close_proc() + * static toff_t lept_size_proc() + * + * Reading tiff: + * PIX *pixReadTiff() [ special top level ] + * PIX *pixReadStreamTiff() + * static PIX *pixReadFromTiffStream() + * + * Writing tiff: + * l_int32 pixWriteTiff() [ special top level ] + * l_int32 pixWriteTiffCustom() [ special top level ] + * l_int32 pixWriteStreamTiff() + * l_int32 pixWriteStreamTiffWA() + * static l_int32 pixWriteToTiffStream() + * static l_int32 writeCustomTiffTags() + * + * Reading and writing multipage tiff + * PIX *pixReadFromMultipageTiff() + * PIXA *pixaReadMultipageTiff() [ special top level ] + * l_int32 pixaWriteMultipageTiff() [ special top level ] + * l_int32 writeMultipageTiff() [ special top level ] + * l_int32 writeMultipageTiffSA() + * + * Information about tiff file + * l_int32 fprintTiffInfo() + * l_int32 tiffGetCount() + * l_int32 getTiffResolution() + * static l_int32 getTiffStreamResolution() + * l_int32 readHeaderTiff() + * l_int32 freadHeaderTiff() + * l_int32 readHeaderMemTiff() + * static l_int32 tiffReadHeaderTiff() + * l_int32 findTiffCompression() + * static l_int32 getTiffCompressedFormat() + * + * Extraction of tiff g4 data: + * l_int32 extractG4DataFromFile() + * + * Open tiff stream from file stream + * static TIFF *fopenTiff() + * + * Wrapper for TIFFOpen: + * static TIFF *openTiff() + * + * Memory I/O: reading memory --> pix and writing pix --> memory + * Ten static low-level memstream functions + * static L_MEMSTREAM *memstreamCreateForRead() + * static L_MEMSTREAM *memstreamCreateForWrite() + * static tsize_t tiffReadCallback() + * static tsize_t tiffWriteCallback() + * static toff_t tiffSeekCallback() + * static l_int32 tiffCloseCallback() + * static toff_t tiffSizeCallback() + * static l_int32 tiffMapCallback() + * static void tiffUnmapCallback() + * static TIFF *fopenTiffMemstream() + * + * PIX *pixReadMemTiff(); + * PIX *pixReadMemFromMultipageTiff(); + * PIXA *pixaReadMemMultipageTiff() [ special top level ] + * l_int32 pixaWriteMemMultipageTiff() [ special top level ] + * l_int32 pixWriteMemTiff(); + * l_int32 pixWriteMemTiffCustom(); + * + * Note 1: To include all necessary functions, use libtiff version 3.7.4 + * (from 2005) or later. + * Note 2: What compression methods in tiff are supported? + * * We support most methods that are fully implemented in the + * tiff library, such as G3, G4, RLE and LZW. + * * The exception is the old-style jpeg tiff format (OJPEG), which + * is not supported. + * * We support two formats requiring external libraries: ZIP and JPEG + * All computers should have the zip library. + * * At present we do not support WEBP in tiff, which uses + * libwebp and was added in tifflib 4.1.0 in 2019. + * Note 3: We set the pad bits to 0 before writing in pixWriteToTiffStream(). + * Although they don't affect the raster image after decompression, + * it is sometimes convenient to use a golden file with a + * byte-by-byte check to verify invariance. The issue came up + * on Windows for 2 and 4 bpp images. + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <math.h> /* for isnan */ +#include <sys/types.h> +#ifndef _MSC_VER +#include <unistd.h> +#else /* _MSC_VER */ +#include <io.h> +#endif /* _MSC_VER */ +#include <fcntl.h> +#include "allheaders.h" + +/* ---------------------------------------------------------*/ +#if HAVE_LIBTIFF && HAVE_LIBJPEG /* defined in environ.h */ +/* ---------------------------------------------------------*/ + +#include "tiff.h" +#include "tiffio.h" + +static const l_int32 DefaultResolution = 300; /* ppi */ +static const l_int32 ManyPagesInTiffFile = 3000; /* warn if big */ + + /* Verified that tiflib makes valid g4 files of this size */ +static const l_int32 MaxTiffWidth = 1 << 20; /* 1M pixels */ +static const l_int32 MaxTiffHeight = 1 << 20; /* 1M pixels */ + + /* Check g4 data size */ +static const size_t MaxNumTiffBytes = (1 << 28) - 1; /* 256 MB */ + + /* All functions with TIFF interfaces are static. */ +static PIX *pixReadFromTiffStream(TIFF *tif); +static l_int32 getTiffStreamResolution(TIFF *tif, l_int32 *pxres, + l_int32 *pyres); +static l_int32 tiffReadHeaderTiff(TIFF *tif, l_int32 *pwidth, + l_int32 *pheight, l_int32 *pbps, + l_int32 *pspp, l_int32 *pres, + l_int32 *pcmap, l_int32 *pformat); +static l_int32 writeCustomTiffTags(TIFF *tif, NUMA *natags, + SARRAY *savals, SARRAY *satypes, + NUMA *nasizes); +static l_int32 pixWriteToTiffStream(TIFF *tif, PIX *pix, l_int32 comptype, + NUMA *natags, SARRAY *savals, + SARRAY *satypes, NUMA *nasizes); +static TIFF *fopenTiff(FILE *fp, const char *modestring); +static TIFF *openTiff(const char *filename, const char *modestring); + + /* Static helper for tiff compression type */ +static l_int32 getTiffCompressedFormat(l_uint16 tiffcomp); + + /* Static function for memory I/O */ +static TIFF *fopenTiffMemstream(const char *filename, const char *operation, + l_uint8 **pdata, size_t *pdatasize); + + /* This structure defines a transform to be performed on a TIFF image + * (note that the same transformation can be represented in + * several different ways using this structure since + * vflip + hflip + counterclockwise == clockwise). */ +struct tiff_transform { + int vflip; /* if non-zero, image needs a vertical fip */ + int hflip; /* if non-zero, image needs a horizontal flip */ + int rotate; /* -1 -> counterclockwise 90-degree rotation, + 0 -> no rotation + 1 -> clockwise 90-degree rotation */ +}; + + /* This describes the transformations needed for a given orientation + * tag. The tag values start at 1, so you need to subtract 1 to get a + * valid index into this array. It is only valid when not using + * TIFFReadRGBAImageOriented(). */ +static struct tiff_transform tiff_orientation_transforms[] = { + {0, 0, 0}, + {0, 1, 0}, + {1, 1, 0}, + {1, 0, 0}, + {0, 1, -1}, + {0, 0, 1}, + {0, 1, 1}, + {0, 0, -1} +}; + + /* Same as above, except that test transformations are only valid + * when using TIFFReadRGBAImageOriented(). Transformations + * were determined empirically. See the libtiff mailing list for + * more discussion: http://www.asmail.be/msg0054683875.html */ +static struct tiff_transform tiff_partial_orientation_transforms[] = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 1, -1}, + {0, 1, 1}, + {1, 0, 1}, + {0, 1, -1} +}; + + +/*-----------------------------------------------------------------------* + * TIFFClientOpen() wrappers for FILE* * + * Provided by Jürgen Buchmüller * + * * + * We previously used TIFFFdOpen(), which used low-level file * + * descriptors. It had portability issues with Windows, along * + * with other limitations from lack of stream control operations. * + * These callbacks to TIFFClientOpen() avoid the problems. * + * * + * Jürgen made the functions use 64 bit file operations where possible * + * or required, namely for seek and size. On Windows there are specific * + * _fseeki64() and _ftelli64() functions. On unix it is common to look * + * for a macro _LARGEFILE64_SOURCE being defined, which makes available * + * the off64_t type, and to use fseeko() and ftello() in this case. * + *-----------------------------------------------------------------------*/ +static tsize_t +lept_read_proc(thandle_t cookie, + tdata_t buff, + tsize_t size) +{ + FILE* fp = (FILE *)cookie; + tsize_t done; + if (!buff || !cookie || !fp) + return (tsize_t)-1; + done = fread(buff, 1, size, fp); + return done; +} + +static tsize_t +lept_write_proc(thandle_t cookie, + tdata_t buff, + tsize_t size) +{ + FILE* fp = (FILE *)cookie; + tsize_t done; + if (!buff || !cookie || !fp) + return (tsize_t)-1; + done = fwrite(buff, 1, size, fp); + return done; +} + +static toff_t +lept_seek_proc(thandle_t cookie, + toff_t offs, + int whence) +{ + FILE* fp = (FILE *)cookie; +#if defined(_MSC_VER) + __int64 pos = 0; + if (!cookie || !fp) + return (tsize_t)-1; + switch (whence) { + case SEEK_SET: + pos = 0; + break; + case SEEK_CUR: + pos = ftell(fp); + break; + case SEEK_END: + _fseeki64(fp, 0, SEEK_END); + pos = _ftelli64(fp); + break; + } + pos = (__int64)(pos + offs); + _fseeki64(fp, pos, SEEK_SET); + if (pos == _ftelli64(fp)) + return (tsize_t)pos; +#elif defined(_LARGEFILE64_SOURCE) + off64_t pos = 0; + if (!cookie || !fp) + return (tsize_t)-1; + switch (whence) { + case SEEK_SET: + pos = 0; + break; + case SEEK_CUR: + pos = ftello(fp); + break; + case SEEK_END: + fseeko(fp, 0, SEEK_END); + pos = ftello(fp); + break; + } + pos = (off64_t)(pos + offs); + fseeko(fp, pos, SEEK_SET); + if (pos == ftello(fp)) + return (tsize_t)pos; +#else + off_t pos = 0; + if (!cookie || !fp) + return (tsize_t)-1; + switch (whence) { + case SEEK_SET: + pos = 0; + break; + case SEEK_CUR: + pos = ftell(fp); + break; + case SEEK_END: + fseek(fp, 0, SEEK_END); + pos = ftell(fp); + break; + } + pos = (off_t)(pos + offs); + fseek(fp, pos, SEEK_SET); + if (pos == ftell(fp)) + return (tsize_t)pos; +#endif + return (tsize_t)-1; +} + +static int +lept_close_proc(thandle_t cookie) +{ + FILE* fp = (FILE *)cookie; + if (!cookie || !fp) + return 0; + fseek(fp, 0, SEEK_SET); + return 0; +} + +static toff_t +lept_size_proc(thandle_t cookie) +{ + FILE* fp = (FILE *)cookie; +#if defined(_MSC_VER) + __int64 pos; + __int64 size; + if (!cookie || !fp) + return (tsize_t)-1; + pos = _ftelli64(fp); + _fseeki64(fp, 0, SEEK_END); + size = _ftelli64(fp); + _fseeki64(fp, pos, SEEK_SET); +#elif defined(_LARGEFILE64_SOURCE) + off64_t pos; + off64_t size; + if (!fp) + return (tsize_t)-1; + pos = ftello(fp); + fseeko(fp, 0, SEEK_END); + size = ftello(fp); + fseeko(fp, pos, SEEK_SET); +#else + off_t pos; + off_t size; + if (!cookie || !fp) + return (tsize_t)-1; + pos = ftell(fp); + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, pos, SEEK_SET); +#endif + return (toff_t)size; +} + + +/*--------------------------------------------------------------* + * Reading from file * + *--------------------------------------------------------------*/ +/*! + * \brief pixReadTiff() + * + * \param[in] filename + * \param[in] n page number 0 based + * \return pix, or NULL on error + * + * <pre> + * Notes: + * (1) This is a version of pixRead(), specialized for tiff + * files, that allows specification of the page to be returned + * (2) No warning messages on failure, because of how multi-page + * TIFF reading works. You are supposed to keep trying until + * it stops working. + * </pre> + */ +PIX * +pixReadTiff(const char *filename, + l_int32 n) +{ +FILE *fp; +PIX *pix; + + if (!filename) + return (PIX *)ERROR_PTR("filename not defined", __func__, NULL); + + if ((fp = fopenReadStream(filename)) == NULL) + return (PIX *)ERROR_PTR_1("image file not found", + filename, __func__, NULL); + pix = pixReadStreamTiff(fp, n); + fclose(fp); + return pix; +} + + +/*--------------------------------------------------------------* + * Reading from stream * + *--------------------------------------------------------------*/ +/*! + * \brief pixReadStreamTiff() + * + * \param[in] fp file stream + * \param[in] n page number: 0 based + * \return pix, or NULL on error or if there are no more images in the file + * + * <pre> + * Notes: + * (1) No warning messages on failure, because of how multi-page + * TIFF reading works. You are supposed to keep trying until + * it stops working. + * </pre> + */ +PIX * +pixReadStreamTiff(FILE *fp, + l_int32 n) +{ +PIX *pix; +TIFF *tif; + + if (!fp) + return (PIX *)ERROR_PTR("stream not defined", __func__, NULL); + + if ((tif = fopenTiff(fp, "r")) == NULL) + return (PIX *)ERROR_PTR("tif not opened", __func__, NULL); + + if (TIFFSetDirectory(tif, n) == 0) { + TIFFCleanup(tif); + return NULL; + } + if ((pix = pixReadFromTiffStream(tif)) == NULL) { + TIFFCleanup(tif); + return NULL; + } + TIFFCleanup(tif); + return pix; +} + + +/*! + * \brief pixReadFromTiffStream() + * + * \param[in] tif TIFF handle + * \return pix, or NULL on error + * + * <pre> + * Notes: + * (1) We can read the following images (up to 32 bits/pixel): + * 1 spp (grayscale): 1, 2, 4, 8, 16 bps + * 1 spp (colormapped): 1, 2, 4, 8 bps + * 2 spp (gray+alpha): 8 bps + * 3 spp (rgb) and 4 spp (rgba): 8 or 16 bps + * Note that 16 bps rgb and rgba are converted to 8 bps in the pix. + * (2) In particular, we do not support + * 16 bps for spp == 2 + * 4 bps for spp == 3 or spp == 4. + * (3) We only support uint image data. + * (4) We do not support tiled format, old-style jpeg encoding, + * or webp encoded tiff. + * (5) 2 bpp gray+alpha are rasterized as 32 bit/pixel rgba, with + * the gray value replicated in r, g and b. + * (6) For colormapped images, we support 8 bits/color in the palette. + * Tiff colormaps have 16 bits/color, and we reduce them to 8. + * (7) Quoting the libtiff documentation at + * http://libtiff.maptools.org/libtiff.html + * "libtiff provides a high-level interface for reading image data + * from a TIFF file. This interface handles the details of data + * organization and format for a wide variety of TIFF files; + * at least the large majority of those files that one would + * normally encounter. Image data is, by default, returned as + * ABGR pixels packed into 32-bit words (8 bits per sample). + * Rectangular rasters can be read or data can be intercepted + * at an intermediate level and packed into memory in a format + * more suitable to the application. The library handles all + * the details of the format of data stored on disk and, + * in most cases, if any colorspace conversions are required: + * bilevel to RGB, greyscale to RGB, CMYK to RGB, YCbCr to RGB, + * 16-bit samples to 8-bit samples, associated/unassociated alpha, + * etc." + * </pre> + */ +static PIX * +pixReadFromTiffStream(TIFF *tif) +{ +char *text; +l_uint8 *linebuf, *data, *rowptr; +l_uint16 spp, bps, photometry, tiffcomp, orientation, sample_fmt; +l_uint16 *redmap, *greenmap, *bluemap; +l_int32 d, wpl, bpl, comptype, i, j, k, ncolors, rval, gval, bval, aval; +l_int32 xres, yres, tiffbpl, packedbpl, half_size, twothirds_size; +l_uint32 w, h, tiffword, read_oriented; +l_uint32 *line, *ppixel, *tiffdata, *pixdata; +PIX *pix, *pix1; +PIXCMAP *cmap; + + if (!tif) + return (PIX *)ERROR_PTR("tif not defined", __func__, NULL); + + read_oriented = 0; + + /* Only accept uint image data: + * SAMPLEFORMAT_UINT = 1; + * SAMPLEFORMAT_INT = 2; + * SAMPLEFORMAT_IEEEFP = 3; + * SAMPLEFORMAT_VOID = 4; */ + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_fmt); + if (sample_fmt != SAMPLEFORMAT_UINT) { + L_ERROR("sample format = %d is not uint\n", __func__, sample_fmt); + return NULL; + } + + /* Can't read tiff in tiled format. For what is involved, see, e.g: + * https://www.cs.rochester.edu/~nelson/courses/vision/\ + * resources/tiff/libtiff.html#Tiles + * A tiled tiff can be converted to a normal (strip) tif: + * tiffcp -s <input-tiled-tif> <output-strip-tif> */ + if (TIFFIsTiled(tif)) { + L_ERROR("tiled format is not supported\n", __func__); + return NULL; + } + + /* Old style jpeg is not supported. We tried supporting 8 bpp. + * TIFFReadScanline() fails on this format, so we used RGBA + * reading, which generates a 4 spp image, and pulled out the + * red component. However, there were problems with double-frees + * in cleanup. For RGB, tiffbpl is exactly half the size that + * you would expect for the raster data in a scanline, which + * is 3 * w. */ + TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &tiffcomp); + if (tiffcomp == COMPRESSION_OJPEG) { + L_ERROR("old style jpeg format is not supported\n", __func__); + return NULL; + } + + /* webp in tiff is in 4.1.0 and not yet supported in Adobe registry */ +#if defined(COMPRESSION_WEBP) + if (tiffcomp == COMPRESSION_WEBP) { + L_ERROR("webp in tiff not generally supported yet\n", __func__); + return NULL; + } +#endif /* COMPRESSION_WEBP */ + + /* Use default fields for bps and spp */ + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps); + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); + if (bps != 1 && bps != 2 && bps != 4 && bps != 8 && bps != 16) { + L_ERROR("invalid bps = %d\n", __func__, bps); + return NULL; + } + if (spp == 2 && bps != 8) { + L_ERROR("for 2 spp, only handle 8 bps; this is %d bps\n", + __func__, bps); + return NULL; + } + if ((spp == 3 || spp == 4) && bps < 8) { + L_ERROR("for 3 and 4 spp, only handle 8 and 16 bps; this is %d bps\n", + __func__, bps); + return NULL; + } + if (spp == 1) { + d = bps; + } else if (spp == 2) { /* gray plus alpha */ + d = 32; /* will convert to RGBA */ + } else if (spp == 3 || spp == 4) { + d = 32; + } else { + L_ERROR("spp = %d; not in {1,2,3,4}\n", __func__, spp); + return NULL; + } + + TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w); + TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h); + if (w > MaxTiffWidth) { + L_ERROR("width = %d pixels; too large\n", __func__, w); + return NULL; + } + if (h > MaxTiffHeight) { + L_ERROR("height = %d pixels; too large\n", __func__, h); + return NULL; + } + + /* The relation between the size of a byte buffer required to hold + a raster of image pixels (packedbpl) and the size of the tiff + buffer (tiffbuf) is either 1:1 or approximately 1.5:1 or 2:1, + depending on how the data is stored and subsampled. For security, + we test this relation between tiffbuf and the image parameters + w, spp and bps. */ + tiffbpl = TIFFScanlineSize(tif); + packedbpl = (bps * spp * w + 7) / 8; + half_size = (L_ABS(2 * tiffbpl - packedbpl) <= 8); + twothirds_size = (L_ABS(3 * tiffbpl - 2 * packedbpl) <= 8); +#if 0 + if (half_size) + L_INFO("half_size: packedbpl = %d is approx. twice tiffbpl = %d\n", + __func__, packedbpl, tiffbpl); + if (twothirds_size) + L_INFO("twothirds_size: packedbpl = %d is approx. 1.5 tiffbpl = %d\n", + __func__, packedbpl, tiffbpl); + lept_stderr("tiffbpl = %d, packedbpl = %d, bps = %d, spp = %d, w = %d\n", + tiffbpl, packedbpl, bps, spp, w); +#endif + if (tiffbpl != packedbpl && !half_size && !twothirds_size) { + L_ERROR("invalid tiffbpl: tiffbpl = %d, packedbpl = %d, " + "bps = %d, spp = %d, w = %d\n", + __func__, tiffbpl, packedbpl, bps, spp, w); + return NULL; + } + + /* Use a linebuf that will hold all the pixels generated + by tiff when reading (decompressing) a scanline. */ + if ((pix = pixCreate(w, h, d)) == NULL) + return (PIX *)ERROR_PTR("pix not made", __func__, NULL); + pixSetInputFormat(pix, IFF_TIFF); + data = (l_uint8 *)pixGetData(pix); + wpl = pixGetWpl(pix); + bpl = 4 * wpl; + if (spp == 1) { + linebuf = (l_uint8 *)LEPT_CALLOC(4 * wpl, sizeof(l_uint8)); + for (i = 0; i < h; i++) { + if (TIFFReadScanline(tif, linebuf, i, 0) < 0) { + LEPT_FREE(linebuf); + pixDestroy(&pix); + L_ERROR("spp = 1, read fail at line %d\n", __func__, i); + return NULL; + } + memcpy(data, linebuf, tiffbpl); + data += bpl; + } + if (bps <= 8) + pixEndianByteSwap(pix); + else /* bps == 16 */ + pixEndianTwoByteSwap(pix); + LEPT_FREE(linebuf); + } else if (spp == 2 && bps == 8) { /* gray plus alpha */ + L_INFO("gray+alpha is not supported; converting to RGBA\n", __func__); + pixSetSpp(pix, 4); + linebuf = (l_uint8 *)LEPT_CALLOC(4 * wpl, sizeof(l_uint8)); + pixdata = pixGetData(pix); + for (i = 0; i < h; i++) { + if (TIFFReadScanline(tif, linebuf, i, 0) < 0) { + LEPT_FREE(linebuf); + pixDestroy(&pix); + L_ERROR("spp = 2, read fail at line %d\n", __func__, i); + return NULL; + } + rowptr = linebuf; + ppixel = pixdata + i * wpl; + for (j = k = 0; j < w; j++) { + /* Copy gray value into r, g and b */ + SET_DATA_BYTE(ppixel, COLOR_RED, rowptr[k]); + SET_DATA_BYTE(ppixel, COLOR_GREEN, rowptr[k]); + SET_DATA_BYTE(ppixel, COLOR_BLUE, rowptr[k++]); + SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, rowptr[k++]); + ppixel++; + } + } + LEPT_FREE(linebuf); + } else { /* rgb and rgba */ + if ((tiffdata = (l_uint32 *)LEPT_CALLOC((size_t)w * h, + sizeof(l_uint32))) == NULL) { + pixDestroy(&pix); + return (PIX *)ERROR_PTR("calloc fail for tiffdata", __func__, NULL); + } + /* TIFFReadRGBAImageOriented() converts to 8 bps */ + if (!TIFFReadRGBAImageOriented(tif, w, h, tiffdata, + ORIENTATION_TOPLEFT, 0)) { + LEPT_FREE(tiffdata); + pixDestroy(&pix); + return (PIX *)ERROR_PTR("failed to read tiffdata", __func__, NULL); + } else { + read_oriented = 1; + } + + if (spp == 4) pixSetSpp(pix, 4); + line = pixGetData(pix); + for (i = 0; i < h; i++, line += wpl) { + for (j = 0, ppixel = line; j < w; j++) { + /* TIFFGet* are macros */ + tiffword = tiffdata[i * w + j]; + rval = TIFFGetR(tiffword); + gval = TIFFGetG(tiffword); + bval = TIFFGetB(tiffword); + if (spp == 3) { + composeRGBPixel(rval, gval, bval, ppixel); + } else { /* spp == 4 */ + aval = TIFFGetA(tiffword); + composeRGBAPixel(rval, gval, bval, aval, ppixel); + } + ppixel++; + } + } + LEPT_FREE(tiffdata); + } + + if (getTiffStreamResolution(tif, &xres, &yres) == 0) { + pixSetXRes(pix, xres); + pixSetYRes(pix, yres); + } + + /* Find and save the compression type */ + comptype = getTiffCompressedFormat(tiffcomp); + pixSetInputFormat(pix, comptype); + + if (TIFFGetField(tif, TIFFTAG_COLORMAP, &redmap, &greenmap, &bluemap)) { + /* Save the colormap as a pix cmap. Because the + * tiff colormap components are 16 bit unsigned, + * and go from black (0) to white (0xffff), the + * the pix cmap takes the most significant byte. */ + if (bps > 8) { + pixDestroy(&pix); + return (PIX *)ERROR_PTR("colormap size > 256", __func__, NULL); + } + if ((cmap = pixcmapCreate(bps)) == NULL) { + pixDestroy(&pix); + return (PIX *)ERROR_PTR("colormap not made", __func__, NULL); + } + ncolors = 1 << bps; + for (i = 0; i < ncolors; i++) + pixcmapAddColor(cmap, redmap[i] >> 8, greenmap[i] >> 8, + bluemap[i] >> 8); + if (pixSetColormap(pix, cmap)) { + pixDestroy(&pix); + return (PIX *)ERROR_PTR("invalid colormap", __func__, NULL); + } + + /* Remove the colormap for 1 bpp. */ + if (bps == 1) { + pix1 = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); + pixDestroy(&pix); + pix = pix1; + } + } else { /* No colormap: check photometry and invert if necessary */ + if (!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometry)) { + /* Guess default photometry setting. Assume min_is_white + * if compressed 1 bpp; min_is_black otherwise. */ + if (tiffcomp == COMPRESSION_CCITTFAX3 || + tiffcomp == COMPRESSION_CCITTFAX4 || + tiffcomp == COMPRESSION_CCITTRLE || + tiffcomp == COMPRESSION_CCITTRLEW) { + photometry = PHOTOMETRIC_MINISWHITE; + } else { + photometry = PHOTOMETRIC_MINISBLACK; + } + } + if ((d == 1 && photometry == PHOTOMETRIC_MINISBLACK) || + (d == 8 && photometry == PHOTOMETRIC_MINISWHITE)) + pixInvert(pix, pix); + } + + if (TIFFGetField(tif, TIFFTAG_ORIENTATION, &orientation)) { + if (orientation >= 1 && orientation <= 8) { + struct tiff_transform *transform = (read_oriented) ? + &tiff_partial_orientation_transforms[orientation - 1] : + &tiff_orientation_transforms[orientation - 1]; + if (transform->vflip) pixFlipTB(pix, pix); + if (transform->hflip) pixFlipLR(pix, pix); + if (transform->rotate) { + PIX *oldpix = pix; + pix = pixRotate90(oldpix, transform->rotate); + pixDestroy(&oldpix); + } + } + } + + text = NULL; + TIFFGetField(tif, TIFFTAG_IMAGEDESCRIPTION, &text); + if (text) pixSetText(pix, text); + return pix; +} + + +/*--------------------------------------------------------------* + * Writing to file * + *--------------------------------------------------------------*/ +/*! + * \brief pixWriteTiff() + * + * \param[in] filename to write to + * \param[in] pix any depth, colormap will be removed + * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS, + * IFF_TIFF_G3, IFF_TIFF_G4, + * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG + * \param[in] modestr "a" or "w" + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) For multipage tiff, write the first pix with mode "w" and + * all subsequent pix with mode "a". + * (2) For multipage tiff, there is considerable overhead in the + * machinery to append an image and add the directory entry, + * and the time required for each image increases linearly + * with the number of images in the file. + * </pre> + */ +l_ok +pixWriteTiff(const char *filename, + PIX *pix, + l_int32 comptype, + const char *modestr) +{ + return pixWriteTiffCustom(filename, pix, comptype, modestr, + NULL, NULL, NULL, NULL); +} + + +/*! + * \brief pixWriteTiffCustom() + * + * \param[in] filename to write to + * \param[in] pix + * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS, + * IFF_TIFF_G3, IFF_TIFF_G4, + * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG + * \param[in] modestr "a" or "w" + * \param[in] natags [optional] NUMA of custom tiff tags + * \param[in] savals [optional] SARRAY of values + * \param[in] satypes [optional] SARRAY of types + * \param[in] nasizes [optional] NUMA of sizes + * \return 0 if OK, 1 on error + * + * Usage: + * 1 This writes a page image to a tiff file, with optional + * extra tags defined in tiff.h + * 2 For multipage tiff, write the first pix with mode "w" and + * all subsequent pix with mode "a". + * 3 For the custom tiff tags: + * a The three arrays {natags, savals, satypes} must all be + * either NULL or defined and of equal size. + * b If they are defined, the tags are an array of integers, + * the vals are an array of values in string format, and + * the types are an array of types in string format. + * c All valid tags are definined in tiff.h. + * d The types allowed are the set of strings: + * "char*" + * "l_uint8*" + * "l_uint16" + * "l_uint32" + * "l_int32" + * "l_float64" + * "l_uint16-l_uint16" note the dash; use it between the + * two l_uint16 vals in the val string + * Of these, "char*" and "l_uint16" are the most commonly used. + * e The last array, nasizes, is also optional. It is for + * tags that take an array of bytes for a value, a number of + * elements in the array, and a type that is either "char*" + * or "l_uint8*" probably either will work. + * Use NULL if there are no such tags. + * f VERY IMPORTANT: if there are any tags that require the + * extra size value, stored in nasizes, they must be + * written first! + */ +l_ok +pixWriteTiffCustom(const char *filename, + PIX *pix, + l_int32 comptype, + const char *modestr, + NUMA *natags, + SARRAY *savals, + SARRAY *satypes, + NUMA *nasizes) +{ +l_int32 ret; +TIFF *tif; + + if (!filename) + return ERROR_INT("filename not defined", __func__, 1); + if (!pix) + return ERROR_INT("pix not defined", __func__, 1); + + if ((tif = openTiff(filename, modestr)) == NULL) + return ERROR_INT("tif not opened", __func__, 1); + ret = pixWriteToTiffStream(tif, pix, comptype, natags, savals, + satypes, nasizes); + TIFFClose(tif); + return ret; +} + + +/*--------------------------------------------------------------* + * Writing to stream * + *--------------------------------------------------------------*/ +/*! + * \brief pixWriteStreamTiff() + * + * \param[in] fp file stream + * \param[in] pix + * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS, + * IFF_TIFF_G3, IFF_TIFF_G4, + * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This writes a single image to a file stream opened for writing. + * (2) If the pix has a colormap, it is preserved in the output file. + * (3) For images with bpp > 1, this resets the comptype, if + * necessary, to write uncompressed data. + * (4) G3 and G4 are only defined for 1 bpp. + * (5) We only allow PACKBITS for bpp = 1, because for bpp > 1 + * it typically expands images that are not synthetically generated. + * (6) G4 compression is typically about twice as good as G3. + * G4 is excellent for binary compression of text/line-art, + * but terrible for halftones and dithered patterns. (In + * fact, G4 on halftones can give a file that is larger + * than uncompressed!) If a binary image has dithered + * regions, it is usually better to compress with png. + * </pre> + */ +l_ok +pixWriteStreamTiff(FILE *fp, + PIX *pix, + l_int32 comptype) +{ + return pixWriteStreamTiffWA(fp, pix, comptype, "w"); +} + + +/*! + * \brief pixWriteStreamTiffWA() + * + * \param[in] fp file stream opened for append or write + * \param[in] pix + * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS, + * IFF_TIFF_G3, IFF_TIFF_G4, + * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG + * \param[in] modestr "w" or "a" + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) See pixWriteStreamTiff() + * </pre> + */ +l_ok +pixWriteStreamTiffWA(FILE *fp, + PIX *pix, + l_int32 comptype, + const char *modestr) +{ +TIFF *tif; + + if (!fp) + return ERROR_INT("stream not defined", __func__, 1 ); + if (!pix) + return ERROR_INT("pix not defined", __func__, 1 ); + if (strcmp(modestr, "w") && strcmp(modestr, "a")) { + L_ERROR("modestr = %s; not 'w' or 'a'\n", __func__, modestr); + return 1; + } + + if (pixGetDepth(pix) != 1 && comptype != IFF_TIFF && + comptype != IFF_TIFF_LZW && comptype != IFF_TIFF_ZIP && + comptype != IFF_TIFF_JPEG) { + L_WARNING("invalid compression type %d for bpp > 1; using TIFF_ZIP\n", + __func__, comptype); + comptype = IFF_TIFF_ZIP; + } + + if ((tif = fopenTiff(fp, modestr)) == NULL) + return ERROR_INT("tif not opened", __func__, 1); + + if (pixWriteToTiffStream(tif, pix, comptype, NULL, NULL, NULL, NULL)) { + TIFFCleanup(tif); + return ERROR_INT("tif write error", __func__, 1); + } + + TIFFCleanup(tif); + return 0; +} + + +/*! + * \brief pixWriteToTiffStream() + * + * \param[in] tif data structure, opened to a file + * \param[in] pix + * \param[in] comptype IFF_TIFF: for any image; no compression + * IFF_TIFF_RLE, IFF_TIFF_PACKBITS: for 1 bpp only + * IFF_TIFF_G4 and IFF_TIFF_G3: for 1 bpp only + * IFF_TIFF_LZW, IFF_TIFF_ZIP: lossless for any image + * IFF_TIFF_JPEG: lossy 8 bpp gray or rgb + * \param[in] natags [optional] NUMA of custom tiff tags + * \param[in] savals [optional] SARRAY of values + * \param[in] satypes [optional] SARRAY of types + * \param[in] nasizes [optional] NUMA of sizes + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This static function should only be called through higher + * level functions in this file; namely, pixWriteTiffCustom(), + * pixWriteTiff(), pixWriteStreamTiff(), pixWriteMemTiff() + * and pixWriteMemTiffCustom(). + * (2) We only allow PACKBITS for bpp = 1, because for bpp > 1 + * it typically expands images that are not synthetically generated. + * (3) See pixWriteTiffCustom() for details on how to use + * the last four parameters for customized tiff tags. + * (4) The only valid pixel depths in leptonica are 1, 2, 4, 8, 16 + * and 32. However, it is possible, and in some cases desirable, + * to write out a tiff file using an rgb pix that has 24 bpp. + * This can be created by appending the raster data for a 24 bpp + * image (with proper scanline padding) directly to a 24 bpp + * pix that was created without a data array. See note in + * pixWriteStreamPng() for an example. + * </pre> + */ +static l_int32 +pixWriteToTiffStream(TIFF *tif, + PIX *pix, + l_int32 comptype, + NUMA *natags, + SARRAY *savals, + SARRAY *satypes, + NUMA *nasizes) +{ +l_uint8 *linebuf, *data; +l_uint16 redmap[256], greenmap[256], bluemap[256]; +l_int32 w, h, d, spp, i, j, k, wpl, bpl, tiffbpl, ncolors, cmapsize; +l_int32 *rmap, *gmap, *bmap; +l_int32 xres, yres; +l_uint32 *line, *ppixel; +PIX *pixt; +PIXCMAP *cmap; +char *text; + + if (!tif) + return ERROR_INT("tif stream not defined", __func__, 1); + if (!pix) + return ERROR_INT( "pix not defined", __func__, 1 ); + + pixSetPadBits(pix, 0); + pixGetDimensions(pix, &w, &h, &d); + spp = pixGetSpp(pix); + xres = pixGetXRes(pix); + yres = pixGetYRes(pix); + if (xres == 0) xres = DefaultResolution; + if (yres == 0) yres = DefaultResolution; + + /* ------------------ Write out the header ------------- */ + TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (l_uint32)RESUNIT_INCH); + TIFFSetField(tif, TIFFTAG_XRESOLUTION, (l_float64)xres); + TIFFSetField(tif, TIFFTAG_YRESOLUTION, (l_float64)yres); + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (l_uint32)w); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (l_uint32)h); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + + if ((text = pixGetText(pix)) != NULL) + TIFFSetField(tif, TIFFTAG_IMAGEDESCRIPTION, text); + + if (d == 1 && !pixGetColormap(pix)) { + /* If d == 1, preserve the colormap. Note that when + * d == 1 pix with colormaps are read, the colormaps + * are removed. The only pix in leptonica that have + * colormaps are made programmatically. */ + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE); + } else if ((d == 32 && spp == 3) || d == 24) { + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, + (l_uint16)8, (l_uint16)8, (l_uint16)8); + } else if (d == 32 && spp == 4) { + l_uint16 val[1]; + val[0] = EXTRASAMPLE_ASSOCALPHA; + TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, (l_uint16)1, &val); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)4); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, + (l_uint16)8, (l_uint16)8, (l_uint16)8, (l_uint16)8); + } else if (d == 16) { /* we only support spp = 1, bps = 16 */ + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); + } else if ((cmap = pixGetColormap(pix)) == NULL) { + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); + } else { /* Save colormap in the tiff; not more than 256 colors */ + if (d > 8) { + L_ERROR("d = %d > 8 with colormap!; reducing to 8\n", __func__, d); + d = 8; + } + pixcmapToArrays(cmap, &rmap, &gmap, &bmap, NULL); + ncolors = pixcmapGetCount(cmap); + ncolors = L_MIN(256, ncolors); /* max 256 */ + cmapsize = 1 << d; + cmapsize = L_MIN(256, cmapsize); /* power of 2; max 256 */ + if (ncolors > cmapsize) { + L_WARNING("too many colors in cmap for tiff; truncating\n", + __func__); + ncolors = cmapsize; + } + for (i = 0; i < ncolors; i++) { + redmap[i] = (rmap[i] << 8) | rmap[i]; + greenmap[i] = (gmap[i] << 8) | gmap[i]; + bluemap[i] = (bmap[i] << 8) | bmap[i]; + } + for (i = ncolors; i < cmapsize; i++) /* init, even though not used */ + redmap[i] = greenmap[i] = bluemap[i] = 0; + LEPT_FREE(rmap); + LEPT_FREE(gmap); + LEPT_FREE(bmap); + + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)1); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (l_uint16)d); + TIFFSetField(tif, TIFFTAG_COLORMAP, redmap, greenmap, bluemap); + } + + if (d <= 16) { + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (l_uint16)d); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)1); + } + + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + if (comptype == IFF_TIFF) { /* no compression */ + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + } else if (comptype == IFF_TIFF_G4) { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4); + } else if (comptype == IFF_TIFF_G3) { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX3); + } else if (comptype == IFF_TIFF_RLE) { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_CCITTRLE); + } else if (comptype == IFF_TIFF_PACKBITS) { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS); + } else if (comptype == IFF_TIFF_LZW) { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_LZW); + } else if (comptype == IFF_TIFF_ZIP) { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE); + } else if (comptype == IFF_TIFF_JPEG) { + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_JPEG); + } else { + L_WARNING("unknown tiff compression; using none\n", __func__); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + } + + /* This is a no-op if arrays are NULL */ + writeCustomTiffTags(tif, natags, savals, satypes, nasizes); + + /* ------------- Write out the image data ------------- */ + tiffbpl = TIFFScanlineSize(tif); + wpl = pixGetWpl(pix); + bpl = 4 * wpl; + if (tiffbpl > bpl) + lept_stderr("Big trouble: tiffbpl = %d, bpl = %d\n", tiffbpl, bpl); + if ((linebuf = (l_uint8 *)LEPT_CALLOC(1, bpl)) == NULL) + return ERROR_INT("calloc fail for linebuf", __func__, 1); + + /* Use single strip for image */ + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, h); + + if (d != 24 && d != 32) { + if (d == 16) + pixt = pixEndianTwoByteSwapNew(pix); + else + pixt = pixEndianByteSwapNew(pix); + data = (l_uint8 *)pixGetData(pixt); + for (i = 0; i < h; i++, data += bpl) { + memcpy(linebuf, data, tiffbpl); + if (TIFFWriteScanline(tif, linebuf, i, 0) < 0) + break; + } + pixDestroy(&pixt); + } else if (d == 24) { /* See note 4 above: special case of 24 bpp rgb */ + for (i = 0; i < h; i++) { + line = pixGetData(pix) + i * wpl; + if (TIFFWriteScanline(tif, (l_uint8 *)line, i, 0) < 0) + break; + } + } else { /* 32 bpp rgb or rgba */ + for (i = 0; i < h; i++) { + line = pixGetData(pix) + i * wpl; + for (j = 0, k = 0, ppixel = line; j < w; j++) { + linebuf[k++] = GET_DATA_BYTE(ppixel, COLOR_RED); + linebuf[k++] = GET_DATA_BYTE(ppixel, COLOR_GREEN); + linebuf[k++] = GET_DATA_BYTE(ppixel, COLOR_BLUE); + if (spp == 4) + linebuf[k++] = GET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL); + ppixel++; + } + if (TIFFWriteScanline(tif, linebuf, i, 0) < 0) + break; + } + } + +/* TIFFWriteDirectory(tif); */ + LEPT_FREE(linebuf); + + return 0; +} + + +/*! + * \brief writeCustomTiffTags() + * + * \param[in] tif + * \param[in] natags [optional] NUMA of custom tiff tags + * \param[in] savals [optional] SARRAY of values + * \param[in] satypes [optional] SARRAY of types + * \param[in] nasizes [optional] NUMA of sizes + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This static function should be called indirectly through + * higher level functions, such as pixWriteTiffCustom(), + * which call pixWriteToTiffStream(). See details in + * pixWriteTiffCustom() for using the 4 input arrays. + * (2) This is a no-op if the first 3 arrays are all NULL. + * (3) Otherwise, the first 3 arrays must be defined and all + * of equal size. + * (4) The fourth array is always optional. + * (5) The most commonly used types are "char*" and "u_int16". + * See tiff.h for a full listing of the tiff tags. + * Note that many of these tags, in particular the bit tags, + * are intended to be private, and cannot be set by this function. + * Examples are the STRIPOFFSETS and STRIPBYTECOUNTS tags, + * which are bit tags that are automatically set in the header, + * and can be extracted using tiffdump. + * </pre> + */ +static l_int32 +writeCustomTiffTags(TIFF *tif, + NUMA *natags, + SARRAY *savals, + SARRAY *satypes, + NUMA *nasizes) +{ +char *sval, *type; +l_int32 i, n, ns, size, tagval, val; +l_float64 dval; +l_uint32 uval, uval2; + + if (!tif) + return ERROR_INT("tif stream not defined", __func__, 1); + if (!natags && !savals && !satypes) + return 0; + if (!natags || !savals || !satypes) + return ERROR_INT("not all arrays defined", __func__, 1); + n = numaGetCount(natags); + if ((sarrayGetCount(savals) != n) || (sarrayGetCount(satypes) != n)) + return ERROR_INT("not all sa the same size", __func__, 1); + + /* The sized arrays (4 args to TIFFSetField) are written first */ + if (nasizes) { + ns = numaGetCount(nasizes); + if (ns > n) + return ERROR_INT("too many 4-arg tag calls", __func__, 1); + for (i = 0; i < ns; i++) { + numaGetIValue(natags, i, &tagval); + sval = sarrayGetString(savals, i, L_NOCOPY); + type = sarrayGetString(satypes, i, L_NOCOPY); + numaGetIValue(nasizes, i, &size); + if (strcmp(type, "char*") && strcmp(type, "l_uint8*")) + L_WARNING("array type not char* or l_uint8*; ignore\n", + __func__); + TIFFSetField(tif, tagval, size, sval); + } + } else { + ns = 0; + } + + /* The typical tags (3 args to TIFFSetField) are now written */ + for (i = ns; i < n; i++) { + numaGetIValue(natags, i, &tagval); + sval = sarrayGetString(savals, i, L_NOCOPY); + type = sarrayGetString(satypes, i, L_NOCOPY); + if (!strcmp(type, "char*") || !strcmp(type, "const char*")) { + TIFFSetField(tif, tagval, sval); + } else if (!strcmp(type, "l_uint16")) { + if (sscanf(sval, "%u", &uval) == 1) { + TIFFSetField(tif, tagval, (l_uint16)uval); + } else { + lept_stderr("val %s not of type %s\n", sval, type); + return ERROR_INT("custom tag(s) not written", __func__, 1); + } + } else if (!strcmp(type, "l_uint32")) { + if (sscanf(sval, "%u", &uval) == 1) { + TIFFSetField(tif, tagval, uval); + } else { + lept_stderr("val %s not of type %s\n", sval, type); + return ERROR_INT("custom tag(s) not written", __func__, 1); + } + } else if (!strcmp(type, "l_int32")) { + if (sscanf(sval, "%d", &val) == 1) { + TIFFSetField(tif, tagval, val); + } else { + lept_stderr("val %s not of type %s\n", sval, type); + return ERROR_INT("custom tag(s) not written", __func__, 1); + } + } else if (!strcmp(type, "l_float64")) { + if (sscanf(sval, "%lf", &dval) == 1) { + TIFFSetField(tif, tagval, dval); + } else { + lept_stderr("val %s not of type %s\n", sval, type); + return ERROR_INT("custom tag(s) not written", __func__, 1); + } + } else if (!strcmp(type, "l_uint16-l_uint16")) { + if (sscanf(sval, "%u-%u", &uval, &uval2) == 2) { + TIFFSetField(tif, tagval, (l_uint16)uval, (l_uint16)uval2); + } else { + lept_stderr("val %s not of type %s\n", sval, type); + return ERROR_INT("custom tag(s) not written", __func__, 1); + } + } else { + lept_stderr("unknown type %s\n",type); + return ERROR_INT("unknown type; tag(s) not written", __func__, 1); + } + } + return 0; +} + + +/*--------------------------------------------------------------* + * Reading and writing multipage tiff * + *--------------------------------------------------------------*/ +/*! + * \brief pixReadFromMultipageTiff() + * + * \param[in] fname filename + * \param[in,out] poffset set offset to 0 for first image + * \return pix, or NULL on error or if previous call returned the last image + * + * <pre> + * Notes: + * (1) This allows overhead for traversal of a multipage tiff file + * to be linear in the number of images. This will also work + * with a singlepage tiff file. + * (2) No TIFF internal data structures are exposed to the caller + * (thanks to Jeff Breidenbach). + * (3) offset is the byte offset of a particular image in a multipage + * tiff file. To get the first image in the file, input the + * special offset value of 0. + * (4) The offset is updated to point to the next image, for a + * subsequent call. + * (5) On the last image, the offset returned is 0. Exit the loop + * when the returned offset is 0. + * (6) For reading a multipage tiff from a memory buffer, see + * pixReadMemFromMultipageTiff() + * (7) Example usage for reading all the images in the tif file: + * size_t offset = 0; + * do { + * Pix *pix = pixReadFromMultipageTiff(filename, &offset); + * // do something with pix + * } while (offset != 0); + * </pre> + */ +PIX * +pixReadFromMultipageTiff(const char *fname, + size_t *poffset) +{ +l_int32 retval; +size_t offset; +PIX *pix; +TIFF *tif; + + if (!fname) + return (PIX *)ERROR_PTR("fname not defined", __func__, NULL); + if (!poffset) + return (PIX *)ERROR_PTR("&offset not defined", __func__, NULL); + + if ((tif = openTiff(fname, "r")) == NULL) { + L_ERROR("tif open failed for %s\n", __func__, fname); + return NULL; + } + + /* Set ptrs in the TIFF to the beginning of the image */ + offset = *poffset; + retval = (offset == 0) ? TIFFSetDirectory(tif, 0) + : TIFFSetSubDirectory(tif, offset); + if (retval == 0) { + TIFFClose(tif); + return NULL; + } + + if ((pix = pixReadFromTiffStream(tif)) == NULL) { + TIFFClose(tif); + return NULL; + } + + /* Advance to the next image and return the new offset */ + TIFFReadDirectory(tif); + *poffset = TIFFCurrentDirOffset(tif); + TIFFClose(tif); + return pix; +} + + +/*! + * \brief pixaReadMultipageTiff() + * + * \param[in] filename input tiff file + * \return pixa of page images, or NULL on error + */ +PIXA * +pixaReadMultipageTiff(const char *filename) +{ +l_int32 i, npages; +FILE *fp; +PIX *pix; +PIXA *pixa; +TIFF *tif; + + if (!filename) + return (PIXA *)ERROR_PTR("filename not defined", __func__, NULL); + + if ((fp = fopenReadStream(filename)) == NULL) + return (PIXA *)ERROR_PTR_1("stream not opened", + filename, __func__, NULL); + if (fileFormatIsTiff(fp)) { + tiffGetCount(fp, &npages); + L_INFO(" Tiff: %d pages\n", __func__, npages); + } else { + return (PIXA *)ERROR_PTR_1("file is not tiff", + filename, __func__, NULL); + } + + if ((tif = fopenTiff(fp, "r")) == NULL) + return (PIXA *)ERROR_PTR_1("tif not opened", + filename, __func__, NULL); + + pixa = pixaCreate(npages); + pix = NULL; + for (i = 0; i < npages; i++) { + if ((pix = pixReadFromTiffStream(tif)) != NULL) { + pixaAddPix(pixa, pix, L_INSERT); + } else { + L_WARNING("pix not read for page %d\n", __func__, i); + } + + /* Advance to the next directory (i.e., the next image) */ + if (TIFFReadDirectory(tif) == 0) + break; + } + + fclose(fp); + TIFFCleanup(tif); + return pixa; +} + + +/*! + * \brief pixaWriteMultipageTiff() + * + * \param[in] fname input tiff file + * \param[in] pixa any depth; colormap will be removed + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The tiff directory overhead is O(n^2). I have not been + * able to reduce it to O(n). The overhead for n = 2000 is + * about 1 second. + * </pre> + */ +l_ok +pixaWriteMultipageTiff(const char *fname, + PIXA *pixa) +{ +const char *modestr; +l_int32 i, n; +PIX *pix1; + + if (!fname) + return ERROR_INT("fname not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + + n = pixaGetCount(pixa); + for (i = 0; i < n; i++) { + modestr = (i == 0) ? "w" : "a"; + pix1 = pixaGetPix(pixa, i, L_CLONE); + if (pixGetDepth(pix1) == 1) + pixWriteTiff(fname, pix1, IFF_TIFF_G4, modestr); + else + pixWriteTiff(fname, pix1, IFF_TIFF_ZIP, modestr); + pixDestroy(&pix1); + } + + return 0; +} + + +/*! + * \brief writeMultipageTiff() + * + * \param[in] dirin input directory + * \param[in] substr [optional] substring filter on filenames; can be NULL + * \param[in] fileout output multipage tiff file + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This writes a set of image files in a directory out + * as a multipage tiff file. The images can be in any + * initial file format. + * (2) Images with a colormap have the colormap removed before + * re-encoding as tiff. + * (3) All images are encoded losslessly. Those with 1 bpp are + * encoded 'g4'. The rest are encoded as 'zip' (flate encoding). + * Because it is lossless, this is an expensive method for + * saving most rgb images. + * (4) The tiff directory overhead is quadratic in the number of + * images. To avoid this for very large numbers of images to be + * written, apply the method used in pixaWriteMultipageTiff(). + * </pre> + */ +l_ok +writeMultipageTiff(const char *dirin, + const char *substr, + const char *fileout) +{ +SARRAY *sa; + + if (!dirin) + return ERROR_INT("dirin not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + + /* Get all filtered and sorted full pathnames. */ + sa = getSortedPathnamesInDirectory(dirin, substr, 0, 0); + + /* Generate the tiff file */ + writeMultipageTiffSA(sa, fileout); + sarrayDestroy(&sa); + return 0; +} + + +/*! + * \brief writeMultipageTiffSA() + * + * \param[in] sa string array of full path names + * \param[in] fileout output ps file + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) See writeMultipageTiff() + * </pre> + */ +l_ok +writeMultipageTiffSA(SARRAY *sa, + const char *fileout) +{ +char *fname; +const char *op; +l_int32 i, nfiles, firstfile, format; +PIX *pix; + + if (!sa) + return ERROR_INT("sa not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + + nfiles = sarrayGetCount(sa); + firstfile = TRUE; + for (i = 0; i < nfiles; i++) { + op = (firstfile) ? "w" : "a"; + fname = sarrayGetString(sa, i, L_NOCOPY); + findFileFormat(fname, &format); + if (format == IFF_UNKNOWN) { + L_INFO("format of %s not known\n", __func__, fname); + continue; + } + + if ((pix = pixRead(fname)) == NULL) { + L_WARNING("pix not made for file: %s\n", __func__, fname); + continue; + } + if (pixGetDepth(pix) == 1) + pixWriteTiff(fileout, pix, IFF_TIFF_G4, op); + else + pixWriteTiff(fileout, pix, IFF_TIFF_ZIP, op); + firstfile = FALSE; + pixDestroy(&pix); + } + + return 0; +} + + +/*--------------------------------------------------------------* + * Print info to stream * + *--------------------------------------------------------------*/ +/*! + * \brief fprintTiffInfo() + * + * \param[in] fpout stream for output of tag data + * \param[in] tiffile input + * \return 0 if OK; 1 on error + */ +l_ok +fprintTiffInfo(FILE *fpout, + const char *tiffile) +{ +TIFF *tif; + + if (!tiffile) + return ERROR_INT("tiffile not defined", __func__, 1); + if (!fpout) + return ERROR_INT("stream out not defined", __func__, 1); + + if ((tif = openTiff(tiffile, "rb")) == NULL) + return ERROR_INT("tif not open for read", __func__, 1); + + TIFFPrintDirectory(tif, fpout, 0); + TIFFClose(tif); + + return 0; +} + + +/*--------------------------------------------------------------* + * Get page count * + *--------------------------------------------------------------*/ +/*! + * \brief tiffGetCount() + * + * \param[in] fp file stream opened for read + * \param[out] pn number of images + * \return 0 if OK; 1 on error + */ +l_ok +tiffGetCount(FILE *fp, + l_int32 *pn) +{ +l_int32 i; +TIFF *tif; + + if (!fp) + return ERROR_INT("stream not defined", __func__, 1); + if (!pn) + return ERROR_INT("&n not defined", __func__, 1); + *pn = 0; + + if ((tif = fopenTiff(fp, "r")) == NULL) + return ERROR_INT("tif not open for read", __func__, 1); + + for (i = 1; ; i++) { + if (TIFFReadDirectory(tif) == 0) + break; + if (i == ManyPagesInTiffFile + 1) { + L_WARNING("big file: more than %d pages\n", __func__, + ManyPagesInTiffFile); + } + } + *pn = i; + TIFFCleanup(tif); + return 0; +} + + +/*--------------------------------------------------------------* + * Get resolution from tif * + *--------------------------------------------------------------*/ +/*! + * \brief getTiffResolution() + * + * \param[in] fp file stream opened for read + * \param[out] pxres, pyres resolution in ppi + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) If neither resolution field is set, this is not an error; + * the returned resolution values are 0 (designating 'unknown'). + * </pre> + */ +l_ok +getTiffResolution(FILE *fp, + l_int32 *pxres, + l_int32 *pyres) +{ +TIFF *tif; + + if (!pxres || !pyres) + return ERROR_INT("&xres and &yres not both defined", __func__, 1); + *pxres = *pyres = 0; + if (!fp) + return ERROR_INT("stream not opened", __func__, 1); + + if ((tif = fopenTiff(fp, "r")) == NULL) + return ERROR_INT("tif not open for read", __func__, 1); + getTiffStreamResolution(tif, pxres, pyres); + TIFFCleanup(tif); + return 0; +} + + +/*! + * \brief getTiffStreamResolution() + * + * \param[in] tif TIFF handle opened for read + * \param[out] pxres, pyres resolution in ppi + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) If neither resolution field is set, this is not an error; + * the returned resolution values are 0 (designating 'unknown'). + * </pre> + */ +static l_int32 +getTiffStreamResolution(TIFF *tif, + l_int32 *pxres, + l_int32 *pyres) +{ +l_uint16 resunit; +l_int32 foundxres, foundyres; +l_float32 fxres, fyres; + + if (!tif) + return ERROR_INT("tif not opened", __func__, 1); + if (!pxres || !pyres) + return ERROR_INT("&xres and &yres not both defined", __func__, 1); + *pxres = *pyres = 0; + + TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit); + foundxres = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &fxres); + foundyres = TIFFGetField(tif, TIFFTAG_YRESOLUTION, &fyres); + if (!foundxres && !foundyres) return 1; + if (isnan(fxres) || isnan(fyres)) return 1; + if (!foundxres && foundyres) + fxres = fyres; + else if (foundxres && !foundyres) + fyres = fxres; + + /* Avoid overflow into int32; set max fxres and fyres to 5 x 10^8 */ + if (fxres < 0 || fxres > (1L << 29) || fyres < 0 || fyres > (1L << 29)) + return ERROR_INT("fxres and/or fyres values are invalid", __func__, 1); + + if (resunit == RESUNIT_CENTIMETER) { /* convert to ppi */ + *pxres = (l_int32)(2.54 * fxres + 0.5); + *pyres = (l_int32)(2.54 * fyres + 0.5); + } else { + *pxres = (l_int32)(fxres + 0.5); + *pyres = (l_int32)(fyres + 0.5); + } + + return 0; +} + + +/*--------------------------------------------------------------* + * Get some tiff header information * + *--------------------------------------------------------------*/ +/*! + * \brief readHeaderTiff() + * + * \param[in] filename + * \param[in] n page image number: 0-based + * \param[out] pw [optional] width + * \param[out] ph [optional] height + * \param[out] pbps [optional] bits per sample -- 1, 2, 4 or 8 + * \param[out] pspp [optional] samples per pixel -- 1 or 3 + * \param[out] pres [optional] resolution in x dir; NULL to ignore + * \param[out] pcmap [optional] colormap exists; input NULL to ignore + * \param[out] pformat [optional] tiff format; input NULL to ignore + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) If there is a colormap, cmap is returned as 1; else 0. + * (2) If %n is equal to or greater than the number of images, returns 1. + * </pre> + */ +l_ok +readHeaderTiff(const char *filename, + l_int32 n, + l_int32 *pw, + l_int32 *ph, + l_int32 *pbps, + l_int32 *pspp, + l_int32 *pres, + l_int32 *pcmap, + l_int32 *pformat) +{ +l_int32 ret; +FILE *fp; + + if (pw) *pw = 0; + if (ph) *ph = 0; + if (pbps) *pbps = 0; + if (pspp) *pspp = 0; + if (pres) *pres = 0; + if (pcmap) *pcmap = 0; + if (pformat) *pformat = 0; + if (!filename) + return ERROR_INT("filename not defined", __func__, 1); + if (!pw && !ph && !pbps && !pspp && !pres && !pcmap && !pformat) + return ERROR_INT("no results requested", __func__, 1); + + if ((fp = fopenReadStream(filename)) == NULL) + return ERROR_INT_1("image file not found", filename, __func__, 1); + ret = freadHeaderTiff(fp, n, pw, ph, pbps, pspp, pres, pcmap, pformat); + fclose(fp); + return ret; +} + + +/*! + * \brief freadHeaderTiff() + * + * \param[in] fp file stream + * \param[in] n page image number: 0-based + * \param[out] pw [optional] width + * \param[out] ph [optional] height + * \param[out] pbps [optional] bits per sample -- 1, 2, 4 or 8 + * \param[out] pspp [optional] samples per pixel -- 1 or 3 + * \param[out] pres [optional] resolution in x dir; NULL to ignore + * \param[out] pcmap [optional] colormap exists; input NULL to ignore + * \param[out] pformat [optional] tiff format; input NULL to ignore + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) If there is a colormap, cmap is returned as 1; else 0. + * (2) If %n is equal to or greater than the number of images, returns 1. + * </pre> + */ +l_ok +freadHeaderTiff(FILE *fp, + l_int32 n, + l_int32 *pw, + l_int32 *ph, + l_int32 *pbps, + l_int32 *pspp, + l_int32 *pres, + l_int32 *pcmap, + l_int32 *pformat) +{ +l_int32 i, ret, format; +TIFF *tif; + + if (pw) *pw = 0; + if (ph) *ph = 0; + if (pbps) *pbps = 0; + if (pspp) *pspp = 0; + if (pres) *pres = 0; + if (pcmap) *pcmap = 0; + if (pformat) *pformat = 0; + if (!fp) + return ERROR_INT("stream not defined", __func__, 1); + if (n < 0) + return ERROR_INT("image index must be >= 0", __func__, 1); + if (!pw && !ph && !pbps && !pspp && !pres && !pcmap && !pformat) + return ERROR_INT("no results requested", __func__, 1); + + findFileFormatStream(fp, &format); + if (!L_FORMAT_IS_TIFF(format)) + return ERROR_INT("file not tiff format", __func__, 1); + + if ((tif = fopenTiff(fp, "r")) == NULL) + return ERROR_INT("tif not open for read", __func__, 1); + + for (i = 0; i < n; i++) { + if (TIFFReadDirectory(tif) == 0) + return ERROR_INT("image n not found in file", __func__, 1); + } + + ret = tiffReadHeaderTiff(tif, pw, ph, pbps, pspp, pres, pcmap, pformat); + TIFFCleanup(tif); + return ret; +} + + +/*! + * \brief readHeaderMemTiff() + * + * \param[in] cdata const; tiff-encoded + * \param[in] size size of data + * \param[in] n page image number: 0-based + * \param[out] pw [optional] width + * \param[out] ph [optional] height + * \param[out] pbps [optional] bits per sample -- 1, 2, 4 or 8 + * \param[out] pspp [optional] samples per pixel -- 1 or 3 + * \param[out] pres [optional] resolution in x dir; NULL to ignore + * \param[out] pcmap [optional] colormap exists; input NULL to ignore + * \param[out] pformat [optional] tiff format; input NULL to ignore + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Use TIFFClose(); TIFFCleanup() doesn't free internal memstream. + * (2) Returns res = 0 if not set in the file. + * </pre> + */ +l_ok +readHeaderMemTiff(const l_uint8 *cdata, + size_t size, + l_int32 n, + l_int32 *pw, + l_int32 *ph, + l_int32 *pbps, + l_int32 *pspp, + l_int32 *pres, + l_int32 *pcmap, + l_int32 *pformat) +{ +l_uint8 *data; +l_int32 i, ret; +TIFF *tif; + + if (pw) *pw = 0; + if (ph) *ph = 0; + if (pbps) *pbps = 0; + if (pspp) *pspp = 0; + if (pres) *pres = 0; + if (pcmap) *pcmap = 0; + if (pformat) *pformat = 0; + if (!pw && !ph && !pbps && !pspp && !pres && !pcmap && !pformat) + return ERROR_INT("no results requested", __func__, 1); + if (!cdata) + return ERROR_INT("cdata not defined", __func__, 1); + + /* Open a tiff stream to memory */ + data = (l_uint8 *)cdata; /* we're really not going to change this */ + if ((tif = fopenTiffMemstream("tifferror", "r", &data, &size)) == NULL) + return ERROR_INT("tiff stream not opened", __func__, 1); + + for (i = 0; i < n; i++) { + if (TIFFReadDirectory(tif) == 0) { + TIFFClose(tif); + return ERROR_INT("image n not found in file", __func__, 1); + } + } + + ret = tiffReadHeaderTiff(tif, pw, ph, pbps, pspp, pres, pcmap, pformat); + TIFFClose(tif); + return ret; +} + + +/*! + * \brief tiffReadHeaderTiff() + * + * \param[in] tif + * \param[out] pw [optional] width + * \param[out] ph [optional] height + * \param[out] pbps [optional] bits per sample -- 1, 2, 4 or 8 + * \param[out] pspp [optional] samples per pixel -- 1 or 3 + * \param[out] pres [optional] resolution in x dir; NULL to ignore + * \param[out] pcmap [optional] cmap exists; input NULL to ignore + * \param[out] pformat [optional] tiff format; input NULL to ignore + * \return 0 if OK, 1 on error + */ +static l_int32 +tiffReadHeaderTiff(TIFF *tif, + l_int32 *pw, + l_int32 *ph, + l_int32 *pbps, + l_int32 *pspp, + l_int32 *pres, + l_int32 *pcmap, + l_int32 *pformat) +{ +l_uint16 tiffcomp; +l_uint16 bps, spp; +l_uint16 *rmap, *gmap, *bmap; +l_int32 xres, yres; +l_uint32 w, h; + + if (!tif) + return ERROR_INT("tif not opened", __func__, 1); + + TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w); + TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h); + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps); + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); + if (w < 1 || h < 1) + return ERROR_INT("tif w and h not both > 0", __func__, 1); + if (bps != 1 && bps != 2 && bps != 4 && bps != 8 && bps != 16) + return ERROR_INT("bps not in set {1,2,4,8,16}", __func__, 1); + if (spp != 1 && spp != 2 && spp != 3 && spp != 4) + return ERROR_INT("spp not in set {1,2,3,4}", __func__, 1); + if (pw) *pw = w; + if (ph) *ph = h; + if (pbps) *pbps = bps; + if (pspp) *pspp = spp; + if (pres) { + if (getTiffStreamResolution(tif, &xres, &yres) == 0) + *pres = (l_int32)xres; + } + if (pcmap) { + if (TIFFGetField(tif, TIFFTAG_COLORMAP, &rmap, &gmap, &bmap)) + *pcmap = 1; + } + if (pformat) { + TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &tiffcomp); + *pformat = getTiffCompressedFormat(tiffcomp); + } + return 0; +} + + +/*! + * \brief findTiffCompression() + * + * \param[in] fp file stream; must be rewound to BOF + * \param[out] pcomptype compression type + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The returned compression type is that defined in + * the enum in imageio.h. It is not the tiff flag value. + * (2) The compression type is initialized to IFF_UNKNOWN. + * If it is not one of the specified types, the returned + * type is IFF_TIFF, which indicates no compression. + * (3) When this function is called, the stream must be at BOF. + * If the opened stream is to be used again to read the + * file, it must be rewound to BOF after calling this function. + * </pre> + */ +l_ok +findTiffCompression(FILE *fp, + l_int32 *pcomptype) +{ +l_uint16 tiffcomp; +TIFF *tif; + + if (!pcomptype) + return ERROR_INT("&comptype not defined", __func__, 1); + *pcomptype = IFF_UNKNOWN; /* init */ + if (!fp) + return ERROR_INT("stream not defined", __func__, 1); + + if ((tif = fopenTiff(fp, "r")) == NULL) + return ERROR_INT("tif not opened", __func__, 1); + TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &tiffcomp); + *pcomptype = getTiffCompressedFormat(tiffcomp); + TIFFCleanup(tif); + return 0; +} + + +/*! + * \brief getTiffCompressedFormat() + * + * \param[in] tiffcomp defined in tiff.h + * \return compression format defined in imageio.h + * + * <pre> + * Notes: + * (1) The input must be the actual tiff compression type + * returned by a tiff library call. It should always be + * a valid tiff type. + * (2) The return type is defined in the enum in imageio.h. + * </pre> + */ +static l_int32 +getTiffCompressedFormat(l_uint16 tiffcomp) +{ +l_int32 comptype; + + switch (tiffcomp) + { + case COMPRESSION_CCITTFAX4: + comptype = IFF_TIFF_G4; + break; + case COMPRESSION_CCITTFAX3: + comptype = IFF_TIFF_G3; + break; + case COMPRESSION_CCITTRLE: + comptype = IFF_TIFF_RLE; + break; + case COMPRESSION_PACKBITS: + comptype = IFF_TIFF_PACKBITS; + break; + case COMPRESSION_LZW: + comptype = IFF_TIFF_LZW; + break; + case COMPRESSION_ADOBE_DEFLATE: + comptype = IFF_TIFF_ZIP; + break; + case COMPRESSION_JPEG: + comptype = IFF_TIFF_JPEG; + break; + default: + comptype = IFF_TIFF; + break; + } + return comptype; +} + + +/*--------------------------------------------------------------* + * Extraction of tiff g4 data * + *--------------------------------------------------------------*/ +/*! + * \brief extractG4DataFromFile() + * + * \param[in] filein + * \param[out] pdata binary data of ccitt g4 encoded stream + * \param[out] pnbytes size of binary data + * \param[out] pw [optional] image width + * \param[out] ph [optional] image height + * \param[out] pminisblack [optional] boolean + * \return 0 if OK, 1 on error + */ +l_ok +extractG4DataFromFile(const char *filein, + l_uint8 **pdata, + size_t *pnbytes, + l_int32 *pw, + l_int32 *ph, + l_int32 *pminisblack) +{ +l_uint8 *inarray, *data; +l_uint16 minisblack, comptype; /* accessors require l_uint16 */ +l_int32 istiff; +l_uint32 w, h, rowsperstrip; /* accessors require l_uint32 */ +l_uint32 diroff; +size_t fbytes, nbytes; +FILE *fpin; +TIFF *tif; + + if (!pdata) + return ERROR_INT("&data not defined", __func__, 1); + if (!pnbytes) + return ERROR_INT("&nbytes not defined", __func__, 1); + if (!pw && !ph && !pminisblack) + return ERROR_INT("no output data requested", __func__, 1); + *pdata = NULL; + *pnbytes = 0; + + if ((fpin = fopenReadStream(filein)) == NULL) + return ERROR_INT_1("stream not opened to file", filein, __func__, 1); + istiff = fileFormatIsTiff(fpin); + fclose(fpin); + if (!istiff) + return ERROR_INT_1("filein not tiff", filein, __func__, 1); + + if ((inarray = l_binaryRead(filein, &fbytes)) == NULL) + return ERROR_INT_1("inarray not made", filein, __func__, 1); + + /* Get metadata about the image */ + if ((tif = openTiff(filein, "rb")) == NULL) { + LEPT_FREE(inarray); + return ERROR_INT_1("tif not open for read", filein, __func__, 1); + } + TIFFGetField(tif, TIFFTAG_COMPRESSION, &comptype); + if (comptype != COMPRESSION_CCITTFAX4) { + LEPT_FREE(inarray); + TIFFClose(tif); + return ERROR_INT_1("filein is not g4 compressed", filein, __func__, 1); + } + + TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w); + TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h); + TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); + if (h != rowsperstrip) + L_WARNING("more than 1 strip\n", __func__); + /* From the standard: + TIFFTAG_PHOTOMETRIC = 0 (false) --> min value is white. + TIFFTAG_PHOTOMETRIC = 1 (true) --> min value is black. + Most 1 bpp tiffs have the tag value 0 (black is 1), + because there are fewer black pixels than white pixels, + so it makes sense to encode runs of black pixels. */ + TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &minisblack); +/* TIFFPrintDirectory(tif, stderr, 0); */ + TIFFClose(tif); + if (pw) *pw = (l_int32)w; + if (ph) *ph = (l_int32)h; + if (pminisblack) *pminisblack = (l_int32)minisblack; + + /* The header has 8 bytes: the first 2 are the magic number, + * the next 2 are the version, and the last 4 are the + * offset to the first directory. That's what we want here. + * We have to test the byte order before decoding 4 bytes! */ + if (inarray[0] == 0x4d) { /* big-endian */ + diroff = (inarray[4] << 24) | (inarray[5] << 16) | + (inarray[6] << 8) | inarray[7]; + } else { /* inarray[0] == 0x49 : little-endian */ + diroff = (inarray[7] << 24) | (inarray[6] << 16) | + (inarray[5] << 8) | inarray[4]; + } +/* lept_stderr(" diroff = %d, %x\n", diroff, diroff); */ + + /* Extract the ccittg4 encoded data from the tiff file. + * We skip the 8 byte header and take nbytes of data, + * up to the beginning of the directory (at diroff) */ + nbytes = diroff - 8; + if (nbytes > MaxNumTiffBytes) { + LEPT_FREE(inarray); + L_ERROR("requesting %zu bytes > %zu\n", __func__, + nbytes, MaxNumTiffBytes); + return 1; + } + *pnbytes = nbytes; + if ((data = (l_uint8 *)LEPT_CALLOC(nbytes, sizeof(l_uint8))) == NULL) { + LEPT_FREE(inarray); + return ERROR_INT("data not allocated", __func__, 1); + } + *pdata = data; + memcpy(data, inarray + 8, nbytes); + LEPT_FREE(inarray); + + return 0; +} + + +/*--------------------------------------------------------------* + * Open tiff stream from file stream * + *--------------------------------------------------------------*/ +/*! + * \brief fopenTiff() + * + * \param[in] fp file stream + * \param[in] modestring "r", "w", ... + * \return tiff data structure, opened for a file descriptor + * + * <pre> + * Notes: + * (1) Why is this here? Leffler did not provide a function that + * takes a stream and gives a TIFF. He only gave one that + * generates a TIFF starting with a file descriptor. So we + * need to make it here, because it is useful to have functions + * that take a stream as input. + * (2) We use TIFFClientOpen() together with a set of static wrapper + * functions which map TIFF read, write, seek, close and size. + * to functions expecting a cookie of type stream (i.e. FILE *). + * This implementation was contributed by Jürgen Buchmüller. + * </pre> + */ +static TIFF * +fopenTiff(FILE *fp, + const char *modestring) +{ + if (!fp) + return (TIFF *)ERROR_PTR("stream not opened", __func__, NULL); + if (!modestring) + return (TIFF *)ERROR_PTR("modestring not defined", __func__, NULL); + + TIFFSetWarningHandler(NULL); /* disable warnings */ + TIFFSetErrorHandler(NULL); /* disable error messages */ + + fseek(fp, 0, SEEK_SET); + return TIFFClientOpen("TIFFstream", modestring, (thandle_t)fp, + lept_read_proc, lept_write_proc, lept_seek_proc, + lept_close_proc, lept_size_proc, NULL, NULL); +} + + +/*--------------------------------------------------------------* + * Wrapper for TIFFOpen * + *--------------------------------------------------------------*/ +/*! + * \brief openTiff() + * + * \param[in] filename + * \param[in] modestring "r", "w", ... + * \return tiff data structure + * + * <pre> + * Notes: + * (1) This handles multi-platform file naming. + * </pre> + */ +static TIFF * +openTiff(const char *filename, + const char *modestring) +{ +char *fname; +TIFF *tif; + + if (!filename) + return (TIFF *)ERROR_PTR("filename not defined", __func__, NULL); + if (!modestring) + return (TIFF *)ERROR_PTR("modestring not defined", __func__, NULL); + + TIFFSetWarningHandler(NULL); /* disable warnings */ + TIFFSetErrorHandler(NULL); /* disable error messages */ + + fname = genPathname(filename, NULL); + tif = TIFFOpen(fname, modestring); + LEPT_FREE(fname); + return tif; +} + + +/*----------------------------------------------------------------------* + * Memory I/O: reading memory --> pix and writing pix --> memory * + *----------------------------------------------------------------------*/ +/* It would be nice to use open_memstream() and fmemopen() + * for writing and reading to memory, rsp. These functions manage + * memory for writes and reads that use a file streams interface. + * Unfortunately, the tiff library only has an interface for reading + * and writing to file descriptors, not to file streams. The tiff + * library procedure is to open a "tiff stream" and read/write to it. + * The library provides a client interface for managing the I/O + * from memory, which requires seven callbacks. See the TIFFClientOpen + * man page for callback signatures. Adam Langley provided the code + * to do this. */ + +/*! + * \brief Memory stream buffer used with TIFFClientOpen() + * + * The L_Memstram %buffer has different functions in writing and reading. + * + * * In reading, it is assigned to the data and read from as + * the tiff library uncompresses the data and generates the pix. + * The %offset points to the current read position in the data, + * and the %hw always gives the number of bytes of data. + * The %outdata and %outsize ptrs are not used. + * When finished, tiffCloseCallback() simply frees the L_Memstream. + * + * * In writing, it accepts the data that the tiff library + * produces when a pix is compressed. the buffer points to a + * malloced area of %bufsize bytes. The current writing position + * in the buffer is %offset and the most ever written is %hw. + * The buffer is expanded as necessary. When finished, + * tiffCloseCallback() assigns the %outdata and %outsize ptrs + * to the %buffer and %bufsize results, and frees the L_Memstream. + */ +struct L_Memstream +{ + l_uint8 *buffer; /* expands to hold data when written to; */ + /* fixed size when read from. */ + size_t bufsize; /* current size allocated when written to; */ + /* fixed size of input data when read from. */ + size_t offset; /* byte offset from beginning of buffer. */ + size_t hw; /* high-water mark; max bytes in buffer. */ + l_uint8 **poutdata; /* input param for writing; data goes here. */ + size_t *poutsize; /* input param for writing; data size goes here. */ +}; +typedef struct L_Memstream L_MEMSTREAM; + + + /* These are static functions for memory I/O */ +static L_MEMSTREAM *memstreamCreateForRead(l_uint8 *indata, size_t pinsize); +static L_MEMSTREAM *memstreamCreateForWrite(l_uint8 **poutdata, + size_t *poutsize); +static tsize_t tiffReadCallback(thandle_t handle, tdata_t data, tsize_t length); +static tsize_t tiffWriteCallback(thandle_t handle, tdata_t data, + tsize_t length); +static toff_t tiffSeekCallback(thandle_t handle, toff_t offset, l_int32 whence); +static l_int32 tiffCloseCallback(thandle_t handle); +static toff_t tiffSizeCallback(thandle_t handle); +static l_int32 tiffMapCallback(thandle_t handle, tdata_t *data, toff_t *length); +static void tiffUnmapCallback(thandle_t handle, tdata_t data, toff_t length); + + +static L_MEMSTREAM * +memstreamCreateForRead(l_uint8 *indata, + size_t insize) +{ +L_MEMSTREAM *mstream; + + mstream = (L_MEMSTREAM *)LEPT_CALLOC(1, sizeof(L_MEMSTREAM)); + mstream->buffer = indata; /* handle to input data array */ + mstream->bufsize = insize; /* amount of input data */ + mstream->hw = insize; /* high-water mark fixed at input data size */ + mstream->offset = 0; /* offset always starts at 0 */ + return mstream; +} + + +static L_MEMSTREAM * +memstreamCreateForWrite(l_uint8 **poutdata, + size_t *poutsize) +{ +L_MEMSTREAM *mstream; + + mstream = (L_MEMSTREAM *)LEPT_CALLOC(1, sizeof(L_MEMSTREAM)); + mstream->buffer = (l_uint8 *)LEPT_CALLOC(8 * 1024, 1); + mstream->bufsize = 8 * 1024; + mstream->poutdata = poutdata; /* used only at end of write */ + mstream->poutsize = poutsize; /* ditto */ + mstream->hw = mstream->offset = 0; + return mstream; +} + + +static tsize_t +tiffReadCallback(thandle_t handle, + tdata_t data, + tsize_t length) +{ +L_MEMSTREAM *mstream; +size_t amount; + + mstream = (L_MEMSTREAM *)handle; + amount = L_MIN((size_t)length, mstream->hw - mstream->offset); + + /* Fuzzed files can create this condition! */ + if (mstream->offset + amount < amount || /* overflow */ + mstream->offset + amount > mstream->hw) { + lept_stderr("Bad file: amount too big: %zu\n", amount); + return 0; + } + + memcpy(data, mstream->buffer + mstream->offset, amount); + mstream->offset += amount; + return amount; +} + + +static tsize_t +tiffWriteCallback(thandle_t handle, + tdata_t data, + tsize_t length) +{ +L_MEMSTREAM *mstream; +size_t newsize; + + /* reallocNew() uses calloc to initialize the array. + * If malloc is used instead, for some of the encoding methods, + * not all the data in 'bufsize' bytes in the buffer will + * have been initialized by the end of the compression. */ + mstream = (L_MEMSTREAM *)handle; + if (mstream->offset + length > mstream->bufsize) { + newsize = 2 * (mstream->offset + length); + mstream->buffer = (l_uint8 *)reallocNew((void **)&mstream->buffer, + mstream->hw, newsize); + mstream->bufsize = newsize; + } + + memcpy(mstream->buffer + mstream->offset, data, length); + mstream->offset += length; + mstream->hw = L_MAX(mstream->offset, mstream->hw); + return length; +} + + +static toff_t +tiffSeekCallback(thandle_t handle, + toff_t offset, + l_int32 whence) +{ +L_MEMSTREAM *mstream; + + mstream = (L_MEMSTREAM *)handle; + switch (whence) { + case SEEK_SET: +/* lept_stderr("seek_set: offset = %d\n", offset); */ + if((size_t)offset != offset) { /* size_t overflow on uint32 */ + return (toff_t)ERROR_INT("too large offset value", __func__, 1); + } + mstream->offset = offset; + break; + case SEEK_CUR: +/* lept_stderr("seek_cur: offset = %d\n", offset); */ + mstream->offset += offset; + break; + case SEEK_END: +/* lept_stderr("seek end: hw = %d, offset = %d\n", + mstream->hw, offset); */ + mstream->offset = mstream->hw - offset; /* offset >= 0 */ + break; + default: + return (toff_t)ERROR_INT("bad whence value", __func__, + mstream->offset); + } + + return mstream->offset; +} + + +static l_int32 +tiffCloseCallback(thandle_t handle) +{ +L_MEMSTREAM *mstream; + + mstream = (L_MEMSTREAM *)handle; + if (mstream->poutdata) { /* writing: save the output data */ + *mstream->poutdata = mstream->buffer; + *mstream->poutsize = mstream->hw; + } + LEPT_FREE(mstream); /* never free the buffer! */ + return 0; +} + + +static toff_t +tiffSizeCallback(thandle_t handle) +{ +L_MEMSTREAM *mstream; + + mstream = (L_MEMSTREAM *)handle; + return mstream->hw; +} + + +static l_int32 +tiffMapCallback(thandle_t handle, + tdata_t *data, + toff_t *length) +{ +L_MEMSTREAM *mstream; + + mstream = (L_MEMSTREAM *)handle; + *data = mstream->buffer; + *length = mstream->hw; + return 0; +} + + +static void +tiffUnmapCallback(thandle_t handle, + tdata_t data, + toff_t length) +{ + return; +} + + +/*! + * \brief fopenTiffMemstream() + * + * \param[in] filename for error output; can be "" + * \param[in] operation "w" for write, "r" for read + * \param[out] pdata written data + * \param[out] pdatasize size of written data + * \return tiff data structure, opened for write to memory + * + * <pre> + * Notes: + * (1) This wraps up a number of callbacks for either: + * * reading from tiff in memory buffer --> pix + * * writing from pix --> tiff in memory buffer + * (2) After use, the memstream is automatically destroyed when + * TIFFClose() is called. TIFFCleanup() doesn't free the memstream. + * (3) This does not work in append mode, and in write mode it + * does not append. + * </pre> + */ +static TIFF * +fopenTiffMemstream(const char *filename, + const char *operation, + l_uint8 **pdata, + size_t *pdatasize) +{ +L_MEMSTREAM *mstream; +TIFF *tif; + + if (!filename) + return (TIFF *)ERROR_PTR("filename not defined", __func__, NULL); + if (!operation) + return (TIFF *)ERROR_PTR("operation not defined", __func__, NULL); + if (!pdata) + return (TIFF *)ERROR_PTR("&data not defined", __func__, NULL); + if (!pdatasize) + return (TIFF *)ERROR_PTR("&datasize not defined", __func__, NULL); + if (strcmp(operation, "r") && strcmp(operation, "w")) + return (TIFF *)ERROR_PTR("op not 'r' or 'w'", __func__, NULL); + + if (!strcmp(operation, "r")) + mstream = memstreamCreateForRead(*pdata, *pdatasize); + else + mstream = memstreamCreateForWrite(pdata, pdatasize); + if (!mstream) + return (TIFF *)ERROR_PTR("mstream not made", __func__, NULL); + + TIFFSetWarningHandler(NULL); /* disable warnings */ + TIFFSetErrorHandler(NULL); /* disable error messages */ + + tif = TIFFClientOpen(filename, operation, (thandle_t)mstream, + tiffReadCallback, tiffWriteCallback, + tiffSeekCallback, tiffCloseCallback, + tiffSizeCallback, tiffMapCallback, + tiffUnmapCallback); + if (!tif) + LEPT_FREE(mstream); + return tif; +} + + +/*! + * \brief pixReadMemTiff() + * + * \param[in] cdata const; tiff-encoded + * \param[in] size size of cdata + * \param[in] n page image number: 0-based + * \return pix, or NULL on error + * + * <pre> + * Notes: + * (1) This is a version of pixReadTiff(), where the data is read + * from a memory buffer and uncompressed. + * (2) Use TIFFClose(); TIFFCleanup() doesn't free internal memstream. + * (3) No warning messages on failure, because of how multi-page + * TIFF reading works. You are supposed to keep trying until + * it stops working. + * (4) Tiff directory overhead is linear in the input page number. + * If reading many images, use pixReadMemFromMultipageTiff(). + * </pre> + */ +PIX * +pixReadMemTiff(const l_uint8 *cdata, + size_t size, + l_int32 n) +{ +l_uint8 *data; +l_int32 i; +PIX *pix; +TIFF *tif; + + if (!cdata) + return (PIX *)ERROR_PTR("cdata not defined", __func__, NULL); + + data = (l_uint8 *)cdata; /* we're really not going to change this */ + if ((tif = fopenTiffMemstream("tifferror", "r", &data, &size)) == NULL) + return (PIX *)ERROR_PTR("tiff stream not opened", __func__, NULL); + + pix = NULL; + for (i = 0; ; i++) { + if (i == n) { + if ((pix = pixReadFromTiffStream(tif)) == NULL) { + TIFFClose(tif); + return NULL; + } + pixSetInputFormat(pix, IFF_TIFF); + break; + } + if (TIFFReadDirectory(tif) == 0) + break; + if (i == ManyPagesInTiffFile + 1) { + L_WARNING("big file: more than %d pages\n", __func__, + ManyPagesInTiffFile); + } + } + + TIFFClose(tif); + return pix; +} + + +/*! + * \brief pixReadMemFromMultipageTiff() + * + * \param[in] cdata const; tiff-encoded + * \param[in] size size of cdata + * \param[in,out] poffset set offset to 0 for first image + * \return pix, or NULL on error or if previous call returned the last image + * + * <pre> + * Notes: + * (1) This is a read-from-memory version of pixReadFromMultipageTiff(). + * See that function for usage. + * (2) If reading sequentially from the tiff data, this is more + * efficient than pixReadMemTiff(), which has an overhead + * proportional to the image index n. + * (3) Example usage for reading all the images: + * size_t offset = 0; + * do { + * Pix *pix = pixReadMemFromMultipageTiff(data, size, &offset); + * // do something with pix + * } while (offset != 0); + * </pre> + */ +PIX * +pixReadMemFromMultipageTiff(const l_uint8 *cdata, + size_t size, + size_t *poffset) +{ +l_uint8 *data; +l_int32 retval; +size_t offset; +PIX *pix; +TIFF *tif; + + if (!cdata) + return (PIX *)ERROR_PTR("cdata not defined", __func__, NULL); + if (!poffset) + return (PIX *)ERROR_PTR("&offset not defined", __func__, NULL); + + data = (l_uint8 *)cdata; /* we're really not going to change this */ + if ((tif = fopenTiffMemstream("tifferror", "r", &data, &size)) == NULL) + return (PIX *)ERROR_PTR("tiff stream not opened", __func__, NULL); + + /* Set ptrs in the TIFF to the beginning of the image */ + offset = *poffset; + retval = (offset == 0) ? TIFFSetDirectory(tif, 0) + : TIFFSetSubDirectory(tif, offset); + if (retval == 0) { + TIFFClose(tif); + return NULL; + } + + if ((pix = pixReadFromTiffStream(tif)) == NULL) { + TIFFClose(tif); + return NULL; + } + + /* Advance to the next image and return the new offset */ + TIFFReadDirectory(tif); + *poffset = TIFFCurrentDirOffset(tif); + TIFFClose(tif); + return pix; +} + + +/*! + * \brief pixaReadMemMultipageTiff() + * + * \param[in] data const; multiple pages; tiff-encoded + * \param[in] size size of cdata + * \return pixa, or NULL on error + * + * <pre> + * Notes: + * (1) This is an O(n) read-from-memory version of pixaReadMultipageTiff(). + * </pre> + */ +PIXA * +pixaReadMemMultipageTiff(const l_uint8 *data, + size_t size) +{ +size_t offset; +PIX *pix; +PIXA *pixa; + + if (!data) + return (PIXA *)ERROR_PTR("data not defined", __func__, NULL); + + offset = 0; + pixa = pixaCreate(0); + do { + pix = pixReadMemFromMultipageTiff(data, size, &offset); + pixaAddPix(pixa, pix, L_INSERT); + } while (offset != 0); + return pixa; +} + + +/*! + * \brief pixaWriteMemMultipageTiff() + * + * \param[out] pdata const; tiff-encoded + * \param[out] psize size of data + * \param[in] pixa any depth; colormap will be removed + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) fopenTiffMemstream() does not work in append mode, so we + * must work-around with a temporary file. + * (2) Getting a file stream from + * open_memstream((char **)pdata, psize) + * does not work with the tiff directory. + * </pre> + */ +l_ok +pixaWriteMemMultipageTiff(l_uint8 **pdata, + size_t *psize, + PIXA *pixa) +{ +const char *modestr; +l_int32 i, n; +FILE *fp; +PIX *pix1; + + if (pdata) *pdata = NULL; + if (!pdata) + return ERROR_INT("pdata not defined", __func__, 1); + if (!pixa) + return ERROR_INT("pixa not defined", __func__, 1); + +#ifdef _WIN32 + if ((fp = fopenWriteWinTempfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); +#else + if ((fp = tmpfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", __func__, 1); +#endif /* _WIN32 */ + + n = pixaGetCount(pixa); + for (i = 0; i < n; i++) { + modestr = (i == 0) ? "w" : "a"; + pix1 = pixaGetPix(pixa, i, L_CLONE); + if (pixGetDepth(pix1) == 1) + pixWriteStreamTiffWA(fp, pix1, IFF_TIFF_G4, modestr); + else + pixWriteStreamTiffWA(fp, pix1, IFF_TIFF_ZIP, modestr); + pixDestroy(&pix1); + } + + rewind(fp); + *pdata = l_binaryReadStream(fp, psize); + fclose(fp); + return 0; +} + + +/*! + * \brief pixWriteMemTiff() + * + * \param[out] pdata data of tiff compressed image + * \param[out] psize size of returned data + * \param[in] pix + * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS, + * IFF_TIFF_G3, IFF_TIFF_G4, + * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG + * \return 0 if OK, 1 on error + * + * Usage: + * 1) See pixWriteTiff(. This version writes to + * memory instead of to a file. + */ +l_ok +pixWriteMemTiff(l_uint8 **pdata, + size_t *psize, + PIX *pix, + l_int32 comptype) +{ + return pixWriteMemTiffCustom(pdata, psize, pix, comptype, + NULL, NULL, NULL, NULL); +} + + +/*! + * \brief pixWriteMemTiffCustom() + * + * \param[out] pdata data of tiff compressed image + * \param[out] psize size of returned data + * \param[in] pix + * \param[in] comptype IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS, + * IFF_TIFF_G3, IFF_TIFF_G4, + * IFF_TIFF_LZW, IFF_TIFF_ZIP, IFF_TIFF_JPEG + * \param[in] natags [optional] NUMA of custom tiff tags + * \param[in] savals [optional] SARRAY of values + * \param[in] satypes [optional] SARRAY of types + * \param[in] nasizes [optional] NUMA of sizes + * \return 0 if OK, 1 on error + * + * Usage: + * 1) See pixWriteTiffCustom(. This version writes to + * memory instead of to a file. + * 2) Use TIFFClose(); TIFFCleanup( doesn't free internal memstream. + */ +l_ok +pixWriteMemTiffCustom(l_uint8 **pdata, + size_t *psize, + PIX *pix, + l_int32 comptype, + NUMA *natags, + SARRAY *savals, + SARRAY *satypes, + NUMA *nasizes) +{ +l_int32 ret; +TIFF *tif; + + if (!pdata) + return ERROR_INT("&data not defined", __func__, 1); + if (!psize) + return ERROR_INT("&size not defined", __func__, 1); + if (!pix) + return ERROR_INT("&pix not defined", __func__, 1); + if (pixGetDepth(pix) != 1 && comptype != IFF_TIFF && + comptype != IFF_TIFF_LZW && comptype != IFF_TIFF_ZIP && + comptype != IFF_TIFF_JPEG) { + L_WARNING("invalid compression type for bpp > 1\n", __func__); + comptype = IFF_TIFF_ZIP; + } + + if ((tif = fopenTiffMemstream("tifferror", "w", pdata, psize)) == NULL) + return ERROR_INT("tiff stream not opened", __func__, 1); + ret = pixWriteToTiffStream(tif, pix, comptype, natags, savals, + satypes, nasizes); + + TIFFClose(tif); + return ret; +} + +/* ---------------------------------------*/ +#endif /* HAVE_LIBTIFF && HAVE_LIBJPEG */ +/* ---------------------------------------*/
