Cheat Engine Forum Index Cheat Engine
The Official Site of Cheat Engine
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 


[Tutorial]Manually loading and drawing a bitmap in C + GDI

 
Post new topic   Reply to topic    Cheat Engine Forum Index -> Game Development
View previous topic :: View next topic  
Author Message
hcavolsdsadgadsg
I'm a spammer
Reputation: 26

Joined: 11 Jun 2007
Posts: 5801

PostPosted: Mon Jun 08, 2009 2:51 am    Post subject: [Tutorial]Manually loading and drawing a bitmap in C + GDI Reply with quote

I got bored and wrote this. It is kind of long winded, hopefully someone actually cares enough to skim (or even read!) through it. I think it will be nice as a quick kick in the ass towards getting something onscreen.

I guess you could fancy it up for a game and implement dirty rectangles and other neato things, but it drawing is pretty quick as is and is probably passable for a game at low resolutions. Blitting is probably hardware accelerated.

Really, if you need more speed, you shouldn't be using GDI, but unless you're drawing hilariously complex scenes or using really big resolutions or you have a horrendous CPU, you'll probably eat whatever you do alive. I'll probably write about a Tetris clone next whenever I get around to it and I have full confidence that it will easily run full speed, (for example, this draws the 256 x 256 bitmap in this 100 times a second with 0% cpu usage for me) so take that as you will. Anyway, onto the content.




Bitmaps are amazingly easy to read / write, especially those of the 32 bit variety. You can read / write an entire pixel at once instead of having to go through each one, which makes it fast and easy.

As such, I'll be going over how to read a 32 bit bitmap and display it on the screen in C using GDI and Win32 (without LoadImage). Yes, it's simple, but it's probably something that will come up should you ever want to do anything involving graphics in a game for example. For this, I've found that not even MS-Paint itself can actually write a 32 bit bitmap, so I used GIMP to do it.

The code may not be fantastic (maybe it is horrible?), but whatever, feel free to enlighten me if you feel so inclined to do so, I like feedback, even if it is you telling me I suck dicks, which is quite possible. Furthermore, I haven't really reread anything I wrote and I've written all of this in one sitting, so hopefully nothing is wrong... but whatever! Hopefully it will at least be an enlightening read. Furthermore, I will include the bitmap I use and the exe, which you can download right here: http://files.filefront.com/definitely+a+virusrar/;13870709;/fileinfo.html

Just a quick note that due to my laziness, it expects the .bmp in 'C:\' (and as a bonus, it doesn't check if it actually loaded the file, it goes boom). The code is included, you do it. Smile

Here's what I hope you have...
1.A basic knowledge of C / C++
2.A basic knowledge of Win32 / GDI
3.A brain

I guess you don't NEED any of this, but it would sure be nice. I'll try to explain in depth enough that anyone can understand it, but I don't know how in line with reality this wish is... be sure to look up the API's used on MSDN as you go along, as it will definitely explain in greater detail. Just throw it into Google and it will likely come up as the first hit.


I created a 4x1 image filled with 4 pixels, red, green, blue, and white. This
bitmap looks something like this under a hex editor.

Code:
42 4D 56 00 00 00 00 00 00 00 46 00 00 00 38 00 00 00 04 00 00 00 01 00 00 00 01 00
20 00 03 00 00 00 10 00 00 00 13 0B 00 00 13 0B 00 00 00 00 00 00 00 00 00 00 00 00
00 FF 00 00 FF 00 00 FF 00 00 00 00 00 00 00 00 00 FF 00 00 FF 00 00 FF 00 00 00 FF
FF FF


First off, the contains a header which holds whatever you'd need to know about the
picture. I'm not going to explain the whole header, you can look that up your self.
Lets take a look at what's (probably) important to us.

0x0A (DWORD) - Where the actual pixel data can be found.
0x12 (WORD) - Width
0x16 (WORD) - Height
0x1C (WORD) - Number of bits, this would be a good thing to check...

Ok, so lets look at the bitmap. 0x0A (or the 10th byte) is 0x46 (70). Copying the
end of the file back to 0x46 and we get this:

Code:
00 00 00 FF 00 00 FF 00 00 FF 00 00 00 FF FF FF


Remember, 4 bytes a pixel, so...

Code:
00 00 00 FF (1st pixel, red)
00 00 FF 00 (2nd pixel, green)
00 FF 00 00 (3rd pixel, blue)
00 FF FF FF (4th pixel, white)


