/*
 * Image handling.
 * Images are the device independent representation of rasters.
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 Includes
 */

#include <stdlib.h>
#include <string.h>

#include "batypes.h"
#include "balibtyp.h"
#include "badebug.h"
#include "bastream.h"
#include "bamemory.h"
#include "babinary.h"
#include "rwstring.h"

/* Image handling */
#include "baimform.h"

#if (!defined(DOXYGEN))
static const char  __RWUNUSED__ rcsid[]  =
    "@@(#)$Id: baimform.c,v 1.42 2001/09/18 17:13:18 mattt Exp $";
#endif /* (!defined(DOXYGEN)) */


#if (rwLIBRARYCURRENTVERSION >= 0x31100)

/*
 * The functions:
 *   RwImageReadBMP
 *   RwImageWriteBMP
 *   RwImageReadRAS
 *   RwImageWriteRAS
 * are now superfluous after the introduction of RtBmp and RtRas toolkits.
 * The toolkits contain the following functions:
 *   RtBMPImageRead
 *   RtBMPImageWrite
 *   RtRASImageRead
 *   RtRASImageWrite
 */
#error \
The functions defined in baimform.c and baimform.h should be removed \
as they have been made redundant by the introduction of the RtBMP \
and RtRAS toolkits.

/*
 * The funtions will remain until then.
 */
#else /* (rwLIBRARYCURRENTVERSION >= 0x31100) */

/****************************************************************************
 Local Types
 */

/* Sun raster header format ------------------------------------------- */
typedef struct _RwSunRasterHeader RwSunRasterHeader;
struct _RwSunRasterHeader
{
        RwInt32             magic;
        RwInt32             width;
        RwInt32             height;
        RwInt32             bitsPerPixel;
        RwInt32             imageLength;
        RwInt32             type;
        RwInt32             mapType;
        RwInt32             mapLength;
};

#define rwSUNRASTERMAGIC    0x956aa659

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

#define RWBYTESWAPINT32(int32var)               \
    MACRO_START                                 \
    {                                           \
        RwUInt8     caTmp[4];                   \
        RwUInt8     cTmp;                       \
                                                \
        *(RwInt32 *) caTmp = (int32var);        \
        cTmp = caTmp[0];                        \
        caTmp[0] = caTmp[3];                    \
        caTmp[3] = cTmp;                        \
                                                \
        cTmp = caTmp[1];                        \
        caTmp[1] = caTmp[2];                    \
        caTmp[2] = cTmp;                        \
                                                \
        (int32var) = *(RwInt32 *)caTmp;         \
    }                                           \
    MACRO_STOP

#define RWBYTESWAPINT16(int16var)               \
    MACRO_START                                 \
    {                                           \
        RwUInt8     caTmp[2];                   \
        RwUInt8     cTmp;                       \
                                                \
        *(RwInt16 *) caTmp = (int16var);        \
        cTmp = caTmp[0];                        \
        caTmp[0] = caTmp[1];                    \
        caTmp[1] = cTmp;                        \
                                                \
        (int16var) = *(RwInt16 *)caTmp;         \
    }                                           \
    MACRO_STOP

/* Get numbers in little endian -- Intel -- format */
#define CHAROFF(p, off)     ((RwUInt8 *)(p) + (off))

#ifdef rwLITTLEENDIAN

/* We can just pull from memory */
#define RWRWINT32GETLITTLE(p, off)  (*(RwInt32 *)(CHAROFF(p, off)))
#define RWRWINT16GETLITTLE(p, off)  (*(RwInt16 *)(CHAROFF(p, off)))
#else /* rwLITTLEENDIAN */

/* We have to shuffle the bits */
#define RWRWINT32GETLITTLE(p, off)  ((((RwInt32)(*CHAROFF(p, off)))) | \
                                     (((RwInt32)(*CHAROFF(p, off+1))) << 8) | \
                                     (((RwInt32)(*CHAROFF(p, off+2))) << 16) | \
                                     (((RwInt32)(*CHAROFF(p, off+3))) << 24))
#define RWRWINT16GETLITTLE(p, off)  ((((RwInt16)(*CHAROFF(p, off)))) | \
                                     (((RwInt16)(*CHAROFF(p, off+1))) << 8))
#endif /* rwLITTLEENDIAN */

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Image handling

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/****************************************************************************
 rwImageSetSpan

 Expands a span into the appropriate format

 On entry   : Image
            : Y coord of span
            : Data
            : Depth
            : TRUE if RGB (or GBR)
 On exit    : TRUE on success
 */

#define GENSWITCHKEY(srcDepth, destDepth) (((srcDepth) << 8) | (destDepth))

