/******************************Module*Header*******************************\
* Module Name: fastdib.c
*
* CreateCompatibleDIB implementation.
*
* Copyright (c) 1996 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#include "sscommon.h"

BOOL APIENTRY GetDIBTranslationVector(HDC hdcMem, HPALETTE hpal, BYTE *pbVector);
static BOOL bFillBitmapInfo(HDC hdc, HPALETTE hpal, BITMAPINFO *pbmi);
static BOOL bFillColorTable(HDC hdc, HPALETTE hpal, BITMAPINFO *pbmi);
static UINT MyGetSystemPaletteEntries(HDC hdc, UINT iStartIndex, UINT nEntries,
                                      LPPALETTEENTRY lppe);
static BOOL bComputeLogicalToSurfaceMap(HDC hdc, HPALETTE hpal,
                                        BYTE *pajVector);

/******************************Public*Routine******************************\
* CreateCompatibleDIB
*
* Create a DIB section with an optimal format w.r.t. the specified hdc.
*
* If DIB <= 8bpp, then the DIB color table is initialized based on the
* specified palette.  If the palette handle is NULL, then the system
* palette is used.
*
* Note: The hdc must be a direct DC (not an info or memory DC).
*
* Note: On palettized displays, if the system palette changes the
*       UpdateDIBColorTable function should be called to maintain
*       the identity palette mapping between the DIB and the display.
*
* Returns:
*   Valid bitmap handle if successful, NULL if error.
\**************************************************************************/

