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

/*
 *  bmpio.c
 *
 *      Read bmp from file
 *           PIX          *pixReadStreamBmp()
 *
 *      Write bmp to file
 *           l_int32       pixWriteStreamBmp()
 *
 *      Read/write to memory
 *           PIX          *pixReadMemBmp()
 *           l_int32       pixWriteMemBmp()
 *
 *    On systems like windows without fmemopen() and open_memstream(),
 *    we write data to a temp file and read it back for operations
 *    between pix and compressed-data, such as pixReadMemPng() and
 *    pixWriteMemPng().
 */

#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif  /* HAVE_CONFIG_H */

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

/* --------------------------------------------*/
#if  USE_BMPIO   /* defined in environ.h */
/* --------------------------------------------*/

    /* Here we're setting the pixel value 0 to white (255) and the
     * value 1 to black (0).  This is the convention for grayscale, but
     * the opposite of the convention for 1 bpp, where 0 is white
     * and 1 is black.  Both colormap entries are opaque (alpha = 255) */
RGBA_QUAD   bwmap[2] = { {255,255,255,255}, {0,0,0,255} };

    /* Colormap size limit */
static const l_int32  L_MAX_ALLOWED_NUM_COLORS = 256;

    /* Image dimension limits */
static const l_int32  L_MAX_ALLOWED_WIDTH = 1000000;
static const l_int32  L_MAX_ALLOWED_HEIGHT = 1000000;
static const l_int64  L_MAX_ALLOWED_AREA = 400000000LL;

#ifndef  NO_CONSOLE_IO
#define  DEBUG     0
#endif  /* ~NO_CONSOLE_IO */


/*!
 *  pixReadStreamBmp()
 *
 *      Input:  stream opened for read
 *      Return: pix, or null on error
 *
 *  Notes:
 *      (1) Here are references on the bmp file format:
 *          http://en.wikipedia.org/wiki/BMP_file_format
 *          http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html
 */
