
//***************************************************
// Generalised Bitmap Module C++ adapter for GBM plugins
//
// Requires: GBM.DLL version 1.35 or higher (with multipage support)
//
// Copyright (C) 2006-2008 Heiko Nitzsche
//
//   This software is provided 'as-is', without any express or implied
//   warranty.  In no event will the author be held liable for any damages
//   arising from the use of this software.
//
//   Permission is granted to anyone to use this software for any purpose,
//   including commercial applications, and to alter it and redistribute it
//   freely, subject to the following restrictions:
//
//   1. The origin of this software must not be misrepresented; you must not
//      claim that you wrote the original software. If you use this software
//      in a product, an acknowledgment in the product documentation would be
//      appreciated but is not required.
//   2. Altered source versions must be plainly marked as such, and must not be
//      misrepresented as being the original software.
//   3. This notice may not be removed or altered from any source distribution.
//
//***************************************************

#ifdef __IBMCPP__
  #pragma strings( readonly )
#endif

#include "os2defs.h"

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

#include "GbmRenderer.hpp"

#include "gbm.h"
#include "gbmmem.h"
#include "gbmscale.h"
#include "gbmmir.h"
#include "gbmrect.h"

// ---------------------------------------------------------

GbmRenderer::GbmRenderer()
    : renderPage(NULL),
      unscaledWidth(0),
      unscaledHeight(0),
      unscaledBpp(0),
      mScaleFilter(GbmRenderer::ScalerType_SIMPLE)
{
}

// ---------------------------------------------------------

GbmRenderer::~GbmRenderer()
{
    renderPage = NULL;
}

// ---------------------------------------------------------

void GbmRenderer::setRenderPage(GbmDocumentPage * page)
{
    if (page != renderPage)
    {
        // clear old render cache
        cacheDescription.clear();
        renderPage = page;
    }
}

GbmDocumentPage * GbmRenderer::getRenderPage() const
{
    return renderPage;
}

// ---------------------------------------------------------

void GbmRenderer::setScalerType(const GbmRenderer::ScalerType filter)
{
    if (mScaleFilter != filter)
    {
       // clear old render cache to force recalculation
       cacheDescription.clear();
    }
    mScaleFilter = filter;
}

// ---------------------------------------------------------

GbmRenderer::ScalerType GbmRenderer::getScalerType() const
{
    return mScaleFilter;
}

// ---------------------------------------------------------

/**
 * @param src_x         x coordinate of upper left corner
 * @param src_y         y coordinate of upper left corner
 * @param src_width     width of rectangle to render
 * @param src_height    height of rectangle to render
 * @param scale_x       scale specified as pixels per point (width)
 * @param scale_y       scale specified as pixels per point (height)
 * @param rect          RECTL structure, defines render area on HPS
 * @param hps           handle of presentation space to render into
 *
 * 1. Scale the document to match the specified pixels per point
 * 2. Render the rectangle given by the upper left corner at
 *    (src_x, src_y) and src_width and src_height.
 **/

