Drawing NES Graphics With C#

One of the tricky parts of working with NES ROMs in .NET is drawing game graphics. There are several approaches that could be tried, but many have problems or simply don’t suit NES graphics. GDI+ is much too slow with tile based graphics, especially indexed (paletted) graphics. GDI is a better fit, but it is more difficult, still not ideal, and requires P/Invoke. Libraries such as DirectX or XNA offer the best performance, but require more work, especially if you need to learn the API. They can also add one more dependency that you may prefer to do without.

My preferred technique is to simply do the blitting myself. Knowing how to access graphics using LockBits is requisite.

Formats

8-bpp is probably the simplest format. Working with an indexed bitmap allows you to deal with palettes simply and flexibly. It’s also easy when every pixel is a byte, especially if you are working with byte arrays. The source tiles should be loaded into an 8-bpp bitmap using colors 0-3. Things will be simplest if the tiles are loaded in a single row rather than in a grid. (An image containing 256 8×8 tiles would be 2048×8 pixels.)

The destination image should also be 8-bpp. The color palette should be a composite of all 32 colors that the game would load: four palettes of four colors for the background and four palettes of four colors for sprites. This only uses one eighth of the 256 color palette. The rest of the colors can be used for various purposes. A brightened palette can be used for a highlight effect, or some colors may be used for the editor’s purposes. The palette can be modified via the bitmap’s Palette property.

When a tile is drawn it needs to use the correct palette. To do this, just “or” each byte from the source with a certain value to get the correct value in the destination image. For example, palette 1 would use colors 4-7. We want to “or” each source byte with a value of 4 to get the destination value between 4 and 7. A source pixel with color 2 will be “or”ed with 4 to produce a value of 6: the third color in the second palette.

Blitting

To do the actual drawing you need blitting (pixel-copying) code. The basic concept is pretty simple. First calculate the source and destination offsets. That’s easy:

int Offset = x + y * stride;

Using this base offset, we can scan horizontally by incrementing or decrementing the offset, and vertically by adding or subtracting the stride. A simple tile copy routine would look something like this:

// Assuming SourceStride and DestStride are already declared
// and initialized in this class...
const int TileWidth = 8;
const int TileHeight = 8;

// source and dest coords are in units of tiles
public void BlitTile(int sourceX, int sourceY, int destX, int destY, int paletteIndex) {
    int sourceOffset = (sourceX * TileWidth) + (sourceY * TileHeight) * SourceStride;
    int destOffset = (destX * TileWidth) + (destY * TileHeight) * DestStride;
    int paletteBits = paletteIndex << 2;

    for(int y = 0; y < TileHeight; y++) {
        for(int x = 0; x < TileWidth; x++) {
            // Copy pixel with palette or'd in.
            destBytes[destOffset + x] = (byte)(sourceBytes[sourceOffset + x] | paletteBits);
        }

        // Seek to next row
        sourceOffset += SourceStride;
        destOffset += DestStride;
    }
}

This will do the trick, and it could probably draw a screen built from tiles faster that GDI+, but it’s not 100% yet. It might help to unroll the inner loop. Using pointers would certainly be faster (though it requires unsafe code and doesn’t translate int VB). We’ll probably also want a function to clear part or all of the destination.

As far as sprites go, we haven’t addressed the issue of transparency, or the fact that sprite tiles don’t fit neatly into a grid like background tiles do. This also means we’ll probably need clipping for sprites. (Or, if you want to cheat, you can leave an extra 8-pixel wide border around the destination image. This way, you’ll never have to do any clipping calculations. If any part of the tile is in the visible part of the screen image, the whole tile will fit into the destination bitmap; just leave out the extra border space when you show the image on screen.)

Other Drawing

Drawing simple things like horizontal/vertical lines and rectangles is pretty easy. There are more complex operations we might want, but it’s not worth the effort of coding these things ourselves when the features already come packaged with GDI+. The problem is that we can’t use GDI+ to draw to an indexed bitmap. What we can do, however, is perform additional drawing in the Paint event of the control our image will be shown in.

Using 32-bit Images

There’s no reason the destination can’t be 32-bits. In this case we would probably want to deal with the destination image data as an array of UInt32. The color palette can be stored in a UInt32 array containing the ARGB values for each palette entry. The source image can still be 8-bpp. We would just treat each source pixel value as an index into the color palette array, in which case our pixel copying code would probably look more like so:

int colorIndex = sourceBytes[sourceOffset + x] | paletteBits;
destInts[destOffset + x] = paletteEntries[colorIndex];
// When calculating dest offset, keep in mind that the stride is in bytes,
// while the array index refers to 4-byte integers.

If the destination image is 32-bits we are free to use GDI+ to draw directly to it once we are done drawing our tiles.

There are a couple of disadvantages to using 32-bit images, but they are minor. First, the images will be four times as large. Unless you have very many loaded at the same time this won’t be an issue. The other disadvantage is that you can’t change the image’s palette without re-drawing the image.

Leave a Reply

Your email address will not be published. Required fields are marked *