/*====================================================================*
 -  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.
 *====================================================================*/

/*
 * writefile.c
 *
 *     High-level procedures for writing images to file:
 *        l_int32     pixaWriteFiles()
 *        l_int32     pixWrite()    [behavior depends on WRITE_AS_NAMED]
 *        l_int32     pixWriteAutoFormat()
 *        l_int32     pixWriteStream()
 *        l_int32     pixWriteImpliedFormat()
 *        l_int32     pixWriteTempfile()
 *
 *     Selection of output format if default is requested
 *        l_int32     pixChooseOutputFormat()
 *        l_int32     getImpliedFileFormat()
 *        l_int32     pixGetAutoFormat()
 *        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        l_chooseDisplayProg()
 *
 *  Supported file formats:
 *  (1) Writing is supported without any external libraries:
 *          bmp
 *          pnm   (including pbm, pgm, etc)
 *          spix  (raw serialized)
 *  (2) Writing is supported with installation of external libraries:
 *          png
 *          jpg   (standard jfif version)
 *          tiff  (including most varieties of compression)
 *          gif
 *          webp
 *          jp2 (jpeg2000)
 *  (3) Writing is supported through special interfaces:
 *          ps (PostScript, in psio1.c, psio2.c):
 *              level 1 (uncompressed)
 *              level 2 (g4 and dct encoding: requires tiff, jpg)
 *              level 3 (g4, dct and flate encoding: requires tiff, jpg, zlib)
 *          pdf (PDF, in pdfio.c):
 *              level 1 (g4 and dct encoding: requires tiff, jpg)
 *              level 2 (g4, dct and flate encoding: requires tiff, jpg, zlib)
 */

#include <string.h>
#include "allheaders.h"

    /*   Special flag for pixWrite().  The default for both unix and     */
    /*   windows is to use whatever filename is given, as opposed to     */
    /*   insuring the filename extension matches the image compression.  */
#define  WRITE_AS_NAMED    1

    /* Display program (xv, xli, xzgv, open) to be invoked by pixDisplay()  */
#ifdef _WIN32
static l_int32  var_DISPLAY_PROG = L_DISPLAY_WITH_IV;  /* default */
#elif  defined(__APPLE__)
static l_int32  var_DISPLAY_PROG = L_DISPLAY_WITH_OPEN;  /* default */
#else
static l_int32  var_DISPLAY_PROG = L_DISPLAY_WITH_XZGV;  /* default */
#endif  /* _WIN32 */

static const l_int32  L_BUF_SIZE = 512;
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.       */
    /* The empty string at the end represents the serialized format,     */
    /* which has no recognizable extension name, but the array must      */
    /* be padded to agree with the format enum.                          */
    /* (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 = 19;  /* array size */
LEPT_DLL const char *ImageFileFormatExtensions[] =
         {"unknown",
          "bmp",
          "jpg",
          "png",
          "tif",
          "tif",
          "tif",
          "tif",
          "tif",
          "tif",
          "tif",
          "pnm",
          "ps",
          "gif",
          "jp2",
          "webp",
          "pdf",
          "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        },
                              { ".pdf",  IFF_LPDF      },
                              { ".webp", IFF_WEBP      } };


/*---------------------------------------------------------------------*
 *           Top-level procedures for writing images to file           *
 *---------------------------------------------------------------------*/
/*!
 *  pixaWriteFiles()
 *
 *      Input:  rootname
 *              pixa
 *              format  (defined in imageio.h; see notes for default)
 *      Return: 0 if OK; 1 on error
 *
 *  Notes:
 *      (1) Use @format = IFF_DEFAULT to decide the output format
 *          individually for each pix.
 */
