Read pixels (raw bytes) from an hbitmap without copying? - c++

I have an HBITMAP handle to a device-dependent bitmap, resulting from this code:
// copy screen to bitmap
HDC hScreen = GetDC(NULL);//don't use hwnd, it doesn't work for non native windows
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BOOL bRet = BitBlt(hDC, 0, 0, w, h, hScreen, x1, y1, SRCCOPY);
I want to access (read-only) the bits of the bitmap, read some values, do some calculations, then discard it.
According to this answer, I shouldn't use GetDIBits() or GetBitmapBits() since they copy the data, but instead I should use:
BITMAP bitmap;
GetObject(hBitmap, sizeof(bitmap), (LPVOID)&bitmap);
But according to GetObject's documentation:
If hgdiobj is a handle to a bitmap created by calling CreateDIBSection, and the specified buffer is large enough, the GetObject function returns a DIBSECTION structure. In addition, the bmBits member of the BITMAP structure contained within the DIBSECTION will contain a pointer to the bitmap's bit values.
If hgdiobj is a handle to a bitmap created by any other means, GetObject returns only the width, height, and color format information of the bitmap. You can obtain the bitmap's bit values by calling the GetDIBits or GetBitmapBits function.
Which is pointed to in a comment on the above answer:
According to this https://msdn.microsoft.com/en-us/library/dd144904(v=vs.85).aspx if bitmap is not created by CreateDIBSection bitmap.bmBit pointer will be null.
So, is there anyway to access the bits of the bitmap (RGB values), without copying them?
If it helps, the device on which the bitmap is dependent is always a screen, so I guess it's always a 24bit or 32bit bitmap.
In this article, regarding how to save an HBITMAP to a file, there's this section:
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits < 24) pbmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1 << cClrBits));
// There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel
else pbmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER));
So, I don't really understand what's happening here, and which is right?

Related

WinAPI/GDI: How to use GetDIBits() to get color table synthesized for a bitmap?

