Loading Images under Windows (an OLE Image Loader)
OLE can be used to open a few of image file formats (JPEG, BMP, GIF but no PNG) on Windows. I have put together a simple C++ class that will open image and read image files.
Basically, here is how you can proceed to open an image with OLE (this is actually inspired from this codeproject article):
Obtain an IStream Pointer
Load the file into global memory and create an IStream*
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // open the file HANDLE hFile = CreateFile(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); // allocate storage to read the file DWORD dwFileSize = GetFileSize(hFile, NULL); HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize); LPVOID pvData = GlobalLock(hGlobal); DWORD dwBytesRead = 0; // read the file BOOL bRead = ReadFile(hFile, pvData, dwFileSize, &dwBytesRead, NULL); GlobalUnlock(hGlobal); CloseHandle(hFile); // create the IStream* from the global memory LPSTREAM pstm = NULL; HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pstm); |
Import with OleLoadPicture
1 2 | LPPICTURE picture; hr = ::OleLoadPicture(pstm, dwFileSize, FALSE, IID_IPicture, (LPVOID *)&(picture)); |
Obtain Image Information
Once we have obtained the PICTURE object, we can query its size:
1 2 3 4 5 6 7 8 9 | long hmWidth = 0, hmHeight = 0; picture->get_Width (&hmWidth); picture->get_Height(&hmHeight); HDC Screen = ::CreateCompatibleDC(0); int width = MulDiv(hmWidth, GetDeviceCaps(Screen, LOGPIXELSX), HIMETRIC_INCH); int height = MulDiv(hmHeight, GetDeviceCaps(Screen, LOGPIXELSY), HIMETRIC_INCH); ::DeleteDC(Screen); |
Load the Image
Finally, loading the image has to be done within a custom DC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | HDC hDC = CreateCompatibleDC(0); BITMAPINFO bmInfo = {0}; bmInfo.bmiHeader.biSize = sizeof(BITMAPINFO); bmInfo.bmiHeader.biSizeImage = 0; bmInfo.bmiHeader.biPlanes = 1; bmInfo.bmiHeader.biBitCount = 32; // or 24 if you don't care about alpha bmInfo.bmiHeader.biWidth = width; bmInfo.bmiHeader.biHeight = height; unsigned char *pixels; HBITMAP bm = CreateDIBSection(hDC, &bmInfo, DIB_RGB_COLORS, (void**)&pixels, NULL, 0); HGDIOBJ old = SelectObject(hDC, bm); RECT rc; rc.left = 0; rc.top = 0; rc.right = width; rc.bottom = height; HRESULT hrP = picture->Render(hDC, 0, 0, width, height, 0, hmHeight, hmWidth, -hmHeight, &rc); BITMAP b; SelectObject(hDC, old); GetObject(bm, sizeof(b), &b); int rowLength = b.bmWidthBytes; // allocate a buffer unsigned char *data = new unsigned char[width*height*4]; // load the texture into the data buffer GetBitmapBits(bm, b.bmHeight*rowLength, data); DeleteObject(bm); DeleteDC(hDC); |
Special Case – Loading Alpha Correctly
Older BMP did not support 32 bit with alpha channel. Support for an alpha channel was added in XP I believe. As a consequence, some BMP have been written as 32bit with an unused (at the time) alpha channel that’s completely transparent. If you plan on using the alpha channel from the BMP files, it’s generally a good idea to check if the entire alpha channel is transparent. In such a case, we would assume that the entire channel should make it fully opaque. This is what the following piece of code does:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // scan to check if the alpha is all transparent bool allTransp = true; unsigned char *row = data; for (int y = 0; allTransp && y < b.bmHeight; ++y) { for (int x = 0; allTransp && x < b.bmWidth; ++x) { int a = row[x*4+3]; if (a != 0) allTransp = false; } row += rowLength; } if (allTransp) { // if everything is transparent, make everything opaque row = data; for (int y = 0; y < b.bmHeight; ++y) { for (int x = 0; x < b.bmWidth; ++x) { row[x*4+3] = 255; } row += rowLength; } } |
