Jump to content

  • Log In with Google      Sign In   
  • Create Account

- - - - -

04.05 - Image Transfer

  • You cannot reply to this topic
30 replies to this topic

#1 Teej   GDNet+   -  Reputation: 176

Like
Likes
Like

Posted 22 May 2001 - 03:29 AM

Copying and Pasting The trend that we're trying to set here is to collect all of your game images into a single bitmap, load that bitmap into a DirectDraw surface, and 'copy and paste' these images onto our backbuffer using Blt() or BltFast() . We already have enough tools to accomplish this task with our game template code -- now it's just a matter of learning some techniques. For this article I'm using BaseCode1 again, in its original form. You should do the same, as we've made various changes to the template code in the last article. Also, it doesn't really matter which resolution or color depth you use, as we'll only be concentrating on surface blitting (BLT = Bit bLock Transfer, or 'blitting' for slang). What we will be interested in this time around is the RESOURCE.BMP file that's included in the template code... Letters And Numbers It isn't hard to imagine what type of task we could perform with RESOURCE.BMP. What we're interested in doing is creating a function that, given a string of letters, numbers and spaces, displays the string at a requested location on the display. In other words, we'd like to write a function like this: void DrawFont(int x, int y, const char *pszText); So then if we wanted to display a string on the display in the top-left corner, we'd make a call like so: DrawFont(0, 0, "Hello there"); Feeling Out RESOURCE.BMP We definitely need to take a closer look at our bitmap image in order to start coming up with a game plan. Here are some observations:
  • Everything is organized by row. The first row contains all of the capital letters from A to Z, in order. The second row contains the small letters, and the final row contains the digits from 0 to 9.
  • The font used in the image is fixed-pitch, which means that all characters are evenly spaced in the row.
  • The rows of characters are not the same height. The small letters are obviously taller than the capitals or numbers.
Perhaps the most important observation of them all has to do with the ASCII character set, which numbers all of the letters, numbers and punctuation symbols commonly used on computers. All of the letters (both capital and small) and numbers are listed in their natural order in the ASCII table, which means that we can take advantage of the way they are grouped mathematically. By the way, you can do an index search on 'ASCII character codes chart' in the online SDK documentation if you want to take a good look at the ASCII table. For characters, the capital letter 'A' is character number 65, like so: char c = 'A'; printf("Char = %d", c); // output: Char = 65 We can perform mathematical calculations directly using characters: char c = 'A'; c += 3; printf("Char = %c (number %d)", c, c); // output: Char = D (number 68) There is a direct relationship between a character, its position in the ASCII table, and its position in our image, and now we're going to figure it out. From the Known to the Unknown So long as you have multiple images in a surface, you're always going to need to be able to find the proper coordinates that isolate any image. What is always known is that (0, 0) is the top-left of the image surface. In order to copy an area of memory from one surface to another, you need to know the starting top-left coordinate of the source image, as well as its width and height. Let's start with the character 'A'. We know that its starting coordinates are (0,0), and by using the fact that all capital letters are evenly spaced, we can determine that a capital letter is 11 pixels wide and 13 pixels high. Simple enough:
#define ALPHA_LARGE_STARTX  0
#define ALPHA_LARGE_STARTY  0
#define ALPHA_LARGE_WIDTH   11
#define ALPHA_LARGE_HEIGHT  13
           
Since we need to describe this character in a RECT structure for use with Blt() or BltFast() , here's what it might look like:
rectSrc.left = ALPHA_LARGE_STARTX;
rectSrc.top = ALPHA_LARGE_STARTY;
rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1;
rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT - 1;
   
