So now we get to a significantly meaty part of the engine, where we get into handling images. There is a lot of code to go over, so I’m breaking this into two parts, the basics and the advanced.
To start off, we need to be able to load images. The base library of SDL allows you to load bitmap files(.bmp), with the SDL_LoadBMP() function, but bitmaps are fairly limited and aren’t compressed. If we want other formats we must use the SDL_Image library. It’s a fairly simple library, the only things we need to concern ourselves with for the forseeable future are IMG_Init(), IMG_Quit(), and IMG_Load(). It also provides a way to read images from memory, which can be used when you are say, storing all your files in a zip, but it’s far more complicated, requires additional libraries for the compression conversion, and there’s no real reason to not use normal assets. It falls into ‘potentially useful to be aware of, not important right now’ territory.
So now that we’ve got this image loaded into memory, how do we actually get it up on the window. We’re going to cover a bunch of new functions today. Loading an image with SDL_image looks like: SDL_Surface *image = IMG_Load(“image.png”); We can then use that surface to do all sorts of drawing operations.
The arguments for this function are the surface you are drawing, the surface you are drawing to (usually sdl.screen), the x coordinate on the surface you are drawing to and the y coordinate you are drawing to.
Drawing an entire loaded image onto the screen is a fairly simple affair, you just create a SDL_Rect that has the x and y coordinates you were passed from the arguments (the 0,0 denotes width and height, which doesn’t matter for this case). The meat of the function is the SDL_BlitSurface() function.
The argument pass order for BlitSurface() is the surface you are blitting, the clipping rect for the surface you are blitting (which we pass NULL since we want to draw everything), the surface you are blitting to, and the positioning data for the surface you are blitting.
DrawPartial allows you to set a clipping rectangle so that you can draw part of an image. This makes sprites and bitmap based text possible (you could probably do them with separate images for each state, but that would be insanity).
This function is more or less similar to our draw function, but with additional arguments (width, height, x and y) for our clipping rectangle. We also use the clipping rectangle in the SDL_BlitSurface function instead of NULL.
This function gets the data of a specific pixel in a surface. It calculates the offset of the pixel in memory and then grabs it and returns it based on the bit size of the image. The offset is because we’re treating image data as a 2d array (with an x and y coordinate for each pixel) when in reality memory is addressed in a linear fashion. This means we have to convert our 2D positional data into a functional memory location to access the pixel.
Somewhat annoyingly 24-bit pixel data can be in either big endian or little endian notation (Basically the binary can be arranged two different ways for those of you without a CS background). This means we need a special cases and fancy return statements involving binary math and bitwise shifting (binary math is really fast computationally, but not really intuitive if you haven’t used it much. I have to sit down and think it through to figure out what this code does). Now we’ll go over why you would never actually want to use a 24-bit image in !!SCIENCE!! #3, but you always want things to work in case some weird edge case comes up or you absentmindedly do something stupid (Better to have a semi-descriptive error message than just non-functionality in that event).
SetPixel is more or less the same as GetPixel, here we’re doing binary ands against max value instead of binary ors for 24-bit images.
You may note that I haven’t included any specific loading functionality yet. This is because in the past I tended to use gamephase specific asset handlers, so I implemented all the various loading and cleanup functionality in the generic asset handler. I still feel this is the best direction to go, as each game is going to have its own little quirks, and trying to force them to mold to a specific loading methodology moves away from the ‘simple’, let alone someone with their own coding style.
Other important SDL functionality that we should be aware of at this point is SDL_SetColorKey() and SDL_SetAlpha(). SetColorKey allows us to set a specific RGB value that the surface will treat as transparent. SetAlpha sets the opacity of the entire surface. The thing about these is there is a significant amount of interaction between them, and drawing may produce significantly different results when these are set. We’ll dive into the various ways this comes up in the significantly lengthy !!Science!! #3.