/******************************Module*Header*******************************\
* Module Name: ssdib.c
*
* Operations on .bmp files
*
* Copyright (c) 1995 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> 
#include "tk.h"
#include "sscommon.h"

#define BFT_BITMAP  0x4d42  // 'BM' -- indicates structure is BITMAPFILEHEADER

// struct BITMAPFILEHEADER {
//      WORD  bfType
//      DWORD bfSize
//      WORD  bfReserved1
//      WORD  bfReserved2
//      DWORD bfOffBits
// }
#define OFFSET_bfType       0
#define OFFSET_bfSize       2
#define OFFSET_bfReserved1  6
#define OFFSET_bfReserved2  8
#define OFFSET_bfOffBits    10
#define SIZEOF_BITMAPFILEHEADER 14

// Read a WORD-aligned DWORD.  Needed because BITMAPFILEHEADER has
// WORD-alignment.
#define READDWORD(pv)   ( (DWORD)((PWORD)(pv))[0]               \
                          | ((DWORD)((PWORD)(pv))[1] << 16) )   \

// Computes the number of BYTES needed to contain n number of bits.
#define BITS2BYTES(n)   ( ((n) + 7) >> 3 )

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibNumColors(VOID FAR * pv)                                *
 *                                                                          *
 *  PURPOSE    : Determines the number of colors in the DIB by looking at   *
 *               the BitCount filed in the info block.                      *
 *                                                                          *
 *  RETURNS    : The number of colors in the DIB.                           *
 *                                                                          *
 * Stolen from SDK ShowDIB example.                                         *
 ****************************************************************************/

static WORD DibNumColors(VOID FAR * pv)
{
    WORD                bits;
    BITMAPINFOHEADER UNALIGNED *lpbi;
    BITMAPCOREHEADER UNALIGNED *lpbc;

    lpbi = ((LPBITMAPINFOHEADER)pv);
    lpbc = ((LPBITMAPCOREHEADER)pv);

    /*  With the BITMAPINFO format headers, the size of the palette
     *  is in biClrUsed, whereas in the BITMAPCORE - style headers, it
     *  is dependent on the bits per pixel ( = 2 raised to the power of
     *  bits/pixel).
     *
     *  Because of the way we use this call, BITMAPINFOHEADER may be out
     *  of alignment if it follows a BITMAPFILEHEADER.  So use the macro
     *  to safely access DWORD fields.
     */
    if (READDWORD(&lpbi->biSize) != sizeof(BITMAPCOREHEADER)){
        if (READDWORD(&lpbi->biClrUsed) != 0)
        {
            return (WORD) READDWORD(&lpbi->biClrUsed);
        }
        bits = lpbi->biBitCount;
    }
    else
        bits = lpbc->bcBitCount;

    switch (bits){
        case 1:
            return 2;
        case 4:
            return 16;
        case 8:
            return 256;
        default:
            /* A 24 bitcount DIB has no color table */
            return 0;
    }
}

/******************************Public*Routine******************************\
* ss_DIBImageLoad
*
* Hacked form of tk_DIBImageLoad(), for reading a .bmp file from a resource
*
* Loads a DIB file (specified as either an ANSI or Unicode filename,
* depending on the bUnicode flag) and converts it into a TK image format.
*
* The technique used is based on CreateDIBSection and SetDIBits.
* CreateDIBSection is used to create a DIB with a format easily converted
* into the TK image format (packed 24BPP RGB).  The only conversion 
* required is swapping R and B in each RGB triplet (see history below)
* The resulting bitmap is selected into a memory DC.
*
* The DIB file is mapped into memory and SetDIBits called to initialize
* the memory DC bitmap.  It is during this step that GDI converts the
* arbitrary DIB file format to RGB format.
*
* Finally, the RGB data in the DIB section is read out and repacked
* as 24BPP 'BGR'.
*
* Returns:
*   BOOL.  If an error occurs, a diagnostic error
*   message is put into the error stream and tkQuit() is called,
*   terminating the app.
*
* History:
*   - 11/30/95: [marcfo]
*     Modified from tkDIBImageLoad, to work on resource bmp file.
*     At first I tried accessing the bitmap resource as an RT_BITMAP, where
*     the resource compiler strips out file information, and leaves you simple
*     bitmap data.  But this only worked on the Alpha architecture, x86
*     produced a resource with corrupted image data (?palette :)).  So I ended
*     up just slamming the entire .bmp file in as a resource.
*
\**************************************************************************/

