/*====================================================================* - 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() [behavior depends on WRITE_AS_NAMED] * 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 pixDisplayMultiple() * l_int32 pixDisplayWrite() * l_int32 pixDisplayWriteFormat() * l_int32 pixSaveTiled() * l_int32 pixSaveTiledOutline() * l_int32 pixSaveTiledWithText() * void chooseDisplayProg() */ #include #include #include #include "allheaders.h" /* ----------------------------------------------------------- */ /* Special flag for pixWrite(). The default value is 1. */ /* For the effect of this parameter, see comments there. */ #define WRITE_AS_NAMED 1 /* ----------------------------------------------------------- */ #ifdef COMPILER_MSVC #define MAX_PATH 260 /* actually in ; brings in other files */ #endif /* COMPILER_MSVC */ /* MS VC++ can't handle array initialization with static consts ! */ #define L_BUF_SIZE 512 /* Display program (xv, xli or xzgv) to be invoked by pixDisplay() */ #if COMPILER_MSVC static l_int32 ChosenDisplayProg = L_DISPLAY_WITH_IV; /* default */ #else static l_int32 ChosenDisplayProg = L_DISPLAY_WITH_XV; /* default */ #endif /* COMPILER_MSVC */ 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. * (Note on 'const': The size of the array can't be defined 'const' * because that makes it static. The 'const' in the definition of * the array refers to the strings in the array; the ptr to the * array is not const and can be used 'extern' in other files.) */ LEPT_DLL l_int32 NumImageFileFormatExtensions = 16; /* array size */ LEPT_DLL const char *ImageFileFormatExtensions[] = {"unknown", "bmp", "jpg", "png", "tif", "tif", "tif", "tif", "tif", "tif", "tif", "pnm", "ps", "gif", "jp2" "default"}; /* 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 }, { ".jp2", IFF_JP2 }, { ".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); if (format < 0 || format >= NumImageFileFormatExtensions) return ERROR_INT("invalid format", 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. * (2) There are two modes with respect to file naming. * (a) The default code writes to @filename. * (b) If WRITE_AS_NAMED is defined to 0, it's a bit fancier. * Then, if @filename does not have a file extension, one is * automatically appended, depending on the requested format. * We provide option (b) because Windows programs need the * file type as an extension to the file name. * (3) If the default format is requested, we use the input format; * if the input format is unknown, a lossless format is assigned. */ 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 (format == IFF_JP2) return ERROR_INT("jp2 not supported", procName, 1); #if WRITE_AS_NAMED /* Default */ if ((fp = fopen(filename, "wb+")) == NULL) return ERROR_INT("stream not opened", procName, 1); #else /* Add an extension to the output name if none exists */ {l_int32 extlen; char *extension, *filebuf; splitPathAtExtension(filename, NULL, &extension); extlen = strlen(extension); FREE(extension); if (extlen == 0) { if (format == IFF_DEFAULT || format == IFF_UNKNOWN) format = pixChooseOutputFormat(pix); filebuf = (char *)CALLOC(strlen(filename) + 10, sizeof(char)); if (!filebuf) return ERROR_INT("filebuf not made", procName, 1); strncpy(filebuf, filename, strlen(filename)); strcat(filebuf, "."); strcat(filebuf, ImageFileFormatExtensions[format]); } else filebuf = (char *)filename; fp = fopen(filebuf, "wb+"); if (filebuf != filename) FREE(filebuf); if (fp == NULL) return ERROR_INT("stream not opened", procName, 1); } #endif /* WRITE_AS_NAMED */ if (pixWriteStream(fp, pix, format)) { fclose(fp); return ERROR_INT("pix not written to stream", procName, 1); } if (format != IFF_GIF) /* EGifCloseFile() closes file stream! */ 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; case IFF_JP2: return ERROR_INT("jp2 format not supported", procName, 1); 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_ZIP; } 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 >= NumImageFileFormatExtensions) return (const char *)ERROR_PTR("invalid format", 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; case IFF_JP2: return ERROR_INT("jp2 not supported", procName, 1); 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 display frame on the screen) * Return: 0 if OK; 1 on error * * Notes: * (1) This displays the image using xv, xli or xzgv on Unix, * or i_view on Windows. The display program must be on * your $PATH variable. It is chosen by setting the global * ChosenDisplayProg, using chooseDisplayProg(). * Default on Unix is xv. * (2) Images with dimensions larger than MAX_DISPLAY_WIDTH or * MAX_DISPLAY_HEIGHT are downscaled to fit those constraints. * This is particulary important for displaying 1 bpp images * with xv, because xv automatically downscales large images * by subsampling, which looks lousy. For 1 bpp, we use * scale-to-gray to get decent-looking anti-aliased images. * In all cases, we write a temporary file to /tmp, that is * read by the display program. * (3) Note: 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 display frame) * title ( on frame; can be NULL); * dispflag (1 to write, else disabled) * 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; #ifndef COMPILER_MSVC l_int32 wt, ht; #else char pathname[MAX_PATH]; #endif /* COMPILER_MSVC */ PROCNAME("pixDisplayWithTitle"); if (dispflag != 1) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (ChosenDisplayProg != L_DISPLAY_WITH_XV && ChosenDisplayProg != L_DISPLAY_WITH_XLI && ChosenDisplayProg != L_DISPLAY_WITH_XZGV && ChosenDisplayProg != L_DISPLAY_WITH_IV) return ERROR_INT("no program chosen for display", 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 /tmp/junk_display.*"); system(buffer); } index++; if (pixGetDepth(pixt) < 8 || (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) { snprintf(buffer, L_BUF_SIZE, "/tmp/junk_display.%03d.png", index); pixWrite(buffer, pixt, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/junk_display.%03d.jpg", index); pixWrite(buffer, pixt, IFF_JFIF_JPEG); } tempname = stringNew(buffer); #ifndef COMPILER_MSVC /* Unix */ if (ChosenDisplayProg == L_DISPLAY_WITH_XV) { 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); } else if (ChosenDisplayProg == L_DISPLAY_WITH_XLI) { if (title) snprintf(buffer, L_BUF_SIZE, "xli -quiet -geometry +%d+%d -title \"%s\" %s &", x, y, title, tempname); else snprintf(buffer, L_BUF_SIZE, "xli -quiet -geometry +%d+%d %s &", x, y, tempname); } else if (ChosenDisplayProg == L_DISPLAY_WITH_XZGV) { /* no way to display title */ pixGetDimensions(pixt, &wt, &ht, NULL); snprintf(buffer, L_BUF_SIZE, "xzgv --geometry %dx%d+%d+%d %s &", wt + 10, ht + 10, x, y, tempname); } system(buffer); #else /* COMPILER_MSVC */ /* Windows: L_DISPLAY_WITH_IV */ _fullpath(pathname, tempname, sizeof(pathname)); if (title) snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d) /title=\"%s\"", pathname, x, y, title); else snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d)", pathname, x, y); system(buffer); #endif /* COMPILER_MSVC */ pixDestroy(&pixt); FREE(tempname); return 0; } /*! * pixDisplayMultiple() * * Input: filepattern * Return: 0 if OK; 1 on error * * Notes: * (1) This allows display of multiple images using gthumb on unix * and i_view32 on windows. The @filepattern is a regular * expression that is expanded by the shell. * (2) _fullpath automatically changes '/' to '\' if necessary. */ l_int32 pixDisplayMultiple(const char *filepattern) { char buffer[L_BUF_SIZE]; #ifdef COMPILER_MSVC char pathname[MAX_PATH]; char *dir, *tail; #endif /* COMPILER_MSVC */ PROCNAME("pixDisplayMultiple"); if (!filepattern || strlen(filepattern) == 0) return ERROR_INT("filepattern not defined", procName, 1); #ifndef COMPILER_MSVC snprintf(buffer, L_BUF_SIZE, "gthumb %s &", filepattern); #else /* irFanView wants absolute path for directory */ _fullpath(pathname, filepattern, sizeof(pathname)); splitPathAtDirectory(pathname, &dir, &tail); snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /filepattern=\"%s\" /thumbs", dir, tail); FREE(dir); FREE(tail); #endif system(buffer); 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 using * pixDisplayMultiple("/tmp/junk_write_display*"); * (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 /tmp/junk_write_display.*.png /tmp/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, "/tmp/junk_write_display.%03d.png", index); pixWrite(buffer, pix8, IFF_PNG); pixDestroy(&pix8); } else if (pixGetDepth(pixt) < 8 || pixGetColormap(pixt) || format == IFF_PNG) { snprintf(buffer, L_BUF_SIZE, "/tmp/junk_write_display.%03d.png", index); pixWrite(buffer, pixt, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/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. */ l_int32 pixSaveTiled(PIX *pixs, PIXA *pixa, l_int32 reduction, l_int32 newrow, l_int32 space, l_int32 dp) { /* Save without an outline */ return pixSaveTiledOutline(pixs, pixa, reduction, newrow, space, 0, dp); } /*! * pixSaveTiledOutline() * * 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) * linewidth (width of added outline for image; 0 for no outline) * 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 pixSaveTiledOutline(PIX *pixs, PIXA *pixa, l_int32 reduction, l_int32 newrow, l_int32 space, l_int32 linewidth, l_int32 dp) { l_int32 n, top, left, bx, by, bw, w, h, depth, bottom; l_float32 scale; BOX *box; PIX *pix, *pixt1, *pixt2, *pixt3; PROCNAME("pixSaveTiledOutline"); 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); } /* Scale and convert to output depth */ 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); /* Add black outline */ if (linewidth > 0) pixt3 = pixAddBorder(pixt2, linewidth, 0); else pixt3 = pixClone(pixt2); pixDestroy(&pixt2); /* 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(pixt3, &w, &h, NULL); bottom = L_MAX(bottom, top + h); box = boxCreate(left, top, w, h); pixaAddPix(pixa, pixt3, 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; } /*! * pixSaveTiledWithText() * * Input: pixs (1, 2, 4, 8, 32 bpp) * pixa (the pix are accumulated here; as 32 bpp) * outwidth (in pixels; use 0 to disable entirely) * newrow (1 to start a new row; 0 to go on same row as previous) * space (horizontal and vertical spacing, in pixels) * linewidth (width of added outline for image; 0 for no outline) * bmf ( font struct) * textstr ( text string to be added) * val (color to set the text) * location (L_ADD_ABOVE, L_ADD_AT_TOP, L_ADD_AT_BOTTOM, * L_ADD_BELOW) * 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) @outwidth is the scaled width. After scaling, 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 @outwidth == 0. * (3) @newrow and @space specify the location of the new pix * with respect to the last one(s) that were entered. * (4) All pix are saved as 32 bpp RGB. * (5) If both @bmf and @textstr are defined, this generates a pix * with the additional text; otherwise, no text is written. * (6) The text is written before scaling, so it is properly * antialiased in the scaled pix. However, if the pix on * different calls have different widths, the size of the * text will vary. * (7) See pixSaveTiledOutline() for other implementation details. */ l_int32 pixSaveTiledWithText(PIX *pixs, PIXA *pixa, l_int32 outwidth, l_int32 newrow, l_int32 space, l_int32 linewidth, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location) { PIX *pixt1, *pixt2, *pixt3, *pixt4; PROCNAME("pixSaveTiledWithText"); if (outwidth == 0) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); pixt1 = pixConvertTo32(pixs); if (linewidth > 0) pixt2 = pixAddBorder(pixt1, linewidth, 0); else pixt2 = pixClone(pixt1); if (bmf && textstr) pixt3 = pixAddSingleTextblock(pixt2, bmf, textstr, val, location, NULL); else pixt3 = pixClone(pixt2); pixt4 = pixScaleToSize(pixt3, outwidth, 0); pixSaveTiled(pixt4, pixa, 1, newrow, space, 32); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); return 0; } void chooseDisplayProg(l_int32 selection) { if (selection == L_DISPLAY_WITH_XLI || selection == L_DISPLAY_WITH_XZGV || selection == L_DISPLAY_WITH_XV) ChosenDisplayProg = selection; else L_ERROR("invalid unix display program", "chooseDisplayProg"); return; }