Graphics Formats

From TLoZ: ALTTP Hacking Resources
Jump to: navigation, search

See also: Standard Notation

See also: Graphics Palettes

The SNES display is 256 pixels wide and 224 pixels high with a color depth of 15 bits (32768 distinct colors). While the fundamental unit of measurement for SNES graphics is still pixels (with the (0,0) origin at the top-left corner), the fundamental unit of SNES graphical display is the 8x8 pixel tile. Larger objects are composed of multiple 8x8 tiles, and smaller objects are still 8x8 tiles but with most pixels transparent.

ALTTP graphics are stored in the ROM as 2bpp/3bpp/4bpp bitplane-and-row-interleaved 8x8-pixel tiles, each tile following the other in sequence. These formats are converted at load time to the appropriate SNES display format.

Row, Column, and Pixel Indices

For ease of reference, the following method is used to index an 8x8 tile.

Column 0 Column 1 Column 2 Column 3 Column 4 Column 5 Column 6 Column 7
Row 0 Pixel 0 Pixel 1 Pixel 2 Pixel 3 Pixel 4 Pixel 5 Pixel 6 Pixel 7
Row 1 Pixel 8 Pixel 9 Pixel 10 Pixel 11 Pixel 12 Pixel 13 Pixel 14 Pixel 15
Row 2 Pixel 16 Pixel 17 Pixel 18 Pixel 19 Pixel 20 Pixel 21 Pixel 22 Pixel 23
Row 3 Pixel 24 Pixel 25 Pixel 26 Pixel 27 Pixel 28 Pixel 29 Pixel 30 Pixel 31
Row 4 Pixel 32 Pixel 33 Pixel 34 Pixel 35 Pixel 36 Pixel 37 Pixel 38 Pixel 39
Row 5 Pixel 40 Pixel 41 Pixel 42 Pixel 43 Pixel 44 Pixel 45 Pixel 46 Pixel 47
Row 6 Pixel 48 Pixel 49 Pixel 50 Pixel 51 Pixel 52 Pixel 53 Pixel 54 Pixel 55
Row 7 Pixel 56 Pixel 57 Pixel 58 Pixel 59 Pixel 60 Pixel 61 Pixel 62 Pixel 63

Bitplanes

Just as a value is composed of a certain number of bits, an array of values is composed of a certain number of bitplanes. Consider the following 3bpp palette and sprite.

%000 %001 %010 %011 %100 %101 %110 %111
%001 %001 %001 %001 %001 %001 %001 %001
%001 %011 %010 %101 %101 %010 %011 %001
%001 %010 %100 %110 %110 %100 %010 %001
%001 %101 %000 %111 %111 %000 %101 %001
%001 %101 %000 %111 %111 %000 %101 %001
%001 %010 %100 %110 %110 %100 %010 %001
%001 %011 %010 %101 %101 %010 %011 %001
%001 %001 %001 %001 %001 %001 %001 %001

All of the bit index 2 values become bitplane 2.

%0 %0 %0 %0 %0 %0 %0 %0
%0 %0 %0 %1 %1 %0 %0 %0
%0 %0 %1 %1 %1 %1 %0 %0
%0 %1 %0 %1 %1 %0 %1 %0
%0 %1 %0 %1 %1 %0 %1 %0
%0 %0 %1 %1 %1 %1 %0 %0
%0 %0 %0 %1 %1 %0 %0 %0
%0 %0 %0 %0 %0 %0 %0 %0

All of the bit index 1 values become bitplane 1.

%0 %0 %0 %0 %0 %0 %0 %0
%0 %1 %1 %0 %0 %1 %1 %0
%0 %1 %0 %1 %1 %0 %1 %0
%0 %0 %0 %1 %1 %0 %0 %0
%0 %0 %0 %1 %1 %0 %0 %0
%0 %1 %0 %1 %1 %0 %1 %0
%0 %1 %1 %0 %0 %1 %1 %0
%0 %0 %0 %0 %0 %0 %0 %0

All of the bit index 0 values become bitplane 0.

%1 %1 %1 %1 %1 %1 %1 %1
%1 %1 %0 %1 %1 %0 %1 %1
%1 %0 %0 %0 %0 %0 %0 %1
%1 %1 %0 %1 %1 %0 %1 %1
%1 %1 %0 %1 %1 %0 %1 %1
%1 %0 %0 %0 %0 %0 %0 %1
%1 %1 %0 %1 %1 %0 %1 %1
%1 %1 %1 %1 %1 %1 %1 %1

