• Advertisement
Sign in to follow this  

HBITMAP - How do I do tiles with a bitmap?

This topic is 3623 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've had occasions of using HBITMAP to do an overlay for an application, but they used solely the entire image, to cover the window. My question is basically, how do I access the individual tile inside the image? Any snippets that can be provided or insight in the direction to follow? Thanks. Quinn

Share this post


Link to post
Share on other sites
Advertisement
If you know how to draw a bitmap to a region of the window you already have most of what it takes to tile it. When tiling a bitmap you basically just draw it multiple times and each time you draw it you shift the location of where you're drawing it. The functions you'll need to use are GetObject(), to know the size of the HBITMAP, and BitBlt() (or similar) to actually draw the bitmap.

The basic algorithm is to start at (0, 0) in the destination and blit your image. Then move W pixels to the right, where W is the width of the bitmap, and blit your image again. Do this until you reach the end of the line then move down by H pixels, where H is the height of the bitmap, and back to the start of the row.

Share this post


Link to post
Share on other sites
Well, I was aware of using BitBlt and GetObject, but my concern, is that it would entail loading several instances of the HBitmap object, wouldn't it?

For example, if I have a struct TILE, that consists of an HBitmap - when you load the data in, that will end up with having multiple instances of the same bitmap loading up, the entire image! over the course of your data structure of tiles.

TILE MAP[MAP_WIDTH][MAP_HEIGHT] for example.

In WM_PAINT:


...
for (int x = 0; x < MAP_WIDTH; x++)
for (int y = 0; y < MAP_HEIGHT; y++)
{
RECT rSource = { (x * TILE_WIDTH),
(y * TILE_HEIGHT),
(x * TILE_WIDTH + TILE_WIDTH),
(y * TILE_HEIGHT + TILE_HEIGHT)};

hDCMem = CreateCompatibleDC(hdc);
SelectObject(hDCMem, MAP[x][y].hTile);
BitBlt(hdc, rSource.left, rSource.top, rSource.right, rSource.bottom, hDCMem,rSource.left, rSource.top , SRCCOPY);
}



In WinMain


for (int x = 0; x < MAP_WIDTH; x++)
for (int y = 0; y < MAP_HEIGHT; y++)
{
MAP[x][y].hTile = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP1));
MAP[x][y].bBlocked = false;
}



It displays my tiles fine, but isn't there a more efficient way to do it, that I may be overlooking?

Share this post


Link to post
Share on other sites
Firstly, you shouldn't be creating a DC every time you need to paint, and you don't seem to be calling DeleteDC, which is a resource leak. You should create the DC at init time, not at render time.
I might be wrong about this, but the way I've always done this is to have one HDC per HBITMAP, so the HBITMAP is always selected into the HDC. Again, I might be wrong, I'm not sure if that's the correct way, or if you should only have one HDC and change the bitmap that's selected into it.

The way I'd handle this, is to have a vector of HBITMAPs, one HBITMAP for each unique image. Then instead of using MAP[x][y].hTile, you'd have an index into the std::vector of bitmaps, and use that. So it's like a look up table.

Share this post


Link to post
Share on other sites
Quote:
Original post by Evil Steve
I might be wrong about this, but the way I've always done this is to have one HDC per HBITMAP, so the HBITMAP is always selected into the HDC. Again, I might be wrong, I'm not sure if that's the correct way, or if you should only have one HDC and change the bitmap that's selected into it.

Since the GDI functions almost all work on DCs, these are the correct ways to do this. If I have just one bitmap or if I have many bitmaps that persist for a long time I give them each a DC. If there are several bitmaps that are used occasionally I have one DC and use that to switch between them.

Share this post


Link to post
Share on other sites
In regards to the DeleteDC(), its already there, I just pasted a snippet into the source box, that didn't include it. So, I need to create MAP_WIDTH * MAP_HEIGHT DC's now too? Isn't that a little much? So, just add a DC to my struct, and throw them in the call?

Share this post


Link to post
Share on other sites
You shouldn't need to have each and every tile in it's own HBITMAP, because BitBlit/StretchBlit both support source coordinates so you can just select a region from the source bitmap and copy that into your frame buffer.

I think that was your original question, right? How do you select a single tile from an image comprised of many tiles, like a tile-sheet?

Share this post