void GbmRenderer::renderToHPS(long   src_x    , long   src_y,
                              long   src_width, long   src_height,
                              double scale_x  , double scale_y,
                              const  RECTL  & render_area,
                              const  HPS      hps) throw ( GbmException )
{
    if (hps == NULLHANDLE)
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Missing HPS handle");
    }
    if ((src_y < 0) || (src_x < 0) || (src_width <= 0) || (src_height <= 0))
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Bad render options");
    }

    BMPHEADER bmpHeader;
    GBMRGB    gbmrgb[0x100] = { 0 };
    BOOLEAN   isGray        = FALSE;
    copyBmpHeaderAndGetPaletteAsGBMRGB(&bmpHeader, gbmrgb, &isGray);

    // update the render cache based on scale factors
    prepareRenderCache(scale_x, scale_y);

    bmpHeader.bmp2.cx        = cacheDescription.width;
    bmpHeader.bmp2.cy        = cacheDescription.height;
    bmpHeader.bmp2.cBitCount = cacheDescription.bpp;
    bmpHeader.bmp2.cclrUsed  = cacheDescription.bpp > 8 ? 0 : (1 << cacheDescription.bpp);
    bmpHeader.bmp2.cbImage   = (((cacheDescription.width * cacheDescription.bpp + 31)/32) * 4)
                               * cacheDescription.height;

    // update the bitmap header and the color palette to be returned
    copyGBMRGB2BmpHeader(cacheDescription.gbmrgb, &bmpHeader);

    // protect against buffer overrun due to rounding errors
    if (src_x + src_width > cacheDescription.width)
    {
        src_width = cacheDescription.width - src_x;
    }
    if (src_y + src_height > cacheDescription.height)
    {
        src_height = cacheDescription.height - src_y;
    }

    // Draw the bitmap
    // ===============

    // set forground/background color if we render a 1bpp bitmap
    if (bmpHeader.bmp2.cBitCount == 1)
    {
        const RGB2 * pRGBA = bmpHeader.argb2Color;

        const LONG colorFg = (pRGBA[0].bRed << 16) + (pRGBA[0].bGreen << 8) + pRGBA[0].bBlue;
        const LONG colorBg = (pRGBA[1].bRed << 16) + (pRGBA[1].bGreen << 8) + pRGBA[1].bBlue;

        LONG colorTable[2] = { colorBg, colorFg };

        GpiCreateLogColorTable(hps, 0, LCOLF_CONSECRGB, 0, 2, (PLONG) colorTable);
        /* if something goes wrong when creating color table, use the closest color */
        GpiSetBackColor(hps, GpiQueryColorIndex(hps, 0, colorBg));
        GpiSetColor    (hps, GpiQueryColorIndex(hps, 0, colorFg));
    }

    RECTL aptlPoints[2] = { 0 };

    aptlPoints[0] = render_area;

    aptlPoints[1].xLeft   = src_x;
    aptlPoints[1].yBottom = cacheDescription.height - (src_y + src_height);
    aptlPoints[1].xRight  = aptlPoints[1].xLeft   + src_width;
    aptlPoints[1].yTop    = aptlPoints[1].yBottom + src_height;

    if (GpiDrawBits(hps,
                    (PBYTE) cacheDescription.getDataPtr(),
                    (PBITMAPINFO2)(&bmpHeader),
                    4L,
                    (PPOINTL) aptlPoints,
                    ROP_SRCCOPY,
                    BBO_IGNORE ) == GPI_ERROR)
    {
        throw GbmException(GBM_ERR_MEM);
    }
}

// ---------------------------------------------------------

/**
 * @param src_x         x coordinate of upper left corner
 * @param src_y         y coordinate of upper left corner
 * @param src_width     width of rectangle to render
 * @param src_height    height of rectangle to render
 * @param scale_x       scale specified as pixels per point (width)
 * @param scale_y       scale specified as pixels per point (height)
 *
 * @return the required buffer size in bytes
 **/

unsigned long GbmRenderer::calcRenderBufferSize(long     src_x    , long   src_y,
                                                long     src_width, long   src_height,
                                                double   scale_x  , double scale_y) const throw ( GbmException )
{
    if (renderPage == NULL)
    {
        throw GbmException(GBM_ERR_BAD_ARG, "No render page set");
    }
    if ((scale_x < 0.0) || (scale_y < 0.0) ||
        (src_y < 0)     || (src_x < 0)     || (src_width <= 0) || (src_height <= 0))
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Bad render options");
    }

    // calc visible rectangle of the drawing buffer
    GBM renderGbm;

    // Needs additional scaling ?
    if ((scale_x != 1.0) || (scale_y != 1.0))
    {
        BMPHEADER bmpHeader;
        GBMRGB    gbmrgb[0x100] = { 0 };
        BOOLEAN   isGray        = FALSE;
        copyBmpHeaderAndGetPaletteAsGBMRGB(&bmpHeader, gbmrgb, &isGray);

        renderGbm.w   = (int) ((scale_x * ((double) (renderPage->getWidth() ))) + 0.5);
        renderGbm.h   = (int) ((scale_y * ((double) (renderPage->getHeight()))) + 0.5);
        renderGbm.bpp = isGray ? 8 : renderPage->getColorDepth();
    }
    else
    {
        renderGbm.w   = renderPage->getWidth();
        renderGbm.h   = renderPage->getHeight();
        renderGbm.bpp = renderPage->getColorDepth();
    }

    // protect against buffer overrun due to rounding errors
    if (src_x + src_width > renderGbm.w)
    {
        src_width = renderGbm.w - src_x;
    }
    if (src_y + src_height > renderGbm.h)
    {
        src_height = renderGbm.h - src_y;
    }

    const unsigned long stride  = (((unsigned long)src_width * renderGbm.bpp + 31)/32) * 4;
    const unsigned long dataLen = stride * src_height;

    return dataLen;
}

// ---------------------------------------------------------