PIX *
pixReadStreamBmp(FILE  *fp)
{
l_uint16   sval;
l_uint32   ival;
l_int16    bfType, bfSize, bfFill1, bfReserved1, bfReserved2;
l_int16    offset, bfFill2, biPlanes, depth, d;
l_int32    biSize, width, height, xres, yres, compression;
l_int32    imagebytes, biClrUsed, biClrImportant;
size_t     filesize;
l_uint8   *colormapBuf;
l_int32    colormapEntries;
l_int32    fileBpl, extrabytes;
l_int32    pixWpl, pixBpl;
l_int32    i, j, k;
l_int64    area;
l_uint8    pel[4];
l_uint8   *data;
l_uint32  *line, *pword;
PIX        *pix, *pixt;
PIXCMAP   *cmap;

    PROCNAME("pixReadStreamBmp");

    if (!fp)
        return (PIX *)ERROR_PTR("fp not defined", procName, NULL);

        /* Read bitmap file header */
    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 1 not read", procName, NULL);
    bfType = convertOnBigEnd16(sval);
    if (bfType != BMP_ID)
        return (PIX *)ERROR_PTR("not bmf format", procName, NULL);

    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 2 not read", procName, NULL);
    bfSize = convertOnBigEnd16(sval);
    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 3 not read", procName, NULL);
    bfFill1 = convertOnBigEnd16(sval);
    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 4 not read", procName, NULL);
    bfReserved1 = convertOnBigEnd16(sval);
    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 5 not read", procName, NULL);
    bfReserved2 = convertOnBigEnd16(sval);
    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 6 not read", procName, NULL);
    offset = convertOnBigEnd16(sval);
    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 7 not read", procName, NULL);
    bfFill2 = convertOnBigEnd16(sval);

        /* Read bitmap info header */
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 8 not read", procName, NULL);
    biSize = convertOnBigEnd32(ival);
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 9 not read", procName, NULL);
    width = convertOnBigEnd32(ival);
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 10 not read", procName, NULL);
    height = convertOnBigEnd32(ival);
    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 11 not read", procName, NULL);
    biPlanes = convertOnBigEnd16(sval);
    if (fread((char *)&sval, 2, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 12 not read", procName, NULL);
    depth = convertOnBigEnd16(sval);
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 13 not read", procName, NULL);
    compression = convertOnBigEnd32(ival);
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 14 not read", procName, NULL);
    imagebytes = convertOnBigEnd32(ival);
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 15 not read", procName, NULL);
    xres = convertOnBigEnd32(ival);
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 16 not read", procName, NULL);
    yres = convertOnBigEnd32(ival);
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 17 not read", procName, NULL);
    biClrUsed = convertOnBigEnd32(ival);
    if (fread((char *)&ival, 4, 1, fp) != 1)
        return (PIX *)ERROR_PTR("item 18 not read", procName, NULL);
    biClrImportant = convertOnBigEnd32(ival);

    if (compression != 0)
        return (PIX *)ERROR_PTR("cannot read compressed BMP files",
                                procName, NULL);

        /* Some sanity checking.  We impose limits on the image
         * dimensions and number of pixels.  We make sure the file
         * is large enough to hold the amount of uncompressed data
         * that is specified in the header.  The number of colormap
         * entries is checked: it can be either 0 (no cmap) or some
         * number between 2 and 256.
         * Note that the imagebytes for uncompressed images is either
         * 0 or the size of the file data.  (The fact that it can
         * be 0 is perhaps some legacy glitch). */
    if (width < 1)
        return (PIX *)ERROR_PTR("width < 1", procName, NULL);
    if (width > L_MAX_ALLOWED_WIDTH)
        return (PIX *)ERROR_PTR("width too large", procName, NULL);
    if (height < 1)
        return (PIX *)ERROR_PTR("height < 1", procName, NULL);
    if (height > L_MAX_ALLOWED_HEIGHT)
        return (PIX *)ERROR_PTR("height too large", procName, NULL);
    area = 1LL * width * height;
    if (area > L_MAX_ALLOWED_AREA)
        return (PIX *)ERROR_PTR("area too large", procName, NULL);
    if (depth != 1 && depth != 2 && depth != 4 && depth != 8 &&
        depth != 16 && depth != 24 && depth != 32)
        return (PIX *)ERROR_PTR("depth not in {1, 2, 4, 8, 16, 24, 32}",
                                procName,NULL);
    fileBpl = 4 * ((1LL * width * depth + 31)/32);
    if (imagebytes != 0 && imagebytes != fileBpl * height)
        return (PIX *)ERROR_PTR("invalid imagebytes", procName, NULL);
    colormapEntries = (offset - BMP_FHBYTES - BMP_IHBYTES) / sizeof(RGBA_QUAD);
    if (colormapEntries < 0 || colormapEntries == 1)
        return (PIX *)ERROR_PTR("invalid: cmap size < 0 or 1", procName, NULL);
    if (colormapEntries > L_MAX_ALLOWED_NUM_COLORS)
        return (PIX *)ERROR_PTR("invalid cmap: too large", procName,NULL);
    filesize = fnbytesInFile(fp);
    if (filesize < 1LL * fileBpl * height)
        return (PIX *)ERROR_PTR("file too small to hold data", procName,NULL);

        /* Handle the colormap */
    colormapBuf = NULL;
    if (colormapEntries > 0) {
        if ((colormapBuf = (l_uint8 *)LEPT_CALLOC(colormapEntries,
                                             sizeof(RGBA_QUAD))) == NULL)
            return (PIX *)ERROR_PTR("colormapBuf alloc fail", procName, NULL );

            /* Read colormap */
        if (fread(colormapBuf, sizeof(RGBA_QUAD), colormapEntries, fp)
                 != colormapEntries) {
            LEPT_FREE(colormapBuf);
            return (PIX *)ERROR_PTR( "colormap read fail", procName, NULL);
        }
    }

        /* Make a 32 bpp pix if depth is 24 bpp */
    d = depth;
    if (depth == 24)
        d = 32;
    if ((pix = pixCreate(width, height, d)) == NULL) {
        LEPT_FREE(colormapBuf);
        return (PIX *)ERROR_PTR( "pix not made", procName, NULL);
    }
    pixSetXRes(pix, (l_int32)((l_float32)xres / 39.37 + 0.5));  /* to ppi */
    pixSetYRes(pix, (l_int32)((l_float32)yres / 39.37 + 0.5));  /* to ppi */
    pixSetInputFormat(pix, IFF_BMP);
    pixWpl = pixGetWpl(pix);
    pixBpl = 4 * pixWpl;

    cmap = NULL;
    if (colormapEntries > 0) {  /* import the colormap to the pix cmap */
        cmap = pixcmapCreate(L_MIN(d, 8));
        LEPT_FREE(cmap->array);  /* remove generated cmap array */
        cmap->array  = (void *)colormapBuf;  /* and replace */
        cmap->n = L_MIN(colormapEntries, 256);
    }
    pixSetColormap(pix, cmap);

        /* Seek to the start of the image data in the file */
    fseek(fp, offset, 0);

    if (depth != 24) {  /* typ. 1 or 8 bpp */
        data = (l_uint8 *)pixGetData(pix) + pixBpl * (height - 1);
        for (i = 0; i < height; i++) {
            if (fread(data, 1, fileBpl, fp) != fileBpl) {
                pixDestroy(&pix);
                return (PIX *)ERROR_PTR("BMP read fail", procName, NULL);
            }
            data -= pixBpl;
        }
    } else {  /*  24 bpp file; 32 bpp pix
             *  Note: for bmp files, pel[0] is blue, pel[1] is green,
             *  and pel[2] is red.  This is opposite to the storage
             *  in the pix, which puts the red pixel in the 0 byte,
             *  the green in the 1 byte and the blue in the 2 byte.
             *  Note also that all words are endian flipped after
             *  assignment on L_LITTLE_ENDIAN platforms.
             *
             *  We can then make these assignments for little endians:
             *      SET_DATA_BYTE(pword, 1, pel[0]);      blue
             *      SET_DATA_BYTE(pword, 2, pel[1]);      green
             *      SET_DATA_BYTE(pword, 3, pel[2]);      red
             *  This looks like:
             *          3  (R)     2  (G)        1  (B)        0
             *      |-----------|------------|-----------|-----------|
             *  and after byte flipping:
             *           3          2  (B)     1  (G)        0  (R)
             *      |-----------|------------|-----------|-----------|
             *
             *  For big endians we set:
             *      SET_DATA_BYTE(pword, 2, pel[0]);      blue
             *      SET_DATA_BYTE(pword, 1, pel[1]);      green
             *      SET_DATA_BYTE(pword, 0, pel[2]);      red
             *  This looks like:
             *          0  (R)     1  (G)        2  (B)        3
             *      |-----------|------------|-----------|-----------|
             *  so in both cases we get the correct assignment in the PIX.
             *
             *  Can we do a platform-independent assignment?
             *  Yes, set the bytes without using macros:
             *      *((l_uint8 *)pword) = pel[2];           red
             *      *((l_uint8 *)pword + 1) = pel[1];       green
             *      *((l_uint8 *)pword + 2) = pel[0];       blue
             *  For little endians, before flipping, this looks again like:
             *          3  (R)     2  (G)        1  (B)        0
             *      |-----------|------------|-----------|-----------|
             */
        extrabytes = fileBpl - 3 * width;
        line = pixGetData(pix) + pixWpl * (height - 1);
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                pword = line + j;
                if (fread(&pel, 1, 3, fp) != 3) {
                    pixDestroy(&pix);
                    return (PIX *)ERROR_PTR("bmp(1) read fail", procName, NULL);
                }
                *((l_uint8 *)pword + COLOR_RED) = pel[2];
                *((l_uint8 *)pword + COLOR_GREEN) = pel[1];
                *((l_uint8 *)pword + COLOR_BLUE) = pel[0];
            }
            if (extrabytes) {
                for (k = 0; k < extrabytes; k++) {
                    if (fread(&pel, 1, 1, fp) != 1) {
                        pixDestroy(&pix);
                        return (PIX *)ERROR_PTR("bmp(2) read fail",
                                                procName, NULL);
                    }
                }
            }
            line -= pixWpl;
        }
    }

    pixEndianByteSwap(pix);

        /* ----------------------------------------------
         * The bmp colormap determines the values of black
         * and white pixels for binary in the following way:
         * (a) white = 0 [255], black = 1 [0]
         *      255, 255, 255, 255, 0, 0, 0, 255
         * (b) black = 0 [0], white = 1 [255]
         *      0, 0, 0, 255, 255, 255, 255, 255
         * We have no need for a 1 bpp pix with a colormap!
         * Note: the alpha component here is 255 (opaque)
         * ---------------------------------------------- */
    if (depth == 1 && cmap) {
        pixt = pixRemoveColormap(pix, REMOVE_CMAP_TO_BINARY);
        pixDestroy(&pix);
        pix = pixt;  /* rename */
    }

    return pix;
}



