🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Win32 GDI - Need help Adding a bitmap to this example

Started by
37 comments, last by aston2 4 years, 2 months ago

I'd like to just use pure GDI at this time to avoid users having to install OpenGL/ DirectX to run the program. I'm sure most people have either one or the other, but I like the simplicity of GDI as it pretty much offers everything I need. If I ever get to writing a raycaster or voxel engine, I'd probably go with OpenGL. Thanks for the example, I'll give it a look!

None

Advertisement

Hi, I'm new here! Nothing is impossible with the Windows GDI. I am working on my own 2d game engine just for the purpose of fun and learning. If you really want to see what is possible, I make regular updates on my youtube channel.

Here is a video of me doing a test run!

And yes this is using straight Win32 GDI. I have been working on this for about 1 year so it takes considerable time. You will be met with many challenges. There are a lot of things I had to figure out but that's what makes it fun!

I'm not using any graphics libraries, it's just all raw gdi and the background music and jumping sound effects are being played by a separate program I created that launches instances of Playsound, my game engine talks with it and tells it what to play, works really well.

Best;

Rafael

Hello! I totally agree! It is enjoyable seeing everything coming together, piece by piece. Sometimes I'm stuck for days or more on a problem, but finding the solution makes it all worth it in the end.

I've actually seen your work quite recently on YouTube. I'm very impressed and must say that it's quite possibly the most polished work using GDI only that I've seen to this point. Although I'm not going to do anything so expansive or complex, It is cool to see what can be done with GDI.

None

Airbatz said:

Hello! I totally agree! It is enjoyable seeing everything coming together, piece by piece. Sometimes I'm stuck for days or more on a problem, but finding the solution makes it all worth it in the end.

I've actually seen your work quite recently on YouTube. I'm very impressed and must say that it's quite possibly the most polished work using GDI only that I've seen to this point. Although I'm not going to do anything so expansive or complex, It is cool to see what can be done with GDI.

Great to hear! The problem solving is really why I embarked on this project, I had to figure out jump algorithms, game cycles to keep the game running at the same speed across different computers, how to use a back buffer with the gdi .. my first attempts were total failures but I NEVER gave up, every day I experimented with a ton of things and then I would sit down and figure out how to squeeze every last ounce of performance to see what I could and could not do. It has been exciting from the start and it feels so good to know that it is all my hard work and dedication.

Hopefully, I can figure out the final requirements for my own current project and get it finished. It's nothing big, but better to do it right than release something half-baked. GDI has so many different ways of going about things, I spend more than half of the time experimenting, lol.

Going back to my initial question. Since I switched over to using BitBlt and making the background pic a smaller masked image to reduce filesize, it refuses to work with the animation. About 12 hours later of trying to find a solution, I'm probably overlooking something obvious. The below code is supposed to draw the ball sprite, bouncing around the window while the background image is shown. So far, both are being drawn, but with black backgrounds. The ball also has a part of the background on it which IS transparent, for some reason.

void DrawBall(HDC hdc, RECT* prc)
{
	HDC hdcBuffer = CreateCompatibleDC(hdc);
	HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc->bottom);
	HBITMAP hbmOldBuffer = (HBITMAP)SelectObject(hdcBuffer, hbmBuffer);

	HDC hdcMem = CreateCompatibleDC(hdc);
	HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmMask);

	FillRect(hdcBuffer, prc, (HBRUSH)GetStockObject(WHITE_BRUSH));

	HBITMAP hbmOld2        = (HBITMAP)SelectObject( hdcMem, g_hbmTitle );
        BitBlt( hdcBuffer, 0, 0, prc->right, prc->bottom, hdcMem, 0, 0, SRCCOPY );

	BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem,
	0, 0, SRCAND);

	SelectObject(hdcMem, g_hbmBall);
	BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem,
	0, 0, SRCPAINT);

	BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);

	SelectObject(hdcMem, hbmOld);
	DeleteDC(hdcMem);

	SelectObject(hdcBuffer, hbmOldBuffer);
	DeleteDC(hdcBuffer);
	DeleteObject(hbmBuffer);
}

