Tree branch
Another tree branch
OpenGL Tutorials
Spider web
Facebook Twitter Google Plus
DirectDraw Sprite Transparency and Sprite Stretching

This tutorial had been originally planned to be about just transparent sprites. But since I thought that that would end up being a really quick tutorial I decided to describe sprite stretching as well. Sprite stretching is also made easy by DirectDraw(like most things).

I will start with transparent sprites. I just want to note for those who came here for an alpha-blending tutorial(and are confused): Transparency and alpha-blending are two different things.

Transparency is when only parts of a sprite are 100% "hollow" and you can see through those parts.

Alpha-blending on the other hand is when the whole sprite has a certain amount of visibility over the background or other sprite and is "blended" with the pixels of that surface or sprite. Alpha-blending is also referred to as Translucency(not Transparency).

Anyway, let's create some transparent sprites. Surprise. Actually we already have all the base functions to do it and the whole process is basically changing some flags here and there in the code we've mastered in the previous tutorial. And as you recall, to create a surface we simply call this function and assign it to something(such as "mysurface" in this example):

LPDIRECTDRAWSURFACE mysurface = CreateSurface(100, 100, true);

I've talked about the function CreateSurface before in the previous tutorial where we created just plain surfaces without transparency. I don't think I listed the exact code of that function there but you can still look it up in the source code of this and previous tutorials. And since I'm so kind you won't have to do that because I will introduce you to it once again just below this line.

LPDIRECTDRAWSURFACE7 CreateSurface (int width, int height, bool transparent) { DDCOLORKEY ck; LPDIRECTDRAWSURFACE7 surface = NULL; // NOTE: parameter transparent is not used in this tutorial. // Clear the ddsd memory EraseDesc(); if (transparent) ddsd.dwFlags = DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT|DDSD_CKSRCBLT; else ddsd.dwFlags = DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT; // Fill in the surface info ddsd.dwFlags = DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT; ddsd.dwWidth = width; ddsd.dwHeight = height; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN|DDSCAPS_VIDEOMEMORY; // Create the surface long rval = lpdd7->CreateSurface(&ddsd, &surface, NULL); if (transparent) { ck.dwColorSpaceLowValue = TC16BIT; ck.dwColorSpaceHighValue = TC16BIT; surface->SetColorKey(DDCKEY_SRCBLT, &ck); } if (rval != DD_OK) MessageBox(hwnd, "Failed to create an offscreen surface", "Surface Error", MB_OK); return surface; }

Everything looks familiar(if you read the 6th tutorial) except the DDCOLORKEY structure. This structure holds all the information about your transparent color; which is basically dwColorSpaceLowValue and dwColorSpaceLowValue doublewords(so much for information).

As I've mentioned at least once before, you can create a normal and transparent sprite using this same function. To create a transparent sprite simply pass true to the last parameter of the function.

Follow the code and you will see that during creation of a transparent surfaces at least 3 more steps are taken. These are as follows:

1. OR'ing DDSD_CKSRCBLT to the surface description struct's flags; indicating that this is a transparent surface and the two doubleworded values mentioned above are valid.

2. Setting those two values to a color (TC16BIT is defined somewhere in your code as the transparent color - in this case defined by a 16-bit color macro described before - which won't be drawn during the rendering operations).

Why do we set two values to that color you may ask? You see, you're actually passing a RANGE of colors between those two. However setting both values to the same color makes the surfaces have only one valid transparent color. Don't worry you will never need more than one. And I think ranges of transparent colors are not that really useful. And finally...

3. Calling the SetColorKey member function and passing it the color structure filled in as described above. You also pass the DDCKEY_SRCBLT flag - that's a must, just like in step #1 you OR the DDSD_CKSRCBLT flag, this lets DirectDraw know that you're using a transparent color in this surface. This whole technology is called Color Keying.

These steps are crucial to create a transparent surface and indeed they will. Is drawing a transparent surface any different from drawing a normal surface? The answer is yes but it's also a matter of a single flag being OR'ed to the Blt member function of the surface(that flag is DDBLT_KEYSRC).

Note that in the source code I have two version of the sprite drawing functions - BltSprite and BltSpriteT. You guessed it, the latter blits a transparent sprite. It is up to you as a programmer to identify what sprites are transparent and what sprites are not. But it is not necessary for not-so-graphically-intense DirectDraw applications.

You can always use the BltSpriteT function for all sprites. If the sprite doesn't have a transparent color it will still be blitted as a normal sprite(however at a certain speed loss because transparent blits are usually slower) Anyway, to draw a transparent sprite you'd use something like this:

void BltSpriteT (SPRITE *sprite, int num, int x, int y) { RECT dstrect = {x, y, x + sprite->width, y + sprite->height}; lpBackBuffer->Blt(&dstrect, sprite->surface[num], NULL, DDBLT_WAIT|DDBLT_KEYSRC, NULL); }

Note I'm not passing a surface to it. To make things easier I created a sprite struct. I've also included a function called CreateSprite which wraps the CreateSurface function and acts just like CreateSurface but for sprites only. This functionality is included in the sprite.h header and appropriate .cpp files. I promised to describe sprite stretching.

I didn't mention the sprite struct out of the blue. With my sprite struct it is possible to change the original width and height of the previously created surface by simply assigning new width and height values to the sprite object holding that surface. When either of the BltSprite functions is called, it "blits" a sprite's surface with width and height specified by the object, and not by the original values you passed to create the surface.

The outcome is that DirectDraw already has stretching done for you inside the blitting function, just pass it new width or height. And that's exactly what BltSprite and BltSpriteT do. Actually that's(as many things) done in hardware, and if your card doesn't support stretching it will simulate it in software(to my knowledge). HOWEVER, fear not. I've yet to see a card that DIDN'T support sprite stretching, and I've worked with a ton of them.

To demonstrate what's been described in this tutorial I loaded two identical sprites of a pink monster from DOOM(drawn by myself) into their respective sprite objects. I stretched each sprite(simply by assigning new values to the sprite objects) differently. One monster is taller than it should be and another one is wider than it should be(use the original BMP as reference).

To make things more exciting I decided to spin each monster at different speeds around the center of the screen in clock-wise direction. I'm not locking the frame rate to anything so they can spin pretty fast on your computer if you have a resonably fast system. In best case your DirectX hardware will vsync the frame rate and it'll look smooth.

The source code has a goodie for you: a simple math library(secluded in math.h and math.cpp) responsible for spinning the monsters. It includes some sin and cos declarations and creates a sin-cos look-up table. This can be useful for many things especially in video games. And I've just wanted to demonstrate one of them which is rotating. Simply for fun.

© 2014 OpenGL Tutorials.

About | Privacy | Contact

Built with Tornado PHP FrameworkTornado PHP Framework with Wooden PHP Template template | Web design by Web Design by Greg.