summaryrefslogtreecommitdiff
path: root/tools/gbagfx
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gbagfx')
-rw-r--r--tools/gbagfx/convert_png.c2
-rw-r--r--tools/gbagfx/gfx.c163
-rw-r--r--tools/gbagfx/gfx.h17
-rw-r--r--tools/gbagfx/main.c110
-rw-r--r--tools/gbagfx/options.h4
-rw-r--r--tools/gbagfx/util.c7
-rw-r--r--tools/gbagfx/util.h1
7 files changed, 292 insertions, 12 deletions
diff --git a/tools/gbagfx/convert_png.c b/tools/gbagfx/convert_png.c
index cdfa39a..4f1b39e 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 f927dee..8d95946 100644
--- a/tools/gbagfx/gfx.c
+++ b/tools/gbagfx/gfx.c
@@ -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);
}
diff --git a/tools/gbagfx/gfx.h b/tools/gbagfx/gfx.h
index 5355ced..edb9e62 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 aa0681f..61e93ea 100644
--- a/tools/gbagfx/main.c
+++ b/tools/gbagfx/main.c
@@ -27,7 +27,17 @@ void ConvertGbaToPng(char *inputPath, char *outputPath, struct GbaToPngOptions *
if (options->paletteFilePath != NULL)
{
- ReadGbaPalette(options->paletteFilePath, &image.palette);
+ char *paletteFileExtension = GetFileExtensionAfterDot(options->paletteFilePath);
+
+ if (strcmp(paletteFileExtension, "gbapal") == 0)
+ {
+ ReadGbaPalette(options->paletteFilePath, &image.palette);
+ }
+ else
+ {
+ ReadJascPalette(options->paletteFilePath, &image.palette);
+ }
+
image.hasPalette = true;
}
else
@@ -35,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;
@@ -49,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);
@@ -59,7 +84,7 @@ void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions *
void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
- char *inputFileExtension = GetFileExtension(inputPath);
+ char *inputFileExtension = GetFileExtensionAfterDot(inputPath);
struct GbaToPngOptions options;
options.paletteFilePath = NULL;
options.bitDepth = inputFileExtension[0] - '0';
@@ -67,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++)
{
@@ -124,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);
@@ -138,13 +175,15 @@ void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **a
void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
- char *outputFileExtension = GetFileExtension(outputPath);
+ char *outputFileExtension = GetFileExtensionAfterDot(outputPath);
int bitDepth = outputFileExtension[0] - '0';
struct PngToGbaOptions options;
options.numTiles = 0;
options.bitDepth = bitDepth;
options.metatileWidth = 1;
options.metatileHeight = 1;
+ options.tilemapFilePath = NULL;
+ options.isAffineMap = false;
for (int i = 3; i < argc; i++)
{
@@ -198,9 +237,17 @@ void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **a
ConvertPngToGba(inputPath, outputPath, &options);
}
+void HandlePngToJascPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
+{
+ struct Palette palette = {};
+
+ ReadPngPalette(inputPath, &palette);
+ WriteJascPalette(outputPath, &palette);
+}
+
void HandlePngToGbaPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
- struct Palette palette;
+ struct Palette palette = {};
ReadPngPalette(inputPath, &palette);
WriteGbaPalette(outputPath, &palette);
@@ -208,7 +255,7 @@ void HandlePngToGbaPaletteCommand(char *inputPath, char *outputPath, int argc UN
void HandleGbaToJascPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
- struct Palette palette;
+ struct Palette palette = {};
ReadGbaPalette(inputPath, &palette);
WriteJascPalette(outputPath, &palette);
@@ -241,7 +288,7 @@ void HandleJascToGbaPaletteCommand(char *inputPath, char *outputPath, int argc,
}
}
- struct Palette palette;
+ struct Palette palette = {};
ReadJascPalette(inputPath, &palette);
@@ -254,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);
@@ -264,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;
@@ -276,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);
@@ -286,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;
@@ -298,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);
@@ -308,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;
@@ -483,6 +536,8 @@ void HandleHuffDecompressCommand(char *inputPath, char *outputPath, int argc UNU
int main(int argc, char **argv)
{
+ char converted = 0;
+
if (argc < 3)
FATAL_ERROR("Usage: gbagfx INPUT_PATH OUTPUT_PATH [options...]\n");
@@ -495,6 +550,7 @@ int main(int argc, char **argv)
{ "png", "4bpp", HandlePngToGbaCommand },
{ "png", "8bpp", HandlePngToGbaCommand },
{ "png", "gbapal", HandlePngToGbaPaletteCommand },
+ { "png", "pal", HandlePngToJascPaletteCommand },
{ "gbapal", "pal", HandleGbaToJascPaletteCommand },
{ "pal", "gbapal", HandleJascToGbaPaletteCommand },
{ "latfont", "png", HandleLatinFontToPngCommand },
@@ -514,14 +570,39 @@ int main(int argc, char **argv)
char *inputPath = argv[1];
char *outputPath = argv[2];
- char *inputFileExtension = GetFileExtension(inputPath);
- char *outputFileExtension = GetFileExtension(outputPath);
+ char *inputFileExtension = GetFileExtensionAfterDot(inputPath);
+ char *outputFileExtension = GetFileExtensionAfterDot(outputPath);
if (inputFileExtension == NULL)
FATAL_ERROR("Input file \"%s\" has no extension.\n", inputPath);
if (outputFileExtension == NULL)
- FATAL_ERROR("Output file \"%s\" has no extension.\n", outputPath);
+ {
+ outputFileExtension = GetFileExtension(outputPath);
+
+ if (*outputFileExtension == '.')
+ outputFileExtension++;
+
+ if (*outputFileExtension == 0)
+ FATAL_ERROR("Output file \"%s\" has no extension.\n", outputPath);
+
+ size_t newOutputPathSize = strlen(inputPath) - strlen(inputFileExtension) + strlen(outputFileExtension);
+ outputPath = malloc(newOutputPathSize);
+
+ if (outputPath == NULL)
+ FATAL_ERROR("Failed to allocate memory for new output path.\n");
+
+ for (int i = 0; i < newOutputPathSize; i++)
+ {
+ outputPath[i] = inputPath[i];
+
+ if (outputPath[i] == '.')
+ {
+ strcpy(&outputPath[i + 1], outputFileExtension);
+ break;
+ }
+ }
+ }
for (int i = 0; handlers[i].function != NULL; i++)
{
@@ -529,9 +610,16 @@ int main(int argc, char **argv)
&& (handlers[i].outputFileExtension == NULL || strcmp(handlers[i].outputFileExtension, outputFileExtension) == 0))
{
handlers[i].function(inputPath, outputPath, argc, argv);
- return 0;
+ converted = 1;
+ break;
}
}
- FATAL_ERROR("Don't know how to convert \"%s\" to \"%s\".\n", inputPath, outputPath);
+ if (outputPath != argv[2])
+ free(outputPath);
+
+ if (!converted)
+ FATAL_ERROR("Don't know how to convert \"%s\" to \"%s\".\n", argv[1], argv[2]);
+
+ return 0;
}
diff --git a/tools/gbagfx/options.h b/tools/gbagfx/options.h
index 2ff3967..3b038f5 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
diff --git a/tools/gbagfx/util.c b/tools/gbagfx/util.c
index 87abeb3..7350376 100644
--- a/tools/gbagfx/util.c
+++ b/tools/gbagfx/util.c
@@ -47,6 +47,13 @@ char *GetFileExtension(char *path)
while (extension > path && *extension != '.')
extension--;
+ return extension;
+}
+
+char *GetFileExtensionAfterDot(char *path)
+{
+ char *extension = GetFileExtension(path);
+
if (extension == path)
return NULL;
diff --git a/tools/gbagfx/util.h b/tools/gbagfx/util.h
index 6d7a9c2..7802f1d 100644
--- a/tools/gbagfx/util.h
+++ b/tools/gbagfx/util.h
@@ -7,6 +7,7 @@
bool ParseNumber(char *s, char **end, int radix, int *intValue);
char *GetFileExtension(char *path);
+char *GetFileExtensionAfterDot(char *path);
unsigned char *ReadWholeFile(char *path, int *size);
unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount);
void WriteWholeFile(char *path, void *buffer, int bufferSize);