From 14f76fbe03d740d76c6867a87713fb469ae7eaa7 Mon Sep 17 00:00:00 2001 From: PikalaxALT Date: Fri, 24 Apr 2020 08:47:22 -0400 Subject: [PATCH] Add tilemap rendering capability to gbagfx --- src/title_screen.c | 5 +- tools/gbagfx/convert_png.c | 2 +- tools/gbagfx/gfx.c | 163 +++++++++++++++++++++++++++++++++++++ tools/gbagfx/gfx.h | 17 ++++ tools/gbagfx/main.c | 29 +++++++ tools/gbagfx/options.h | 4 + 6 files changed, 218 insertions(+), 2 deletions(-) diff --git a/src/title_screen.c b/src/title_screen.c index 600dd18c8..aa91351df 100644 --- a/src/title_screen.c +++ b/src/title_screen.c @@ -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(); diff --git a/tools/gbagfx/convert_png.c b/tools/gbagfx/convert_png.c index cdfa39a7a..4f1b39e6d 100644 --- a/tools/gbagfx/convert_png.c +++ b/tools/gbagfx/convert_png.c @@ -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; diff --git a/tools/gbagfx/gfx.c b/tools/gbagfx/gfx.c index f927deed9..8d959465f 100644 --- a/tools/gbagfx/gfx.c +++ b/tools/gbagfx/gfx.c @@ -4,6 +4,7 @@ #include #include #include +#include #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); } diff --git a/tools/gbagfx/gfx.h b/tools/gbagfx/gfx.h index 5355ced85..edb9e62c4 100644 --- a/tools/gbagfx/gfx.h +++ b/tools/gbagfx/gfx.h @@ -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); diff --git a/tools/gbagfx/main.c b/tools/gbagfx/main.c index 9fd72383d..5c9bfd641 100644 --- a/tools/gbagfx/main.c +++ b/tools/gbagfx/main.c @@ -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++) { diff --git a/tools/gbagfx/options.h b/tools/gbagfx/options.h index 2ff3967a4..3b038f572 100644 --- a/tools/gbagfx/options.h +++ b/tools/gbagfx/options.h @@ -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