From ffa32a94e84a255d422b243d639e4f61fa1b32e1 Mon Sep 17 00:00:00 2001 From: Rangi <35663410+Rangi42@users.noreply.github.com> Date: Sat, 3 Oct 2026 13:26:02 -0400 Subject: [PATCH] Implement `-c embedded:multiple` (#2194) --- include/gfx/main.hpp | 4 +++ include/gfx/pal_spec.hpp | 1 + man/rgbgfx.1 | 5 +++ src/gfx/main.cpp | 12 +++++++ src/gfx/pal_spec.cpp | 44 +++++++++++++++++++++++-- src/gfx/reverse.cpp | 2 +- test/gfx/embedded_multiple.flags | 1 + test/gfx/embedded_multiple.out.2bpp | Bin 0 -> 128 bytes test/gfx/embedded_multiple.out.pal | Bin 0 -> 16 bytes test/gfx/embedded_multiple.out.palmap | Bin 0 -> 8 bytes test/gfx/embedded_multiple.out.tilemap | Bin 0 -> 8 bytes test/gfx/embedded_multiple.png | Bin 0 -> 209 bytes 12 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 test/gfx/embedded_multiple.flags create mode 100644 test/gfx/embedded_multiple.out.2bpp create mode 100644 test/gfx/embedded_multiple.out.pal create mode 100644 test/gfx/embedded_multiple.out.palmap create mode 100644 test/gfx/embedded_multiple.out.tilemap create mode 100644 test/gfx/embedded_multiple.png 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 0000000000000000000000000000000000000000..e81a97f8fd9cc299ff91b243bf22fce35524996d GIT binary patch literal 128 zcmZ9Du@S&92m`;O$^eX#Lc^qD3Lmpa_+0rp3JD%=l9xQQZ=wjqM^Ba_W~-#zo!ow0 R7Rt(I%;k8B?uf^vfgc}23V8qk literal 0 HcmV?d00001 diff --git a/test/gfx/embedded_multiple.out.pal b/test/gfx/embedded_multiple.out.pal new file mode 100644 index 0000000000000000000000000000000000000000..37954ee0e6b440d36d8c3fa3b6bfc3dd8ee313d5 GIT binary patch literal 16 XcmZQzkYrF`wq#~tkW5iXw@e2B3VZ?` literal 0 HcmV?d00001 diff --git a/test/gfx/embedded_multiple.out.palmap b/test/gfx/embedded_multiple.out.palmap new file mode 100644 index 0000000000000000000000000000000000000000..4eb0818254b2a5ffcabb6d0c81a91ae2ef2f180a GIT binary patch literal 8 NcmZQz00KrvMgRZ~00aO4 literal 0 HcmV?d00001 diff --git a/test/gfx/embedded_multiple.out.tilemap b/test/gfx/embedded_multiple.out.tilemap new file mode 100644 index 0000000000000000000000000000000000000000..e0d84f457d2b5c802ecd286e7e576365d94af3b9 GIT binary patch literal 8 PcmZQzWMXDvWn%{b09*ha literal 0 HcmV?d00001 diff --git a/test/gfx/embedded_multiple.png b/test/gfx/embedded_multiple.png new file mode 100644 index 0000000000000000000000000000000000000000..063a98c051ec0a5d710e2ea2d9ad6e435346ed41 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et!3-oF?)7E^DaPU;cPEB*=VV?2IT8UrA+A9B z3>cpQQ9w2jg1{A_mU}>cx~Gd{h{nXJebHP86nI#&H~GG=e>{mjYf+Gu!p|Ft*I)l~ z%r5Kb_+n%{eNW}P^K)aJJ+htWT9}^s%C4w&t<&~L0>^en2N#wD$tnleFLe}jYy2Yf rlRqm%%1zAVOw+SsXP*WBA&KX?|DR-eZM;`s4&(+;S3j3^P6