04.05 - Image Transfer

Started by
29 comments, last by Teej 20 years, 7 months ago
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
Advertisement
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
"We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success." - 2000"If You Keep Looking Forward Your Gonna End Up Looking Backwards At Yourself Running Sideways!" - 2001
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
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
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
"We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success." - 2000"If You Keep Looking Forward Your Gonna End Up Looking Backwards At Yourself Running Sideways!" - 2001
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
MortÔ¿Ô¬
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
Sorry Teej

I actually forgot about the tall small letters (eg. b,d,f) though.
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!!!
To code is to make things come to life ;)
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

This topic is closed to new replies.

Advertisement