static              RwBool
rwImageSetSpan(RwImage * image, RwInt32 y, RwUInt8 * cpSpan,
               RwInt32 depth, RwBool bRGB)
{
    RwUInt8             cCur;
    RwInt32             shift;
    RwRGBA             *rpPal = image->palette;
    RwInt32             i = image->width;
    RwInt32             j;
    RwUInt32            switchKey;
    RwUInt8            *cpDestin;
    RwRGBA             *rpDestin;

    RWFUNCTION(RWSTRING("rwImageSetSpan"));
    RWASSERT(image);
    RWASSERT(cpSpan);

    cpDestin = (RwUInt8 *) (image->cpPixels + (image->stride * y));
    rpDestin = (RwRGBA *) cpDestin;

    switchKey = GENSWITCHKEY(depth, image->depth);
    switch (switchKey)
    {
        case (GENSWITCHKEY(1, 4)): /* 1 to 4 bit */
        case (GENSWITCHKEY(1, 8)): /* 1 to 8 bit */
            {
                shift = 7;
                cCur = *cpSpan++;

                while (i--)
                {
                    (*cpDestin) = (cCur >> shift) & 1;

                    cpDestin++;
                    shift--;

                    /* Run out of bits? */
                    if (shift < 0)
                    {
                        shift = 7;
                        cCur = *cpSpan++;
                    }
                }

                break;
            }
        case (GENSWITCHKEY(4, 4)): /* 4 to 4 bit */
        case (GENSWITCHKEY(4, 8)): /* 4 to 8 bit */
            {
                shift = 4;
                cCur = *cpSpan++;

                while (i--)
                {
                    (*cpDestin) = (cCur >> shift) & 0xf;

                    cpDestin++;
                    shift -= 4;

                    /* Run out of bits? */
                    if (shift < 0)
                    {
                        shift = 4;
                        cCur = *cpSpan++;
                    }
                }

                break;
            }
        case (GENSWITCHKEY(8, 8)): /* 8 to 8 bit */
            {
                memcpy(cpDestin, cpSpan, i);
                break;
            }
        case (GENSWITCHKEY(1, 32)):
            {
                shift = 7;
                cCur = *cpSpan++;

                while (i--)
                {
                    (*rpDestin) = rpPal[(cCur >> shift) & 1];

                    rpDestin++;
                    shift--;

                    /* Run out of bits? */
                    if (shift < 0)
                    {
                        shift = 7;
                        cCur = *cpSpan++;
                    }
                }

                break;
            }
        case (GENSWITCHKEY(4, 32)):
            {
                shift = 4;
                cCur = *cpSpan++;

                while (i--)
                {
                    (*rpDestin) = rpPal[(cCur >> shift) & 0xf];

                    rpDestin++;
                    shift -= 4;

                    /* Run out of bits? */
                    if (shift < 0)
                    {
                        shift = 4;
                        cCur = *cpSpan++;
                    }
                }

                break;
            }
        case (GENSWITCHKEY(8, 32)):
            {
                while (i--)
                {
                    *rpDestin++ = rpPal[*cpSpan++];
                }

                break;
            }
        case (GENSWITCHKEY(15, 32)):
            {

                while (i--)
                {
                    j = *(RwUInt16 *) cpSpan;

                    rpDestin->red = (RwUInt8) ((j >> 7) & 0xf8);
                    rpDestin->green = (RwUInt8) ((j >> 2) & 0xf8);
                    rpDestin->blue = (RwUInt8) ((j << 3) & 0xf8);
                    rpDestin->alpha = (RwUInt8) (0xff);

                    cpSpan += 2;
                    rpDestin++;
                }
                break;
            }
        case (GENSWITCHKEY(16, 32)):
            {
                while (i--)
                {
                    j = *(RwUInt16 *) cpSpan;

                    rpDestin->red = (RwUInt8) ((j >> 8) & 0xf8);
                    rpDestin->green = (RwUInt8) ((j >> 3) & 0xfc);
                    rpDestin->blue = (RwUInt8) ((j << 3) & 0xf8);
                    rpDestin->alpha = (RwUInt8) (0xff);

                    cpSpan += 2;
                    rpDestin++;
                }
                break;
            }
        case (GENSWITCHKEY(24, 32)):
            {
                if (bRGB)
                {
                    while (i--)
                    {
                        rpDestin->red = (*(cpSpan++));
                        rpDestin->green = (*(cpSpan++));
                        rpDestin->blue = (*(cpSpan++));
                        rpDestin->alpha = (0xff);

                        rpDestin++;
                    }
                }
                else
                {
                    while (i--)
                    {
                        rpDestin->blue = (*(cpSpan++));
                        rpDestin->green = (*(cpSpan++));
                        rpDestin->red = (*(cpSpan++));
                        rpDestin->alpha = (0xff);

                        rpDestin++;
                    }
                }

                break;
            }
        case (GENSWITCHKEY(32, 32)):
            {
                while (i--)
                {
                    rpDestin->red = (*cpSpan++);
                    rpDestin->green = (*cpSpan++);
                    rpDestin->blue = (*cpSpan++);
                    rpDestin->alpha = (0xff);

                    cpSpan++;
                    rpDestin++;
                }
                break;
            }
            /* Unhandled format or conversion */
        default:
            {
                RWERROR((E_RW_INVIMAGEDEPTH));
                RWRETURN(FALSE);
            }
    }

    RWRETURN(TRUE);
}

/*
 * \ref RwImageReadRAS is used to read a Sun Raster format from disk.  The
 * filename can either be absolute or relative to the directory the application
 * is running from.  The image search path is not used to find image files and
 * no gamma correction is applied to the image as it is read.
 *
 * This function has been made redundant with the introduction of the RtRAS
 * toolkit. RwImageReadRAS will be removed from future releases.
 *
 * \param imageName  Pointer to the filename of the RAS file to read.
 *
 * \return Returns an image on success, or NULL on failure.
 *
 * \see RtRASImageRead
 * \see RtRASImageWrite
 * \see RwImageWriteRAS
 * \see RwImageRead
 * \see RwImageWrite
 * \see RwImageRegisterImageFormat
 *
 */