I find it difficult to understand the excerpt below from MSDN site on GetDIBits() function:
If lpvBits is NULL and the bit count member of BITMAPINFO is
initialized to zero, GetDIBits fills in a BITMAPINFOHEADER
structure or BITMAPCOREHEADER without the color table. This
technique can be used to query bitmap attributes.
Question-1: What is meant by "the bit count member of BITMAPINFO"? Does it mean some_bmi.bmiHeader.biBitCount?
Question-2: What is meant by "GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table"? What color table is there to fill in those structures? None of them seems to have a member related to the color table. Is this about the array some_bmi.bmiColors?
Question-3: Is there a way to use GetDIBits() to get the color table(i.e. the array mapping indexes to colors) for a bitmap?
EDIT:
From the comments so far, it looks like breaking the question down into smaller parts was not effective. I will try it another way.
This is what I understand from the part I quoted from MSDN at the beginning:
Assuming the function call is GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage); if lpvBits is NULL and lpvBits->bmiHeader.biBitCount is initialized to zero, GetDIBits() fills in lpbi->bmiHeader only and lpbi->bmiColors is not modified.
Is this the correct way to understand it? And if so, is there a way to get GetDIBits() to fill in lpbi->bmiColors, such as initializing lpvBits->bmiHeader.biBitCount to bit-depth of the bitmap?
I tried testing Question-1's assumption as follows but GetDIBits() fails in that case:
void test(HWND hWnd) {
// get a memory DC
HDC hdc = GetDC(hWnd);
HDC hdcmem = CreateCompatibleDC(hdc); // has 1x1 mono bitmap selected
// into it initially by default
// select a 16x16 mono bmp into it
const int bmp_h = 16, bmp_w = 16;
const int bits_per_px = 1;
HBITMAP hbmp = CreateCompatibleBitmap(hdcmem, bmp_h, bmp_w); // 16x16 mono bitmap
HGDIOBJ hOldBmp = SelectObject(hdcmem, hbmp);
// initialize BITMAPINFO ptr
// (make sure to allocate a buffer large enough for 2 RGBQUADs
// in case color table is retured by GetDIBits() call)
const int bmi_buf_sz =
sizeof(BITMAPINFO) + sizeof(RGBQUAD) * (1 << bits_per_px); // 2 + 1(extra) RGBQUADs allocated for pbmi->bimColors
BYTE* p_bmi_buf = new BYTE[bmi_buf_sz];
BITMAPINFO* pbmi = reinterpret_cast<BITMAPINFO*>(p_bmi_buf);
ZeroMemory(pbmi, bmi_buf_sz);
// populate BITMAPINFO
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biBitCount = 1; // set to 1 just to see if GetDIBits()
// fills in pbmi->bmiColors too
// (when set to 0, only pbmi->bmiHeader is filled)
if(!GetDIBits(hdcmem, hbmp,
0, (UINT)bmp_h,
NULL, pbmi, DIB_PAL_COLORS)) {
MessageBox(hWnd, L"GetDIBits() failed!", NULL, MB_OK);
}
// clean-up
delete[] p_bmi_buf;
SelectObject(hdcmem, hOldBmp); // push hbmp out
DeleteObject(hbmp);
DeleteDC(hdcmem);
ReleaseDC(hWnd, hdc);
}
The easiest way to get the color table is with GetDibColorTable:
HDC memdc = CreateCompatibleDC(NULL);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
int ncolors = 1 << bm.bmBitsPixel;
std::vector<RGBQUAD> rgb(ncolors);
if(ncolors == GetDIBColorTable(memdc, 0, ncolors, &rgb[0]))
{
//success!
}
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
Back to your question: GetDIBits expects pbmi to contain all zeros (except for bmiHeader.biSize member). So pbmi->bmiHeader.biBitCount should be zero.
//pbmi->bmiHeader.biBitCount = 1; <<= comment out this line
This should work; however, as it is stated in documentation, this will only fill the info header, not the color table. To get the color table you have to make another call to GetDIBits with enough allocation for the dib bits.
Moreover DIB_PAL_COLORS will return palette index array (I am not sure what you can do with that). You may want to use DIB_RGB_COLORS flag which will return actual colors when a color table is present.
Try this with a bitmap loaded from file:
HBITMAP hbitmap = (HBITMAP)LoadImage(0, L"8bit.bmp",
IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
//don't continue for hi color bitmaps
if(bm.bmBitsPixel > 8) return;
int ncolors = 1 << bm.bmBitsPixel;
HDC memdc = CreateCompatibleDC(NULL);
int bmpinfo_size = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * ncolors;
std::vector<BYTE> buf(bmpinfo_size);
BITMAPINFO* bmpinfo = (BITMAPINFO*)buf.data();
bmpinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
if(!GetDIBits(memdc, hbitmap, 0, bm.bmHeight, NULL, bmpinfo, DIB_RGB_COLORS))
{
DWORD err = GetLastError();
//...
}
int dibsize = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
std::vector<BYTE> dib(dibsize);
if(!GetDIBits(memdc, hbitmap, 0, (UINT)bm.bmHeight, &dib[0], bmpinfo, DIB_RGB_COLORS))
{
DWORD err = GetLastError();
//...
}
Now bmpinfo->bmiColors should contain the same values as rgb array shown earlier.
Possible confusion between BITMAPINFO and BITMAPINFOHEADER:
The above structures are declared as follows:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
So BITMAPINFO does not have biBitCount member. But it does have bmiHeader.biBitCount member.
When you declare a BITMAPINFOHEADER variable, you have to set biSize member (that's Windows' idea of version control). When you declare a BITMAPINFO variable, you have to make sure it's BITMAPINFOHEADER is taken care of.
Note that most of the time you don't have to worry about palette. For example LoadImage will return a compatible bitmap (if you don't specify LR_CREATEDIBSECTION) and you can use that right away.
Although the accepted answer covers the details, this is more of a direct answer to the questions in the OP.
Assuming the function call is GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage);
Question-1:
That is correct. "The bit count member of BITMAPINFO" refers to lpbi->bmiHeader.biBitCount.
Question-2:
When GetDIBits() is called to get DIB bits(i.e. with a non-null lpvBits and an appropriately initialized lpbi->bmiHeader), lpbi->bmiColors also gets filled by the function with the color table(if bit depth is less then 24 bpp).
Unfortunately, this is not clear in the documentation of the function. With that in mind, what the quoted part means is that, when lpvBits is NULL and lpbi->bmiHeader.biBitCount is zero, the function fills lpbi->bmiHeader only, and does not modify lpbi->bimColor(as opposed to when caling the function to get DIB bits).
Question-3:
You can get the function to return color table(for bitmaps with 8-bbp or less) in lpbi->bmiColors by calling it with a non-null lpvBits and an appropriately initialized lpbi->bmiHeader. IOW, when you call the function to get DIB bits as usual, it fills lpbi->bmiColors as well.
Question in EDIT section:
If lpvBits is NULL and lpvBits->bmiHeader.biBitCount is initialized to
zero, GetDIBits() fills in lpbi->bmiHeader only and lpbi->bmiColors is
not modified.
Is this the correct way to understand it?
Yes, that is correct.
And if so, is there a way to get GetDIBits() to fill in
lpbi->bmiColors, such as initializing lpvBits->bmiHeader.biBitCount to
bit-depth of the bitmap?
Yes, there is a way to get the function to return the color table, but as explained in answer to Q2, initializing lpvBits->bmiHeader.biBitCount to bit-depth of the bitmap alone is not enough. All members of lpvBits->bmiHeader must be appropriately initialized and lpvBits must be non-null. This is basically the same as calling the function to get the DIB bits.

