//**********************************************************************
//
//  dibutil.c
//
//  Source file for Device-Independent Bitmap (DIB) API.  Provides
//  the following functions:
//
//  CreateDIB()         - Creates new DIB
//  FindDIBBits()       - Sets pointer to the DIB bits
//  DIBWidth()          - Gets the width of the DIB
//  DIBHeight()         - Gets the height of the DIB
//  PaletteSize()       - Calculates the buffer size required by a palette
//  DIBNumColors()      - Calculates number of colors in the DIB's color table
//  CreateDIBPalette()  - Creates a palette from a DIB
//  DIBToBitmap()       - Creates a bitmap from a DIB
//  BitmapToDIB()       - Creates a DIB from a bitmap
//  PalEntriesOnDevice()- Gets the number of palette entries of a device
//  GetSystemPalette()  - Returns a handle to the current system palette
//  AllocRoomForDIB()   - Allocates memory for a DIB
//  ChangeDIBFormat()   - Changes a DIB's BPP and/or compression format
//  ChangeBitmapFormat()- Changes a bitmap to a DIB with specified BPP and
//                        compression format
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright (C) 1991-1996 Microsoft Corporation. All rights reserved.
//**********************************************************************

#define     STRICT      // enable strict type checking

#include <windows.h>
#include <assert.h>
#include "dibapi.h"
#include "dibutil.h"
#include <stdio.h>


/*************************************************************************
 *
 * CreateDIB()
 *
 * Parameters:
 *
 * DWORD dwWidth    - Width for new bitmap, in pixels
 * DWORD dwHeight   - Height for new bitmap 
 * WORD  wBitCount  - Bit Count for new DIB (1, 4, 8, or 24)
 *
 * Return Value:
 *
 * HDIB             - Handle to new DIB
 *
 * Description:
 *
 * This function allocates memory for and initializes a new DIB by
 * filling in the BITMAPINFOHEADER, allocating memory for the color
 * table, and allocating memory for the bitmap bits.  As with all
 * HDIBs, the header, colortable and bits are all in one contiguous
 * memory block.  This function is similar to the CreateBitmap() 
 * Windows API.
 *
 * The colortable and bitmap bits are left uninitialized (zeroed) in the
 * returned HDIB.
 *
 *
 ************************************************************************/

HDIB CreateDIB(DWORD dwWidth, DWORD dwHeight, WORD wBitCount)
{
    BITMAPINFOHEADER    bi;             // bitmap header
    LPBITMAPINFOHEADER  lpbi;           // pointer to BITMAPINFOHEADER
    DWORD               dwLen;          // size of memory block
    HDIB                hDIB;
    DWORD               dwBytesPerLine; // Number of bytes per scanline


    // Make sure bits per pixel is valid

    if (wBitCount <= 1)
        wBitCount = 1;
    else if (wBitCount <= 4)
        wBitCount = 4;
    else if (wBitCount <= 8)
        wBitCount = 8;
    else if (wBitCount <= 24)
        wBitCount = 24;
    else
        wBitCount = 4;  // set default value to 4 if parameter is bogus

    // initialize BITMAPINFOHEADER

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = dwWidth;         // fill in width from parameter
    bi.biHeight = dwHeight;       // fill in height from parameter
    bi.biPlanes = 1;              // must be 1
    bi.biBitCount = wBitCount;    // from parameter
    bi.biCompression = BI_RGB;    
    bi.biSizeImage = 0;           // 0's here mean "default"
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    // calculate size of memory block required to store the DIB.  This
    // block should be big enough to hold the BITMAPINFOHEADER, the color
    // table, and the bits

    dwBytesPerLine = WIDTHBYTES(wBitCount * dwWidth);
    dwLen = bi.biSize + PaletteSize((LPSTR)&bi) + (dwBytesPerLine * dwHeight);

    // alloc memory block to store our bitmap

    hDIB = GlobalAlloc(GHND, dwLen);

    // major bummer if we couldn't get memory block

    if (!hDIB)
        return NULL;

    // lock memory and get pointer to it

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    // use our bitmap info structure to fill in first part of
    // our DIB with the BITMAPINFOHEADER

    *lpbi = bi;

    // Since we don't know what the colortable and bits should contain,
    // just leave these blank.  Unlock the DIB and return the HDIB.

    GlobalUnlock(hDIB);

    //return handle to the DIB

    return hDIB;
}