None

@Airbatz If you want the ball to be transparent, you have to take another route, your technique is how I used to do it but on my 2d game engine, if I was creating an asset, it looks something like this, you may be able to use some of the code and adapt it to your code.

// Macro for transparency color
#define chromakey RGB(100,100,100)

HDC hdc = NULL; // global DC for easy access

from my back buffer class

void BACKBUFFER2D::create(float f_width_, float f_height_)
// Prepares the back buffer surface for use.
{
    // save the back buffer size
	f_width=f_width_; f_height=f_height_;

	// create back buffer object
	backbuffer=CreateCompatibleDC(hdc);
	hbbackbuffer=CreateCompatibleBitmap(hdc,f_width,f_height);
	oldObject=SelectObject(backbuffer,hbbackbuffer);
}

This function from a class creates an asset,

void BITMAP2D::createasset(int i_width, int i_height, COLORREF cfColor)
// creates a bitmap of the specified width and height and fills it with cfColor.
{
    if(b_havebitmap) return; // we already have a bitmap!
    hbitmap=CreateCompatibleBitmap(hdc,i_width,i_height); // create bitmap surface
    GetObject(hbitmap,sizeof(bitmap),&bitmap); // get bitmap information
    hdcbuffer=CreateCompatibleDC(hdc); // create compatible dc from client
    oldObject = SelectObject(hdcbuffer,hbitmap); // select bitmap into buffer
    RECT rect = { 0, 0, i_width, i_height }; // create a rect
    FillRect(hdcbuffer,&rect,CreateSolidBrush(cfColor)); // paint with cfColor
    b_havebitmap=true; // we now have a bitmap!
}

I paint the sprite not using BitBlt() but by using a function from a library that you might already have in your compiler, it is called TransparentBlt() and works beautifully, instead of creating a mask for your ball, you can simply choose which color you want to make transparent.

void BITMAP2D::paintsp(HDC hdc_surface,
float x,float y,int i_width,int i_height,int i_xbitmap,int i_ybitmap)
// draws a section of the bitmap tile surface using the transparency macro.
{
    if(!b_havebitmap) return; // no bitmap

    TransparentBlt(hdc_surface, // destination device
        x,y, // destination position
        i_width,i_height, // dimensions
        hdcbuffer, // source device
        i_width*i_xbitmap, // source x position
        i_height*i_ybitmap, // source y position
        i_width,i_height, // source dimensions
        chromakey); // transparency macro
}

Then free up the resources,

BITMAP2D::~BITMAP2D()
// destructor cleans up
{
    DeleteObject(hbitmap);
    DeleteDC(hdcbuffer);
    SelectObject(hdcbuffer,oldObject);
}
BACKBUFFER2D::~BACKBUFFER2D()
// class destructor
{
    DeleteDC(backbuffer);
    DeleteObject(hbbackbuffer);
    SelectObject(backbuffer,oldObject);
}

I can post my BACKBUFFER and BITMAP2D classes here if you want to full around with them. When I created those two classes, my nightmares of allocating resources for the GDI were out the window.

To use TransparentBlt() you just need to include the “winmm” library. The final drawing operation is handled by the viewport class which renders the individual bitmap surfaces to the back buffer and then calls BitBlt() to paint the result.

// paint the background type to the back buffer
background.paintbk(backbuffer.get(),f_xbbIndex,f_ybbIndex,i_width,i_height);

// paint something else

// and some other thing…

// copy the back buffer contents to the window DC
BitBlt(hdc,x,y,i_width,i_height,backbuffer.get(),f_xbbIndex,f_ybbIndex,SRCCOPY);