/**
 * Scale the document to match the specified pixels per point
 *
 * @param scale_x       scale specified as pixels per point (width)
 * @param scale_y       scale specified as pixels per point (height)
 **/
void GbmRenderer::prepareRenderCache(const double scale_x,
                                     const double scale_y) throw (GbmException)
{
    if (renderPage == NULL)
    {
        throw GbmException(GBM_ERR_BAD_ARG, "No render page set");
    }
    if ((scale_x < 0.0) || (scale_y < 0.0))
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Bad render options");
    }

    GBMRGB gbmrgb[0x100] = { 0 };
    BOOLEAN isGray       = FALSE;
    getPaletteAsGBMRGB(gbmrgb, &isGray);

    // Check if any important render parameters (scaling, page size) have changed.
    if ((scale_x                     != cacheDescription.scale_x) ||
        (scale_y                     != cacheDescription.scale_y) ||
        (renderPage->getColorDepth() != unscaledBpp)              ||
        (renderPage->getWidth()      != unscaledWidth)            ||
        (renderPage->getHeight()     != unscaledHeight))
    {
        // force full recalculation of target bitmap
        cacheDescription.clear();
        unscaledBpp    = renderPage->getColorDepth();
        unscaledWidth  = renderPage->getWidth();
        unscaledHeight = renderPage->getHeight();
    }

          int           copySrcWidth  = cacheDescription.width;
          int           copySrcHeight = cacheDescription.height;
          unsigned long copySrcStride = cacheDescription.rowStride;
          int           copySrcBpp    = cacheDescription.bpp;
    const byte *        copySrcData   = cacheDescription.getDataPtr();
          byte *        scaledData    = NULL;

    if (copySrcData == NULL)
    {
        copySrcWidth  = renderPage->getWidth();
        copySrcHeight = renderPage->getHeight();
        copySrcStride = renderPage->getBitmapDataRowSize();
        copySrcBpp    = renderPage->getColorDepth();
        copySrcData   = renderPage->getBitmapDataPtr();

        // Needs additional scaling ?
        if ((scale_x != 1.0) || (scale_y != 1.0))
        {
            const int           scaledWidth   = (int) ((scale_x * ((double) (copySrcWidth ))) + 0.5);
            const int           scaledHeight  = (int) ((scale_y * ((double) (copySrcHeight))) + 0.5);
            const int           scaledBpp     = isGray ? 8 : copySrcBpp;
            const unsigned long scaledStride  = (((unsigned long)scaledWidth * scaledBpp + 31)/32) * 4;
            const unsigned long scaledDataLen = scaledStride * scaledHeight;

            scaledData = (byte *)gbmmem_malloc(scaledDataLen);
            if (scaledData != NULL)
            {
                GBM_ERR gbmrc = GBM_ERR_OK;

                // scale to requested size
                if ((mScaleFilter != ScalerType_SIMPLE) &&
                    (isGray || (renderPage->getColorDepth() > 8)))
                {
                    // map to GBM scale filter (1:1 int)
                    const GBM_SCALE_FILTER gbmScaleFilter = (GBM_SCALE_FILTER) mScaleFilter;

                    if (isGray)
                    {
                        gbmrc = gbm_quality_scale_gray(copySrcData, copySrcWidth  , copySrcHeight,
                                                       renderPage->getColorDepth(), gbmrgb,
                                                       scaledData , scaledWidth   , scaledHeight,
                                                       gbmrgb     , gbmScaleFilter);
                    }
                    else
                    {
                        gbmrc = gbm_quality_scale_bgra(copySrcData, copySrcWidth  , copySrcHeight,
                                                       scaledData , scaledWidth   , scaledHeight,
                                                       renderPage->getColorDepth(), gbmScaleFilter);
                    }
                }
                else
                {
                    gbmrc = gbm_simple_scale(copySrcData, copySrcWidth , copySrcHeight,
                                             scaledData , scaledWidth  , scaledHeight,
                                             renderPage->getColorDepth());
                }
                if (gbmrc == GBM_ERR_OK)
                {
                    // set the copy source to the scaled data
                    copySrcWidth  = scaledWidth;
                    copySrcHeight = scaledHeight;
                    copySrcStride = scaledStride;
                    copySrcBpp    = scaledBpp;
                    copySrcData   = scaledData;
                }
                else
                {
                    gbmmem_free(scaledData);
                    scaledData = NULL;
                    throw GbmException(gbmrc);
                }
            }
            else
            {
                /* In case we had a problem with scaling (e.g. out of memory), */
                /* render nothing (or later show an error icon?).              */
                throw GbmException(GBM_ERR_MEM);
            }
        }

        cacheDescription.width     = copySrcWidth;
        cacheDescription.height    = copySrcHeight;
        cacheDescription.rowStride = copySrcStride;
        cacheDescription.bpp       = copySrcBpp;
        cacheDescription.scale_x   = scale_x;
        cacheDescription.scale_y   = scale_y;
        memcpy(cacheDescription.gbmrgb, gbmrgb, sizeof(cacheDescription.gbmrgb));

        if (copySrcData == scaledData)
        {
            // this data can be freed as we own it
            cacheDescription.setData((byte *) scaledData);
        }
        else
        {
            // just store the reference which will not be freed
            cacheDescription.setConstData(copySrcData);

            // Don't free the scaledData buffer if it points
            // to the same address as the cache.
            gbmmem_free(scaledData);
            scaledData = NULL;
        }
    }
}

