/*====================================================================* - 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 pnmio.c *
 *
 *      Stream interface
 *          PIX             *pixReadStreamPnm()
 *          l_int32          readHeaderPnm()
 *          l_int32          freadHeaderPnm()
 *          l_int32          pixWriteStreamPnm()
 *          l_int32          pixWriteStreamAsciiPnm()
 *          l_int32          pixWriteStreamPam()
 *
 *      Read/write to memory
 *          PIX             *pixReadMemPnm()
 *          l_int32          readHeaderMemPnm()
 *          l_int32          pixWriteMemPnm()
 *          l_int32          pixWriteMemPam()
 *
 *      Local helpers
 *          static l_int32   pnmReadNextAsciiValue();
 *          static l_int32   pnmReadNextNumber();
 *          static l_int32   pnmReadNextString();
 *          static l_int32   pnmSkipCommentLines();
 *
 *      These are here by popular demand, with the help of Mattias
 *      Kregert (mattias@kregert.se), who provided the first implementation.
 *
 *      The pnm formats are exceedingly simple, because they have
 *      no compression and no colormaps.  They support images that
 *      are 1 bpp; 2, 4, 8 and 16 bpp grayscale; and rgb.
 *
 *      The original pnm formats ("ASCII") are included for completeness,
 *      but their use is deprecated for all but tiny iconic images.
 *      They are extremely wasteful of memory; for example, the P1 binary
 *      ASCII format is 16 times as big as the packed uncompressed
 *      format, because 2 characters are used to represent every bit
 *      (pixel) in the image.  Reading is slow because we check for extra
 *      white space and EOL at every sample value.
 *
 *      The packed pnm formats ("raw") give file sizes similar to
 *      bmp files, which are uncompressed packed.  However, bmp
 *      are more flexible, because they can support colormaps.
 *
 *      We don't differentiate between the different types ("pbm",
 *      "pgm", "ppm") at the interface level, because this is really a
 *      "distinction without a difference."  You read a file, you get
 *      the appropriate Pix.  You write a file from a Pix, you get the
 *      appropriate type of file.  If there is a colormap on the Pix,
 *      and the Pix is more than 1 bpp, you get either an 8 bpp pgm
 *      or a 24 bpp RGB pnm, depending on whether the colormap colors
 *      are gray or rgb, respectively.
 *
 *      This follows the general policy that the I/O routines don't
 *      make decisions about the content of the image -- you do that
 *      with image processing before you write it out to file.
 *      The I/O routines just try to make the closest connection
 *      possible between the file and the Pix in memory.
 *
 *      On systems like windows without fmemopen() and open_memstream(),
 *      we write data to a temp file and read it back for operations
 *      between pix and compressed-data, such as pixReadMemPnm() and
 *      pixWriteMemPnm().
 *
 *      The P7 format is new. It introduced a header with multiple
 *      lines containing distinct tags for the various fields.
 *      See: http://netpbm.sourceforge.net/doc/pam.html
 *
 *        WIDTH          ; mandatory, exactly once
 *        HEIGHT         ; mandatory, exactly once
 *        DEPTH          ; mandatory, exactly once,
 *                            ; its meaning is equivalent to spp
 *        MAXVAL         ; mandatory, one of 1, 3, 15, 255 or 65535
 *        TUPLTYPE    ; optional; BLACKANDWHITE, GRAYSCALE, RGB
 *                            ; and optional suffix _ALPHA, e.g. RGB_ALPHA
 *        ENDHDR              ; mandatory, last header line
 *
 *      Reading BLACKANDWHITE_ALPHA and GRAYSCALE_ALPHA, which have a DEPTH
 *      value of 2, is supported. The original image is converted to a Pix
 *      with 32-bpp and alpha channel (spp == 4).
 *
 *      Writing P7 format is currently selected for 32-bpp with alpha
 *      channel, i.e. for Pix which have spp == 4, using pixWriteStreamPam().
 *      Jürgen Buchmüller provided the implementation for the P7 (pam) format.
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include #include "allheaders.h" /* --------------------------------------------*/ #if USE_PNMIO /* defined in environ.h */ /* --------------------------------------------*/ static l_int32 pnmReadNextAsciiValue(FILE *fp, l_int32 *pval); static l_int32 pnmReadNextNumber(FILE *fp, l_int32 *pval); static l_int32 pnmReadNextString(FILE *fp, char *buff, l_int32 size); static l_int32 pnmSkipCommentLines(FILE *fp); /* a sanity check on the size read from file */ static const l_int32 MAX_PNM_WIDTH = 100000; static const l_int32 MAX_PNM_HEIGHT = 100000; /*--------------------------------------------------------------------* * Stream interface * *--------------------------------------------------------------------*/ /*! * \brief pixReadStreamPnm() * * \param[in] fp file stream opened for read * \return pix, or NULL on error */ PIX * pixReadStreamPnm(FILE *fp) { l_uint8 val8, rval8, gval8, bval8, aval8, mask8; l_uint16 val16, rval16, gval16, bval16, aval16; l_int32 w, h, d, bps, spp, bpl, wpl, i, j, type; l_int32 val, rval, gval, bval; l_uint32 rgbval; l_uint32 *line, *data; PIX *pix; PROCNAME("pixReadStreamPnm"); if (!fp) return (PIX *)ERROR_PTR("fp not defined", procName, NULL); if (freadHeaderPnm(fp, &w, &h, &d, &type, &bps, &spp)) return (PIX *)ERROR_PTR("header read failed", procName, NULL); if (bps < 1 || bps > 16) return (PIX *)ERROR_PTR("invalid bps", procName, NULL); if (spp < 1 || spp > 4) return (PIX *)ERROR_PTR("invalid spp", procName, NULL); if ((pix = pixCreate(w, h, d)) == NULL) return (PIX *)ERROR_PTR("pix not made", procName, NULL); pixSetInputFormat(pix, IFF_PNM); data = pixGetData(pix); wpl = pixGetWpl(pix); /* If type == 6 and bps == 16, we use the code in type 7 * to read 6 bytes/pixel from the input file. */ if (type == 6 && bps == 16) type = 7; switch (type) { case 1: case 2: /* Old "ASCII" binary or gray format */ for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (pnmReadNextAsciiValue(fp, &val)) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read abend", procName, NULL); } pixSetPixel(pix, j, i, val); } } break; case 3: /* Old "ASCII" rgb format */ for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (pnmReadNextAsciiValue(fp, &rval)) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read abend", procName, NULL); } if (pnmReadNextAsciiValue(fp, &gval)) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read abend", procName, NULL); } if (pnmReadNextAsciiValue(fp, &bval)) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read abend", procName, NULL); } composeRGBPixel(rval, gval, bval, &rgbval); pixSetPixel(pix, j, i, rgbval); } } break; case 4: /* "raw" format for 1 bpp */ bpl = (d * w + 7) / 8; for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < bpl; j++) { if (fread(&val8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error in 4", procName, NULL); } SET_DATA_BYTE(line, j, val8); } } break; case 5: /* "raw" format for grayscale */ for (i = 0; i < h; i++) { line = data + i * wpl; if (d != 16) { for (j = 0; j < w; j++) { if (fread(&val8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("error in 5", procName, NULL); } if (d == 2) SET_DATA_DIBIT(line, j, val8); else if (d == 4) SET_DATA_QBIT(line, j, val8); else /* d == 8 */ SET_DATA_BYTE(line, j, val8); } } else { /* d == 16 */ for (j = 0; j < w; j++) { if (fread(&val16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("16 bpp error", procName, NULL); } SET_DATA_TWO_BYTES(line, j, val16); } } } break; case 6: /* "raw" format, type == 6; 8 bps, rgb */ for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < wpl; j++) { if (fread(&rval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 6", procName, NULL); } if (fread(&gval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 6", procName, NULL); } if (fread(&bval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 6", procName, NULL); } composeRGBPixel(rval8, gval8, bval8, &rgbval); line[j] = rgbval; } } break; case 7: /* "arbitrary" format; type == 7; */ if (bps != 16) { mask8 = (1 << bps) - 1; switch (spp) { case 1: /* 1, 2, 4, 8 bpp grayscale */ for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (fread(&val8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } val8 = val8 & mask8; if (bps == 1) val8 ^= 1; /* white-is-1 photometry */ pixSetPixel(pix, j, i, val8); } } break; case 2: /* 1, 2, 4, 8 bpp grayscale + alpha */ for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (fread(&val8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&aval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } val8 = val8 & mask8; aval8 = aval8 & mask8; composeRGBAPixel(val8, val8, val8, aval8, &rgbval); pixSetPixel(pix, j, i, rgbval); } } pixSetSpp(pix, 4); break; case 3: /* rgb */ for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < wpl; j++) { if (fread(&rval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&gval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&bval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } rval8 = rval8 & mask8; gval8 = gval8 & mask8; bval8 = bval8 & mask8; composeRGBPixel(rval8, gval8, bval8, &rgbval); line[j] = rgbval; } } break; case 4: /* rgba */ for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < wpl; j++) { if (fread(&rval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&gval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&bval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&aval8, 1, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } rval8 = rval8 & mask8; gval8 = gval8 & mask8; bval8 = bval8 & mask8; aval8 = aval8 & mask8; composeRGBAPixel(rval8, gval8, bval8, aval8, &rgbval); line[j] = rgbval; } } pixSetSpp(pix, 4); break; } } else { /* bps == 16 */ /* I have only seen one example that is type 6, 16 bps. * It was 3 spp (rgb), and the 8 bps of real data was stored * in the second byte. In the following, I make the wild * assumption that for all 16 bpp pnm/pam files, we can * take the second byte. */ switch (spp) { case 1: /* 16 bps grayscale */ for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (fread(&val16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } val8 = val16 & 0xff; pixSetPixel(pix, j, i, val8); } } break; case 2: /* 16 bps grayscale + alpha */ for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (fread(&val16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&aval16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } val8 = val16 & 0xff; aval8 = aval16 & 0xff; composeRGBAPixel(val8, val8, val8, aval8, &rgbval); pixSetPixel(pix, j, i, rgbval); } } pixSetSpp(pix, 4); break; case 3: /* 16bps rgb */ for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < wpl; j++) { if (fread(&rval16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&gval16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&bval16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } rval8 = rval16 & 0xff; gval8 = gval16 & 0xff; bval8 = bval16 & 0xff; composeRGBPixel(rval8, gval8, bval8, &rgbval); line[j] = rgbval; } } break; case 4: /* 16bps rgba */ for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < wpl; j++) { if (fread(&rval16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&gval16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&bval16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } if (fread(&aval16, 2, 1, fp) != 1) { pixDestroy(&pix); return (PIX *)ERROR_PTR("read error type 7", procName, NULL); } rval8 = rval16 & 0xff; gval8 = gval16 & 0xff; bval8 = bval16 & 0xff; aval8 = aval16 & 0xff; composeRGBAPixel(rval8, gval8, bval8, aval8, &rgbval); line[j] = rgbval; } } pixSetSpp(pix, 4); break; } } break; } return pix; } /*! * \brief readHeaderPnm() * * \param[in] filename * \param[out] pw [optional] * \param[out] ph [optional] * \param[out] pd [optional] * \param[out] ptype [optional] pnm type * \param[out] pbps [optional] bits/sample * \param[out] pspp [optional] samples/pixel * \return 0 if OK, 1 on error */ l_ok readHeaderPnm(const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pd, l_int32 *ptype, l_int32 *pbps, l_int32 *pspp) { l_int32 ret; FILE *fp; PROCNAME("readHeaderPnm"); if (pw) *pw = 0; if (ph) *ph = 0; if (pd) *pd = 0; if (ptype) *ptype = 0; if (pbps) *pbps = 0; if (pspp) *pspp = 0; if (!filename) return ERROR_INT("filename not defined", procName, 1); if ((fp = fopenReadStream(filename)) == NULL) return ERROR_INT("image file not found", procName, 1); ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp); fclose(fp); return ret; } /*! * \brief freadHeaderPnm() * * \param[in] fp file stream opened for read * \param[out] pw [optional] * \param[out] ph [optional] * \param[out] pd [optional] * \param[out] ptype [optional] pnm type * \param[out] pbps [optional] bits/sample * \param[out] pspp [optional] samples/pixel * \return 0 if OK, 1 on error */ l_ok freadHeaderPnm(FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pd, l_int32 *ptype, l_int32 *pbps, l_int32 *pspp) { char tag[16], tupltype[32]; l_int32 i, w, h, d, bps, spp, type; l_int32 maxval; l_int32 ch; PROCNAME("freadHeaderPnm"); if (pw) *pw = 0; if (ph) *ph = 0; if (pd) *pd = 0; if (ptype) *ptype = 0; if (pbps) *pbps = 0; if (pspp) *pspp = 0; if (!fp) return ERROR_INT("fp not defined", procName, 1); if (fscanf(fp, "P%d\n", &type) != 1) return ERROR_INT("invalid read for type", procName, 1); if (type < 1 || type > 7) return ERROR_INT("invalid pnm file", procName, 1); if (pnmSkipCommentLines(fp)) return ERROR_INT("no data in file", procName, 1); if (type == 7) { w = h = d = bps = spp = maxval = 0; for (i = 0; i < 10; i++) { /* limit to 10 lines of this header */ if (pnmReadNextString(fp, tag, sizeof(tag))) return ERROR_INT("found no next tag", procName, 1); if (!strcmp(tag, "WIDTH")) { if (pnmReadNextNumber(fp, &w)) return ERROR_INT("failed reading width", procName, 1); continue; } if (!strcmp(tag, "HEIGHT")) { if (pnmReadNextNumber(fp, &h)) return ERROR_INT("failed reading height", procName, 1); continue; } if (!strcmp(tag, "DEPTH")) { if (pnmReadNextNumber(fp, &spp)) return ERROR_INT("failed reading depth", procName, 1); continue; } if (!strcmp(tag, "MAXVAL")) { if (pnmReadNextNumber(fp, &maxval)) return ERROR_INT("failed reading maxval", procName, 1); continue; } if (!strcmp(tag, "TUPLTYPE")) { if (pnmReadNextString(fp, tupltype, sizeof(tupltype))) return ERROR_INT("failed reading tuple type", procName, 1); continue; } if (!strcmp(tag, "ENDHDR")) { if ('\n' != (ch = fgetc(fp))) return ERROR_INT("missing LF after ENDHDR", procName, 1); break; } } if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT) { L_INFO("invalid size: w = %d, h = %d\n", procName, w, h); return 1; } if (maxval == 1) { d = bps = 1; } else if (maxval == 3) { d = bps = 2; } else if (maxval == 15) { d = bps = 4; } else if (maxval == 255) { d = bps = 8; } else if (maxval == 0xffff) { d = bps = 16; } else { L_INFO("invalid maxval = %d\n", procName, maxval); return 1; } switch (spp) { case 1: /* d and bps are already set */ break; case 2: case 3: case 4: /* create a 32 bpp Pix */ d = 32; break; default: L_INFO("invalid depth = %d\n", procName, spp); return 1; } } else { if (fscanf(fp, "%d %d\n", &w, &h) != 2) return ERROR_INT("invalid read for w,h", procName, 1); if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT) { L_INFO("invalid size: w = %d, h = %d\n", procName, w, h); return 1; } /* Get depth of pix. For types 2 and 5, we use the maxval. * Important implementation note: * - You can't use fscanf(), which throws away whitespace, * and will discard binary data if it starts with whitespace(s). * - You can't use fgets(), which stops at newlines, but this * dumb format doesn't require a newline after the maxval * number -- it just requires one whitespace character. * - Which leaves repeated calls to fgetc, including swallowing * the single whitespace character. */ if (type == 1 || type == 4) { d = 1; spp = 1; bps = 1; } else if (type == 2 || type == 5) { if (pnmReadNextNumber(fp, &maxval)) return ERROR_INT("invalid read for maxval (2,5)", procName, 1); if (maxval == 3) { d = 2; } else if (maxval == 15) { d = 4; } else if (maxval == 255) { d = 8; } else if (maxval == 0xffff) { d = 16; } else { lept_stderr("maxval = %d\n", maxval); return ERROR_INT("invalid maxval", procName, 1); } bps = d; spp = 1; } else { /* type == 3 || type == 6; this is rgb */ if (pnmReadNextNumber(fp, &maxval)) return ERROR_INT("invalid read for maxval (3,6)", procName, 1); if (maxval != 255 && maxval != 0xffff) { L_ERROR("unexpected maxval = %d\n", procName, maxval); return 1; } bps = (maxval == 255) ? 8 : 16; d = 32; spp = 3; } } if (pw) *pw = w; if (ph) *ph = h; if (pd) *pd = d; if (ptype) *ptype = type; if (pbps) *pbps = bps; if (pspp) *pspp = spp; return 0; } /*! * \brief pixWriteStreamPnm() * * \param[in] fp file stream opened for write * \param[in] pix * \return 0 if OK; 1 on error * *
 * Notes:
 *      (1) This writes "raw" packed format only:
 *          1 bpp --> pbm (P4)
 *          2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm (P5)
 *          2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm (P6)
 *      (2) 24 bpp rgb are not supported in leptonica, but this will
 *          write them out as a packed array of bytes (3 to a pixel).
 * 
