Intermediate C++ Game Programming Tutorial 10

From Chilipedia
Jump to: navigation, search

The promised day has come. We're finally going to be loading images from bitmap files into memory and drawing them as sprites on the screen. This is where all that stuff we've been learning--data types, pointers, memory management, file access, etc.--is going to pay off bigtime. Also, get fukt massive PutPixel sprite functions; die in a garbage fire.

Topics Covered

  • Surface class for storing image data in memory
  • Parsing Windows bitmap file headers and loading pixel data
  • Calculating padding
  • Drawing sprites (pixel blocks) from a Surface to the screen

Notes

Channel Byte Order

There is a rather large issue with the order in which the color channels are loaded from the bitmap file, but due to a freak of coincidence, the code works regardless. Still, we must address this buttfuckery.

The way the channels are laid out in the file is (in order of low offset to high offset) |B| |G| |R|. Now the line of code that simultaneously reads the 3 bytes of the 3 color channels, constructs a Color object, and writes it into the Surface is: PutPixel( x,y,Color( file.get(),file.get(),file.get() ) );. The function prototype of the Color constructor is Color( unsigned char r,unsigned char g,unsigned char b ), so the order here is RGB, and we would expect that our code would be messed up (the R and B channels would be swapped). But this is not the case. What gives?

Well you might (understandably) assume that our function call to the ctor evaluates the get() parameter expressions in the following order Color( file.get()first(B),file.get()second(G),file.get()third(R) ), that is to say, the list of parameter expressions being evaluated in left-to-right order. In actuality, the compiler evaluates them in right-to-left order, i.e. Color( file.get()third(R),file.get()second(G),file.get()first(B) ), and this is why our code happens to work!

Even stranger is the fact that the C++ standard does not specify the order in which the parameter expressions are evaluated! So it is possible that a different compiler could generate code that gives a different permutation of the color channels. In actuality, we can expect most compilers to do it right-to-left because of the way parameters will be pushed onto the stack (which is mandated), but still, it is terrible practice to rely on this unspecified behavior to always be the way we wish it to be.

The following code fixes this problem. It will be applied (in later downstream commits) to the Sprite repo and the Twin repo on GitHub.

const unsigned char b = file.get();
const unsigned char g = file.get();
const unsigned char r = file.get();
PutPixel( x,y,{ r,g,b } );

Self Assignment

We definitely should be checking for self assignment (mySurf = mySurf;) in the assignment operator for Surface. See the Errata of Intermediate 6 for more details.

Video Timestamp Index

Tutorial 10

  • Creating a datatype (Surface class) to hold image data 0:13
  • Add member function DrawSprite to Graphics class to draw a Surface object to the screen 5:03
  • Test the code with a funky sprite design 7:43
  • Load sprites in a Surface object from a Bitmap file 9:09
  • Exploring the Bitmap Filetype 9:18
  • Load the Bitmap file header BITMAPFILEHEADER 10:54
  • Load the DIB Header BITMAPINFOHEADER 15:08
  • Load 8-bit RBG values of the Bitmap File into pixel array of a Surface object 17:00
  • Calculate padding at the end of a bitmap row 18:40
  • Do a test run, loading a Bitmap file and drawing it to the screen 22:32
  • Test the order of the color channels 24:46
  • Using assert, add a test to see if file loading succeeded 25:21
  • Homework assignment 27:21

Source Code

Sprite Repo

Download Materials

Homework

The homework is to improve the bitmap loading routine to support 32-bit RGB format and to support bitmaps with reverse row order.

See also