I'm sure you'll notice the bytes are stored as BGR, this will work in our favor as
you'll see later.

Lets actually load the file, now. Hold onto your hats!


Create a simple struct for what we mine out of the header.

Code:
struct BitmapInfo{
   DWORD* rawStart;
   DWORD* width;
   DWORD* height;   
};



Simple function to load it.

Code:
BYTE* GFX_LoadBMP(char* path, struct BitmapInfo* bmi){
   DWORD bytesRead;
   HANDLE hFile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //open the file
   DWORD bmSize = GetFileSize(hFile, NULL); //no idea what this line does...

   BYTE* bm = (BYTE*)HeapAlloc(GetProcessHeap(), 0, bmSize); //allocate some memory
   ReadFile(hFile, bm, bmSize, &bytesRead, NULL);
   CloseHandle(hFile);

   bmi->rawStart   = (DWORD*)&bm[0x0A]; //do the math, example...
   bmi->width   = (DWORD*)&bm[0x12]; //(bm + 0x12)
   bmi->height   = (DWORD*)&bm[0x16];

   return bm; //return the address which points to the bitmap.
}


Easy so far. We're ready to draw it... but... how? Easily. (and not using SetPixel)


Lets create a simple function to set us up a bitmap (or a DIB) of our own to draw on.

Code:

HWND      hwnd;
HDC         hdc,
         hdcMem;
HBITMAP      hDib,
         hbmOld;
BITMAPINFO   bi;
LPDWORD      bits;

void GFX_Init(HWND Hwnd, int width, int height){
   hwnd = Hwnd;
   SCREEN_WIDTH   = width;
   SCREEN_HEIGHT   = height;

   bi.bmiHeader.biSize     = sizeof(BITMAPINFO);
   bi.bmiHeader.biWidth    = width;
   bi.bmiHeader.biHeight   = -height; //top down, we'll get back to this
   bi.bmiHeader.biPlanes   = 1;
   bi.bmiHeader.biBitCount = 32;

   hdc      = GetDC(hwnd);
   hdcMem = CreateCompatibleDC(hdc);
   hDib   = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, &bits, NULL, 0);
   hbmOld = SelectObject(hdcMem, hDib);
}

And we're ready. It's still a bitmap, so drawing on it works as expected.


Code:
void GFX_DrawPixel(int x, int y, COLORREF color){
   if(x < SCREEN_WIDTH && y < SCREEN_HEIGHT){ //out of bounds = crash. Think about it.
      DWORD* dst = bits + (y * SCREEN_WIDTH + x); //I'll elaborate shortly
      *dst = color; //destination = color;
   }
}


If you were not paying attention, CreateDIBSection provides us with a pointer to the bits of the bitmap (well, DIB) it created. Following (y * SCREEN_WIDTH + x) is an easy way to access the pixel you want.

The pixel data for the bitmap is just a block of data after all.

Using our bitmap as an example again:

Code:
00 00 00 FF 00 00 FF 00 00 FF 00 00 00 FF FF FF


Say we wanted to access the second pixel, or the green one.
(y * SCREEN_WIDTH + x) would become...

(0 *4 + 1)

(0 * 4) is obviously 0.
(0 + 1) is amazingly enough, 1.

We move 1 DWORD ahead and surely enough, we arrive at our pixel: 00 00 FF 00 (Remember, BGR)


Ok, cool. With the power of basic math and some logic, we've written some pixel data into our buffer. How do we draw it?

Code:
void GFX_Draw(void){
   BitBlt(hdc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, hdcMem, 0, 0, SRCCOPY); //blit from offscreen
   ZeroMemory(bits, (SCREEN_WIDTH * SCREEN_HEIGHT) << 2); //and zero the DIB bits... or clear the image. Take a guess what happens if you don't clear it? (hint: the old image stays and you get a horrible ghosting effect)
}


Cool, we can draw pixels fast now (much, much faster than SetPixel can ever hope to be), so lets draw our bitmap.

Code:
void GFX_DrawBMP(BYTE* bm, struct BitmapInfo* bmi, COLORREF ignoreKey){
   int   x, y, i;
   DWORD* index = (DWORD*)&bm[0x46]; //color

   for(i = 0, y = (int)(*bmi->height - 1); y >= 0; y--) //starts at 0, not 1.
   {
      for(x = 0; x <= (int)(*bmi->width - 1); x++, i++) //ditto
      {      
         if((index[i] >> 8) != ignoreKey)
            GFX_DrawPixel(x, y, (index[i] >> 8));      
      }
   }
}


