Allow rgbgfx to generate a palette from a spec, without an image (#1192)

This commit is contained in:
Rangi
2023-11-02 15:12:48 -04:00
committed by GitHub
parent 0d72ba886b
commit 5a25c547ab
4 changed files with 83 additions and 59 deletions

View File

@@ -9,6 +9,7 @@
#ifndef RGBDS_GFX_CONVERT_HPP
#define RGBDS_GFX_CONVERT_HPP
void processPalettes();
void process();
#endif // RGBDS_GFX_CONVERT_HPP

View File

@@ -623,6 +623,12 @@ and the tile map in
.Pp
.Dl $ rgbgfx -u title_screen.png -o title_screen.2bpp -t title_screen.tilemap
.Pp
The following will convert the given inline palette specification to a palette set, and store the palette set in
.Ql colors.pal ,
without needing an input image.
.Pp
.Dl $ rgbgfx -c '#fff,#ff0,#f80,#000' -p colors.pal
.Pp
TODO: more examples.
.Sh BUGS
Please report bugs and mistakes in this man page on

View File

@@ -766,22 +766,25 @@ int main(int argc, char *argv[]) {
fputs("Ready.\n", stderr);
}
if (options.input.empty()) {
fputs("FATAL: No input image specified\n", stderr);
printUsage();
exit(1);
}
// Do not do anything if option parsing went wrong
if (nbErrors) {
giveUp();
}
if (!options.input.empty()) {
if (options.reverse()) {
reverse();
} else {
process();
}
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT
&& !options.reverse()) {
processPalettes();
} else {
fputs("FATAL: No input image specified\n", stderr);
printUsage();
exit(1);
}
if (nbErrors) {
giveUp();

View File

@@ -511,6 +511,25 @@ struct AttrmapEntry {
}
};
static void generatePalSpec(Png const &png) {
// Generate a palette spec from the first few colors in the embedded palette
auto [embPalSize, embPalRGB, embPalAlpha] = png.getEmbeddedPal();
if (embPalRGB == nullptr) {
fatal("`-c embedded` was given, but the PNG does not have an embedded palette!");
}
// Fill in the palette spec
options.palSpec.emplace_back(); // A single palette, with `#00000000`s (transparent)
assert(options.palSpec.size() == 1);
if (embPalSize > options.maxOpaqueColors()) { // Ignore extraneous colors if they are unused
embPalSize = options.maxOpaqueColors();
}
for (int i = 0; i < embPalSize; ++i) {
options.palSpec[0][i] = Rgba(embPalRGB[i].red, embPalRGB[i].green, embPalRGB[i].blue,
embPalAlpha ? embPalAlpha[i] : 0xFF);
}
}
static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
// Run a "pagination" problem solver
@@ -555,26 +574,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
}
static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
makePalsAsSpecified(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
if (options.palSpecType == Options::EMBEDDED) {
// Generate a palette spec from the first few colors in the embedded palette
auto [embPalSize, embPalRGB, embPalAlpha] = png.getEmbeddedPal();
if (embPalRGB == nullptr) {
fatal("`-c embedded` was given, but the PNG does not have an embedded palette!");
}
// Fill in the palette spec
options.palSpec.emplace_back(); // A single palette, with `#00000000`s (transparent)
assert(options.palSpec.size() == 1);
if (embPalSize > options.maxOpaqueColors()) { // Ignore extraneous colors if they are unused
embPalSize = options.maxOpaqueColors();
}
for (int i = 0; i < embPalSize; ++i) {
options.palSpec[0][i] = Rgba(embPalRGB[i].red, embPalRGB[i].green, embPalRGB[i].blue,
embPalAlpha ? embPalAlpha[i] : 0xFF);
}
}
makePalsAsSpecified(std::vector<ProtoPalette> const &protoPalettes) {
// Convert the palette spec to actual palettes
std::vector<Palette> palettes(options.palSpec.size());
for (auto [spec, pal] : zip(options.palSpec, palettes)) {
@@ -626,19 +626,40 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
}
static void outputPalettes(std::vector<Palette> const &palettes) {
if (options.verbosity >= Options::VERB_INTERM) {
for (auto &&palette : palettes) {
fputs("{ ", stderr);
for (uint16_t colorIndex : palette) {
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
}
fputs("}\n", stderr);
}
}
if (palettes.size() > options.nbPalettes) {
// If the palette generation is wrong, other (dependee) operations are likely to be
// nonsensical, so fatal-error outright
fatal("Generated %zu palettes, over the maximum of %" PRIu8, palettes.size(),
options.nbPalettes);
}
if (!options.palettes.empty()) {
File output;
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes),
strerror(errno));
}
for (Palette const &palette : palettes) {
for (uint8_t i = 0; i < options.nbColorsPerPal; ++i) {
uint16_t color = palette.colors[i]; // Will return `UINT16_MAX` for unused slots
// Will output `UINT16_MAX` for unused slots
uint16_t color = palette.colors[i];
output->sputc(color & 0xFF);
output->sputc(color >> 8);
}
}
}
}
class TileData {
std::array<uint8_t, 16> _data;
@@ -955,6 +976,16 @@ static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
} // namespace optimized
void processPalettes() {
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
std::vector<ProtoPalette> protoPalettes;
std::vector<Palette> palettes;
std::tie(std::ignore, palettes) = makePalsAsSpecified(protoPalettes);
outputPalettes(palettes);
}
void process() {
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
@@ -1063,30 +1094,13 @@ contained:;
}
}
if (options.palSpecType == Options::EMBEDDED) {
generatePalSpec(png);
}
auto [mappings, palettes] = options.palSpecType == Options::NO_SPEC
? generatePalettes(protoPalettes, png)
: makePalsAsSpecified(protoPalettes, png);
if (options.verbosity >= Options::VERB_INTERM) {
for (auto &&palette : palettes) {
fputs("{ ", stderr);
for (uint16_t colorIndex : palette) {
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
}
fputs("}\n", stderr);
}
}
if (palettes.size() > options.nbPalettes) {
// If the palette generation is wrong, other (dependee) operations are likely to be
// nonsensical, so fatal-error outright
fatal("Generated %zu palettes, over the maximum of %" PRIu8, palettes.size(),
options.nbPalettes);
}
if (!options.palettes.empty()) {
: makePalsAsSpecified(protoPalettes);
outputPalettes(palettes);
}
// If deduplication is not happening, we just need to output the tile data and/or maps as-is
if (!options.allowDedup) {