BOOL ss_DIBImageLoad(PVOID pv, TEXTURE *ptex)
{
    BOOL             fSuccess = FALSE;
    WORD             wNumColors;    // Number of colors in color table
    BITMAPFILEHEADER *pbmf;         // Ptr to file header
    BITMAPINFOHEADER UNALIGNED *pbmihFile;
    BITMAPCOREHEADER UNALIGNED *pbmchFile; // Ptr to file's core header (if it exists)
    PVOID            pvBits;    // Ptr to bitmap bits in file
    PBYTE            pjBitsRGB;     // Ptr to 24BPP RGB image in DIB section
    PBYTE            pjTKBits = (PBYTE) NULL;   // Ptr to final TK image bits
    PBYTE            pjSrc;         // Ptr to image file used for conversion
    PBYTE            pjDst;         // Ptr to TK image used for conversion

    // These need to be cleaned up when we exit:
    HDC        hdcMem = (HDC) NULL;                 // 24BPP mem DC
    HBITMAP    hbmRGB = (HBITMAP) NULL;             // 24BPP RGB bitmap
    BITMAPINFO *pbmiSource = (BITMAPINFO *) NULL;   // Ptr to source BITMAPINFO
    BITMAPINFO *pbmiRGB = (BITMAPINFO *) NULL;      // Ptr to file's BITMAPINFO

    int i, j;
    int padBytes;

// Otherwise, this may be a raw BITMAPINFOHEADER or BITMAPCOREHEADER
// followed immediately with the color table and the bitmap bits.

    pbmf = (BITMAPFILEHEADER *) pv;

    if ( pbmf->bfType == BFT_BITMAP )
    {
        pbmihFile = (BITMAPINFOHEADER *) ((PBYTE) pbmf + SIZEOF_BITMAPFILEHEADER);

    // BITMAPFILEHEADER is WORD aligned, so use safe macro to read DWORD
    // bfOffBits field.

        pvBits = (PVOID *) ((PBYTE) pbmf
                                + READDWORD((PBYTE) pbmf + OFFSET_bfOffBits));
    }
    else
    {
        pbmihFile = (BITMAPINFOHEADER *) pv;

    // Determination of where the bitmaps bits are needs to wait until we
    // know for sure whether we have a BITMAPINFOHEADER or a BITMAPCOREHEADER.
    }

// Determine the number of colors in the DIB palette.  This is non-zero
// only for 8BPP or less.

    wNumColors = DibNumColors(pbmihFile);

// Create a BITMAPINFO (with color table) for the DIB file.  Because the
// file may not have one (BITMAPCORE case) and potential alignment problems,
// we will create a new one in memory we allocate.
//
// We distinguish between BITMAPINFO and BITMAPCORE cases based upon
// BITMAPINFOHEADER.biSize.

    pbmiSource = (BITMAPINFO *)
        LocalAlloc(LMEM_FIXED, sizeof(BITMAPINFO)
                               + wNumColors * sizeof(RGBQUAD));
    if (!pbmiSource)
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

    // Note: need to use safe READDWORD macro because pbmihFile may
    // have only WORD alignment if it follows a BITMAPFILEHEADER.

    switch (READDWORD(&pbmihFile->biSize))
    {
    case sizeof(BITMAPINFOHEADER):

    // Convert WORD-aligned BITMAPINFOHEADER to aligned BITMAPINFO.

        pbmiSource->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
        pbmiSource->bmiHeader.biWidth         = READDWORD(&pbmihFile->biWidth);
        pbmiSource->bmiHeader.biHeight        = READDWORD(&pbmihFile->biHeight);
        pbmiSource->bmiHeader.biPlanes        = pbmihFile->biPlanes;
        pbmiSource->bmiHeader.biBitCount      = pbmihFile->biBitCount;
        pbmiSource->bmiHeader.biCompression   = 
                                        READDWORD(&pbmihFile->biCompression);
        pbmiSource->bmiHeader.biSizeImage     = 
                                        READDWORD(&pbmihFile->biSizeImage);
        pbmiSource->bmiHeader.biXPelsPerMeter = 
                                        READDWORD(&pbmihFile->biXPelsPerMeter);
        pbmiSource->bmiHeader.biYPelsPerMeter = 
                                        READDWORD(&pbmihFile->biYPelsPerMeter);
        pbmiSource->bmiHeader.biClrUsed       = 
                                        READDWORD(&pbmihFile->biClrUsed);
        pbmiSource->bmiHeader.biClrImportant  = 
                                        READDWORD(&pbmihFile->biClrImportant);

    // Copy color table.  It immediately follows the BITMAPINFOHEADER.

        memcpy((PVOID) &pbmiSource->bmiColors[0], (PVOID) (pbmihFile + 1),
               wNumColors * sizeof(RGBQUAD));

    // If we haven't already determined the position of the image bits,
    // we may now assume that they immediately follow the color table.

        if (!pvBits)
            pvBits = (PVOID) ((PBYTE) (pbmihFile + 1)
                         + wNumColors * sizeof(RGBQUAD));
        break;

    case sizeof(BITMAPCOREHEADER):
        pbmchFile = (BITMAPCOREHEADER *) pbmihFile;

    // Convert BITMAPCOREHEADER to BITMAPINFOHEADER.

        pbmiSource->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
        pbmiSource->bmiHeader.biWidth         = (DWORD) pbmchFile->bcWidth;
        pbmiSource->bmiHeader.biHeight        = (DWORD) pbmchFile->bcHeight;
        pbmiSource->bmiHeader.biPlanes        = pbmchFile->bcPlanes;
        pbmiSource->bmiHeader.biBitCount      = pbmchFile->bcBitCount;
        pbmiSource->bmiHeader.biCompression   = BI_RGB;
        pbmiSource->bmiHeader.biSizeImage     = 0;
        pbmiSource->bmiHeader.biXPelsPerMeter = 0;
        pbmiSource->bmiHeader.biYPelsPerMeter = 0;
        pbmiSource->bmiHeader.biClrUsed       = wNumColors;
        pbmiSource->bmiHeader.biClrImportant  = wNumColors;

    // Convert RGBTRIPLE color table into RGBQUAD color table.

        {
            RGBQUAD *rgb4 = pbmiSource->bmiColors;
            RGBTRIPLE *rgb3 = (RGBTRIPLE *) (pbmchFile + 1);

            for (i = 0; i < wNumColors; i++)
            {
                rgb4->rgbRed   = rgb3->rgbtRed  ;
                rgb4->rgbGreen = rgb3->rgbtGreen;
                rgb4->rgbBlue  = rgb3->rgbtBlue ;
                rgb4->rgbReserved = 0;

                rgb4++;
                rgb3++;
            }
        }

    // If we haven't already determined the position of the image bits,
    // we may now assume that they immediately follow the color table.

        if (!pvBits)
            pvBits = (PVOID) ((PBYTE) (pbmihFile + 1)
                         + wNumColors * sizeof(RGBTRIPLE));
        break;

    default:
        MESSAGEBOX(GetFocus(), "Unknown DIB file format.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

// Fill in default values (for fields that can have defaults).

    if (pbmiSource->bmiHeader.biSizeImage == 0)
        pbmiSource->bmiHeader.biSizeImage = 
            BITS2BYTES( (DWORD) pbmiSource->bmiHeader.biWidth * 
                                pbmiSource->bmiHeader.biBitCount ) * 
                                pbmiSource->bmiHeader.biHeight;
    if (pbmiSource->bmiHeader.biClrUsed == 0)
        pbmiSource->bmiHeader.biClrUsed = wNumColors;

// Create memory DC.

    hdcMem = CreateCompatibleDC(NULL);
    if (!hdcMem) {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

// Create a 24BPP RGB DIB section and select it into the memory DC.

    pbmiRGB = (BITMAPINFO *)
              LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, sizeof(BITMAPINFO) );
    if (!pbmiRGB)
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

    pbmiRGB->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
    pbmiRGB->bmiHeader.biWidth         = pbmiSource->bmiHeader.biWidth;
    pbmiRGB->bmiHeader.biHeight        = pbmiSource->bmiHeader.biHeight;
    pbmiRGB->bmiHeader.biPlanes        = 1;
    pbmiRGB->bmiHeader.biBitCount      = 24;
    pbmiRGB->bmiHeader.biCompression   = BI_RGB;
    pbmiRGB->bmiHeader.biSizeImage     = pbmiRGB->bmiHeader.biWidth
                                         * abs(pbmiRGB->bmiHeader.biHeight) * 3;

    hbmRGB = CreateDIBSection(hdcMem, pbmiRGB, DIB_RGB_COLORS, 
                              (PVOID *) &pjBitsRGB, NULL, 0);

    if (!hbmRGB)
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK); 
        goto tkDIBLoadImage_cleanup;
    }

    if (!SelectObject(hdcMem, hbmRGB))
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

// Slam the DIB file image into the memory DC.  GDI will do the work of
// translating whatever format the DIB file has into RGB format.

    if (!SetDIBits(hdcMem, hbmRGB, 0, pbmiSource->bmiHeader.biHeight, 
                   pvBits, pbmiSource, DIB_RGB_COLORS))
    {
        MESSAGEBOX(GetFocus(), "Image file conversion error.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }
    GdiFlush();     // make sure that SetDIBits executes

// Convert to TK image format (packed RGB format).
// Allocate with malloc to be consistent with tkRGBImageLoad (i.e., app
// can deallocate with free()).

    pjTKBits = (PBYTE) malloc(pbmiRGB->bmiHeader.biSizeImage);
    if (!pjTKBits)
    {
        MESSAGEBOX(GetFocus(), "Out of memory.", "Error", MB_OK);
        goto tkDIBLoadImage_cleanup;
    }

    pjSrc = pjBitsRGB;
    pjDst = pjTKBits;
    // src lines end on LONG boundary - so need to skip over any padding bytes
    padBytes = pbmiSource->bmiHeader.biWidth % sizeof(LONG);
    for (i = 0; i < pbmiSource->bmiHeader.biHeight; i++)
    {
        for (j = 0; j < pbmiSource->bmiHeader.biWidth; j++)
        {
            // swap R and B
            *pjDst++ = pjSrc[2];
            *pjDst++ = pjSrc[1];
            *pjDst++ = pjSrc[0];
            pjSrc += 3;
        }
        pjSrc += padBytes;
    }

// Initialize the texture structure

    // If we get to here, we have suceeded!
    ptex->width = pbmiSource->bmiHeader.biWidth;
    ptex->height = pbmiSource->bmiHeader.biHeight;
    ptex->format = GL_RGB;
    ptex->components = 3;
    ptex->data = pjTKBits;
    ptex->pal_size = 0;
    ptex->pal = NULL;

    fSuccess = TRUE;
    
// Cleanup objects.

tkDIBLoadImage_cleanup:
    {
        if (hdcMem)
            DeleteDC(hdcMem);

        if (hbmRGB)
            DeleteObject(hbmRGB);

        if (pbmiRGB)
            LocalFree(pbmiRGB);

        if (pbmiSource)
            LocalFree(pbmiSource);
    }

// Check for error.

    if (!fSuccess)
    {
        if (pjTKBits)
            free(pjTKBits);
    }

    return fSuccess;
}

/******************************Public*Routine******************************\
*
* bVerifyDIB
*
* Stripped down version of tkDIBImageLoadAW that verifies that a bitmap
* file is valid and, if so, returns the bitmap dimensions.
*
* Returns:
*   TRUE if valid bitmap file; otherwise, FALSE.
*
\**************************************************************************/

BOOL 
bVerifyDIB(LPTSTR pszFileName, ISIZE *pSize )
{
    BOOL bRet = FALSE;
    BITMAPFILEHEADER *pbmf;         // Ptr to file header
    BITMAPINFOHEADER *pbmihFile;    // Ptr to file's info header (if it exists)
    BITMAPCOREHEADER *pbmchFile;    // Ptr to file's core header (if it exists)

    // These need to be cleaned up when we exit:
    HANDLE     hFile = INVALID_HANDLE_VALUE;        // File handle
    HANDLE     hMap = (HANDLE) NULL;                // Mapping object handle
    PVOID      pvFile = (PVOID) NULL;               // Ptr to mapped file

// Map the DIB file into memory.

    hFile = CreateFile((LPTSTR) pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
    if (hFile == INVALID_HANDLE_VALUE)
        goto bVerifyDIB_cleanup;

    hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (!hMap)
        goto bVerifyDIB_cleanup;

    pvFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
    if (!pvFile)
        goto bVerifyDIB_cleanup;

// Check the file header.  If the BFT_BITMAP magic number is there,
// then the file format is a BITMAPFILEHEADER followed immediately
// by either a BITMAPINFOHEADER or a BITMAPCOREHEADER.  The bitmap
// bits, in this case, are located at the offset bfOffBits from the
// BITMAPFILEHEADER.
//
// Otherwise, this may be a raw BITMAPINFOHEADER or BITMAPCOREHEADER
// followed immediately with the color table and the bitmap bits.

    pbmf = (BITMAPFILEHEADER *) pvFile;

    if ( pbmf->bfType == BFT_BITMAP )
        pbmihFile = (BITMAPINFOHEADER *) ((PBYTE) pbmf + SIZEOF_BITMAPFILEHEADER);
    else
        pbmihFile = (BITMAPINFOHEADER *) pvFile;

// Get the width and height from whatever header we have.
//
// We distinguish between BITMAPINFO and BITMAPCORE cases based upon
// BITMAPINFOHEADER.biSize.

    // Note: need to use safe READDWORD macro because pbmihFile may
    // have only WORD alignment if it follows a BITMAPFILEHEADER.

    switch (READDWORD(&pbmihFile->biSize))
    {
    case sizeof(BITMAPINFOHEADER):

        if( pSize != NULL ) {
            pSize->width  = READDWORD(&pbmihFile->biWidth);
            pSize->height = READDWORD(&pbmihFile->biHeight);
        }
        bRet = TRUE;

        break;

    case sizeof(BITMAPCOREHEADER):
        pbmchFile = (BITMAPCOREHEADER *) pbmihFile;

    // Convert BITMAPCOREHEADER to BITMAPINFOHEADER.

        if( pSize != NULL ) {
            pSize->width  = (DWORD) pbmchFile->bcWidth;
            pSize->height = (DWORD) pbmchFile->bcHeight;
        }
        bRet = TRUE;

        break;

    default:
        break;
    }

bVerifyDIB_cleanup:

    if (pvFile)
        UnmapViewOfFile(pvFile);

    if (hMap)
        CloseHandle(hMap);

    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    return bRet;
}