Create a HBITMAP which points to an existing memory buffer

I am using the FreeImage library to store and manipulate bitmap data. Part of my code requires me to take a screenshot of a window (in Windows), and store it as a FBITMAP* (FreeImage's bitmap data structure). The method I have worked out of doing this involves two steps: capture the image, then convert it to a FBITMAP*.
To capture the image, I do something like this:
HWND window; // Assume this is a valid handle to my window
int width; // width of window client area
int height; // height of window client area
HDC windowDC = GetDC(window);
HDC captureDC = CreateCompatibleDC(windowDC);
HBITMAP screenshot = CreateCompatibleBitmap(windowDC, width, height);
SelectObject(captureDC, screenshot);
BitBlt(captureDC, 0, 0, width, height,
captureDC, 0, 0, SRCCOPY|CAPTUREBLT);
ReleaseDC(window, windowDC);
DeleteDC(captureDC);
FreeImage provides a function which returns a raw pointer to the pixel data:
BYTE* FreeImage_GetBits(FBITMAP*)
The FAQ explains that a HBITMAP (WinAPI handle to a bitmap) can be converted to a FBITMAP* using GetDIBits, which takes a source HBITMAP and a destination raw pointer as arguments, and copies the pixel data from one to the other.
The problem with this approach is that I have copied the data twice - once in the BitBlt from the window DC to the HBITMAP selected in the memory DC, and then again from the HBITMAP to the FreeImage memory buffer. I wish to remove this inefficiency and copy the data directly to my raw pointer in the BitBlt operation. For this to work, I need a memory DC which has a HBITMAP selected into it, and where that HBITMAP points to my memory buffer instead of to memory that Windows allocated for it.
How can I achieve this?

Why does GetObject return an BITMAP with null bmBits?

Context: I'm trying to take a screenshot of another window to feed it into OpenCV. I found some code on the web that should be able to convert a BITMAP to something OpenCV can work with. Unfortunately I ran into some trouble.
Question: Why is the bmBits attribute/member always null? (I also tried with PrintWindow instead of BitBlt the result was the same)
#include <iostream>
#include <string>
#include <Windows.h>
int main(int argc, char* argv[])
{
std::wstring windowName = L"Calculator";
RECT rect;
HWND hwnd = FindWindow(NULL, windowName.c_str());
if (hwnd == NULL)
{
return 0;
}
GetClientRect(hwnd, &rect);
HDC hdcScreen = GetDC(NULL);
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen,
rect.right - rect.left, rect.bottom - rect.top);
SelectObject(hdc, hbmp);
PrintWindow(hwnd, hdc, PW_CLIENTONLY);
BITMAP bmp;
GetObject(hbmp, sizeof(BITMAP), &bmp);
return 0;
}
The bmBits member is non-null for DIB sections. For device-dependent bitmaps (such as the one you're creating), the bmBits is not set because the pixels are on the video card, not in main memory.
In your example, you need to change CreateCompatibleBitmap to CreateDIBSection if you want direct access to the bits.
Just for information.
When loading bitmap from file and want to use BITMAP .bmBits (for glTexImage2D, glDrawPixels):
LoadImage(NULL, "path_to.bmp", IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE);
u must specify flag LR_CREATEDIBSECTION
HBITMAP hBmp = NULL;
BITMAP BMp;
hBmp = (HBITMAP) LoadImage(NULL, "bitmap.bmp", IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE | LR_CREATEDIBSECTION);
GetObject(hBmp, sizeof(BMp), &BMp);
//BMp.bmBits now points to data
From GetObject documentation on MSDN. Please note the second paragraph.
If hgdiobj is a handle to a bitmap created by calling CreateDIBSection, and the specified buffer is large enough, the GetObject function returns a DIBSECTION structure. In addition, the bmBits member of the BITMAP structure contained within the DIBSECTION will contain a pointer to the bitmap's bit values.
If hgdiobj is a handle to a bitmap created by any other means, GetObject returns only the width, height, and color format information of the bitmap. You can obtain the bitmap's bit values by calling the GetDIBits or GetBitmapBits function.
One thing which you could do is to look at the return value of GetObject. If 0 you know something has gone wrong. Something wrong with the parameters of the call.

GDI C/C++ - BITMAP into an existing HBITMAP

How can I create a device context compatible bitmap and then associating the obtained handle to a BITMAP struct?
If I write:
...
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, width, height); // these three arguments are initialized somewhere else
hbitmap = CreateBitmapIndirect(bitmap); // argument already initialized and properly filled
...
A HBITMAP handle compatible with hdc is created, and then a new HBITMAP (filled with bitmap data) is initialized, though without keeping its compatibility. Is there a function which allows not to create a HBITMAP from a BITMAP, but rather fills an initialized HBITMAP with an already existing BITMAP source?
CopyImage function
Creates a new image (icon, cursor, or bitmap) and copies the attributes of the specified image to the new one. If necessary, the function stretches the bits to fit the desired size of the new image.
HANDLE WINAPI CopyImage(
HANDLE hImage,
UINT uType,
int cxDesired,
int cyDesired,
UINT fuFlags
);
hImage A handle to the image to be copied.
uType The type of image to be copied. This parameter can be one of the following values.
IMAGE_BITMAP 0 Copies a bitmap.
IMAGE_ICON 1 Copies an icon.
IMAGE_CURSOR 2 Copies a cursor.
cxDesired The desired width, in pixels, of the image. If this is zero, then the returned image will have the same width as the original hImage.
cyDesired The desired height, in pixels, of the image. If this is zero, then the returned image will have the same height as the original hImage.
fuFlags
CreateBitmapIndirect takes BITMAP on its input. And you can get it via GetObject from HBITMAP:
BITMAP Bitmap;
INT nResult = GetObject((HGDIOBJ) hBitmap, sizeof Bitmap, &Bitmap);
CreateBitmapIndirect will be able create a bitmap from this struct. Or you can use CreateCompatibleBitmap to create compatible bitmap providing width/height from obtained Bitmap.

