This question already has answers here:
Freeing memory error?
(2 answers)
Closed 3 years ago.
I am trying out a code to capture bitmap information using CreateDIBSection. Since I want to make the size of the array (which to hold each pixel value of the display monitor) flexible in order to suit monitor of different size, I create a dynamic unsigned char array (each color channel is 1 byte).
However, when I run the program, the program crashed right at the part of deleting the array, near the end of the program.
I tried to cast the array back to original type, i.e. unsigned char*, since I suspected it had been casted to void** when it being passed into CreateDIBSection(), but it doesn't work.
Below is the code, appreciate all the advice.
#include <Windows.h>
#include <cstdint>
HWND m_hwnd;
void GetBitMapInfo(const int& x_Coordinate, const int& y_Coordinate, const int& iWidth, const int& iHeight)
{
DWORD imageSize = iWidth * iHeight * 4;
// Get the display window
HDC displayWindow = GetDC(m_hwnd);
HDC hdc = CreateCompatibleDC(displayWindow);
// Fill in the Bitmap information
BITMAPINFO bmpInfo;
ZeroMemory(&bmpInfo, sizeof(BITMAPINFO));
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = iWidth;
bmpInfo.bmiHeader.biHeight = iHeight;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 32;
bmpInfo.bmiHeader.biCompression = BI_RGB;
bmpInfo.bmiHeader.biSizeImage = 0;
bmpInfo.bmiHeader.biClrUsed = 0;
bmpInfo.bmiHeader.biClrImportant = 0;
// Create the storage for the pixel information
uint8_t* image = new uint8_t[imageSize];
// Populate the storage with the BMP pixel information
HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)(&image), nullptr, NULL);
SelectObject(hdc, hBitmap);
BitBlt(hdc, x_Coordinate, y_Coordinate, iWidth, iHeight, displayWindow, 0, 0, SRCCOPY);
delete[] image; //Program crashed here: identifier "image" is undefined
image = nullptr;
DeleteDC(hdc);
DeleteDC(displayWindow);
DeleteObject(hBitmap);
return;
}
int main()
{
GetBitMapInfo(0, 0, 1920, 1080);
return 0;
}
The CreateDIBSection function make the pointer
... receives a pointer to the location of the DIB bit values.
The memory you have allocated for image is lost (a memory leak) and the new pointer can't be passed to delete[] (that will lead to undefined behavior).
The documentation states that with a null pointer hSection the memory allocated by CreateDIBSection will be free'd by DeleteObject.
Related
I am trying to put an application icon into a char array. The code below converts a HICON into a BITMAP, then attempts to extract the bytes from the BITMAP into a char array. As I step through the code, I observed that the second GetDIBits() modifies the destination pointer to NULL despite claiming 16 bytes were written. This behavior is very puzzling. I suspect that casting BITMAPINFOHEADER* into BITMAPINFO* might be problematic, but using a BITMAPINFO directly causes stack corruption upon exiting the function. Does anyone know why GetDIBits() behaves in such a way?
std::unique_ptr<char> getRawImg(HICON& icon)
{
// step 1 : get a bitmap from an application icon
ICONINFO iconInfo;
ZeroMemory(&iconInfo, sizeof(iconInfo));
BITMAP bitMap;
ZeroMemory(&bitMap, sizeof(bitMap));
HRESULT bRes = GetIconInfo(icon, &iconInfo);
int width;
int height;
int bitsPerPixel;
if (iconInfo.hbmColor) // color icon
{
if (GetObject(iconInfo.hbmColor, sizeof(bitMap), &bitMap))
{
width = bitMap.bmWidth;
height = bitMap.bmHeight;
bitsPerPixel = bitMap.bmBitsPixel;
}
}
else if (iconInfo.hbmMask) // black and white icon
{
if (GetObject(iconInfo.hbmMask, sizeof(bitMap), &bitMap))
{
width = bitMap.bmWidth;
height = bitMap.bmHeight / 2;
bitsPerPixel = 1;
}
}
// step 2 : extract bytes from the bitmap into a byte array
HBITMAP hBitmap = CreateBitmapIndirect(&bitMap);
int stride = (width * bitsPerPixel + 31) / 32 * 4;
HDC hdc = GetDC(NULL);
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = height;
bi.biPlanes = 1;
bi.biBitCount = bitsPerPixel;
bi.biCompression = BI_RGB;
bi.biSizeImage = stride * height;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
BITMAPINFO* pBitmapInfo = (BITMAPINFO*)&bi;
if (!GetDIBits(hdc, hBitmap, 0, 0, NULL, pBitmapInfo, DIB_RGB_COLORS)) {
// error
std::cout << "failed to get bitmap info" << std::endl;
}
std::unique_ptr<char> buffer(new char[bi.biWidth * bi.biHeight * bi.biBitCount / 8]);
// Buffer points to some address before calling GetDIBits(). After calling GetDIBits(), buffer points to NULL. bytesWritten is 16
int bytesWritten = GetDIBits(hdc, hBitmap, 0, height, (LPVOID)buffer.get(), pBitmapInfo, DIB_RGB_COLORS);
if (bytesWritten <= 0) {
// error
std::cout << "failed" << std::endl;
}
DeleteObject(hBitmap);
ReleaseDC(NULL, hdc);
if (iconInfo.hbmColor)
DeleteObject(iconInfo.hbmColor);
if (iconInfo.hbmMask)
DeleteObject(iconInfo.hbmMask);
return buffer;
}
You need to allocate a suitably-sized BITMAPINFO and cast it to BITMAPINFOHEADER* (or just use its bmiHeader member). Not allocate a BITMAPINFOHEADER and cast it to BITMAPINFO*. A BITMAPINFO consists of a BITMAPINFOHEADER followed by an array of 0 or more RGBQUAD elements for a color table. A BITMAPINFOHEADER itself does not contain the color table, but it does describe the color table that follows it.
Per the GetDIBits() documentation:
If the requested format for the DIB matches its internal format, the RGB values for the bitmap are copied. If the requested format doesn't match the internal format, a color table is synthesized...
If the lpvBits parameter is a valid pointer, the first six members of the BITMAPINFOHEADER structure must be initialized to specify the size and format of the DIB. The scan lines must be aligned on a DWORD except for RLE compressed bitmaps.
A bottom-up DIB is specified by setting the height to a positive number, while a top-down DIB is specified by setting the height to a negative number. The bitmap color table will be appended to the BITMAPINFO structure.
If lpvBits is NULL, GetDIBits examines the first member of the first structure pointed to by lpbi. This member must specify the size, in bytes, of a BITMAPCOREHEADER or a BITMAPINFOHEADER structure. The function uses the specified size to determine how the remaining members should be initialized.
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.
So, in your case, you are setting lpvBits to NULL, but the BITMAPINFOHEADER::biBitCount field is not 0, so GetDIBits() will try to fill in the color table of the provided BITMAPINFO, but you are not allocating any memory to receive that color table. So GetDIBits() ends up corrupting the memory that follows the BITMAPINFOHEADER.
I think you did not guarantee that the hbm parameter of GetDIBits is compatible bitmap during the process of converting HICON to HBITMAP.
Try to use the following code and test :
#include <iostream>
#include <windows.h>
using namespace std;
HBITMAP getBmp(HICON hIcon)
{
HDC hDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hDC);
HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, 32, 32);
HBITMAP hResultBmp = NULL;
HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);
DrawIconEx(hMemDC, 0, 0, hIcon, 32, 32, 0, NULL, DI_NORMAL);
hResultBmp = hMemBmp;
hMemBmp = NULL;
SelectObject(hMemDC, hOrgBMP);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DestroyIcon(hIcon);
return hResultBmp;
}
BYTE* getPixArray(HBITMAP hBitmap)
{
HDC hdc, hdcMem;
hdc = GetDC(NULL);
hdcMem = CreateCompatibleDC(hdc);
BITMAPINFO MyBMInfo = { 0 };
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
if (0 == GetDIBits(hdcMem, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS))
{
cout << " fail " << endl;
}
BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
MyBMInfo.bmiHeader.biBitCount = 32;
MyBMInfo.bmiHeader.biCompression = BI_RGB;
MyBMInfo.bmiHeader.biHeight = (MyBMInfo.bmiHeader.biHeight < 0) ? (-MyBMInfo.bmiHeader.biHeight) : (MyBMInfo.bmiHeader.biHeight);
// get the actual bitmap buffer
if (0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS))
{
cout << " fail " << endl;
}
return lpPixels;
}
int main(int argc, const char* argv[])
{
HICON hIcon = (HICON)LoadImage(0, L"test.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
HBITMAP hbmp = getBmp(hIcon);
getPixArray(hbmp);
return 0;
}
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.
I am searching for the memory leak(s) in this code.
I am new to GDI+ and I am not sure what I am doing wrong.
The class you see below gets called in a loop in my main function.
Each loop iteration I push an other vector to the function.
Everything is working fine except there is a memory leak.
I tried the program cppCheck to find the leak but it found no memory leaks :/
My last chance to fix the problem is to ask someone who has more experience than me with GDI+
Thank you very much for the help and sorry for the long code :)
#include "helper.h"
Gui::Gui(const TCHAR* fileName) {
this->fileName = fileName;
}
void Gui::drawGui(Gdiplus::Bitmap* image, std::vector<std::wstring> &vec) {
// Init graphics
Gdiplus::Graphics* graphics = Gdiplus::Graphics::FromImage(image);
Gdiplus::Pen penWhite (Gdiplus::Color::White);
Gdiplus::Pen penRed (Gdiplus::Color::Red);
Gdiplus::SolidBrush redBrush(Gdiplus::Color(255, 255, 0, 0));
penRed.SetWidth(8);
unsigned short marginTop = 15;
unsigned short marginLeft = 5;
unsigned short horizontalBarsizeStart = marginLeft + 60;
for (unsigned short iter = 0; iter < 8; iter++) {
// Draw text
std::wstring coreLabel = L"Core " + std::to_wstring(iter) + L':';
Gdiplus::Font myFont(L"Arial", 12);
Gdiplus::PointF origin(marginLeft, marginTop - 10);
graphics->DrawString(coreLabel.c_str(), coreLabel.length(), &myFont, origin, &redBrush);
// Draw CPU lines
unsigned short horizontalBarsizeEnd = horizontalBarsizeStart + std::stoi(vec.at(iter)); // 100 == Max cpu load
graphics->DrawLine(&penRed, horizontalBarsizeStart, marginTop, horizontalBarsizeEnd, marginTop);
// Draw border
Gdiplus::Rect rect(horizontalBarsizeStart, marginTop - 5, 100, 8);
graphics->DrawRectangle(&penWhite, rect);
// Next element
marginTop += 17;
}
}
bool Gui::SetColorBackgroundFromFile(std::vector<std::wstring> &vec) {
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
// Initialize GDI+.
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HDC hdc = GetDC(NULL);
// Load the image. Any of the following formats are supported: BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF
Gdiplus::Bitmap* image = Gdiplus::Bitmap::FromFile(this->fileName, false);
if (image == NULL) {
return false;
}
// Draw the gui
this->drawGui(image, vec);
// Get the bitmap handle
HBITMAP hBitmap = NULL;
Gdiplus::Status status = image->GetHBITMAP(RGB(0, 0, 0), &hBitmap);
if (status != Gdiplus::Ok) {
return false;
}
BITMAPINFO bitmapInfo = { 0 };
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
// Check what we got
int ret = GetDIBits(hdc, hBitmap, 0, 0, NULL, &bitmapInfo, DIB_RGB_COLORS);
if (LOGI_LCD_COLOR_WIDTH != bitmapInfo.bmiHeader.biWidth || LOGI_LCD_COLOR_HEIGHT != bitmapInfo.bmiHeader.biHeight) {
std::cout << "Oooops. Make sure to use a 320 by 240 image for color background." << std::endl;
return false;
}
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biHeight = -bitmapInfo.bmiHeader.biHeight; // this value needs to be inverted, or else image will show up upside/down
BYTE byteBitmap[LOGI_LCD_COLOR_WIDTH * LOGI_LCD_COLOR_HEIGHT * 4]; // we have 32 bits per pixel, or 4 bytes
// Gets the "bits" from the bitmap and copies them into a buffer
// which is pointed to by byteBitmap.
ret = GetDIBits(hdc, hBitmap, 0,
-bitmapInfo.bmiHeader.biHeight, // height here needs to be positive. Since we made it negative previously, let's reverse it again.
&byteBitmap,
(BITMAPINFO *)&bitmapInfo, DIB_RGB_COLORS);
LogiLcdColorSetBackground(byteBitmap); // Send image to LCD
// delete the image when done
if (image) {
delete image;
image = NULL;
Gdiplus::GdiplusShutdown(gdiplusToken); // Shutdown GDI+
}
return true;
}
In drawGui() you're leaking the graphics object. This line creates a new Gdiplus::Graphics object:
Gdiplus::Graphics* graphics = Gdiplus::Graphics::FromImage(image);
But nowhere do you call delete graphics to delete it once you're done with it.
In SetColorBackgroundFromFile, you're leaking the DC.
HDC hdc = GetDC(NULL);
This gets a DC for the screen, but nowhere do you call ReleaseDC(NULL, hdc); to free it.
In the same function, you are creating an HBITMAP using the following call:
Gdiplus::Status status = image->GetHBITMAP(RGB(0, 0, 0), &hBitmap);
But nowhere do you call DeleteObject(hBitmap); to free it.
You also have the problem that in case of errors, your code can return without doing necessary cleanup. E.g. if the GetHBITMAP call fails, you return immediately and will leak the image object that you created a few lines above.
I searched and I understood I'll have to use GetDIBits(). I don't know what to do with the LPVOID lpvBits out parameter.
Could someone please explain it to me? I need to get the pixel color information in a two dimensional matrix form so that I can retrieve the information for a particular (x,y) coordinate pair.
I am programming in C++ using Win32 API.
first you need a bitmap and open it
HBITMAP hBmp = (HBITMAP) LoadImage(GetModuleHandle(NULL), _T("test.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if(!hBmp) // failed to load bitmap
return false;
//getting the size of the picture
BITMAP bm;
GetObject(hBmp, sizeof(bm), &bm);
int width(bm.bmWidth),
height(bm.bmHeight);
//creating a bitmapheader for getting the dibits
BITMAPINFOHEADER bminfoheader;
::ZeroMemory(&bminfoheader, sizeof(BITMAPINFOHEADER));
bminfoheader.biSize = sizeof(BITMAPINFOHEADER);
bminfoheader.biWidth = width;
bminfoheader.biHeight = -height;
bminfoheader.biPlanes = 1;
bminfoheader.biBitCount = 32;
bminfoheader.biCompression = BI_RGB;
bminfoheader.biSizeImage = width * 4 * height;
bminfoheader.biClrUsed = 0;
bminfoheader.biClrImportant = 0;
//create a buffer and let the GetDIBits fill in the buffer
unsigned char* pPixels = new unsigned char[(width * 4 * height)];
if( !GetDIBits(CreateCompatibleDC(0), hBmp, 0, height, pPixels, (BITMAPINFO*) &bminfoheader, DIB_RGB_COLORS)) // load pixel info
{
//return if fails but first delete the resources
DeleteObject(hBmp);
delete [] pPixels; // delete the array of objects
return false;
}
int x, y; // fill the x and y coordinate
unsigned char r = pPixels[(width*y+x) * 4 + 2];
unsigned char g = pPixels[(width*y+x) * 4 + 1];
unsigned char b = pPixels[(width*y+x) * 4 + 0];
//clean up the bitmap and buffer unless you still need it
DeleteObject(hBmp);
delete [] pPixels; // delete the array of objects
so in short, the lpvBits out parameter is the pointer to the pixels
but if it is only 1 pixel you need i suggest to use getpixel to
I'm not sure if this is what you're looking for but GetPixel does pretty much what you need ...at least from i can tell from the function's description
I've been attempting to draw on an 8bpp grayscale bitmap without success. Here are some of my attempts. Maybe someone can point out what I'm doing wrong.
===================================================
Attempt 1: Create, select, and draw:
In constructor:
CBitmap bm;
bm.CreateBitmap (200, 200, 1, 8, NULL);
In OnDraw:
CDC *mdc=new CDC ();
HGDIOBJ tmp = mdc->SelectObject(bm);
Result: tmp is NULL, indicating failure.
===================================================
Attempt 2: CreateDIBSection
In constructor:
HBITMAP hbm;
BITMAPINFOHEADER bih;
BITMAPINFO bi;
HANDLE hb;
CDC* myDc = new CDC ();
HDC hdc = myDc->GetSafeHdc ();
void* bits;
RGBQUAD rq [256];
initBi ();
hbm = CreateDIBSection (hdc, &bi, DIB_RGB_COLORS, &bits, NULL, 0);
...
void CEightBitDrawingView::initBi()
{
bih.biSize = sizeof (BITMAPINFOHEADER);
bih.biWidth = 200;
bih.biHeight = -200;
bih.biPlanes = 1;
bih.biBitCount = 8;
bih.biCompression = BI_RGB;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 14173;
bih.biYPelsPerMeter = 14173;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
memset ((void *) rq, 0, 256 * sizeof (RGBQUAD));
bi.bmiHeader = bih;
bi.bmiColors = rq;
}
Result: This doesn't even compile because the BITMAPINFO bmiColors member is defined as:
RGBQUAD bmiColors[1];
so won't accept more than one RGB color. In fact, nothing I assign to this member compiles! (Could they possibly make it any more complex!?)
Any suggestions would be appreciated! Thanks!
===================================================
Here. Code that demonstrates how to - in a not managed world - allocate a dynamically sized structure on the stack, fill it in and pass it to CreateDIBSection.
#include <malloc.h>
HBITMAP CreateGreyscaleBitmap(int cx, int cy)
{
BITMAPINFO* pbmi = (BITMAPINFO*)alloca( sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256);
pbmi->bmiHeader.biSize = sizeof (pbmi->bmiHeader);
pbmi->bmiHeader.biWidth = cx;
pbmi->bmiHeader.biHeight = cy;
pbmi->bmiHeader.biPlanes = 1;
pbmi->bmiHeader.biBitCount = 8;
pbmi->bmiHeader.biCompression = BI_RGB;
pbmi->bmiHeader.biSizeImage = 0;
pbmi->bmiHeader.biXPelsPerMeter = 14173;
pbmi->bmiHeader.biYPelsPerMeter = 14173;
pbmi->bmiHeader.biClrUsed = 0;
pbmi->bmiHeader.biClrImportant = 0;
for(int i=0; i<256; i++)
{
pbmi->bmiColors[i].rgbRed = i;
pbmi->bmiColors[i].rgbGreen = i;
pbmi->bmiColors[i].rgbBlue = i;
pbmi->bmiColors[i].rgbReserved = 0;
}
PVOID pv;
return CreateDIBSection(NULL,pbmi,DIB_RGB_COLORS,&pv,NULL,0);
}
In both your examples, you created a new CDC with the following line:
CDC* pDC = new CDC();
But there's something missing: This will just create a new CDC object, but without a valid HDC handle attached to it. You need to call CDC::CreateCompatibleDC first, otherwise trying to select any object into this DC will fail.
Regarding the bmiColors: This member is defined as 1 sized array because the data behind it depends on the color depth and type of bitmap. This is documented in the MSDN. For example, if you had a 128x128 pixels 8Bit Bitmap, you would have to allocate the following amount of mem:
128 * 128 * sizeof(WORD) + sizeof(BITMAPINFOHEADER)
I finally resorted to using a .NET graphics tool (Aurigma) to create an 8bpp bitmap, and passed its handle to the unmanaged C++.
Then in C++:
HDC memDc = CreateCompatibleDC (NULL);
HGDIOBJ Obmp = ::SelectObject(memDc, varLayer); // Handle to 8-bit bitmap.
I was able to select the bitmap into a CDC and draw on it. Not 100% unmanaged, but this allowed me to do the drawing in unmanaged code, which gives acceptable speed.
Your bitmap needs to be compatible (same color-depth) as the display context you're going to render it on. Also, 8-bits/pixel bitmaps aren't necessarily grayscale - that's a function of what palette you're using.