// ---------------------------------------------------------

/**
 * @param src_x         x coordinate of upper left corner
 * @param src_y         y coordinate of upper left corner
 * @param src_width     width of rectangle to render
 * @param src_height    height of rectangle to render
 * @param scale_x       scale specified as pixels per point (width)
 * @param scale_y       scale specified as pixels per point (height)
 * @param renderBuffer  render buffer (4 byte aligned row size according
 *                      to src_width and page colour depth of page set)
 * @param bmpHeader     the bitmapinfo for direct use by GPI
 *
 * 1. Scale the document to match the specified pixels per point
 * 2. Render the rectangle given by the upper left corner at
 *    (src_x, src_y) and src_width and src_height.
 **/

void GbmRenderer::renderToBuffer(long        src_x    , long   src_y,
                                 long        src_width, long   src_height,
                                 double      scale_x  , double scale_y,
                                 byte      * renderBuffer,
                                 BMPHEADER * bmpHeader) throw ( GbmException )
{
    if (bmpHeader == NULL)
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Missing bitmap header");
    }
    if ((src_y < 0) || (src_x < 0) || (src_width <= 0) || (src_height <= 0))
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Bad render options");
    }

    GBMRGB gbmrgb[0x100] = { 0 };
    BOOLEAN isGray       = FALSE;
    copyBmpHeaderAndGetPaletteAsGBMRGB(bmpHeader, gbmrgb, &isGray);

    prepareRenderCache(scale_x, scale_y);

    // update the bitmap header and the color palette to be returned
    copyGBMRGB2BmpHeader(cacheDescription.gbmrgb, bmpHeader);

    // copy visible rectangle to the drawing buffer
    GBM renderGbm;
    renderGbm.w   = cacheDescription.width;
    renderGbm.h   = cacheDescription.height;
    renderGbm.bpp = cacheDescription.bpp;

    // protect against buffer overrun due to rounding errors
    if (src_x + src_width > renderGbm.w)
    {
        src_width = renderGbm.w - src_x;
    }
    if (src_y + src_height > renderGbm.h)
    {
        src_height = renderGbm.h - src_y;
    }

    gbm_subrectangle(&renderGbm,
                      src_x, renderGbm.h-(src_y+src_height), src_width, src_height,
                      cacheDescription.getDataPtr(), renderBuffer);

    bmpHeader->bmp2.cx        = src_width;
    bmpHeader->bmp2.cy        = src_height;
    bmpHeader->bmp2.cBitCount = renderGbm.bpp;
    bmpHeader->bmp2.cclrUsed  = renderGbm.bpp > 8 ? 0 : (1 << renderGbm.bpp);
    bmpHeader->bmp2.cbImage   = (((src_width * renderGbm.bpp + 31)/32) * 4) * src_height;
}

// ---------------------------------------------------------

/**
 * @param src_x           x coordinate of upper left corner
 * @param src_y           y coordinate of upper left corner
 * @param src_width       width of rectangle to render
 * @param src_height      height of rectangle to render
 * @param scale_x         scale specified as pixels per point (width)
 * @param scale_y         scale specified as pixels per point (height)
 * @param renderBuffer24  render buffer (4 byte aligned row size according
 *                        to src_width and 24bpp colour depth)
 * @param bmpHeader       the bitmapinfo for direct use by GPI
 *
 * 1. Scale the document to match the specified pixels per point
 * 2. Render the rectangle given by the upper left corner at
 *    (src_x, src_y) and src_width and src_height.
 **/

