blob: a009e2500d81e814790b0733a0b2432edf461d53 [file] [log] [blame]
/*====================================================================*
- 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 (<return> data of tiff compressed image)
* &size (<return> 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 (<optional> 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;
}