The Allegro Wiki is migrating to github at https://github.com/liballeg/allegro_wiki/wiki

Fast AA Sprites

From Allegro Wiki
Jump to: navigation, search

Rationale

Using pre-anti-aliased sprites can go a long way to making your game look professional. Unfortunately, sometimes you are unable to use the proper tools (AllegroGL or Allegro5) for the job, and are stuck with software drawing methods of Allegro 4. The standard method of accomplishing this is to use the draw_trans_sprite, which uses the software blending functions to draw the anti-aliased sprites using their alpha channel. This is slow and wasteful, however, as only a small number of pixels actually need to be blended (the ones at the edges) while the rest can be taken care of using the much faster draw_sprite function.

General Method

The idea behind this class and functions is to separate the pixels that need alpha blending done on them from the ones that do not. The first step in using these advantages is to preprocess the 32 bpp alpha channel pre-anti-aliased sprite. The next step is to use the new drawing functions to actually blit the sprites to the destination memory bitmap. Be sure you blit this to memory bitmaps only!

Files

There are 2 files for this class/functions.

aasprite.h - Include this file to use this class/functions in your program <highlightSyntax language="cpp">

  1. pragma once
  2. include <allegro.h>
  3. include <stdbool.h>

typedef struct { int X; int Y; int Color; } Pixel;

typedef struct { BITMAP* SolidPart;

Pixel* Pixels; int NumPixels; } AASprite;

/* Covert a 32 bpp alpha channel bitmap into the optimized AA sprite

  • /

AASprite* CreateAASprite(BITMAP* src); void DestroyAASprite(AASprite* sprite);

/* Draws the AA sprite onto a destination memory bitmap, that is 32 bpp Analogous to draw_sprite The flip parameter flips the sprite 180 degrees before drawing

  • /

void DrawAASprite(BITMAP* dest, AASprite* sprite, int x, int y, bool flip); /* Draws the AA sprite onto a destination memory bitmap, that is 32 bpp Analogous to masked_blit

  • /

void BlitAASprite(BITMAP* dest, AASprite* sprite, int source_x, int source_y, int dest_x, int dest_y, int width, int height); </highlightSyntax>

aasprite.c - The actual implementation file <highlightSyntax language="cpp">

  1. include "aasprite.h"

/* All in one putpixel + blending function Only works well for memory bitmaps, probably crashes and burns for all others

  • /

inline void trans_putpixel32(BITMAP* dest, int x, int y, int color) { /* Try to reject the pixel */ if(dest->clip) { if(x < dest->cl || x >= dest->cr - 1 || y < dest->ct || y >= dest->cb - 1) return; }

int dest_col = ((int*)dest->line[y])[x];

/* Allegro's alpha blending follows */

int b = dest_col; int a = color; int g; int res;

int n = geta32(a); if(n) n++;

res = ((a & 0xFF00FF) - (b & 0xFF00FF)) * n / 256 + b; b &= 0xFF00; a &= 0xFF00; g = (a - b) * n / 256 + b;

res &= 0xFF00FF; g &= 0xFF00;

((int*)dest->line[y])[x] = res | g; }

void DestroyAASprite(AASprite* sprite) { destroy_bitmap(sprite->SolidPart); free(sprite->Pixels); free(sprite); }

/* Source bitmap is rgba, also the sprite now owns the passed bitmap, so you no longer have to keep track of it

  • /

AASprite* CreateAASprite(BITMAP* src) { ASSERT(bitmap_color_depth(src) == 32); ASSERT(is_memory_bitmap(src) == TRUE);

const int magick_pink = makecol(255, 0, 255);

AASprite* ret = malloc(sizeof(AASprite)); ret->SolidPart = src;

/* A reasonable estimate I'd think */ int max_size = 2 * src->h + 2 * src->w; ret->NumPixels = 0; ret->Pixels = malloc(sizeof(Pixel) * max_size); /* Just so we can do realloc safely */ Pixel* pixels = 0;

