Implement rgbgfx -O (#1240)

This commit is contained in:
Rangi
2023-11-21 10:19:44 -05:00
committed by GitHub
parent 99671b8eb5
commit 083a82f6d1
4 changed files with 80 additions and 51 deletions

View File

@@ -10,6 +10,7 @@ _rgbgfx_completions() {
[V]="version:normal"
[C]="color-curve:normal"
[m]="mirror-tiles:normal"
[O]="group-outputs:normal"
[u]="unique-tiles:normal"
[v]="verbose:normal"
[Z]="columns:normal"

View File

@@ -16,6 +16,7 @@ local args=(
'(-a --attr-map -A --output-attr-map)'{-A,--output-attr-map}'[Shortcut for -a <file>.attrmap]'
'(-C --color-curve)'{-C,--color-curve}'[Generate palettes using GBC color curve]'
'(-m --mirror-tiles)'{-m,--mirror-tiles}'[Eliminate mirrored tiles from output]'
'(-O --group-outputs)'{-O,--group-outputs}'[Base "shortcut" options on the output path, not input]'
'(-p --palette -P --output-palette)'{-P,--output-palette}'[Shortcut for -p <file>.pal]'
'(-q --palette-map -Q --output-palette-map)'{-Q,--output-palette-map}'[Shortcut for -p <file>.palmap]'
'(-t --tilemap -T --output-tilemap)'{-T,--output-tilemap}'[Shortcut for -t <file>.tilemap]'

View File

@@ -10,7 +10,7 @@
.Nd Game Boy graphics converter
.Sh SYNOPSIS
.Nm
.Op Fl CmuVZ
.Op Fl CmOuVZ
.Op Fl v Op Fl v No ...
.Op Fl a Ar attrmap | Fl A
.Op Fl b Ar base_ids
@@ -100,11 +100,8 @@ follows the same order
and has the same size.
.It Fl A , Fl Fl output-attr-map
Same as
.Fl a Ar path ,
where
.Ar path
is the input image's path with the extension set to
.Pa .attrmap .
.Fl a Ar base_path Ns .attrmap
.Pq see Sx Automatic output paths .
.It Fl b Ar base_ids , Fl Fl base-tiles Ar base_ids
Set the base IDs for tile map output.
.Ar base_ids
@@ -207,6 +204,13 @@ This may not be more than 256.
.Pp
Note that attribute map output only has 3 bits for the palette ID, so a limit higher than 8 may yield incomplete data unless relying on a palette map
.Pq see Fl q .
.It Fl O , Fl Fl group-outputs
Sets the
.Sq base path
to be the output tile data path from
.Fl o
instead of the input image path
.Pq see Sx Automatic output paths .
.It Fl o Ar out_file , Fl Fl output Ar out_file
Output the tile data in native 2bpp format or in 1bpp
.Pq depending on Fl d
@@ -215,21 +219,15 @@ to this file.
Output the image's palette set to this file.
.It Fl P , Fl Fl output-palette
Same as
.Fl p Ar path ,
where
.Ar path
is the input image's path with the extension set to
.Pa .pal .
.Fl p Ar base_path Ns .pal
.Pq see Sx Automatic output paths .
.It Fl q Ar pal_file , Fl Fl palette-map Ar pal_file
Output the image's palette map to this file.
This is useful if the input image contains more than 8 palettes, as the attribute map only contains the lower 3 bits of the palette indices.
.It Fl Q , Fl Fl output-palette-map
Same as
.Fl q Ar path ,
where
.Ar path
is the input image's path with the extension set to
.Pa .palmap .
.Fl q Ar base_path Ns .palmap
.Pq see Sx Automatic output paths .
.It Fl r Ar width , Fl Fl reverse Ar width
Switches
.Nm
@@ -264,11 +262,8 @@ and/or
to keep track of duplicate tiles.
.It Fl T , Fl Fl output-tilemap
Same as
.Fl t Ar path ,
where
.Ar path
is the input image's path with the extension set to
.Pa .tilemap .
.Fl t Ar base_path Ns .tilemap
.Pq see Sx Automatic output paths .
.It Fl u , Fl Fl unique-tiles
Deduplicate identical tiles. Only one of each unique tile will be saved in the tile data file.
Useful with a tile map
@@ -533,6 +528,28 @@ The contents of individual bytes follows the GBC's native format:
.El
.Pp
Note that if more than 8 palettes are used, only the lowest 3 bits of the palette ID are output.
.Ss Automatic output paths
For convenience,
.Nm
provides shortcuts to generate all files in the same directory.
This is done by using the uppercase version of a flag
.Pq for example, Fl A No instead of Fl a .
The
.Ar base_path
is the input image path
.Pq or the output tile data path from Fl o , No if Fl O No was given
with its extension, if any, removed.
.Pp
For example, these two are equivalent:
.Bd -literal -indent Ds
$ rgbgfx img/player.png -o build/player.2bpp -P
$ rgbgfx img/player.png -o build/player.2bpp -p img/player.pal
.Pp
And so are these two:
.Bd -literal -indent Ds
$ rgbgfx img/player.png -o build/player.2bpp -O -P
$ rgbgfx img/player.png -o build/player.2bpp -p build/player.pal
.El
.Sh REVERSE MODE
.Nm
can produce a PNG image from valid data.

View File

@@ -34,7 +34,16 @@
using namespace std::literals::string_view_literals;
Options options;
char const *externalPalSpec = nullptr;
static struct LocalOptions {
char const *externalPalSpec;
bool autoAttrmap;
bool autoTilemap;
bool autoPalettes;
bool autoPalmap;
bool groupOutputs;
} localOptions;
static uintmax_t nbErrors;
[[noreturn]] void giveUp() {
@@ -91,7 +100,7 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
}
// Short options
static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:o:Pp:Qq:r:s:Tt:U:uVvx:Z";
static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
/*
* Equivalent long options
@@ -118,6 +127,7 @@ static struct option const longopts[] = {
{"mirror-tiles", no_argument, NULL, 'm'},
{"nb-tiles", required_argument, NULL, 'N'},
{"nb-palettes", required_argument, NULL, 'n'},
{"group-outputs", no_argument, NULL, 'O'},
{"output", required_argument, NULL, 'o'},
{"output-palette", no_argument, NULL, 'P'},
{"palette", required_argument, NULL, 'p'},
@@ -136,7 +146,7 @@ static struct option const longopts[] = {
};
static void printUsage(void) {
fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
fputs("Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
@@ -321,23 +331,21 @@ static std::vector<size_t> readAtFile(std::filesystem::path const &path,
}
/*
* Parses an arg vector, modifying `options` as options are read.
* The three booleans are for the "auto path" flags, since their processing must be deferred to the
* end of option parsing.
* Parses an arg vector, modifying `options` and `localOptions` as options are read.
* The `localOptions` struct is for flags which must be processed after the option parsing finishes.
*
* Returns NULL if the vector was fully parsed, or a pointer (which is part of the arg vector) to an
* "at-file" path if one is encountered.
*/
static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap,
bool &autoPalettes, bool &autoPalmap) {
static char *parseArgv(int argc, char **argv) {
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
char *arg = musl_optarg; // Make a copy for scanning
switch (ch) {
case 'A':
autoAttrmap = true;
localOptions.autoAttrmap = true;
break;
case 'a':
autoAttrmap = false;
localOptions.autoAttrmap = false;
if (options.attrmap.has_value())
warning("Overriding attrmap file %s", options.attrmap->c_str());
options.attrmap = musl_optarg;
@@ -385,7 +393,7 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
// size to be split; thus, we defer that
// TODO: this does not validate the `fmt` part of any external spec but the last
// one, but I guess that's okay
externalPalSpec = musl_optarg;
localOptions.externalPalSpec = musl_optarg;
}
break;
case 'd':
@@ -481,25 +489,28 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
error("Number of palettes (-n) may not be 0!");
}
break;
case 'O':
localOptions.groupOutputs = true;
break;
case 'o':
if (options.output.has_value())
warning("Overriding tile data file %s", options.output->c_str());
options.output = musl_optarg;
break;
case 'P':
autoPalettes = true;
localOptions.autoPalettes = true;
break;
case 'p':
autoPalettes = false;
localOptions.autoPalettes = false;
if (options.palettes.has_value())
warning("Overriding palettes file %s", options.palettes->c_str());
options.palettes = musl_optarg;
break;
case 'Q':
autoPalmap = true;
localOptions.autoPalmap = true;
break;
case 'q':
autoPalmap = false;
localOptions.autoPalmap = false;
if (options.palmap.has_value())
warning("Overriding palette map file %s", options.palmap->c_str());
options.palmap = musl_optarg;
@@ -525,10 +536,10 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
}
break;
case 'T':
autoTilemap = true;
localOptions.autoTilemap = true;
break;
case 't':
autoTilemap = false;
localOptions.autoTilemap = false;
if (options.tilemap.has_value())
warning("Overriding tilemap file %s", options.tilemap->c_str());
options.tilemap = musl_optarg;
@@ -577,8 +588,6 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
}
int main(int argc, char *argv[]) {
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false;
struct AtFileStackEntry {
int parentInd; // Saved offset into parent argv
std::vector<char *> argv; // This context's arg pointer vec
@@ -592,8 +601,7 @@ int main(int argc, char *argv[]) {
int curArgc = argc;
char **curArgv = argv;
for (;;) {
char *atFileName =
parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap);
char *atFileName = parseArgv(curArgc, curArgv);
if (atFileName) {
// Copy `argv[0]` for error reporting, and because option parsing skips it
AtFileStackEntry &stackEntry =
@@ -649,22 +657,24 @@ int main(int argc, char *argv[]) {
auto autoOutPath = [](bool autoOptEnabled, std::optional<std::filesystem::path> &path,
char const *extension) {
if (autoOptEnabled) {
if (!options.input.has_value()) {
fputs("FATAL: No input image specified\n", stderr);
auto image = localOptions.groupOutputs ? options.output : options.input;
if (!image.has_value()) {
fprintf(stderr, "FATAL: No %s specified\n", localOptions.groupOutputs
? "output tile data file" : "input image");
printUsage();
exit(1);
}
path.emplace(*options.input).replace_extension(extension);
path.emplace(*image).replace_extension(extension);
}
};
autoOutPath(autoAttrmap, options.attrmap, ".attrmap");
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
autoOutPath(autoPalettes, options.palettes, ".pal");
autoOutPath(autoPalmap, options.palmap, ".palmap");
autoOutPath(localOptions.autoAttrmap, options.attrmap, ".attrmap");
autoOutPath(localOptions.autoTilemap, options.tilemap, ".tilemap");
autoOutPath(localOptions.autoPalettes, options.palettes, ".pal");
autoOutPath(localOptions.autoPalmap, options.palmap, ".palmap");
// Execute deferred external pal spec parsing, now that all other params are known
if (externalPalSpec) {
parseExternalPalSpec(externalPalSpec);
if (localOptions.externalPalSpec) {
parseExternalPalSpec(localOptions.externalPalSpec);
}
if (options.verbosity >= Options::VERB_CFG) {