What about the letter B? We know intuitively that it starts 13 pixels over from the beginning of the letter A, and has the same height and width: rectSrc.left = ALPHA_LARGE_STARTX + ALPHA_LARGE_WIDTH; (the other three RECT fields are the same as with 'A') Here's the letter C: rectSrc.left = ALPHA_LARGE_STARTX + (ALPHA_LARGE_WIDTH * 2); Can you see the pattern? If X is the letter that we want (A = 0, B = 1, C = 2, etc.), something like this would work: rectSrc.left = ALPHA_LARGE_STARTX + (ALPHA_LARGE_WIDTH * X); Now that we have a mathematical expression that describes the section of the image that we want, all we need to do is take a given character and convert it into a proper X value. Since we know that 'A' = 65, wouldn't it be alright to say something like: X = C - 65; ...where C is the current character? Yessir it is! Here's a quick and dirty routine for calculating the source RECT:
RECT rectSrc;
char szText[] = {"ABCDEFG"};
 
for (int j = 0; j < strlen(szText); j++)
{
    rectSrc.left = ALPHA_LARGE_STARTX + ((szText[j]- 'A') * ALPHA_LARGE_WIDTH);
    rectSrc.top = ALPHA_LARGE_STARTY;
    rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1;
    rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT - 1;
}
 
Of course, this routine assumes that every character in szText is a capital letter. Since we've really got three types of characters to display, we'll need to perform some decision-making in our loop like so: if (szText[j] >= 'A' && szText[j] <= 'Z') ... Capital letters if (szText[j] >= 'a' && szText[j] <= 'z') ... Small letters if (szText[j] >= '0' && szText[j] <= '9') ... Numbers if (szText[j] == ' ') ... Space I threw the last one in (space) so that we have at least something useable. Putting the Pieces Together First, here's our definitions:
// Font definitions
#define ALPHA_LARGE_STARTX  0
#define ALPHA_LARGE_STARTY  0
#define ALPHA_LARGE_WIDTH   11
#define ALPHA_LARGE_HEIGHT  13
 
#define ALPHA_SMALL_STARTX  0
#define ALPHA_SMALL_STARTY  13
#define ALPHA_SMALL_WIDTH   11
#define ALPHA_SMALL_HEIGHT  17
 
#define NUMERIC_STARTX      0
#define NUMERIC_STARTY      29
#define NUMERIC_WIDTH       11
#define NUMERIC_HEIGHT      13
   
Now, here's a look at a possible algorithm for drawing a single line of text consisting of letters (large and small), numbers and spaces:
  • For each character in the string:
    • Is it a capital letter? If so, use ALPHA_LARGE_XXX defines to construct source rectangle
    • Is it a small letter? If so, use ALPHA_SMALL_XXX defines to construct source rectangle
    • Is it a number? If so, use NUMERIC_XXX defines to construct source rectangle
    • Is it a space? If so, move over one character and continue with the next character
    • If it is not a valid character, continue with the next character in the string
  • Display the character on the backbuffer
  • Increment x counter
Here's my first run-through for this function. Keep in mind that we don't shoot for an ideal function our first time through, just a working one:
                      
//////////////////////////////////////////////////////////////////////////////

// DrawFont

//

