mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Implement "palette map" output
This commit is contained in:
@@ -43,6 +43,7 @@ struct Options {
|
||||
uint8_t nbPalettes = 8; // -n
|
||||
std::string output{}; // -o
|
||||
std::string palettes{}; // -p, -P
|
||||
std::string palmap{}; // -q, -Q
|
||||
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
||||
std::string tilemap{}; // -t, -T
|
||||
std::array<uint16_t, 2> unitSize{1, 1}; // -U (in tiles)
|
||||
|
||||
18
man/rgbgfx.1
18
man/rgbgfx.1
@@ -26,6 +26,7 @@
|
||||
.Op Fl n Ar nb_pals
|
||||
.Op Fl o Ar out_file
|
||||
.Op Fl p Ar pal_file | Fl P
|
||||
.Op Fl q Ar pal_map | Fl Q
|
||||
.Op Fl s Ar nb_colors
|
||||
.Op Fl t Ar tilemap | Fl T
|
||||
.Op Fl U Ar unit_size
|
||||
@@ -72,8 +73,6 @@ All of these are equivalent:
|
||||
.Ql 0X2A ,
|
||||
.Ql 0x2a .
|
||||
.Pp
|
||||
TODO: add "palette map" output.
|
||||
.Pp
|
||||
The following options are accepted:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
|
||||
@@ -182,7 +181,10 @@ is not specified, no limit will be set on the amount of tiles placed in bank 0,
|
||||
Abort if more than
|
||||
.Ar nb_pals
|
||||
palettes are generated.
|
||||
Note that attribute map output only has 3 bits for the palette ID, so a limit higher than 8 may yield incomplete data.
|
||||
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 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
|
||||
@@ -196,6 +198,16 @@ where
|
||||
.Ar path
|
||||
is the input image's path with the extension set to
|
||||
.Pa .pal .
|
||||
.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 .
|
||||
.It Fl s Ar nb_colors , Fl Fl palette-size Ar nb_colors
|
||||
Specify how many colors each palette contains, including the transparent one if any.
|
||||
.Ar nb_colors
|
||||
|
||||
@@ -87,7 +87,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:r:s:Tt:U:uVvx:Z";
|
||||
static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:o:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
@@ -117,6 +117,8 @@ static struct option const longopts[] = {
|
||||
{"output", required_argument, NULL, 'o'},
|
||||
{"output-palette", no_argument, NULL, 'P'},
|
||||
{"palette", required_argument, NULL, 'p'},
|
||||
{"output-palette-map", no_argument, NULL, 'Q'},
|
||||
{"palette-map", required_argument, NULL, 'q'},
|
||||
{"reverse", required_argument, NULL, 'r'},
|
||||
{"output-tilemap", no_argument, NULL, 'T'},
|
||||
{"tilemap", required_argument, NULL, 't'},
|
||||
@@ -132,8 +134,8 @@ static struct option const longopts[] = {
|
||||
static void printUsage(void) {
|
||||
fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b base_ids] [-c color_spec] [-d <depth>] [-L slice] [-N nb_tiles]\n"
|
||||
" [-n nb_pals] [-o <out_file>] [-p <pal_file> | -P] [-s nb_colors]\n"
|
||||
" [-t <tile_map> | -T] [-U unit_size] [-x <tiles>] <file>\n"
|
||||
" [-n nb_pals] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_mal> | -Q ]\n"
|
||||
" [-s nb_colors] [-t <tile_map> | -T] [-U unit_size] [-x <tiles>] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||
" -o, --output <path> set the output binary file\n"
|
||||
@@ -319,7 +321,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
* "at-file" path if one is encountered.
|
||||
*/
|
||||
static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap,
|
||||
bool &autoPalettes) {
|
||||
bool &autoPalettes, bool &autoPalmap) {
|
||||
int opt;
|
||||
|
||||
while ((opt = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1) {
|
||||
@@ -422,12 +424,12 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
options.nbPalettes = parseNumber(arg, "Number of palettes", 8);
|
||||
options.nbPalettes = parseNumber(arg, "Number of palettes", 256);
|
||||
if (*arg != '\0') {
|
||||
error("Number of palettes (-n) must be a valid number, not \"%s\"", musl_optarg);
|
||||
}
|
||||
if (options.nbPalettes > 8) {
|
||||
error("Number of palettes (-n) must not exceed 8!");
|
||||
if (options.nbPalettes > 256) {
|
||||
error("Number of palettes (-n) must not exceed 256!");
|
||||
} else if (options.nbPalettes == 0) {
|
||||
error("Number of palettes (-n) may not be 0!");
|
||||
}
|
||||
@@ -442,6 +444,13 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
||||
autoPalettes = false;
|
||||
options.palettes = musl_optarg;
|
||||
break;
|
||||
case 'Q':
|
||||
autoPalmap = true;
|
||||
break;
|
||||
case 'q':
|
||||
autoPalmap = false;
|
||||
options.palmap = musl_optarg;
|
||||
break;
|
||||
case 'r':
|
||||
options.reversedWidth = parseNumber(arg, "Reversed image stride");
|
||||
if (*arg != '\0') {
|
||||
@@ -512,7 +521,7 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false;
|
||||
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false;
|
||||
|
||||
struct AtFileStackEntry {
|
||||
int parentInd; // Saved offset into parent argv
|
||||
@@ -527,7 +536,8 @@ int main(int argc, char *argv[]) {
|
||||
int curArgc = argc;
|
||||
char **curArgv = argv;
|
||||
for (;;) {
|
||||
char *atFileName = parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes);
|
||||
char *atFileName =
|
||||
parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap);
|
||||
if (atFileName) {
|
||||
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
||||
AtFileStackEntry &stackEntry =
|
||||
@@ -606,6 +616,7 @@ int main(int argc, char *argv[]) {
|
||||
autoOutPath(autoAttrmap, options.attrmap, ".attrmap");
|
||||
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
|
||||
autoOutPath(autoPalettes, options.palettes, ".pal");
|
||||
autoOutPath(autoPalmap, options.palmap, ".palmap");
|
||||
|
||||
// Execute deferred external pal spec parsing, now that all other params are known
|
||||
if (externalPalSpec) {
|
||||
|
||||
@@ -739,7 +739,7 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
|
||||
|
||||
static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
std::optional<std::filebuf> tilemapOutput, attrmapOutput;
|
||||
std::optional<std::filebuf> tilemapOutput, attrmapOutput, palmapOutput;
|
||||
if (!options.tilemap.empty()) {
|
||||
tilemapOutput.emplace();
|
||||
tilemapOutput->open(options.tilemap, std::ios_base::out | std::ios_base::binary);
|
||||
@@ -748,6 +748,10 @@ static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrm
|
||||
attrmapOutput.emplace();
|
||||
attrmapOutput->open(options.attrmap, std::ios_base::out | std::ios_base::binary);
|
||||
}
|
||||
if (!options.palmap.empty()) {
|
||||
palmapOutput.emplace();
|
||||
palmapOutput->open(options.palmap, std::ios_base::out | std::ios_base::binary);
|
||||
}
|
||||
|
||||
uint8_t tileID = 0;
|
||||
uint8_t bank = 0;
|
||||
@@ -765,9 +769,12 @@ static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrm
|
||||
if (attrmapOutput.has_value()) {
|
||||
uint8_t palID = iter->getPalID(mappings) & 7;
|
||||
attrmapOutput->sputc(palID | bank << 3); // The other flags are all 0
|
||||
++iter;
|
||||
}
|
||||
if (palmapOutput.has_value()) {
|
||||
palmapOutput->sputc(iter->getPalID(mappings));
|
||||
}
|
||||
++tileID;
|
||||
++iter;
|
||||
}
|
||||
assert(iter == attrmap.end());
|
||||
}
|
||||
@@ -874,11 +881,21 @@ static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
for (AttrmapEntry const &entry : attrmap) {
|
||||
uint8_t attr = entry.xFlip << 5 | entry.yFlip << 6;
|
||||
attr |= entry.bank << 3;
|
||||
attr |= mappings[entry.protoPaletteID] & 7;
|
||||
attr |= entry.getPalID(mappings) & 7;
|
||||
output.sputc(attr);
|
||||
}
|
||||
}
|
||||
|
||||
static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
std::filebuf output;
|
||||
output.open(options.attrmap, std::ios_base::out | std::ios_base::binary);
|
||||
|
||||
for (AttrmapEntry const &entry : attrmap) {
|
||||
output.sputc(entry.getPalID(mappings));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace optimized
|
||||
|
||||
void process() {
|
||||
@@ -1023,9 +1040,10 @@ contained:;
|
||||
unoptimized::outputTileData(png, attrmap, palettes, mappings);
|
||||
}
|
||||
|
||||
if (!options.tilemap.empty() || !options.attrmap.empty()) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT,
|
||||
"Generating unoptimized tilemap and/or attrmap...\n");
|
||||
if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) {
|
||||
options.verbosePrint(
|
||||
Options::VERB_LOG_ACT,
|
||||
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n");
|
||||
unoptimized::outputMaps(png, attrmap, mappings);
|
||||
}
|
||||
} else {
|
||||
@@ -1052,5 +1070,10 @@ contained:;
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n");
|
||||
optimized::outputAttrmap(attrmap, mappings);
|
||||
}
|
||||
|
||||
if (!options.palmap.empty()) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n");
|
||||
optimized::outputPalmap(attrmap, mappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,9 +178,17 @@ void reverse() {
|
||||
// We do this now for two reasons:
|
||||
// 1. Checking those during the main loop is harmful to optimization, and
|
||||
// 2. It clutters the code more, and it's not in great shape to begin with
|
||||
// TODO
|
||||
}
|
||||
|
||||
// TODO: palette map (overrides attributes)
|
||||
std::optional<DefaultInitVec<uint8_t>> palmap;
|
||||
if (!options.palmap.empty()) {
|
||||
palmap = readInto(options.palmap);
|
||||
if (palmap->size() != nbTileInstances) {
|
||||
fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(),
|
||||
nbTileInstances);
|
||||
}
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
||||
std::filebuf pngFile;
|
||||
@@ -246,6 +254,8 @@ void reverse() {
|
||||
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];
|
||||
}
|
||||
assert(tileID < nbTileInstances); // Should have been checked earlier
|
||||
size_t palID = palmap ? (*palmap)[index] : attribute & 0b111;
|
||||
assert(palID < palettes.size()); // Should be ensured on data read
|
||||
|
||||
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
|
||||
static std::array<uint8_t, 16> const trimmedTile{
|
||||
@@ -255,8 +265,7 @@ void reverse() {
|
||||
uint8_t const *tileData = tileID > nbTileInstances - options.trim
|
||||
? trimmedTile.data()
|
||||
: &tiles[tileID * tileSize];
|
||||
assert((attribute & 0b111) < palettes.size()); // Should be ensured on data read
|
||||
auto const &palette = palettes[attribute & 0b111];
|
||||
auto const &palette = palettes[palID];
|
||||
for (uint8_t y = 0; y < 8; ++y) {
|
||||
// If vertically mirrored, fetch the bytes from the other end
|
||||
uint8_t realY = attribute & 0x40 ? 7 - y : y;
|
||||
|
||||
Reference in New Issue
Block a user