RwImage            *
RwImageReadRAS(const RwChar * imageName)
{
    RWAPIFUNCTION(RWSTRING("RwImageReadRAS"));
    RWASSERT(imageName);

    RWERROR((E_RW_REDUNDANT_FUNCTION));

    {
        RwStream           *stream;

        stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, imageName);
        if (stream)
        {
            RwSunRasterHeader   srHeader;
            RwInt32             i, j;
            RwImage            *image;
            RwInt32             nOutDepth;
            RwBool              bRGB = FALSE;
            RwInt32             stride;
            RwUInt8            *scanLineBuf;
            RwUInt32            size;

            /* Test to make sure it's a RAS file */
            if (RwStreamRead(stream, &srHeader, sizeof(srHeader)) != sizeof(srHeader))
            {
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }

            /* Warning, Sun raster file is BIGENDIAN so only convert first word */
            RwMemNative(&srHeader, sizeof(RwInt32));

            /* Is this a sun raster ? */
            if (srHeader.magic != (RwInt32) rwSUNRASTERMAGIC)
            {
                /* It's not the right type */
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }

#ifdef rwLITTLEENDIAN
            /* Make the header little endian */
            {
                RwInt32            *npData = (RwInt32 *) & srHeader;

                for (i = sizeof(srHeader) / sizeof(RwInt32); i; i--)
                {
                    RWBYTESWAPINT32((*npData));

                    npData++;
                }
            }
#endif /* rwLITTLEENDIAN */

            /* Calculate length if necessary */
            if (srHeader.imageLength)
            {
                srHeader.imageLength = srHeader.width * srHeader.height;
            }

            /* Create the image and allocate it */
            switch (srHeader.bitsPerPixel)
            {
                case 1:
                case 4:
                    {
                        nOutDepth = 4;
                        break;
                    }
                case 8:
                    {
                        nOutDepth = 8;
                        break;
                    }
                case 24:
                case 32:
                    {
                        nOutDepth = 32;
                        break;
                    }
                default:
                    {
                        RWERROR((E_RW_INVIMAGEFORMAT));
                        RwStreamClose(stream, NULL);
                        RWRETURN((RwImage *)NULL);
                    }
            }

            /* Create the image */
            image = RwImageCreate(srHeader.width, srHeader.height,
                                  nOutDepth);
            if (!image)
            {
                RWRETURN((RwImage *)NULL);
            }

            /* Allocate memory */
            if (!RwImageAllocatePixels(image))
            {
                RwImageDestroy(image);
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }

            /* Handle on header start */
            if (srHeader.mapType && srHeader.mapLength)
            {
                RwUInt8            *cpClutBuffer;
                RwInt32             nNext;

                cpClutBuffer = (RwUInt8 *) RwMalloc(srHeader.mapLength);

                if (!cpClutBuffer)
                {
                    RWERROR((E_RW_NOMEM, srHeader.mapLength));
                    RwImageFreePixels(image);
                    RwImageDestroy(image);
                    RwStreamClose(stream, NULL);
                    RWRETURN((RwImage *)NULL);
                }

                /* Read in the palette */
                size = srHeader.mapLength;
                if (RwStreamRead(stream, cpClutBuffer, size) != size)
                {
                    RwImageFreePixels(image);
                    RwImageDestroy(image);
                    RwStreamClose(stream, NULL);
                    RWRETURN((RwImage *)NULL);
                }

                /* Convert */
                switch (srHeader.mapType)
                {
                    case 1:
                        {
                            /* Straight RGB palette */
                            nNext = srHeader.mapLength / 3;
                            for (i = 0; i < nNext; i++)
                            {
                                (image->palette)[i].red = cpClutBuffer[i];
                                (image->palette)[i].green =
                                    cpClutBuffer[i + nNext];
                                (image->palette)[i].blue =
                                    cpClutBuffer[i + (nNext << 1)];
                                (image->palette)[i].alpha = 0xff;
                            }
                            break;
                        }
                    case 2:
                        {
                            /* Greyscale palette */
                            for (i = 0; i < srHeader.mapLength; i++)
                            {
                                (image->palette)[i].red = cpClutBuffer[i];
                                (image->palette)[i].green = cpClutBuffer[i];
                                (image->palette)[i].blue = cpClutBuffer[i];
                                (image->palette)[i].alpha = 0xff;
                            }
                            break;
                        }
                    default:
                        {
                            break;
                        }
                }

                /* No longer needed */

                RwFree(cpClutBuffer);
            }
            else
            {
                /* Black and white image */
                if (srHeader.bitsPerPixel == 1)
                {
                    (image->palette)[0].red = 0;
                    (image->palette)[0].green = 0;
                    (image->palette)[0].blue = 0;
                    (image->palette)[0].alpha = 255;

                    (image->palette)[1].red = 255;
                    (image->palette)[1].green = 255;
                    (image->palette)[1].blue = 255;
                    (image->palette)[1].alpha = 255;

                    srHeader.mapLength = 2;
                }
                else
                {
                    /* Greyscale ramp */
                    if (srHeader.bitsPerPixel == 8)
                    {
                        for (i = 0; i < 256; i++)
                        {
                            (image->palette)[i].red = (RwUInt8) i;
                            (image->palette)[i].green = (RwUInt8) i;
                            (image->palette)[i].blue = (RwUInt8) i;
                        }
                        srHeader.mapLength = 256;
                    }
                }
            }

            /* Calculate stride */
            stride = (srHeader.width * srHeader.bitsPerPixel + 7) / 8;
            stride = (stride + 1) & -2;

            /* Allocate scanline buffer */
            scanLineBuf = (RwUInt8 *) RwMalloc(stride);

            if (!scanLineBuf)
            {
                RWERROR((E_RW_NOMEM, stride));
                RwImageFreePixels(image);
                RwImageDestroy(image);
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }

            switch (srHeader.type)
            {
                case 3:
                    {
                        bRGB = TRUE; /* its BGR */
                        /*FALLTHROUGH */
                    }
                case 0:
                    {
                        /*FALLTHROUGH */
                    }
                case 1:
                    {
                        for (i = 0; i < srHeader.height; i++)
                        {
                            if (RwStreamRead
                                (stream, scanLineBuf,
                                 stride) != (RwUInt32) stride)
                            {
                                RwFree(scanLineBuf);
                                RwImageFreePixels(image);
                                RwImageDestroy(image);
                                RwStreamClose(stream, NULL);
                                RWRETURN((RwImage *)NULL);
                            }

                            rwImageSetSpan(image, i, scanLineBuf,
                                           srHeader.bitsPerPixel, bRGB);
                        }
                        break;
                    }
                case 2:
                    {
                        RwUInt8             cPixel;
                        RwUInt8            *cpCur;

                        i = srHeader.height;
                        j = stride; /* X position */
                        cpCur = scanLineBuf;

                        while (i)
                        {
                            if (RwStreamRead(stream, &cPixel, 1) != 1)
                            {
                                RwFree(scanLineBuf);
                                RwImageFreePixels(image);
                                RwImageDestroy(image);
                                RwStreamClose(stream, NULL);
                                RWRETURN((RwImage *)NULL);
                            }

                            if (cPixel != 0x80)
                            {
                                (*cpCur) = cPixel;
                                cpCur++;

                                if (j-- <= 0)
                                {
                                    rwImageSetSpan(image, i,
                                                   scanLineBuf,
                                                   srHeader.
                                                   bitsPerPixel, bRGB);

                                    j = stride; /* X position */
                                    i--;
                                }
                            }
                            else
                            {
                                RwUInt8             cCount;
                                RwInt32             nCount;

                                if (RwStreamRead(stream, &cCount, 1) !=
                                    1)
                                {
                                    RwFree(scanLineBuf);
                                    RwImageFreePixels(image);
                                    RwImageDestroy(image);
                                    RwStreamClose(stream, NULL);
                                    RWRETURN((RwImage *)NULL);
                                }

                                if (cCount == 0)
                                {
                                    cPixel = 128;
                                }
                                else
                                {
                                    if (RwStreamRead(stream, &cPixel, 1)
                                        != 1)
                                    {
                                        RwFree(scanLineBuf);
                                        RwImageFreePixels(image);
                                        RwImageDestroy(image);
                                        RwStreamClose(stream, NULL);
                                        RWRETURN((RwImage *)NULL);
                                    }
                                }

                                nCount = cCount;

                                while (nCount-- >= 0)
                                {
                                    (*cpCur) = cPixel;
                                    cpCur++;

                                    if (j-- <= 0)
                                    {
                                        rwImageSetSpan(image, i,
                                                       scanLineBuf,
                                                       srHeader.
                                                       bitsPerPixel,
                                                       bRGB);
                                        j = stride; /* X position */
                                        i--;
                                    }
                                }
                            }
                        }

                        break;
                    }
                default:
                    {
                        RWERROR((E_RW_INVIMAGEFORMAT));
                        RwFree(scanLineBuf);
                        RwImageFreePixels(image);
                        RwImageDestroy(image);
                        RwStreamClose(stream, NULL);
                        RWRETURN((RwImage *)NULL);
                    }
            }

            /* No longer need the scan line */
            RwFree(scanLineBuf);

            /* Finished with this */
            RwStreamClose(stream, NULL);

            /* All done */
            RWRETURN(image);
        }

        /* Couldn't find the file */
        RWRETURN((RwImage *)NULL);
    }
}

