Figure 3   Colors

Colors.h

 //***************************************************************************
//
//  Colors.h
//
//***************************************************************************

#define IDR_MENU                    100
#define IDD_COLORBITSDLG            101
#define IDC_COLORBITS               102
#define IDC_SPINBUTTON              103

#define IDM_OPEN                    200
#define IDM_NO_PALETTE              201
#define IDM_HALFTONE_PALETTE        202
#define IDM_OPTIMIZED_PALETTE       203
#define IDM_COLOR_BITS              204
#define IDM_EXIT                    205

typedef struct _NODE {
    BOOL bIsLeaf;               // TRUE if node has no children
    UINT nPixelCount;           // Number of pixels represented by this leaf
    UINT nRedSum;               // Sum of red components
    UINT nGreenSum;             // Sum of green components
    UINT nBlueSum;              // Sum of blue components
    struct _NODE* pChild[8];    // Pointers to child nodes
    struct _NODE* pNext;        // Pointer to next reducible node
} NODE;

Colors.rc

 //***********************************************************************
//
//  Colors.rc
//
//***********************************************************************

#include <windows.h>
#include <commctrl.h>
#include "Colors.h"

IDR_MENU MENU
BEGIN
    POPUP "&Options" {
        MENUITEM "&Open...",            IDM_OPEN
        MENUITEM SEPARATOR
        MENUITEM "&No Palette",         IDM_NO_PALETTE
        MENUITEM "&Halftone Palette",   IDM_HALFTONE_PALETTE,   CHECKED
        MENUITEM "Optimi&zed Palette",  IDM_OPTIMIZED_PALETTE
        MENUITEM SEPARATOR
        MENUITEM "&Color Bits...",      IDM_COLOR_BITS
        MENUITEM SEPARATOR
        MENUITEM "E&xit",               IDM_EXIT
    }
END

IDD_COLORBITSDLG DIALOG 0, 0, 144, 72
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_VISIBLE |
    WS_SYSMENU
CAPTION "Set Color Bits"
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "Significant Color Bits", -1, 16, 24, 68, 8
    EDITTEXT        IDC_COLORBITS, 88, 20, 40, 14, ES_AUTOHSCROLL
    CONTROL         "", IDC_SPINBUTTON, "MSCTLS_UPDOWN32", UDS_SETBUDDYINT |
                    UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_ALIGNRIGHT, 0, 0, 0, 0
    DEFPUSHBUTTON   "OK", IDOK, 16, 48, 50, 14, WS_GROUP
    PUSHBUTTON      "Cancel", IDCANCEL, 80, 48, 50, 14, WS_GROUP
END

Colors.c

 //***************************************************************************
//
//  Colors presents an implementation of the Gervautz-Purgathofer octree
//  color quanitization algorithm that creates optimized color palettes for
//  for 16, 24, and 32-bit DIB sections
//
//***************************************************************************

#include <windows.h>
#include <commctrl.h>
#include "Colors.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK DlgProc (HWND, UINT, WPARAM, LPARAM);

HPALETTE CreateExactPalette (HANDLE);
HPALETTE CreateOctreePalette (HANDLE, UINT, UINT);
void AddColor (NODE**, BYTE, BYTE, BYTE, UINT, UINT, UINT*, NODE**);
NODE* CreateNode (UINT, UINT, UINT*, NODE**);
void ReduceTree (UINT, UINT*, NODE**);
void DeleteTree (NODE**);
void GetPaletteColors (NODE*, PALETTEENTRY*, UINT*);
int GetRightShiftCount (DWORD);
int GetLeftShiftCount (DWORD);

void UpdatePaletteType (HWND, UINT);
BOOL DoFileOpen (HWND);
void UpdateWindowTitle (HWND, LPSTR);
void DisplayDIBSection (HDC, HANDLE, HPALETTE);
HPALETTE GetPaletteHandle (HWND, HANDLE);
UINT BitsPerPixel (HANDLE);