l_int32
pixaWriteFiles(const char  *rootname,
               PIXA        *pixa,
               l_int32      format)
{
char     bigbuf[L_BUF_SIZE];
l_int32  i, n, pixformat;
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 == IFF_UNKNOWN ||
        format >= NumImageFileFormatExtensions)
        return ERROR_INT("invalid format", procName, 1);

    n = pixaGetCount(pixa);
    for (i = 0; i < n; i++) {
        pix = pixaGetPix(pixa, i, L_CLONE);
        if (format == IFF_DEFAULT)
            pixformat = pixChooseOutputFormat(pix);
        else
            pixformat = format;
        snprintf(bigbuf, L_BUF_SIZE, "%s%03d.%s", rootname, i,
                 ImageFileFormatExtensions[pixformat]);
        pixWrite(bigbuf, pix, pixformat);
        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) If the default image format IFF_DEFAULT is requested:
 *          use the input format if known; otherwise, use a lossless format.
 *      (3) 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.
 *          The original intent for providing option (b) was to insure
 *          that filenames on Windows have an extension that matches
 *          the image compression.  However, this is not the default.
 */
l_int32
pixWrite(const char  *filename,
         PIX         *pix,
         l_int32      format)
{
char  *fname;
FILE  *fp;

    PROCNAME("pixWrite");

    if (!pix)
        return ERROR_INT("pix not defined", procName, 1);
    if (!filename)
        return ERROR_INT("filename not defined", procName, 1);

    fname = genPathname(filename, NULL);

#if  WRITE_AS_NAMED  /* Default */

    if ((fp = fopenWriteStream(fname, "wb+")) == NULL) {
        FREE(fname);
        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(fname, NULL, &extension);
        extlen = strlen(extension);
        FREE(extension);
        if (extlen == 0) {
            if (format == IFF_DEFAULT || format == IFF_UNKNOWN)
                format = pixChooseOutputFormat(pix);

            filebuf = (char *)CALLOC(strlen(fname) + 10, sizeof(char));
            if (!filebuf) {
                return ERROR_INT("filebuf not made", procName, 1);
                FREE(fname);
            }
            strncpy(filebuf, fname, strlen(fname));
            strcat(filebuf, ".");
            strcat(filebuf, ImageFileFormatExtensions[format]);
        } else {
            filebuf = (char *)fname;
        }

        fp = fopenWriteStream(filebuf, "wb+");
        if (filebuf != fname)
            FREE(filebuf);
        if (fp == NULL) {
            FREE(fname);
            return ERROR_INT("stream not opened", procName, 1);
        }
    }

#endif  /* WRITE_AS_NAMED */

    FREE(fname);
    if (pixWriteStream(fp, pix, format)) {
        fclose(fp);
        return ERROR_INT("pix not written to stream", procName, 1);
    }
    fclose(fp);

    return 0;
}


/*!
 *  pixWriteAutoFormat()
 *
 *      Input:  filename
 *              pix
 *      Return: 0 if OK; 1 on error
 */
l_int32
pixWriteAutoFormat(const char  *filename,
                   PIX         *pix)
{
l_int32  format;

    PROCNAME("pixWriteAutoFormat");

    if (!pix)
        return ERROR_INT("pix not defined", procName, 1);
    if (!filename)
        return ERROR_INT("filename not defined", procName, 1);

    if (pixGetAutoFormat(pix, &format))
        return ERROR_INT("auto format not returned", procName, 1);
    return pixWrite(filename, pix, format);
}


/*!
 *  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_PS:
        return pixWriteStreamPS(fp, pix, NULL, 0, DEFAULT_SCALING);
        break;

    case IFF_GIF:
        return pixWriteStreamGif(fp, pix);
        break;

    case IFF_JP2:
        return pixWriteStreamJp2k(fp, pix, 34, 0, 0, 0);
        break;

    case IFF_WEBP:
        return pixWriteStreamWebP(fp, pix, 80, 0);
        break;

    case IFF_LPDF:
        return pixWriteStreamPdf(fp, pix, 0, NULL);
        break;

    case IFF_SPIX:
        return pixWriteStreamSpix(fp, pix);
        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
#ifdef _WIN32
            format = IFF_TIFF_LZW;  /* poor compression */
#else
            format = IFF_TIFF_ZIP;  /* native windows tools can't handle this */