static RwImage     *
rwImageWriteRAS4or8(RwImage * image, RwStream * stream, RwInt32 stride)
{
    RwInt32             i;
    RwUInt8            *cpCur;
    RwRGBA              unGammaPalette[256];
    RwRGBA             *palToWrite;
    RwInt32             palSize;

    RWFUNCTION(RWSTRING("rwImageWriteRAS4or8"));
    RWASSERT(image);
    RWASSERT(stream);
    RWASSERT(stride);

    palSize = (1 << image->depth);

    /********************* 8 bit raster *************************/

    /* Assume ungammacorrected first */
    palToWrite = image->palette;
    if (image->flags & rwIMAGEGAMMACORRECTED)
    {
        /* Write out the palette - ungamma correct it first */
        _rwImageGammaUnCorrectArrayOfRGBA(unGammaPalette,
                                          image->palette, palSize);
        palToWrite = unGammaPalette;
    }

    for (i = 0; i < palSize; i++)
    {
        if (!RwStreamWrite(stream, &palToWrite[i].red, sizeof(RwUInt8)))
        {
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }
    }
    for (i = 0; i < palSize; i++)
    {
        if (!RwStreamWrite(stream, &palToWrite[i].green, sizeof(RwUInt8)))
        {
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }
    }
    for (i = 0; i < palSize; i++)
    {
        if (!RwStreamWrite(stream, &palToWrite[i].blue, sizeof(RwUInt8)))
        {
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }
    }

    /* Write the actual data */
    cpCur = image->cpPixels;

    for (i = 0; i < image->height; i++)
    {
        if (!RwStreamWrite(stream, cpCur, stride))
        {
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }

        cpCur += image->stride;
    }

    RWRETURN(image);
}

static RwImage     *
rwImageWriteRAS32(RwImage * image, RwStream * stream, RwInt32 stride)
{
    RwInt32             i, j;
    RwInt32             nExtra = stride % 3;

    RWFUNCTION(RWSTRING("rwImageWriteRAS32"));
    RWASSERT(image);
    RWASSERT(stream);
    RWASSERT(stride);

    /********************* 32 bit raster ************************/

    /* Write the actual data */
    for (i = 0; i < image->height; i++)
    {
        RwUInt8             caRGB[3];
        RwRGBA             *rpCur = (RwRGBA *) (image->cpPixels + (i * image->stride));

        for (j = 0; j < (stride / 3); j++)
        {
            if (image->flags & rwIMAGEGAMMACORRECTED)
            {
                RwRGBA              unGammaPixel;

                /* Gamma correction needed */
                _rwImageGammaUnCorrectArrayOfRGBA(&unGammaPixel,
                                                  &rpCur[j], 1);
                caRGB[2] = unGammaPixel.red;
                caRGB[1] = unGammaPixel.green;
                caRGB[0] = unGammaPixel.blue;
            }
            else
            {
                /* No ungamma correction needed */
                caRGB[2] = rpCur[j].red;
                caRGB[1] = rpCur[j].green;
                caRGB[0] = rpCur[j].blue;
            }

            if (!RwStreamWrite(stream, caRGB, 3))
            {
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }
        }
        if (nExtra)
        {
            if (!RwStreamWrite(stream, caRGB, nExtra))
            {
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }
        }
    }

    RWRETURN(image);
}

/*
 * \ref RwImageWriteRAS is used to write an image as a RAS file.
 *
 * The image file name can either be absolute or relative to the directory the
 * application is running from.
 *
 * Note that if the image has been gamma corrected using \ref RwImageGammaCorrect,
 * the gamma correction is removed from the image before writing it to disk.
 * Inverse gamma correction is performed using the current gamma correction value.
 *
 * This function has been made redundant with the introduction of the RtRAS
 * toolkit. RwImageWriteRAS will be removed from future releases.
 *
 * \param image  Pointer to the image to write
 * \param imageName  Pointer to the filename of the RAS file to write.
 *
 * \return Returns an image on success, or NULL on failure.
 *
 * \see RtRASImageRead
 * \see RtRASImageWrite
 * \see RwImageReadRAS
 * \see RwImageRead
 * \see RwImageWrite
 * \see RwImageRegisterImageFormat
 *
 */
RwImage            *
RwImageWriteRAS(RwImage * image, const RwChar * imageName)
{
    RWAPIFUNCTION(RWSTRING("RwImageWriteRAS"));
    RWASSERT(image);
    RWASSERT(imageName);

    RWERROR((E_RW_REDUNDANT_FUNCTION));

    {
        RwStream           *stream;

        stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, imageName);
        if (stream)
        {
            RwSunRasterHeader   srHeader;
            RwInt32             stride;
            RwInt32             i;

            switch (image->depth)
            {
                case 4:
                case 8:
                    {
                        srHeader.bitsPerPixel = image->depth;
                        srHeader.mapLength = (1 << image->depth) * 3;
                        break;
                    }
                case 32:
                    {
                        srHeader.bitsPerPixel = 24;
                        srHeader.mapLength = 0;
                        break;
                    }
                default:
                    {
                        RWERROR((E_RW_INVIMAGEDEPTH));
                        RWRETURN((RwImage *)NULL);
                    }
            }

            /* Set up the header */
            srHeader.width = image->width;
            srHeader.height = image->height;
            srHeader.type = 1; /* No compression ! */
            srHeader.mapType = 1; /* RGB ... */

            /* Calculate the stride */
            stride = (srHeader.width * srHeader.bitsPerPixel + 7) / 8;
            stride = (stride + 1) & -2;

            srHeader.imageLength = stride * srHeader.height;

            /* Put in correct format */
            {
                RwInt32            *npData = (RwInt32 *) & srHeader;

                for (i = sizeof(srHeader) / sizeof(RwInt32); i; i--)
                {
                    RWBYTESWAPINT32((*npData));

                    npData++;
                }
            }

            /* Don't flip this around */
            srHeader.magic = rwSUNRASTERMAGIC;

            /* Ready to write */
            if (!RwStreamWrite
                (stream, &srHeader, sizeof(RwSunRasterHeader)))
            {
                RWRETURN((RwImage *)NULL);
            }

            /* Deal with the data */
            switch (image->depth)
            {
                case 4:
                case 8:
                    {
                        image =
                            rwImageWriteRAS4or8(image, stream, stride);
                        break;
                    }

                case 32:
                    {
                        image =
                            rwImageWriteRAS32(image, stream, stride);
                        break;
                    }
            }

            RwStreamClose(stream, NULL);
            RWRETURN(image);
        }

        /* Failed to open the stream */
        RWRETURN((RwImage *)NULL);
    }
}