/////////////////////////////////////////////////////////////////////////////
// Global variables

UINT        g_nPaletteType          = IDM_HALFTONE_PALETTE;
HANDLE      g_hImage                = NULL;
HPALETTE    g_hPalette              = NULL;
UINT        g_nColorBits            = 6;

/////////////////////////////////////////////////////////////////////////////
// WinMain

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    static char szAppName[] = "Colors";
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style = 0;
    wc.lpfnWndProc = (WNDPROC) WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName = MAKEINTRESOURCE (IDR_MENU);
    wc.lpszClassName = szAppName;

    RegisterClass (&wc);

    hwnd = CreateWindow (szAppName, szAppName,
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);

    ShowWindow (hwnd, nCmdShow);
    UpdateWindow (hwnd);

    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    return msg.wParam;
}

/////////////////////////////////////////////////////////////////////////////
// Window procedure

LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    UINT nColors;

    switch (msg) {

    case WM_CREATE:
        InitCommonControls ();
        return 0;

    case WM_PAINT:
        hdc = BeginPaint (hwnd, &ps);
        if (g_hImage != NULL)
            DisplayDIBSection (hdc, g_hImage, g_hPalette);
        EndPaint (hwnd, &ps);
        return 0;

    case WM_QUERYNEWPALETTE:
        if (g_hPalette != NULL) {
            hdc = GetDC (hwnd);
            SelectPalette (hdc, g_hPalette, FALSE);

            if (nColors = RealizePalette (hdc))
                InvalidateRect (hwnd, NULL, FALSE);

            ReleaseDC (hwnd, hdc);
            return nColors;
        }
        break;

    case WM_PALETTECHANGED:
        if ((g_hPalette != NULL) && ((HWND) wParam != hwnd)) {
            hdc = GetDC (hwnd);
            SelectPalette (hdc, g_hPalette, FALSE);

            if (RealizePalette (hdc))
                InvalidateRect (hwnd, NULL, FALSE);

            ReleaseDC (hwnd, hdc);
            return 0;
        }
        break;

    case WM_COMMAND:
        switch (LOWORD (wParam)) {

        case IDM_OPEN:
            if (DoFileOpen (hwnd)) {
                if (g_hPalette != NULL)
                    DeleteObject (g_hPalette);
                g_hPalette = GetPaletteHandle (hwnd, g_hImage);
                InvalidateRect (hwnd, NULL, TRUE);
            }
            return 0;

        case IDM_NO_PALETTE:
            UpdatePaletteType (hwnd, IDM_NO_PALETTE);
            return 0;

        case IDM_HALFTONE_PALETTE:
            UpdatePaletteType (hwnd, IDM_HALFTONE_PALETTE);
            return 0;


        case IDM_OPTIMIZED_PALETTE:
            UpdatePaletteType (hwnd, IDM_OPTIMIZED_PALETTE);
            return 0;

        case IDM_COLOR_BITS:
            if (DialogBox ((HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE),
                MAKEINTRESOURCE (IDD_COLORBITSDLG), hwnd, DlgProc)) {
                if (g_hPalette != NULL)
                    DeleteObject (g_hPalette);
                if (g_hImage != NULL) {
                    g_hPalette = GetPaletteHandle (hwnd, g_hImage);
                    InvalidateRect (hwnd, NULL, FALSE);
                }
            }
            return 0;

        case IDM_EXIT:
            PostMessage (hwnd, WM_CLOSE, 0, 0);
            return 0;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage (0);
        return 0;
    }
    return DefWindowProc (hwnd, msg, wParam, lParam);
}

/////////////////////////////////////////////////////////////////////////////
// Palette generation routines and helpers

