Monday, September 7, 2009

Simple Animated Image with C++/SDL for the Non-Programmer

My friends and family often ask me what exactly I do at my job, so although they were probably only asking in a rhetorical sense, I thought I'd try to answer the question in a little example that also could serve as a basic programming instructional. This is some code taken from my open source music player daemon frontend, ommpc. While it isn't actually from "work", all the basics apply. So, for the non-technical, skip over anything that doesn't make sense and take a look at some of the things a "computer programmer" does and for the technical folks, treat this as a code review and let me know what I can improve. Now for some code.

The basics
This selection of code is actually a little bit more exciting than most of the stuff I do at work. Alot of my work consists of processing files and whatnot and so the only output is text to log files, and generally any user interface work is pretty high level, consisting of creating some dialog in a WYSIWYG editor and then just populating the contents. But with this gp2x I have to actually do all of the drawing of various elements on the screen manually. So without further ado, let's go...
bool ArtButton::draw(SDL_Surface* screen, SDL_Surface* bg, bool forceRefresh)
{
if(!m_artParms.doArtLoad && (m_refresh||forceRefresh)) {
//clear this portion of the screen
SDL_SetClipRect(screen, &m_clearRect);
SDL_BlitSurface(bg, &m_clearRect, screen, &m_clearRect );
if(m_showInfo || m_animate) {
//show info
}
if(!m_showInfo || m_animate) {
if(m_animate) {
if(!m_showInfo) {
if(m_moveRect.y <= 0)
m_animate = false;
else
m_moveRect.y -= 10;
} else {
if(m_moveRect.y >= 160)
m_animate = false;
else
m_moveRect.y += 10;
}
}

SDL_BlitSurface(m_artParms.artSurface, &m_moveRect, screen, &m_destRect );
}
m_artParms.doArtLoad = false;
m_refresh = false;
}
return m_refresh;
}
That is pretty much the entirity of the routine that draw the album art image on my Now Playing screen. You can click the image and it will change to show the id3 information for the song. This is my first little attempt at creating an animated transition when you click the image. So let's examine the pieces of this code.
bool ArtButton::draw(SDL_Surface* screen, SDL_Surface* bg, bool forceRefresh)
This line of code shows our function, what needs to be passed to it and what it will return. The "surfaces" are object we interact with to display things on the screen. The first parameter, "screen" is our main, overall area we draw to. For example, if your web browser was my program, "screen" would be everything contained within the borders of the browser window. In my case the program runs full screen all the time, so it's simply the entire viewable screen.

The parameter "bg" is a background image we loaded earlier, that we will need to basically cut a section from and display it. The last parameter tells us that this area of the screen needs to be forced to refresh because some other event in the program might have displayed over it or caused it to need to be changed. So, how do we now use those parameters in our drawing routine?
if(!m_artParms.doArtLoad && (m_refresh||forceRefresh)) {
//clear this portion of the screen
SDL_SetClipRect(screen, &m_clearRect);
SDL_BlitSurface(bg, &m_clearRect, screen, &m_clearRect );

Okay, now we have what is called a conditional statement with a few special characters. '!' = 'not', '&&' = 'and' , and '||' = 'or'. So we only will process the following code in the brackets 'if' doArtLoad is 'not' taking place 'and' we need to refresh 'or' force a refresh. The refresh variables cause us to only redraw the screen when we need to and the doArtLoad variable tells us not to try to draw anything while we are still loading an album art image. So once we get into this drawing code we use our SDL functions to actually start manipulating the screen.

SDL_SetClipRect defines the area of the screen we are going to draw to. The clearRect variable was loaded earlier from a configuration file with the size and position of our album art.

SDL_BlitSurface outputs to the screen. It takes our background image 'bg' and grabs the portion of it at the position outlined in clearRect and outputs it to the 'screen', also at the position from clearRect. Bascially this draws a portion of our background image for the area of the screen affected by our album art. We only draw the portions of the screen we need to so as not to waste time updating parts of the screen that are static. Moving on...
if(m_showInfo || m_animate) {
//show info
}
if(!m_showInfo || m_animate) {
We now have some more conditionals. We have two boolean(true or false) variables one which tells us if we are displaying the album art or the id3info and another to tell us if we need to be animating the transition. The first statement says to enter this block of code if either of those variables are true as we need to show the information both while drawing the animation and once it's over and we're just showing the info. I've left out the actual code because it's just a bunch of other draw function calls.

Next we have a block of code we enter if we are not showing the info(thus showing the art) or during animation.
if(m_animate) {
if(!m_showInfo) {
if(m_moveRect.y <= 0)
m_animate = false;
else
m_moveRect.y -= 10;
else {
if(m_moveRect.y >= 160)
m_animate = false;
else
m_moveRect.y += 10;
}
}

SDL_BlitSurface(m_artParms.artSurface, &m_moveRect, screen, &m_destRect );
We now have to check to see if we are indeed drawing the animation, then if we are we check if we're supposed to be drawing the info or the art image. The 'destRect' is loaded earlier with the same position/size as our clearRect as it dictacts where we are going to draw on the 'screen'. However, 'moveRect' is based on the size of our album art(currently 160 pixels) and its position is relative to that art image. So width and height are 160px and the x,y position are 0,0 indicating the top, left corner of our image.

Let's first examine the case where we are not drawing the animation. In that case, we will skip the entirity of the above code up until the SDL_BlitSurface call. And that call will take 'artSurface' which is just an album art image file loaded earlier and draw the entire image as given by 'moveRect' to 'screen' and position 'destRect'. Easy-peasy-lemon-squeezy.

Now, what happens if we are animating? As you see, depending on whether or not we are showing the art or the info, we are either increasing or decreasing the 'y' coordinate of 'moveRect' by 10 pixels. What this does is tells the blit function to only draw a portion of the image instead of the entire image. So instead of drawing starting at 0,0 it draws starting at 0,10...then 0,20. So each time it enters the routine it draw 10 pixels less of the top of the image(or 10 more if going the other way), thus creating the effect of the art image scrolling up and scrolling back down.

That's pretty much it. Some simple animation in a simple drawing routine. Each section of the screen, the buttons, the seek bar, the song title, etc get's it's own drawing function and we only redraw that particular section when we need to. I hope that was informative enough for the non-programmer and I would love to get feedback from coders out there on my approach to the problem. Thanks for reading.


No comments:

Post a Comment