void DrawFont(int x, int y, const char *pszText)
{
    RECT rectSrc;
    int location = 0;
    int letterSpacing;
    char curr;
 
    // Go through each character in the given string

    for (int i = 0; i < (int)strlen(pszText); i++)
    {
        // Get the current character

        curr = pszText[i];
        
        // Capital letter?

        if (curr >= 'A' && curr <= 'Z')
        {
            rectSrc.left = ALPHA_LARGE_STARTX + ((curr - 'A') * ALPHA_LARGE_WIDTH);
            rectSrc.top = ALPHA_LARGE_STARTY;
            rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1;
            rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT - 1;
 
            letterSpacing = ALPHA_LARGE_WIDTH;
        }
        
        // Small letter?

        else if (curr >= 'a' && curr <= 'z')
        {
            rectSrc.left = ALPHA_SMALL_STARTX + ((curr - 'a') * ALPHA_SMALL_WIDTH);
            rectSrc.top = ALPHA_SMALL_STARTY;
            rectSrc.right = rectSrc.left + ALPHA_SMALL_WIDTH - 1;
            rectSrc.bottom = rectSrc.top + ALPHA_SMALL_HEIGHT - 1;
 
            letterSpacing = ALPHA_SMALL_WIDTH;
        }
 
        // Number?

        else if (curr >= '0' && curr <= '9')
        {
            rectSrc.left = NUMERIC_STARTX + ((curr - '0') * NUMERIC_WIDTH);
            rectSrc.top = NUMERIC_STARTY;
            rectSrc.right = rectSrc.left + NUMERIC_WIDTH - 1;
            rectSrc.bottom = rectSrc.top + NUMERIC_HEIGHT - 1;
 
            letterSpacing = NUMERIC_WIDTH;
        }
        
        // Other (space or non-supported character)

        else
        {
            // If it's a space, move over one character (screen position)

            if (curr == ' ') location++;
            
            // Skip to the next character in the string

            continue;
        }
 
        // Draw this character to the backbuffer at the proper location

        G.lpDDSBack->BltFast(x + (location++ * letterSpacing), y,
                             G.lpDDSRes,
                             &rectSrc,
                             DDBLTFAST_WAIT | DDBLTFAST_NOCOLORKEY);
    }
}
                      
Once again, we're totally neglecting the fact that DirectX methods return HRESULT values that indicate an error condition, and a future article will fill in such 'gaps'. Using this function shouldn't be difficult to figure out -- just make sure that you don't call EraseBackground() after using it, or else everything will be erased. Also, note that this function only draws a single line of text, and trusts that you don't go over the edge of the screen. A proper font blitting function would also allow for punctuation characters, clip against the edge of the screen, and perhaps even allow for multi-line text (i.e. wraparound), but we're more interested in learning some fundamentals here. While it is true that Win32 GDI functions are painfully slow, we also have to make sure that our custom-made font blitting function is both fast and efficient. Just the Beginning... The importance of this lesson had little to do with writing a font blitter, and much to do with retrieving images from a DirectDraw surface at specific coordinates. It is the key to sprites, animation, and copying areas of memory quickly to the backbuffer. You are going to notice that although our data manipulation is going to get more complicated, almost everything we do from here on in relies on resources in an image and their extraction during run-time. After all, when you think back to the overhead projector analogy, being able to copy and paste pieces of transparency sheets onto a single 'backbuffer' sheet is a major part of preparing a frame of animation. Next, we'll be using real sprite images instead of font characters, and our journey will take us into the realm of sprite animation, physics and AI. Oh boy, can't wait! Questions? Comments? Please post a reply to this topic. Edited by - Teej on May 24, 2001 6:08:09 PM

Sponsor:

#2 Crash   Members   -  Reputation: 122

Like
Likes
Like

Posted 22 May 2001 - 06:58 AM

Well done Teej, another great tut. This is just what I have been looking for.

1 question are u gettin sick of posts like this? I am, so I''ll stop for now.

Thanks again,
Crash,



We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success

#3 White Knight   Members   -  Reputation: 122

Like
Likes
Like

Posted 23 May 2001 - 02:02 AM

Hi Teej

I think you may an error in "Feeling Out RESOURCE.BMP" you say "The small letters are obviously taller than the capitals or numbers". Should it not be "The small letters are obviously shorter than the capitals or numbers". If I am wrong, then why are the small letters taller?

Other than that everything is great. Keep the articles coming.

Edited by - White Knight on May 23, 2001 9:28:51 AM

#4 Weatherman   Members   -  Reputation: 122

Like
Likes
Like

Posted 23 May 2001 - 03:02 AM

Couple of minor corrections:

quote:

rectSrc.left = ALPHA_LARGE_STARTX;
rectSrc.top = ALPHA_LARGE_STARTY;
rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1;
rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT;



should be:

  
rectSrc.left = ALPHA_LARGE_STARTX;
rectSrc.top = ALPHA_LARGE_STARTY;
rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1;
rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT - 1;


and

