Add tilemap rendering capability to gbagfx

This commit is contained in:
PikalaxALT 2020-04-24 08:47:22 -04:00
parent dc126e3016
commit 14f76fbe03
6 changed files with 218 additions and 2 deletions

View File

@ -546,11 +546,14 @@ void CB2_InitTitleScreen(void)
gMain.state = 1;
break;
case 1:
LZ77UnCompVram(gTitleScreenPokemonLogoGfx, (void *)VRAM);
// bg2
LZ77UnCompVram(gTitleScreenPokemonLogoGfx, (void *)(BG_CHAR_ADDR(0)));
LZ77UnCompVram(gUnknown_08DE0644, (void *)(BG_SCREEN_ADDR(9)));
LoadPalette(gTitleScreenBgPalettes, 0, 0x1E0);
// bg3
LZ77UnCompVram(sTitleScreenRayquazaGfx, (void *)(BG_CHAR_ADDR(2)));
LZ77UnCompVram(sTitleScreenRayquazaTilemap, (void *)(BG_SCREEN_ADDR(26)));
// bg1
LZ77UnCompVram(sTitleScreenCloudsGfx, (void *)(BG_CHAR_ADDR(3)));
LZ77UnCompVram(gUnknown_08DDE458, (void *)(BG_SCREEN_ADDR(27)));
ScanlineEffect_Stop();

View File

@ -125,7 +125,7 @@ void ReadPng(char *path, struct Image *image)
free(row_pointers);
fclose(fp);
if (bit_depth != image->bitDepth)
if (bit_depth != image->bitDepth && image->tilemap.data.affine == NULL)
{
unsigned char *src = image->pixels;

View File

@ -4,6 +4,7 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "global.h"
#include "gfx.h"
#include "util.h"
@ -203,6 +204,147 @@ static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numT
}
}
static void DecodeAffineTilemap(unsigned char *input, unsigned char *output, unsigned char *tilemap, int tileSize, int numTiles)
{
for (int i = 0; i < numTiles; i++)
{
memcpy(&output[i * tileSize], &input[tilemap[i] * tileSize], tileSize);
}
}
#define REVERSE_BIT_ORDER(x) ({ \
((((x) >> 7) & 1) << 0) \
| ((((x) >> 6) & 1) << 1) \
| ((((x) >> 5) & 1) << 2) \
| ((((x) >> 4) & 1) << 3) \
| ((((x) >> 3) & 1) << 4) \
| ((((x) >> 2) & 1) << 5) \
| ((((x) >> 1) & 1) << 6) \
| ((((x) >> 0) & 1) << 7); \
})
#define SWAP_BYTES(a, b) ({ \
unsigned char tmp = *(a); \
*(a) = *(b); \
*(b) = tmp; \
})
#define NSWAP(x) ({ (((x) >> 4) & 0xF) | (((x) << 4) & 0xF0); })
#define SWAP_NYBBLES(a, b) ({ \
unsigned char tmp = NSWAP(*(a)); \
*(a) = NSWAP(*(b)); \
*(b) = tmp; \
})
static void VflipTile(unsigned char * tile, int bitDepth)
{
int i;
switch (bitDepth)
{
case 1:
SWAP_BYTES(&tile[0], &tile[7]);
SWAP_BYTES(&tile[1], &tile[6]);
SWAP_BYTES(&tile[2], &tile[5]);
SWAP_BYTES(&tile[3], &tile[4]);
break;
case 4:
for (i = 0; i < 4; i++)
{
SWAP_BYTES(&tile[i + 0], &tile[i + 28]);
SWAP_BYTES(&tile[i + 4], &tile[i + 24]);
SWAP_BYTES(&tile[i + 8], &tile[i + 20]);
SWAP_BYTES(&tile[i + 12], &tile[i + 16]);
}
break;
case 8:
for (i = 0; i < 8; i++)
{
SWAP_BYTES(&tile[i + 0], &tile[i + 56]);
SWAP_BYTES(&tile[i + 8], &tile[i + 48]);
SWAP_BYTES(&tile[i + 16], &tile[i + 40]);
SWAP_BYTES(&tile[i + 24], &tile[i + 32]);
}
break;
}
}
static void HflipTile(unsigned char * tile, int bitDepth)
{
int i;
switch (bitDepth)
{
case 1:
for (i = 0; i < 8; i++)
tile[i] = REVERSE_BIT_ORDER(tile[i]);
break;
case 4:
for (i = 0; i < 8; i++)
{
SWAP_NYBBLES(&tile[4 * i + 0], &tile[4 * i + 3]);
SWAP_NYBBLES(&tile[4 * i + 1], &tile[4 * i + 2]);;
}
break;
case 8:
for (i = 0; i < 8; i++)
{
SWAP_BYTES(&tile[8 * i + 0], &tile[8 * i + 7]);
SWAP_BYTES(&tile[8 * i + 1], &tile[8 * i + 6]);
SWAP_BYTES(&tile[8 * i + 2], &tile[8 * i + 5]);
SWAP_BYTES(&tile[8 * i + 3], &tile[8 * i + 4]);
}
break;
}
}
static void DecodeNonAffineTilemap(unsigned char *input, unsigned char *output, struct NonAffineTile *tilemap, int tileSize, int outTileSize, int bitDepth, int numTiles)
{
unsigned char * in_tile;
unsigned char * out_tile = output;
int effectiveBitDepth = tileSize == outTileSize ? bitDepth : 8;
for (int i = 0; i < numTiles; i++)
{
in_tile = &input[tilemap[i].index * tileSize];
if (tileSize == outTileSize)
memcpy(out_tile, in_tile, tileSize);
else
{
for (int j = 0; j < 64; j++)
{
int shift = (j & 1) * 4;
out_tile[j] = (in_tile[j / 2] & (0xF << shift)) >> shift;
}
}
if (tilemap[i].hflip)
HflipTile(out_tile, effectiveBitDepth);
if (tilemap[i].vflip)
VflipTile(out_tile, effectiveBitDepth);
if (bitDepth == 4 && effectiveBitDepth == 8)
{
for (int j = 0; j < 64; j++)
{
out_tile[j] &= 0xF;
out_tile[j] |= (15 - tilemap[i].palno) << 4;
}
}
out_tile += outTileSize;
}
}
static unsigned char *DecodeTilemap(unsigned char *tiles, struct Tilemap *tilemap, int *numTiles_p, bool isAffine, int tileSize, int outTileSize, int bitDepth)
{
int mapTileSize = isAffine ? 1 : 2;
int numTiles = tilemap->size / mapTileSize;
unsigned char *decoded = calloc(numTiles, outTileSize);
if (isAffine)
DecodeAffineTilemap(tiles, decoded, tilemap->data.affine, tileSize, numTiles);
else
DecodeNonAffineTilemap(tiles, decoded, tilemap->data.non_affine, tileSize, outTileSize, bitDepth, numTiles);
free(tiles);
*numTiles_p = numTiles;
return decoded;
}
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
{
int tileSize = bitDepth * 8;
@ -211,6 +353,16 @@ void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int
unsigned char *buffer = ReadWholeFile(path, &fileSize);
int numTiles = fileSize / tileSize;
if (image->tilemap.data.affine != NULL)
{
int outTileSize = (bitDepth == 4 && image->palette.numColors > 16) ? 64 : tileSize;
buffer = DecodeTilemap(buffer, &image->tilemap, &numTiles, image->isAffine, tileSize, outTileSize, bitDepth);
if (outTileSize == 64)
{
tileSize = 64;
image->bitDepth = bitDepth = 8;
}
}
int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth;
@ -298,6 +450,11 @@ void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int m
void FreeImage(struct Image *image)
{
if (image->tilemap.data.affine != NULL)
{
free(image->tilemap.data.affine);
image->tilemap.data.affine = NULL;
}
free(image->pixels);
image->pixels = NULL;
}
@ -318,6 +475,12 @@ void ReadGbaPalette(char *path, struct Palette *palette)
palette->colors[i].green = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_GREEN(paletteEntry));
palette->colors[i].blue = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_BLUE(paletteEntry));
}
// png can only accept 16 or 256 colors, so fill the remainder with black
if (palette->numColors > 16)
{
memset(&palette->colors[palette->numColors], 0, (256 - palette->numColors) * sizeof(struct Color));
palette->numColors = 256;
}
free(data);
}