/*************************************************************************
 *
 * FindDIBBits()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * LPSTR            - pointer to the DIB bits
 *
 * Description:
 *
 * This function calculates the address of the DIB's bits and returns a
 * pointer to the DIB bits.
 *
 ************************************************************************/

LPSTR FindDIBBits(LPSTR lpDIB)
{
   return (lpDIB + *(LPDWORD)lpDIB + PaletteSize(lpDIB));
}


/*************************************************************************
 *
 * DIBWidth()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * DWORD            - width of the DIB
 *
 * Description:
 *
 * This function gets the width of the DIB from the BITMAPINFOHEADER
 * width field if it is a Windows 3.0-style DIB or from the BITMAPCOREHEADER
 * width field if it is an OS/2-style DIB.
 *
 ************************************************************************/


DWORD DIBWidth(LPSTR lpDIB)
{
    LPBITMAPINFOHEADER   lpbmi;  // pointer to a Win 3.0-style DIB
    LPBITMAPCOREHEADER   lpbmc;  // pointer to an OS/2-style DIB

    // point to the header (whether Win 3.0 and OS/2)

    lpbmi = (LPBITMAPINFOHEADER)lpDIB;
    lpbmc = (LPBITMAPCOREHEADER)lpDIB;

    // return the DIB width if it is a Win 3.0 DIB

    if (lpbmi->biSize == sizeof(BITMAPINFOHEADER))
        return lpbmi->biWidth;
    else  // it is an OS/2 DIB, so return its width
        return (DWORD)lpbmc->bcWidth;
}


/*************************************************************************
 *
 * DIBHeight()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * DWORD            - height of the DIB
 *
 * Description:
 *
 * This function gets the height of the DIB from the BITMAPINFOHEADER
 * height field if it is a Windows 3.0-style DIB or from the BITMAPCOREHEADER
 * height field if it is an OS/2-style DIB.
 *
 ************************************************************************/

DWORD DIBHeight(LPSTR lpDIB)
{
   LPBITMAPINFOHEADER   lpbmi;  // pointer to a Win 3.0-style DIB
   LPBITMAPCOREHEADER   lpbmc;  // pointer to an OS/2-style DIB

   // point to the header (whether OS/2 or Win 3.0

   lpbmi = (LPBITMAPINFOHEADER)lpDIB;
   lpbmc = (LPBITMAPCOREHEADER)lpDIB;

    // return the DIB height if it is a Win 3.0 DIB
    if (lpbmi->biSize == sizeof(BITMAPINFOHEADER))
        return lpbmi->biHeight;
    else  // it is an OS/2 DIB, so return its height
        return (DWORD)lpbmc->bcHeight;
}


/*************************************************************************
 *
 * PaletteSize()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * WORD             - size of the color palette of the DIB
 *
 * Description:
 *
 * This function gets the size required to store the DIB's palette by
 * multiplying the number of colors by the size of an RGBQUAD (for a
 * Windows 3.0-style DIB) or by the size of an RGBTRIPLE (for an OS/2-
 * style DIB).
 *
 ************************************************************************/

WORD PaletteSize(LPSTR lpDIB)
{
    // calculate the size required by the palette
    if (IS_WIN30_DIB (lpDIB))
        return (DIBNumColors(lpDIB) * sizeof(RGBQUAD));
    else
        return (DIBNumColors(lpDIB) * sizeof(RGBTRIPLE));
}


/*************************************************************************
 *
 * DIBNumColors()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * WORD             - number of colors in the color table
 *
 * Description:
 *
 * This function calculates the number of colors in the DIB's color table
 * by finding the bits per pixel for the DIB (whether Win3.0 or OS/2-style
 * DIB). If bits per pixel is 1: colors=2, if 4: colors=16, if 8: colors=256,
 * if 24, no colors in color table.
 *
 ************************************************************************/

WORD DIBNumColors(LPSTR lpDIB)
{
    WORD wBitCount;  // DIB bit count

    // If this is a Windows-style DIB, the number of colors in the
    // color table can be less than the number of bits per pixel
    // allows for (i.e. lpbi->biClrUsed can be set to some value).
    // If this is the case, return the appropriate value.
    

    if (IS_WIN30_DIB(lpDIB))
    {
        DWORD dwClrUsed;

        dwClrUsed = ((LPBITMAPINFOHEADER)lpDIB)->biClrUsed;
        if (dwClrUsed)

        return (WORD)dwClrUsed;
    }

    // Calculate the number of colors in the color table based on
    // the number of bits per pixel for the DIB.
    
    if (IS_WIN30_DIB(lpDIB))
        wBitCount = ((LPBITMAPINFOHEADER)lpDIB)->biBitCount;
    else
        wBitCount = ((LPBITMAPCOREHEADER)lpDIB)->bcBitCount;

    // return number of colors based on bits per pixel

    switch (wBitCount)
    {
        case 1:
            return 2;

        case 4:
            return 16;

        case 8:
            return 256;

        default:
            return 0;
    }
}