quote:

for (int i = 0; i < strlen(szText); i++)
{
rectSrc.left = ALPHA_LARGE_STARTX + ((szText - 'A') * ALPHA_LARGE_WIDTH);
rectSrc.top = ALPHA_LARGE_STARTY;
rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH;
rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT;
}



should be:

  
for (int i = 0; i < strlen(szText); i++)
{
rectSrc.left = ALPHA_LARGE_STARTX + ((szText[i] - 'A') * ALPHA_LARGE_WIDTH);
rectSrc.top = ALPHA_LARGE_STARTY;
rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH;
rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT;
}


Both have been corrected in the code extract.



Edited by - Weatherman on May 23, 2001 10:09:45 AM

Edited by - Weatherman on May 23, 2001 10:14:54 AM

#5 Crash   Members   -  Reputation: 122

Like
Likes
Like

Posted 23 May 2001 - 06:01 AM

Hmmm Weatherman, intresting points but you seem to have used the code inside Teej''s tut, in which case you''re right, but if you look at the example code at the end of the tut you''ll see the code is correct.

Just thought I''d point that out to save everybody the time of checking through the example code for the things you''ve pointed out.

Not trying to be a wise-guy,
Crash,



We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success

#6 Mortation   Members   -  Reputation: 122

Like
Likes
Like

Posted 23 May 2001 - 08:55 AM

I have a question, why did you use StretchBlt in your Utils_CopyBitmap()? Just wondering if their was a reason or not, I changed it BitBlt might even change that to BltFast...

V^^^V
** |Mortation| **
@hotmail.com
AOL:M0RTATI0N ICQ:2515421

#7 Teej   GDNet+   -  Reputation: 176

Like
Likes
Like

Posted 24 May 2001 - 11:16 AM

Hmm...

The problem with the tutorial was mainly to do with formatting codes... for instance, using i as an index is the same character sequence as italicize on this BBS. Same goes with the > and < symbols.

For the > and < symbols I know what the escape sequence is (which is why you can see them properly), but not for the square brackets.

In any case, I''ve used j instead of i as a fix...perhaps someone can tell me what the other character sequences are for this thing...

White Knight: Actually if you take a look at RESOURCE.BMP, the small letters are actually ''taller'' than the capitals...this is because some of the small letters have tails (e.g. g j p q) and therefore need the extra room.

Teej


#8 White Knight   Members   -  Reputation: 122

Like
Likes
Like

Posted 24 May 2001 - 06:27 PM

Sorry Teej

I actually forgot about the tall small letters (eg. b,d,f) though.

#9 Corrosive   Members   -  Reputation: 122

Like
Likes
Like

Posted 26 May 2001 - 07:36 AM

Hi guys... I have a question, maybe a little silly, but i don´t know very well this stuff then here it goes:

I´m Trying to implement the code about fonts Teej has posted in the tutorial. I have tried it many ways but it didn´t work.

I did this:

G.lpDDSRes=Utils_LoadBitmap(G.lpDD, "Resource.bmp", 0, 0);
DrawFont(0, 0, "testando");

Didn´t work... Then...

Utils_ReloadBitmap(G.lpDDSRes, 0, 0);
DrawFont(0, 0, "testando");

The same thing... Someone can help me out? What i´m doing wrong? Please.. Thank´s!!!

#10 Dionysis   Members   -  Reputation: 122

Like
Likes
Like

Posted 26 May 2001 - 12:21 PM

Corrosive,
You don''t have to use any of the Utils_* functions before using the DrawFont function.
Just put DrawFont(0,0,"testando"); in Game_Main, somewhere between EraseBackground() and lpDDSPrimary->Flip() and it should work fine, that is assuming your function definition is correct

#11 Corrosive   Members   -  Reputation: 122

Like
Likes
Like

Posted 26 May 2001 - 07:04 PM

Thank´s.. But, just to know, how can i use that functions?

To load a Bitmap for example? Maybe i should wait for the next article.. Teej would like to explain this later perhaps?