Let me know if you've made any progress or if you were able to find the problem. I think I know where your problem is but I'm going to keep quiet to see if you can spot it. 12 hours is a long time!

@rafaelsantana

Thanks for the code. If you could post your classes, I'm sure I'll learn a thing or two from them! Endurion used TransparentBlt in his example as well and it works fine, but I'd like to know exactly what's wrong with my code. The reason I didn't go immediately with TransparentBlt was due to performance, but as Endurion indicated, it's likely irrelevant. I did make some progress on the problem. I believe it has to do with the drawing area of both graphics overlapping which caused one of them to be obscured and flickering. Following from this, both are being drawn to the screen with no flicker, but only the ball has a working mask. The remaining issues are that the background graphic is being drawn with a solid black mask, rather than with transparency. The ball sprite is also going behind the graphic, which is the wrong z-order. 12 hours is indeed long, but if the problem is solved, it's well-spent ?

Screenshot showing problem (placeholder graphics)

None

There is absolutely no performance hit when using TransparentBlt() it works exactly like BitBlt() but has the added ability to ignore a pixel of a certain color. You have seen my 2d game engine before, all surfaces are painted using TransparentBlt() and the final call is done with BitBlt() The way you are doing it requires an extra step when using SRCAND, when your scenes become more saturated, you will start to notice a performance hit.

Here are the two classes and then I'll tell you how to use them. I know you're supposed to separate them in two files but I don't bother with that since I'm constantly improving the classes.

BACK BUFFER class

#ifndef BACKBUFFER2D_H
#define BACKBUFFER2D_H

/*
     ____    ____    __  __  _  ____   __ __  _____  _____  ___  ____
    |    \  /    |  /  ]|  |/ ]|    \ |  |  ||     ||     |/  _]|    \
    |  o  )|  o  | /  / |  ' / |  o  )|  |  ||   __||   __/  [_ |  D  )
    |     ||     |/  /  |    \ |     ||  |  ||  |_  |  |_|    _]|    /
    |  O  ||  _  /   \_ |     \|  O  ||  :  ||   _] |   _]   [_ |    \
    |     ||  |  \     ||  .  ||     ||     ||  |   |  | |     ||  .  \
    |_____||__|__|\____||__|\_||_____| \____||__|   |__| |_____||__|\__|

        Backbuffer class, Copyright (c) 2019 Rafael Santana

    A class to allocate the necessary resources to create a back buffer
    to implement double-buffering for the 2d game.
*/

class BACKBUFFER2D
{
	public: // class interface
    	void create(float,float);
	~BACKBUFFER2D(); // destructor

    	// getter functions
	HDC get() { return backbuffer; }
	float getwidth() { return f_width; }
    	float getheight() { return f_height; }

	private:
	float f_width; // width of back buffer
	float f_height; // height of back buffer
	HDC backbuffer; // device context
	HBITMAP hbbackbuffer; // bitmap handle
	HGDIOBJ oldObject; // original object
};

BACKBUFFER2D::~BACKBUFFER2D()
// class destructor
{
    DeleteDC(backbuffer);
    DeleteObject(hbbackbuffer);
    SelectObject(backbuffer,oldObject);
}

void BACKBUFFER2D::create(float f_width_, float f_height_)
// Prepares the back buffer surface for use.
{
    	// save the back buffer size
	f_width=f_width_; f_height=f_height_;

	// create back buffer object
	backbuffer=CreateCompatibleDC(hdc);
	hbbackbuffer=CreateCompatibleBitmap(hdc,f_width,f_height);
	oldObject=SelectObject(backbuffer,hbbackbuffer);
}

#endif // BACKBUFFER2D_H

BITMAP2D CLASS

#ifndef BITMAP2D_H
#define BITMAP2D_H