View File

@ -17,6 +17,21 @@ struct Palette {
int numColors;
};
struct NonAffineTile {
unsigned short index:10;
unsigned short hflip:1;
unsigned short vflip:1;
unsigned short palno:4;
} __attribute__((packed));
struct Tilemap {
union {
struct NonAffineTile *non_affine;
unsigned char *affine;
} data;
int size;
};
struct Image {
int width;
int height;
@ -25,6 +40,8 @@ struct Image {
bool hasPalette;
struct Palette palette;
bool hasTransparency;
struct Tilemap tilemap;
bool isAffine;
};
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);

View File

@ -45,6 +45,20 @@ void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions *
image.hasPalette = false;
}
if (options->tilemapFilePath != NULL)
{
int fileSize;
image.tilemap.data.affine = ReadWholeFile(options->tilemapFilePath, &fileSize);
if (options->isAffineMap && options->bitDepth != 8)
FATAL_ERROR("affine maps are necessarily 8bpp\n");
image.isAffine = options->isAffineMap;
image.tilemap.size = fileSize;
}
else
{
image.tilemap.data.affine = NULL;
}
ReadImage(inputPath, options->width, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
image.hasTransparency = options->hasTransparency;
@ -59,6 +73,7 @@ void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions *
struct Image image;
image.bitDepth = options->bitDepth;
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
ReadPng(inputPath, &image);
@ -77,6 +92,7 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a
options.width = 1;
options.metatileWidth = 1;
options.metatileHeight = 1;
options.isAffineMap = false;
for (int i = 3; i < argc; i++)
{
@ -134,6 +150,17 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a
if (options.metatileHeight < 1)
FATAL_ERROR("metatile height must be positive.\n");
}
else if (strcmp(option, "-tilemap") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No tilemap value following \"-tilemap\".\n");
i++;
options.tilemapFilePath = argv[i];
}
else if (strcmp(option, "-affine") == 0)
{
options.isAffineMap = true;
}
else
{
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
@ -155,6 +182,8 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a
options.bitDepth = bitDepth;
options.metatileWidth = 1;
options.metatileHeight = 1;
options.tilemapFilePath = NULL;
options.isAffineMap = false;
for (int i = 3; i < argc; i++)
{

View File

@ -12,6 +12,8 @@ struct GbaToPngOptions {
int width;
int metatileWidth;
int metatileHeight;
char *tilemapFilePath;
bool isAffineMap;
};
struct PngToGbaOptions {
@ -19,6 +21,8 @@ struct PngToGbaOptions {
int bitDepth;
int metatileWidth;
int metatileHeight;
char *tilemapFilePath;
bool isAffineMap;
};
#endif // OPTIONS_H