#endif  /* _WIN32 */
    }

    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\n", procName);
        }
        if (quality == 0)
            quality = 75;
        pixWriteJpeg (filename, pix, quality, progressive);
    } else {
        pixWrite(filename, pix, format);
    }

    return 0;
}


/*!
 *  pixWriteTempfile()
 *
 *      Input:  dir (directory name; use '.' for local dir; no trailing '/')
 *              tail (<optional> tailname, including extension if any)
 *              pix
 *              format
 *              &filename (<optional> return actual filename used; use
 *                         null to skip)
 *      Return: 0 if OK; 1 on error
 *
 *  Notes:
 *      (1) This generates a temp filename, writes the pix to it,
 *          and optionally returns the temp filename.
 *      (2) If the filename is returned to a windows program from a DLL,
 *          use lept_free() to free it.
 *      (3) See genTempFilename() for details.  We omit the time and pid
 *          here.
 */
l_int32
pixWriteTempfile(const char  *dir,
                 const char  *tail,
                 PIX         *pix,
                 l_int32      format,
                 char       **pfilename)
{
char    *filename;
l_int32  ret;

    PROCNAME("pixWriteTempfile");

    if (!dir)
        return ERROR_INT("filename not defined", procName, 1);
    if (!pix)
        return ERROR_INT("pix not defined", procName, 1);

    if ((filename = genTempFilename(dir, tail, 0, 0)) == NULL)
        return ERROR_INT("temp filename not made", procName, 1);

    ret = pixWrite(filename, pix, format);
    if (pfilename)
        *pfilename = filename;
    else
        FREE(filename);

    return ret;
}


/*---------------------------------------------------------------------*
 *          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;
}


/*!
 *  pixGetAutoFormat()
 *
 *      Input:  pix
 *              &format
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) The output formats are restricted to tiff, jpeg and png
 *          because these are the most commonly used image formats and
 *          the ones that are typically installed with leptonica.
 *      (2) This decides what compression to use based on the pix.
 *          It chooses tiff-g4 if 1 bpp without a colormap, jpeg with
 *          quality 75 if grayscale, rgb or rgba (where it loses
 *          the alpha layer), and lossless png for all other situations.
 */
l_int32
pixGetAutoFormat(PIX      *pix,
                 l_int32  *pformat)
{
l_int32   d;
PIXCMAP  *cmap;

    PROCNAME("pixGetAutoFormat");

    if (!pformat)
        return ERROR_INT("&format not defined", procName, 0);
    *pformat = IFF_UNKNOWN;
    if (!pix)
        return ERROR_INT("pix not defined", procName, 0);

    d = pixGetDepth(pix);
    cmap = pixGetColormap(pix);
    if (d == 1 && !cmap) {
        *pformat = IFF_TIFF_G4;
    } else if ((d == 8 && !cmap) || d == 24 || d == 32) {
        *pformat = IFF_JFIF_JPEG;
    } else {
        *pformat = IFF_PNG;
    }

    return 0;
}