/*************************************************************************
 *
 * CreateDIBPalette()
 *
 * Parameter:
 *
 * HDIB hDIB        - specifies the DIB
 *
 * Return Value:
 *
 * HPALETTE         - specifies the palette
 *
 * Description:
 *
 * This function creates a palette from a DIB by allocating memory for the
 * logical palette, reading and storing the colors from the DIB's color table
 * into the logical palette, creating a palette from this logical palette,
 * and then returning the palette's handle. This allows the DIB to be
 * displayed using the best possible colors (important for DIBs with 256 or
 * more colors).
 *
 ************************************************************************/

HPALETTE CreateDIBPalette(HDIB hDIB)
{
    LPLOGPALETTE        lpPal;          // pointer to a logical palette
    HANDLE              hLogPal;        // handle to a logical palette
    HPALETTE            hPal = NULL;    // handle to a palette
    int                 i, wNumColors;  // loop index, number of colors in color table
    LPSTR               lpbi;           // pointer to packed-DIB
    LPBITMAPINFO        lpbmi;          // pointer to BITMAPINFO structure (Win3.0)
    LPBITMAPCOREINFO    lpbmc;          // pointer to BITMAPCOREINFO structure (OS/2)
    BOOL                bWinStyleDIB;   // Win3.0 DIB?

    // if handle to DIB is invalid, return NULL

    if (!hDIB)
        return NULL;

    // lock DIB memory block and get a pointer to it

    lpbi = GlobalLock(hDIB);

    // get pointer to BITMAPINFO (Win 3.0)

    lpbmi = (LPBITMAPINFO)lpbi;

    // get pointer to BITMAPCOREINFO (OS/2 1.x)

    lpbmc = (LPBITMAPCOREINFO)lpbi;

    // get the number of colors in the DIB 

    wNumColors = DIBNumColors(lpbi);

    // is this a Win 3.0 DIB?

    bWinStyleDIB = IS_WIN30_DIB(lpbi);
    if (wNumColors)
    {
        // allocate memory block for logical palette

        hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) +
                sizeof(PALETTEENTRY) * wNumColors);

        // if not enough memory, clean up and return NULL

        if (!hLogPal)
        {
            GlobalUnlock(hDIB);
            return NULL;
        }

        // lock memory block and get pointer to it

        lpPal = (LPLOGPALETTE)GlobalLock(hLogPal);

        // set version and number of palette entries

        lpPal->palVersion = PALVERSION;
        lpPal->palNumEntries = wNumColors;

        // store RGB triples (if Win 3.0 DIB) or RGB quads (if OS/2 DIB)
        // into palette
        
        for (i = 0; i < wNumColors; i++)
        {
            if (bWinStyleDIB)
            {
                lpPal->palPalEntry[i].peRed = lpbmi->bmiColors[i].rgbRed;
                lpPal->palPalEntry[i].peGreen = lpbmi->bmiColors[i].rgbGreen;
                lpPal->palPalEntry[i].peBlue = lpbmi->bmiColors[i].rgbBlue;
                lpPal->palPalEntry[i].peFlags = 0;
            }
            else
            {
                lpPal->palPalEntry[i].peRed = lpbmc->bmciColors[i].rgbtRed;
                lpPal->palPalEntry[i].peGreen = lpbmc->bmciColors[i].rgbtGreen;
                lpPal->palPalEntry[i].peBlue = lpbmc->bmciColors[i].rgbtBlue;
                lpPal->palPalEntry[i].peFlags = 0;
            }
        }

        // create the palette and get handle to it

        hPal = CreatePalette(lpPal);

        // if error getting handle to palette, clean up and return NULL

        if (!hPal)
        {
            GlobalUnlock(hLogPal);
            GlobalFree(hLogPal);
            return NULL;
        }
    }

    // clean up 

    GlobalUnlock(hLogPal);
    GlobalFree(hLogPal);
    GlobalUnlock(hDIB);

    // return handle to DIB's palette
    return hPal;
}