How to convert HICON to HBITMAP in VC++?

How to convert HICON to HBITMAP in VC++?
I know this is an FAQ but all the solutions I've found on Google don't work. What I need is a function which takes a parameter HICON and returns HBITMAP.
Greatest if possible to make conversion to 32-bit bitmap even the icon is 24-bit, 16-bit or 8-bit.
This is the code, I don't know where it goes wrong:
HBITMAP icon_to_bitmap(HICON Icon_Handle) {
HDC Screen_Handle = GetDC(NULL);
HDC Device_Handle = CreateCompatibleDC(Screen_Handle);
HBITMAP Bitmap_Handle =
CreateCompatibleBitmap(Device_Handle,GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON));
HBITMAP Old_Bitmap = (HBITMAP)SelectObject(Device_Handle,Bitmap_Handle);
DrawIcon(Device_Handle, 0,0, Icon_Handle);
SelectObject(Device_Handle,Old_Bitmap);
DeleteDC(Device_Handle);
ReleaseDC(NULL,Screen_Handle);
return Bitmap_Handle;
}
this code do it:
HICON hIcon = (HICON)LoadImage(instance, MAKEINTRESOURCEW(IDI_ICON), IMAGE_ICON, width, height, 0);
ICONINFO iconinfo;
GetIconInfo(hIcon, &iconinfo);
HBITMAP hBitmap = iconinfo.hbmColor;
and this is the code in the *.rc file:
IDI_ICON ICON "example.ico"
and this is the code in the *.h file:
#define IDI_ICON 4000
HDC hDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hDC);
HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, x, y);
HBITMAP hResultBmp = NULL;
HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);
DrawIconEx(hMemDC, 0, 0, hIcon, x, y, 0, NULL, DI_NORMAL);
hResultBmp = hMemBmp;
hMemBmp = NULL;
SelectObject(hMemDC, hOrgBMP);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DestroyIcon(hIcon);
return hResultBmp;
I don't have code readily available to share, but I think this is pretty easy. You have to create the HBITMAP, create a device context, select the bitmap into the DC (this will make the bitmap the drawing area for this DC). Finally call the DrawIcon() function to draw your icon on this DC. After that detach the bitmap from the DC and destroy the DC. Your bitmap now should be ready to go.
Update after looking at your code:
I believe the problem is in the createCompatibleBitmap call. You are asking for a bitmap compatible with the memory DC, but memory DCs start with a 1 bit/pixel bitmap selected into them. Try asking for a bitmap compatible with the screen DC instead.
Update 2: you may want to look at this question as it seems related to your problem.
I found this(similar code works for me - 32x32 icons with or without alpha data):
used CopyImage (msdn link)
HICON hICON = /*your code here*/
HBITMAP hBITMAPcopy;
ICONINFOEX IconInfo;
BITMAP BM_32_bit_color;
BITMAP BM_1_bit_mask;
// 1. From HICON to HBITMAP for color and mask separately
//.cbSize required
//memset((void*)&IconInfo, 0, sizeof(ICONINFOEX));
IconInfo.cbSize = sizeof(ICONINFOEX);
GetIconInfoEx( hICON , &IconInfo);
//HBITMAP IconInfo.hbmColor is 32bit per pxl, however alpha bytes can be zeroed or can be not.
//HBITMAP IconInfo.hbmMask is 1bit per pxl
// 2. From HBITMAP to BITMAP for color
// (HBITMAP without raw data -> HBITMAP with raw data)
// LR_CREATEDIBSECTION - DIB section will be created,
// so .bmBits pointer will not be null
hBITMAPcopy = (HBITMAP)CopyImage(IconInfo.hbmColor, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
// (HBITMAP to BITMAP)
GetObject(hBITMAPcopy, sizeof(BITMAP), &BM_32_bit_color);
//Now: BM_32_bit_color.bmBits pointing to BGRA data.(.bmWidth * .bmHeight * (.bmBitsPixel/8))
// 3. From HBITMAP to BITMAP for mask
hBITMAPcopy = (HBITMAP)CopyImage(IconInfo.hbmMask, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
GetObject(hBITMAPcopy, sizeof(BITMAP), &BM_1_bit_mask);
//Now: BM_1_bit_mask.bmBits pointing to mask data (.bmWidth * .bmHeight Bits!)
BM_32_bit_color bitmap may be have Alpha *channel*(each 4th byte) already set! So - check for it before u add mask bit to color data.