HPALETTE CreateExactPalette (HANDLE hImage)
{
    DIBSECTION ds;
    UINT nColors, i;
    RGBQUAD* prgb;
    LOGPALETTE* plp;
    HDC hdc, hdcMem;
    HBITMAP hOldBitmap;
    HPALETTE hPalette;
    DWORD dwSize;

    // Get the number of colors in the image
    GetObject (hImage, sizeof (ds), &ds);
    if (ds.dsBmih.biClrUsed != 0)
        nColors = ds.dsBmih.biClrUsed;
    else
        nColors = 1 << ds.dsBmih.biBitCount;

    if (nColors > 256) // Sanity check
        return NULL;

    // Retrieve the image's color table
    if ((prgb = (RGBQUAD*) HeapAlloc (GetProcessHeap (), 0,
        nColors * sizeof (RGBQUAD))) == NULL)
        return NULL;

    hdc = GetDC (NULL);
    hdcMem = CreateCompatibleDC (hdc);
    hOldBitmap = SelectObject (hdcMem, hImage);
    nColors = min (nColors, GetDIBColorTable (hdcMem, 0, nColors, prgb));
    SelectObject (hdcMem, hOldBitmap);
    DeleteDC (hdcMem);
    ReleaseDC (NULL, hdc);

    if (nColors == 0) { // Another sanity check
        HeapFree (GetProcessHeap (), 0, prgb);
        return NULL;
    }
        
    // Create a logical palette from the colors in the color table
    dwSize = sizeof (LOGPALETTE) + ((nColors - 1) * sizeof (PALETTEENTRY));
    if ((plp = (LOGPALETTE*) HeapAlloc (GetProcessHeap (), 0,
        dwSize)) == NULL) {
        HeapFree (GetProcessHeap (), 0, prgb);
        return NULL;
    }

    plp->palVersion = 0x300;
    plp->palNumEntries = (WORD) nColors;

    for (i=0; i<nColors; i++) {
        plp->palPalEntry[i].peRed   = prgb[i].rgbRed;
        plp->palPalEntry[i].peGreen = prgb[i].rgbGreen;
        plp->palPalEntry[i].peBlue  = prgb[i].rgbBlue;
        plp->palPalEntry[i].peFlags = 0;
    }

    hPalette = CreatePalette (plp);

    HeapFree (GetProcessHeap (), 0, plp);
    HeapFree (GetProcessHeap (), 0, prgb);
    return hPalette;
}