//HBITMAP APIENTRY
HBITMAP
SSDIB_CreateCompatibleDIB(HDC hdc, HPALETTE hpal, ULONG ulWidth, ULONG ulHeight,
                    PVOID *ppvBits)
{
    HBITMAP hbmRet = (HBITMAP) NULL;
    BYTE aj[sizeof(BITMAPINFO) + (sizeof(RGBQUAD) * 255)];
    BITMAPINFO *pbmi = (BITMAPINFO *) aj;

    //
    // Validate hdc.
    //

    if ( GetObjectType(hdc) != OBJ_DC )
    {
        SS_DBGPRINT("CreateCompatibleDIB: not OBJ_DC\n");
        return hbmRet;
    }

    memset(aj, 0, sizeof(aj));
    if ( bFillBitmapInfo(hdc, hpal, pbmi) )
    {
        //
        // Change bitmap size to match specified dimensions.
        //

        pbmi->bmiHeader.biWidth = ulWidth;
        pbmi->bmiHeader.biHeight = ulHeight;
        if (pbmi->bmiHeader.biCompression == BI_RGB)
        {
            pbmi->bmiHeader.biSizeImage = 0;
        }
        else
        {
            if ( pbmi->bmiHeader.biBitCount == 16 )
                pbmi->bmiHeader.biSizeImage = ulWidth * ulHeight * 2;
            else if ( pbmi->bmiHeader.biBitCount == 32 )
                pbmi->bmiHeader.biSizeImage = ulWidth * ulHeight * 4;
            else
                pbmi->bmiHeader.biSizeImage = 0;
        }
        pbmi->bmiHeader.biClrUsed = 0;
        pbmi->bmiHeader.biClrImportant = 0;

        //
        // Create the DIB section.  Let Win32 allocate the memory and return
        // a pointer to the bitmap surface.
        //

        hbmRet = CreateDIBSection(hdc, pbmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
        GdiFlush();

        if ( !hbmRet )
        {
            SS_DBGPRINT("CreateCompatibleDIB: CreateDIBSection failed\n");
        }
    }
    else
    {
        SS_DBGPRINT("CreateCompatibleDIB: bFillBitmapInfo failed\n");
    }

    return hbmRet;
}

/******************************Public*Routine******************************\
* UpdateDIBColorTable
*
* Synchronize the DIB color table to the specified palette hpal.
* If hpal is NULL, then use the system palette.
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

BOOL APIENTRY
SSDIB_UpdateColorTable(HDC hdcMem, HDC hdc, HPALETTE hpal)
{
    BOOL bRet = FALSE;
    HBITMAP hbm;
    DIBSECTION ds;
    BYTE aj[(sizeof(RGBQUAD) + sizeof(PALETTEENTRY)) * 256];
    LPPALETTEENTRY lppe = (LPPALETTEENTRY) aj;
    LPRGBQUAD prgb = (LPRGBQUAD) (lppe + 256);
    ULONG cColors;

    //
    // Validate hdc.
    //

    if ( GetObjectType(hdc) != OBJ_DC )
    {
        SS_DBGPRINT("UpdateDIBColorTable: not OBJ_DC\n");
        return bRet;
    }
    if ( GetObjectType(hdcMem) != OBJ_MEMDC )
    {
        SS_DBGPRINT("UpdateDIBColorTable: not OBJ_MEMDC\n");
        return bRet;
    }

    //
    // Get the bitmap handle out of the memdc.
    //

    hbm = GetCurrentObject(hdcMem, OBJ_BITMAP);

    //
    // Validate bitmap (must be DIB section).
    //

    if ( (GetObject(hbm, sizeof(ds), &ds) == sizeof(ds)) &&
         ds.dsBm.bmBits )
    {
        //
        // Get palette entries from specified palette or system palette.
        //

        cColors = 1 << ds.dsBmih.biBitCount;


        if ( hpal ? GetPaletteEntries(hpal, 0, cColors, lppe)
                  : MyGetSystemPaletteEntries(hdc, 0, cColors, lppe)
           )
        {
            UINT i;

            //
            // Convert to RGBQUAD.
            //

            for (i = 0; i < cColors; i++)
            {
                prgb[i].rgbRed      = lppe[i].peRed;
                prgb[i].rgbGreen    = lppe[i].peGreen;
                prgb[i].rgbBlue     = lppe[i].peBlue;
                prgb[i].rgbReserved = 0;
            }

            //
            // Set the DIB color table.
            //

            bRet = (BOOL) SetDIBColorTable(hdcMem, 0, cColors, prgb);

            if (!bRet)
            {
                SS_DBGPRINT("UpdateDIBColorTable: SetDIBColorTable failed\n");
            }
        }
        else
        {
            SS_DBGPRINT("UpdateDIBColorTable: MyGetSystemPaletteEntries failed\n");
        }
    }
    else
    {
        SS_DBGPRINT("UpdateDIBColorTable: GetObject failed\n");
    }

    return bRet;
}

/******************************Public*Routine******************************\
* GetCompatibleDIBInfo
*
* Copies pointer to bitmap origin to ppvBase and bitmap stride to plStride.
* Win32 DIBs can be created bottom-up (the default) with the origin at the
* lower left corner or top-down with the origin at the upper left corner.
* If the bitmap is top-down, *plStride is positive; if bottom-up, *plStride
* us negative.
*
* Also, because of restrictions on the alignment of scan lines the width
* the bitmap is often not the same as the stride (stride is the number of
* bytes between vertically adjacent pixels).
*
* The ppvBase and plStride value returned will allow you to address any
* given pixel (x, y) in the bitmap as follows:
*
* PIXEL *ppix;
*
* ppix = (PIXEL *) (((BYTE *)*ppvBase) + (y * *plStride) + (x * sizeof(PIXEL)));
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

BOOL APIENTRY
GetCompatibleDIBInfo(HBITMAP hbm, PVOID *ppvBase, LONG *plStride)
{
    BOOL bRet = FALSE;
    DIBSECTION ds;

    //
    // Call GetObject to return a DIBSECTION.  If successful, the
    // bitmap is a DIB section and we can retrieve the pointer to
    // the bitmap bits and other parameters.
    //

    if ( (GetObject(hbm, sizeof(ds), &ds) == sizeof(ds))
         && ds.dsBm.bmBits )
    {
        // For backwards compatibility with Get/SetBitmapBits, GDI does
        // not accurately report the bitmap pitch in bmWidthBytes.  It
        // always computes bmWidthBytes assuming WORD-aligned scanlines
        // regardless of the platform.
        //
        // Therefore, if the platform is WinNT, which uses DWORD-aligned
        // scanlines, adjust the bmWidthBytes value.

        {
            OSVERSIONINFO osvi;

            osvi.dwOSVersionInfoSize = sizeof(osvi);
            if (GetVersionEx(&osvi))
            {
                if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_NT )
                {
                    ds.dsBm.bmWidthBytes = (ds.dsBm.bmWidthBytes + 3) & ~3;
                }
            }
            else
            {
                SS_DBGPRINT1("GetCompatibleDIBInfo: GetVersionEx failed with %d\n", GetLastError());
                return bRet;
            }
        }

        //
        // If biHeight is positive, then the bitmap is a bottom-up DIB.
        // If biHeight is negative, then the bitmap is a top-down DIB.
        //

        if ( ds.dsBmih.biHeight > 0 )
        {
            *ppvBase  = (PVOID) (((int) ds.dsBm.bmBits) + (ds.dsBm.bmWidthBytes * (ds.dsBm.bmHeight - 1)));
            *plStride = (ULONG) (-ds.dsBm.bmWidthBytes);
        }
        else
        {
            *ppvBase  = ds.dsBm.bmBits;
            *plStride = ds.dsBm.bmWidthBytes;
        }

        bRet = TRUE;
    }
    else
    {
        SS_DBGPRINT("GetCompatibleDIBInfo: cannot get pointer to DIBSECTION bmBits\n");
    }

    return bRet;
}

/******************************Public*Routine******************************\
* GetDIBTranslationVector
*
* Copies the translation vector that maps colors in the specified palette,
* hpal, to the DIB selected into the specified DC, hdcMem.
*
* Effects:
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

BOOL APIENTRY
GetDIBTranslationVector(HDC hdcMem, HPALETTE hpal, BYTE *pbVector)
{
    BOOL bRet = FALSE;
    HBITMAP hbm;
    DIBSECTION ds;

    //
    // Validate parameters.
    //

    if ( GetObjectType(hdcMem) != OBJ_MEMDC ||
         GetObjectType(hpal) != OBJ_PAL ||
         !pbVector )
    {
        SS_DBGPRINT("GetDIBTranslationVector: bad parameter\n");
        return bRet;
    }

    //
    // The function bComputeLogicalToSurfaceMap cannot handle palettes
    // greater than 256 entries.
    //

    if ( GetPaletteEntries(hpal, 0, 1, NULL) > 256 )
    {
        SS_DBGPRINT("GetDIBTranslationVector: palette too big\n");
        return bRet;
    }

    //
    // The DIB must have a color table.
    //

    hbm = GetCurrentObject(hdcMem, OBJ_BITMAP);
    if ( (GetObject(hbm, sizeof(ds), &ds) == sizeof(ds))
         && (ds.dsBmih.biBitCount <= 8) )
    {
        bRet = bComputeLogicalToSurfaceMap(hdcMem, hpal, pbVector);
    }
    else
    {
        SS_DBGPRINT("GetDIBTranslationVector: not a DIB section\n");
        return bRet;
    }

    return bRet;
}

//////////////////// Below here are internal-only routines ////////////////////

/******************************Public*Routine******************************\
* bFillBitmapInfo
*
* Fills in the fields of a BITMAPINFO so that we can create a bitmap
* that matches the format of the display.
*
* This is done by creating a compatible bitmap and calling GetDIBits
* to return the color masks.  This is done with two calls.  The first
* call passes in biBitCount = 0 to GetDIBits which will fill in the
* base BITMAPINFOHEADER data.  The second call to GetDIBits (passing
* in the BITMAPINFO filled in by the first call) will return the color
* table or bitmasks, as appropriate.
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

static BOOL
bFillBitmapInfo(HDC hdc, HPALETTE hpal, BITMAPINFO *pbmi)
{
    HBITMAP hbm;
    BOOL    bRet = FALSE;

    //
    // Create a dummy bitmap from which we can query color format info
    // about the device surface.
    //

    if ( (hbm = CreateCompatibleBitmap(hdc, 1, 1)) != NULL )
    {
        pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

        //
        // Call first time to fill in BITMAPINFO header.
        //

        GetDIBits(hdc, hbm, 0, 0, NULL, pbmi, DIB_RGB_COLORS);

        if ( pbmi->bmiHeader.biBitCount <= 8 )
        {
            bRet = bFillColorTable(hdc, hpal, pbmi);
        }
        else
        {
            if ( pbmi->bmiHeader.biCompression == BI_BITFIELDS )
            {
                //
                // Call a second time to get the color masks.
                // It's a GetDIBits Win32 "feature".
                //

                GetDIBits(hdc, hbm, 0, pbmi->bmiHeader.biHeight, NULL, pbmi,
                          DIB_RGB_COLORS);
            }

            bRet = TRUE;
        }

        DeleteObject(hbm);
    }
    else
    {
        SS_DBGPRINT("bFillBitmapInfo: CreateCompatibleBitmap failed\n");
    }

    return bRet;
}

/******************************Public*Routine******************************\
* bFillColorTable
*
* Initialize the color table of the BITMAPINFO pointed to by pbmi.  Colors
* are set to the current system palette.
*
* Note: call only valid for displays of 8bpp or less.
*
* Returns:
*   TRUE if successful, FALSE otherwise.
\**************************************************************************/

static BOOL
bFillColorTable(HDC hdc, HPALETTE hpal, BITMAPINFO *pbmi)
{
    BOOL bRet = FALSE;
    BYTE aj[sizeof(PALETTEENTRY) * 256];
    LPPALETTEENTRY lppe = (LPPALETTEENTRY) aj;
    RGBQUAD *prgb = (RGBQUAD *) &pbmi->bmiColors[0];
    ULONG cColors;

    cColors = 1 << pbmi->bmiHeader.biBitCount;
    if ( cColors <= 256 )
    {
        if ( hpal ? GetPaletteEntries(hpal, 0, cColors, lppe)
                  : MyGetSystemPaletteEntries(hdc, 0, cColors, lppe) )
        {
            UINT i;

            for (i = 0; i < cColors; i++)
            {
                prgb[i].rgbRed      = lppe[i].peRed;
                prgb[i].rgbGreen    = lppe[i].peGreen;
                prgb[i].rgbBlue     = lppe[i].peBlue;
                prgb[i].rgbReserved = 0;
            }

            bRet = TRUE;
        }
        else
        {
            SS_DBGPRINT("bFillColorTable: MyGetSystemPaletteEntries failed\n");
        }
    }

    return bRet;
}

/******************************Public*Routine******************************\
* MyGetSystemPaletteEntries
*
* Internal version of GetSystemPaletteEntries.
*
* GetSystemPaletteEntries fails on some 4bpp devices.  This version
* will detect the 4bpp case and supply the hardcoded 16-color VGA palette.
* Otherwise, it will pass the call on to GDI's GetSystemPaletteEntries.
*
* It is expected that this call will only be called in the 4bpp and 8bpp
* cases as it is not necessary for OpenGL to query the system palette
* for > 8bpp devices.
\**************************************************************************/

static PALETTEENTRY gapeVgaPalette[16] =
{
    { 0,   0,   0,    0 },
    { 0x80,0,   0,    0 },
    { 0,   0x80,0,    0 },
    { 0x80,0x80,0,    0 },
    { 0,   0,   0x80, 0 },
    { 0x80,0,   0x80, 0 },
    { 0,   0x80,0x80, 0 },
    { 0x80,0x80,0x80, 0 },
    { 0xC0,0xC0,0xC0, 0 },
    { 0xFF,0,   0,    0 },
    { 0,   0xFF,0,    0 },
    { 0xFF,0xFF,0,    0 },
    { 0,   0,   0xFF, 0 },
    { 0xFF,0,   0xFF, 0 },
    { 0,   0xFF,0xFF, 0 },
    { 0xFF,0xFF,0xFF, 0 }
};

static UINT
MyGetSystemPaletteEntries(HDC hdc, UINT iStartIndex, UINT nEntries,
                          LPPALETTEENTRY lppe)
{
    int nDeviceBits;

    nDeviceBits = GetDeviceCaps(hdc, BITSPIXEL) * GetDeviceCaps(hdc, PLANES);

    //
    // Some 4bpp displays will fail the GetSystemPaletteEntries call.
    // So if detected, return the hardcoded table.
    //

    if ( nDeviceBits == 4 )
    {
        if ( lppe )
        {
            nEntries = min(nEntries, (16 - iStartIndex));

            memcpy(lppe, &gapeVgaPalette[iStartIndex],
                   nEntries * sizeof(PALETTEENTRY));
        }
        else
            nEntries = 16;

        return nEntries;
    }
    else
    {
        return GetSystemPaletteEntries(hdc, iStartIndex, nEntries, lppe);
    }
}

/******************************Public*Routine******************************\
* bComputeLogicalToSurfaceMap
*
* Copy logical palette to surface palette translation vector to the buffer
* pointed to by pajVector.  The logical palette is specified by hpal.  The
* surface is specified by hdc.
*
* Note: The hdc may identify either a direct (display) dc or a DIB memory dc.
* If hdc is a display dc, then the surface palette is the system palette.
* If hdc is a memory dc, then the surface palette is the DIB color table.
\**************************************************************************/

static BOOL bComputeLogicalToSurfaceMap(HDC hdc, HPALETTE hpal, BYTE *pajVector)
{
    BOOL bRet = FALSE;
    HPALETTE hpalSurf;
    ULONG cEntries, cSysEntries;
    DWORD dwDcType = GetObjectType(hdc);
    LPPALETTEENTRY lppeTmp, lppeEnd;

    BYTE aj[sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 512) + (sizeof(RGBQUAD) * 256)];
    LOGPALETTE *ppal = (LOGPALETTE *) aj;
    LPPALETTEENTRY lppeSurf = &ppal->palPalEntry[0];
    LPPALETTEENTRY lppe = lppeSurf + 256;
    RGBQUAD *prgb = (RGBQUAD *) (lppe + 256);

    //
    // Determine number of colors in each palette.
    //

    cEntries = GetPaletteEntries(hpal, 0, 1, NULL);
    if ( dwDcType == OBJ_DC )
        cSysEntries = MyGetSystemPaletteEntries(hdc, 0, 1, NULL);
    else
        cSysEntries = 256;

    //
    // Get the logical palette entries.
    //

    cEntries = GetPaletteEntries(hpal, 0, cEntries, lppe);

    //
    // Get the surface palette entries.
    //

    if ( dwDcType == OBJ_DC )
    {
        cSysEntries = MyGetSystemPaletteEntries(hdc, 0, cSysEntries, lppeSurf);

        lppeTmp = lppeSurf;
        lppeEnd = lppeSurf + cSysEntries;

        for (; lppeTmp < lppeEnd; lppeTmp++)
            lppeTmp->peFlags = 0;
    }
    else
    {
        RGBQUAD *prgbTmp;

        //
        // First get RGBQUADs from DIB color table...
        //

        cSysEntries = GetDIBColorTable(hdc, 0, cSysEntries, prgb);

        //
        // ...then convert RGBQUADs into PALETTEENTRIES.
        //

        prgbTmp = prgb;
        lppeTmp = lppeSurf;
        lppeEnd = lppeSurf + cSysEntries;

        while ( lppeTmp < lppeEnd )
        {
            lppeTmp->peRed   = prgbTmp->rgbRed;
            lppeTmp->peGreen = prgbTmp->rgbGreen;
            lppeTmp->peBlue  = prgbTmp->rgbBlue;
            lppeTmp->peFlags = 0;

            lppeTmp++;
            prgbTmp++;

        }
    }

    //
    // Construct a translation vector by using GetNearestPaletteIndex to
    // map each entry in the logical palette to the surface palette.
    //

    if ( cEntries && cSysEntries )
    {
        //
        // Create a temporary logical palette that matches the surface
        // palette retrieved above.
        //

        ppal->palVersion = 0x300;
        ppal->palNumEntries = (USHORT) cSysEntries;

        if ( hpalSurf = CreatePalette(ppal) )
        {
            //
            // Translate each logical palette entry into a surface palette
            // index.
            //

            lppeTmp = lppe;
            lppeEnd = lppe + cEntries;

            for ( ; lppeTmp < lppeEnd; lppeTmp++, pajVector++)
            {
                *pajVector = (BYTE) GetNearestPaletteIndex(
                                        hpalSurf,
                                        RGB(lppeTmp->peRed,
                                            lppeTmp->peGreen,
                                            lppeTmp->peBlue)
                                        );
            }

            bRet = TRUE;

            DeleteObject(hpalSurf);
        }
        else
        {
            SS_DBGPRINT("bComputeLogicalToSurfaceMap: CreatePalette failed\n");
        }
    }
    else
    {
        SS_DBGPRINT("bComputeLogicalToSurfaceMap: failed to get pal info\n");
    }

    return bRet;
}
