diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index 237a9831..7267f94d 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -28,6 +28,7 @@ struct Options { INLINE, EXTERNAL, EMBEDDED, + EMBEDDED_MULTIPLE, DMG, } palSpecType = NO_SPEC; // -c std::vector, 4>> palSpec{}; @@ -61,6 +62,9 @@ struct Options { uint16_t maxNbColors() const { return nbColorsPerPal * nbPalettes; } bool hasExplicitPalSpec() const { return palSpecType == INLINE || palSpecType == EXTERNAL; } + bool hasEmbeddedPalSpec() const { + return palSpecType == EMBEDDED || palSpecType == EMBEDDED_MULTIPLE; + } uint8_t dmgColors[4] = {}; uint8_t dmgValue(uint8_t i) const { diff --git a/include/gfx/pal_spec.hpp b/include/gfx/pal_spec.hpp index 6cf3dcd8..3e23729f 100644 --- a/include/gfx/pal_spec.hpp +++ b/include/gfx/pal_spec.hpp @@ -10,6 +10,7 @@ void parseInlinePalSpec(char const * const rawArg); void parseEmbeddedPalSpec(Png const &png); +void parseEmbeddedMultiplePalSpec(Png const &png); void parseExternalPalSpec(char const *arg); void parseDmgPalSpec(char const * const rawArg); void parseBackgroundPalSpec(char const *arg); diff --git a/man/rgbgfx.1 b/man/rgbgfx.1 index 50ffa503..0dc8be00 100644 --- a/man/rgbgfx.1 +++ b/man/rgbgfx.1 @@ -189,6 +189,11 @@ is the case-insensitive word then the first four colors of the input PNG's embedded palette are used. It is an error if the PNG is not indexed, or if colors other than these 4 are used. .Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded . +If +.Ar pal_spec +is case-insensitive +.Cm embedded:multiple , +then every four colors of the input PNG's embedded palette are used for multiple palettes. .It Sy DMG palette spec If .Ar pal_spec diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 1406632c..944e93ea 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -200,6 +200,9 @@ static void parseArg(int ch, char *arg) { } else if (strcasecmp(arg, "embedded") == 0) { // Use PLTE, error out if missing options.palSpecType = Options::EMBEDDED; + } else if (strcasecmp(arg, "embedded:multiple") == 0) { + // Use PLTE, error out if missing + options.palSpecType = Options::EMBEDDED_MULTIPLE; } else if (strcasecmp(arg, "auto") == 0) { options.palSpecType = Options::NO_SPEC; } else if (strcasecmp(arg, "dmg") == 0) { @@ -526,7 +529,10 @@ static void verboseOutputConfig() { case Options::INLINE: case Options::EXTERNAL: case Options::EMBEDDED: + case Options::EMBEDDED_MULTIPLE: if (options.palSpecType == Options::EMBEDDED) { + fputs("\tEmbedded single palette spec from PNG indexed PLTE chunk\n", stderr); + } else if (options.palSpecType == Options::EMBEDDED_MULTIPLE) { fputs("\tEmbedded palette spec from PNG indexed PLTE chunk\n", stderr); } else { fputs("\tExplicit palette spec\n", stderr); @@ -695,6 +701,12 @@ int main(int argc, char *argv[]) { parseEmbeddedPalSpec(png); } break; + case Options::EMBEDDED_MULTIPLE: + assume(!localOptions.palSpec); + if (useInputImage) { + parseEmbeddedMultiplePalSpec(png); + } + break; case Options::INLINE: assume(localOptions.palSpec); parseInlinePalSpec(localOptions.palSpec->c_str()); diff --git a/src/gfx/pal_spec.cpp b/src/gfx/pal_spec.cpp index f667b36a..c0c62ccd 100644 --- a/src/gfx/pal_spec.cpp +++ b/src/gfx/pal_spec.cpp @@ -185,8 +185,8 @@ void parseEmbeddedPalSpec(Png const &png) { // Ignore extraneous colors if they are unused size_t nbColors = png.palette.size(); - if (nbColors > options.maxOpaqueColors()) { - nbColors = options.maxOpaqueColors(); + if (nbColors > options.nbColorsPerPal) { + nbColors = options.nbColorsPerPal; } // Fill in the palette spec @@ -198,6 +198,46 @@ void parseEmbeddedPalSpec(Png const &png) { } } +void parseEmbeddedMultiplePalSpec(Png const &png) { + // Generate a palette spec from N palettes' length of colors in the embedded palette + if (png.palette.empty()) { + error("\"-c embedded:multiple\" was given, but the PNG does not have an embedded palette"); + return; + } + + // Use enough embedded colors to evenly divide into N palettes + size_t nbColors = png.palette.size(); + size_t nbPalettes = nbColors / options.nbColorsPerPal; + if (uint16_t maxNbColors = options.maxNbColors(); nbColors > maxNbColors) { + warnx( + "PNG embedded palette contains %zu colors, but there can only be %" PRIu16 + "; ignoring extra", + nbColors, + maxNbColors + ); + nbColors = maxNbColors; + nbPalettes = options.nbPalettes; + } + if (size_t nbUsedColors = options.nbColorsPerPal * nbPalettes; nbColors > nbUsedColors) { + warnx( + "PNG embedded palette contains %zu colors, but only %zu can be used for %zu palettes; " + "ignoring extra", + nbColors, + nbUsedColors, + nbPalettes + ); + } + + // Fill in the palette spec + options.palSpec.clear(); + for (size_t i = 0; i < nbPalettes; ++i) { + auto &palette = options.palSpec.emplace_back(); + for (size_t j = 0; j < options.nbColorsPerPal; ++j) { + palette[j] = png.palette[i * options.nbColorsPerPal + j]; + } + } +} + // Appends the first line read from `file` to the end of the provided `buffer`. // Returns true if a line was read. [[nodiscard]] diff --git a/src/gfx/reverse.cpp b/src/gfx/reverse.cpp index 362d2db7..d6d31aff 100644 --- a/src/gfx/reverse.cpp +++ b/src/gfx/reverse.cpp @@ -282,7 +282,7 @@ void reverse() { palettes[0][i] = grayColors[options.dmgValue(i)]; } grayscale = true; - } else if (options.palSpecType == Options::EMBEDDED) { + } else if (options.hasEmbeddedPalSpec()) { warnx("An embedded palette was requested, but no palette file was specified; ignoring " "request"); grayscale = true; diff --git a/test/gfx/embedded_multiple.flags b/test/gfx/embedded_multiple.flags new file mode 100644 index 00000000..49549765 --- /dev/null +++ b/test/gfx/embedded_multiple.flags @@ -0,0 +1 @@ +-c embedded:multiple diff --git a/test/gfx/embedded_multiple.out.2bpp b/test/gfx/embedded_multiple.out.2bpp new file mode 100644 index 00000000..e81a97f8 Binary files /dev/null and b/test/gfx/embedded_multiple.out.2bpp differ diff --git a/test/gfx/embedded_multiple.out.pal b/test/gfx/embedded_multiple.out.pal new file mode 100644 index 00000000..37954ee0 Binary files /dev/null and b/test/gfx/embedded_multiple.out.pal differ diff --git a/test/gfx/embedded_multiple.out.palmap b/test/gfx/embedded_multiple.out.palmap new file mode 100644 index 00000000..4eb08182 Binary files /dev/null and b/test/gfx/embedded_multiple.out.palmap differ diff --git a/test/gfx/embedded_multiple.out.tilemap b/test/gfx/embedded_multiple.out.tilemap new file mode 100644 index 00000000..e0d84f45 Binary files /dev/null and b/test/gfx/embedded_multiple.out.tilemap differ diff --git a/test/gfx/embedded_multiple.png b/test/gfx/embedded_multiple.png new file mode 100644 index 00000000..063a98c0 Binary files /dev/null and b/test/gfx/embedded_multiple.png differ