gfx: Add mirrored tile check when generating tilemap

This commit is contained in:
Quint Guvernator
2019-05-19 20:16:47 +02:00
parent b2c1f6122e
commit 21aea281bd
7 changed files with 298 additions and 60 deletions

View File

@@ -36,6 +36,8 @@ Other contributors
- The OpenBSD Project <http://www.openbsd.org>
- Quint Guvernator <quint@guvernator.net>
- Sanqui <gsanky@gmail.com>
- YamaArashi <shadow962@live.com>

View File

@@ -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);

View File

@@ -21,10 +21,13 @@ struct Options {
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 RGBColor {
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 @@ struct GBImage {
int trim;
};
struct Tilemap {
struct Mapfile {
uint8_t *data;
int size;
};

View File

@@ -85,8 +85,87 @@ int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles, int tile_size)
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;
@@ -94,6 +173,7 @@ void create_tilemap(const struct Options *opts, struct GBImage *gb,
int max_tiles;
int num_tiles;
int index;
int flags;
int gb_size;
uint8_t *tile;
uint8_t **tiles;
@@ -109,19 +189,33 @@ void create_tilemap(const struct Options *opts, struct GBImage *gb,
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];
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 @@ void create_tilemap(const struct Options *opts, struct GBImage *gb,
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 create_tilemap(const struct Options *opts, struct GBImage *gb,
}
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,

View File

@@ -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,21 +30,29 @@ int main(int argc, char *argv[])
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.";
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 @@ int main(int argc, char *argv[])
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 @@ int main(int argc, char *argv[])
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 @@ int main(int argc, char *argv[])
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,25 +161,45 @@ int main(int argc, char *argv[])
(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)
@@ -189,19 +222,35 @@ int main(int argc, char *argv[])
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");
}
}
@@ -226,17 +275,20 @@ int main(int argc, char *argv[])
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);

View File

@@ -649,10 +649,16 @@ static void get_text(const struct PNGImage *img,
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;
@@ -699,18 +705,30 @@ static void set_text(const struct PNGImage *img,
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);
}
if (*png_options->palfile) {
text[0].key = "p";
text[0].text = "";

View File

@@ -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 @@ The input image may not contain more colors than the selected bit depth
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 @@ The bit depth of the output image (either 1 or 2).
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 @@ Same as
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
@@ -105,6 +132,14 @@ The following creates a planar 2bpp file with only unique tiles, and its 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
.D1 $ rgbgfx in.png