/*!
 *  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 (<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;

    case IFF_GIF:
        ret = pixWriteMemGif(pdata, psize, pix);
        break;

    case IFF_JP2:
        ret = pixWriteMemJp2k(pdata, psize, pix, 34, 0, 0, 0);
        break;

    case IFF_WEBP:
        ret = pixWriteMemWebP(pdata, psize, pix, 80, 0);
        break;

    case IFF_LPDF:
        ret = pixWriteMemPdf(pdata, psize, pix, 0, NULL);
        break;

    case IFF_SPIX:
        ret = pixWriteMemSpix(pdata, psize, pix);
        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 xzgv, xli or xv on Unix,
 *          or i_view on Windows.  The display program must be on
 *          your $PATH variable.  It is chosen by setting the global
 *          var_DISPLAY_PROG, using l_chooseDisplayProg().
 *          Default on Unix is xzgv.
 *      (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 poor.  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) For spp == 4, we call pixDisplayLayersRGBA() to show 3
 *          versions of the image: the image with a fully opaque
 *          alpha, the alpha, and the image as it would appear with
 *          a white background.
 *      (4) 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 (<optional> 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, spp, maxheight, opaque, threeviews, ignore;
l_float32       ratw, rath, ratmin;
PIX            *pix0, *pix1, *pix2;
PIXCMAP        *cmap;
#ifndef _WIN32
l_int32         wt, ht;
#else
char           *pathname;
char            fullpath[_MAX_PATH];
#endif  /* _WIN32 */

    PROCNAME("pixDisplayWithTitle");

    if (dispflag != 1) return 0;
    if (!pixs)
        return ERROR_INT("pixs not defined", procName, 1);
    if (var_DISPLAY_PROG != L_DISPLAY_WITH_XZGV &&
        var_DISPLAY_PROG != L_DISPLAY_WITH_XLI &&
        var_DISPLAY_PROG != L_DISPLAY_WITH_XV &&
        var_DISPLAY_PROG != L_DISPLAY_WITH_IV &&
        var_DISPLAY_PROG != L_DISPLAY_WITH_OPEN) {
        return ERROR_INT("no program chosen for display", procName, 1);
    }

        /* Display with three views if either spp = 4 or if colormapped
         * and the alpha component is not fully opaque */
    opaque = TRUE;
    if ((cmap = pixGetColormap(pixs)) != NULL)
        pixcmapIsOpaque(cmap, &opaque);
    spp = pixGetSpp(pixs);
    threeviews = (spp == 4 || !opaque) ? TRUE : FALSE;

        /* If colormapped and not opaque, remove the colormap to RGBA */
    if (!opaque)
        pix0 = pixRemoveColormap(pixs, REMOVE_CMAP_WITH_ALPHA);
    else
        pix0 = pixClone(pixs);

        /* Scale if necessary; this will also remove a colormap */
    pixGetDimensions(pix0, &w, &h, &d);
    maxheight = (threeviews) ? MAX_DISPLAY_HEIGHT / 3 : MAX_DISPLAY_HEIGHT;
    if (w <= MAX_DISPLAY_WIDTH && h <= maxheight) {
        if (d == 16)  /* take MSB */
            pix1 = pixConvert16To8(pix0, 1);
        else
            pix1 = pixClone(pix0);
    } else {
        ratw = (l_float32)MAX_DISPLAY_WIDTH / (l_float32)w;
        rath = (l_float32)maxheight / (l_float32)h;
        ratmin = L_MIN(ratw, rath);
        if (ratmin < 0.125 && d == 1)
            pix1 = pixScaleToGray8(pix0);
        else if (ratmin < 0.25 && d == 1)
            pix1 = pixScaleToGray4(pix0);
        else if (ratmin < 0.33 && d == 1)
            pix1 = pixScaleToGray3(pix0);
        else if (ratmin < 0.5 && d == 1)
            pix1 = pixScaleToGray2(pix0);
        else
            pix1 = pixScale(pix0, ratmin, ratmin);
    }
    pixDestroy(&pix0);
    if (!pix1)
        return ERROR_INT("pix1 not made", procName, 1);

        /* Generate the three views if required */
    if (threeviews)
        pix2 = pixDisplayLayersRGBA(pix1, 0xffffff00, 0);
    else
        pix2 = pixClone(pix1);

    if (index == 0) {
        lept_rmdir("disp");
        lept_mkdir("disp");
    }

    index++;
    if (pixGetDepth(pix2) < 8 ||
        (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) {
        snprintf(buffer, L_BUF_SIZE, "/tmp/disp/write.%03d.png", index);
        pixWrite(buffer, pix2, IFF_PNG);
    } else {
        snprintf(buffer, L_BUF_SIZE, "/tmp/disp/write.%03d.jpg", index);
        pixWrite(buffer, pix2, IFF_JFIF_JPEG);
    }
    tempname = genPathname(buffer, NULL);

#ifndef _WIN32

        /* Unix */
    if (var_DISPLAY_PROG == L_DISPLAY_WITH_XZGV) {
            /* no way to display title */
        pixGetDimensions(pix2, &wt, &ht, NULL);
        snprintf(buffer, L_BUF_SIZE,
                 "xzgv --geometry %dx%d+%d+%d %s &", wt + 10, ht + 10,
                 x, y, tempname);
    } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XLI) {
        if (title) {
            snprintf(buffer, L_BUF_SIZE,
               "xli -dispgamma 1.0 -quiet -geometry +%d+%d -title \"%s\" %s &",
               x, y, title, tempname);
        } else {
            snprintf(buffer, L_BUF_SIZE,
               "xli -dispgamma 1.0 -quiet -geometry +%d+%d %s &",
               x, y, tempname);
        }
    } else if (var_DISPLAY_PROG == 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 (var_DISPLAY_PROG == L_DISPLAY_WITH_OPEN) {
        snprintf(buffer, L_BUF_SIZE, "open %s &", tempname);
    }
    ignore = system(buffer);

#else  /* _WIN32 */

        /* Windows: L_DISPLAY_WITH_IV */
    pathname = genPathname(tempname, NULL);
    _fullpath(fullpath, pathname, sizeof(fullpath));
    if (title) {
        snprintf(buffer, L_BUF_SIZE,
                 "i_view32.exe \"%s\" /pos=(%d,%d) /title=\"%s\"",
                 fullpath, x, y, title);
    } else {
        snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d)",
                 fullpath, x, y);
    }
    ignore = system(buffer);
    FREE(pathname);