void GbmRenderer::renderToBuffer24bpp(long        src_x    , long   src_y,
                                      long        src_width, long   src_height,
                                      double      scale_x  , double scale_y,
                                      byte      * renderBuffer24,
                                      BMPHEADER * bmpHeader) throw ( GbmException )
{
    if (renderPage == NULL)
    {
        throw GbmException(GBM_ERR_BAD_ARG, "No render page set");
    }

    // if the source is already 24bpp, just forward to direct render function
    if (renderPage->getColorDepth() == 24)
    {
        renderToBuffer(src_x         , src_y,
                       src_width     , src_height,
                       scale_x       , scale_y,
                       renderBuffer24, bmpHeader);
        return;
    }

    if ((scale_x < 0.0) || (scale_y < 0.0) ||
        (src_y < 0)     || (src_x < 0)     || (src_width <= 0) || (src_height <= 0))
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Bad render options");
    }

    const unsigned long scaledDataLength = calcRenderBufferSize(src_x    , src_y,
                                                                src_width, src_height,
                                                                scale_x  , scale_y);
    byte * scaledData = (byte *)gbmmem_malloc(scaledDataLength);
    if (scaledData != NULL)
    {
        // first prepare the bitmap as usual but then expand it to 24bpp
        try
        {
            renderToBuffer(src_x     , src_y,
                           src_width , src_height,
                           scale_x   , scale_y,
                           scaledData, bmpHeader);

            expandTo24bpp(bmpHeader , src_width, src_height,
                          scaledData, renderBuffer24);

            /* update relevant bitmap header data */
            const unsigned long bmpDataStride = (((unsigned long)src_width * 24 + 31)/32) * 4;
            const unsigned long bmpDataLength = bmpDataStride * src_height;

            bmpHeader->bmp2.cBitCount = 24;            /*  Number of bits per pel within a plane. */
            bmpHeader->bmp2.cclrUsed  = 0;             /*  Number of used colors in the palette */
            bmpHeader->bmp2.cbImage   = bmpDataLength; /*  Length of bit-map storage data, in bytes. */
        }
        catch(GbmException & ex)
        {
            gbmmem_free(scaledData);
            scaledData = NULL;
            throw ex;
        }
        gbmmem_free(scaledData);
        scaledData = NULL;
        // DONE
    }
    else
    {
        /* In case we had a problem with scaling (e.g. out of memory), */
        /* render nothing (or later show an error icon?).              */
        throw GbmException(GBM_ERR_MEM);
    }
}

// ---------------------------------------------------------

void GbmRenderer::expandTo24bpp(const BMPHEADER * bmpHeader,
                                int src_w, int src_h,
                                const byte *data, byte * data24) const throw ( GbmException )
{
    if ((data == NULL) || (data24 == NULL) || (bmpHeader == NULL) ||
        (src_w < 1) || (src_h < 1))
    {
        throw GbmException(GBM_ERR_BAD_ARG);
    }
    if (bmpHeader->bmp2.cBitCount >= 24)
    {
        throw GbmException(GBM_ERR_BAD_ARG);
    }

    const RGB2 *           pRGB    = bmpHeader->argb2Color;
    const unsigned long stride     = (((unsigned long)src_w * bmpHeader->bmp2.cBitCount + 31)/32) * 4;
    const unsigned long new_stride = (((unsigned long)src_w * 3 + 3) & ~3);

    const byte *src  = data;
          byte *dest = data24;

    int  x, y;
    byte c;

    switch (bmpHeader->bmp2.cBitCount)
    {
        case 1:
        {
            for ( y = 0; y < src_h; y++ )
            {
                for ( x = 0; x < src_w; x++ )
                {
                    if ( (x & 7) == 0 )
                    {
                        c = *src++;
                    }
                    else
                    {
                        c <<= 1;
                    }
                    *dest++ = pRGB[c >> 7].bBlue;
                    *dest++ = pRGB[c >> 7].bGreen;
                    *dest++ = pRGB[c >> 7].bRed;
                }
                src  += stride;
                dest += new_stride;
            }
        }
        break;

        case 4:
        {
            for ( y = 0; y < src_h; y++ )
            {
                for ( x = 0; x + 1 < src_w; x += 2 )
                {
                    c       = *src++;
                    *dest++ = pRGB[c >> 4].bBlue;
                    *dest++ = pRGB[c >> 4].bGreen;
                    *dest++ = pRGB[c >> 4].bRed;
                    *dest++ = pRGB[c & 15].bBlue;
                    *dest++ = pRGB[c & 15].bGreen;
                    *dest++ = pRGB[c & 15].bRed;
                }

                if ( x < src_w )
                {
                    c       = *src;
                    *dest++ = pRGB[c >> 4].bBlue;
                    *dest++ = pRGB[c >> 4].bGreen;
                    *dest++ = pRGB[c >> 4].bRed;
                }
                src  += stride;
                dest += new_stride;
            }
        }
        break;

        case 8:
        {
            for ( y = 0; y < src_h; y++ )
            {
                for ( x = 0; x < src_w; x++ )
                {
                    c       = *src++;
                    *dest++ = pRGB[c].bBlue;
                    *dest++ = pRGB[c].bGreen;
                    *dest++ = pRGB[c].bRed;
                }
                src  += stride;
                dest += new_stride;
            }
        }
        break;

        default:
           throw GbmException(GBM_ERR_NOT_SUPP);
    }
}