/*
     ____   ____  ______  ___ ___   ____  ____    _____
    |    \ |    ||      ||   |   | /    ||    \  / ___/
    |  o  ) |  | |      || _   _ ||  o  ||  o  )(   \_
    |     | |  | |_|  |_||  \_/  ||     ||   _/  \__  |
    |  O  | |  |   |  |  |   |   ||  _  ||  |    /  \ |
    |     | |  |   |  |  |   |   ||  |  ||  |    \    |
    |_____||____|  |__|  |___|___||__|__||__|     \___|

    Bitmap resources, Copyright (c) 2019 Rafael Santana

    A class to represent bitmaps as objects. Allows loading of bitmaps from disk
    and allocating the necessary resources to display the bitmap surface.

    paintsp; used for drawing sprites in the 2d game.
    paintbk; usually used for painting the background layer.
    blendsp; used for alpha blending effects

    Note: The hdc is a global variable in game2d.h
*/

class BITMAP2D
{
    public: // class interface
    ~BITMAP2D(); // destructor

    void loadasset(string);
    void createasset(int,int,COLORREF);
    void blendsp(HDC,float,float,int,int,int,int,BYTE);
    void paintsp(HDC,float,float,int,int,int,int);
    void paintbk(HDC,float,float,int,int,float,float);

    // getter functions
    int getwidth() { return bitmap.bmWidth; }
    int getheight() { return bitmap.bmHeight; }
    string getfilename() { return sg_filename; }
    HDC getsurface() { return hdcbuffer; }

    private:
    HDC hdcbuffer; // handle to device
    HBITMAP hbitmap; // bitmap handle
    BITMAP bitmap; // bitmap surface
    HGDIOBJ oldObject; // to store original object
    BLENDFUNCTION blendFt; // used by alpha blend
    string sg_filename; // filename used

    bool b_havebitmap = false;
};

BITMAP2D::~BITMAP2D()
// destructor cleans up
{
    DeleteObject(hbitmap);
    DeleteDC(hdcbuffer);
    SelectObject(hdcbuffer,oldObject);
}