/*************************************************************************
 *
 * DIBToBitmap()
 *
 * Parameters:
 *
 * HDIB hDIB        - specifies the DIB to convert
 *
 * HPALETTE hPal    - specifies the palette to use with the bitmap
 *
 * Return Value:
 *
 * HBITMAP          - identifies the device-dependent bitmap
 *
 * Description:
 *
 * This function creates a bitmap from a DIB using the specified palette.
 * If no palette is specified, default is used.
 *
 * NOTE:
 *
 * The bitmap returned from this funciton is always a bitmap compatible
 * with the screen (e.g. same bits/pixel and color planes) rather than
 * a bitmap with the same attributes as the DIB.  This behavior is by
 * design, and occurs because this function calls CreateDIBitmap to
 * do its work, and CreateDIBitmap always creates a bitmap compatible
 * with the hDC parameter passed in (because it in turn calls
 * CreateCompatibleBitmap).
 *
 * So for instance, if your DIB is a monochrome DIB and you call this
 * function, you will not get back a monochrome HBITMAP -- you will
 * get an HBITMAP compatible with the screen DC, but with only 2
 * colors used in the bitmap.
 *
 * If your application requires a monochrome HBITMAP returned for a
 * monochrome DIB, use the function SetDIBits().
 *
 * Also, the DIBpassed in to the function is not destroyed on exit. This
 * must be done later, once it is no longer needed.
 *
 ************************************************************************/

