mirror of https://github.com/pret/pokeemerald.git
Merge pull request #1027 from PikalaxALT/gbagfx_tilemap
Add tilemap rendering capability to gbagfx
This commit is contained in:
commit
e0ae696479
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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++)
|
||||
{
|
||||
|
@ -272,6 +301,7 @@ void HandleJascToGbaPaletteCommand(char *inputPath, char *outputPath, int argc,
|
|||
void HandleLatinFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
|
||||
|
||||
ReadLatinFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
@ -282,6 +312,7 @@ void HandleLatinFontToPngCommand(char *inputPath, char *outputPath, int argc UNU
|
|||
void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
|
||||
|
||||
image.bitDepth = 2;
|
||||
|
||||
|
@ -294,6 +325,7 @@ void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNU
|
|||
void HandleHalfwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
|
||||
|
||||
ReadHalfwidthJapaneseFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
@ -304,6 +336,7 @@ void HandleHalfwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath,
|
|||
void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
|
||||
|
||||
image.bitDepth = 2;
|
||||
|
||||
|
@ -316,6 +349,7 @@ void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath,
|
|||
void HandleFullwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
|
||||
|
||||
ReadFullwidthJapaneseFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
@ -326,6 +360,7 @@ void HandleFullwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath,
|
|||
void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
image.tilemap.data.affine = NULL; // initialize to NULL to avoid issues in FreeImage
|
||||
|
||||
image.bitDepth = 2;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue