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
|
uint8_t nbPalettes = 8; // -n
|
||||||
std::string output{}; // -o
|
std::string output{}; // -o
|
||||||
std::string palettes{}; // -p, -P
|
std::string palettes{}; // -p, -P
|
||||||
|
std::string palmap{}; // -q, -Q
|
||||||
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
uint8_t nbColorsPerPal = 0; // -s; 0 means "auto" = 1 << bitDepth;
|
||||||
std::string tilemap{}; // -t, -T
|
std::string tilemap{}; // -t, -T
|
||||||
std::array<uint16_t, 2> unitSize{1, 1}; // -U (in tiles)
|
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 n Ar nb_pals
|
||||||
.Op Fl o Ar out_file
|
.Op Fl o Ar out_file
|
||||||
.Op Fl p Ar pal_file | Fl P
|
.Op Fl p Ar pal_file | Fl P
|
||||||
|
.Op Fl q Ar pal_map | Fl Q
|
||||||
.Op Fl s Ar nb_colors
|
.Op Fl s Ar nb_colors
|
||||||
.Op Fl t Ar tilemap | Fl T
|
.Op Fl t Ar tilemap | Fl T
|
||||||
.Op Fl U Ar unit_size
|
.Op Fl U Ar unit_size
|
||||||
@@ -72,8 +73,6 @@ All of these are equivalent:
|
|||||||
.Ql 0X2A ,
|
.Ql 0X2A ,
|
||||||
.Ql 0x2a .
|
.Ql 0x2a .
|
||||||
.Pp
|
.Pp
|
||||||
TODO: add "palette map" output.
|
|
||||||
.Pp
|
|
||||||
The following options are accepted:
|
The following options are accepted:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl a Ar attrmap , Fl Fl attr-map Ar attrmap
|
.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
|
Abort if more than
|
||||||
.Ar nb_pals
|
.Ar nb_pals
|
||||||
palettes are generated.
|
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
|
.It Fl o Ar out_file , Fl Fl output Ar out_file
|
||||||
Output the tile data in native 2bpp format or in 1bpp
|
Output the tile data in native 2bpp format or in 1bpp
|
||||||
.Pq depending on Fl d
|
.Pq depending on Fl d
|
||||||
@@ -196,6 +198,16 @@ where
|
|||||||
.Ar path
|
.Ar path
|
||||||
is the input image's path with the extension set to
|
is the input image's path with the extension set to
|
||||||
.Pa .pal .
|
.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
|
.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.
|
Specify how many colors each palette contains, including the transparent one if any.
|
||||||
.Ar nb_colors
|
.Ar nb_colors
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// 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
|
* Equivalent long options
|
||||||
@@ -117,6 +117,8 @@ static struct option const longopts[] = {
|
|||||||
{"output", required_argument, NULL, 'o'},
|
{"output", required_argument, NULL, 'o'},
|
||||||
{"output-palette", no_argument, NULL, 'P'},
|
{"output-palette", no_argument, NULL, 'P'},
|
||||||
{"palette", required_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'},
|
{"reverse", required_argument, NULL, 'r'},
|
||||||
{"output-tilemap", no_argument, NULL, 'T'},
|
{"output-tilemap", no_argument, NULL, 'T'},
|
||||||
{"tilemap", required_argument, NULL, 't'},
|
{"tilemap", required_argument, NULL, 't'},
|
||||||
@@ -132,8 +134,8 @@ static struct option const longopts[] = {
|
|||||||
static void printUsage(void) {
|
static void printUsage(void) {
|
||||||
fputs("Usage: rgbgfx [-r stride] [-CmuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
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"
|
" [-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"
|
" [-n nb_pals] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_mal> | -Q ]\n"
|
||||||
" [-t <tile_map> | -T] [-U unit_size] [-x <tiles>] <file>\n"
|
" [-s nb_colors] [-t <tile_map> | -T] [-U unit_size] [-x <tiles>] <file>\n"
|
||||||
"Useful options:\n"
|
"Useful options:\n"
|
||||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||||
" -o, --output <path> set the output binary file\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.
|
* "at-file" path if one is encountered.
|
||||||
*/
|
*/
|
||||||
static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap,
|
static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilemap,
|
||||||
bool &autoPalettes) {
|
bool &autoPalettes, bool &autoPalmap) {
|
||||||
int opt;
|
int opt;
|
||||||
|
|
||||||
while ((opt = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1) {
|
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;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
options.nbPalettes = parseNumber(arg, "Number of palettes", 8);
|
options.nbPalettes = parseNumber(arg, "Number of palettes", 256);
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
error("Number of palettes (-n) must be a valid number, not \"%s\"", musl_optarg);
|
error("Number of palettes (-n) must be a valid number, not \"%s\"", musl_optarg);
|
||||||
}
|
}
|
||||||
if (options.nbPalettes > 8) {
|
if (options.nbPalettes > 256) {
|
||||||
error("Number of palettes (-n) must not exceed 8!");
|
error("Number of palettes (-n) must not exceed 256!");
|
||||||
} else if (options.nbPalettes == 0) {
|
} else if (options.nbPalettes == 0) {
|
||||||
error("Number of palettes (-n) may not be 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;
|
autoPalettes = false;
|
||||||
options.palettes = musl_optarg;
|
options.palettes = musl_optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'Q':
|
||||||
|
autoPalmap = true;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
autoPalmap = false;
|
||||||
|
options.palmap = musl_optarg;
|
||||||
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
options.reversedWidth = parseNumber(arg, "Reversed image stride");
|
options.reversedWidth = parseNumber(arg, "Reversed image stride");
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
@@ -512,7 +521,7 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false;
|
bool autoAttrmap = false, autoTilemap = false, autoPalettes = false, autoPalmap = false;
|
||||||
|
|
||||||
struct AtFileStackEntry {
|
struct AtFileStackEntry {
|
||||||
int parentInd; // Saved offset into parent argv
|
int parentInd; // Saved offset into parent argv
|
||||||
@@ -527,7 +536,8 @@ int main(int argc, char *argv[]) {
|
|||||||
int curArgc = argc;
|
int curArgc = argc;
|
||||||
char **curArgv = argv;
|
char **curArgv = argv;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
char *atFileName = parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes);
|
char *atFileName =
|
||||||
|
parseArgv(curArgc, curArgv, autoAttrmap, autoTilemap, autoPalettes, autoPalmap);
|
||||||
if (atFileName) {
|
if (atFileName) {
|
||||||
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
||||||
AtFileStackEntry &stackEntry =
|
AtFileStackEntry &stackEntry =
|
||||||
@@ -606,6 +616,7 @@ int main(int argc, char *argv[]) {
|
|||||||
autoOutPath(autoAttrmap, options.attrmap, ".attrmap");
|
autoOutPath(autoAttrmap, options.attrmap, ".attrmap");
|
||||||
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
|
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
|
||||||
autoOutPath(autoPalettes, options.palettes, ".pal");
|
autoOutPath(autoPalettes, options.palettes, ".pal");
|
||||||
|
autoOutPath(autoPalmap, options.palmap, ".palmap");
|
||||||
|
|
||||||
// Execute deferred external pal spec parsing, now that all other params are known
|
// Execute deferred external pal spec parsing, now that all other params are known
|
||||||
if (externalPalSpec) {
|
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,
|
static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||||
DefaultInitVec<size_t> const &mappings) {
|
DefaultInitVec<size_t> const &mappings) {
|
||||||
std::optional<std::filebuf> tilemapOutput, attrmapOutput;
|
std::optional<std::filebuf> tilemapOutput, attrmapOutput, palmapOutput;
|
||||||
if (!options.tilemap.empty()) {
|
if (!options.tilemap.empty()) {
|
||||||
tilemapOutput.emplace();
|
tilemapOutput.emplace();
|
||||||
tilemapOutput->open(options.tilemap, std::ios_base::out | std::ios_base::binary);
|
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.emplace();
|
||||||
attrmapOutput->open(options.attrmap, std::ios_base::out | std::ios_base::binary);
|
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 tileID = 0;
|
||||||
uint8_t bank = 0;
|
uint8_t bank = 0;
|
||||||
@@ -765,9 +769,12 @@ static void outputMaps(Png const &png, DefaultInitVec<AttrmapEntry> const &attrm
|
|||||||
if (attrmapOutput.has_value()) {
|
if (attrmapOutput.has_value()) {
|
||||||
uint8_t palID = iter->getPalID(mappings) & 7;
|
uint8_t palID = iter->getPalID(mappings) & 7;
|
||||||
attrmapOutput->sputc(palID | bank << 3); // The other flags are all 0
|
attrmapOutput->sputc(palID | bank << 3); // The other flags are all 0
|
||||||
++iter;
|
}
|
||||||
|
if (palmapOutput.has_value()) {
|
||||||
|
palmapOutput->sputc(iter->getPalID(mappings));
|
||||||
}
|
}
|
||||||
++tileID;
|
++tileID;
|
||||||
|
++iter;
|
||||||
}
|
}
|
||||||
assert(iter == attrmap.end());
|
assert(iter == attrmap.end());
|
||||||
}
|
}
|
||||||
@@ -874,11 +881,21 @@ static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
|||||||
for (AttrmapEntry const &entry : attrmap) {
|
for (AttrmapEntry const &entry : attrmap) {
|
||||||
uint8_t attr = entry.xFlip << 5 | entry.yFlip << 6;
|
uint8_t attr = entry.xFlip << 5 | entry.yFlip << 6;
|
||||||
attr |= entry.bank << 3;
|
attr |= entry.bank << 3;
|
||||||
attr |= mappings[entry.protoPaletteID] & 7;
|
attr |= entry.getPalID(mappings) & 7;
|
||||||
output.sputc(attr);
|
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
|
} // namespace optimized
|
||||||
|
|
||||||
void process() {
|
void process() {
|
||||||
@@ -1023,9 +1040,10 @@ contained:;
|
|||||||
unoptimized::outputTileData(png, attrmap, palettes, mappings);
|
unoptimized::outputTileData(png, attrmap, palettes, mappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.tilemap.empty() || !options.attrmap.empty()) {
|
if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) {
|
||||||
options.verbosePrint(Options::VERB_LOG_ACT,
|
options.verbosePrint(
|
||||||
"Generating unoptimized tilemap and/or attrmap...\n");
|
Options::VERB_LOG_ACT,
|
||||||
|
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n");
|
||||||
unoptimized::outputMaps(png, attrmap, mappings);
|
unoptimized::outputMaps(png, attrmap, mappings);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1052,5 +1070,10 @@ contained:;
|
|||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n");
|
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n");
|
||||||
optimized::outputAttrmap(attrmap, mappings);
|
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:
|
// We do this now for two reasons:
|
||||||
// 1. Checking those during the main loop is harmful to optimization, and
|
// 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
|
// 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");
|
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
||||||
std::filebuf pngFile;
|
std::filebuf pngFile;
|
||||||
@@ -246,6 +254,8 @@ void reverse() {
|
|||||||
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];
|
(*tilemap)[index] - options.baseTileIDs[bank] + bank * options.maxNbTiles[0];
|
||||||
}
|
}
|
||||||
assert(tileID < nbTileInstances); // Should have been checked earlier
|
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"
|
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
|
||||||
static std::array<uint8_t, 16> const trimmedTile{
|
static std::array<uint8_t, 16> const trimmedTile{
|
||||||
@@ -255,8 +265,7 @@ void reverse() {
|
|||||||
uint8_t const *tileData = tileID > nbTileInstances - options.trim
|
uint8_t const *tileData = tileID > nbTileInstances - options.trim
|
||||||
? trimmedTile.data()
|
? trimmedTile.data()
|
||||||
: &tiles[tileID * tileSize];
|
: &tiles[tileID * tileSize];
|
||||||
assert((attribute & 0b111) < palettes.size()); // Should be ensured on data read
|
auto const &palette = palettes[palID];
|
||||||
auto const &palette = palettes[attribute & 0b111];
|
|
||||||
for (uint8_t y = 0; y < 8; ++y) {
|
for (uint8_t y = 0; y < 8; ++y) {
|
||||||
// If vertically mirrored, fetch the bytes from the other end
|
// If vertically mirrored, fetch the bytes from the other end
|
||||||
uint8_t realY = attribute & 0x40 ? 7 - y : y;
|
uint8_t realY = attribute & 0x40 ? 7 - y : y;
|
||||||
|
|||||||
Reference in New Issue
Block a user