/*====================================================================* - 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. *====================================================================*/ /* * writefile.c * * High-level procedures for writing images to file: * l_int32 pixaWriteFiles() * l_int32 pixWrite() * l_int32 pixWriteStream() * l_int32 pixWriteImpliedFormat() * * Selection of output format if default is requested * l_int32 pixChooseOutputFormat() * l_int32 getImpliedFileFormat() * const char *getFormatExtension() * * Write to memory * l_int32 pixWriteMem() * * Image display for debugging * l_int32 pixDisplay() * l_int32 pixDisplayWithTitle() * l_int32 pixDisplayWrite() * l_int32 pixDisplayWriteFormat() * l_int32 pixSaveTiled() */ #include #include #include #include "allheaders.h" /* MS VC++ can't handle array initialization with static consts ! */ #define L_BUF_SIZE 512 /* For display using xv */ static const l_int32 MAX_DISPLAY_WIDTH = 1000; static const l_int32 MAX_DISPLAY_HEIGHT = 800; static const l_int32 MAX_SIZE_FOR_PNG = 200; /* PostScript output for printing */ static const l_float32 DEFAULT_SCALING = 1.0; /* Global array of image file format extension names. * This is in 1-1 corrspondence with format enum in imageio.h. */ static const l_int32 NUM_EXTENSIONS = 14; const char *ImageFileFormatExtensions[] = {"unknown", "bmp", "jpg", "png", "tif", "tif", "tif", "tif", "tif", "tif", "tif", "pnm", "ps", "gif"}; /* Local map of image file name extension to output format */ struct ExtensionMap { char extension[8]; l_int32 format; }; static const struct ExtensionMap extension_map[] = { { ".bmp", IFF_BMP }, { ".jpg", IFF_JFIF_JPEG }, { ".jpeg", IFF_JFIF_JPEG }, { ".png", IFF_PNG }, { ".tif", IFF_TIFF }, { ".tiff", IFF_TIFF }, { ".pnm", IFF_PNM }, { ".gif", IFF_GIF }, { ".ps", IFF_PS } }; /*---------------------------------------------------------------------* * Top-level procedures for writing images to file * *---------------------------------------------------------------------*/ /*! * pixaWriteFiles() * * Input: rootname * pixa * format (defined in imageio.h) * Return: 0 if OK; 1 on error */ l_int32 pixaWriteFiles(const char *rootname, PIXA *pixa, l_int32 format) { char bigbuf[L_BUF_SIZE]; l_int32 i, n; PIX *pix; PROCNAME("pixaWriteFiles"); if (!rootname) return ERROR_INT("rootname not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); n = pixaGetCount(pixa); for (i = 0; i < n; i++) { snprintf(bigbuf, L_BUF_SIZE, "%s%03d.%s", rootname, i, ImageFileFormatExtensions[format]); pix = pixaGetPix(pixa, i, L_CLONE); pixWrite(bigbuf, pix, format); pixDestroy(&pix); } return 0; } /*! * pixWrite() * * Input: filename * pix * format (defined in imageio.h) * Return: 0 if OK; 1 on error * * Notes: * (1) Open for write using binary mode (with the "b" flag) * to avoid having Windows automatically translate the NL * into CRLF, which corrupts image files. On non-windows * systems this flag should be ignored, per ISO C90. * Thanks to Dave Bryan for pointing this out. */ l_int32 pixWrite(const char *filename, PIX *pix, l_int32 format) { FILE *fp; PROCNAME("pixWrite"); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (!filename) return ERROR_INT("filename not defined", procName, 1); if ((fp = fopen(filename, "wb+")) == NULL) return ERROR_INT("stream not opened", procName, 1); if (pixWriteStream(fp, pix, format)) { fclose(fp); return ERROR_INT("pix not written to stream", procName, 1); } fclose(fp); return 0; } /*! * pixWriteStream() * * Input: stream * pix * format * Return: 0 if OK; 1 on error. */ l_int32 pixWriteStream(FILE *fp, PIX *pix, l_int32 format) { PROCNAME("pixWriteStream"); if (!fp) return ERROR_INT("stream not defined", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (format == IFF_DEFAULT) format = pixChooseOutputFormat(pix); switch(format) { case IFF_BMP: pixWriteStreamBmp(fp, pix); break; case IFF_JFIF_JPEG: /* default quality; baseline sequential */ return pixWriteStreamJpeg(fp, pix, 75, 0); break; case IFF_PNG: /* no gamma value stored */ return pixWriteStreamPng(fp, pix, 0.0); break; case IFF_TIFF: /* uncompressed */ case IFF_TIFF_PACKBITS: /* compressed, binary only */ case IFF_TIFF_RLE: /* compressed, binary only */ case IFF_TIFF_G3: /* compressed, binary only */ case IFF_TIFF_G4: /* compressed, binary only */ case IFF_TIFF_LZW: /* compressed, all depths */ case IFF_TIFF_ZIP: /* compressed, all depths */ return pixWriteStreamTiff(fp, pix, format); break; case IFF_PNM: return pixWriteStreamPnm(fp, pix); break; case IFF_GIF: return pixWriteStreamGif(fp, pix); break; case IFF_PS: return pixWriteStreamPS(fp, pix, NULL, 0, DEFAULT_SCALING); break; default: return ERROR_INT("unknown format", procName, 1); break; } return 0; } /*! * pixWriteImpliedFormat() * * Input: filename * pix * quality (iff JPEG; 1 - 100, 0 for default) * progressive (iff JPEG; 0 for baseline seq., 1 for progressive) * Return: 0 if OK; 1 on error * * Notes: * (1) This determines the output format from the filename extension. * (2) The last two args are ignored except for requests for jpeg files. * (3) The jpeg default quality is 75. */ l_int32 pixWriteImpliedFormat(const char *filename, PIX *pix, l_int32 quality, l_int32 progressive) { l_int32 format; PROCNAME("pixWriteImpliedFormat"); if (!filename) return ERROR_INT ("filename not defined", procName, 1); if (!pix) return ERROR_INT ("pix not defined", procName, 1); /* Determine output format */ format = getImpliedFileFormat(filename); if (format == IFF_UNKNOWN) format = IFF_PNG; else if (format == IFF_TIFF) { if (pixGetDepth(pix) == 1) format = IFF_TIFF_G4; else format = IFF_TIFF_LZW; } if (format == IFF_JFIF_JPEG) { quality = L_MIN(quality, 100); quality = L_MAX(quality, 0); if (progressive != 0 && progressive != 1) { progressive = 0; L_WARNING("invalid progressive; setting to baseline", procName); } if (quality == 0) quality = 75; pixWriteJpeg (filename, pix, quality, progressive); } else pixWrite(filename, pix, format); return 0; } /*---------------------------------------------------------------------* * Selection of output format if default is requested * *---------------------------------------------------------------------*/ /*! * pixChooseOutputFormat() * * Input: pix * Return: output format, or 0 on error * * Notes: * (1) This should only be called if the requested format is IFF_DEFAULT. * (2) If the pix wasn't read from a file, its input format value * will be IFF_UNKNOWN, and in that case it is written out * in a compressed but lossless format. */ l_int32 pixChooseOutputFormat(PIX *pix) { l_int32 d, format; PROCNAME("pixChooseOutputFormat"); if (!pix) return ERROR_INT("pix not defined", procName, 0); d = pixGetDepth(pix); format = pixGetInputFormat(pix); if (format == IFF_UNKNOWN) { /* output lossless */ if (d == 1) format = IFF_TIFF_G4; else format = IFF_PNG; } return format; } /*! * getImpliedFileFormat() * * Input: filename * Return: output format, or IFF_UNKNOWN on error or invalid extension. * * Notes: * (1) This determines the output file format from the extension * of the input filename. */ l_int32 getImpliedFileFormat(const char *filename) { char *extension; int i, numext; l_int32 format = IFF_UNKNOWN; if (splitPathAtExtension (filename, NULL, &extension)) return IFF_UNKNOWN; numext = sizeof(extension_map) / sizeof(extension_map[0]); for (i = 0; i < numext; i++) { if (!strcmp(extension, extension_map[i].extension)) { format = extension_map[i].format; break; } } FREE(extension); return format; } /*! * getFormatExtension() * * Input: format (integer) * Return: extension (string), or null if format is out of range * * Notes: * (1) This string is NOT owned by the caller; it is just a pointer * to a global string. Do not free it. */ const char * getFormatExtension(l_int32 format) { PROCNAME("getFormatExtension"); if (format < 0 || format >= NUM_EXTENSIONS) return (const char *)ERROR_PTR("format out of bounds", procName, NULL); return ImageFileFormatExtensions[format]; } /*---------------------------------------------------------------------* * Write to memory * *---------------------------------------------------------------------*/ /*! * pixWriteMem() * * Input: &data ( data of tiff compressed image) * &size ( size of returned data) * pix * format (defined in imageio.h) * Return: 0 if OK, 1 on error * * Notes: * (1) On windows, this will only write tiff and PostScript to memory. * For other formats, it requires open_memstream(3). * (2) PostScript output is uncompressed, in hex ascii. * Most printers support level 2 compression (tiff_g4 for 1 bpp, * jpeg for 8 and 32 bpp). */ l_int32 pixWriteMem(l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 format) { l_int32 ret; PROCNAME("pixWriteMem"); 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 (format == IFF_DEFAULT) format = pixChooseOutputFormat(pix); switch(format) { case IFF_BMP: ret = pixWriteMemBmp(pdata, psize, pix); break; case IFF_JFIF_JPEG: /* default quality; baseline sequential */ ret = pixWriteMemJpeg(pdata, psize, pix, 75, 0); break; case IFF_PNG: /* no gamma value stored */ ret = pixWriteMemPng(pdata, psize, pix, 0.0); break; case IFF_TIFF: /* uncompressed */ case IFF_TIFF_PACKBITS: /* compressed, binary only */ case IFF_TIFF_RLE: /* compressed, binary only */ case IFF_TIFF_G3: /* compressed, binary only */ case IFF_TIFF_G4: /* compressed, binary only */ case IFF_TIFF_LZW: /* compressed, all depths */ case IFF_TIFF_ZIP: /* compressed, all depths */ ret = pixWriteMemTiff(pdata, psize, pix, format); break; case IFF_PNM: ret = pixWriteMemPnm(pdata, psize, pix); break; case IFF_PS: ret = pixWriteMemPS(pdata, psize, pix, NULL, 0, DEFAULT_SCALING); break; default: return ERROR_INT("unknown format", procName, 1); break; } return ret; } /*---------------------------------------------------------------------* * Image display for debugging * *---------------------------------------------------------------------*/ /*! * pixDisplay() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * x, y (location of xv frame) * Return: 0 if OK; 1 on error * * Notes: * (1) This uses xv to display. It must be on your $PATH variable. * (2) Because xv reduces images to fit the screen, we do this * reduction in advance, and write it out to a temporary file * in the current directory with the name "junk_xv_display.*" * (3) This function uses a static internal variable to number * output files written by a single process. Behavior * with a shared library may be unpredictable. */ l_int32 pixDisplay(PIX *pixs, l_int32 x, l_int32 y) { return pixDisplayWithTitle(pixs, x, y, NULL, 1); } /*! * pixDisplayWithTitle() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * x, y (location of xv frame) * title ( on xv window; can be NULL); * dispflag (0 to disable; 1 to write) * Return: 0 if OK; 1 on error * * Notes: * (1) See notes for pixDisplay(). * (2) This displays the image if dispflag == 1. */ l_int32 pixDisplayWithTitle(PIX *pixs, l_int32 x, l_int32 y, const char *title, l_int32 dispflag) { char *tempname; char buffer[L_BUF_SIZE]; static l_int32 index = 0; /* caution: not .so or thread safe */ l_int32 w, h, d; l_float32 ratw, rath, ratmin; PIX *pixt; PROCNAME("pixDisplayWithTitle"); if (dispflag == 0) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (w <= MAX_DISPLAY_WIDTH && h <= MAX_DISPLAY_HEIGHT) { if (d == 16) /* take MSB */ pixt = pixConvert16To8(pixs, 1); else pixt = pixClone(pixs); } else { ratw = (l_float32)MAX_DISPLAY_WIDTH / (l_float32)w; rath = (l_float32)MAX_DISPLAY_HEIGHT / (l_float32)h; ratmin = L_MIN(ratw, rath); if (ratmin < 0.125 && d == 1) pixt = pixScaleToGray8(pixs); else if (ratmin < 0.25 && d == 1) pixt = pixScaleToGray4(pixs); else if (ratmin < 0.33 && d == 1) pixt = pixScaleToGray3(pixs); else if (ratmin < 0.5 && d == 1) pixt = pixScaleToGray2(pixs); else pixt = pixScale(pixs, ratmin, ratmin); if (!pixt) return ERROR_INT("pixt not made", procName, 1); } if (index == 0) { snprintf(buffer, L_BUF_SIZE, "rm -f junk_xv_display.*"); system(buffer); } index++; if (pixGetDepth(pixt) < 8 || (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) { snprintf(buffer, L_BUF_SIZE, "junk_xv_display.%03d.png", index); pixWrite(buffer, pixt, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "junk_xv_display.%03d.jpg", index); pixWrite(buffer, pixt, IFF_JFIF_JPEG); } tempname = stringNew(buffer); if (title) snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d -name \"%s\" %s &", x, y, title, tempname); else snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d %s &", x, y, tempname); system(buffer); pixDestroy(&pixt); FREE(tempname); return 0; } /*! * pixDisplayWrite() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * reduction (-1 to reset/erase; 0 to disable; * otherwise this is a reduction factor) * Return: 0 if OK; 1 on error * * Notes: * (1) This defaults to jpeg output for pix that are 32 bpp or * 8 bpp without a colormap. If you want to write all images * losslessly, use format == IFF_PNG in pixDisplayWriteFormat(). * (2) See pixDisplayWriteFormat() for usage details. */ l_int32 pixDisplayWrite(PIX *pixs, l_int32 reduction) { return pixDisplayWriteFormat(pixs, reduction, IFF_JFIF_JPEG); } /*! * pixDisplayWriteFormat() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * reduction (-1 to reset/erase; 0 to disable; * otherwise this is a reduction factor) * format (IFF_PNG or IFF_JFIF_JPEG) * Return: 0 if OK; 1 on error * * Notes: * (1) This writes files if reduction > 0. These can be * displayed, ordered in a tiled representation, with, * for example, gthumb. * (2) All previously written files can be erased by calling with * reduction < 0; the value of pixs is ignored. * (3) If reduction > 1 and depth == 1, this does a scale-to-gray * reduction. * (4) This function uses a static internal variable to number * output files written by a single process. Behavior * with a shared library may be unpredictable. * (5) Output file format is as follows: * format == IFF_JFIF_JPEG: * png if d < 8 or d == 16 or if the output pix * has a colormap. Otherwise, output is jpg. * format == IFF_PNG: * png (lossless) on all images. * (6) For 16 bpp, the choice of full dynamic range with log scale * is the best for displaying these images. Alternative outputs are * pix8 = pixMaxDynamicRange(pixt, L_LINEAR_SCALE); * pix8 = pixConvert16To8(pixt, 0); // low order byte * pix8 = pixConvert16To8(pixt, 1); // high order byte */ l_int32 pixDisplayWriteFormat(PIX *pixs, l_int32 reduction, l_int32 format) { char buffer[L_BUF_SIZE]; l_float32 scale; PIX *pixt, *pix8; static l_int32 index = 0; /* caution: not .so or thread safe */ PROCNAME("pixDisplayWriteFormat"); if (reduction == 0) return 0; if (reduction < 0) { index = 0; /* reset; this will cause erasure at next call to write */ return 0; } if (format != IFF_JFIF_JPEG && format != IFF_PNG) return ERROR_INT("invalid format", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (index == 0) { snprintf(buffer, L_BUF_SIZE, "rm -f junk_write_display.*.png junk_write_display.*.jpg"); system(buffer); } index++; if (reduction == 1) pixt = pixClone(pixs); else { scale = 1. / (l_float32)reduction; if (pixGetDepth(pixs) == 1) pixt = pixScaleToGray(pixs, scale); else pixt = pixScale(pixs, scale, scale); } if (pixGetDepth(pixt) == 16) { pix8 = pixMaxDynamicRange(pixt, L_LOG_SCALE); snprintf(buffer, L_BUF_SIZE, "junk_write_display.%03d.png", index); pixWrite(buffer, pix8, IFF_PNG); pixDestroy(&pix8); } else if (pixGetDepth(pixt) < 8 || pixGetColormap(pixt)) { snprintf(buffer, L_BUF_SIZE, "junk_write_display.%03d.png", index); pixWrite(buffer, pixt, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "junk_write_display.%03d.jpg", index); pixWrite(buffer, pixt, format); } pixDestroy(&pixt); return 0; } /*! * pixSaveTiled() * * Input: pixs (1, 2, 4, 8, 32 bpp) * pixa (the pix are accumulated here) * reduction (0 to disable; otherwise this is a reduction factor) * newrow (0 if placed on the same row as previous; 1 otherwise) * space (horizontal and vertical spacing, in pixels) * dp (depth of pixa; 8 or 32 bpp; only used on first call) * Return: 0 if OK, 1 on error. * * Notes: * (1) Before calling this function for the first time, use * pixaCreate() to make the @pixa that will accumulate the pix. * This is passed in each time pixSaveTiled() is called. * (2) @reduction is the integer reduction factor for the input * image. After reduction and possible depth conversion, * the image is saved in the input pixa, along with a box * that specifies the location to place it when tiled later. * Disable saving the pix by setting reduction == 0. * (3) @newrow and @space specify the location of the new pix * with respect to the last one(s) that were entered. * (4) @dp specifies the depth at which all pix are saved. It can * be only 8 or 32 bpp. Any colormap is removed. This is only * used at the first invocation. * (5) This function uses two variables from call to call. * If they were static, the function would not be .so or thread * safe, and furthermore, there would be interference with two or * more pixa accumulating images at a time. Consequently, * we use the first pix in the pixa to store and obtain both * the depth and the current position of the bottom (one pixel * below the lowest image raster line when laid out using * the boxa). The bottom variable is stored in the input format * field, which is the only field available for storing an int. */ l_int32 pixSaveTiled(PIX *pixs, PIXA *pixa, l_int32 reduction, l_int32 newrow, l_int32 space, l_int32 dp) { l_int32 n, top, left, bx, by, bw, w, h, depth, bottom; l_float32 scale; BOX *box; PIX *pix, *pixt1, *pixt2; PROCNAME("pixSaveTiled"); if (reduction == 0) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); n = pixaGetCount(pixa); if (n == 0) { bottom = 0; if (dp != 8 && dp != 32) { L_WARNING("dp not 8 or 32 bpp; using 32", procName); depth = 32; } else depth = dp; } else { /* extract the depth and bottom params from the first pix */ pix = pixaGetPix(pixa, 0, L_CLONE); depth = pixGetDepth(pix); bottom = pixGetInputFormat(pix); /* not typical usage! */ pixDestroy(&pix); } if (reduction == 1) pixt1 = pixClone(pixs); else { scale = 1. / (l_float32)reduction; if (pixGetDepth(pixs) == 1) pixt1 = pixScaleToGray(pixs, scale); else pixt1 = pixScale(pixs, scale, scale); } if (depth == 8) pixt2 = pixConvertTo8(pixt1, 0); else pixt2 = pixConvertTo32(pixt1); pixDestroy(&pixt1); /* Find position of current pix (UL corner plus size) */ if (n == 0) { top = 0; left = 0; } else if (newrow == 1) { top = bottom + space; left = 0; } else if (n > 0) { pixaGetBoxGeometry(pixa, n - 1, &bx, &by, &bw, NULL); top = by; left = bx + bw + space; } pixGetDimensions(pixt2, &w, &h, NULL); bottom = L_MAX(bottom, top + h); box = boxCreate(left, top, w, h); pixaAddPix(pixa, pixt2, L_INSERT); pixaAddBox(pixa, box, L_INSERT); /* Save the new bottom value */ pix = pixaGetPix(pixa, 0, L_CLONE); pixSetInputFormat(pix, bottom); /* not typical usage! */ pixDestroy(&pix); return 0; }