mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Allow rgbgfx to generate a palette from a spec, without an image (#1192)
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
#ifndef RGBDS_GFX_CONVERT_HPP
|
#ifndef RGBDS_GFX_CONVERT_HPP
|
||||||
#define RGBDS_GFX_CONVERT_HPP
|
#define RGBDS_GFX_CONVERT_HPP
|
||||||
|
|
||||||
|
void processPalettes();
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
#endif // RGBDS_GFX_CONVERT_HPP
|
#endif // RGBDS_GFX_CONVERT_HPP
|
||||||
|
|||||||
@@ -623,6 +623,12 @@ and the tile map in
|
|||||||
.Pp
|
.Pp
|
||||||
.Dl $ rgbgfx -u title_screen.png -o title_screen.2bpp -t title_screen.tilemap
|
.Dl $ rgbgfx -u title_screen.png -o title_screen.2bpp -t title_screen.tilemap
|
||||||
.Pp
|
.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.
|
TODO: more examples.
|
||||||
.Sh BUGS
|
.Sh BUGS
|
||||||
Please report bugs and mistakes in this man page on
|
Please report bugs and mistakes in this man page on
|
||||||
|
|||||||
@@ -766,21 +766,24 @@ int main(int argc, char *argv[]) {
|
|||||||
fputs("Ready.\n", stderr);
|
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
|
// Do not do anything if option parsing went wrong
|
||||||
if (nbErrors) {
|
if (nbErrors) {
|
||||||
giveUp();
|
giveUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.reverse()) {
|
if (!options.input.empty()) {
|
||||||
reverse();
|
if (options.reverse()) {
|
||||||
|
reverse();
|
||||||
|
} else {
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT
|
||||||
|
&& !options.reverse()) {
|
||||||
|
processPalettes();
|
||||||
} else {
|
} else {
|
||||||
process();
|
fputs("FATAL: No input image specified\n", stderr);
|
||||||
|
printUsage();
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbErrors) {
|
if (nbErrors) {
|
||||||
|
|||||||
@@ -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>>
|
static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||||
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
|
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
|
||||||
// Run a "pagination" problem solver
|
// 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>>
|
static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||||
makePalsAsSpecified(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
|
makePalsAsSpecified(std::vector<ProtoPalette> const &protoPalettes) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the palette spec to actual palettes
|
// Convert the palette spec to actual palettes
|
||||||
std::vector<Palette> palettes(options.palSpec.size());
|
std::vector<Palette> palettes(options.palSpec.size());
|
||||||
for (auto [spec, pal] : zip(options.palSpec, palettes)) {
|
for (auto [spec, pal] : zip(options.palSpec, palettes)) {
|
||||||
@@ -626,16 +626,37 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void outputPalettes(std::vector<Palette> const &palettes) {
|
static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||||
File output;
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
|
for (auto &&palette : palettes) {
|
||||||
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
fputs("{ ", stderr);
|
||||||
|
for (uint16_t colorIndex : palette) {
|
||||||
|
fprintf(stderr, "%04" PRIx16 ", ", colorIndex);
|
||||||
|
}
|
||||||
|
fputs("}\n", stderr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Palette const &palette : palettes) {
|
if (palettes.size() > options.nbPalettes) {
|
||||||
for (uint8_t i = 0; i < options.nbColorsPerPal; ++i) {
|
// If the palette generation is wrong, other (dependee) operations are likely to be
|
||||||
uint16_t color = palette.colors[i]; // Will return `UINT16_MAX` for unused slots
|
// nonsensical, so fatal-error outright
|
||||||
output->sputc(color & 0xFF);
|
fatal("Generated %zu palettes, over the maximum of %" PRIu8, palettes.size(),
|
||||||
output->sputc(color >> 8);
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Palette const &palette : palettes) {
|
||||||
|
for (uint8_t i = 0; i < options.nbColorsPerPal; ++i) {
|
||||||
|
// Will output `UINT16_MAX` for unused slots
|
||||||
|
uint16_t color = palette.colors[i];
|
||||||
|
output->sputc(color & 0xFF);
|
||||||
|
output->sputc(color >> 8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -955,6 +976,16 @@ static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
|||||||
|
|
||||||
} // namespace optimized
|
} // 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() {
|
void process() {
|
||||||
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
|
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
|
auto [mappings, palettes] = options.palSpecType == Options::NO_SPEC
|
||||||
? generatePalettes(protoPalettes, png)
|
? generatePalettes(protoPalettes, png)
|
||||||
: makePalsAsSpecified(protoPalettes, png);
|
: makePalsAsSpecified(protoPalettes);
|
||||||
|
outputPalettes(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()) {
|
|
||||||
outputPalettes(palettes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If deduplication is not happening, we just need to output the tile data and/or maps as-is
|
// If deduplication is not happening, we just need to output the tile data and/or maps as-is
|
||||||
if (!options.allowDedup) {
|
if (!options.allowDedup) {
|
||||||
|
|||||||
Reference in New Issue
Block a user