for(int y = 0; y < src->h; y++) { for(int x = 0; x < src->w; x++) { int color = _getpixel32(src, x, y); int alpha = geta32(color); if(alpha != 255) { if(alpha > 0) { ret->Pixels[ret->NumPixels].X = x; ret->Pixels[ret->NumPixels].Y = y; ret->Pixels[ret->NumPixels].Color = color; ret->NumPixels++; if(ret->NumPixels >= max_size) { max_size *= 2; pixels = realloc(ret->Pixels, sizeof(Pixel) * max_size); if(pixels) { ret->Pixels = pixels; } else { return ret; } } } _putpixel32(src, x, y, magick_pink); } } }

/* Now shrink down the Pixels array so we don't use more than we need */

pixels = malloc(sizeof(Pixel) * ret->NumPixels); memcpy(pixels, ret->Pixels, sizeof(Pixel) * ret->NumPixels); free(ret->Pixels); ret->Pixels = pixels;

return ret; }

/* The flip parameter rotates the bitmap 180 degrees before drawing it Which is useful for some purposes

  • /

void DrawAASprite(BITMAP* dest, AASprite* sprite, int x, int y, bool flip) { ASSERT(bitmap_color_depth(dest) == 32); ASSERT(is_memory_bitmap(dest) == TRUE);

if(flip) { draw_sprite_vh_flip(dest, sprite->SolidPart, x, y);

const int h = sprite->SolidPart->h; const int w = sprite->SolidPart->w;

const int sz = sprite->NumPixels; for(int ii = 0; ii < sz; ii++) { putpixel(dest, w - sprite->Pixels[ii].X - 1 + x, h - sprite->Pixels[ii].Y - 1 + y, sprite->Pixels[ii].Color); } } else { draw_sprite(dest, sprite->SolidPart, x, y);

const int sz = sprite->NumPixels; for(int ii = 0; ii < sz; ii++) { trans_putpixel32(dest, sprite->Pixels[ii].X + x, sprite->Pixels[ii].Y + y, sprite->Pixels[ii].Color); } } }

/* A blit like version of draw aa sprite, the flip option was removed This one is a little slower than the other one, avoid this using this if you don't have to

  • /

void BlitAASprite(BITMAP* dest, AASprite* sprite, int source_x, int source_y, int dest_x, int dest_y, int width, int height) { ASSERT(bitmap_color_depth(dest) == 32); ASSERT(is_memory_bitmap(dest) == TRUE);

masked_blit(sprite->SolidPart, dest, source_x, source_y, dest_x, dest_y, width, height);

/* We'll use allegro's built in clipping to take care of the non-drawn transparent pixels */ int old_clip[4]; get_clip_rect(dest, &old_clip[0], &old_clip[1], &old_clip[2], &old_clip[3]); set_clip_rect(dest, dest_x, dest_y, dest_x + width, dest_y + height);

const int sz = sprite->NumPixels; for(int ii = 0; ii < sz; ii++) { trans_putpixel32(dest, sprite->Pixels[ii].X + dest_x - source_x, sprite->Pixels[ii].Y + dest_y - source_y, sprite->Pixels[ii].Color); }

/* Restore old clipping state */ set_clip_rect(dest, old_clip[0], old_clip[1], old_clip[2], old_clip[3]); } </highlightSyntax>

Sample Usage

<highlightSyntax language="cpp"> /* Create the optimized sprite from an alpha-channel tga image

  • /

BITMAP* source_bmp = load_bitmap("source.tga", 0); AASprite* sprite = CreateAASprite(source_bmp); //AASprite* sprite2 = CreateAASprite(source_bmp); <--- This would be wrong, can't reuse the same source BITMAP

/* Draw it to a memory backbuffer

  • /

DrawAASprite(backbuffer, sprite, 100, 100, false); //DrawAASprite(screen, sprite, 100, 100, false); <--- This would be wrong, can't draw to video bitmaps

/* Destroy the sprite and destroy the source_bmp in one step

  • /

DestroyAASprite(sprite); //destroy_bitmap(source_bmp); <--- This would be wrong, source_bmp is already freed by the above function </highlightSyntax>

Benchmarks

My own tests of these functions showed that the faster version of DrawAASprite is roughly 3-4 times faster than the equivalent call to draw_trans_sprite. The slower version of the DrawAASprite is roughly 90% as fast as the faster version.