#endif  /* _WIN32 */

    pixDestroy(&pix1);
    pixDestroy(&pix2);
    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];
l_int32  ignore;
#ifdef _WIN32
char    *pathname;
char    *dir, *tail;
char     fullpath[_MAX_PATH];
#endif  /* _WIN32 */

    PROCNAME("pixDisplayMultiple");

    if (!filepattern || strlen(filepattern) == 0)
        return ERROR_INT("filepattern not defined", procName, 1);

#ifndef _WIN32
    snprintf(buffer, L_BUF_SIZE, "gthumb %s &", filepattern);
#else
        /* irFanView wants absolute path for directory */
    pathname = genPathname(filepattern, NULL);
    splitPathAtDirectory(pathname, &dir, &tail);
    _fullpath(fullpath, dir, sizeof(fullpath));

    snprintf(buffer, L_BUF_SIZE,
             "i_view32.exe \"%s\" /filepattern=\"%s\" /thumbs", fullpath, tail);
    FREE(pathname);
    FREE(dir);
    FREE(tail);
#endif  /* _WIN32 */

    ignore = system(buffer);  /* gthumb || i_view32.exe */
    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/display/file*");
 *      (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            buf[L_BUF_SIZE];
char           *fname;
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) {
        lept_rmdir("display");
        lept_mkdir("display");
    }
    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(buf, L_BUF_SIZE, "file.%03d.png", index);
        fname = genPathname("/tmp/display", buf);
        pixWrite(fname, pix8, IFF_PNG);
        pixDestroy(&pix8);
    } else if (pixGetDepth(pixt) < 8 || pixGetColormap(pixt) ||
             format == IFF_PNG) {
        snprintf(buf, L_BUF_SIZE, "file.%03d.png", index);
        fname = genPathname("/tmp/display", buf);
        pixWrite(fname, pixt, IFF_PNG);
    } else {
        snprintf(buf, L_BUF_SIZE, "file.%03d.jpg", index);
        fname = genPathname("/tmp/display", buf);
        pixWrite(fname, pixt, format);
    }
    FREE(fname);
    pixDestroy(&pixt);

    return 0;
}


/*!
 *  pixSaveTiled()
 *
 *      Input:  pixs (1, 2, 4, 8, 32 bpp)
 *              pixa (the pix are accumulated here)
 *              scalefactor (0.0 to disable; otherwise this is a scale 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_float32  scalefactor,
             l_int32    newrow,
             l_int32    space,
             l_int32    dp)
{
        /* Save without an outline */
    return pixSaveTiledOutline(pixs, pixa, scalefactor, newrow, space, 0, dp);
}