#12 Dionysis   Members   -  Reputation: 122

Like
Likes
Like

Posted 26 May 2001 - 08:01 PM

Corrosive,
You use the Utils_LoadBitmap() function in Game_Initialize() to load a bitmap onto an offscreen surface to use for blitting to the backbuffer later, like Teej has done with the "Resource.bmp" at the bottom of Game_Initialize().
Utils_ReloadBitmap() is used in RestoreAll() to load your previous bitmaps back onto their surfaces if the surfaces become lost.
Hope this helps

#13 Corrosive   Members   -  Reputation: 122

Like
Likes
Like

Posted 27 May 2001 - 03:53 PM

Thank´s again man!! I didn´t looked at the initterm file!!

Now it make sense to me!! But i have puted the DrawFont function inside the GameMain() and all i see is a black screen!

I have checked if the surface was G.lpDDSRes etc... All fine!

Do you have a guess about this? The string doesn´t appear in the screen! I will try other ways, any guess please tell me!

#14 Dionysis   Members   -  Reputation: 122

Like
Likes
Like

Posted 28 May 2001 - 12:50 PM

Ok, first make sure that the call to DrawFont() is made right before the Flip() call, you might be drawing something over it earlier in the function like pixels or something.
If that doesn''t work, I don''t know what else you could do besides comparing your DrawFont() function to Teej''s and make sure everything is exactly the same.
Good luck man

#15 MtSMox   Members   -  Reputation: 128

Like
Likes
Like

Posted 29 May 2001 - 10:33 AM

I want to say something about the RECT for the source. You set it up as follows:

rectSrc.left = ALPHA_LARGE_STARTX;
rectSrc.top = ALPHA_LARGE_STARTY;
rectSrc.right = rectSrc.left + ALPHA_LARGE_WIDTH - 1;
rectSrc.bottom = rectSrc.top + ALPHA_LARGE_HEIGHT - 1;

But I think the "- 1" shouldn''t be there...
It says in the SDK (I''m not quoting the SDK, but I remember reading it once) that the RECT that is used by Blt (or any other function using a RECT) excluding the bottom-right coord. So the RECT that is extually used is already "- 1". So you don''t have to specify "- 1" in your code, you should just set up a RECT by specifying the right as "left + width" and the bottom as "top + height".
That''s what I thought, I hope I''m not mistaking.
This "error" is not so grave for your example because the right line (and the bottom line) of pixels is black most of the time, so you don''t see it''s clipped. But I think the "A" and "M" and "j" and possibly some other chars are clipped. I haven''t tried your code, but that''s what I can see from a "distance".

But just to remind you of the good job you''re doing here: KEEP UP THE GOOD WORK

Mox

#16 MtSMox   Members   -  Reputation: 128

Like
Likes
Like

Posted 02 June 2001 - 09:23 AM

Here''s the effidence from the SDK:

RECT structures are defined so that the right and bottom members are exclusive—therefore, right minus left equals the width of the rectangle, not 1 less than the width.

look it up, you know I''m right

Mox

#17 Teej   GDNet+   -  Reputation: 176

Like
Likes
Like

Posted 04 June 2001 - 12:16 PM

Guys, here''s my take on the RECTs.

First, it makes sense that the #defines in GLOBALS.H should reflect the actual dimension of the bitmap. Sure, I could have subtracted there, but if the damned thing is called SPRITE_WIDTH, you want the number to be the width of the sprite -- no trickery.

As for the - 1, why trust the SDK? You can try any combination of +1, -1, or nothing, and the image won''t appear properly -- you''ll either see part of the border, or the bitmap will be slightly clipped. That''s all the proof I ever need.

BTW, Andre Lamothe made that mistake in his book...perhaps you''re right and the docs do say that, but I didn''t fall for it.

Teej


#18 MtSMox   Members   -  Reputation: 128

Like
Likes
Like

Posted 07 June 2001 - 01:20 AM