/*!
 *  pixWriteStreamBmp()
 *
 *      Input:  stream opened for write
 *              pix (1, 4, 8, 32 bpp)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) We position fp at the beginning of the stream, so it
 *          truncates any existing data
 *      (2) 2 bpp Bmp files are apparently not valid!.  We can
 *          write and read them, but nobody else can read ours.
 */
l_int32
pixWriteStreamBmp(FILE  *fp,
                  PIX   *pix)
{
l_uint32    offbytes, filebytes, fileimagebytes;
l_int32     width, height, depth, d, xres, yres;
l_uint16    bfType, bfSize, bfFill1, bfReserved1, bfReserved2;
l_uint16    bfOffBits, bfFill2, biPlanes, biBitCount;
l_uint16    sval;
l_uint32    biSize, biWidth, biHeight, biCompression, biSizeImage;
l_uint32    biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportant;
l_int32     pixWpl, pixBpl, extrabytes, writeerror;
l_int32     fileBpl, fileWpl;
l_int32     i, j, k;
l_int32     heapcm;  /* extra copy of cta on the heap ? 1 : 0 */
l_uint8    *data;
l_uint8     pel[4];
l_uint32   *line, *pword;
PIXCMAP    *cmap;
l_uint8    *cta;          /* address of the bmp color table array */
l_int32     cmaplen;      /* number of bytes in the bmp colormap */
l_int32     ncolors, val, stepsize;
RGBA_QUAD  *pquad;

    PROCNAME("pixWriteStreamBmp");

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

    width  = pixGetWidth(pix);
    height = pixGetHeight(pix);
    d  = pixGetDepth(pix);
    if (d == 2)
        L_WARNING("writing 2 bpp bmp file; nobody else can read\n", procName);
    depth = d;
    if (d == 32)
        depth = 24;
    xres = (l_int32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5);  /* to ppm */
    yres = (l_int32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5);  /* to ppm */

    pixWpl = pixGetWpl(pix);
    pixBpl = 4 * pixWpl;
    fileWpl = (width * depth + 31) / 32;
    fileBpl = 4 * fileWpl;
    fileimagebytes = height * fileBpl;

    heapcm = 0;
    if (d == 32) {   /* 24 bpp rgb; no colormap */
        ncolors = 0;
        cmaplen = 0;
    } else if ((cmap = pixGetColormap(pix))) {   /* existing colormap */
        ncolors = pixcmapGetCount(cmap);
        cmaplen = ncolors * sizeof(RGBA_QUAD);
        cta = (l_uint8 *)cmap->array;
    } else {   /* no existing colormap; make a binary or gray one */
        if (d == 1) {
            cmaplen  = sizeof(bwmap);
            ncolors = 2;
            cta = (l_uint8 *)bwmap;
        } else {   /* d != 32; output grayscale version */
            ncolors = 1 << depth;
            cmaplen = ncolors * sizeof(RGBA_QUAD);

            heapcm = 1;
            if ((cta = (l_uint8 *)LEPT_CALLOC(cmaplen, 1)) == NULL)
                return ERROR_INT("colormap alloc fail", procName, 1);

            stepsize = 255 / (ncolors - 1);
            for (i = 0, val = 0, pquad = (RGBA_QUAD *)cta;
                 i < ncolors;
                 i++, val += stepsize, pquad++) {
                pquad->blue = pquad->green = pquad->red = val;
                pquad->alpha = 255;  /* opaque */
            }
        }
    }

#if DEBUG
    {l_uint8  *pcmptr;
        pcmptr = (l_uint8 *)pixGetColormap(pix)->array;
        fprintf(stderr, "Pix colormap[0] = %c%c%c%d\n",
            pcmptr[0], pcmptr[1], pcmptr[2], pcmptr[3]);
        fprintf(stderr, "Pix colormap[1] = %c%c%c%d\n",
            pcmptr[4], pcmptr[5], pcmptr[6], pcmptr[7]);
    }
#endif  /* DEBUG */

    fseek(fp, 0L, 0);

        /* Convert to little-endian and write the file header data */
    bfType = convertOnBigEnd16(BMP_ID);
    offbytes = BMP_FHBYTES + BMP_IHBYTES + cmaplen;
    filebytes = offbytes + fileimagebytes;
    sval = filebytes & 0x0000ffff;
    bfSize = convertOnBigEnd16(sval);
    sval = (filebytes >> 16) & 0x0000ffff;
    bfFill1 = convertOnBigEnd16(sval);
    bfReserved1 = 0;
    bfReserved2 = 0;
    sval = offbytes & 0x0000ffff;
    bfOffBits = convertOnBigEnd16(sval);
    sval = (offbytes >> 16) & 0x0000ffff;
    bfFill2 = convertOnBigEnd16(sval);
    fwrite(&bfType, 1, 2, fp);
    fwrite(&bfSize, 1, 2, fp);
    fwrite(&bfFill1, 1, 2, fp);
    fwrite(&bfReserved1, 1, 2, fp);
    fwrite(&bfReserved2, 1, 2, fp);
    fwrite(&bfOffBits, 1, 2, fp);
    fwrite(&bfFill2, 1, 2, fp);

        /* Convert to little-endian and write the info header data */
    biSize = convertOnBigEnd32(BMP_IHBYTES);
    biWidth = convertOnBigEnd32(width);
    biHeight = convertOnBigEnd32(height);
    biPlanes = convertOnBigEnd16(1);
    biBitCount = convertOnBigEnd16(depth);
    biCompression   = 0;
    biSizeImage = convertOnBigEnd32(fileimagebytes);
    biXPelsPerMeter = convertOnBigEnd32(xres);
    biYPelsPerMeter = convertOnBigEnd32(yres);
    biClrUsed = convertOnBigEnd32(ncolors);
    biClrImportant = convertOnBigEnd32(ncolors);
    fwrite(&biSize, 1, 4, fp);
    fwrite(&biWidth, 1, 4, fp);
    fwrite(&biHeight, 1, 4, fp);
    fwrite(&biPlanes, 1, 2, fp);
    fwrite(&biBitCount, 1, 2, fp);
    fwrite(&biCompression, 1, 4, fp);
    fwrite(&biSizeImage, 1, 4, fp);
    fwrite(&biXPelsPerMeter, 1, 4, fp);
    fwrite(&biYPelsPerMeter, 1, 4, fp);
    fwrite(&biClrUsed, 1, 4, fp);
    fwrite(&biClrImportant, 1, 4, fp);

        /* Write the colormap data */
    if (ncolors > 0) {
        if (fwrite(cta, 1, cmaplen, fp) != cmaplen) {
            if (heapcm)
                LEPT_FREE(cta);
            return ERROR_INT("colormap write fail", procName, 1);
        }
        if (heapcm)
            LEPT_FREE(cta);
    }

        /* When you write a binary image with a colormap
         * that sets BLACK to 0, you must invert the data */
    if (depth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0) {
        pixInvert(pix, pix);
    }

    pixEndianByteSwap(pix);

    writeerror = 0;
    if (depth != 24) {   /* typ 1 or 8 bpp */
        data = (l_uint8 *)pixGetData(pix) + pixBpl * (height - 1);
        for (i = 0; i < height; i++) {
            if (fwrite(data, 1, fileBpl, fp) != fileBpl)
                writeerror = 1;
            data -= pixBpl;
        }
    } else {  /* 32 bpp pix; 24 bpp file
             * See the comments in pixReadStreamBMP() to
             * understand the logic behind the pixel ordering below.
             * Note that we have again done an endian swap on
             * little endian machines before arriving here, so that
             * the bytes are ordered on both platforms as:
                        Red         Green        Blue         --
                    |-----------|------------|-----------|-----------|
             */
        extrabytes = fileBpl - 3 * width;
        line = pixGetData(pix) + pixWpl * (height - 1);
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                pword = line + j;
                pel[2] = *((l_uint8 *)pword + COLOR_RED);
                pel[1] = *((l_uint8 *)pword + COLOR_GREEN);
                pel[0] = *((l_uint8 *)pword + COLOR_BLUE);
                if (fwrite(&pel, 1, 3, fp) != 3)
                    writeerror = 1;
            }
            if (extrabytes) {
                for (k = 0; k < extrabytes; k++)
                    fwrite(&pel, 1, 1, fp);
            }
            line -= pixWpl;
        }
    }

        /* Restore to original state */
    pixEndianByteSwap(pix);
    if (depth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0)
        pixInvert(pix, pix);

    if (writeerror)
        return ERROR_INT("image write fail", procName, 1);

    return 0;
}


