Accessing bits of a bitmap created with CreateDIBSection

Hello,

I would like to ask for help in my first C++ project. I am using Visual Studio 2010 Express.

I am trying to access and change the bits of a bitmap made with CreateDIBSection.
The program is a win32 project.

The full code is appended to the end of the message


I am able to load a bitmap in:
1
2
3
4
5
6
	// Load the JELLYFISH bitmap
	hBitmap = (HBITMAP)LoadImage(NULL, TEXT("Jellyfish.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
	if (hBitmap == NULL) 
		{
			ErrorExit(TEXT("LoadImage : "));
		}

where hBitmap is a global.

I then create a 'new' bitmap - and copy the bits of the first bitmap to this second bitmap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	// Create a copy of this bitmap but we need a pointer to the bits in the bitmap	
	BITMAPINFO mybmi;
	HDC hdc = GetDC(hWnd);
	mybmi.bmiHeader.biSize = sizeof(mybmi);
        mybmi.bmiHeader.biWidth = 256;
        mybmi.bmiHeader.biHeight = 192;
        mybmi.bmiHeader.biPlanes = 1;
        mybmi.bmiHeader.biBitCount = 32;
        mybmi.bmiHeader.biCompression = BI_RGB;
        mybmi.bmiHeader.biSizeImage = ((256 * (mybmi.bmiHeader.biBitCount / 8) + 3) & -4) * 192;	
	newbitmap = CreateDIBSection(hdc, &mybmi, DIB_RGB_COLORS,(VOID **)&bits, 0, 0);
	if (!newbitmap)  {ErrorExit(TEXT("CreateDIBSection"));}
	int result = GetDIBits(hdc, hBitmap, 0, 192, bits, (BITMAPINFO *)&mybmi, DIB_RGB_COLORS);
	if (!result)  {ErrorExit(TEXT("GetDIBits"));}
	ReleaseDC(hWnd, hdc);



and then try to change the pixels by directly accessing the 'bits'

 
SetRGBs(bits, 1000, 0xFFFFFFFF);


which calls the (incomplete) and failing function:

1
2
3
4
5
6
7
8
9
void SetRGBs(unsigned char* pData, int num, int colour)
{
	
	for (int i = 0; i >= 100; i++)
	{
		pData[i] = 0xFF;
	}
}


At the moment the full code displays the first bitmap and then the second after I press "Press me" menu item, but I was expecting a some of the bitmap to be white and it isn't.

I am obviously confusing some things and my questions are:

1. If I declare bits as unsigned char* bits why do I have to put (VOID **)&bits in the CreateDIBSection call?
2. Do I need to create an array of unsignded bytes on the fly everytime I call SetRGBs?
3. when using pData[i] - actually, I haven't really got a clue what I am doing here and this is the main point. What am I doing?

The reason I am doing it this way is that I have a program in another language which has a DIBSection and an address to that DIBSection. I am aiming to create a dll (all in good time) which is able to manipulate those DIBSections but only by passing the address of the DIB section (and other data such as height and width, obviously). I am trying to create the functions here first, but am obviously lost. I am more than aware that this has probably all been done before and I am reinventing the wheel but I want it to be MY wheel :).

I would be grateful for any guidance.

Michael


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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// Display Bitmap.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "Display Bitmap.h"
#include <strsafe.h>

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name
// My Global Variables
HBITMAP hBitmap, newbitmap;
HMENU   hmenu; 
unsigned char* bits;

// Forward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);
// My Functions
VOID                DisplayABitmap(HWND, HBITMAP, int, int, int, int);
VOID                SetRGBs(unsigned char*, int, int);


// Nice microsnuff error reporter.
void ErrorExit(LPTSTR lpszFunction) 
{ 
    ..Deleted to save space in message..
}


int APIENTRY _tWinMain(..Deleted to save space in message..)

ATOM MyRegisterClass(HINSTANCE hInstance)
{..Deleted to save space in message..}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

	// Load the JELLYFISH bitmap
	hBitmap = (HBITMAP)LoadImage(NULL, TEXT("Jellyfish.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
	if (hBitmap == NULL) 
		{
			ErrorExit(TEXT("LoadImage : "));
		}
	// Create a copy of this bitmap but we need a pointer to the bits in the bitmap	
	BITMAPINFO mybmi;
	HDC hdc = GetDC(hWnd);
	mybmi.bmiHeader.biSize = sizeof(mybmi);
    mybmi.bmiHeader.biWidth = 256;
    mybmi.bmiHeader.biHeight = 192;
    mybmi.bmiHeader.biPlanes = 1;
    mybmi.bmiHeader.biBitCount = 32;
    mybmi.bmiHeader.biCompression = BI_RGB;
    mybmi.bmiHeader.biSizeImage = ((256 * (mybmi.bmiHeader.biBitCount / 8) + 3) & -4) * 192;	
	newbitmap = CreateDIBSection(hdc, &mybmi, DIB_RGB_COLORS,(VOID **)&bits, 0, 0);
	if (!newbitmap)  {ErrorExit(TEXT("CreateDIBSection"));}
	int result = GetDIBits(hdc, hBitmap, 0, 192, bits, (BITMAPINFO *)&mybmi, DIB_RGB_COLORS);
	if (!result)  {ErrorExit(TEXT("GetDIBits"));}
	ReleaseDC(hWnd, hdc);

	// Make a new menu item
	hmenu = GetMenu(hWnd);
	if (!hmenu) {ErrorExit(TEXT("GetMenu"));}
	AppendMenu(hmenu, MF_ENABLED, IDM_VIEW, TEXT("Press Me"));
	DrawMenuBar(hWnd);

	// Display the bitmap for the first time
	DisplayABitmap(hWnd, hBitmap, 0, 0, 0, 0);
	InvalidateRect(hWnd, 0, 0);

   return TRUE;
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// Parse the menu selections:
		switch (wmId)
		{
		case IDM_VIEW:
			
			SetRGBs(bits, 1000, 0xFFFFFFFF);
			DisplayABitmap(hWnd, newbitmap, 256, 0, 0, 0);
			InvalidateRect(hWnd, 0, 0);
			
			break;
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		EndPaint(hWnd, &ps);
	break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

void DisplayABitmap(HWND hwnd, HBITMAP hbitmap, int x, int y, int cx, int cy)
{
	// Get a handle to the applications DC
	HDC htempdc, hdc;
	BITMAP bm;
	
	hdc = GetDC(hwnd);
	if (!hdc) {ErrorExit(TEXT("GetDC"));}

	htempdc = CreateCompatibleDC(hdc);
	if (!htempdc) {ErrorExit(TEXT("CreateCompatibleDC"));}

	// Get the information about the required bitmap
	int iReturn = GetObject(hbitmap, sizeof(bm), &bm);
	// if (!iReturn) {ErrorExit(TEXT("GetObject"));}
    // Really don't f..f.f..fffff...fudgeing understand why this returns an error but seems to fill out the bm{}

	HBITMAP hOldBitmap = (HBITMAP)SelectObject(htempdc, hbitmap);
	if (hOldBitmap == HGDI_ERROR) {ErrorExit(TEXT("SelectObject"));}
	BitBlt(hdc, x, y, bm.bmWidth, bm.bmHeight, htempdc, 0, 0,  SRCCOPY);
	
	// Delete the objects we created
	DeleteDC(htempdc);
	ReleaseDC(hwnd, hdc);
}

void SetRGBs(unsigned char* pData, int num, int colour)
{
	
	for (int i = 0; i >= 100; i++)
	{
		pData[i] = 0xFF;
	}
}



1. If I declare bits as unsigned char* bits why do I have to put (VOID **)&bits in the CreateDIBSection call?


This is just a quirk. CreateDIBSection takes a void** and an unsigned char** cannot be implicitly cast to a void**, so you have to explicitly cast.

Normally I say "don't cast around compiler errors", but in this case it is the right thing to do. You are doing it correctly.


2. Do I need to create an array of unsignded bytes on the fly everytime I call SetRGBs?


No. pData is the array. It actually points directly to the bitmap bits.

3. when using pData[i] - actually, I haven't really got a clue what I am doing here and this is the main point. What am I doing?


After the CreateDIBSection call, pData points to the bitmap bits. So you can effectively treat it as an array of pixels. Think of it like this:

 
unsigned char pData[bitmap_width * 4 * bitmap_height]; // see technicality notes below 


Accessing individual pixels can be done like this:

1
2
3
4
5
6
7
8
9
10
// we want to change the pixel at coord 5,7 to be green
int x = 5;
int y = 7;

pData[ (y * bitmap_width * 4) + (x * 4) + 0 ] = 0x00;  // blue component  *
pData[ (y * bitmap_width * 4) + (x * 4) + 1 ] = 0xFF;  // green component
pData[ (y * bitmap_width * 4) + (x * 4) + 2 ] = 0x00;  // red component  *

// * I might have blue and red backwards.  Try it out and see
//  * also, see technicality notes below 


Alternatively, if you want to operate on the pixels as a whole instead of their individual components, you can have pData be a DWORD* (32 bits) instead of a unsigned char* (8 bits). I find this to be much easier, and is also likely faster:

1
2
3
4
5
6
7
8
9
//DWORD pData[bitmap_width * bitmap_height];  <- imagine it's declared like this:

// let's do the same thing.  set 5,7 to green
int x = 5;
int y = 7;

pData[ (y * bitmap_width) + x ] = 0x0000FF00;
                             //   0x__RRGGBB
                             //   again I might have R and B backwards 


That's all there is to it. Once you write to pData, the changes will be immediately visible in the bitmap. You don't have to do any other function calls or anything. (Though you will have to blit the DC to the display in order to see the bitmap)



Technicality notes

1) Bitmap rows are padded to 4 byte boundaries so my above examples are only really correct for 32-bit bitmaps. If you have a different bitwidth, you'll need to calculate the byte size of each row separately from the width (for example, if you have a 16-bit bitmap that is 5 pixels wide, that would be 10 bytes of pixel data -- but each row in the bitmap would be 12 bytes long -- padded to the nearest 4-byte boundary)


2) Bitmap data is stored UPSIDE-DOWN so the above formulas are incorrect of you have a "normal" bitmap. The origin in a normal bitmap is the bottom-left corner, not the top left like you'd expect.

That said, you can "flip" a bitmap to make the origin the upper-left corner by creating it with a negative height. So in the above, when you call CreateDIBSection, I would recommend setting the height to -192 instead of 192.


EDIT:

Now that I actually look at your code, you are kind of displaying the bitmap wrong, and in an unnecessarily complex way.

For a single bitmap you need 3 things:

1) an HBITMAP
2) an HDC to hold the bitmap
3) another HBITMAP (the "old" bitmap)

