/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - This software is distributed in the hope that it will be - useful, but with NO WARRANTY OF ANY KIND. - No author or distributor accepts responsibility to anyone for the - consequences of using this software, or for whether it serves any - particular purpose or works at all, unless he or she says so in - writing. Everyone is granted permission to copy, modify and - redistribute this source code, for commercial or non-commercial - purposes, with the following restrictions: (1) the origin of this - source code must not be misrepresented; (2) modified versions must - be plainly marked as such; and (3) this notice may not be removed - or altered from any source or modified source distribution. *====================================================================*/ /* * pnmio.c * * Stream interface * PIX *pixReadStreamPnm() * l_int32 pixWriteStreamPnm() * l_int32 pixWriteStreamAsciiPnm() * * Read/write to memory [not on windows] * PIX *pixReadMemPnm() * l_int32 pixWriteMemPnm() * * Local helpers * static l_int32 pnmReadNextAsciiValue(); * 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. */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include "allheaders.h" /* --------------------------------------------*/ #if USE_PNMIO /* defined in environ.h */ /* --------------------------------------------*/ static l_int32 pnmReadNextAsciiValue(FILE *fp, l_int32 *pval); 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 * *--------------------------------------------------------------------*/ /*! * pixReadStreamPnm() * * Input: stream opened for read * Return: pix, or null on error */ PIX * pixReadStreamPnm(FILE *fp) { l_uint8 val8, rval8, gval8, bval8; l_uint16 val16; l_int32 w, h, d, bpl, wpl, i, j, type; l_int32 maxval, 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); fscanf(fp, "P%d\n", &type); if (type < 1 || type > 6) return (PIX *)ERROR_PTR("invalid pnm file", procName, NULL); if (pnmSkipCommentLines(fp)) return (PIX *)ERROR_PTR("no data in file", procName, NULL); fscanf(fp, "%d %d\n", &w, &h); if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT) return (PIX *)ERROR_PTR("invalid sizes", procName, NULL); /* Get depth of pix */ if (type == 1 || type == 4) d = 1; else if (type == 2 || type == 5) { fscanf(fp, "%d\n", &maxval); if (maxval == 3) d = 2; else if (maxval == 15) d = 4; else if (maxval == 255) d = 8; else if (maxval == 0xffff) d = 16; else { fprintf(stderr, "maxval = %d\n", maxval); return (PIX *)ERROR_PTR("invalid maxval", procName, NULL); } } else { /* type == 3 || type == 6; this is rgb */ fscanf(fp, "%d\n", &maxval); if (maxval != 255) L_WARNING_INT("unexpected maxval = %d", procName, maxval); d = 32; } if ((pix = pixCreate(w, h, d)) == NULL) return (PIX *)ERROR_PTR( "pix not made", procName, NULL); data = pixGetData(pix); wpl = pixGetWpl(pix); /* Old "ascii" format */ if (type <= 3) { for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (type == 1 || type == 2) { if (pnmReadNextAsciiValue(fp, &val)) return (PIX *)ERROR_PTR( "read abend", procName, pix); pixSetPixel(pix, j, i, val); } else { /* type == 3 */ if (pnmReadNextAsciiValue(fp, &rval)) return (PIX *)ERROR_PTR( "read abend", procName, pix); if (pnmReadNextAsciiValue(fp, &gval)) return (PIX *)ERROR_PTR( "read abend", procName, pix); if (pnmReadNextAsciiValue(fp, &bval)) return (PIX *)ERROR_PTR( "read abend", procName, pix); composeRGBPixel(rval, gval, bval, &rgbval); pixSetPixel(pix, j, i, rgbval); } } } return pix; } /* "raw" format for 1 bpp */ if (type == 4) { bpl = (d * w + 7) / 8; for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < bpl; j++) { fread(&val8, 1, 1, fp); SET_DATA_BYTE(line, j, val8); } } return pix; } /* "raw" format for grayscale */ if (type == 5) { bpl = (d * w + 7) / 8; for (i = 0; i < h; i++) { line = data + i * wpl; if (d != 16) { for (j = 0; j < w; j++) { fread(&val8, 1, 1, fp); 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++) { fread(&val16, 2, 1, fp); SET_DATA_TWO_BYTES(line, j, val16); } } } return pix; } /* "raw" format, type == 6; rgb */ for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < wpl; j++) { fread(&rval8, 1, 1, fp); fread(&gval8, 1, 1, fp); fread(&bval8, 1, 1, fp); composeRGBPixel(rval8, gval8, bval8, &rgbval); line[j] = rgbval; } } return pix; } /*! * pixWriteStreamPnm() * * Input: stream opened for write * 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_int32 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 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 leptonlib (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 leptonlib (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 leptonlib (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] = *((l_uint8 *)pword + 3); /* red */ pel[1] = *((l_uint8 *)pword + 2); /* green */ pel[2] = *((l_uint8 *)pword + 1); /* blue */ if (fwrite(&pel, 1, 3, fp) != 3) writeerror = 1; } } } } pixDestroy(&pixs); if (writeerror) return ERROR_INT("image write fail", procName, 1); return 0; } /*! * pixWriteStreamAsciiPnm() * * Input: stream opened for write * 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_int32 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 leptonlib (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); } } } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) { /* grayscale */ maxval = (1 << ds) - 1; fprintf(fp, "P2\n# Ascii PGM file written by leptonlib (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) { sprintf(buffer, "%1d ", val); fwrite(buffer, 1, 2, fp); count += 2; } else if (ds == 4) { sprintf(buffer, "%2d ", val); fwrite(buffer, 1, 3, fp); count += 3; } else if (ds == 8) { sprintf(buffer, "%3d ", val); fwrite(buffer, 1, 4, fp); count += 4; } else { /* ds == 16 */ sprintf(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 leptonlib (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++) { sprintf(buffer, "%3d ", cval[k]); fwrite(buffer, 1, 4, fp); count += 4; if (count >= 60) { fputc('\n', fp); count = 0; } } } } } pixDestroy(&pixs); return 0; } /*---------------------------------------------------------------------* * Read/write to memory * *---------------------------------------------------------------------*/ #ifdef HAVE_CONFIG_H #include "config_auto.h" #endif /* HAVE_CONFIG_H */ #if HAVE_FMEMOPEN #include "_stdio.h" /* extern FILE *open_memstream(char **data, size_t *size); */ /* extern FILE *fmemopen(void *data, size_t size, const char *mode); */ /*! * pixReadMemPnm() * * Input: cdata (const; pnm-encoded) * 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 *cdata, size_t size) { l_uint8 *data; FILE *fp; PIX *pix; PROCNAME("pixReadMemPnm"); if (!cdata) return (PIX *)ERROR_PTR("cdata not defined", procName, NULL); data = (l_uint8 *)cdata; /* we're really not going to change this */ if ((fp = fmemopen(data, size, "r")) == NULL) return (PIX *)ERROR_PTR("stream not opened", procName, NULL); pix = pixReadStreamPnm(fp); fclose(fp); return pix; } /*! * pixWriteMemPnm() * * Input: &data (<return> data of tiff compressed image) * &size (<return> size of returned data) * 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_int32 pixWriteMemPnm(l_uint8 **pdata, size_t *psize, PIX *pix) { l_int32 ret; FILE *fp; PROCNAME("pixWriteMemPnm"); 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 ((fp = open_memstream((char **)pdata, psize)) == NULL) return ERROR_INT("stream not opened", procName, 1); ret = pixWriteStreamPnm(fp, pix); fclose(fp); return ret; } #else PIX * pixReadMemPnm(const l_uint8 *cdata, size_t size) { return (PIX *)ERROR_PTR( "pnm read from memory not implemented on this platform", "pixReadMemPnm", NULL); } l_int32 pixWriteMemPnm(l_uint8 **pdata, size_t *psize, PIX *pix) { return ERROR_INT( "pnm write to memory not implemented on this platform", "pixWriteMemPnm", 1); } #endif /* HAVE_FMEMOPEN */ /*--------------------------------------------------------------------* * Static helpers * *--------------------------------------------------------------------*/ /*! * pnmReadNextAsciiValue() * * Return: 0 if OK, 1 on error or EOF. * * Notes: * (1) This reads the next sample value in ascii from the the file. */ static l_int32 pnmReadNextAsciiValue(FILE *fp, l_int32 *pval) { l_int32 c; PROCNAME("pnmReadNextAsciiValue"); if (!fp) return ERROR_INT("stream not open", procName, 1); if (!pval) return ERROR_INT("&val not defined", procName, 1); *pval = 0; do { /* skip whitespace */ if ((c = fgetc(fp)) == EOF) return 1; } while (c == ' ' || c == '\t' || c == '\n' || c == '\r'); fseek(fp, -1L, SEEK_CUR); /* back up one byte */ fscanf(fp, "%d", pval); return 0; } /*! * 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 */ /* --------------------------------------------*/