/*!
 *  pixSaveTiledOutline()
 *
 *      Input:  pixs (1, 2, 4, 8, 32 bpp)
 *              pixa (the pix are accumulated here)
 *              scalefactor (0.0 to disable; otherwise this is a scale 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) @scalefactor scales the input image.  After scaling 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 @scalefactor == 0.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_float32  scalefactor,
                    l_int32    newrow,
                    l_int32    space,
                    l_int32    linewidth,
                    l_int32    dp)
{
l_int32  n, top, left, bx, by, bw, w, h, depth, bottom;
BOX     *box;
PIX     *pix1, *pix2, *pix3, *pix4;

    PROCNAME("pixSaveTiledOutline");

    if (scalefactor == 0.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\n", procName);
            depth = 32;
        } else {
            depth = dp;
        }
    } else {  /* extract the depth and bottom params from the first pix */
        pix1 = pixaGetPix(pixa, 0, L_CLONE);
        depth = pixGetDepth(pix1);
        bottom = pixGetInputFormat(pix1);  /* not typical usage! */
        pixDestroy(&pix1);
    }

        /* Remove colormap if it exists; otherwise a copy.  This
         * guarantees that pix4 is not a clone of pixs. */
    pix1 = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_BASED_ON_SRC, L_COPY);

        /* Scale and convert to output depth */
    if (scalefactor == 1.0) {
        pix2 = pixClone(pix1);
    } else if (scalefactor > 1.0) {
        pix2 = pixScale(pix1, scalefactor, scalefactor);
    } else if (scalefactor < 1.0) {
        if (pixGetDepth(pix1) == 1)
            pix2 = pixScaleToGray(pix1, scalefactor);
        else
            pix2 = pixScale(pix1, scalefactor, scalefactor);
    }
    pixDestroy(&pix1);
    if (depth == 8)
        pix3 = pixConvertTo8(pix2, 0);
    else
        pix3 = pixConvertTo32(pix2);
    pixDestroy(&pix2);

        /* Add black outline */
    if (linewidth > 0)
        pix4 = pixAddBorder(pix3, linewidth, 0);
    else
        pix4 = pixClone(pix3);
    pixDestroy(&pix3);

        /* 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(pix4, &w, &h, NULL);
    bottom = L_MAX(bottom, top + h);
    box = boxCreate(left, top, w, h);
    pixaAddPix(pixa, pix4, L_INSERT);
    pixaAddBox(pixa, box, L_INSERT);

        /* Save the new bottom value */
    pix1 = pixaGetPix(pixa, 0, L_CLONE);
    pixSetInputFormat(pix1, bottom);  /* not typical usage! */
    pixDestroy(&pix1);
    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 (<optional> font struct)
 *              textstr (<optional> text string to be added)
 *              val (color to set the text)
 *              location (L_ADD_ABOVE, L_ADD_AT_TOP, L_ADD_AT_BOT, 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  *pix1, *pix2, *pix3, *pix4;

    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);

    pix1 = pixConvertTo32(pixs);
    if (linewidth > 0)
        pix2 = pixAddBorder(pix1, linewidth, 0);
    else
        pix2 = pixClone(pix1);
    if (bmf && textstr)
        pix3 = pixAddSingleTextblock(pix2, bmf, textstr, val, location, NULL);
    else
        pix3 = pixClone(pix2);
    pix4 = pixScaleToSize(pix3, outwidth, 0);
    pixSaveTiled(pix4, pixa, 1.0, newrow, space, 32);
    pixDestroy(&pix1);
    pixDestroy(&pix2);
    pixDestroy(&pix3);
    pixDestroy(&pix4);
    return 0;
}


void
l_chooseDisplayProg(l_int32  selection)
{
    if (selection == L_DISPLAY_WITH_XLI ||
        selection == L_DISPLAY_WITH_XZGV ||
        selection == L_DISPLAY_WITH_XV ||
        selection == L_DISPLAY_WITH_IV ||
        selection == L_DISPLAY_WITH_OPEN) {
        var_DISPLAY_PROG = selection;
    } else {
        L_ERROR("invalid display program\n", "l_chooseDisplayProg");
    }
    return;
}