HBITMAP DIBToBitmap(HDIB hDIB, HPALETTE hPal)
{
    LPSTR       lpDIBHdr, lpDIBBits;  // pointer to DIB header, pointer to DIB bits
    HBITMAP     hBitmap;            // handle to device-dependent bitmap
    HDC         hDC;                    // handle to DC
    HPALETTE    hOldPal = NULL;    // handle to a palette

    // if invalid handle, return NULL 

    if (!hDIB)
        return NULL;

    // lock memory block and get a pointer to it

    lpDIBHdr = GlobalLock(hDIB);

    // get a pointer to the DIB bits

    lpDIBBits = FindDIBBits(lpDIBHdr);

    // get a DC 

    hDC = GetDC(NULL);
    if (!hDC)
    {
        // clean up and return NULL

        GlobalUnlock(hDIB);
        return NULL;
    }

    // select and realize palette

    if (hPal)
        hOldPal = SelectPalette(hDC, hPal, FALSE);

    RealizePalette(hDC);

    // create bitmap from DIB info. and bits
    hBitmap = CreateDIBitmap(hDC, (LPBITMAPINFOHEADER)lpDIBHdr, CBM_INIT,
            lpDIBBits, (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS);

    // restore previous palette
    if (hOldPal)
        SelectPalette(hDC, hOldPal, FALSE);

    // clean up
    ReleaseDC(NULL, hDC);
    GlobalUnlock(hDIB);

    // return handle to the bitmap
    return hBitmap;
}


/*************************************************************************
 *
 * BitmapToDIB()
 *
 * Parameters:
 *
 * HBITMAP hBitmap  - specifies the bitmap to convert
 *
 * HPALETTE hPal    - specifies the palette to use with the bitmap
 *
 * Return Value:
 *
 * HDIB             - identifies the device-dependent bitmap
 *
 * Description:
 *
 * This function creates a DIB from a bitmap using the specified palette.
 *
 ************************************************************************/

HDIB BitmapToDIB(HBITMAP hBitmap, HPALETTE hPal)
{
    BITMAP              bm;         // bitmap structure
    BITMAPINFOHEADER    bi;         // bitmap header
    LPBITMAPINFOHEADER  lpbi;       // pointer to BITMAPINFOHEADER
    DWORD               dwLen;      // size of memory block
    HANDLE              hDIB, h;    // handle to DIB, temp handle
    HDC                 hDC;        // handle to DC
    WORD                biBits;     // bits per pixel

    // check if bitmap handle is valid

    if (!hBitmap)
        return NULL;

    // fill in BITMAP structure, return NULL if it didn't work

    if (!GetObject(hBitmap, sizeof(bm), (LPSTR)&bm))
        return NULL;

    // if no palette is specified, use default palette

    if (hPal == NULL)
        hPal = GetStockObject(DEFAULT_PALETTE);

    // calculate bits per pixel

    biBits = bm.bmPlanes * bm.bmBitsPixel;

    // make sure bits per pixel is valid

    if (biBits <= 1)
        biBits = 1;
    else if (biBits <= 4)
        biBits = 4;
    else if (biBits <= 8)
        biBits = 8;
    else // if greater than 8-bit, force to 24-bit
        biBits = 24;

    // initialize BITMAPINFOHEADER

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bm.bmWidth;
    bi.biHeight = bm.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = biBits;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    // calculate size of memory block required to store BITMAPINFO

    dwLen = bi.biSize + PaletteSize((LPSTR)&bi);

    // get a DC

    hDC = GetDC(NULL);

    // select and realize our palette

    hPal = SelectPalette(hDC, hPal, FALSE);
    RealizePalette(hDC);

    // alloc memory block to store our bitmap

    hDIB = GlobalAlloc(GHND, dwLen);

    // if we couldn't get memory block

    if (!hDIB)
    {
      // clean up and return NULL

      SelectPalette(hDC, hPal, TRUE);
      RealizePalette(hDC);
      ReleaseDC(NULL, hDC);
      return NULL;
    }

    // lock memory and get pointer to it

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    /// use our bitmap info. to fill BITMAPINFOHEADER

    *lpbi = bi;

    // call GetDIBits with a NULL lpBits param, so it will calculate the
    // biSizeImage field for us    

    GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, NULL, (LPBITMAPINFO)lpbi,
        DIB_RGB_COLORS);

    // get the info. returned by GetDIBits and unlock memory block

    bi = *lpbi;
    GlobalUnlock(hDIB);

    // if the driver did not fill in the biSizeImage field, make one up 
    if (bi.biSizeImage == 0)
        bi.biSizeImage = WIDTHBYTES((DWORD)bm.bmWidth * biBits) * bm.bmHeight;

    // realloc the buffer big enough to hold all the bits

    dwLen = bi.biSize + PaletteSize((LPSTR)&bi) + bi.biSizeImage;

    if (h = GlobalReAlloc(hDIB, dwLen, 0))
        hDIB = h;
    else
    {
        // clean up and return NULL

        GlobalFree(hDIB);
        hDIB = NULL;
        SelectPalette(hDC, hPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
        return NULL;
    }

    // lock memory block and get pointer to it */

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    // call GetDIBits with a NON-NULL lpBits param, and actualy get the
    // bits this time

    if (GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, (LPSTR)lpbi +
            (WORD)lpbi->biSize + PaletteSize((LPSTR)lpbi), (LPBITMAPINFO)lpbi,
            DIB_RGB_COLORS) == 0)
    {
        // clean up and return NULL

        GlobalUnlock(hDIB);
        hDIB = NULL;
        SelectPalette(hDC, hPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
        return NULL;
    }

    bi = *lpbi;

    // clean up 
    GlobalUnlock(hDIB);
    SelectPalette(hDC, hPal, TRUE);
    RealizePalette(hDC);
    ReleaseDC(NULL, hDC);

    // return handle to the DIB
    return hDIB;
}


/*************************************************************************
 *
 * PalEntriesOnDevice()
 *
 * Parameter:
 *
 * HDC hDC          - device context
 *
 * Return Value:
 *
 * int              - number of palette entries on device
 *
 * Description:
 *
 * This function gets the number of palette entries on the specified device
 *
 ************************************************************************/

int PalEntriesOnDevice(HDC hDC)
{
    int nColors;  // number of colors

    // Find out the number of colors on this device.
    
    nColors = (1 << (GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES)));

    assert(nColors);
    return nColors;
}


/*************************************************************************
 *
 * GetSystemPalette()
 *
 * Parameters:
 *
 * None
 *
 * Return Value:
 *
 * HPALETTE         - handle to a copy of the current system palette
 *
 * Description:
 *
 * This function returns a handle to a palette which represents the system
 * palette.  The system RGB values are copied into our logical palette using
 * the GetSystemPaletteEntries function.  
 *
 ************************************************************************/

