ref: 21aea281bdd1a5b8aa0cb4cc73c58b3b9e86b9e7
parent: b2c1f6122eb5ed5a9070c8cf0450f0cf826f6edd
author: Quint Guvernator <quint@guvernator.net>
date: Sun May 19 16:16:47 EDT 2019
gfx: Add mirrored tile check when generating tilemap
--- a/CONTRIBUTORS.rst
+++ b/CONTRIBUTORS.rst
@@ -36,6 +36,8 @@
- The OpenBSD Project <http://www.openbsd.org>
+- Quint Guvernator <quint@guvernator.net>
+
- Sanqui <gsanky@gmail.com>
- YamaArashi <shadow962@live.com>
--- a/include/gfx/gb.h
+++ b/include/gfx/gb.h
@@ -12,14 +12,24 @@
#include <stdint.h>
#include "gfx/main.h"
+#define XFLIP 0x40
+#define YFLIP 0x20
+
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb);
void output_file(const struct Options *opts, const struct GBImage *gb);
int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
int tile_size);
-void create_tilemap(const struct Options *opts, struct GBImage *gb,
- struct Tilemap *tilemap);
+uint8_t reverse_bits(uint8_t b);
+void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size);
+void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size);
+int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
+ int tile_size, int *flags);
+void create_mapfiles(const struct Options *opts, struct GBImage *gb,
+ struct Mapfile *tilemap, struct Mapfile *attrmap);
void output_tilemap_file(const struct Options *opts,
- const struct Tilemap *tilemap);
+ const struct Mapfile *tilemap);
+void output_attrmap_file(const struct Options *opts,
+ const struct Mapfile *attrmap);
void output_palette_file(const struct Options *opts,
const struct RawIndexedImage *raw_image);
--- a/include/gfx/main.h
+++ b/include/gfx/main.h
@@ -21,10 +21,13 @@
bool hardfix;
bool fix;
bool horizontal;
+ bool mirror;
bool unique;
int trim;
- char *mapfile;
- bool mapout;
+ char *tilemapfile;
+ bool tilemapout;
+ char *attrmapfile;
+ bool attrmapout;
char *palfile;
bool palout;
char *outfile;
@@ -40,8 +43,10 @@
struct ImageOptions {
bool horizontal;
int trim;
- char *mapfile;
- bool mapout;
+ char *tilemapfile;
+ bool tilemapout;
+ char *attrmapfile;
+ bool attrmapout;
char *palfile;
bool palout;
};
@@ -71,7 +76,7 @@
int trim;
};
-struct Tilemap {
+struct Mapfile {
uint8_t *data;
int size;
};
--- a/src/gfx/gb.c
+++ b/src/gfx/gb.c
@@ -85,9 +85,88 @@
return -1;
}
-void create_tilemap(const struct Options *opts, struct GBImage *gb,
- struct Tilemap *tilemap)
+uint8_t reverse_bits(uint8_t b)
{
+ uint8_t rev = 0;
+
+ rev |= (b & 0x80) >> 7;
+ rev |= (b & 0x40) >> 5;
+ rev |= (b & 0x20) >> 3;
+ rev |= (b & 0x10) >> 1;
+ rev |= (b & 0x08) << 1;
+ rev |= (b & 0x04) << 3;
+ rev |= (b & 0x02) << 5;
+ rev |= (b & 0x01) << 7;
+ return rev;
+}
+
+void xflip(uint8_t *tile, uint8_t *tile_xflip, int tile_size)
+{
+ int i;
+
+ for (i = 0; i < tile_size; i++)
+ tile_xflip[i] = reverse_bits(tile[i]);
+}
+
+void yflip(uint8_t *tile, uint8_t *tile_yflip, int tile_size)
+{
+ int i;
+
+ for (i = 0; i < tile_size; i++)
+ tile_yflip[i] = tile[(tile_size - i - 1) ^ (depth - 1)];
+}
+
+/*
+ * get_mirrored_tile_index looks for `tile` in tile array `tiles`, also
+ * checking x-, y-, and xy-mirrored versions of `tile`. If one is found,
+ * `*flags` is set according to the type of mirroring and the index of the
+ * matched tile is returned. If no match is found, -1 is returned.
+ */
+int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
+ int tile_size, int *flags)
+{
+ int index;
+ uint8_t *tile_xflip;
+ uint8_t *tile_yflip;
+
+ index = get_tile_index(tile, tiles, num_tiles, tile_size);
+ if (index >= 0) {
+ *flags = 0;
+ return index;
+ }
+
+ tile_yflip = malloc(tile_size);
+ yflip(tile, tile_yflip, tile_size);
+ index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
+ if (index >= 0) {
+ *flags = YFLIP;
+ free(tile_yflip);
+ return index;
+ }
+
+ tile_xflip = malloc(tile_size);
+ xflip(tile, tile_xflip, tile_size);
+ index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size);
+ if (index >= 0) {
+ *flags = XFLIP;
+ free(tile_yflip);
+ free(tile_xflip);
+ return index;
+ }
+
+ yflip(tile_xflip, tile_yflip, tile_size);
+ index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
+ if (index >= 0)
+ *flags = XFLIP | YFLIP;
+
+ free(tile_yflip);
+ free(tile_xflip);
+ return index;
+}
+
+void create_mapfiles(const struct Options *opts, struct GBImage *gb,
+ struct Mapfile *tilemap, struct Mapfile *attrmap)
+{
int i, j;
int gb_i;
int tile_size;
@@ -94,6 +173,7 @@
int max_tiles;
int num_tiles;
int index;
+ int flags;
int gb_size;
uint8_t *tile;
uint8_t **tiles;
@@ -109,11 +189,20 @@
tiles = calloc(max_tiles, sizeof(uint8_t *));
num_tiles = 0;
- tilemap->data = calloc(max_tiles, sizeof(uint8_t));
- tilemap->size = 0;
+ if (*opts->tilemapfile) {
+ tilemap->data = calloc(max_tiles, sizeof(uint8_t));
+ tilemap->size = 0;
+ }
+ if (*opts->attrmapfile) {
+ attrmap->data = calloc(max_tiles, sizeof(uint8_t));
+ attrmap->size = 0;
+ }
+
+
gb_i = 0;
while (gb_i < gb_size) {
+ flags = 0;
tile = malloc(tile_size);
for (i = 0; i < tile_size; i++) {
tile[i] = gb->data[gb_i];
@@ -120,8 +209,13 @@
gb_i++;
}
if (opts->unique) {
- index = get_tile_index(tile, tiles, num_tiles,
- tile_size);
+ if (opts->mirror) {
+ index = get_mirrored_tile_index(tile, tiles, num_tiles,
+ tile_size, &flags);
+ } else {
+ index = get_tile_index(tile, tiles, num_tiles,
+ tile_size);
+ }
if (index < 0) {
index = num_tiles;
tiles[num_tiles] = tile;
@@ -132,8 +226,14 @@
tiles[num_tiles] = tile;
num_tiles++;
}
- tilemap->data[tilemap->size] = index;
- tilemap->size++;
+ if (*opts->tilemapfile) {
+ tilemap->data[tilemap->size] = index;
+ tilemap->size++;
+ }
+ if (*opts->attrmapfile) {
+ attrmap->data[attrmap->size] = flags;
+ attrmap->size++;
+ }
}
if (opts->unique) {
@@ -154,19 +254,35 @@
}
void output_tilemap_file(const struct Options *opts,
- const struct Tilemap *tilemap)
+ const struct Mapfile *tilemap)
{
FILE *f;
- f = fopen(opts->mapfile, "wb");
+ f = fopen(opts->tilemapfile, "wb");
if (!f)
- err(1, "Opening tilemap file '%s' failed", opts->mapfile);
+ err(1, "Opening tilemap file '%s' failed", opts->tilemapfile);
fwrite(tilemap->data, 1, tilemap->size, f);
fclose(f);
- if (opts->mapout)
- free(opts->mapfile);
+ if (opts->tilemapout)
+ free(opts->tilemapfile);
+}
+
+void output_attrmap_file(const struct Options *opts,
+ const struct Mapfile *attrmap)
+{
+ FILE *f;
+
+ f = fopen(opts->attrmapfile, "wb");
+ if (!f)
+ err(1, "Opening attrmap file '%s' failed", opts->attrmapfile);
+
+ fwrite(attrmap->data, 1, attrmap->size, f);
+ fclose(f);
+
+ if (opts->attrmapout)
+ free(opts->attrmapfile);
}
void output_palette_file(const struct Options *opts,
--- a/src/gfx/main.c
+++ b/src/gfx/main.c
@@ -18,8 +18,8 @@
static void print_usage(void)
{
printf(
-"usage: rgbgfx [-DFfhPTuVv] [-d #] [-o outfile] [-p palfile] [-t mapfile]\n"
-" [-x #] infile\n");
+"usage: rgbgfx [-ADFfhmPTuVv] [-o outfile] [-a attrmap] [-d #] [-p palfile]\n"
+" [-t tilemap] [-x #] infile\n");
exit(1);
}
@@ -30,7 +30,8 @@
struct ImageOptions png_options = {0};
struct RawIndexedImage *raw_image;
struct GBImage gb = {0};
- struct Tilemap tilemap = {0};
+ struct Mapfile tilemap = {0};
+ struct Mapfile attrmap = {0};
char *ext;
const char *errmsg = "Warning: The PNG's %s setting is not the same as the setting defined on the command line.";
@@ -37,14 +38,21 @@
if (argc == 1)
print_usage();
- opts.mapfile = "";
+ opts.tilemapfile = "";
+ opts.attrmapfile = "";
opts.palfile = "";
opts.outfile = "";
depth = 2;
- while ((ch = getopt(argc, argv, "Dd:Ffho:Tt:uPp:Vvx:")) != -1) {
+ while ((ch = getopt(argc, argv, "Aa:Dd:Ffhmo:Tt:uPp:Vvx:")) != -1) {
switch (ch) {
+ case 'A':
+ opts.attrmapout = true;
+ break;
+ case 'a':
+ opts.attrmapfile = optarg;
+ break;
case 'D':
opts.debug = true;
break;
@@ -60,6 +68,10 @@
case 'h':
opts.horizontal = true;
break;
+ case 'm':
+ opts.mirror = true;
+ opts.unique = true;
+ break;
case 'o':
opts.outfile = optarg;
break;
@@ -70,10 +82,10 @@
opts.palfile = optarg;
break;
case 'T':
- opts.mapout = true;
+ opts.tilemapout = true;
break;
case 't':
- opts.mapfile = optarg;
+ opts.tilemapfile = optarg;
break;
case 'u':
opts.unique = true;
@@ -107,7 +119,8 @@
raw_image = input_png_file(&opts, &png_options);
- png_options.mapfile = "";
+ png_options.tilemapfile = "";
+ png_options.attrmapfile = "";
png_options.palfile = "";
if (png_options.horizontal != opts.horizontal) {
@@ -148,26 +161,46 @@
(raw_image->width / 8) * (raw_image->height / 8) - 1);
}
- if (strcmp(png_options.mapfile, opts.mapfile) != 0) {
+ if (strcmp(png_options.tilemapfile, opts.tilemapfile) != 0) {
if (opts.verbose)
warnx(errmsg, "tilemap file");
if (opts.hardfix)
- png_options.mapfile = opts.mapfile;
+ png_options.tilemapfile = opts.tilemapfile;
}
- if (!*opts.mapfile)
- opts.mapfile = png_options.mapfile;
+ if (!*opts.tilemapfile)
+ opts.tilemapfile = png_options.tilemapfile;
- if (png_options.mapout != opts.mapout) {
+ if (png_options.tilemapout != opts.tilemapout) {
if (opts.verbose)
warnx(errmsg, "tilemap file");
if (opts.hardfix)
- png_options.mapout = opts.mapout;
+ png_options.tilemapout = opts.tilemapout;
}
- if (png_options.mapout)
- opts.mapout = png_options.mapout;
+ if (png_options.tilemapout)
+ opts.tilemapout = png_options.tilemapout;
+ if (strcmp(png_options.attrmapfile, opts.attrmapfile) != 0) {
+ if (opts.verbose)
+ warnx(errmsg, "attrmap file");
+
+ if (opts.hardfix)
+ png_options.attrmapfile = opts.attrmapfile;
+ }
+ if (!*opts.attrmapfile)
+ opts.attrmapfile = png_options.attrmapfile;
+
+ if (png_options.attrmapout != opts.attrmapout) {
+ if (opts.verbose)
+ warnx(errmsg, "attrmap file");
+
+ if (opts.hardfix)
+ png_options.attrmapout = opts.attrmapout;
+ }
+ if (png_options.attrmapout)
+ opts.attrmapout = png_options.attrmapout;
+
if (strcmp(png_options.palfile, opts.palfile) != 0) {
if (opts.verbose)
warnx(errmsg, "palette file");
@@ -189,22 +222,38 @@
if (png_options.palout)
opts.palout = png_options.palout;
- if (!*opts.mapfile && opts.mapout) {
+ if (!*opts.tilemapfile && opts.tilemapout) {
ext = strrchr(opts.infile, '.');
if (ext != NULL) {
size = ext - opts.infile + 9;
- opts.mapfile = malloc(size);
- strncpy(opts.mapfile, opts.infile, size);
- *strrchr(opts.mapfile, '.') = '\0';
- strcat(opts.mapfile, ".tilemap");
+ opts.tilemapfile = malloc(size);
+ strncpy(opts.tilemapfile, opts.infile, size);
+ *strrchr(opts.tilemapfile, '.') = '\0';
+ strcat(opts.tilemapfile, ".tilemap");
} else {
- opts.mapfile = malloc(strlen(opts.infile) + 9);
- strcpy(opts.mapfile, opts.infile);
- strcat(opts.mapfile, ".tilemap");
+ opts.tilemapfile = malloc(strlen(opts.infile) + 9);
+ strcpy(opts.tilemapfile, opts.infile);
+ strcat(opts.tilemapfile, ".tilemap");
}
}
+ if (!*opts.attrmapfile && opts.attrmapout) {
+ ext = strrchr(opts.infile, '.');
+
+ if (ext != NULL) {
+ size = ext - opts.infile + 9;
+ opts.attrmapfile = malloc(size);
+ strncpy(opts.attrmapfile, opts.infile, size);
+ *strrchr(opts.attrmapfile, '.') = '\0';
+ strcat(opts.attrmapfile, ".attrmap");
+ } else {
+ opts.attrmapfile = malloc(strlen(opts.infile) + 9);
+ strcpy(opts.attrmapfile, opts.infile);
+ strcat(opts.attrmapfile, ".attrmap");
+ }
+ }
+
if (!*opts.palfile && opts.palout) {
ext = strrchr(opts.infile, '.');
@@ -226,16 +275,19 @@
gb.trim = opts.trim;
gb.horizontal = opts.horizontal;
- if (*opts.outfile || *opts.mapfile) {
+ if (*opts.outfile || *opts.tilemapfile || *opts.attrmapfile) {
raw_to_gb(raw_image, &gb);
- create_tilemap(&opts, &gb, &tilemap);
+ create_mapfiles(&opts, &gb, &tilemap, &attrmap);
}
if (*opts.outfile)
output_file(&opts, &gb);
- if (*opts.mapfile)
+ if (*opts.tilemapfile)
output_tilemap_file(&opts, &tilemap);
+
+ if (*opts.attrmapfile)
+ output_attrmap_file(&opts, &attrmap);
if (*opts.palfile)
output_palette_file(&opts, raw_image);
--- a/src/gfx/makepng.c
+++ b/src/gfx/makepng.c
@@ -649,11 +649,17 @@
png_options->trim = strtoul(text[i].text, NULL, 0);
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "t") == 0) {
- png_options->mapfile = text[i].text;
+ png_options->tilemapfile = text[i].text;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "T") == 0 && !*text[i].text) {
- png_options->mapout = true;
+ png_options->tilemapout = true;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
+ } else if (strcmp(text[i].key, "a") == 0) {
+ png_options->attrmapfile = text[i].text;
+ png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
+ } else if (strcmp(text[i].key, "A") == 0 && !*text[i].text) {
+ png_options->attrmapout = true;
+ png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "p") == 0) {
png_options->palfile = text[i].text;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
@@ -699,14 +705,26 @@
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
- if (*png_options->mapfile) {
+ if (*png_options->tilemapfile) {
text[0].key = "t";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
}
- if (png_options->mapout) {
+ if (png_options->tilemapout) {
text[0].key = "T";
+ text[0].text = "";
+ text[0].compression = PNG_TEXT_COMPRESSION_NONE;
+ png_set_text(img->png, img->info, text, 1);
+ }
+ if (*png_options->attrmapfile) {
+ text[0].key = "a";
+ text[0].text = "";
+ text[0].compression = PNG_TEXT_COMPRESSION_NONE;
+ png_set_text(img->png, img->info, text, 1);
+ }
+ if (png_options->attrmapout) {
+ text[0].key = "A";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(img->png, img->info, text, 1);
--- a/src/gfx/rgbgfx.1
+++ b/src/gfx/rgbgfx.1
@@ -13,11 +13,12 @@
.Nd Game Boy graphics converter
.Sh SYNOPSIS
.Nm rgbgfx
-.Op Fl DfFhPTVv
+.Op Fl ADfFhmPTuVv
.Op Fl o Ar outfile
+.Op Fl a Ar attrmap
.Op Fl d Ar depth
.Op Fl p Ar palfile
-.Op Fl t Ar mapfile
+.Op Fl t Ar tilemap
.Op Fl x Ar tiles
.Ar file
.Sh DESCRIPTION
@@ -47,6 +48,19 @@
allows. Transparent pixels are set to palette index 0.
.Sh ARGUMENTS
.Bl -tag -width Ds
+.It Fl a Ar attrmap
+Generate a file of tile mirroring attributes for OAM or (CGB-only) background
+tiles. For each tile in the input file, a byte is written representing the
+dimensions that the associated tile in the output file should be mirrored.
+Useful in combination with
+.Fl m
+to keep track the mirror direction of mirrored duplicate tiles.
+.It Fl A
+Same as
+.Fl a ,
+but the attrmap file output name is made by taking the input filename, removing
+the file extension, and appending
+.Pa .attrmap .
.It Fl D
Debug features are enabled.
.It Fl f
@@ -61,6 +75,12 @@
By default, the bit depth is 2 (two bits per pixel).
.It Fl h
Lay out tiles horizontally rather than vertically.
+.It Fl m
+Truncate tiles by checking for tiles that are mirrored versions of others and
+omitting these from the output file. Useful with tilemaps and attrmaps together
+to keep track of the duplicated tiles and the dimension mirrored. Tiles are
+checked for horizontal, vertical, and horizontal-vertical mirroring. Implies
+.Fl u .
.It Fl o Ar outfile
The name of the output file.
.It Fl p Ar palfile
@@ -74,17 +94,24 @@
but the palette file output name is made by taking the input PNG file's
filename, removing the file extension, and appending
.Pa .pal .
-.It Fl t Ar mapfile
-If any tiles are the same, don't place the repeat tiles in the output file, and
-make a tilemap file.
+.It Fl t Ar tilemap
+Generate a file of tile indices. For each tile in the input file, a byte is
+written representing the index of the associated tile in the output file.
+Useful in combination with
+.Fl u
+or
+.Fl m
+to keep track of duplicate tiles.
.It Fl T
Same as
.Fl t ,
-but the tilemap file output name is made by taking the input filename,
-removing the file extension, and appending
+but the tilemap file output name is made by taking the input filename, removing
+the file extension, and appending
.Pa .tilemap .
.It Fl u
-Truncate repeated tiles. Useful with tilemaps.
+Truncate tiles by checking for tiles that are exact duplicates of others and
+omitting these from the output file. Useful with tilemaps to keep track of the
+duplicated tiles.
.It Fl V
Print the version of the program and exit.
.It Fl v
@@ -104,6 +131,14 @@
.Pa out.tilemap :
.Pp
.D1 $ rgbgfx -T -u -o out.2bpp in.png
+.Pp
+The following creates a planar 2bpp file with only unique tiles (accounting for
+tile mirroring) and its associated tilemap
+.Pa out.tilemap
+and attrmap
+.Pa out.attrmap :
+.Pp
+.D1 $ rgbgfx -A -T -m -o out.2bpp in.png
.Pp
The following will do nothing:
.Pp