HPALETTE CreateOctreePalette (HANDLE hImage, UINT nMaxColors, UINT nColorBits)
{
    DIBSECTION ds;
    int i, j, nPad;
    BYTE* pbBits;
    WORD* pwBits;
    DWORD* pdwBits;
    DWORD rmask, gmask, bmask;
    int rright, gright, bright;
    int rleft, gleft, bleft;
    BYTE r, g, b;
    WORD wColor;
    DWORD dwColor, dwSize;
    LOGPALETTE* plp;
    HPALETTE hPalette;
    NODE* pTree;
    UINT nLeafCount, nIndex;
    NODE* pReducibleNodes[9];

    // Initialize octree variables
    pTree = NULL;
    nLeafCount = 0;
    if (nColorBits > 8) // Just in case
        return NULL;
    for (i=0; i<=(int) nColorBits; i++)
        pReducibleNodes[i] = NULL;

    // Scan the DIB and build the octree
    GetObject (hImage, sizeof (ds), &ds);
    nPad = ds.dsBm.bmWidthBytes - (((ds.dsBmih.biWidth *
        ds.dsBmih.biBitCount) + 7) / 8);

    switch (ds.dsBmih.biBitCount) {

    case 16: // One case for 16-bit DIBs
        if (ds.dsBmih.biCompression == BI_BITFIELDS) {
            rmask = ds.dsBitfields[0];
            gmask = ds.dsBitfields[1];
            bmask = ds.dsBitfields[2];
        }
        else {
            rmask = 0x7C00;
            gmask = 0x03E0;
            bmask = 0x001F;
        }

        rright = GetRightShiftCount (rmask);
        gright = GetRightShiftCount (gmask);
        bright = GetRightShiftCount (bmask);

        rleft = GetLeftShiftCount (rmask);
        gleft = GetLeftShiftCount (gmask);
        bleft = GetLeftShiftCount (bmask);

        pwBits = (WORD*) ds.dsBm.bmBits;
        for (i=0; i<ds.dsBmih.biHeight; i++) {
            for (j=0; j<ds.dsBmih.biWidth; j++) {
                wColor = *pwBits++;
                b = (BYTE) (((wColor & (WORD) bmask) >> bright) << bleft);
                g = (BYTE) (((wColor & (WORD) gmask) >> gright) << gleft);
                r = (BYTE) (((wColor & (WORD) rmask) >> rright) << rleft);
                AddColor (&pTree, r, g, b, nColorBits, 0, &nLeafCount,
                          pReducibleNodes);
                while (nLeafCount > nMaxColors)
                    ReduceTree (nColorBits, &nLeafCount, pReducibleNodes);
            }
            pwBits = (WORD*) (((BYTE*) pwBits) + nPad);
        }
        break;

    case 24: // Another for 24-bit DIBs
        pbBits = (BYTE*) ds.dsBm.bmBits;
        for (i=0; i<ds.dsBmih.biHeight; i++) {
            for (j=0; j<ds.dsBmih.biWidth; j++) {
                b = *pbBits++;
                g = *pbBits++;
                r = *pbBits++;
                AddColor (&pTree, r, g, b, nColorBits, 0, &nLeafCount,
                          pReducibleNodes);
                while (nLeafCount > nMaxColors)
                    ReduceTree (nColorBits, &nLeafCount, pReducibleNodes);
            }
            pbBits += nPad;
        }
        break;

    case 32: // And another for 32-bit DIBs
        if (ds.dsBmih.biCompression == BI_BITFIELDS) {
            rmask = ds.dsBitfields[0];
            gmask = ds.dsBitfields[1];
            bmask = ds.dsBitfields[2];
        }
        else {
            rmask = 0x00FF0000;
            gmask = 0x0000FF00;
            bmask = 0x000000FF;
        }

        rright = GetRightShiftCount (rmask);
        gright = GetRightShiftCount (gmask);
        bright = GetRightShiftCount (bmask);

        pdwBits = (DWORD*) ds.dsBm.bmBits;
        for (i=0; i<ds.dsBmih.biHeight; i++) {
            for (j=0; j<ds.dsBmih.biWidth; j++) {
                dwColor = *pdwBits++;
                b = (BYTE) ((dwColor & bmask) >> bright);
                g = (BYTE) ((dwColor & gmask) >> gright);
                r = (BYTE) ((dwColor & rmask) >> rright);
                AddColor (&pTree, r, g, b, nColorBits, 0, &nLeafCount,
                          pReducibleNodes);
                while (nLeafCount > nMaxColors)
                    ReduceTree (nColorBits, &nLeafCount, pReducibleNodes);
            }
            pdwBits = (DWORD*) (((BYTE*) pdwBits) + nPad);
        }
        break;

    default: // DIB must be 16, 24, or 32-bit!
        return NULL;
    }

    if (nLeafCount > nMaxColors) { // Sanity check
        DeleteTree (&pTree);
        return NULL;
    }

    // Create a logical palette from the colors in the octree
    dwSize = sizeof (LOGPALETTE) + ((nLeafCount - 1) * sizeof (PALETTEENTRY));
    if ((plp = (LOGPALETTE*) HeapAlloc (GetProcessHeap (), 0,
        dwSize)) == NULL) {
        DeleteTree (&pTree);
        return NULL;
    }

    plp->palVersion = 0x300;
    plp->palNumEntries = (WORD) nLeafCount;
    nIndex = 0;
    GetPaletteColors (pTree, plp->palPalEntry, &nIndex);
    hPalette = CreatePalette (plp);

    HeapFree (GetProcessHeap (), 0, plp);
    DeleteTree (&pTree);
    return hPalette;
}