HPALETTE GetSystemPalette(void)
{
    HDC hDC;                // handle to a DC
    static HPALETTE hPal = NULL;   // handle to a palette
    HANDLE hLogPal;         // handle to a logical palette
    LPLOGPALETTE lpLogPal;  // pointer to a logical palette
    int nColors;            // number of colors

    // Find out how many palette entries we want.

    hDC = GetDC(NULL);

    if (!hDC)
        return NULL;

    nColors = PalEntriesOnDevice(hDC);   // Number of palette entries

    // Allocate room for the palette and lock it.

    hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + nColors *
            sizeof(PALETTEENTRY));

    // if we didn't get a logical palette, return NULL

    if (!hLogPal)
        return NULL;

    // get a pointer to the logical palette

    lpLogPal = (LPLOGPALETTE)GlobalLock(hLogPal);

    // set some important fields

    lpLogPal->palVersion = PALVERSION;
    lpLogPal->palNumEntries = nColors;

    // Copy the current system palette into our logical palette

    GetSystemPaletteEntries(hDC, 0, nColors,
            (LPPALETTEENTRY)(lpLogPal->palPalEntry));

    // Go ahead and create the palette.  Once it's created,
    // we no longer need the LOGPALETTE, so free it.    

    hPal = CreatePalette(lpLogPal);

    // clean up

    GlobalUnlock(hLogPal);
    GlobalFree(hLogPal);
    ReleaseDC(NULL, hDC);

    return hPal;
}


/*************************************************************************
 *
 * AllocRoomForDIB()
 *
 * Parameters:
 *
 * BITMAPINFOHEADER - bitmap info header stucture
 *
 * HBITMAP          - handle to the bitmap
 *
 * Return Value:
 *
 * HDIB             - handle to memory block
 *
 * Description:
 *
 *  This routine takes a BITMAPINOHEADER, and returns a handle to global
 *  memory which can contain a DIB with that header.  It also initializes
 *  the header portion of the global memory.  GetDIBits() is used to determine
 *  the amount of room for the DIB's bits.  The total amount of memory
 *  needed = sizeof(BITMAPINFOHEADER) + size of color table + size of bits.
 *
 ************************************************************************/

HANDLE AllocRoomForDIB(BITMAPINFOHEADER bi, HBITMAP hBitmap)
{
    DWORD               dwLen;
    HANDLE              hDIB;
    HDC                 hDC;
    LPBITMAPINFOHEADER  lpbi;
    HANDLE              hTemp;

    // Figure out the size needed to hold the BITMAPINFO structure
    // (which includes the BITMAPINFOHEADER and the color table).

    dwLen = bi.biSize + PaletteSize((LPSTR) &bi);
    hDIB  = GlobalAlloc(GHND,dwLen);

    // Check that DIB handle is valid

    if (!hDIB)
        return NULL;

    // Set up the BITMAPINFOHEADER in the newly allocated global memory,
    // then call GetDIBits() with lpBits = NULL to have it fill in the
    // biSizeImage field for us.

    lpbi  = (LPBITMAPINFOHEADER)GlobalLock(hDIB);
    *lpbi = bi;

    hDC   = GetDC(NULL);

    GetDIBits(hDC, hBitmap, 0, (UINT) bi.biHeight, NULL, (LPBITMAPINFO)lpbi,
            DIB_RGB_COLORS);
    ReleaseDC(NULL, hDC);

    // If the driver did not fill in the biSizeImage field,
    // fill it in -- NOTE: this is a bug in the driver!
    
    if (lpbi->biSizeImage == 0)
        lpbi->biSizeImage = WIDTHBYTES((DWORD)lpbi->biWidth *
                lpbi->biBitCount) * lpbi->biHeight;

    // Get the size of the memory block we need

    dwLen = lpbi->biSize + PaletteSize((LPSTR) &bi) + lpbi->biSizeImage;

    // Unlock the memory block

    GlobalUnlock(hDIB);

    // ReAlloc the buffer big enough to hold all the bits 

    if (hTemp = GlobalReAlloc(hDIB,dwLen,0))
        return hTemp;
    else
    {
        // Else free memory block and return failure

        GlobalFree(hDIB);
        return NULL;
    }
}


/*************************************************************************
 *
 * ChangeDIBFormat()
 *
 * Parameter:
 *
 * HDIB             - handle to packed-DIB in memory
 *
 * WORD             - desired bits per pixel
 *
 * DWORD            - desired compression format
 *
 * Return Value:
 *
 * HDIB             - handle to the new DIB if successful, else NULL
 *
 * Description:
 *
 * This function will convert the bits per pixel and/or the compression
 * format of the specified DIB. Note: If the conversion was unsuccessful,
 * we return NULL. The original DIB is left alone. Don't use code like the
 * following:
 *
 *    hMyDIB = ChangeDIBFormat(hMyDIB, 8, BI_RLE4);
 *
 * The conversion will fail, but hMyDIB will now be NULL and the original
 * DIB will now hang around in memory. We could have returned the old
 * DIB, but we wanted to allow the programmer to check whether this
 * conversion succeeded or failed.
 *
 ************************************************************************/