*/ l_ok pixWriteStreamPnm(FILE *fp, PIX *pix) { l_uint8 val8; l_uint8 pel[4]; l_uint16 val16; l_int32 h, w, d, ds, i, j, wpls, bpl, filebpl, writeerror, maxval; l_uint32 *pword, *datas, *lines; PIX *pixs; PROCNAME("pixWriteStreamPnm"); if (!fp) return ERROR_INT("fp not defined", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); pixGetDimensions(pix, &w, &h, &d); if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 24 && d != 32) return ERROR_INT("d not in {1,2,4,8,16,24,32}", procName, 1); if (d == 32 && pixGetSpp(pix) == 4) return pixWriteStreamPam(fp, pix); /* If a colormap exists, remove and convert to grayscale or rgb */ if (pixGetColormap(pix) != NULL) pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); else pixs = pixClone(pix); ds = pixGetDepth(pixs); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); writeerror = 0; if (ds == 1) { /* binary */ fprintf(fp, "P4\n# Raw PBM file written by leptonica " "(www.leptonica.com)\n%d %d\n", w, h); bpl = (w + 7) / 8; for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < bpl; j++) { val8 = GET_DATA_BYTE(lines, j); fwrite(&val8, 1, 1, fp); } } } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) { /* grayscale */ maxval = (1 << ds) - 1; fprintf(fp, "P5\n# Raw PGM file written by leptonica " "(www.leptonica.com)\n%d %d\n%d\n", w, h, maxval); if (ds != 16) { for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { if (ds == 2) val8 = GET_DATA_DIBIT(lines, j); else if (ds == 4) val8 = GET_DATA_QBIT(lines, j); else /* ds == 8 */ val8 = GET_DATA_BYTE(lines, j); fwrite(&val8, 1, 1, fp); } } } else { /* ds == 16 */ for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { val16 = GET_DATA_TWO_BYTES(lines, j); fwrite(&val16, 2, 1, fp); } } } } else { /* rgb color */ fprintf(fp, "P6\n# Raw PPM file written by leptonica " "(www.leptonica.com)\n%d %d\n255\n", w, h); if (d == 24) { /* packed, 3 bytes to a pixel */ filebpl = 3 * w; for (i = 0; i < h; i++) { /* write out each raster line */ lines = datas + i * wpls; if (fwrite(lines, 1, filebpl, fp) != filebpl) writeerror = 1; } } else { /* 32 bpp rgb */ for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < wpls; j++) { pword = lines + j; pel[0] = GET_DATA_BYTE(pword, COLOR_RED); pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN); pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE); if (fwrite(pel, 1, 3, fp) != 3) writeerror = 1; } } } } pixDestroy(&pixs); if (writeerror) return ERROR_INT("image write fail", procName, 1); return 0; } /*! * \brief pixWriteStreamAsciiPnm() * * \param[in] fp file stream opened for write * \param[in] pix * \return 0 if OK; 1 on error * * Writes "ASCII" format only: * 1 bpp --> pbm P1 * 2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm P2 * 2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm P3 */ l_ok pixWriteStreamAsciiPnm(FILE *fp, PIX *pix) { char buffer[256]; l_uint8 cval[3]; l_int32 h, w, d, ds, i, j, k, maxval, count; l_uint32 val; PIX *pixs; PROCNAME("pixWriteStreamAsciiPnm"); if (!fp) return ERROR_INT("fp not defined", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); pixGetDimensions(pix, &w, &h, &d); if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) return ERROR_INT("d not in {1,2,4,8,16,32}", procName, 1); /* If a colormap exists, remove and convert to grayscale or rgb */ if (pixGetColormap(pix) != NULL) pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); else pixs = pixClone(pix); ds = pixGetDepth(pixs); if (ds == 1) { /* binary */ fprintf(fp, "P1\n# Ascii PBM file written by leptonica " "(www.leptonica.com)\n%d %d\n", w, h); count = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val); if (val == 0) fputc('0', fp); else /* val == 1 */ fputc('1', fp); fputc(' ', fp); count += 2; if (count >= 70) { fputc('\n', fp); count = 0; } } } } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) { /* grayscale */ maxval = (1 << ds) - 1; fprintf(fp, "P2\n# Ascii PGM file written by leptonica " "(www.leptonica.com)\n%d %d\n%d\n", w, h, maxval); count = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val); if (ds == 2) { snprintf(buffer, sizeof(buffer), "%1d ", val); fwrite(buffer, 1, 2, fp); count += 2; } else if (ds == 4) { snprintf(buffer, sizeof(buffer), "%2d ", val); fwrite(buffer, 1, 3, fp); count += 3; } else if (ds == 8) { snprintf(buffer, sizeof(buffer), "%3d ", val); fwrite(buffer, 1, 4, fp); count += 4; } else { /* ds == 16 */ snprintf(buffer, sizeof(buffer), "%5d ", val); fwrite(buffer, 1, 6, fp); count += 6; } if (count >= 60) { fputc('\n', fp); count = 0; } } } } else { /* rgb color */ fprintf(fp, "P3\n# Ascii PPM file written by leptonica " "(www.leptonica.com)\n%d %d\n255\n", w, h); count = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val); cval[0] = GET_DATA_BYTE(&val, COLOR_RED); cval[1] = GET_DATA_BYTE(&val, COLOR_GREEN); cval[2] = GET_DATA_BYTE(&val, COLOR_BLUE); for (k = 0; k < 3; k++) { snprintf(buffer, sizeof(buffer), "%3d ", cval[k]); fwrite(buffer, 1, 4, fp); count += 4; if (count >= 60) { fputc('\n', fp); count = 0; } } } } } pixDestroy(&pixs); return 0; } /*! * \brief pixWriteStreamPam() * * \param[in] fp file stream opened for write * \param[in] pix * \return 0 if OK; 1 on error * *
 * Notes:
 *      (1) This writes arbitrary PAM (P7) packed format.
 *      (2) 24 bpp rgb are not supported in leptonica, but this will
 *          write them out as a packed array of bytes (3 to a pixel).
 * 
*/ l_ok pixWriteStreamPam(FILE *fp, PIX *pix) { l_uint8 val8; l_uint8 pel[8]; l_uint16 val16; l_int32 h, w, d, ds, i, j; l_int32 wpls, spps, filebpl, writeerror, maxval; l_uint32 *pword, *datas, *lines; PIX *pixs; PROCNAME("pixWriteStreamPam"); if (!fp) return ERROR_INT("fp not defined", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); pixGetDimensions(pix, &w, &h, &d); if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 24 && d != 32) return ERROR_INT("d not in {1,2,4,8,16,24,32}", procName, 1); /* If a colormap exists, remove and convert to grayscale or rgb */ if (pixGetColormap(pix) != NULL) pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); else pixs = pixClone(pix); ds = pixGetDepth(pixs); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); spps = pixGetSpp(pixs); if (ds < 24) maxval = (1 << ds) - 1; else maxval = 255; writeerror = 0; fprintf(fp, "P7\n# Arbitrary PAM file written by leptonica " "(www.leptonica.com)\n"); fprintf(fp, "WIDTH %d\n", w); fprintf(fp, "HEIGHT %d\n", h); fprintf(fp, "DEPTH %d\n", spps); fprintf(fp, "MAXVAL %d\n", maxval); if (spps == 1 && ds == 1) fprintf(fp, "TUPLTYPE BLACKANDWHITE\n"); else if (spps == 1) fprintf(fp, "TUPLTYPE GRAYSCALE\n"); else if (spps == 3) fprintf(fp, "TUPLTYPE RGB\n"); else if (spps == 4) fprintf(fp, "TUPLTYPE RGB_ALPHA\n"); fprintf(fp, "ENDHDR\n"); switch (d) { case 1: for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { val8 = GET_DATA_BIT(lines, j); val8 ^= 1; /* pam apparently uses white-is-1 photometry */ if (fwrite(&val8, 1, 1, fp) != 1) writeerror = 1; } } break; case 2: for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { val8 = GET_DATA_DIBIT(lines, j); if (fwrite(&val8, 1, 1, fp) != 1) writeerror = 1; } } break; case 4: for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { val8 = GET_DATA_QBIT(lines, j); if (fwrite(&val8, 1, 1, fp) != 1) writeerror = 1; } } break; case 8: for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { val8 = GET_DATA_BYTE(lines, j); if (fwrite(&val8, 1, 1, fp) != 1) writeerror = 1; } } break; case 16: for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { val16 = GET_DATA_TWO_BYTES(lines, j); if (fwrite(&val16, 2, 1, fp) != 1) writeerror = 1; } } break; case 24: filebpl = 3 * w; for (i = 0; i < h; i++) { lines = datas + i * wpls; if (fwrite(lines, 1, filebpl, fp) != filebpl) writeerror = 1; } break; case 32: switch (spps) { case 3: for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < wpls; j++) { pword = lines + j; pel[0] = GET_DATA_BYTE(pword, COLOR_RED); pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN); pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE); if (fwrite(pel, 1, 3, fp) != 3) writeerror = 1; } } break; case 4: for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < wpls; j++) { pword = lines + j; pel[0] = GET_DATA_BYTE(pword, COLOR_RED); pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN); pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE); pel[3] = GET_DATA_BYTE(pword, L_ALPHA_CHANNEL); if (fwrite(pel, 1, 4, fp) != 4) writeerror = 1; } } break; } break; } pixDestroy(&pixs); if (writeerror) return ERROR_INT("image write fail", procName, 1); return 0; } /*---------------------------------------------------------------------* * Read/write to memory * *---------------------------------------------------------------------*/ /*! * \brief pixReadMemPnm() * * \param[in] data const; pnm-encoded * \param[in] size of data * \return pix, or NULL on error * *
 * Notes:
 *      (1) The %size byte of %data must be a null character.
 * 
*/ PIX * pixReadMemPnm(const l_uint8 *data, size_t size) { FILE *fp; PIX *pix; PROCNAME("pixReadMemPnm"); if (!data) return (PIX *)ERROR_PTR("data not defined", procName, NULL); if ((fp = fopenReadFromMemory(data, size)) == NULL) return (PIX *)ERROR_PTR("stream not opened", procName, NULL); pix = pixReadStreamPnm(fp); fclose(fp); if (!pix) L_ERROR("pix not read\n", procName); return pix; } /*! * \brief readHeaderMemPnm() * * \param[in] data const; pnm-encoded * \param[in] size of data * \param[out] pw [optional] * \param[out] ph [optional] * \param[out] pd [optional] * \param[out] ptype [optional] pnm type * \param[out] pbps [optional] bits/sample * \param[out] pspp [optional] samples/pixel * \return 0 if OK, 1 on error */ l_ok readHeaderMemPnm(const l_uint8 *data, size_t size, l_int32 *pw, l_int32 *ph, l_int32 *pd, l_int32 *ptype, l_int32 *pbps, l_int32 *pspp) { l_int32 ret; FILE *fp; PROCNAME("readHeaderMemPnm"); if (!data) return ERROR_INT("data not defined", procName, 1); if ((fp = fopenReadFromMemory(data, size)) == NULL) return ERROR_INT("stream not opened", procName, 1); ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp); fclose(fp); if (ret) return ERROR_INT("header data read failed", procName, 1); return 0; } /*! * \brief pixWriteMemPnm() * * \param[out] pdata data of PNM image * \param[out] psize size of returned data * \param[in] pix * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) See pixWriteStreamPnm() for usage.  This version writes to
 *          memory instead of to a file stream.
 * 
*/ l_ok pixWriteMemPnm(l_uint8 **pdata, size_t *psize, PIX *pix) { l_int32 ret; FILE *fp; PROCNAME("pixWriteMemPnm"); if (pdata) *pdata = NULL; if (psize) *psize = 0; if (!pdata) return ERROR_INT("&data not defined", procName, 1 ); if (!psize) return ERROR_INT("&size not defined", procName, 1 ); if (!pix) return ERROR_INT("&pix not defined", procName, 1 ); #if HAVE_FMEMOPEN if ((fp = open_memstream((char **)pdata, psize)) == NULL) return ERROR_INT("stream not opened", procName, 1); ret = pixWriteStreamPnm(fp, pix); #else L_INFO("work-around: writing to a temp file\n", procName); #ifdef _WIN32 if ((fp = fopenWriteWinTempfile()) == NULL) return ERROR_INT("tmpfile stream not opened", procName, 1); #else if ((fp = tmpfile()) == NULL) return ERROR_INT("tmpfile stream not opened", procName, 1); #endif /* _WIN32 */ ret = pixWriteStreamPnm(fp, pix); rewind(fp); *pdata = l_binaryReadStream(fp, psize); #endif /* HAVE_FMEMOPEN */ fclose(fp); return ret; } /*! * \brief pixWriteMemPam() * * \param[out] pdata data of PAM image * \param[out] psize size of returned data * \param[in] pix * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) See pixWriteStreamPnm() for usage.  This version writes to
 *          memory instead of to a file stream.
 * 
*/ l_ok pixWriteMemPam(l_uint8 **pdata, size_t *psize, PIX *pix) { l_int32 ret; FILE *fp; PROCNAME("pixWriteMemPam"); if (pdata) *pdata = NULL; if (psize) *psize = 0; if (!pdata) return ERROR_INT("&data not defined", procName, 1 ); if (!psize) return ERROR_INT("&size not defined", procName, 1 ); if (!pix) return ERROR_INT("&pix not defined", procName, 1 ); #if HAVE_FMEMOPEN if ((fp = open_memstream((char **)pdata, psize)) == NULL) return ERROR_INT("stream not opened", procName, 1); ret = pixWriteStreamPam(fp, pix); #else L_INFO("work-around: writing to a temp file\n", procName); #ifdef _WIN32 if ((fp = fopenWriteWinTempfile()) == NULL) return ERROR_INT("tmpfile stream not opened", procName, 1); #else if ((fp = tmpfile()) == NULL) return ERROR_INT("tmpfile stream not opened", procName, 1); #endif /* _WIN32 */ ret = pixWriteStreamPam(fp, pix); rewind(fp); *pdata = l_binaryReadStream(fp, psize); #endif /* HAVE_FMEMOPEN */ fclose(fp); return ret; } /*--------------------------------------------------------------------* * Static helpers * *--------------------------------------------------------------------*/ /*! * \brief pnmReadNextAsciiValue() * * Return: 0 if OK, 1 on error or EOF. * * Notes: * (1) This reads the next sample value in ASCII from the file. */ static l_int32 pnmReadNextAsciiValue(FILE *fp, l_int32 *pval) { l_int32 c, ignore; PROCNAME("pnmReadNextAsciiValue"); if (!pval) return ERROR_INT("&val not defined", procName, 1); *pval = 0; if (!fp) return ERROR_INT("stream not open", procName, 1); do { /* skip whitespace and non-numeric characters */ if ((c = fgetc(fp)) == EOF) return 1; } while (!isdigit(c)); fseek(fp, -1L, SEEK_CUR); /* back up one byte */ ignore = fscanf(fp, "%d", pval); return 0; } /*! * \brief pnmReadNextNumber() * * \param[in] fp file stream * \param[out] pval value as an integer * \return 0 if OK, 1 on error or EOF. * *
 * Notes:
 *      (1) This reads the next set of numeric chars, returning
 *          the value and swallowing the trailing whitespace character.
 *          This is needed to read the maxval in the header, which
 *          precedes the binary data.
 * 
*/ static l_int32 pnmReadNextNumber(FILE *fp, l_int32 *pval) { char buf[8]; l_int32 i, c, foundws; PROCNAME("pnmReadNextNumber"); if (!pval) return ERROR_INT("&val not defined", procName, 1); *pval = 0; if (!fp) return ERROR_INT("stream not open", procName, 1); /* The ASCII characters for the number are followed by exactly * one whitespace character. */ foundws = FALSE; for (i = 0; i < 8; i++) buf[i] = '\0'; for (i = 0; i < 8; i++) { if ((c = fgetc(fp)) == EOF) return ERROR_INT("end of file reached", procName, 1); if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { foundws = TRUE; buf[i] = '\n'; break; } if (!isdigit(c)) return ERROR_INT("char read is not a digit", procName, 1); buf[i] = c; } if (!foundws) return ERROR_INT("no whitespace found", procName, 1); if (sscanf(buf, "%d", pval) != 1) return ERROR_INT("invalid read", procName, 1); return 0; } /*! * \brief pnmReadNextString() * * \param[in] fp file stream * \param[out] buff pointer to the string buffer * \param[in] size max. number of charactes in buffer * \return 0 if OK, 1 on error or EOF. * *
 * Notes:
 *      (1) This reads the next set of alphanumeric chars,
 *          returning the string and swallowing the trailing
 *          whitespace characters.
 *          This is needed to read header lines, which precede
 *          the P7 format binary data.
 * 
*/ static l_int32 pnmReadNextString(FILE *fp, char *buff, l_int32 size) { l_int32 i, c; PROCNAME("pnmReadNextString"); if (!buff) return ERROR_INT("buff not defined", procName, 1); *buff = '\0'; if (!fp) return ERROR_INT("stream not open", procName, 1); if (size <= 0) return ERROR_INT("size is too small", procName, 1); do { /* skip whitespace */ if ((c = fgetc(fp)) == EOF) return ERROR_INT("end of file reached", procName, 1); } while (c == ' ' || c == '\t' || c == '\n' || c == '\r'); /* Comment lines are allowed to appear * anywhere in the header lines */ if (c == '#') { do { /* each line starting with '#' */ do { /* this entire line */ if ((c = fgetc(fp)) == EOF) return ERROR_INT("end of file reached", procName, 1); } while (c != '\n'); if ((c = fgetc(fp)) == EOF) return ERROR_INT("end of file reached", procName, 1); } while (c == '#'); } /* The next string ends when there is * a whitespace character following. */ for (i = 0; i < size - 1; i++) { if (c == ' ' || c == '\t' || c == '\n' || c == '\r') break; buff[i] = c; if ((c = fgetc(fp)) == EOF) return ERROR_INT("end of file reached", procName, 1); } buff[i] = '\0'; /* Back up one byte */ fseek(fp, -1L, SEEK_CUR); if (i >= size - 1) return ERROR_INT("buff size too small", procName, 1); /* Skip over trailing spaces and tabs */ for (;;) { if ((c = fgetc(fp)) == EOF) return ERROR_INT("end of file reached", procName, 1); if (c != ' ' && c != '\t') break; } /* Back up one byte */ fseek(fp, -1L, SEEK_CUR); return 0; } /*! * \brief pnmSkipCommentLines() * * Return: 0 if OK, 1 on error or EOF * * Notes: * (1) Comment lines begin with '#' * (2) Usage: caller should check return value for EOF */ static l_int32 pnmSkipCommentLines(FILE *fp) { l_int32 c; PROCNAME("pnmSkipCommentLines"); if (!fp) return ERROR_INT("stream not open", procName, 1); if ((c = fgetc(fp)) == EOF) return 1; if (c == '#') { do { /* each line starting with '#' */ do { /* this entire line */ if ((c = fgetc(fp)) == EOF) return 1; } while (c != '\n'); if ((c = fgetc(fp)) == EOF) return 1; } while (c == '#'); } /* Back up one byte */ fseek(fp, -1L, SEEK_CUR); return 0; } /* --------------------------------------------*/ #endif /* USE_PNMIO */ /* --------------------------------------------*/