Tom
Tom

Reputation: 57

Display of a bitmap image, GetObject appears to incorrectly size some parameters in BITMAP structure

My question relates to a task I originally thought would be simple. I now find there are many details that were not first considered. My original goal was to draw an image of a flag in a dialog window. For simplicity I started just to display it in the main window. I started by finding a image, resizing it and storing it with my intended color depth. Bitmap / .BMP file seemed simple, that is what I intended to use. In my case four bit/sixteen color was selected to reduce the file size. The actual size in the file is 600 wide by 300 high. This gives a file with 90K pixel data plus the header which in my case is 118 bytes. As I am just learning windows API, I decided to do some initial tests. First I load the image using the following sequence... I removed the error checking for clarity:

hFlag = LoadImage(GetModuleHandle(NULL), wcImageFile, 
        IMAGE_BITMAP, 600, 300, LR_DEFAULTCOLOR | LR_LOADFROMFILE);

GetObject(hFlag, sizeof(BITMAP), &bitmap);

The content of bitmap is not what I expect after running this short sequence. The result for bmType, bmWidth, bmHeight, bmWidthBytes, bmPlanes are all as I expect. The bmBitsPixel however are 0x20 or 32. bmBits is a null pointer.

Something isn't right... LoadImage returns a handle to my bitmap without error. GetObject returns an int of 0x18 which is the size of the BITMAP structure it is filling. All as expected. Yet the bits per pixel which I expect to be 4 returns as 32. In a case of 32 bits/pixel the array size of pixel data will be short by eight times. This would likely be discovered and the null pointer generated.

Interesting also that if I later run:

SelectObject(hdcMem, hFlag);
BitBlt(hdc, 0, 0, 600, 300, hdcMem, 0, 0, SRCCOPY);

The flag is then displayed. I hope someone can help me make sense of this. I am working through Petzold however it's a big book.

Minimal example code...

case WM_CREATE:
    {
        const wchar_t   *wImageFile;
        wImageFile =    L"C:\\Users\\Tom\\Pictures\\Flag\\Flag.bmp";
        DWORD           dwError = 0;

        hFlag = (HBITMAP) LoadImage(GetModuleHandle(NULL), wImageFile, IMAGE_BITMAP, 600, 300, LR_DEFAULTCOLOR | LR_LOADFROMFILE | LR_CREATEDIBSECTION);
        if(hFlag == 0)
            dwError = GetLastError();
        int nWhatsit = GetObject(hFlag, sizeof(BITMAP), &bitmap);
        return (INT_PTR)FALSE;
    }

//////////////////////////

The question first asked suggested display of an image in the main window. Upon noticing that the flag I wanted to display had two colors only, I have deviated from my original question by making the stored image monochrome (black and white when displayed by paint & others). This was done to further reduce the file size.

Wanting to encapsulate the image within the .exe file I created an unusually large array of bytes as follows:

const UINT8 uFlagBin[] = { 0x42, 0x4D, 0x7E, 0x5A, 0x00, 0x00,..... 0xCD, 0xFD };

This array for a 608 X 304 bit monochrome BMP turns out to be 23,168 bytes in length. This includes the bitmap and the headers. I am sure there is a better way to do this that I have not discovered yet.

After reading and looking at example code I tried the following code:

case WM_PAINT:
    {
        PAINTSTRUCT         ps;
        HDC                 hdc;
        HDC                 hdcMem;
        BITMAPFILEHEADER    *pBMFHeader;            // File header
        BITMAPINFO          *pBMInfo;               // Bitmap info
        BITMAPINFOHEADER    *pBMIHeader;            // Bitmap info header
        RGBQUAD             *pRGBQuad;              // RGBQuad array pointer
        HBITMAP             hFlagBitmap = NULL;     // Handle
        BITMAPINFO          bmi;
        BITMAP              bm;
        char*               ppvBits;
        char                *pPixels;               // Pixel array
        BOOL                bFault;
        LRESULT             lrResult;

        const UINT8 uFlagBin[] = { ... }
    

        int nInfo = sizeof(uFlagBin);

        hdc = BeginPaint(hWnd, &ps);
        hdcMem = CreateCompatibleDC(hdc);

        pBMFHeader = (BITMAPFILEHEADER*)uFlagBin;
        pBMInfo = (BITMAPINFO*)(uFlagBin + sizeof(BITMAPFILEHEADER));
        pBMIHeader = (BITMAPINFOHEADER*)pBMInfo;
        pRGBQuad = (RGBQUAD*)(pBMInfo + sizeof(pBMIHeader));

        pPixels = (char*)uFlagBin + pBMFHeader->bfOffBits;

        hFlagBitmap = CreateDIBSection(hdcMem, pBMInfo, DIB_RGB_COLORS, (void**)&ppvBits, NULL, 0);

        GetObject(hFlagBitmap, sizeof(BITMAP), &bm);

        memcpy(bm.bmBits, pPixels, (bm.bmWidth * bm.bmHeight * bm.bmBitsPixel) / 8);

        bm.bmHeight = bm.bmHeight * -1;

        SelectObject(hdcMem, hFlagBitmap);

        const RGBQUAD rgbqRed = { 0x00, 0x00, 0xFF, 0x00 };
        SetDIBColorTable(hdcMem, 0, 1, &rgbqRed);

        BitBlt(hdc, 0, 0, 608, 304, hdcMem, 0, 0, SRCCOPY);

        DeleteObject(hFlagBitmap);

        DeleteDC(hdcMem);

        EndPaint(hWnd, &ps);

        return (INT_PTR)FALSE;
    }

Much of the header definition isn't necessary. Just done for my interest. The Flag is properly displayed and seems to work as designed. Only problem that remains with the code is that huge array created to hold the bit file. It causes Visual Studio to work quit hard anytime it is recompiled.

Thanks to all for suggestions and to a bunch of unknown authors who's code I used to gain understanding of this. -Tom

Upvotes: 0

Views: 106

Answers (0)

Related Questions