Link to post
Share on other sites
Then its easy enough, in your BitBlit call, just set the source X/Y to the upper-left corner of the desired tile -- exactly the same as you do to set the location of the destination blit. For BitBlit, the width and height of the source and destination are the same, so one is implicit. For StretchBlit, the width and height of the source rect can be different than the destination rect, so both must be explicitly provided.

For more information, look at the BitBlit and StretchBlit documentation on MSDN.

Share this post


Link to post
Share on other sites
RECT rSource = { (x * TILE_WIDTH),
(y * TILE_HEIGHT),
(x * TILE_WIDTH + TILE_WIDTH),
(y * TILE_HEIGHT + TILE_HEIGHT)};

hDCMem = CreateCompatibleDC(hdc);
SelectObject(hDCMem, MAP[x][y].hTile);
BitBlt(hdc, rSource.left, rSource.top, rSource.right, rSource.bottom, hDCMem,rSource.left, rSource.top , SRCCOPY);
}



isn't that exactly what I was doing? lol.

Share this post


Link to post
Share on other sites
You define the rect correctly, but the RECT structure in windows is defined as left, top, right, bottom, where bitblit takes left, top, width, height.

To be honest, you've gone about things in a really backwards way here, so instead of trying to explain what's wrong with what you have, I'm just going to outline the "correct" way.


You should have a single HBITMAP which contains all of your tiles in a regular grid with no padding between tiles (in other words, a tilesheet), with some width in tiles "Ws" and some height in tiles "Hs".

You should define some width for a tile image "Wt" and some height for a tile image "Ht".

Each map element in map[Y][X] contains a numeric key used to calculate the top-left corner of the desired tile image within the tilesheet according to the formula: SrcTop = Ht * (map[Y][X] / Ws); SrcLeft = Wt * (map[Y][X] % Ws);

In the end, you end up with something like:

// Assume hDCScreen is the HDC of the BITMAP representing the visible screen.
// Assume hDCTiles is the HDC of the BITMAP containing the tilesheet.

for( /* each visible row of tiles on screen */ )
for( /* each visible column of tiles on screen */ )
BitBlt(hDCScreen,
/* location of row in screen-space */,
/*location of column in screen-space */,
Wt, Ht,
hDCTiles,
Wt * (map[Y][X] % Ws),
Ht * (map[Y][X] / Ws),
SRCCOPY);
}
}




NOTE:Also, be aware that with multi-dimensional arrays in C and C++ (and most other languages) the index "alias" (for lack of a better word) should be defined in the reverse order that we normally think about them because it is more friendly to the CPU's cache.

In other words, the definition "int map[X][Y]" defines an area of memory which is logically represented by X rows, each containing Y columns -- but what you really wanted was Y rows, each containing X columns. To get this behavior you define it as "int map[Y][X]" and swap your X and Y loops accordingly. Granted, the math works out either way as long as you're consistent, but the correct way will greatly reduce cache-misses, which is probably the biggest performance killers for the CPU today.

This extends for additional dimensions (ie prefer map[Z][Y][X] over map[X][Y][Z]) and the general rule is that the alias with the most frequent movement should be to the right, while the one with the least frequent movement should be to the left.

Share this post


Link to post
Share on other sites
Is there any chance you could show me the correct loading code, if I have a 2-D array like I've been using?

What I was initially using I pretty much realized I was loading in an entire HBITMAP to load up, and then when blitting, only referenced that location, via my RECT. So, it accomplished what I needed it to, but I knew it wasn't efficient nor a good way to accomplish.

Once I understand the premise of the snippet, that if you'd be kind enough to provide, I may revert to using maybe a vector<Tile>; but I'd like to get it working correctly, with just the 2-d array first.

Thank in advance Ravyne.

Also, you said not to put any padding - is there a way that I can include padding? such as a 1px border around the object?

Share this post


Link to post
Share on other sites
I'm afraid I don't understand what you're asking; specifically, I don't understand what you mean by the "loading code" you'd like me to show you, so you'll have to clarify before I can give you an answer.

That said, the only loading I can think of is A- Loading the map data into the 2D array, and B- Loading the tilesheet from a file into the BITMAP surface.

On the other hand, I have an inkling that neither of those are what you are talking about. If my suspicion is correct, perhaps you believe that you should be loading from the tilesheet bitmap into an intermediate one, who's hDC is used to blit that intermediate image onto the screen buffer? If so, that's not the case.

You should be going directly from the tilesheet to the screen buffer, and both should use the same pixel format so that you have no need to convert all the time.