void AddColor (NODE** ppNode, BYTE r, BYTE g, BYTE b, UINT nColorBits,
    UINT nLevel, UINT* pLeafCount, NODE** pReducibleNodes)
{
    int nIndex, shift;
    static BYTE mask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };

    // If the node doesn't exist, create it
    if (*ppNode == NULL)
        *ppNode = CreateNode (nLevel, nColorBits, pLeafCount,
                              pReducibleNodes);

    // Update color information if it's a leaf node
    if ((*ppNode)->bIsLeaf) {
        (*ppNode)->nPixelCount++;
        (*ppNode)->nRedSum += r;
        (*ppNode)->nGreenSum += g;
        (*ppNode)->nBlueSum += b;
    }

    // Recurse a level deeper if the node is not a leaf
    else {
        shift = 7 - nLevel;
        nIndex = (((r & mask[nLevel]) >> shift) << 2) |
            (((g & mask[nLevel]) >> shift) << 1) |
            ((b & mask[nLevel]) >> shift);
        AddColor (&((*ppNode)->pChild[nIndex]), r, g, b, nColorBits,
                  nLevel + 1, pLeafCount, pReducibleNodes);
    }
}

NODE* CreateNode (UINT nLevel, UINT nColorBits, UINT* pLeafCount,
                  NODE** pReducibleNodes)
{
    NODE* pNode;

    if ((pNode = (NODE*) HeapAlloc (GetProcessHeap (), HEAP_ZERO_MEMORY,
        sizeof (NODE))) == NULL)
        return NULL;

    pNode->bIsLeaf = (nLevel == nColorBits) ? TRUE : FALSE;
    if (pNode->bIsLeaf)
        (*pLeafCount)++;
    else { // Add the node to the reducible list for this level
        pNode->pNext = pReducibleNodes[nLevel];
        pReducibleNodes[nLevel] = pNode;
    }
    return pNode;
}

void ReduceTree (UINT nColorBits, UINT* pLeafCount, NODE** pReducibleNodes)
{
    int i;
    NODE* pNode;
    UINT nRedSum, nGreenSum, nBlueSum, nChildren;

    // Find the deepest level containing at least one reducible node
    for (i=nColorBits - 1; (i>0) && (pReducibleNodes[i] == NULL); i--);

    // Reduce the node most recently added to the list at level i
    pNode = pReducibleNodes[i];
    pReducibleNodes[i] = pNode->pNext;

    nRedSum = nGreenSum = nBlueSum = nChildren = 0;
    for (i=0; i<8; i++) {
        if (pNode->pChild[i] != NULL) {
            nRedSum += pNode->pChild[i]->nRedSum;
            nGreenSum += pNode->pChild[i]->nGreenSum;
            nBlueSum += pNode->pChild[i]->nBlueSum;
            pNode->nPixelCount += pNode->pChild[i]->nPixelCount;
            HeapFree (GetProcessHeap (), 0, pNode->pChild[i]);
            pNode->pChild[i] = NULL;
            nChildren++;
        }
    }

    pNode->bIsLeaf = TRUE;
    pNode->nRedSum = nRedSum;
    pNode->nGreenSum = nGreenSum;
    pNode->nBlueSum = nBlueSum;
    *pLeafCount -= (nChildren - 1);
}

void DeleteTree (NODE** ppNode)
{
    int i;

    for (i=0; i<8; i++) {
        if ((*ppNode)->pChild[i] != NULL)
            DeleteTree (&((*ppNode)->pChild[i]));
    }
    HeapFree (GetProcessHeap (), 0, *ppNode);
    *ppNode = NULL;
}