/* BMP file  header format ------------------------------------------- */

typedef struct _RwBmpHeader RwBmpHeader;
struct _RwBmpHeader
{
        RwUInt8             caDummy[2];
        RwInt32             nFileSize;
        RwInt16             nResv1;
        RwInt16             nResv2;
        RwInt32             nOffset;

        RwInt32             nHeadSize;
        RwInt32             width;
        RwInt32             height;
        RwInt16             nPlanes;
        RwInt16             nBitsPixel;

        RwInt32             nCompress;
        RwInt32             nSize;
        RwInt32             nPixMeterX;
        RwInt32             nPixMeterY;
        RwInt32             nColsUsed;
        RwInt32             nImportantCols;
};

#define rwBMPMAGIC          0x00004d42
#define rwBMPMAGICMASK      0x0000ffff

/*
 * \ref RwImageReadBMP is used to read a BMP file to create an RwImage.
 *
 * The file name can either be absolute or relative to the directory the
 * application is running from.  The image search path is not used to find
 * image files and no gamma correction is applied to the image as it is read.
 *
 * This function has been made redundant with the introduction of the RtBMP
 * toolkit. RwImageReadBMP will be removed from future releases.
 *
 * \param imageName  Pointer to the filename of the BMP file to read.
 *
 * \return Returns an image on success, or NULL on failure.
 *
 * \see RtBMPImageRead
 * \see RtBMPImageWrite
 * \see RwImageWriteBMP
 * \see RwImageRead
 * \see RwImageWrite
 * \see RwImageRegisterImageFormat
 */

