Implement "palette map" output

This commit is contained in:
ISSOtm
2022-05-20 09:30:35 +02:00
committed by Eldred Habert
parent 531092f5bd
commit 05e36767b0
5 changed files with 104 additions and 48 deletions

View File

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

View File

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

View File

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

View File

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

View File

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