void GetPaletteColors (NODE* pTree, PALETTEENTRY* pPalEntries, UINT* pIndex)
{
    int i;

    if (pTree->bIsLeaf) {
        pPalEntries[*pIndex].peRed =
            (BYTE) ((pTree->nRedSum) / (pTree->nPixelCount));
        pPalEntries[*pIndex].peGreen =
            (BYTE) ((pTree->nGreenSum) / (pTree->nPixelCount));
        pPalEntries[*pIndex].peBlue =
            (BYTE) ((pTree->nBlueSum) / (pTree->nPixelCount));
        (*pIndex)++;
    }
    else {
        for (i=0; i<8; i++) {
            if (pTree->pChild[i] != NULL)
                GetPaletteColors (pTree->pChild[i], pPalEntries, pIndex);
        }
    }
}

int GetRightShiftCount (DWORD dwVal)
{
    int i;

    for (i=0; i<sizeof (DWORD) * 8; i++) {
        if (dwVal & 1)
            return i;
        dwVal >>= 1;
    }
    return -1;
}

int GetLeftShiftCount (DWORD dwVal)
{
    int nCount, i;

    nCount = 0;
    for (i=0; i<sizeof (DWORD) * 8; i++) {
        if (dwVal & 1)
            nCount++;
        dwVal >>= 1;
    }
    return (8 - nCount);
}

/////////////////////////////////////////////////////////////////////////////
// Other helper routines

void UpdatePaletteType (HWND hwnd, UINT nNewPaletteType)
{
    HMENU hMenu;

    hMenu = GetMenu (hwnd);
    CheckMenuItem (hMenu, g_nPaletteType, MF_UNCHECKED);
    CheckMenuItem (hMenu, nNewPaletteType, MF_CHECKED);
    g_nPaletteType = nNewPaletteType;

    if (g_hPalette != NULL)
        DeleteObject (g_hPalette);

    if (g_hImage != NULL) {
        g_hPalette = GetPaletteHandle (hwnd, g_hImage);
        InvalidateRect (hwnd, NULL, FALSE);
    }
}

BOOL DoFileOpen (HWND hwnd)
{
    OPENFILENAME ofn;
    char szFileName[MAX_PATH];
    char szFileTitle[MAX_PATH];
    char szErrMsg[MAX_PATH + 32];
    HANDLE hImage;

    szFileName[0] = 0;
    szFileTitle[0] = 0;
    ZeroMemory (&ofn, sizeof (ofn));
    ofn.lStructSize         = sizeof (ofn); 
    ofn.hwndOwner           = hwnd;
    ofn.lpstrFilter         = "BMP Files\0*.bmp\0All Files\0*.*\0"; 
    ofn.lpstrFile           = szFileName; 
    ofn.nMaxFile            = sizeof (szFileName); 
    ofn.lpstrFileTitle      = szFileTitle; 
    ofn.nMaxFileTitle       = sizeof (szFileTitle); 
    ofn.Flags               = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY |
                              OFN_EXPLORER | OFN_SHOWHELP; 
    ofn.lpstrDefExt         = "bmp"; 

    if (GetOpenFileName (&ofn)) {
        UpdateWindowTitle (hwnd, ofn.lpstrFileTitle);

        hImage = LoadImage (NULL, ofn.lpstrFile, IMAGE_BITMAP, 0, 0,
            LR_LOADFROMFILE | LR_CREATEDIBSECTION);
        if (hImage != NULL) {
            if (g_hImage != NULL)
                DeleteObject (g_hImage);
            g_hImage = hImage;
            return TRUE;
        }
        else { // LoadImage failed
            lstrcpy (szErrMsg, szFileTitle);
            lstrcat (szErrMsg, " does not contain a valid DIB");
            MessageBox (hwnd, szErrMsg, "Error", MB_ICONEXCLAMATION | MB_OK);
        }
    }
    return FALSE;
}

void UpdateWindowTitle (HWND hwnd, LPSTR pszDocName)
{
    char szTitle[MAX_PATH + 16];

    lstrcpy (szTitle, pszDocName);
    lstrcat (szTitle, " - Colors");
    SetWindowText (hwnd, szTitle);
}