RwImage *
RwImageReadBMP(const RwChar *imageName)
{
    RwStream           *stream;

    RWAPIFUNCTION(RWSTRING("RwImageReadBMP"));
    RWASSERT(imageName);

    RWERROR((E_RW_REDUNDANT_FUNCTION));

    stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, imageName);
    if (stream)
    {
        RwUInt32            marker;
        RwImage            *image;
        RwBmpHeader         bhHeader;
        RwUInt8             caClutBuffer[256 * 4];
        RwInt32             position;
        RwInt32             stride;
        RwUInt8            *scanLineBuf;
        RwInt32             nX, y;
        RwUInt32            size;
        RwUInt32            targetDepth;

        /* Find out what kind of image we have */
        if (RwStreamRead(stream, &marker, sizeof(marker)) != sizeof(marker))
        {
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }

        RwMemNative(&marker, sizeof(marker));

        /* Is this a bmp file ? */
        if ((marker & rwBMPMAGICMASK) != rwBMPMAGIC)
        {
            /* Not the right type */
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }

        /* Read the header - halfword address so we can read longs from long boundaries */
        if (RwStreamRead(stream, &caClutBuffer[2], 14) != 14)
        {
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }
        position = 18;

        bhHeader.nColsUsed = 0;

        bhHeader.nOffset = RWRWINT32GETLITTLE(caClutBuffer, 8);
        bhHeader.nHeadSize = RWRWINT32GETLITTLE(caClutBuffer, 12);

        size = bhHeader.nHeadSize - 4;
        if (RwStreamRead(stream, &caClutBuffer[0], size) != size)
        {
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }
        position += size;

        if (bhHeader.nHeadSize == 12)
        {
            bhHeader.width = RWRWINT16GETLITTLE(caClutBuffer, 0);
            bhHeader.height = RWRWINT16GETLITTLE(caClutBuffer, 2);
            bhHeader.nPlanes = RWRWINT16GETLITTLE(caClutBuffer, 4); /* assert planes == 1 */
            bhHeader.nBitsPixel = RWRWINT16GETLITTLE(caClutBuffer, 6);
            bhHeader.nColsUsed = 0;
            bhHeader.nCompress = 0;
        }
        else
        {
            bhHeader.width = RWRWINT32GETLITTLE(caClutBuffer, 0);
            bhHeader.height = RWRWINT32GETLITTLE(caClutBuffer, 4);
            bhHeader.nPlanes = (RwInt16)RWRWINT16GETLITTLE(caClutBuffer, 8);
            bhHeader.nBitsPixel = (RwInt16)RWRWINT16GETLITTLE(caClutBuffer, 10);
            bhHeader.nCompress = RWRWINT32GETLITTLE(caClutBuffer, 12);
            bhHeader.nColsUsed = RWRWINT32GETLITTLE(caClutBuffer, 28);

            /* we cannot handle 4 bit RLE */
            if (bhHeader.nCompress == 2)
            {
                RWERROR((E_RW_INVIMAGEFORMAT));
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }
        }

        /* Check sanity of of ColsUsed field */
        if ((bhHeader.nColsUsed <= 0) ||
            (bhHeader.nColsUsed > (1 << bhHeader.nBitsPixel)))
        {
            bhHeader.nColsUsed = (1 << bhHeader.nBitsPixel);
            bhHeader.nImportantCols = bhHeader.nColsUsed;
        }

        /* Create the image */
        switch (bhHeader.nBitsPixel)
        {
            case (1):
            case (4):
                {
                    targetDepth = 4;
                    break;
                }
            case (8):
                {
                    targetDepth = 8;
                    break;
                }
            case (15):
            case (16):
            case (24):
            case (32):
            default:
                {
                    targetDepth = 32;
                    break;
                }
        }

        /* Allocate the image */
        image = RwImageCreate(bhHeader.width, bhHeader.height, targetDepth);
        if (!image)
        {
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }

        if (!RwImageAllocatePixels(image))
        {
            RwImageDestroy(image);
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }

        /* Deal with a palette? */
        if (bhHeader.nBitsPixel <= 8)
        {
            RwRGBA             *rpPal = rwImageGetPalette(image);

            if (bhHeader.nHeadSize == 12)
            {
                /* OS/2.1 format */
                size = bhHeader.nColsUsed * 3;
                if (RwStreamRead(stream, caClutBuffer, size) != size)
                {
                    RwStreamClose(stream, NULL);
                    RWRETURN((RwImage *)NULL);
                }
                position += size;

                for (nX = 0; nX < bhHeader.nColsUsed; nX++)
                {
                    rpPal[nX].red = caClutBuffer[nX * 3 + 2];
                    rpPal[nX].green = caClutBuffer[nX * 3 + 1];
                    rpPal[nX].blue = caClutBuffer[nX * 3 + 0];
                    rpPal[nX].alpha = 0xff;
                }
            }
            else if (bhHeader.nHeadSize == 40)
            {
                /* Win3.x format */
                size = bhHeader.nColsUsed * 4;
                if (RwStreamRead(stream, caClutBuffer, size) !=
                    size)
                {
                    RwStreamClose(stream, NULL);
                    RWRETURN((RwImage *)NULL);
                }
                position += size;

                for (nX = 0; nX < bhHeader.nColsUsed; nX++)
                {
                    rpPal[nX].red = caClutBuffer[nX * 4 + 2];
                    rpPal[nX].green = caClutBuffer[nX * 4 + 1];
                    rpPal[nX].blue = caClutBuffer[nX * 4 + 0];
                    rpPal[nX].alpha = 0xff;
                }
            }
            else
            {
                /* Beats me then */
                RWERROR((E_RW_INVIMAGEFORMAT));
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }
        }

        /* Skip to the raster data itself */
        if (!RwStreamSkip(stream, bhHeader.nOffset - position))
        {
            RwImageDestroy(image);
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }

        stride = (bhHeader.width * bhHeader.nBitsPixel + 7) / 8;
        stride = (stride + 3) & -4;

        /* Allocate a scanline */
        scanLineBuf =
            (RwUInt8 *) RwMalloc(((bhHeader.width + 7) & -8) * 3);
        if (!scanLineBuf)
        {
            RWERROR(
                    (E_RW_NOMEM,
                     (((bhHeader.width + 7) & -8) * 3)));
            RwImageDestroy(image);
            RwStreamClose(stream, NULL);
            RWRETURN((RwImage *)NULL);
        }

        /* Read the image */
        /* Check if 24 bit non compressed */
        if ((bhHeader.nCompress == 0) || (bhHeader.nBitsPixel == 24))
        {
            for (y = 0; y < bhHeader.height; y++)
            {
                if (RwStreamRead(stream, scanLineBuf, stride) !=
                    (RwUInt32) stride)
                {
                    RwFree(scanLineBuf);
                    RwImageDestroy(image);
                    RwStreamClose(stream, NULL);
                    RWRETURN((RwImage *)NULL);
                }

                rwImageSetSpan(image, (bhHeader.height - 1) - y,
                               scanLineBuf, bhHeader.nBitsPixel,
                               FALSE);
            }
        }
        else
            if (bhHeader.nCompress == 1)
            {
                for (y = 0; y < bhHeader.height; y++)
                {
                    RwBool              bEofLine;

                    /* 8-bit RLE */
                    nX = 0;
                    bEofLine = FALSE;
                    while (!bEofLine)
                    {
                        RwUInt8             caValues[2];

                        size = sizeof(caValues);
                        if (RwStreamRead(stream, caValues, size) != size)
                        {
                            RwFree(scanLineBuf);
                            RwImageDestroy(image);
                            RwStreamClose(stream, NULL);
                            RWRETURN((RwImage *)NULL);
                        }

                        if (caValues[0] != 0)
                        {
                            /* repeat group - duplicate the byte */
                            memset(&scanLineBuf[nX], caValues[1], caValues[0]);
                            nX += caValues[0];
                        }
                        else
                        {
                            if (caValues[1] > 2)
                            {
                                /* literal group */
                                size = caValues[1];
                                if (RwStreamRead(stream, &scanLineBuf[nX],size) != size)
                                {
                                    RwFree(scanLineBuf);
                                    RwImageDestroy(image);
                                    RwStreamClose(stream, NULL);
                                    RWRETURN((RwImage *)NULL);
                                }
                                nX += caValues[1];

                                if (caValues[1] & 1) /* padding byte */
                                {
                                    size = sizeof(RwUInt8);
                                    if (RwStreamRead(stream, caValues, size) != size)
                                    {
                                        RwFree(scanLineBuf);
                                        RwImageDestroy(image);
                                        RwStreamClose(stream, NULL);
                                        RWRETURN((RwImage *)NULL);
                                    }
                                }
                            }
                            else if (caValues[1] == 0)
                            {
                                /* special group - end of line */
                                bEofLine = TRUE;
                            }
                            else if (caValues[1] == 1)
                            {
                                /* special group - end of image */
                                bEofLine = TRUE;
                                y = bhHeader.height - 1;
                            }
                            else
                            {
                                /* we cannot handle deltas (type 2) */
                                /* or any other type */
                                RwFree(scanLineBuf);
                                RwImageDestroy(image);
                                RwStreamClose(stream, NULL);
                                RWRETURN((RwImage *)NULL);
                            }
                        }
                    }

                    /* Save the data */
                    rwImageSetSpan(image, (bhHeader.height - 1) - y,
                                   scanLineBuf, bhHeader.nBitsPixel,
                                   TRUE);

                    /* Onto the next line */
                }
            }
            else
            {
                /* unknown compression */
                RWERROR((E_RW_INVIMAGEFORMAT));
                RwFree(scanLineBuf);
                RwImageDestroy(image);
                RwStreamClose(stream, NULL);
                RWRETURN((RwImage *)NULL);
            }

        /* No longer need the scan line */
        RwFree(scanLineBuf);

        /* Done with this now */
        RwStreamClose(stream, NULL);

        /* Loaded the image */
        RWRETURN(image);
    }

    /* Can't find the file */
    RWRETURN((RwImage *)NULL);
}

/*
 ***********************************************************************
 * BMP output
 */

/*
 * Loosely based on
 * netpbm-7dec1993/ppm/ppmtobmp.c
 * See
 * ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM
 */

#define rwPutUInt8(stream, v_int)                               \
MACRO_START                                                     \
{                                                               \
    RwUInt8 v = (RwUInt8) (v_int);                              \
                                                                \
    RwStreamWrite(stream, &v, sizeof(v));                       \
}                                                               \
MACRO_STOP

#ifdef rwLITTLEENDIAN

#define rwPutUInt16(stream, v_int)                              \
MACRO_START                                                     \
{                                                               \
    RwUInt16 v = (RwUInt16) (v_int);                            \
                                                                \
    RwStreamWrite(stream, &v, sizeof(v)) ;                      \
}                                                               \
MACRO_STOP