HDIB ChangeDIBFormat(HDIB hDIB, WORD wBitCount, DWORD dwCompression)
{
    HDC                hDC;             // Handle to DC
    HBITMAP            hBitmap;         // Handle to bitmap
    BITMAP             Bitmap;          // BITMAP data structure
    BITMAPINFOHEADER   bi;              // Bitmap info header
    LPBITMAPINFOHEADER lpbi;            // Pointer to bitmap info
    HDIB               hNewDIB = NULL;  // Handle to new DIB
    HPALETTE           hPal, hOldPal;   // Handle to palette, prev pal
    WORD               DIBBPP, NewBPP;  // DIB bits per pixel, new bpp
    DWORD              DIBComp, NewComp;// DIB compression, new compression

    // Check for a valid DIB handle

    if (!hDIB)
        return NULL;

    // Get the old DIB's bits per pixel and compression format

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);
    DIBBPP = ((LPBITMAPINFOHEADER)lpbi)->biBitCount;
    DIBComp = ((LPBITMAPINFOHEADER)lpbi)->biCompression;
    GlobalUnlock(hDIB);

    // Validate wBitCount and dwCompression
    // They must match correctly (i.e., BI_RLE4 and 4 BPP or
    // BI_RLE8 and 8BPP, etc.) or we return failure
    if (wBitCount == 0)
    {
        NewBPP = DIBBPP;
        if ((dwCompression == BI_RLE4 && NewBPP == 4) ||
                (dwCompression == BI_RLE8 && NewBPP == 8) ||
                (dwCompression == BI_RGB))
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 1 && dwCompression == BI_RGB)
    {
        NewBPP = wBitCount;
        NewComp = BI_RGB;
    }
    else if (wBitCount == 4)
    {
        NewBPP = wBitCount;
        if (dwCompression == BI_RGB || dwCompression == BI_RLE4)
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 8)
    {
        NewBPP = wBitCount;
        if (dwCompression == BI_RGB || dwCompression == BI_RLE8)
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 24 && dwCompression == BI_RGB)
    {
        NewBPP = wBitCount;
        NewComp = BI_RGB;
    }
    else
        return NULL;

    // Save the old DIB's palette

    hPal = CreateDIBPalette(hDIB);
    if (!hPal)
        return NULL;

    // Convert old DIB to a bitmap

    hBitmap = DIBToBitmap(hDIB, hPal);
    if (!hBitmap)
    {
        DeleteObject(hPal);
        return NULL;
    }

    // Get info about the bitmap
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap);

    // Fill in the BITMAPINFOHEADER appropriately

    bi.biSize               = sizeof(BITMAPINFOHEADER);
    bi.biWidth              = Bitmap.bmWidth;
    bi.biHeight             = Bitmap.bmHeight;
    bi.biPlanes             = 1;
    bi.biBitCount           = NewBPP;
    bi.biCompression        = NewComp;
    bi.biSizeImage          = 0;
    bi.biXPelsPerMeter      = 0;
    bi.biYPelsPerMeter      = 0;
    bi.biClrUsed            = 0;
    bi.biClrImportant       = 0;

    // Go allocate room for the new DIB

    hNewDIB = AllocRoomForDIB(bi, hBitmap);
    if (!hNewDIB)
        return NULL;

    // Get a pointer to the new DIB

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hNewDIB);

    // Get a DC and select/realize our palette in it

    hDC  = GetDC(NULL);
    hOldPal = SelectPalette(hDC, hPal, FALSE);
    RealizePalette(hDC);

    // Call GetDIBits and get the new DIB bits

    if (!GetDIBits(hDC, hBitmap, 0, (UINT) lpbi->biHeight,
            (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize((LPSTR)lpbi),
            (LPBITMAPINFO)lpbi, DIB_RGB_COLORS))
    {
        GlobalUnlock(hNewDIB);
        GlobalFree(hNewDIB);
        hNewDIB = NULL;
    }

    // Clean up and return

    SelectPalette(hDC, hOldPal, TRUE);
    RealizePalette(hDC);
    ReleaseDC(NULL, hDC);

    // Unlock the new DIB's memory block
    if (hNewDIB)
        GlobalUnlock(hNewDIB);

    DeleteObject(hBitmap);
    DeleteObject(hPal);

    return hNewDIB;
}