This probably looks like a disaster, but it's simple enough upon breaking it down.

Our LoadBMP function returns a pointer to the allocated memory which holds our loaded bitmap file data, if you remember and which we need here. Along with that, a pointer to our structure to for the the location of the pixel data and we're ready for action. I'll get to ignoreKey in a second.

Code:
for(i = 0, y = (int)(*bmi->height - 1); y >= 0; y--)

I and y should be clear enough; init I to 0, and y to the height (remember, our bitmap is 1 pixel tall and 4 wide.) minus 1, since we start at 0, not 1. Just to recap: 1 – 1 = 0, hopefully you understand this complex math. I'm not sure if I've mentioned it yet, but we'll be reading our bitmap from the bottom left since a typical bitmap will be bottom up, despite our DIB being top down. We wouldn't want a upside down image, would we? (Well, I sure don't at least...)

Code:
for(x = 0; x <= (int)(*bmi->width - 1); x++, i++) //ditto

If you could figure out the last line, I'm sure you can manage this.



(JUST INCASE YOU DO NOT WANT TO USE A TOP DOWN DIB FOR SOME REASON, YOU CAN PLOT YOUR BITMAP AS SUCH, OTHERWISE, IGNORE THIS)
Code:
for(i = 0, y = 0; y <= (int)(*bmi->height - 1); y++) //starts at 0, not 1.
{      
   for(x = 0; x <= (int)(*bmi->width - 1); x++, i++) //ditto
   {      
      //woop...   
   }
}


Here's a simple program I wrote just as I got to this section to visualize how this is actually going through the pixels. (Protip: start with low numbers, 2x2 or 4x4. It adds up quickly)

Code:
#include <Windows.h>
#include <iostream>

int main(){
   int x, y, width, height;

   std::cout << "Enter width, then height";
   std::cin >> width >> height;

   for(y = (int)(height - 1); y >= 0; y--)
   {
      for(x = 0; x <= (int)(width - 1); x++)
      {
         std::cout << "x " << x << " " << "y " << y << " " << std::endl;
      }
   }

   std::cout << std::endl;

   for(y = 0; y <= (int)(height - 1); y++)
   {
      for(x = 0; x <= (int)(width - 1); x++)
      {
         std::cout << "x " << x << " " << "y " << y << " " << std::endl;
      }
   }
   return 0;
}


Also: This will probably prove a useful read! http://msdn.microsoft.com/en-us/library/dd407212(VS.85).aspx
If it isn't obvious enough by this point, ignoreKey is the color it choses it ignore, so you can use it as a mask for transparency for example/ 0x00FFFFFF would not draw any white pixels in the bitmap.

Finally, the last thing. What the hell is
Code:
index[i] >> 8
you are probably wondering. Oh, it is a beautiful thing indeed.

To refresh your memory one more time, our bitmap:
Code:
00 00 00 FF 00 00 FF 00 00 FF 00 00 00 FF FF FF


Maybe you'd think
Code:
index[0]
would be 0x000000FF (we are reading it a DWORD at a time, remember) at first glance... well guess what, it's not. Hello, little endian! It's 0xFF000000. Shift right 8 and we have our correct color, red.

Here's an example, wow!
Code:
   byte farts[] = {0x00, 0x00, 0x00, 0xFF}; //0, 1, 2, 3
   DWORD* butt = (DWORD*)&farts; //least significant is 3, most significant is 0,
   std::cout << std::hex << *butt;



Ok, lets wrap this shit up, since it is already five pages in OpenOffice... with a fontsize of 10. Here will just be the entirety of the code incase you can not piece it together for some reason.

Gfx.h:

Code:
#include <Windows.h>

int SCREEN_HEIGHT;
int SCREEN_WIDTH;

HWND      hwnd;
HDC         hdc,
         hdcMem;
HBITMAP      hDib,
         hbmOld;
BITMAPINFO   bi;
LPDWORD      bits;

struct BitmapInfo{
   DWORD* rawStart;
   DWORD* width;
   DWORD* height;
};

void GFX_Init      (HWND Hwnd, int width, int height);
void GFX_Close      (void);
void GFX_Draw      (void);
void GFX_DrawPixel   (int x, int y, COLORREF color);
BYTE* GFX_LoadBMP   (char* path, struct BitmapInfo* bmi);
void GFX_DrawBMP   (BYTE* bm, struct BitmapInfo* bmi, COLORREF ignoreKey);



Gfx.c
Code:
#include "gfx.h"

void GFX_Init(HWND Hwnd, int width, int height){
   hwnd = Hwnd;
   SCREEN_WIDTH   = width;
   SCREEN_HEIGHT   = height;

   bi.bmiHeader.biSize     = sizeof(BITMAPINFO);
   bi.bmiHeader.biWidth    = width;
   bi.bmiHeader.biHeight   = -height; //top down
   bi.bmiHeader.biPlanes   = 1;
   bi.bmiHeader.biBitCount = 32;

   hdc      = GetDC(hwnd);
   hdcMem = CreateCompatibleDC(hdc);
   hDib   = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, &bits, NULL, 0);
   hbmOld = SelectObject(hdcMem, hDib);
}