Essentially, the 3bpp image is split into multiple 1bpp images: One for each bit. The bitplanes are indexed by row/column/pixel in the same way as the integrated tile itself.

Bitplane Interlacing

Tiles are always 8x8 pixels in size, and 8 happens to be the number of bits in a byte. One row of one bitplane of a tile (or, another way to look at it, one bitplane of one row of a tile) is stored in each byte. These bytes are interlaced as follows.

SNES Format Byte Index 2bpp 3bpp 4bpp
Byte $00 Row 0 of Bitplane 0 Row 0 of Bitplane 0 Row 0 of Bitplane 0
Byte $01 Row 0 of Bitplane 1 Row 0 of Bitplane 1 Row 0 of Bitplane 1
Byte $02 Row 1 of Bitplane 0 Row 1 of Bitplane 0 Row 1 of Bitplane 0
Byte $03 Row 1 of Bitplane 1 Row 1 of Bitplane 1 Row 1 of Bitplane 1
Byte $04 Row 2 of Bitplane 0 Row 2 of Bitplane 0 Row 2 of Bitplane 0
Byte $05 Row 2 of Bitplane 1 Row 2 of Bitplane 1 Row 2 of Bitplane 1
Byte $06 Row 3 of Bitplane 0 Row 3 of Bitplane 0 Row 3 of Bitplane 0
Byte $07 Row 3 of Bitplane 1 Row 3 of Bitplane 1 Row 3 of Bitplane 1
Byte $08 Row 4 of Bitplane 0 Row 4 of Bitplane 0 Row 4 of Bitplane 0
Byte $09 Row 4 of Bitplane 1 Row 4 of Bitplane 1 Row 4 of Bitplane 1
Byte $0A Row 5 of Bitplane 0 Row 5 of Bitplane 0 Row 5 of Bitplane 0
Byte $0B Row 5 of Bitplane 1 Row 5 of Bitplane 1 Row 5 of Bitplane 1
Byte $0C Row 6 of Bitplane 0 Row 6 of Bitplane 0 Row 6 of Bitplane 0
Byte $0D Row 6 of Bitplane 1 Row 6 of Bitplane 1 Row 6 of Bitplane 1
Byte $0E Row 7 of Bitplane 0 Row 7 of Bitplane 0 Row 7 of Bitplane 0
Byte $0F Row 7 of Bitplane 1 Row 7 of Bitplane 1 Row 7 of Bitplane 1
Byte $10 Row 0 of Bitplane 2 Row 0 of Bitplane 2
Byte $11 Row 1 of Bitplane 2 Row 0 of Bitplane 3
Byte $12 Row 2 of Bitplane 2 Row 1 of Bitplane 2
Byte $13 Row 3 of Bitplane 2 Row 1 of Bitplane 3
Byte $14 Row 4 of Bitplane 2 Row 2 of Bitplane 2
Byte $15 Row 5 of Bitplane 2 Row 2 of Bitplane 3
Byte $16 Row 6 of Bitplane 2 Row 3 of Bitplane 2
Byte $17 Row 7 of Bitplane 2 Row 3 of Bitplane 3
Byte $18 Row 4 of Bitplane 2
Byte $19 Row 4 of Bitplane 3
Byte $1A Row 5 of Bitplane 2
Byte $1B Row 5 of Bitplane 3
Byte $1C Row 6 of Bitplane 2
Byte $1D Row 6 of Bitplane 3
Byte $1E Row 7 of Bitplane 2
Byte $1F Row 7 of Bitplane 3

Sample C# Code