void BITMAP2D::loadasset(string filestr)
// loads bitmap from disk and allocates necessary resources.
{
    if(b_havebitmap) return; // we already have a bitmap!
    sg_filename=filestr; // store the files name for later use
    hbitmap=(HBITMAP)LoadImage(NULL,filestr.c_str(),IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
    GetObject(hbitmap,sizeof(bitmap),&bitmap); // get bitmap information
    hdcbuffer=CreateCompatibleDC(hdc); // create compatible dc from client
    oldObject = SelectObject(hdcbuffer,hbitmap); // select bitmap into buffer
    b_havebitmap=true; // we now have a bitmap!
}

void BITMAP2D::createasset(int i_width, int i_height, COLORREF cfColor)
// creates a bitmap of the specified width and height and fills it with cfColor.
{
    if(b_havebitmap) return; // we already have a bitmap!
    hbitmap=CreateCompatibleBitmap(hdc,i_width,i_height); // create bitmap surface
    GetObject(hbitmap,sizeof(bitmap),&bitmap); // get bitmap information
    hdcbuffer=CreateCompatibleDC(hdc); // create compatible dc from client
    oldObject = SelectObject(hdcbuffer,hbitmap); // select bitmap into buffer
    RECT rect = { 0, 0, i_width, i_height }; // create a rect
    FillRect(hdcbuffer,&rect,CreateSolidBrush(cfColor)); // paint with cfColor
    b_havebitmap=true; // we now have a bitmap!
}

void BITMAP2D::blendsp(HDC hdc_surface,
float x,float y,int i_width,int i_height,int i_xbitmap,int i_ybitmap,BYTE byte_blend)
//  draws the requested bitmap tile with a translucent effect
{
    if(!b_havebitmap) return; // no bitmap

    blendFt = { 0, 0, byte_blend, 0 }; // blend function

    AlphaBlend(hdc_surface, // dest surface
               x,y, // dest x and y
               i_width,i_height, // dest dimensions
               hdcbuffer, // src surface
               i_width*i_xbitmap, // src x position
               i_height*i_ybitmap, // src y position
               i_width,i_height, // src dimensions
               blendFt); // blend function
}

void BITMAP2D::paintsp(HDC hdc_surface,
float x,float y,int i_width,int i_height,int i_xbitmap,int i_ybitmap)
// draws a section of the bitmap tile surface using the transparency macro.
{
    if(!b_havebitmap) return; // no bitmap

    TransparentBlt(hdc_surface, // destination device
        x,y, // destination position
        i_width,i_height, // dimensions
        hdcbuffer, // source device
        i_width*i_xbitmap, // source x position
        i_height*i_ybitmap, // source y position
        i_width,i_height, // source dimensions
        chromakey); // transparency macro
}

void BITMAP2D::paintbk
(HDC hdc_surface,float x,float y,int i_width,int i_height,float f_xindex, float f_yindex)
// Mimics a viewport by drawing a section of the bitmap at the requested
// location by using the index values as the offset to draw from.
{
    if(!b_havebitmap) return; // no bitmap

    TransparentBlt(hdc_surface, // destination device
        x,y, // destination position
        i_width,i_height, // width & height
        hdcbuffer, // source device
        f_xindex,f_yindex, // source y position
        i_width,i_height, // source height
        chromakey); // transparency macro
}

#endif // BITMAP2D_H

So how to use them? In your main.cpp file

#include “backbuffer2d.h”
#include “bitmap2d.h”

// Macro for transparency color
#define chromakey RGB(100,100,100)

PAINTSTRUCT ps = { 0 }; // your typical paint struct
HDC hdc = NULL; // global DC for easy access

// create objects
BACKBUFFER2D backbuffer;
vector<BITMAP2D>bitmap(2);

Then in your Window procedure,

case WM_CREATE: // Prepare the game.
        {
            hdc=GetDC(hWnd); // get window device

            // bitmap assets
            bitmap[0].loadasset(".\\some_background.bmp");
            bitmap[1].loadasset(".\\some_sprite.bmp");

            // back buffer dimensions
            backbuffer.create(1280,720);


            ReleaseDC(hWnd,hdc); // release window device

            break;
        }

And finally in your PAINT section,

case WM_PAINT: // Paint the game
        {
		hdc = BeginPaint(hWnd,&amp;ps);

		// paint the background to the back buffer
		bitmap[0].paintbk(backbuffer.get(),0,0,1280,720,0,0);
		
		// now paint the sprite somewhere
		bitmap[1].paintsp(backbuffer.get(),100,100,100,100,0,0);

		// present the back buffer to the window DC
		BitBlt(hdc,0,0,1280,720,backbuffer.get(),0,0,SRCCOPY);            

		EndPaint(hWnd,&amp;ps);

		break;
        }

If you set it up correctly and give it a background bitmap and another bitmap that is 100x100 in dimensions, it will draw the background and place the other bitmap at co-ordinates 100,100 and if you paint a section of the bitmap in RGB colors 100,100,100 it will use that as the transparent color. You can change the color in the define section above to what ever you like.

I know this is a lot of code, very sorry. My ways are not the best way and certain there is room for improvement here and there but those two classes right there are the heart of my 2d game engine, of course there is the sprite class but there is wayyyyyy to much code in that one to post on here.

I highly recommend you write some classes while working on your example so that you won't have to start from scratch every single time. That way you can focus on the “engine” that is delivering your presentation and you can make improvements at the core level while still continuing your research with different techniques.

Great stuff! It gives me a few ideas for my own code. I haven't used used classes before in anything, but they keep coming up, so I'll have to do some reading I guess. I am now using TransparentBlt and it is working flawlessly. With that out of the way I can work on managing the way I've written the program a little better ?

None

@rafaelsantana could you build your game from the video above to EXE and attach it in an achieve?

This topic is closed to new replies.

Advertisement