Each area (levels, overworld, towns, dungeons, etc) would have its own tilesheet (which might be reused for similar-looking areas) containing tiles specific to that area. That tilesheet would be loaded when you entered the corresponding area; in this way you won't have a bunch of unnecessary dungeon tiles loaded up while you're walking around the overworld.

As for padding, yes you *can* have it, but I wouldn't. By adding padding between images you make the numbers much less nice for the computer to work with and the formula for calculating the tiles top-left corner gets more complicated -- not difficultly complicated, its just a few more adds, but its unnecessarily complicated none-the-less. Unless you have a good reason to add padding, I would avoid it. Since you're using BitBlt, there's no worry of texture bleeding like you might see with some 3D APIs when texture filtering is enabled -- but even if that were the case it'd be better to simply fudge the tex-coords inward a little. So in short, yes you can, but you should have a good reason to... What's yours?

Share this post


Link to post
Share on other sites
Well, the loading code I'm referring to - you stated I should be 'loading' one bitmap, and then pulling all the images from it, so in regards to my 'tileset' I shouldn't break it down and make a bitmap per tile, on my struct - just do one bitmap for the entire tile set and then have a RECT to it instead. I'm assuming anyways...

So, in regards to the reasons behind using my 1px seperator, is that I'm going to be using the tool, afterwards, for both GDI and Direct3D uses, and quite frankly, D3DSprite, doesn't play nicely without the border.

I guess in reference to the 'loading code' it can be assumed a little of both A & B.


for (int x = 0; x < MAP_WIDTH; x++)
for (int y = 0; y < MAP_HEIGHT; y++)
{
MAP[x][y].hTile = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP1));
MAP[x][y].bBlocked = false;
}



is what I did to load everything into my struct, implying each Tile had its own dedicated bitmap. To partake into what you were showing, we have one bitmap, loading, and then subdividing it per all my map. Currently, things load fine - but inefficiently, are you assuming I just do 'one' LoadBitmap call, and perhaps store the RECT and HDC inside the struct? do one loadbitmap call - and be done with it? I'm sorry, I'm trying to understand to the fullest, so that I can completely understand the premise of what I'm working on here.

In regards to the Blt call, the offsets, how would I apply those? how would you recommend? even the vaguest explaination would help - I can tinkle with it tonight - it's all I'll be working on for the next 5-6 hours. :)

Thanks Rav.

Share this post


Link to post
Share on other sites
So, currently you have some kind of tile struct, which is stored in each element of a 2D map array which holds, among other things, an HBITMAP and some collision properties.

What I propose is that, instead of storing the HBITMAP, you store the numeric key in its place. Given that you've loaded the tilesheet previously (just once) so its hDC will never change, you don't need a different hDC for every tile in the map. Using the key value, you can apply a simple formula to calculate the rect of the corresponding tile, so you don't need to store the rect itself (though, you could if you prefer.) The formula will change to reflect the padding, but I'd still argue against it -- you mentioned Direct3D has problems, which is due to a combination of the chosen texture coordinates, destination coordinates, sampling and filtering options. Are you *sure* the texture coordinates are correctly chosen?

Refer to This MSDN Page for clarification. The long and the short of it is that, with proper use, Direct3D doesn't require the padding at all.

Also, another thing you might want to consider is that you may not want to store tile structures directly in the map, but rather store in the map an index or pointer into a separate array of tile structures. When you think about it, most grass-tiles (for instance) have the same graphic, the same collision properties, the same everything. Why store a complete tile structure that might be 8 or more bytes, when a one-or-two byte index or 4 byte pointer would do? Even if you have a grass tile which behaves differently than most, it likely has duplicates itself. You should only store tile structs in the map if there's little correlation between tiles. You have to think "What data should change per map element?" and "what data should change per tile?"

Share this post


Link to post
Share on other sites
Quote:
Original post by QuinnJohns
This thread is a thread I started where I derived that information, they said at least 1-2 px border was necessary for D3DXSprite.


It's not "required", per-se. Like I said in that thread, the padding is prevent your tiles from "bleeding over" when you use bilinear filtering. It's a common problem with texture atlases.

Share this post


Link to post
Share on other sites
Ok, I have a general idea of what I'll do.

Create a map of values, src_rect, and id for each tile-type.

1 [x y width height]

then when it comes to drawing it'll just blit that src rect on the position with the id.

gotcha.



[Edited by - QuinnJohns on March 19, 2008 4:44:13 PM]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement