mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Implement transparency handling
Though none of this has been tested so far...
This commit is contained in:
@@ -56,9 +56,9 @@ struct Options {
|
|||||||
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
|
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?
|
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;
|
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;
|
extern Options options;
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ public:
|
|||||||
void registerColor(Rgba const &rgba) {
|
void registerColor(Rgba const &rgba) {
|
||||||
decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()];
|
decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()];
|
||||||
|
|
||||||
|
if (rgba.cgbColor() == Rgba::transparent) {
|
||||||
|
options.hasTransparentPixels = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!slot.has_value()) {
|
if (!slot.has_value()) {
|
||||||
slot.emplace(rgba);
|
slot.emplace(rgba);
|
||||||
} else if (*slot != rgba) {
|
} else if (*slot != rgba) {
|
||||||
@@ -120,10 +124,10 @@ public:
|
|||||||
|
|
||||||
bool isSuitableForGrayscale() const {
|
bool isSuitableForGrayscale() const {
|
||||||
// Check that all of the grays don't fall into the same "bin"
|
// 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,
|
options.verbosePrint(Options::VERB_DEBUG,
|
||||||
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
|
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
|
||||||
colors.size(), options.maxPalSize());
|
colors.size(), options.maxOpaqueColors());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint8_t bins = 0;
|
uint8_t bins = 0;
|
||||||
@@ -490,8 +494,8 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
|||||||
assert(options.palSpec.size() == 1);
|
assert(options.palSpec.size() == 1);
|
||||||
// TODO: abort if ignored colors are being used; do it now for a friendlier error
|
// TODO: abort if ignored colors are being used; do it now for a friendlier error
|
||||||
// message
|
// message
|
||||||
if (embPalSize > options.maxPalSize()) { // Ignore extraneous colors if they are unused
|
if (embPalSize > options.maxOpaqueColors()) { // Ignore extraneous colors if they are unused
|
||||||
embPalSize = options.maxPalSize();
|
embPalSize = options.maxOpaqueColors();
|
||||||
}
|
}
|
||||||
for (int i = 0; i < embPalSize; ++i) {
|
for (int i = 0; i < embPalSize; ++i) {
|
||||||
options.palSpec[0][i] = Rgba(embPalRGB[i].red, embPalRGB[i].green, embPalRGB[i].blue,
|
options.palSpec[0][i] = Rgba(embPalRGB[i].red, embPalRGB[i].green, embPalRGB[i].blue,
|
||||||
@@ -503,7 +507,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
|||||||
std::vector<Palette> palettes(options.palSpec.size());
|
std::vector<Palette> palettes(options.palSpec.size());
|
||||||
auto palIter = palettes.begin(); // TODO: `zip`
|
auto palIter = palettes.begin(); // TODO: `zip`
|
||||||
for (auto const &spec : options.palSpec) {
|
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)[i] = spec[i].cgbColor();
|
||||||
}
|
}
|
||||||
++palIter;
|
++palIter;
|
||||||
@@ -532,7 +536,8 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
|||||||
output.open(options.palettes, std::ios_base::out | std::ios_base::binary);
|
output.open(options.palettes, std::ios_base::out | std::ios_base::binary);
|
||||||
|
|
||||||
for (Palette const &palette : palettes) {
|
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 & 0xFF);
|
||||||
output.sputc(color >> 8);
|
output.sputc(color >> 8);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,17 +543,19 @@ uint8_t Palette::indexOf(uint16_t color) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Palette::begin() -> decltype(colors)::iterator {
|
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 {
|
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 {
|
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 {
|
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 {
|
uint8_t Palette::size() const {
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ public:
|
|||||||
bool canFit(ProtoPalette const &protoPal) const {
|
bool canFit(ProtoPalette const &protoPal) const {
|
||||||
auto &colors = uniqueColors();
|
auto &colors = uniqueColors();
|
||||||
colors.insert(protoPal.begin(), protoPal.end());
|
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<AssignedProtos> &assignments,
|
|||||||
// Decant on palettes
|
// Decant on palettes
|
||||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
||||||
// If the entire palettes can be merged, move all of `from`'s proto-palettes
|
// 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) {
|
for (ProtoPalAttrs &attrs : from) {
|
||||||
to.assign(attrs.protoPalIndex);
|
to.assign(attrs.protoPalIndex);
|
||||||
}
|
}
|
||||||
@@ -321,7 +322,7 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
|||||||
++attrs;
|
++attrs;
|
||||||
} while (iter != processed.end());
|
} 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
|
// Iterate through the component's proto-palettes, and transfer them
|
||||||
auto member = from.begin();
|
auto member = from.begin();
|
||||||
size_t curIndex = 0;
|
size_t curIndex = 0;
|
||||||
@@ -417,10 +418,10 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
bestPal.assign(std::move(attrs));
|
bestPal.assign(std::move(attrs));
|
||||||
|
|
||||||
// If this overloads the palette, get it back to normal (if possible)
|
// 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,
|
options.verbosePrint(Options::VERB_DEBUG,
|
||||||
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
|
"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)
|
// Look for a proto-pal minimizing "efficiency" (size / rel_size)
|
||||||
auto efficiency = [&bestPal](ProtoPalette const &pal) {
|
auto efficiency = [&bestPal](ProtoPalette const &pal) {
|
||||||
@@ -453,7 +454,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
|
|
||||||
// Deal with palettes still overloaded, by emptying them
|
// Deal with palettes still overloaded, by emptying them
|
||||||
for (AssignedProtos &pal : assignments) {
|
for (AssignedProtos &pal : assignments) {
|
||||||
if (pal.volume() > options.maxPalSize()) {
|
if (pal.volume() > options.maxOpaqueColors()) {
|
||||||
for (ProtoPalAttrs &attrs : pal) {
|
for (ProtoPalAttrs &attrs : pal) {
|
||||||
queue.emplace(std::move(attrs));
|
queue.emplace(std::move(attrs));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,15 +30,15 @@ void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRG
|
|||||||
Palette &palette = palettes[0];
|
Palette &palette = palettes[0];
|
||||||
// Build our candidate array of colors
|
// Build our candidate array of colors
|
||||||
decltype(palette.colors) colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
decltype(palette.colors) colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||||
for (int i = 0; i < options.maxPalSize(); ++i) {
|
for (int i = 0; i < options.maxOpaqueColors(); ++i) {
|
||||||
colors[i] = pngToRgb(i).cgbColor();
|
colors[i + options.hasTransparentPixels] = pngToRgb(i).cgbColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the palette only uses those colors
|
// Check that the palette only uses those colors
|
||||||
if (std::all_of(palette.begin(), palette.end(), [&colors](uint16_t color) {
|
if (std::all_of(palette.begin(), palette.end(), [&colors](uint16_t color) {
|
||||||
return std::find(colors.begin(), colors.end(), color) != colors.end();
|
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 "
|
warning("Unused color in PNG embedded palette was re-added; please use `-c "
|
||||||
"embedded` to get this in future versions");
|
"embedded` to get this in future versions");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ uint16_t Rgba::cgbColor() const {
|
|||||||
|
|
||||||
uint8_t Rgba::grayIndex() const {
|
uint8_t Rgba::grayIndex() const {
|
||||||
assert(isGray());
|
assert(isGray());
|
||||||
// Convert from [0; 256[ to [0; maxPalSize[
|
// Convert from [0; 256[ to [0; maxOpaqueColors[
|
||||||
return static_cast<uint16_t>(255 - red) * options.maxPalSize() / 256;
|
return static_cast<uint16_t>(255 - red) * options.maxOpaqueColors() / 256;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user