/*************************************************************************
 *
 * ChangeBitmapFormat()
 *
 * Parameter:
 *
 * HBITMAP          - handle to a bitmap
 *
 * WORD             - desired bits per pixel
 *
 * DWORD            - desired compression format
 *
 * HPALETTE         - handle to palette
 *
 * Return Value:
 *
 * HDIB             - handle to the new DIB if successful, else NULL
 *
 * Description:
 *
 * This function will convert a bitmap to the specified bits per pixel
 * and compression format. The bitmap and it's palette will remain
 * after calling this function.
 *
 ************************************************************************/

HDIB ChangeBitmapFormat(HBITMAP hBitmap, WORD wBitCount, DWORD dwCompression,
        HPALETTE hPal)
{
    HDC                hDC;          // Screen DC
    HDIB               hNewDIB=NULL; // Handle to new DIB
    BITMAP             Bitmap;       // BITMAP data structure
    BITMAPINFOHEADER   bi;           // Bitmap info. header
    LPBITMAPINFOHEADER lpbi;         // Pointer to bitmap header
    HPALETTE           hOldPal=NULL; // Handle to palette
    WORD               NewBPP;       // New bits per pixel
    DWORD              NewComp;      // New compression format

    // Check for a valid bitmap handle

    if (!hBitmap)
        return NULL;

    // Validate wBitCount and dwCompression
    // They must match correctly (i.e., BI_RLE4 and 4 BPP or
    // BI_RLE8 and 8BPP, etc.) or we return failure
    
    if (wBitCount == 0)
    {
        NewComp = dwCompression;
        if (NewComp == BI_RLE4)
            NewBPP = 4;
        else if (NewComp == BI_RLE8)
            NewBPP = 8;
        else // Not enough info */
            return NULL;
    }
    else if (wBitCount == 1 && dwCompression == BI_RGB)
    {
        NewBPP = wBitCount;
        NewComp = BI_RGB;
    }
    else if (wBitCount == 4)
    {
        NewBPP = wBitCount;
        if (dwCompression == BI_RGB || dwCompression == BI_RLE4)
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 8)
    {
        NewBPP = wBitCount;
        if (dwCompression == BI_RGB || dwCompression == BI_RLE8)
            NewComp = dwCompression;
        else
            return NULL;
    }
    else if (wBitCount == 24 && dwCompression == BI_RGB)
    {
        NewBPP = wBitCount;
        NewComp = BI_RGB;
    }
    else
        return NULL;

    // Get info about the bitmap

    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap);

    // Fill in the BITMAPINFOHEADER appropriately

    bi.biSize               = sizeof(BITMAPINFOHEADER);
    bi.biWidth              = Bitmap.bmWidth;
    bi.biHeight             = Bitmap.bmHeight;
    bi.biPlanes             = 1;
    bi.biBitCount           = NewBPP;
    bi.biCompression        = NewComp;
    bi.biSizeImage          = 0;
    bi.biXPelsPerMeter      = 0;
    bi.biYPelsPerMeter      = 0;
    bi.biClrUsed            = 0;
    bi.biClrImportant       = 0;

    // Go allocate room for the new DIB

    hNewDIB = AllocRoomForDIB(bi, hBitmap);
    if (!hNewDIB)
        return NULL;

    // Get a pointer to the new DIB

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hNewDIB);

    // If we have a palette, get a DC and select/realize it

    if (hPal)
    {
        hDC  = GetDC(NULL);
        hOldPal = SelectPalette(hDC, hPal, FALSE);
        RealizePalette(hDC);
    }

    // Call GetDIBits and get the new DIB bits

    if (!GetDIBits(hDC, hBitmap, 0, (UINT) lpbi->biHeight, (LPSTR)lpbi +
            (WORD)lpbi->biSize + PaletteSize((LPSTR)lpbi), (LPBITMAPINFO)lpbi,
            DIB_RGB_COLORS))
    {
        GlobalUnlock(hNewDIB);
        GlobalFree(hNewDIB);
        hNewDIB = NULL;
    }

    // Clean up and return

    if (hOldPal)
    {
        SelectPalette(hDC, hOldPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
    }

    // Unlock the new DIB's memory block

    if (hNewDIB)
        GlobalUnlock(hNewDIB);

    return hNewDIB;
}