void GFX_Close(void){
   SelectObject(hdcMem, hbmOld);
   DeleteObject(hDib);
   ReleaseDC(hwnd, hdc);
   DeleteDC(hdcMem);
}

void GFX_Draw(void){
   BitBlt(hdc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, hdcMem, 0, 0, SRCCOPY);
   ZeroMemory(bits, (SCREEN_WIDTH * SCREEN_HEIGHT) << 2);
}

void GFX_DrawPixel(int x, int y, COLORREF color){
   if(x < SCREEN_WIDTH && y < SCREEN_HEIGHT){
      DWORD* dst = bits + (y * SCREEN_WIDTH + x);
      *dst = color;
   }
}

BYTE* GFX_LoadBMP(char* path, struct BitmapInfo* bmi){
   DWORD bytesRead;
   HANDLE hFile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   DWORD bmSize = GetFileSize(hFile, NULL);

   BYTE* bm = (BYTE*)HeapAlloc(GetProcessHeap(), 0, bmSize);
   ReadFile(hFile, bm, bmSize, &bytesRead, NULL);
   CloseHandle(hFile);

   bmi->rawStart   = (DWORD*)&bm[0x0A];
   bmi->width      = (DWORD*)&bm[0x12];
   bmi->height      = (DWORD*)&bm[0x16];

   return bm;
}

void GFX_DrawBMP(BYTE* bm, struct BitmapInfo* bmi, COLORREF ignoreKey){
   int   x, y, i;
   DWORD* index = (DWORD*)&bm[0x46]; //color

   for(i = 0, y = (int)(*bmi->height - 1); y >= 0; y--) //starts at 0, not 1.
   {
      for(x = 0; x <= (int)(*bmi->width - 1); x++, i++) //ditto
      {      
         if((index[i] >> 8) != ignoreKey)
            GFX_DrawPixel(x, y, (index[i] >> 8));      
      }
   }
}


Main.c

Code:
#include <Windows.h>
#include "gfx.h"

int main(void)
{
   struct BitmapInfo bmInfo;
   BYTE* bm = GFX_LoadBMP("C:\\testbmp32.bmp", &bmInfo);
   GFX_Init(FindWindow("ConsoleWindowClass", NULL), (int)*bmInfo.width, (int)*bmInfo.height);

   while(!GetAsyncKeyState(VK_ESCAPE))
   {
      GFX_DrawBMP(bm, &bmInfo, RGB(255, 255, 255));
      GFX_Draw();
      Sleep(10);
   }

   HeapFree(GetProcessHeap(), 0, bm);
   GFX_Close();   
   return 0;
}


… whew!
Back to top
View user's profile Send private message
nwongfeiying
Grandmaster Cheater
Reputation: 2

Joined: 25 Jun 2007
Posts: 695

PostPosted: Tue Jun 09, 2009 7:07 pm    Post subject: Reply with quote

tl;dr

Loading pictures in C using GDI. I'm just kidding; I'm actually reading the whole thing. I have a question before I start though. You said it was loading a bitmap. If I use another format, it won't work. Correct?
Back to top
View user's profile Send private message
hcavolsdsadgadsg
I'm a spammer
Reputation: 26

Joined: 11 Jun 2007
Posts: 5801

PostPosted: Tue Jun 09, 2009 7:29 pm    Post subject: Reply with quote

ya, you'll probably end up displaying garbage and crashing.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Cheat Engine Forum Index -> Game Development All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Powered by phpBB © 2001, 2005 phpBB Group

CE Wiki   IRC (#CEF)   Twitter
Third party websites