#else /* rwLITTLEENDIAN */

#define rwPutUInt16(stream, v_int)                              \
MACRO_START                                                     \
{                                                               \
    RwUInt16 v = (RwUInt16) (v_int);                            \
                                                                \
    RWBYTESWAPINT16(v);                                         \
    RwStreamWrite(stream, &v, sizeof(v)) ;                      \
}                                                               \
MACRO_STOP

#endif /* rwLITTLEENDIAN */

#define rwPutUInt32(stream, v_int)                              \
MACRO_START                                                     \
{                                                               \
    RwUInt32 v = (RwUInt32) (v_int);                            \
                                                                \
    RwStreamWriteInt(stream, (const RwInt32 *)&v, sizeof(v)) ;  \
}                                                               \
MACRO_STOP

#define rwPutRwPixel24(stream, packed)                          \
MACRO_START                                                     \
{                                                               \
    RwStreamWrite(stream, packed, sizeof(packed)) ;             \
}                                                               \
MACRO_STOP
static              RwUInt32
BMPlenfileheader(int category)
{
    RwUInt32            result = 0;

    RWFUNCTION(RWSTRING("BMPlenfileheader"));

    switch (category)
    {
        case C_WIN:
            {
                result = 14;
                break;
            }
        case C_OS2:
            {
                result = 14;
                break;
            }
        default:
            {
                RWMESSAGE((RWSTRING("error - BMPlenfileheader")));
                break;
            }

    }
    RWRETURN(result);
}

static              RwUInt32
BMPleninfoheader(int category)
{
    RwUInt32            result = 0;

    RWFUNCTION(RWSTRING("BMPleninfoheader"));

    switch (category)
    {
        case C_WIN:
            {
                result = 40;
                break;
            }
        case C_OS2:
            {
                result = 12;
                break;
            }
        default:
            {
                RWMESSAGE((RWSTRING("error - BMPleninfoheader")));
                break;
            }
    }
    RWRETURN(result);
}

static   RwUInt32
BMPlenrgbtable(int  __RWUNUSED__ category,
               RwUInt32  __RWUNUSED__ bitcount)
{
    RwUInt32            lenrgb = 0;

    RWFUNCTION(RWSTRING("BMPlenrgbtable"));

    RWRETURN(lenrgb);
}

/*
 * length, in bytes, of a line of the image
 *
 * Evidently each row is padded on the right as needed to make it a
 * multiple of 4 bytes long.  This appears to be true of both
 * OS/2 and Windows BMP files.
 */
static              RwUInt32
BMPlenline(int category, RwUInt32 bitcount, RwUInt32 x)
{
    RwUInt32            result = 0;
    RwUInt32            bitsperline;

    RWFUNCTION(RWSTRING("BMPlenline"));

    if ((C_WIN == category) || (C_OS2 == category))
    {
        bitsperline = x * bitcount;

        /*
         * if bitsperline is not a multiple of 32, then round
         * bitsperline up to the next multiple of 32.
         */
        if ((bitsperline % 32) != 0)
        {
            bitsperline += (32 - (bitsperline % 32));
        }

        if ((bitsperline % 32) != 0)
        {
            RWMESSAGE((RWSTRING("error - BMPlenline")));
        }
        else
        {
            /* number of bytes per line == bitsperline/8 */
            result = bitsperline >> 3;
        }

    }
    else
    {
        RWMESSAGE((RWSTRING("error - BMPlenline")));
    }

    RWRETURN(result);
}

/* return the number of bytes used to store the image bits */
static              RwUInt32
BMPlenbits(int category, RwUInt32 bitcount, RwUInt32 x, RwUInt32 y)
{
    RWFUNCTION(RWSTRING("BMPlenbits"));

    RWRETURN(y * BMPlenline(category, bitcount, x));
}

/* return the offset to the BMP image bits */
static              RwUInt32
BMPoffbits(int category, RwUInt32 bitcount)
{
    RwUInt32            result;

    RWFUNCTION(RWSTRING("BMPoffbits"));

    result = BMPlenfileheader(category) +
        BMPleninfoheader(category) + BMPlenrgbtable(category, bitcount);

    RWRETURN(result);
}

/* return the size of the BMP file in bytes */
static              RwUInt32
BMPlenfile(int category, RwUInt32 bitcount, RwUInt32 x, RwUInt32 y)
{
    RwUInt32            result;

    RWFUNCTION(RWSTRING("BMPlenfile"));

    result = BMPoffbits(category, bitcount) +
        BMPlenbits(category, bitcount, x, y);

    RWRETURN(result);
}

/*
 * BMP writing
 */

/*
 * returns the number of bytes written, or -1 on error.
 */
static              RwInt32
BMPwritefileheader(RwStream * stream,
                   RwInt32 category, RwUInt32 bitcount, RwUInt32 x,
                   RwUInt32 y)
{
    RWFUNCTION(RWSTRING("BMPwritefileheader"));

    rwPutUInt8(stream, 'B');
    rwPutUInt8(stream, 'M');

    /* cbSize */
    rwPutUInt32(stream, (RwUInt32) BMPlenfile(category, bitcount, x, y));

    /* xHotSpot */
    rwPutUInt16(stream, 0);

    /* yHotSpot */
    rwPutUInt16(stream, 0);

    /* offBits */
    rwPutUInt32(stream, (RwUInt32) BMPoffbits(category, bitcount));

    RWRETURN(14);
}

/*
 * returns the number of bytes written, or -1 on error.
 */
static              RwInt32
BMPwriteinfoheader(RwStream * stream,
                   RwInt32 category, RwUInt32 bitcount, RwUInt32 x,
                   RwUInt32 y)
{
    RwInt32             cbFix = 0;

    RWFUNCTION(RWSTRING("BMPwriteinfoheader"));

    /* cbFix */
    switch (category)
    {
        case C_WIN:
            {
                cbFix = 40;
                rwPutUInt32(stream, cbFix);

                /* cx */
                rwPutUInt32(stream, (RwUInt32) x);
                /* cy */
                rwPutUInt32(stream, (RwUInt32) y);
                /* cPlanes */
                rwPutUInt16(stream, 1);
                /* cBitCount */
                rwPutUInt16(stream, (RwUInt16) bitcount);

                /*
                 * We've written 16 bytes so far, need to write 24 more
                 * for the required total of 40.
                 */

                rwPutUInt32(stream, 0);
                rwPutUInt32(stream, 0);
                rwPutUInt32(stream, 0);
                rwPutUInt32(stream, 0);
                rwPutUInt32(stream, 0);
                rwPutUInt32(stream, 0);
                break;
            }
        case C_OS2:
            {
                cbFix = 12;
                rwPutUInt32(stream, cbFix);

                /* cx */
                rwPutUInt16(stream, (RwUInt16) x);
                /* cy */
                rwPutUInt16(stream, (RwUInt16) y);
                /* cPlanes */
                rwPutUInt16(stream, 1);
                /* cBitCount */
                rwPutUInt16(stream, (RwUInt16) bitcount);
                break;
            }
        default:
            {
                RWMESSAGE((RWSTRING("error - BMPwriteinfoheader")));
                break;
            }
    }

    RWRETURN(cbFix);
}