quote:
Original post by Teej

First, it makes sense that the #defines in GLOBALS.H should reflect the actual dimension of the bitmap. Sure, I could have subtracted there, but if the damned thing is called SPRITE_WIDTH, you want the number to be the width of the sprite -- no trickery.



I agree with you here, there''s no need to change the width in the defines, they should just represent the real width. (without mentioning that you should substract at all )

quote:

As for the - 1, why trust the SDK? You can try any combination of +1, -1, or nothing, and the image won''t appear properly -- you''ll either see part of the border, or the bitmap will be slightly clipped. That''s all the proof I ever need.

BTW, Andre Lamothe made that mistake in his book...perhaps you''re right and the docs do say that, but I didn''t fall for it.

Teej


This is where I disagree: when I test a image transfer with the different settings (-1 and nothing, I didn''t test +1 because that makes no sense at all) I didn''t get the results I wanted with -1 but it worked correctly with nothing. So that''s why I DO trust the SDK. I''m not just doing it because it is in the SDK, but because I also tested it.
I tested it with an image with a color as a 1 pixel wide border and another color inside it. When I used -1 I didn''t see the right and bottom side of the border, so that''s not the correct way in my opinion.
Correct me if I''m wrong, but this is just what I have used all the time and I have found it to be the correct way...

Mox


#19 mirrorman   Members   -  Reputation: 122

Like
Likes
Like

Posted 20 June 2001 - 06:48 AM

Corrosive,

Did you ever fix your problem with DrawFont? If you didn't, I had the same problem and maybe my solution will help you:

I cut and pasted the DrawFont function into my gamemain.cpp file,
obviously making sure to put the function declaration at the top.

I then deleted everything from my GameMain() function, and put only the following:

void Game_Main()
{
// Black the screen
EraseBackground();

// Draw the string to the screen
DrawFont(300,200,"Hello");

// Flip the surfaces
G.lpDDSPrimary->Flip(NULL, 0);

} //end game_main

I think my problem was that I left all the code from the pixel manipulation tutorial in there, ie the ZeroMemory, Lock and Unlock calls. I hope this helps, and if you've already solved your problem, ignore this reply



Edited by - mirrorman on June 20, 2001 1:54:32 PM

#20 Firemage   Members   -  Reputation: 122

Like
Likes
Like

Posted 13 July 2001 - 11:06 PM

Okay.. I got this RECT thing figured out... I know what is going on. I hope this helps some lucky person down the line.
There are a few problems.. they just happen to cancel eachother out for the most part.

I am only looking at the Height and Y starting points.

As for the -1/0/+1.. the answer is normally actaully 0 for the way this is set up... but then I go and look at "resource.bmp" and I see that the capital letters are only 12 pixels tall.. the pixels numbered 0-11.

I am in favor of calling things by their real height and then using the -1. It makes sense to me. But RECT DOES leave off the bottom coordinates... so we define the height as 13.. subtract one and we get a RECT to pixel 12 on the y-axis. Take a closer look at the bmp and you see that pixel 12 has a few dots for the ''j'' and ''i''. But the RECT excludes the lower pixel and it works fine.

on too the lower case.. we have it starting at pixel 13.. so basically we lose the top pixel on the dots for the ''i'' and ''j''.

As for the numbers.. it''s starts in the right place.. and then goes down to the last line and excludes it.. so it doesn''t matter cause it is blank....

Solution:
Change the height on the capitals to 12 and don''t subtract.. our RECT will do that on it''s own.
Change the y-start on the loewecases to 12 and don''t subtract.
Keep the number Defines but take out the subract.

I know that all I have done is given an extra pixel to ''i'' and ''j'' and added blank lines.. but like Teej said.. the idea isn''t to make the thing.. the idea is too learn the concepts..

Hopefully this has helped.. and if I am wrong, please shoot me and put me out of your misery.

Firemage


To Thine own self be true

No day but Today





PARTNERS