public static class SnesGraphicsFormat
{
	public static void UnpackRowOfBitplaneOfTile ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int BitplaneIndex )
	{
		for ( int PixelIndex = 0 ; PixelIndex < 8 ; PixelIndex ++ )
		{
			int BitIndex = 7 - PixelIndex ;

			int Bit = ( Input [ InputOffset ] >> BitIndex ) & 1 ;

			Output [ OutputOffset + PixelIndex ] |= ( byte ) ( Bit << BitplaneIndex ) ;
		}
	}

	public static void PackRowOfBitplaneOfTile ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int BitplaneIndex )
	{
		for ( int PixelIndex = 0 ; PixelIndex < 8 ; PixelIndex ++ )
		{
			int BitIndex = 7 - PixelIndex ;

			int Bit = ( Input [ InputOffset + PixelIndex ] >> BitplaneIndex ) & 1 ;

			Output [ OutputOffset ] |= ( byte ) ( Bit << BitIndex ) ;
		}
	}

	public const int BytesPerTileUnpacked = 64 ;

	public const int BytesPerTile2bpp = 16 ;

	static int [ ] RowIndices2bpp = new int [ ]
	{
		0 , 0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 5 , 6 , 6 , 7 , 7
	} ;

	static int [ ] BitplaneIndices2bpp = new int [ ]
	{
		0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1
	} ;

	public const int BytesPerTile3bpp = 24 ;

	static int [ ] RowIndices3bpp = new int [ ]
	{
		0 , 0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 5 , 6 , 6 , 7 , 7 ,
		0 , 1 , 2 , 3 , 4 , 5 , 6 , 7
	} ;

	static int [ ] BitplaneIndices3bpp = new int [ ]
	{
		0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 ,
		2 , 2 , 2 , 2 , 2 , 2 , 2 , 2
	} ;

	public const int BytesPerTile4bpp = 32 ;

	static int [ ] RowIndices4bpp = new int [ ]
	{
		0 , 0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 5 , 6 , 6 , 7 , 7 ,
		0 , 0 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 5 , 6 , 6 , 7 , 7
	} ;

	static int [ ] BitplaneIndices4bpp = new int [ ]
	{
		0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 ,
		2 , 3 , 2 , 3 , 2 , 3 , 2 , 3 , 2 , 3 , 2 , 3 , 2 , 3 , 2 , 3
	} ;

	public static void UnpackTile ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int [ ] RowIndices , int [ ] BitplaneIndices )
	{
		for ( int Index = 0 ; Index < RowIndices . Length ; Index ++ )
		{
			UnpackRowOfBitplaneOfTile ( Input , InputOffset + Index , Output , OutputOffset + RowIndices [ Index ] * 8 , BitplaneIndices [ Index ] ) ;
		}
	}

	public static void PackTile ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int [ ] RowIndices , int [ ] BitplaneIndices )
	{
		for ( int Index = 0 ; Index < RowIndices . Length ; Index ++ )
		{
			PackRowOfBitplaneOfTile ( Input , InputOffset + RowIndices [ Index ] * 8 , Output , OutputOffset + Index , BitplaneIndices [ Index ] ) ;
		}
	}

	public static void UnpackTiles ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int TileCount , int BytesPerTile , int [ ] RowIndices , int [ ] BitplaneIndices )
	{
		for ( int Index = 0 ; Index < TileCount ; Index ++ )
		{
			UnpackTile ( Input , InputOffset + BytesPerTile * Index , Output , OutputOffset + BytesPerTileUnpacked * Index , RowIndices , BitplaneIndices ) ;
		}
	}

	public static void PackTiles ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int TileCount , int BytesPerTile , int [ ] RowIndices , int [ ] BitplaneIndices )
	{
		for ( int Index = 0 ; Index < TileCount ; Index ++ )
		{
			PackTile ( Input , InputOffset + BytesPerTileUnpacked * Index , Output , OutputOffset + BytesPerTile * Index , RowIndices , BitplaneIndices ) ;
		}
	}

	public static void Unpack2bppTiles ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int TileCount )
	{
		UnpackTiles ( Input , InputOffset , Output , OutputOffset , TileCount , BytesPerTile2bpp , RowIndices2bpp , BitplaneIndices2bpp ) ;
	}

	public static void Unpack3bppTiles ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int TileCount )
	{
		UnpackTiles ( Input , InputOffset , Output , OutputOffset , TileCount , BytesPerTile3bpp , RowIndices3bpp , BitplaneIndices3bpp ) ;
	}

	public static void Unpack4bppTiles ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int TileCount )
	{
		UnpackTiles ( Input , InputOffset , Output , OutputOffset , TileCount , BytesPerTile4bpp , RowIndices4bpp , BitplaneIndices4bpp ) ;
	}

	public static void Pack2bppTiles ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int TileCount )
	{
		PackTiles ( Input , InputOffset , Output , OutputOffset , TileCount , BytesPerTile2bpp , RowIndices2bpp , BitplaneIndices2bpp ) ;
	}

	public static void Pack3bppTiles ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int TileCount )
	{
		PackTiles ( Input , InputOffset , Output , OutputOffset , TileCount , BytesPerTile3bpp , RowIndices3bpp , BitplaneIndices3bpp ) ;
	}

	public static void Pack4bppTiles ( byte [ ] Input , int InputOffset , byte [ ] Output , int OutputOffset , int TileCount )
	{
		PackTiles ( Input , InputOffset , Output , OutputOffset , TileCount , BytesPerTile4bpp , RowIndices4bpp , BitplaneIndices4bpp ) ;
	}
}