Getting Bitmap pixel values using the Windows GetDIBits function - winapi

I'm trying to get the pixels of a bitmap using the GetDIBits function. As I have not studied the Windows GDI/API, I'm very unsure about the first argument, HDC. I've searched countless posts here on SO and the web but have been unable to find information or example about how to initialize HDC in this specific case. Here's how far I've gone reading pixel values:
HBITMAP hBitmap = (HBITMAP) LoadImage(0, L"C:/tmp/Foo.bmp" ,IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
// check hBitmap for error
BITMAP bm;
::GetObject( hBitmap , sizeof(bm) , &bm );
// TODO: GetDIBits()
Solution:
After scouring the web some more I've been able to cobble together the following:
/* Omitting error checks for brevity */
HDC dcBitmap = CreateCompatibleDC ( NULL );
SelectObject( dcBitmap, hBitmap );
BITMAPINFO bmpInfo;
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = bm.bmWidth;
bmpInfo.bmiHeader.biHeight = -bm.bmHeight;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;
bmpInfo.bmiHeader.biSizeImage = 0;
COLORREF* pixel = new COLORREF [ bm.bmWidth * bm.bmHeight ];
GetDIBits( dcBitmap , hBitmap , 0 , bm.bmHeight , pixel , &bmpInfo , DIB_RGB_COLORS );

The source bitmap is typically a device-dependent bitmap. Although it's less common nowadays, that might mean that the bitmap's pixel values are stored as indexes into a color table. In those cases GetDIBits would need access to the color table, which is stored in a device context.
If your bitmap uses RGB values instead of indexes, then the device context should be irrelevant, though in my experience you must still provide a valid one (see What is the HDC for in GetDIBits?), perhaps it looks at other aspects of the device context, like the color depth.

Is your goal to get the pixel color values, or to call GetDIBits? If you just want the pixel content, you can use GetObject to get the BITMAP structure corresponding to your HBITMAP handle, the bmBits pointer in that structure gives access to the pixels (note: it will be in the bitmap's original format, which might not be 24bpp, so check the other fields of the structure to see what the format is).

Related

Change bitmap size without create a new bitmap

I created a bitmap using CreateDIBSection and specified .biWidth = 100 ; .biHeight = 100 like this pseudo-code :
pBitmapInfo->bmiHeader.biWidth = 100;
pBitmapInfo->bmiHeader.biHeight = 100;
....
CreateDIBSection(DibDC, pBitmapInfo, DIB_RGB_COLORS, 0, 0, 0);
Later, i want to reuse this bitmap, just change the bitmap size to 300x100 (and may clear the old image because i don't need it anymore). Many one say I need to create a new bitmap with new size and delete the old bitmap. But I expected in someway that we can re-use the old bitmap. I don't want to re-create a new bitmap because it cause slow performance while i need to do it repeatly many times. So is there any way to change the bitmap size without re-create a new bitmap?
If you are worried about performance it is indeed not a good idea to keep destroying and creating bitmaps.
There is however an easier solution. Simply create a pool of bitmaps in predefined sizes and use bitmaps from the pool as needed.
If you have a long lived DC, you can use:
hBitmap100x100 = CreateCompatibleBitmap(MyDC, 100,100);
hBitmap300x300 = CreateCompatibleBitmap(MyDC, 300,300);
If you keep changing DC's then use a DIB section
hBitmap100x100 = CreateDIBSection(DibDC, pBitmapInfo100x100, DIB_RGB_COLORS, null, 0, 0);
hBitmap300x300 = CreateDIBSection(DibDC, pBitmapInfo100x100, DIB_RGB_COLORS, null, 0, 0);
Just keep reusing these over and over.
You can even have a dozen of them in an array if you like.
You create them at program startup and dispose of them when done.

Colors are not being correctly displayed

Just to learn Windows API, I'm trying to use a cheap fingerprint device I bought.
The library that came with it captures the fingerprint as an 8bit bitmap of 256x280 pixels and stores the raw pixels in a buffer.
I'm trying to copy this raw pixel stream into a Device Independent Bitmap (DIB) and then trying to use this DIB to draw onto a window.
I managed to display the image but the colors are all wrong. Heres the piece of code that handles the painting.
PAINTSTRUCT ps;
HDC hdc,memDC;
HBITMAP cp_bmp;
HBITMAP di_bmp;
BITMAPINFO di_bmp_info;
void *di_bmp_data;
int ptr;
int x,y;
int aux;
ZeroMemory(&di_bmp_info,sizeof(BITMAPINFO));
di_bmp_info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
di_bmp_info.bmiHeader.biWidth=256;
di_bmp_info.bmiHeader.biHeight=280;
di_bmp_info.bmiHeader.biPlanes=1;
di_bmp_info.bmiHeader.biBitCount=8;
di_bmp_info.bmiHeader.biCompression=BI_RGB;
hdc=BeginPaint(hwnd,&ps);
// create the DIB
di_bmp=CreateDIBSection(hdc,&di_bmp_info,DIB_RGB_COLORS,&di_bmp_data,NULL,0);
// Copy the original bitstream onto the DIB
CopyMemory(di_bmp_data,fingerprint,256*280);
// create the mem dc
memDC=CreateCompatibleDC(hdc);
// create the DDB
cp_bmp=CreateCompatibleBitmap(hdc,256,280);
SelectObject(memDC,cp_bmp);
SetDIBits(memDC,cp_bmp,0,280,di_bmp_data,&di_bmp_info,DIB_RGB_COLORS);
BitBlt(hdc,10,10,256,280,memDC,0,0,SRCCOPY);
DeleteObject(cp_bmp);
EndPaint(hwnd,&ps);
Curious fact is that when I change the di_bmp_info.bmiHeader.biBitCount to 32, the colors display perfectly but the image gets smaller in size and gets repeated around 5 or six times horizontally.
Im stuck!. Thanks in advance.
In short, you have an indexed bitmap (each pixel is not an RGB value but instead an index into a defined palette of colors) and you haven't provided a palette for it to use.
BITMAPINFO is a variable sized structure - a BITMAPINFOHEADER, followed by at least one but potentially more RGBQUAD structures. For indexed bitmap depths (8 bpp and below), a palette needs to be provided following the BITMAPINFOHEADER in memory.
The number of palette entries required is determined by the biBitCount and biClrUsed fields. If biClrUsed is 0 the number of palette entries must be 1 << biBitCount, or 256 in the case of 8bpp. Setting biClrUsed to something other than 0 lets you provide fewer palette entries if the bitmap doesn't need them.
Because you haven't set biClrUsed a full 256 color palette is assumed to follow the BITMAPINFOHEADER in memory and essentially random memory is being used.
BITMAPINFO provides only a single RGBQUAD itself so you need to extend the structure to provide the rest, for example:
struct MyBitmapInfo
{
BITMAPINFOHEADER bmiHeader;
RGBQUAD palette[256];
};
Where you actually get the palette from is up to you, but you could e.g. use a grayscale palette like this:
struct MyBitmapInfo di_bmp_info;
ZeroMemory(&di_bmp_info,sizeof(struct MyBitmapInfo));
di_bmp_info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
di_bmp_info.bmiHeader.biWidth=256;
di_bmp_info.bmiHeader.biHeight=280;
di_bmp_info.bmiHeader.biPlanes=1;
di_bmp_info.bmiHeader.biBitCount=8;
di_bmp_info.bmiHeader.biCompression=BI_RGB;
// initialise greyscale palette
for (int i = 0; i < 256; i++)
{
di_bmp_info.palette[i].rgbRed =
di_bmp_info.palette[i].rgbGreen =
di_bmp_info.palette[i].rgnBlue = i;
}

How do I capture the screen using Ruby?

I need to capture the screen using Ruby, and then get an array of RGB pixel values for each pixel on the screen.
I tried the Windows API, and can bitblt the screen and retrieve the handle of the bitmap, but I have no idea how to access the raw RGB values within this handle's data.
This is what I have at the moment, and it's fast enough, but I need to get the RGB values from the hbitmap into an array that I can work with.
Anything as fast as Bitblt but easier would be appreciated too.
def getscreen()
width = Win32API.new("User32.dll","GetSystemMetrics",["L"],"L").call(0)
height = Win32API.new("User32.dll","GetSystemMetrics",["L"],"L").call(1)
#Get desktop DC, create a compatible dc, create a comaptible bitmap and select into compatible dc.
hddc = Win32API.new("User32.dll","GetDC",["L"],"L").call(Win32API.new("User32.dll","GetDesktopWindow",[],"L").call)
hcdc = Win32API.new("Gdi32.dll","CreateCompatibleDC",["L"],"L").call(hddc)
hbitmap = Win32API.new("Gdi32.dll","CreateCompatibleBitmap",["L","L","L"],"L").call(hddc,width,height)
Win32API.new("Gdi32.dll","SelectObject",["L","L"],"L").call(hcdc,hbitmap)
Win32API.new("Gdi32.dll","BitBlt",["L","L","L","L","L","L","L","L","P"],"L").call(hcdc,0,0,width,height,hddc,0,0,"SRCCOPY|CAPTUREBLT")
#save hbitmap to stream of byte as you mentioned
puts hbitmap
#
Win32API.new("User32.dll","ReleaseDC",["L","L"],"L").call(Win32API.new("User32.dll","GetDesktopWindow",[],"L").call,hddc)
Win32API.new("Gdi32.dll","DeleteDC",["L"],"L").call(hcdc)
Win32API.new("Gdi32.dll","DeleteObject",["L"],"L").call(hbitmap)
#Print screen width and height
puts "Screen width: #{width}"
puts "Screen height: #{height}"
end
I figured out how to do this so I though I would post the solution for others who may need help.
The GetDIBits Win32API function can be used to access the RGB array stored in the bitmap captured using the above code.
http://msdn.microsoft.com/en-us/library/dd144879%28v=vs.85%29.aspx

CreatePatternBrush and screen color depth

I am creating a brush using CreatePatternBrush with a bitmap created with CreateBitmap.
The bitmap is 1 pixel wide and 24 pixels tall, I have the RGB value for each pixel, so I create an array of rgbquads and pass that to CreateBitmap.
This works fine when the screen color depth is 32bpp, since the bitmap I create is also 32bpp.
When the screen color depth is not 32bpp, this fails, and I understand why it does, since I should be creating a compatible bitmap instead.
It seems I should use CreateCompatibleBitmap instead, but how do I put the pixel data I have into that bitmap?
I have also read about CreateDIBPatternBrushPt, CreateDIBitmap, CreateDIBSection, etc.
I donĀ“t understand what is a DIBSection, and find the subject generally confusing.
I do understand that I need a bitmap with the same color depth as the screen, but how do I create it having only the 32bpp pixel data?
You could create a DIB because you can use a Device Independent Bitmap independently of the screen color depth. See CreateDIBSection().
How can you create it having only the 32bpp pixel data? A DIB can be created with 32bpp data. As you can read in the documentation:
The CreateDIBSection function creates
a DIB that applications can write to
directly. The function gives you a
pointer to the location of the bitmap
bit values.
If hSection is NULL, the system
allocates memory for the DIB. If the
function succeeds, the return value is
a handle to the newly created DIB, and
*ppvBits points to the bitmap bit values.
Try something like this:
VOID *ppvBits = NULL;
BITMAPINFO BitmapInfo;
memset(&BitmapInfo, 0, sizeof(BITMAPINFOHEADER));
BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BitmapInfo.bmiHeader.biWidth = 1;
BitmapInfo.bmiHeader.biHeight = 24;
BitmapInfo.bmiHeader.biPlanes = 1;
BitmapInfo.bmiHeader.biBitCount = 32;
BitmapInfo.bmiHeader.biCompression = BI_RGB;
HBITMAP hBitmap = CreateDIBSection(hDC, &BitmapInfo, DIB_RGB_COLORS, &ppvBits, NULL, 0);
In our case *ppvBits points to 1 * 24 * (32 / 8) allocated bytes.
It is important to know that if biHeight is positive, the bitmap is a bottom-up DIB and its origin is the lower-left corner. See BITMAPINFOHEADER Structure for more info.
I solved it by using CreateCompatibleBitmap and SetPixel. Not the best option I guess, but it works.

ImageList Transparency on Listviews?

EDIT: I've offered a bounty, since I doubt I'll be getting any answers otherwise.
Lately I've been working with listviews and I've decided to add an icon for each item indicating whether it's input or output. The icons add fine, but they're not transparent:
As can be seen, the icons are clearly not transparent. I'm currently doing something like this load the icons:
hImageList = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 2, 2);
if (hImageList != NULL)
{
iIN = ImageList_AddIcon(hImageList, LoadIcon(hInstance, MAKEINTRESOURCE(101)));
iOUT = ImageList_AddIcon(hImageList, LoadIcon(hInstance, MAKEINTRESOURCE(102)));
}
I've tried messing with the flags for ImageList_Create & LoadIcon/LoadImage but have had no luck and to be honest I've run out of ideas.
Any help would be very appreciated.
First up, ImageList_ReplaceIcon copies the icon data when adding it to an image list. So the HICON needs to be released afterwards.
Next, imagelists are natively bitmaps, not icons. And the way you are creating your imagelist makes the conversion of icon to bitmap very ambiguous. ILC_COLOR32 implies the imagelist should be created as a 32bit dib section, which typically contain transparency information via an embedded alpha channel. ILC_MASK instead implies that the internal bitmaps are DDB bitmaps, with the transparency information stored as a 1bpp mask bitmap.
The quickest solution to your problem - take your two icons:
Merge them into a single bitmap resource thats 32 pels wide by 16 high. Fill the background with a mask color :- purple or something.
Create the bitmap using ILC_COLOR|ILC_MASK
Load the bitmap being sure NOT to use LR_TRANSPARENT.
Add the bitmap using ImageList_AddMasked passing in a COLORREF that represents the mask color.
OR, for a better visual effect...
export your PNG data as a 32x16 32bpp bitmap file containing pre-multiplied alpha channel data.
Create the imagelist using the ILC_COLOR32 value.
LoadImage() with LR_CREATEDIBSECTION to load the bitmap as a 32bpp dib section.
Add the image using ImageList_Add()
(the last option is kind of tricky as the number of tools that support writing out 32bit bmp files with properly pre multiplied alpha channels is rather low).
Edited to add the following code sample. Using a 4bpp bitmap created in the dev environment this works just great :-
HWND hwndCtl = CreateWindowEx(0,WC_LISTVIEW,TEXT("ListView1"),WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL,0,0,cx,cy,hWnd,(HMENU)101,hModule,NULL);
HBITMAP hbm = (HBITMAP)LoadImage(hModule,MAKEINTRESOURCE(IDB_BITMAP1),IMAGE_BITMAP,0,0,0);
COLORREF crMask=RGB(255,0,255);
HIMAGELIST himl = ImageList_Create(16,16,ILC_COLOR|ILC_MASK,2,0);
ImageList_AddMasked(himl,hbm,crMask);
ListView_SetImageList(hwndCtl,himl,LVSIL_NORMAL);
You want to make your icons have a background color that isn't used anywhere else in the icon, like a really ugly purple, and then use LoadImage(..., LR_LOADTRANSPARENT); The flag says look at the first pixel at 0,0 and make everything that color transparent.
Your code looks fine to me, I always use LoadImage instead of LoadIcon but I suspect that doesn't matter. Have you checked that the icons do indeed have transparent areas and don't themselves have a solid background?
My LoadImage calls look like:
HICON hIcon = (HICON)LoadImage(hinstResources,MAKEINTRESOURCE(IDI_ICON),IMAGE_ICON,16,16,LR_DEFAULTCOLOR);
Here... Create an ImageList, as suggested, make your icons into a Bitmap, 16 pixels high, by 16*n long, where n= the number of icons...
Set the background color to 255, 0, 255, like you have done.
Then, load it, and add it to the image list as I did here:
m_ImageList.Create(16, 16, ILC_COLOR16 | ILC_MASK, 7, 1);
CBitmap bm;
bm.LoadBitmap(IDB_SUPERTREEICONS);
m_ImageList.Add(&bm, RGB(255, 0, 255));
GetTreeCtrl().SetImageList(&m_ImageList, TVSIL_NORMAL);
Of course, this was written in MFC, but as you know, it's just a wrapper to Win32...
Outside of this, you are going to have to go to a custom draw control, in which you draw the icon over whatever background the icon happens to be sitting on. There isn't really any magic "transparent" color, that I know of, in any of these controls.
In the case of a custom draw, you need to use code like the following:
#define TRANSPARENT_COLOR (255,0,255)
UINT iBitmap = IDB_ICON_UP
CDC *dc = GetDC();
int x = 0, y = 0;
CDC *pDisplayMemDC = new CDC;
CDC *pMaskDC = new CDC;
CDC *pMemDC = new CDC;
CBitmap *pBitmap = new CBitmap;
CBitmap *pMaskBitmap = new CBitmap;
CBitmap *pMemBitmap = new CBitmap;
int cxLogo, cyLogo;
BITMAP bm;
pBitmap->LoadBitmap(iBitmap);
pDisplayMemDC->CreateCompatibleDC(dc);
CBitmap *pOldBitmap = (CBitmap *)pDisplayMemDC->SelectObject(pBitmap);
pBitmap->GetObject(sizeof(bm), &bm);
cxLogo = bm.bmWidth;
cyLogo = bm.bmHeight;
pMaskBitmap->CreateBitmap(cxLogo, cyLogo, 1, 1, NULL);
pMaskDC->CreateCompatibleDC(dc);
CBitmap *pOldMask = (CBitmap *)pMaskDC->SelectObject(pMaskBitmap);
COLORREF oldBkColor = pDisplayMemDC->SetBkColor(TRANSPARENT_COLOR);
pMaskDC->BitBlt(0, 0, cxLogo, cyLogo, pDisplayMemDC, 0, 0, SRCCOPY);
pMemBitmap->CreateCompatibleBitmap(dc, cxLogo, cyLogo);
pMemDC->CreateCompatibleDC(dc);
CBitmap *pOldMem = (CBitmap *)pMemDC->SelectObject(pMemBitmap);
pMemDC->BitBlt(0, 0, cxLogo, cyLogo, dc, x, y, SRCCOPY);
pMemDC->BitBlt(0, 0, cxLogo, cyLogo, pDisplayMemDC, 0, 0, SRCINVERT);
pMemDC->BitBlt(0, 0, cxLogo, cyLogo, pMaskDC, 0, 0, SRCAND);
pMemDC->BitBlt(0, 0, cxLogo, cyLogo, pDisplayMemDC, 0, 0, SRCINVERT);
dc->BitBlt(x, y, cxLogo, cyLogo, pMemDC, 0, 0, SRCCOPY);
delete pMemDC->SelectObject(pOldMem);
delete pMemDC;
delete pMaskDC->SelectObject(pOldMask);
delete pMaskDC;
delete pDisplayMemDC->SelectObject(pOldBitmap);
delete pDisplayMemDC;
This code decides where to draw the icon, and takes a snapshot of the background, creates a mask for the icon, and then draws it over the background, giving it a fully transparent background...
Hope that helps somewhat. If not, please explain in more detail what you are trying to make happen, and what you are seeing, or what you are NOT seeing...
I struggled with the same issue using an ImageList in a Tree View. I eventually got Chris Becke's second solution to work, creating an ImageList using the ILC_COLOR32 flag and using LoadImage() with the LR_CREATEDIBSECTION flag. This solution, and probably also the first solution, requires what is described below.
Transparency (and themes) are only supported with comctl32.dll version 6+, to use the correct version, the pre-processor directive on this page worked for me:
https://learn.microsoft.com/en-us/windows/win32/controls/cookbook-overview

Resources