/*---------------------------------------------------------------------*
 *                         Read/write to memory                        *
 *---------------------------------------------------------------------*/
#if HAVE_FMEMOPEN
extern FILE *open_memstream(char **data, size_t *size);
extern FILE *fmemopen(void *data, size_t size, const char *mode);
#endif  /* HAVE_FMEMOPEN */

/*!
 *  pixReadMemBmp()
 *
 *      Input:  cdata (const; bmp-encoded)
 *              size (of data)
 *      Return: pix, or null on error
 *
 *  Notes:
 *      (1) The @size byte of @data must be a null character.
 */
PIX *
pixReadMemBmp(const l_uint8  *cdata,
              size_t          size)
{
FILE  *fp;
PIX   *pix;

    PROCNAME("pixReadMemBmp");

    if (!cdata)
        return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);

#if HAVE_FMEMOPEN
    if ((fp = fmemopen((l_uint8 *)cdata, size, "rb")) == NULL)
        return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
#else
    L_WARNING("work-around: writing to a temp file\n", procName);
    if ((fp = tmpfile()) == NULL)
        return (PIX *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
    fwrite(cdata, 1, size, fp);
    rewind(fp);
#endif  /* HAVE_FMEMOPEN */
    pix = pixReadStreamBmp(fp);
    fclose(fp);
    if (!pix) L_ERROR("pix not read\n", procName);
    return pix;
}


/*!
 *  pixWriteMemBmp()
 *
 *      Input:  &data (<return> data of tiff compressed image)
 *              &size (<return> size of returned data)
 *              pix
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) See pixWriteStreamBmp() for usage.  This version writes to
 *          memory instead of to a file stream.
 */
l_int32
pixWriteMemBmp(l_uint8  **pdata,
               size_t    *psize,
               PIX       *pix)
{
l_int32  ret;
FILE    *fp;

    PROCNAME("pixWriteMemBmp");

    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 HAVE_FMEMOPEN
    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
        return ERROR_INT("stream not opened", procName, 1);
    ret = pixWriteStreamBmp(fp, pix);
#else
    L_WARNING("work-around: writing to a temp file\n", procName);
    if ((fp = tmpfile()) == NULL)
        return ERROR_INT("tmpfile stream not opened", procName, 1);
    ret = pixWriteStreamBmp(fp, pix);
    rewind(fp);
    *pdata = l_binaryReadStream(fp, psize);
#endif  /* HAVE_FMEMOPEN */
    fclose(fp);
    return ret;
}

/* --------------------------------------------*/
#endif  /* USE_BMPIO */
/* --------------------------------------------*/
