diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index b58039bd..f3a778cf 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -56,9 +56,9 @@ struct Options { static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun? format_(printf, 3, 4) void verbosePrint(uint8_t level, char const *fmt, ...) const; - uint8_t maxPalSize() const { - return nbColorsPerPal; // TODO: minus 1 when transparency is active - } + + mutable bool hasTransparentPixels = false; + uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; } }; extern Options options; diff --git a/src/gfx/convert.cpp b/src/gfx/convert.cpp index 9e196152..5d498456 100644 --- a/src/gfx/convert.cpp +++ b/src/gfx/convert.cpp @@ -41,6 +41,10 @@ public: void registerColor(Rgba const &rgba) { decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()]; + if (rgba.cgbColor() == Rgba::transparent) { + options.hasTransparentPixels = true; + } + if (!slot.has_value()) { slot.emplace(rgba); } else if (*slot != rgba) { @@ -120,10 +124,10 @@ public: bool isSuitableForGrayscale() const { // Check that all of the grays don't fall into the same "bin" - if (colors.size() > options.maxPalSize()) { // Apply the Pigeonhole Principle + if (colors.size() > options.maxOpaqueColors()) { // Apply the Pigeonhole Principle options.verbosePrint(Options::VERB_DEBUG, "Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n", - colors.size(), options.maxPalSize()); + colors.size(), options.maxOpaqueColors()); return false; } uint8_t bins = 0; @@ -490,8 +494,8 @@ static std::tuple, std::vector> assert(options.palSpec.size() == 1); // TODO: abort if ignored colors are being used; do it now for a friendlier error // message - if (embPalSize > options.maxPalSize()) { // Ignore extraneous colors if they are unused - embPalSize = options.maxPalSize(); + 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, @@ -503,7 +507,7 @@ static std::tuple, std::vector> std::vector palettes(options.palSpec.size()); auto palIter = palettes.begin(); // TODO: `zip` for (auto const &spec : options.palSpec) { - for (size_t i = 0; i < options.maxPalSize(); ++i) { + for (size_t i = 0; i < options.nbColorsPerPal; ++i) { (*palIter)[i] = spec[i].cgbColor(); } ++palIter; @@ -532,7 +536,8 @@ static void outputPalettes(std::vector const &palettes) { output.open(options.palettes, std::ios_base::out | std::ios_base::binary); for (Palette const &palette : palettes) { - for (uint16_t color : palette) { + for (uint8_t i = 0; i < options.nbColorsPerPal; ++i) { + uint16_t color = palette.colors[i]; // Will return `UINT16_MAX` for unused slots output.sputc(color & 0xFF); output.sputc(color >> 8); } diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index 7d94f646..403f0b21 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -543,17 +543,19 @@ uint8_t Palette::indexOf(uint16_t color) const { } auto Palette::begin() -> decltype(colors)::iterator { - return colors.begin(); + // Skip the first slot if reserved for transparency + return colors.begin() + options.hasTransparentPixels; } auto Palette::end() -> decltype(colors)::iterator { - return std::find(colors.begin(), colors.end(), UINT16_MAX); + return std::find(begin(), colors.end(), UINT16_MAX); } auto Palette::begin() const -> decltype(colors)::const_iterator { - return colors.begin(); + // Skip the first slot if reserved for transparency + return colors.begin() + options.hasTransparentPixels; } auto Palette::end() const -> decltype(colors)::const_iterator { - return std::find(colors.begin(), colors.end(), UINT16_MAX); + return std::find(begin(), colors.end(), UINT16_MAX); } uint8_t Palette::size() const { diff --git a/src/gfx/pal_packing.cpp b/src/gfx/pal_packing.cpp index ac5bb0d7..5b86c0e1 100644 --- a/src/gfx/pal_packing.cpp +++ b/src/gfx/pal_packing.cpp @@ -201,7 +201,7 @@ public: bool canFit(ProtoPalette const &protoPal) const { auto &colors = uniqueColors(); colors.insert(protoPal.begin(), protoPal.end()); - return colors.size() <= options.maxPalSize(); + return colors.size() <= options.maxOpaqueColors(); } /** @@ -275,7 +275,8 @@ static void decant(std::vector &assignments, // Decant on palettes decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) { // If the entire palettes can be merged, move all of `from`'s proto-palettes - if (to.combinedVolume(from.begin(), from.end(), protoPalettes) <= options.maxPalSize()) { + if (to.combinedVolume(from.begin(), from.end(), protoPalettes) + <= options.maxOpaqueColors()) { for (ProtoPalAttrs &attrs : from) { to.assign(attrs.protoPalIndex); } @@ -321,7 +322,7 @@ static void decant(std::vector &assignments, ++attrs; } while (iter != processed.end()); - if (to.combinedVolume(colors.begin(), colors.end()) <= options.maxPalSize()) { + if (to.combinedVolume(colors.begin(), colors.end()) <= options.maxOpaqueColors()) { // Iterate through the component's proto-palettes, and transfer them auto member = from.begin(); size_t curIndex = 0; @@ -417,10 +418,10 @@ std::tuple, size_t> bestPal.assign(std::move(attrs)); // If this overloads the palette, get it back to normal (if possible) - while (bestPal.volume() > options.maxPalSize()) { + while (bestPal.volume() > options.maxOpaqueColors()) { options.verbosePrint(Options::VERB_DEBUG, "Palette %zu is overloaded! (%zu > %" PRIu8 ")\n", - bestPalIndex, bestPal.volume(), options.maxPalSize()); + bestPalIndex, bestPal.volume(), options.maxOpaqueColors()); // Look for a proto-pal minimizing "efficiency" (size / rel_size) auto efficiency = [&bestPal](ProtoPalette const &pal) { @@ -453,7 +454,7 @@ std::tuple, size_t> // Deal with palettes still overloaded, by emptying them for (AssignedProtos &pal : assignments) { - if (pal.volume() > options.maxPalSize()) { + if (pal.volume() > options.maxOpaqueColors()) { for (ProtoPalAttrs &attrs : pal) { queue.emplace(std::move(attrs)); } diff --git a/src/gfx/pal_sorting.cpp b/src/gfx/pal_sorting.cpp index ac5fa15d..af20ef6b 100644 --- a/src/gfx/pal_sorting.cpp +++ b/src/gfx/pal_sorting.cpp @@ -30,15 +30,15 @@ void indexed(std::vector &palettes, int palSize, png_color const *palRG Palette &palette = palettes[0]; // Build our candidate array of colors decltype(palette.colors) colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX}; - for (int i = 0; i < options.maxPalSize(); ++i) { - colors[i] = pngToRgb(i).cgbColor(); + for (int i = 0; i < options.maxOpaqueColors(); ++i) { + colors[i + options.hasTransparentPixels] = pngToRgb(i).cgbColor(); } // Check that the palette only uses those colors if (std::all_of(palette.begin(), palette.end(), [&colors](uint16_t color) { return std::find(colors.begin(), colors.end(), color) != colors.end(); })) { - if (palette.size() != options.maxPalSize()) { + if (palette.size() != options.maxOpaqueColors()) { warning("Unused color in PNG embedded palette was re-added; please use `-c " "embedded` to get this in future versions"); } diff --git a/src/gfx/rgba.cpp b/src/gfx/rgba.cpp index 11d8e422..e10b4553 100644 --- a/src/gfx/rgba.cpp +++ b/src/gfx/rgba.cpp @@ -47,6 +47,6 @@ uint16_t Rgba::cgbColor() const { uint8_t Rgba::grayIndex() const { assert(isGray()); - // Convert from [0; 256[ to [0; maxPalSize[ - return static_cast(255 - red) * options.maxPalSize() / 256; + // Convert from [0; 256[ to [0; maxOpaqueColors[ + return static_cast(255 - red) * options.maxOpaqueColors() / 256; }