/* ---------------------------- */

void GbmRenderer::copyBmpHeaderAndGetPaletteAsGBMRGB(BMPHEADER *bmpHeader,
                                                     GBMRGB    *gbmrgb,
                                                     BOOLEAN   *isGrayscale)
                                                         const throw ( GbmException )
{
    const BITMAPINFO2 * bmpInfo2 = renderPage->getBitmapInfoPtr();
    if (bmpInfo2 == NULL)
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Render page has no bitmap header");
    }

    memset(gbmrgb, 0, sizeof(GBMRGB) * 0x100);

    if (renderPage->getColorDepth() > 8)
    {
        memset(bmpHeader, 0, sizeof(BMPHEADER));
        memcpy(bmpHeader, bmpInfo2, sizeof(BITMAPINFOHEADER2));
        *isGrayscale = FALSE;
    }
    else
    {
        memcpy(bmpHeader, bmpInfo2, sizeof(BMPHEADER));

        const int palette_entries = (renderPage->getColorDepth() > 8)
                                    ? 0 : 1 << renderPage->getColorDepth();
        *isGrayscale = TRUE;

        for (int i = 0; i < palette_entries; i++)
        {
            GBMRGB * pRGB = &(gbmrgb[i]);

            pRGB->r = bmpHeader->argb2Color[i].bRed;
            pRGB->g = bmpHeader->argb2Color[i].bGreen;
            pRGB->b = bmpHeader->argb2Color[i].bBlue;

            if ((pRGB->r != pRGB->g) ||
                (pRGB->r != pRGB->b) ||
                (pRGB->g != pRGB->b))
            {
                *isGrayscale = FALSE;
            }
        }
    }
}

/* ---------------------------- */

void GbmRenderer::getPaletteAsGBMRGB(GBMRGB  *gbmrgb,
                                     BOOLEAN *isGrayscale) const throw (GbmException)
{
    const BITMAPINFO2 * bmpInfo2 = renderPage->getBitmapInfoPtr();
    if (bmpInfo2 == NULL)
    {
        throw GbmException(GBM_ERR_BAD_ARG, "Render page has no bitmap header");
    }

    memset(gbmrgb, 0, sizeof(GBMRGB) * 0x100);

    if (renderPage->getColorDepth() > 8)
    {
        *isGrayscale = FALSE;
    }
    else
    {
        const int palette_entries = (renderPage->getColorDepth() > 8)
                                    ? 0 : 1 << renderPage->getColorDepth();
        *isGrayscale = TRUE;

        const BMPHEADER * bmpHeader = (const BMPHEADER *) bmpInfo2;

        for (int i = 0; i < palette_entries; i++)
        {
            GBMRGB * pRGB = &(gbmrgb[i]);

            pRGB->r = bmpHeader->argb2Color[i].bRed;
            pRGB->g = bmpHeader->argb2Color[i].bGreen;
            pRGB->b = bmpHeader->argb2Color[i].bBlue;

            if ((pRGB->r != pRGB->g) ||
                (pRGB->r != pRGB->b) ||
                (pRGB->g != pRGB->b))
            {
                *isGrayscale = FALSE;
            }
        }
    }
}

/* ---------------------------- */

void GbmRenderer::copyGBMRGB2BmpHeader(const GBMRGB    * gbmrgb,
                                             BMPHEADER * bmpHeader) const
{
    for (int i = 0; i < 256; i++)
    {
        bmpHeader->argb2Color[i].bRed   = gbmrgb[i].r;
        bmpHeader->argb2Color[i].bGreen = gbmrgb[i].g;
        bmpHeader->argb2Color[i].bBlue  = gbmrgb[i].b;
    }
}