void DisplayDIBSection (HDC hdc, HANDLE hImage, HPALETTE hPalette)
{
    HDC hdcMem;
    HBITMAP hOldBitmap;
    DIBSECTION ds;
    int cx, cy;

    hdcMem = CreateCompatibleDC (hdc);
    if (hPalette != NULL) {
        SelectPalette (hdcMem, hPalette, FALSE);
        RealizePalette (hdcMem);
        SelectPalette (hdc, hPalette, FALSE);
        RealizePalette (hdc);
    }

    GetObject (g_hImage, sizeof (ds), &ds);
    cx = ds.dsBmih.biWidth;
    cy = ds.dsBmih.biHeight;
    hOldBitmap = SelectObject (hdcMem, hImage);
    BitBlt (hdc, 0, 0, cx, cy, hdcMem, 0, 0, SRCCOPY);
    SelectObject (hdcMem, hOldBitmap);
    DeleteDC (hdcMem);
}

HPALETTE GetPaletteHandle (HWND hwnd, HANDLE hImage)
{
    HDC hdc;
    HPALETTE hPalette;
    HCURSOR hCursor;
    int bpp;

    // Return NULL if this isn't a palette-based device
    hdc = GetDC (hwnd);
    if (!(GetDeviceCaps (hdc, RASTERCAPS) & RC_PALETTE)) {
        ReleaseDC (hwnd, hdc);
        return NULL;
    }
    // Otherwise create a palette and return the palette handle
    switch (g_nPaletteType) {

    case IDM_NO_PALETTE:
        hPalette = NULL;
        break;
    case IDM_HALFTONE_PALETTE:
        hPalette = CreateHalftonePalette (hdc);
        break;
    case IDM_OPTIMIZED_PALETTE:
        bpp = BitsPerPixel (hImage);
        if (bpp <= 8)
            hPalette = CreateExactPalette (hImage);
        else if ((bpp == 16) || (bpp == 24) || (bpp == 32)) {
            hCursor = SetCursor (LoadCursor (NULL, IDC_WAIT));
            hPalette = CreateOctreePalette (hImage, 236, g_nColorBits);
            SetCursor (hCursor);
        }
        else // Default = halftone palette
            hPalette = CreateHalftonePalette (hdc);
        break;
    }
    ReleaseDC (hwnd, hdc);
    return hPalette;
}

UINT BitsPerPixel (HANDLE hImage)
{
    DIBSECTION ds;
    GetObject (hImage, sizeof (ds), &ds);
    return (UINT) ds.dsBmih.biBitCount;
}
/////////////////////////////////////////////////////////////////////////////
// Dialog Procedure
BOOL CALLBACK DlgProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    UINT nColorBits;
    switch (msg) {
    case WM_INITDIALOG:
        SendDlgItemMessage (hwnd, IDC_SPINBUTTON, UDM_SETRANGE, 0,
                            (LPARAM) MAKELONG (8, 1));
        SetDlgItemInt (hwnd, IDC_COLORBITS, g_nColorBits, FALSE);
        return TRUE;
    case WM_COMMAND:
        switch (LOWORD (wParam)) {
        case IDOK:
            nColorBits = GetDlgItemInt (hwnd, IDC_COLORBITS, NULL, FALSE);
            if ((nColorBits < 1) || (nColorBits > 8)) {
                MessageBox (hwnd, "Enter a value from 1 to 8", "Error",
                            MB_ICONEXCLAMATION | MB_OK);
                SendDlgItemMessage (hwnd, IDC_COLORBITS, EM_SETSEL, 0, -1);
                SetFocus (GetDlgItem (hwnd, IDC_COLORBITS));
            }
            else {
                g_nColorBits = nColorBits;
                EndDialog (hwnd, 1);
            }
            return TRUE;
        case IDCANCEL:
            EndDialog (hwnd, 0);
            return TRUE;
        }
        break;
    }
    return FALSE;
}