Prerequisites:
Some knowledge of SDL (see Installing
SDL)
The SDL Primitive Generator, SPriG,
is a quick and simple interface to making graphics on SDL
surfaces. In this guide, I'll be discussing the notable
features
of Sprig with usage examples. Not all of the functions are
described here. There are plenty more to be found in the
documentation once you get the hang of the stuff found here.
I
will, however, be describing the entire dirty rect interface.
Sprig does three main things: It draws graphical
primitives, it rotates and scales surfaces, and it simplifies access to
SDL surfaces. Once you're off and using Sprig, I suggest that
you
download the documentation so you have easy reference material at hand.
Along
the way, I'll be leaving usage examples. In these, I'll be
using
SDL_ColorDef to define colors, so if you see something like this:
RGB_BLUE(mySurface)
This is just a quick replacement for the usual format-independent call
in SDL:
SDL_MapRGB(mySurface->format, 0, 0, 255)
Isn't that convenient? So, instead of describing a color with red, green, and blue components, I'll just use common names.
History
and Motivation
Sprig
was originally an overhaul of SGE. I called it minSGE back
then
because I was not satisfied with how many peripheral features are in
SGE. I cut away collision detection, fonts, C++ sprite
classes,
and text classes. These features are handled far better by
other
libraries and would be quite the hassle to maintain and upgrade
properly. I assumed that most people used SGE for primitives
and
transforms and used other libraries for all the other stuff.
A
major annoyance to me were the naming conventions, so I worked on that,
too. Well, that set the stage for designing Sprig.
In fact,
I thought that fonts were important enough that I also started NFont as
a successor to SFont for drawing bitmap fonts. Sprig and
NFont
together are all that a simple game needs to get started.
These
decisions also made it easy for Sprig to become a pure C library, so
that users of either C or C++ can enjoy it.
I hope you'll find
that Sprig has a very comfortable feel in its naming scheme and range
of convenient functions. My main goal besides providing
primitives and transforms is to supplement the built-in SDL functions
to make development faster with less typing.
Installation
Installation
for Sprig is just like any other C library. You can check out the
SDL installation guide for generic details (Installing
SDL). For Sprig, you can get the pre-built binaries, use 'make', or build it by hand.
If
you have the binaries for your system, unzip them. Copy 'sprig.h'
and 'sprig_inline.h' to your compiler's /include or /include/SDL
folder. Then copy the appropriate library file (libsprig.a,
libsprig.so, sprig.lib) to your compiler's /lib folder. Now you
can write Sprig programs by using:
#include "sprig.h"
and linking with sprig (-lsprig or sprig.lib).
When you distribute these programs, be sure to include the dynamic library appropriate to your system (sprig.dll, libsprig.so).
Using make (Linux): Download the Sprig source, open a terminal window, and move into the unzipped Sprig source directory. Type:
make install
That should be it!
Building by hand: Download
the Sprig source and unzip it. Make a new dynamic library project
in your favorite compiler that contains all of the source files (.c).
Add SDLmain and SDL to your linker options. Compile it and
install it as above.
If you're willing to comply with the LGPL
license on static linking, you can build a static link library or just
add all of the Sprig source files to your project. You will then
not need to distribute the library with your executable.
If you have any problems or are not on Windows XP or Debian GNU/Linux, then please send me an email!
Lurking
Beneath the Surface
Sprig's
surface functions make creation and control of SDL surfaces quite
easy. Sprig has functions to set up SDL, create surfaces, and
copy surfaces.
SPG_Init
is a wrapper for calls to SDL_Init and SDL_SetVideoMode. It
returns the display surface or NULL if there's an error. Just
like with SDL_SetVideoMode, passing 0 for the surface depth will give
you the best available depth.
SPG_CopySurface returns a duplicate of the given surface.
SDL_Surface* copy = SPG_CopySurface(mySurf);
SPG_Free
is a wrapper for SDL_FreeSurface. It's just for less
typing, nothing special. When you're done using a surface,
you must free the
memory using SPG_Free (or SDL_FreeSurface).
SPG_Free(mySurf);
SPG_SetColorkey
sets the transparent color for a given surface. Colorkey
transparency can only be used for RGB->RGB blits,
RGB->RGBA
blits, or RGBA->RGBA blits with SDL_SRCALPHA disabled.
SPG_SetColorkey(mySurf, RGB_BRIGHTPINK(mySurf));
SDL_BlitSurface(mySurf, NULL, screen, NULL); // Only non-pink
areas will be drawn.
SPG_SetClip sets the clipping rectangle for the given
surface. When stuff is drawn to this surface, only the parts
inside the clipping rect will actually be drawn. You
can reset the clipping rect by using SPG_RestoreClip.
This is really useful for splitscreen effects.
Sprig
is first and foremost a generator for graphics primitives (of
course). These are pixel shapes like lines, circles, and
rectangles. Sprig has functions to create:
Each
of these primitives can be drawn with anti-aliasing, different
alpha-blending modes, and a specific thickness. Rects,
circles,
ellipses, arcs, trigons, and polygons can all be drawn with filled
interiors. Check out the Controls section for more info on
changing how primitives are drawn.
The naming convention that Sprig follows is like so:
SPG_(Shape)(Filled)(Blend)
For example:
SPG_RectFilled
SPG_PixelBlend
SPG_EllipseFilledBlend
The
different words in each function name follow an intuitive hierarchical
naming scheme, based on the magnitude of the change they make to the
shape. This is especially useful when combined with many
IDEs'
autocompletion to save you from lots of typing.
Here are some sample drawing commands. The signature of each
function can be found in the documentation.
Polygons
are great for many effects and can represent platforms or form
characters in various video games. With Sprig's polygon
rotations, you can easily make something like the opening screen for Zelda
III... Some functions use the SPG_Point coordinate structure
to
simplify their calls. SPG_Point contains two floating point
values, x and y. Use SPG_MakePoint to define your own points
for
these functions. You can also fill the struct when it's
created:
SPG_Polygon(SDL_Surface* surface, Uint16 n,
SPG_Point* points, Uint32 color)
void
SPG_CopyPoints(Uint16 n, SPG_Point* points,
SPG_Point* buffer)
void
SPG_RotatePoints(Uint16 n, SPG_Point* points,
float angle)
void
SPG_ScalePoints(Uint16 n, SPG_Point* points,
float xscale, float yscale)
void
SPG_SkewPoints(Uint16 n, SPG_Point* points, float
xskew, float yskew)
void
SPG_TranslatePoints(Uint16 n, SPG_Point* points,
float dx, float dy)
SPG_Trigon
draws a triangle from three points. Just like other primitive
shapes, it can be a line drawing or variable thickness, filled,
alpha-blended, or anti-aliased.
SPG_QuadTexPoints
draws a four-sided polygon that is filled with an image. A
set of
source points allows you to choose exactly what part of the source
image is used. This can be used for stretching, rotating, and
distorting images. There is another function, SPG_QuadTex,
that
uses (x,y) coordinates instead of SPG_Points.
SPG_CopyPoints
makes a static copy of your points into the given buffer. The result
can
be messed with, drawn using SPG_Polygon, then deleted without ruining
your original shape.
It uses a pre-existing buffer, so it's nice and fast. You can also use this to copy part of a polygon
into another polygon with some pointer arithmetic.
SPG_CopyPoints(3, myTrigon, (myHexagon + 3)); //
Copy points into myHexagon[3], [4], and [5]
SPG_RotatePoints
rotates each SPG_Point about the origin by the specified
angle.
To rotate about a different point, you can use SPG_RotatePointsXY (or
translate it back and forth).
SPG_RotatePoints(5, myPentagon, 45);
SPG_ScalePoints
can make a polygon bigger or smaller. It scales about the
origin
(i.e. assumes the center of the polygon is at (0,0)), but you can use
SPG_ScalePointsXY (or translate it back and forth) to change that.
SPG_ScalePoints(5, myPentagon, 2.0f,
2.0f); // Double the size
SPG_SkewPoints applies a shear transform to the polygon (about the
origin, as above - Try out SPG_SkewPointsXY too).
SPG_SkewPoints(4, leaningTower, 2.0f, 1.0f);
SPG_TranslatePoints moves all the points in a polygon.
SPG_TranslatePoints(4, myBox, 200, 400);
Those
Controls are Stacked!
Several
options are controlled through a stack system. When you
change an
option, you push a new value onto the stack. When you're
done,
you pop the stack, returning it to the old state. In this
way,
you can write a block of code or a function that changes the state to
achieve a particular effect, then returns it to the old state for the
rest of the program to use.
There are three stacks held by Sprig. The thickness stack
controls the thickness of any line primitives that are drawn (default:
1). The blending stack determines the special blending mode
for alpha-blended primitives and SPG_Blit (default: SPG_DEST_ALPHA).
The anti-alias stack controls whether graphics primitives are
anti-aliased (smoothed) or not (default: 0). The surface
alpha stack controls whether or not SPG_Blit combines the per-surface
alpha with the per-pixel alpha (default: 0).
void
SPG_PushThickness(Uint16 state)
Uint16
SPG_PopThickness()
void
SPG_PushBlend(Uint8 state)
Uint8
SPG_PopBlend()
void
SPG_PushAA(SPG_bool state)
SPG_bool
SPG_PopAA()
void
SPG_PushSurfaceAlpha(SPG_bool state)
SPG_bool
SPG_PopSurfaceAlpha()
Here's a table of the available blending modes:
Name
RGB
Alpha
Description
SPG_DEST_ALPHA
Blend
Dest
Blends colors
normally and keeps the destination per-pixel alpha. This is
the same as SDL_BlitSurface's blending.
SPG_SRC_ALPHA
Blend
Src
Blends colors and
copies the source alpha.
SPG_COMBINE_ALPHA
Blend
Blend
Blends colors and
blends the alpha. This is "True" blending and lends well to
some neat compositing effects.
SPG_COPY_NO_ALPHA
Src
Opaque
Copies the source
color, sets the dest alpha to opaque.
SPG_COPY_DEST_ALPHA
Src
Dest
Copies the source
color, keeps the dest alpha.
SPG_COPY_SRC_ALPHA
Src
Src
Copies the source
color and alpha.
SPG_COPY_COMBINE_ALPHA
Src
Blend
Copies the source
color and blends the alpha.
SPG_COPY_ALPHA_ONLY
Dest
Src
Keeps the dest
color, but copies the source alpha.
SPG_COMBINE_ALPHA_ONLY
Dest
Blend
Keeps the dest
color, but blends the alpha.
SPG_REPLACE_COLORKEY
Src
Src
If
the destination surface has the SDL_SRCCOLORKEY flag, this replaces the
dest colorkey color in the image with the source color and alpha.
This is similar to palette-swapping, but can be used with
gradients and other images.
Sprig
can work with both degree angle measure and radians. By
default,
all functions use the more common (in everyday life) units of
degrees.
You can change this behavior with a call like:
SPG_EnableRadians(1);
This
avoids one floating-point multiplication in some of Sprig's internal
conversions (Sprig uses radians internally). To
make radian measure easier to use, there are several #defines
involving pi:
A
number after 'PI' indicates multiplication, and an underscore means
division. So PI3_2 is "Three halves times pi" or
1.5*pi. An
amount equal to pi radians is equivalent to 180 degrees.
Every quarter value of pi (45 degree increments) up to 2*pi (360
degrees) is provided. Sprig
also provides conversion factors for converting between degrees and
radians:
float radians = degrees * RADPERDEG;
or
float degrees = radians * DEGPERRAD;
These
#defines are named after the unit conversion, which is a
fraction. The measure that you're converting to will be in
the
numerator (top), with the old unit in the denominator (bottom).
More
Than Meets the Eye
Sprig transformation routines can scale, rotate, and flip SDL surfaces.
Anti-aliasing
is handled a little differently for the transform functions than for
primitives. This is because they operate on a different
level:
surfaces rather than shapes. You don't usually want your
choices
for one to affect your results for the other. For this
reason,
SPG_Transform and SPG_TransformX use bit flags to control their
behavior:
SPG_NONE - Normal, same as 0.
SPG_TAA - Anti-aliasing (no more jaggies)
SPG_TSAFE - Makes incompatible surface depths work right
SPG_TTMAP - Uses texture mapping (SPG_QuadTex) for a faster, but uglier
result
SPG_TSLOW - Uses a slow transformation, but is very accurate
SPG_TCOLORKEY - Ignores the colorkey value on the destination surface SPG_TBLEND - Alpha-blend on the destination surface SPG_TSURFACE_ALPHA - Blend with the per-surface alpha
SPG_Rotate and SPG_Scale are convenience functions that call
SPG_TransformX. So, to simply rotate an image:
This function takes a surface, transforms it, and draws
it onto another surface. The first two arguments are the
source
surface and the destination surface. The third argument is a
rotation angle. The next two are scaling factors.
The sixth
and seventh are pivot point coordinates. These determine the
point about which the surface is to be rotated. The eighth
and
ninth arguments are the drawing destination coordinates. The
last
is a bitwise OR '|' combination of transformation flags.
Draw
Me Close...
Sprig
has plenty of handy drawing functions. These give you quick
control over SDL surfaces. The ones you should take note of
are:
In many functions, like SDL_BlitSurface, you use an SDL_Rect to define the source
pixels or to indicate the drawing location.
SPG_MakeRect
can be used to make rects for such use:
SDL_Rect rect = SPG_MakeRect(40, 30, 60,
60); // A 60x60 rect at the location (40,30)
SPG_Blit
draws an image using Sprig's custom blitter. It's
pretty
slow, but allows you to use several alpha-blending modes.
SPG_ReplaceColor
lets you blit an image only to specific colored areas on the
destination surface. This is an effect similar to the
palette-swapping that tons of old 8-bit games used to make identical
enemies of different colors.
SPG_Draw is a quick wrapper call to SDL_BlitSurface.
SPG_Draw(mySurf, screen, x, y);
SPG_DrawCenter
is just like SPG_Draw except that it uses the width and height of the
source surface in order to draw it centered on the given coordinates.
SPG_FloodFill fills in the target area (all pixels of the same color)
with the given color.
SPG_FloodFill(mySurf, x, y, color);
Those
Low-down Dirty Rects!
If
a game is changing the entire screen every frame, it makes sense to
update the entire screen. But if you're only moving a few
sprites
on a static background, then a huge time savings can be made by
updating only the "dirty" parts. Sprig includes a dirty rect
system to make this easy to do. David Olofson's code from
Fixed
Rate Pig (http://olofson.net/)
turned out to be a great and simple implementation, so he
deserves all the credit for what ended up in Sprig.
The complete Dirty system consists of these calls:
The first group up there are the important ones. To start,
call:
SPG_EnableDirty(1);
This
tells all of the primitives to output dirty rects. These
rects
are collected by the 'front' dirty table. There are two of
these
tables, the 'front' and the 'back'. Everything happens on the
front table and the back table is used to save old dirty rects for an
extra frame. This is necessary since you must update the new
position of a sprite to show where it is, but also the old position in
order to show empty space where it once was. The definition
of
the tables looks like this:
typedef struct SPG_DirtyTable
{
Uint16
size; /*
Table size */
SDL_Rect
*rects; /* Table of rects */
Uint16
count; /*
# of rects currently used */
Uint16
best; /*
Merge testing starts here! */
} SPG_DirtyTable;
You will use the count to loop over the rects when redrawing your
background.
Next, call:
SPG_DirtyInit(64);
This
initializes the front and back tables to contain a maximum of 64 dirty
rects. When it reaches that limit, some rects will be
combined as
more are added. Now you're ready to enter your main drawing
loop. Whenever you draw something to the screen, you need to
collect its dirty rect. If you use SDL_BlitSurface(), then
this
is pretty easy:
SDL_BlitSurface
will write the actual dirty rect into 'dirty'. SPG_DirtyAdd()
puts the given rect into the front table and does any necessary
merging. You must be sure that the rects that you add to the
front table are properly clipped to the screen. If not, the
internal call to SDL_UpdateRects() will crash. If you're not
sure
about it, call:
SPG_DirtyClip(screen, &dirty);
This will
make sure that your rect is safe to add. Any calls to Sprig
primitives and blits will automatically do all of this.
When you're all done adding dirty rects (you're done drawing to the
screen), make this call:
SPG_DirtyTable* table = SPG_DirtyUpdate(screen);
Now
you've updated the screen, just as if you had called SDL_Flip(screen),
and you receive a pointer to the front table. This has all of
the
newly updated rects in it for you to redraw the screen's background:
// This is for a full-screen background image
int i;
SDL_Rect dest;
for(i = 0; i < table->count; i++)
{
dest = table->rects[i]; //
SDL_BlitSurface is destructive to your rects
SDL_BlitSurface(myBackground,
&(table->rects[i]), screen, &dest);
}
The screen is all set for more drawing. Now we have to swap
the tables:
SPG_DirtySwap();
This clears and switches the tables so that they are ready for the next
frame.
The
rest of the functions are for if you want to do fancy things with the
dirty rects.
You can make your own table with SPG_DirtyMake. You can add
rects to it with
SPG_DirtyAddTo and
free it with SPG_DirtyFree when you're done.
SPG_DirtyEnabled tells you if dirty rects will be
automatically generated.
SPG_bool gettingDirty = SPG_DirtyEnabled();
SPG_DirtyGet returns a pointer to the
front table (just be aware that the tables are switched each time
SPG_DirtySwap is called).
SPG_DirtyTable* front = SPG_DirtyGet();
// Add stuff to it or whatever
SPG_DirtyClear will reset a given
table, and you can adjust the constants in the dirty algorithm with
SPG_DirtyLevel. These constants determine the thresholds for
merging rects.
SPG_DirtyClear(table);
SPG_DirtyLevel(200);
There you go. By now you
should have a feeling of what Sprig can do for you and what kinds of
awesome games and applications you can write using it. If you
have any questions, suggestions, or contributions, drop me an email.