/*
 * returns the number of bytes written, or -1 on error.
 */
static              RwInt32
BMPwriterow(RwStream * stream, RwImage * image,
            RwInt32 y, RwUInt32 cx,
            RwUInt32 __RWUNUSED__ bpp , RwPixel24 * packed)
{
    RwRGBA             *pixel = (RwRGBA *)NULL;
    RwPixel24           staticPacked;
    RwUInt32            nbyte = 0;
    RwUInt32            x;

    RWFUNCTION(RWSTRING("BMPwriterow"));

    /* If there's an array of pixels we batch up and burst write */
    if (packed)
    {
        /* Write into the array, then write whole array */
        for (x = 0; x < cx; x++)
        {
            pixel = (RwRGBA *)rwImageGetPixel(image, x, y);
            packed[x][2] = pixel->red;
            packed[x][1] = pixel->green;
            packed[x][0] = pixel->blue;
            nbyte += sizeof(RwPixel24);
        }
        RwStreamWrite(stream, packed, nbyte);
    }
    else
    {
        /* Write a pixel at a time */
        for (x = 0; x < cx; x++)
        {
            pixel = (RwRGBA *) rwImageGetPixel(image, x, y);
            RWASSERT((RwRGBA *)(RwRGBA *)NULL != pixel);
            staticPacked[2] = pixel->red;
            staticPacked[1] = pixel->green;
            staticPacked[0] = pixel->blue;
            rwPutRwPixel24(stream, staticPacked);
            nbyte += sizeof(staticPacked);
        }
    }

    /*
     * Make sure we write a multiple of 4 bytes.
     */
    while (nbyte & 3)
    {
        rwPutUInt8(stream, 0);
        nbyte++;
    }

    RWRETURN(nbyte);
}

/*
 * returns the number of bytes written, or -1 on error.
 */
static              RwInt32
BMPwritebits(RwStream * stream,
             RwUInt32 cx,
             RwUInt32 cy, RwUInt32 cBitCount, RwImage * image)
{
    RwInt32             nbyte = 0;
    RwInt32             y;
    RwPixel24          *packed = (RwPixel24 *)NULL;

    RWFUNCTION(RWSTRING("BMPwritebits"));

    RWASSERT(24 <= cBitCount);

    /* Try to malloc an array of pixels for the row */
    packed = (RwPixel24 *) RwMalloc(sizeof(RwPixel24) * cx);

    /* The picture is stored bottom line first, top line last */
    for (y = cy - 1; y >= 0; y--)
    {
        RwInt32             rc;

        rc = BMPwriterow(stream, image, y, cx, cBitCount, packed);

        if (rc == -1)
        {
            RWMESSAGE(
                      (RWSTRING("error - couldn't write row %d"),
                       (int) y));
        }

        RWASSERT(!(rc & 3));

        nbyte += rc;
    }

    /* Free the array */
    if (packed)
    {
        RwFree(packed);
    }

    RWRETURN(nbyte);
}

/*
 * Write a BMP file of the given category.
 *
 * Note that we must have 'colors' in order to know exactly how many
 * colors are in the R, G, B, arrays.  Entries beyond those in the
 * arrays are undefined.
 */
static void
BMPEncode(RwStream * stream,
          const RwInt32 category, const RwUInt32 x, const RwUInt32 y,
          RwImage * image)
{
    const RwUInt32      bpp = 24; /* bits per pixel */
    RwUInt32            nbyte = 0;

    RWFUNCTION(RWSTRING("BMPEncode"));

    nbyte += BMPwritefileheader(stream, category, bpp, x, y);
    nbyte += BMPwriteinfoheader(stream, category, bpp, x, y);

    if (nbyte != (BMPlenfileheader(category)
                  + BMPleninfoheader(category)
                  + BMPlenrgbtable(category, bpp)))
    {
        RWMESSAGE((RWSTRING("error - BMPEncode")));
    }

    nbyte += BMPwritebits(stream, x, y, bpp, image);
    if (nbyte != BMPlenfile(category, bpp, x, y))
    {
        RWMESSAGE((RWSTRING("error - BMPEncode")));
    }

    RWRETURNVOID();
}

/*
 * \ref RwImageWriteBMP is used to write an image as a BMP file.
 *
 * The image file name can either be absolute or relative to the directory the
 * application is running from.
 *
 * Note that if the image has been gamma corrected using \ref RwImageGammaCorrect,
 * the gamma correction is not removed from the image before writing it to disk.
 *
 * This function has been made redundant with the introduction of the RtBMP
 * toolkit. RwImageWriteBMP will be removed from future releases.
 *
 * \param image  Pointer to the image to write
 * \param imageName  Pointer to the filename of the BMP file to write.
 *
 * \return Returns an image on success, or NULL on failure.
 *
 * \see RtBMPImageRead
 * \see RtBMPImageWrite
 * \see RwImageReadBMP
 * \see RwImageRead
 * \see RwImageWrite
 * \see RwImageRegisterImageFormat
 */
RwImage            *
RwImageWriteBMP(RwImage * image, const RwChar * imageName)
{
    RwStream           *stream;

    RWAPIFUNCTION(RWSTRING("RwImageWriteBMP"));
    RWASSERT(image);
    RWASSERT(imageName);

    RWERROR((E_RW_REDUNDANT_FUNCTION));

    stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, imageName);
    if (stream)
    {
        const RwInt32       category = C_WIN;
        const RwUInt32      rows = rwImageGetHeight(image);
        const RwUInt32      cols = rwImageGetWidth(image);

        BMPEncode(stream, category, cols, rows, image);

        RwStreamClose(stream, NULL);
        RWRETURN(image);
    }

    /* Failed to open the stream */
    RWRETURN((RwImage *)NULL);
}

#endif /* (rwLIBRARYCURRENTVERSION >= 0x31100) */