These 3 things should exist for the lifetime of the bitmap. do not keep recreating and destroying the DC every time you want to draw. That is extremely wasteful.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// set it up:
HDC dc = CreateCompatibleDC( NULL );
HBITMAP bmp = CreateDIBSection( ... );
HBITMAP bmpold = (HBITMAP)SelectObject(dc,bmp);

// keep those 3 vars like that for the lifetime of the bitmap
//  when you want to display the bitmap

HDC screenDC = GetDC(wnd);
BitBlt( ... );  // blit 'dc' onto 'screenDC'
ReleaseDC(wnd,screenDC);

// then, when it's time to clean up -- when you no longer need the bitmap:
SelectObject(dc,bmpold);  // put the old bmp back in the dc
DeleteDC(dc);  // kill the DC
DeleteObject(bmp);  // kill the bitmap 



It's worth noting that since dc,bmp, and bmpold are so tightly coupled, it makes perfect sense to put all of this in a class. Then you can overload the () operator to set individual pixels and stuff to make life easier.
Last edited on
Disch,

Thank you very much for the help. It is really appreciated.

I'll take your comments on board and try to tidy up my code a bit more.

As too:
It's worth noting that since dc,bmp, and bmpold are so tightly coupled, it makes perfect sense to put all of this in a class. Then you can overload the () operator to set individual pixels and stuff to make life easier.

I'm just going to have to nod my head and look like I know what your talking about. I haven't got that far in C++ yet, but one day!

Michael
I'm just going to have to nod my head and look like I know what your talking about. I haven't got that far in C++ yet, but one day!


This would actually be a very good introductory project for learning C++ classes. I would really recommend you try to make a class around this as a learning experiment.

It could work like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// "Image" is the class you'd create
Image myimage(800,600);  // Creates an 800x600 pixel image

myimage.SetPixel( x, y, 0x00RRGGBB ); // set an individual pixel

DWORD whatever = myimage.GetPixel( x, y );  // get a pixel

// or you could get fancy and instead of having get/set pixel functions
//   just overload the () operator to function as both:
myimage(x,y) = 0xRRGGBB;
whatever = myimage(x,y);

// then when you want to draw it to the screen:
myimage.Draw( screenDC, coords_and_stuff_go_here );

// and write a destructor to automatically clean up 
Last edited on
Ok, Thanks for the pointers and encouragement. I've cleaned up the original code a lot and got somewhere towards understanding more of what is going on.

I'll have a further read about classes and try to implement something. I am sure I'll be back asking more.

Michael
Topic archived. No new replies allowed.