mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-29 22:37:50 +00:00
Run clang-format on everything (#1332)
This commit is contained in:
205
src/gfx/main.cpp
205
src/gfx/main.cpp
@@ -118,52 +118,54 @@ static char const *optstring = "-Aa:b:Cc:Dd:FfhL:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
||||
* over short opt matching
|
||||
*/
|
||||
static option const longopts[] = {
|
||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||
{"output-attr-map", no_argument, nullptr, -'A'}, // Deprecated
|
||||
{"attr-map", required_argument, nullptr, 'a'},
|
||||
{"base-tiles", required_argument, nullptr, 'b'},
|
||||
{"color-curve", no_argument, nullptr, 'C'},
|
||||
{"colors", required_argument, nullptr, 'c'},
|
||||
{"depth", required_argument, nullptr, 'd'},
|
||||
{"slice", required_argument, nullptr, 'L'},
|
||||
{"mirror-tiles", no_argument, nullptr, 'm'},
|
||||
{"nb-tiles", required_argument, nullptr, 'N'},
|
||||
{"nb-palettes", required_argument, nullptr, 'n'},
|
||||
{"group-outputs", no_argument, nullptr, 'O'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"auto-palette", no_argument, nullptr, 'P'},
|
||||
{"output-palette", no_argument, nullptr, -'P'}, // Deprecated
|
||||
{"palette", required_argument, nullptr, 'p'},
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
||||
{"output-palette-map", no_argument, nullptr, -'Q'}, // Deprecated
|
||||
{"palette-map", required_argument, nullptr, 'q'},
|
||||
{"reverse", required_argument, nullptr, 'r'},
|
||||
{"auto-tilemap", no_argument, nullptr, 'T'},
|
||||
{"output-tilemap", no_argument, nullptr, -'T'}, // Deprecated
|
||||
{"tilemap", required_argument, nullptr, 't'},
|
||||
{"unit-size", required_argument, nullptr, 'U'},
|
||||
{"unique-tiles", no_argument, nullptr, 'u'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"trim-end", required_argument, nullptr, 'x'},
|
||||
{"columns", no_argument, nullptr, 'Z'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
{"auto-attr-map", no_argument, nullptr, 'A' },
|
||||
{"output-attr-map", no_argument, nullptr, -'A'}, // Deprecated
|
||||
{"attr-map", required_argument, nullptr, 'a' },
|
||||
{"base-tiles", required_argument, nullptr, 'b' },
|
||||
{"color-curve", no_argument, nullptr, 'C' },
|
||||
{"colors", required_argument, nullptr, 'c' },
|
||||
{"depth", required_argument, nullptr, 'd' },
|
||||
{"slice", required_argument, nullptr, 'L' },
|
||||
{"mirror-tiles", no_argument, nullptr, 'm' },
|
||||
{"nb-tiles", required_argument, nullptr, 'N' },
|
||||
{"nb-palettes", required_argument, nullptr, 'n' },
|
||||
{"group-outputs", no_argument, nullptr, 'O' },
|
||||
{"output", required_argument, nullptr, 'o' },
|
||||
{"auto-palette", no_argument, nullptr, 'P' },
|
||||
{"output-palette", no_argument, nullptr, -'P'}, // Deprecated
|
||||
{"palette", required_argument, nullptr, 'p' },
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q' },
|
||||
{"output-palette-map", no_argument, nullptr, -'Q'}, // Deprecated
|
||||
{"palette-map", required_argument, nullptr, 'q' },
|
||||
{"reverse", required_argument, nullptr, 'r' },
|
||||
{"auto-tilemap", no_argument, nullptr, 'T' },
|
||||
{"output-tilemap", no_argument, nullptr, -'T'}, // Deprecated
|
||||
{"tilemap", required_argument, nullptr, 't' },
|
||||
{"unit-size", required_argument, nullptr, 'U' },
|
||||
{"unique-tiles", no_argument, nullptr, 'u' },
|
||||
{"version", no_argument, nullptr, 'V' },
|
||||
{"verbose", no_argument, nullptr, 'v' },
|
||||
{"trim-end", required_argument, nullptr, 'x' },
|
||||
{"columns", no_argument, nullptr, 'Z' },
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
static void printUsage() {
|
||||
fputs("Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
|
||||
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
|
||||
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||
" -o, --output <path> output the tile data to this path\n"
|
||||
" -t, --tilemap <path> output the tile map to this path\n"
|
||||
" -u, --unique-tiles optimize out identical tiles\n"
|
||||
" -V, --version print RGBGFX version and exit\n"
|
||||
"\n"
|
||||
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr);
|
||||
fputs(
|
||||
"Usage: rgbgfx [-r stride] [-CmOuVZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||
" [-b <base_ids>] [-c <colors>] [-d <depth>] [-L <slice>] [-N <nb_tiles>]\n"
|
||||
" [-n <nb_pals>] [-o <out_file>] [-p <pal_file> | -P] [-q <pal_map> | -Q]\n"
|
||||
" [-s <nb_colors>] [-t <tile_map> | -T] [-x <nb_tiles>] <file>\n"
|
||||
"Useful options:\n"
|
||||
" -m, --mirror-tiles optimize out mirrored tiles\n"
|
||||
" -o, --output <path> output the tile data to this path\n"
|
||||
" -t, --tilemap <path> output the tile map to this path\n"
|
||||
" -u, --unique-tiles optimize out identical tiles\n"
|
||||
" -V, --version print RGBGFX version and exit\n"
|
||||
"\n"
|
||||
"For help, use `man rgbgfx' or go to https://rgbds.gbdev.io/docs/\n",
|
||||
stderr
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -215,8 +217,9 @@ static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVa
|
||||
};
|
||||
|
||||
if (charIndex(*string) == 255) {
|
||||
error("%s: expected digit%s, but found nothing", errPrefix,
|
||||
base != 10 ? " after base" : "");
|
||||
error(
|
||||
"%s: expected digit%s, but found nothing", errPrefix, base != 10 ? " after base" : ""
|
||||
);
|
||||
return errVal;
|
||||
}
|
||||
uint16_t number = 0;
|
||||
@@ -246,10 +249,13 @@ static void skipWhitespace(char *&arg) {
|
||||
|
||||
static void registerInput(char const *arg) {
|
||||
if (!options.input.empty()) {
|
||||
fprintf(stderr,
|
||||
"FATAL: input image specified more than once! (first \"%s\", then "
|
||||
"\"%s\")\n",
|
||||
options.input.c_str(), arg);
|
||||
fprintf(
|
||||
stderr,
|
||||
"FATAL: input image specified more than once! (first \"%s\", then "
|
||||
"\"%s\")\n",
|
||||
options.input.c_str(),
|
||||
arg
|
||||
);
|
||||
printUsage();
|
||||
exit(1);
|
||||
} else if (arg[0] == '\0') { // Empty input path
|
||||
@@ -272,8 +278,10 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
}
|
||||
|
||||
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
|
||||
static_assert(std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
||||
"isblank(char_traits<...>::eof()) is UB!");
|
||||
static_assert(
|
||||
std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
||||
"isblank(char_traits<...>::eof()) is UB!"
|
||||
);
|
||||
std::vector<size_t> argvOfs;
|
||||
|
||||
for (;;) {
|
||||
@@ -296,7 +304,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
||||
}
|
||||
continue; // Start processing the next line
|
||||
// If it's an empty line, ignore it
|
||||
case '\r': // Assuming CRLF here
|
||||
case '\r': // Assuming CRLF here
|
||||
file->sbumpc(); // Discard the upcoming '\n'
|
||||
[[fallthrough]];
|
||||
case '\n':
|
||||
@@ -371,8 +379,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
skipWhitespace(arg);
|
||||
if (*arg != ',') {
|
||||
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg);
|
||||
error(
|
||||
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg
|
||||
);
|
||||
break;
|
||||
}
|
||||
++arg; // Skip comma
|
||||
@@ -384,8 +394,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
options.baseTileIDs[1] = number;
|
||||
}
|
||||
if (*arg != '\0') {
|
||||
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg);
|
||||
error(
|
||||
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -474,8 +486,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
}
|
||||
skipWhitespace(arg);
|
||||
if (*arg != ',') {
|
||||
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg);
|
||||
error(
|
||||
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg
|
||||
);
|
||||
break;
|
||||
}
|
||||
++arg; // Skip comma
|
||||
@@ -485,8 +499,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
error("Bank 1 cannot contain more than 256 tiles");
|
||||
}
|
||||
if (*arg != '\0') {
|
||||
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg);
|
||||
error(
|
||||
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
||||
musl_optarg
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -604,7 +620,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct AtFileStackEntry {
|
||||
int parentInd; // Saved offset into parent argv
|
||||
int parentInd; // Saved offset into parent argv
|
||||
std::vector<char *> argv; // This context's arg pointer vec
|
||||
std::vector<char> argPool;
|
||||
|
||||
@@ -633,7 +649,7 @@ int main(int argc, char *argv[]) {
|
||||
curArgc = stackEntry.argv.size() - 1;
|
||||
curArgv = stackEntry.argv.data();
|
||||
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
|
||||
continue; // Begin scanning that arg vector
|
||||
continue; // Begin scanning that arg vector
|
||||
}
|
||||
|
||||
if (musl_optind != curArgc) {
|
||||
@@ -665,16 +681,23 @@ int main(int argc, char *argv[]) {
|
||||
if (options.nbColorsPerPal == 0) {
|
||||
options.nbColorsPerPal = 1u << options.bitDepth;
|
||||
} else if (options.nbColorsPerPal > 1u << options.bitDepth) {
|
||||
error("%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8, options.bitDepth,
|
||||
1u << options.bitDepth, options.nbColorsPerPal);
|
||||
error(
|
||||
"%" PRIu8 "bpp palettes can only contain %u colors, not %" PRIu8,
|
||||
options.bitDepth,
|
||||
1u << options.bitDepth,
|
||||
options.nbColorsPerPal
|
||||
);
|
||||
}
|
||||
|
||||
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
|
||||
if (autoOptEnabled) {
|
||||
auto &image = localOptions.groupOutputs ? options.output : options.input;
|
||||
if (image.empty()) {
|
||||
fprintf(stderr, "FATAL: No %s specified\n", localOptions.groupOutputs
|
||||
? "output tile data file" : "input image");
|
||||
fprintf(
|
||||
stderr,
|
||||
"FATAL: No %s specified\n",
|
||||
localOptions.groupOutputs ? "output tile data file" : "input image"
|
||||
);
|
||||
printUsage();
|
||||
exit(1);
|
||||
}
|
||||
@@ -723,7 +746,8 @@ int main(int argc, char *argv[]) {
|
||||
static std::array<char const *, 3> textbox{
|
||||
" ,----------------------------------------.",
|
||||
" | Augh, dimensional interference again?! |",
|
||||
" `----------------------------------------'"};
|
||||
" `----------------------------------------'",
|
||||
};
|
||||
for (size_t i = 0; i < gfx.size(); ++i) {
|
||||
uint16_t row = gfx[i];
|
||||
for (uint8_t _ = 0; _ < 10; ++_) {
|
||||
@@ -781,15 +805,27 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
fputs("\t]\n", stderr);
|
||||
}
|
||||
fprintf(stderr,
|
||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32
|
||||
", %" PRIi32 ")\n",
|
||||
options.inputSlice.width, options.inputSlice.height, options.inputSlice.left,
|
||||
options.inputSlice.top);
|
||||
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
|
||||
options.baseTileIDs[1]);
|
||||
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
|
||||
options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32
|
||||
")\n",
|
||||
options.inputSlice.width,
|
||||
options.inputSlice.height,
|
||||
options.inputSlice.left,
|
||||
options.inputSlice.top
|
||||
);
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n",
|
||||
options.baseTileIDs[0],
|
||||
options.baseTileIDs[1]
|
||||
);
|
||||
fprintf(
|
||||
stderr,
|
||||
"\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
auto printPath = [](char const *name, std::string const &path) {
|
||||
if (!path.empty()) {
|
||||
fprintf(stderr, "\t%s: %s\n", name, path.c_str());
|
||||
@@ -814,8 +850,7 @@ int main(int argc, char *argv[]) {
|
||||
} else {
|
||||
process();
|
||||
}
|
||||
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT
|
||||
&& !options.reverse()) {
|
||||
} else if (!options.palettes.empty() && options.palSpecType == Options::EXPLICIT && !options.reverse()) {
|
||||
processPalettes();
|
||||
} else {
|
||||
fputs("FATAL: No input image specified\n", stderr);
|
||||
@@ -832,7 +867,7 @@ int main(int argc, char *argv[]) {
|
||||
void Palette::addColor(uint16_t color) {
|
||||
for (size_t i = 0; true; ++i) {
|
||||
assert(i < colors.size()); // The packing should guarantee this
|
||||
if (colors[i] == color) { // The color is already present
|
||||
if (colors[i] == color) { // The color is already present
|
||||
break;
|
||||
} else if (colors[i] == UINT16_MAX) { // Empty slot
|
||||
colors[i] = color;
|
||||
@@ -858,9 +893,9 @@ auto Palette::begin() -> decltype(colors)::iterator {
|
||||
auto Palette::end() -> decltype(colors)::iterator {
|
||||
// Return an iterator pointing past the last non-empty element.
|
||||
// Since the palette may contain gaps, we must scan from the end.
|
||||
return std::find_if(colors.rbegin(), colors.rend(),
|
||||
[](uint16_t c) { return c != UINT16_MAX; })
|
||||
.base();
|
||||
return std::find_if(
|
||||
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
|
||||
).base();
|
||||
}
|
||||
|
||||
auto Palette::begin() const -> decltype(colors)::const_iterator {
|
||||
@@ -870,9 +905,9 @@ auto Palette::begin() const -> decltype(colors)::const_iterator {
|
||||
|
||||
auto Palette::end() const -> decltype(colors)::const_iterator {
|
||||
// Same as the non-const end().
|
||||
return std::find_if(colors.rbegin(), colors.rend(),
|
||||
[](uint16_t c) { return c != UINT16_MAX; })
|
||||
.base();
|
||||
return std::find_if(
|
||||
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
|
||||
).base();
|
||||
}
|
||||
|
||||
uint8_t Palette::size() const {
|
||||
|
||||
@@ -140,9 +140,10 @@ public:
|
||||
*/
|
||||
template<typename... Ts>
|
||||
void assign(Ts &&...args) {
|
||||
auto freeSlot = std::find_if_not(
|
||||
RANGE(_assigned),
|
||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); });
|
||||
auto freeSlot =
|
||||
std::find_if_not(RANGE(_assigned), [](std::optional<ProtoPalAttrs> const &slot) {
|
||||
return slot.has_value();
|
||||
});
|
||||
|
||||
if (freeSlot == _assigned.end()) { // We are full, use a new slot
|
||||
_assigned.emplace_back(std::forward<Ts>(args)...);
|
||||
@@ -158,15 +159,20 @@ public:
|
||||
bool empty() const {
|
||||
return std::find_if(
|
||||
RANGE(_assigned),
|
||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); })
|
||||
[](std::optional<ProtoPalAttrs> const &slot) { return slot.has_value(); }
|
||||
)
|
||||
== _assigned.end();
|
||||
}
|
||||
size_t nbProtoPals() const { return std::distance(RANGE(*this)); }
|
||||
|
||||
private:
|
||||
template<typename Iter>
|
||||
static void addUniqueColors(std::unordered_set<uint16_t> &colors, Iter iter, Iter const &end,
|
||||
std::vector<ProtoPalette> const &protoPals) {
|
||||
static void addUniqueColors(
|
||||
std::unordered_set<uint16_t> &colors,
|
||||
Iter iter,
|
||||
Iter const &end,
|
||||
std::vector<ProtoPalette> const &protoPals
|
||||
) {
|
||||
for (; iter != end; ++iter) {
|
||||
ProtoPalette const &protoPal = protoPals[iter->protoPalIndex];
|
||||
colors.insert(RANGE(protoPal));
|
||||
@@ -226,8 +232,8 @@ public:
|
||||
* Computes the "relative size" of a set of proto-palettes on this palette
|
||||
*/
|
||||
template<typename Iter>
|
||||
auto combinedVolume(Iter &&begin, Iter const &end,
|
||||
std::vector<ProtoPalette> const &protoPals) const {
|
||||
auto combinedVolume(Iter &&begin, Iter const &end, std::vector<ProtoPalette> const &protoPals)
|
||||
const {
|
||||
auto &colors = uniqueColors();
|
||||
addUniqueColors(colors, std::forward<Iter>(begin), end, protoPals);
|
||||
return colors.size();
|
||||
@@ -243,8 +249,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static void decant(std::vector<AssignedProtos> &assignments,
|
||||
std::vector<ProtoPalette> const &protoPalettes) {
|
||||
static void decant(
|
||||
std::vector<AssignedProtos> &assignments, std::vector<ProtoPalette> const &protoPalettes
|
||||
) {
|
||||
// "Decanting" is the process of moving all *things* that can fit in a lower index there
|
||||
auto decantOn = [&assignments](auto const &tryDecanting) {
|
||||
// No need to attempt decanting on palette #0, as there are no palettes to decant to
|
||||
@@ -268,8 +275,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
||||
}
|
||||
};
|
||||
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes before decanting\n",
|
||||
assignments.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG, "%zu palettes before decanting\n", assignments.size()
|
||||
);
|
||||
|
||||
// Decant on palettes
|
||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
||||
@@ -281,8 +289,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
||||
from.clear();
|
||||
}
|
||||
});
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n",
|
||||
assignments.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG, "%zu palettes after decanting on palettes\n", assignments.size()
|
||||
);
|
||||
|
||||
// Decant on "components" (= proto-pals sharing colors)
|
||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
||||
@@ -331,8 +340,9 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
||||
}
|
||||
}
|
||||
});
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n",
|
||||
assignments.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG, "%zu palettes after decanting on \"components\"\n", assignments.size()
|
||||
);
|
||||
|
||||
// Decant on individual proto-palettes
|
||||
decantOn([&protoPalettes](AssignedProtos &to, AssignedProtos &from) {
|
||||
@@ -343,14 +353,16 @@ static void decant(std::vector<AssignedProtos> &assignments,
|
||||
}
|
||||
}
|
||||
});
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n",
|
||||
assignments.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG, "%zu palettes after decanting on proto-palettes\n", assignments.size()
|
||||
);
|
||||
}
|
||||
|
||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT,
|
||||
"Paginating palettes using \"overload-and-remove\" strategy...\n");
|
||||
options.verbosePrint(
|
||||
Options::VERB_LOG_ACT, "Paginating palettes using \"overload-and-remove\" strategy...\n"
|
||||
);
|
||||
|
||||
// Sort the proto-palettes by size, which improves the packing algorithm's efficiency
|
||||
DefaultInitVec<size_t> sortedProtoPalIDs(protoPalettes.size());
|
||||
@@ -379,9 +391,14 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
continue;
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_DEBUG, "%zu/%zu: Rel size: %f (size = %zu)\n", i + 1,
|
||||
assignments.size(), assignments[i].relSizeOf(protoPal),
|
||||
protoPal.size());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"%zu/%zu: Rel size: %f (size = %zu)\n",
|
||||
i + 1,
|
||||
assignments.size(),
|
||||
assignments[i].relSizeOf(protoPal),
|
||||
protoPal.size()
|
||||
);
|
||||
if (assignments[i].relSizeOf(protoPal) < bestRelSize) {
|
||||
bestPalIndex = i;
|
||||
}
|
||||
@@ -397,21 +414,26 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
|
||||
// If this overloads the palette, get it back to normal (if possible)
|
||||
while (bestPal.volume() > options.maxOpaqueColors()) {
|
||||
options.verbosePrint(Options::VERB_DEBUG,
|
||||
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
|
||||
bestPalIndex, bestPal.volume(), options.maxOpaqueColors());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
|
||||
bestPalIndex,
|
||||
bestPal.volume(),
|
||||
options.maxOpaqueColors()
|
||||
);
|
||||
|
||||
// Look for a proto-pal minimizing "efficiency" (size / rel_size)
|
||||
auto efficiency = [&bestPal](ProtoPalette const &pal) {
|
||||
return pal.size() / bestPal.relSizeOf(pal);
|
||||
};
|
||||
auto [minEfficiencyIter, maxEfficiencyIter] =
|
||||
std::minmax_element(RANGE(bestPal),
|
||||
[&efficiency, &protoPalettes](ProtoPalAttrs const &lhs,
|
||||
ProtoPalAttrs const &rhs) {
|
||||
return efficiency(protoPalettes[lhs.protoPalIndex])
|
||||
< efficiency(protoPalettes[rhs.protoPalIndex]);
|
||||
});
|
||||
auto [minEfficiencyIter, maxEfficiencyIter] = std::minmax_element(
|
||||
RANGE(bestPal),
|
||||
[&efficiency,
|
||||
&protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) {
|
||||
return efficiency(protoPalettes[lhs.protoPalIndex])
|
||||
< efficiency(protoPalettes[rhs.protoPalIndex]);
|
||||
}
|
||||
);
|
||||
|
||||
// All efficiencies are identical iff min equals max
|
||||
// TODO: maybe not ideal to re-compute these two?
|
||||
@@ -443,18 +465,24 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
while (!queue.empty()) {
|
||||
ProtoPalAttrs const &attrs = queue.front();
|
||||
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
|
||||
auto iter =
|
||||
std::find_if(RANGE(assignments),
|
||||
[&protoPal](AssignedProtos const &pal) { return pal.canFit(protoPal); });
|
||||
auto iter = std::find_if(RANGE(assignments), [&protoPal](AssignedProtos const &pal) {
|
||||
return pal.canFit(protoPal);
|
||||
});
|
||||
if (iter == assignments.end()) { // No such page, create a new one
|
||||
options.verbosePrint(Options::VERB_DEBUG,
|
||||
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
|
||||
assignments.size(), attrs.protoPalIndex);
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Adding new palette (%zu) for overflowing proto-pal %zu\n",
|
||||
assignments.size(),
|
||||
attrs.protoPalIndex
|
||||
);
|
||||
assignments.emplace_back(protoPalettes, std::move(attrs));
|
||||
} else {
|
||||
options.verbosePrint(Options::VERB_DEBUG,
|
||||
"Assigning overflowing proto-pal %zu to palette %zu\n",
|
||||
attrs.protoPalIndex, iter - assignments.begin());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Assigning overflowing proto-pal %zu to palette %zu\n",
|
||||
attrs.protoPalIndex,
|
||||
iter - assignments.begin()
|
||||
);
|
||||
iter->assign(std::move(attrs));
|
||||
}
|
||||
queue.pop();
|
||||
|
||||
@@ -13,13 +13,20 @@
|
||||
|
||||
namespace sorting {
|
||||
|
||||
void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRGB,
|
||||
int palAlphaSize, png_byte *palAlpha) {
|
||||
void indexed(
|
||||
std::vector<Palette> &palettes,
|
||||
int palSize,
|
||||
png_color const *palRGB,
|
||||
int palAlphaSize,
|
||||
png_byte *palAlpha
|
||||
) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes using embedded palette...\n");
|
||||
|
||||
auto pngToRgb = [&palRGB, &palAlphaSize, &palAlpha](int index) {
|
||||
auto const &c = palRGB[index];
|
||||
return Rgba(c.red, c.green, c.blue, palAlpha && index < palAlphaSize ? palAlpha[index] : 0xFF);
|
||||
return Rgba(
|
||||
c.red, c.green, c.blue, palAlpha && index < palAlphaSize ? palAlpha[index] : 0xFF
|
||||
);
|
||||
};
|
||||
|
||||
for (Palette &pal : palettes) {
|
||||
@@ -43,8 +50,9 @@ void indexed(std::vector<Palette> &palettes, int palSize, png_color const *palRG
|
||||
}
|
||||
}
|
||||
|
||||
void grayscale(std::vector<Palette> &palettes,
|
||||
std::array<std::optional<Rgba>, 0x8001> const &colors) {
|
||||
void grayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
||||
) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
|
||||
|
||||
// This method is only applicable if there are at most as many colors as colors per palette, so
|
||||
|
||||
@@ -66,10 +66,12 @@ void parseInlinePalSpec(char const * const rawArg) {
|
||||
assert(len <= arg.length());
|
||||
|
||||
errorMessage(msg);
|
||||
fprintf(stderr,
|
||||
"In inline palette spec: %s\n"
|
||||
" ",
|
||||
rawArg);
|
||||
fprintf(
|
||||
stderr,
|
||||
"In inline palette spec: %s\n"
|
||||
" ",
|
||||
rawArg
|
||||
);
|
||||
for (auto i = ofs; i; --i) {
|
||||
putc(' ', stderr);
|
||||
}
|
||||
@@ -97,12 +99,17 @@ void parseInlinePalSpec(char const * const rawArg) {
|
||||
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
|
||||
switch (pos - n) {
|
||||
case 3:
|
||||
color = Rgba(singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]),
|
||||
0xFF);
|
||||
color = Rgba(
|
||||
singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]), 0xFF
|
||||
);
|
||||
break;
|
||||
case 6:
|
||||
color = Rgba(toHex(arg[n + 0], arg[n + 1]), toHex(arg[n + 2], arg[n + 3]),
|
||||
toHex(arg[n + 4], arg[n + 5]), 0xFF);
|
||||
color = Rgba(
|
||||
toHex(arg[n + 0], arg[n + 1]),
|
||||
toHex(arg[n + 2], arg[n + 3]),
|
||||
toHex(arg[n + 4], arg[n + 5]),
|
||||
0xFF
|
||||
);
|
||||
break;
|
||||
case 0:
|
||||
parseError(n - 1, 1, "Missing color after '#'");
|
||||
@@ -239,36 +246,31 @@ static std::optional<U> parseDec(std::string const &str, std::string::size_type
|
||||
return std::optional<U>{value};
|
||||
}
|
||||
|
||||
static std::optional<Rgba> parseColor(std::string const &str, std::string::size_type &n,
|
||||
uint16_t i) {
|
||||
static std::optional<Rgba>
|
||||
parseColor(std::string const &str, std::string::size_type &n, uint16_t i) {
|
||||
std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
|
||||
if (!r) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
skipWhitespace(str, n);
|
||||
if (n == str.length()) {
|
||||
error("Failed to parse color #%d (\"%s\"): missing green component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): missing green component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<uint8_t> g = parseDec<uint8_t>(str, n);
|
||||
if (!g) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid green component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): invalid green component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
skipWhitespace(str, n);
|
||||
if (n == str.length()) {
|
||||
error("Failed to parse color #%d (\"%s\"): missing blue component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): missing blue component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<uint8_t> b = parseDec<uint8_t>(str, n);
|
||||
if (!b) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid blue component", i + 1,
|
||||
str.c_str());
|
||||
error("Failed to parse color #%d (\"%s\"): invalid blue component", i + 1, str.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -301,10 +303,14 @@ static void parsePSPFile(std::filebuf &file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; *nbColors > nbPalColors) {
|
||||
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
*nbColors, nbPalColors);
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
*nbColors > nbPalColors) {
|
||||
warning(
|
||||
"PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
*nbColors,
|
||||
nbPalColors
|
||||
);
|
||||
nbColors = nbPalColors;
|
||||
}
|
||||
|
||||
@@ -320,8 +326,11 @@ static void parsePSPFile(std::filebuf &file) {
|
||||
return;
|
||||
}
|
||||
if (n != line.length()) {
|
||||
error("Failed to parse color #%d (\"%s\"): trailing characters after blue component",
|
||||
i + 1, line.c_str());
|
||||
error(
|
||||
"Failed to parse color #%d (\"%s\"): trailing characters after blue component",
|
||||
i + 1,
|
||||
line.c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -372,9 +381,12 @@ static void parseGPLFile(std::filebuf &file) {
|
||||
}
|
||||
|
||||
if (nbColors > maxNbColors) {
|
||||
warning("GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors, maxNbColors);
|
||||
warning(
|
||||
"GPL file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors,
|
||||
maxNbColors
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,8 +405,11 @@ static void parseHEXFile(std::filebuf &file) {
|
||||
|
||||
if (line.length() != 6
|
||||
|| line.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string::npos) {
|
||||
error("Failed to parse color #%d (\"%s\"): invalid \"rrggbb\" line",
|
||||
nbColors + 1, line.c_str());
|
||||
error(
|
||||
"Failed to parse color #%d (\"%s\"): invalid \"rrggbb\" line",
|
||||
nbColors + 1,
|
||||
line.c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -411,9 +426,12 @@ static void parseHEXFile(std::filebuf &file) {
|
||||
}
|
||||
|
||||
if (nbColors > maxNbColors) {
|
||||
warning("HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors, maxNbColors);
|
||||
warning(
|
||||
"HEX file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors,
|
||||
maxNbColors
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,10 +454,14 @@ static void parseACTFile(std::filebuf &file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) {
|
||||
warning("ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors, nbPalColors);
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
nbColors > nbPalColors) {
|
||||
warning(
|
||||
"ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors,
|
||||
nbPalColors
|
||||
);
|
||||
nbColors = nbPalColors;
|
||||
}
|
||||
|
||||
@@ -486,10 +508,14 @@ static void parseACOFile(std::filebuf &file) {
|
||||
}
|
||||
uint16_t nbColors = readBE<uint16_t>(buf);
|
||||
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes; nbColors > nbPalColors) {
|
||||
warning("ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors, nbPalColors);
|
||||
if (uint16_t nbPalColors = options.nbColorsPerPal * options.nbPalettes;
|
||||
nbColors > nbPalColors) {
|
||||
warning(
|
||||
"ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||
"; ignoring extra",
|
||||
nbColors,
|
||||
nbPalColors
|
||||
);
|
||||
nbColors = nbPalColors;
|
||||
}
|
||||
|
||||
@@ -543,16 +569,22 @@ static void parseGBCFile(std::filebuf &file) {
|
||||
if (len == 0) {
|
||||
break;
|
||||
} else if (len != sizeof(buf)) {
|
||||
error("GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
|
||||
options.palSpec.size(), options.palSpec.size() == 1 ? "" : "s", len,
|
||||
len == 1 ? "" : "s");
|
||||
error(
|
||||
"GBC palette dump contains %zu 8-byte palette%s, plus %zu byte%s",
|
||||
options.palSpec.size(),
|
||||
options.palSpec.size() == 1 ? "" : "s",
|
||||
len,
|
||||
len == 1 ? "" : "s"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
options.palSpec.push_back({Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))});
|
||||
options.palSpec.push_back(
|
||||
{Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
|
||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6]))}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,14 +608,16 @@ void parseExternalPalSpec(char const *arg) {
|
||||
std::tuple{"GBC", &parseGBCFile, std::ios::binary},
|
||||
};
|
||||
|
||||
auto iter = std::find_if(RANGE(parsers),
|
||||
[&arg, &ptr](decltype(parsers)::value_type const &parser) {
|
||||
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
||||
});
|
||||
auto iter =
|
||||
std::find_if(RANGE(parsers), [&arg, &ptr](decltype(parsers)::value_type const &parser) {
|
||||
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
||||
});
|
||||
if (iter == parsers.end()) {
|
||||
error("Unknown external palette format \"%.*s\"",
|
||||
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
|
||||
arg);
|
||||
error(
|
||||
"Unknown external palette format \"%.*s\"",
|
||||
static_cast<int>(std::min(ptr - arg, static_cast<decltype(ptr - arg)>(INT_MAX))),
|
||||
arg
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,9 @@ public:
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return std::count_if(RANGE(_colors),
|
||||
[](decltype(_colors)::value_type const &slot) {
|
||||
return slot.has_value() && !slot->isTransparent();
|
||||
});
|
||||
return std::count_if(RANGE(_colors), [](decltype(_colors)::value_type const &slot) {
|
||||
return slot.has_value() && !slot->isTransparent();
|
||||
});
|
||||
}
|
||||
decltype(_colors) const &raw() const { return _colors; }
|
||||
|
||||
@@ -105,10 +104,13 @@ class Png {
|
||||
self->file->sgetn(reinterpret_cast<char *>(data), expectedLen);
|
||||
|
||||
if (nbBytesRead != expectedLen) {
|
||||
fatal("Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
||||
"bytes after reading %zu)",
|
||||
self->c_str(), length - nbBytesRead,
|
||||
(size_t)self->file->pubseekoff(0, std::ios_base::cur));
|
||||
fatal(
|
||||
"Error reading input image (\"%s\"): file too short (expected at least %zd more "
|
||||
"bytes after reading %zu)",
|
||||
self->c_str(),
|
||||
length - nbBytesRead,
|
||||
(size_t)self->file->pubseekoff(0, std::ios_base::cur)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,9 +136,12 @@ public:
|
||||
bool isSuitableForGrayscale() const {
|
||||
// Check that all of the grays don't fall into the same "bin"
|
||||
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.maxOpaqueColors());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Too many colors for grayscale sorting (%zu > %" PRIu8 ")\n",
|
||||
colors.size(),
|
||||
options.maxOpaqueColors()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
uint8_t bins = 0;
|
||||
@@ -145,9 +150,11 @@ public:
|
||||
continue;
|
||||
}
|
||||
if (!color->isGray()) {
|
||||
options.verbosePrint(Options::VERB_DEBUG,
|
||||
"Found non-gray color #%08x, not using grayscale sorting\n",
|
||||
color->toCSS());
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Found non-gray color #%08x, not using grayscale sorting\n",
|
||||
color->toCSS()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
uint8_t mask = 1 << color->grayIndex();
|
||||
@@ -155,7 +162,8 @@ public:
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Color #%08x conflicts with another one, not using grayscale sorting\n",
|
||||
color->toCSS());
|
||||
color->toCSS()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
bins |= mask;
|
||||
@@ -174,8 +182,7 @@ public:
|
||||
*/
|
||||
explicit Png(std::string const &filePath) : path(filePath), colors() {
|
||||
if (file.open(path, std::ios_base::in | std::ios_base::binary) == nullptr) {
|
||||
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path),
|
||||
strerror(errno));
|
||||
fatal("Failed to open input image (\"%s\"): %s", file.c_str(path), strerror(errno));
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Opened input file\n");
|
||||
@@ -190,8 +197,9 @@ public:
|
||||
|
||||
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
|
||||
|
||||
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError,
|
||||
handleWarning);
|
||||
png = png_create_read_struct(
|
||||
PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, handleWarning
|
||||
);
|
||||
if (!png) {
|
||||
fatal("Failed to allocate PNG structure: %s", strerror(errno));
|
||||
}
|
||||
@@ -215,8 +223,9 @@ public:
|
||||
|
||||
int bitDepth, interlaceType; //, compressionType, filterMethod;
|
||||
|
||||
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr,
|
||||
nullptr);
|
||||
png_get_IHDR(
|
||||
png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, nullptr
|
||||
);
|
||||
|
||||
if (options.inputSlice.width == 0 && width % 8 != 0) {
|
||||
fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width);
|
||||
@@ -253,23 +262,35 @@ public:
|
||||
fatal("Unknown interlace type %d", interlaceType);
|
||||
}
|
||||
};
|
||||
options.verbosePrint(Options::VERB_INTERM,
|
||||
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n", width,
|
||||
height, bitDepth, colorTypeName(), interlaceTypeName());
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM,
|
||||
"Input image: %" PRIu32 "x%" PRIu32 " pixels, %dbpp %s, %s\n",
|
||||
width,
|
||||
height,
|
||||
bitDepth,
|
||||
colorTypeName(),
|
||||
interlaceTypeName()
|
||||
);
|
||||
|
||||
if (png_get_PLTE(png, info, &embeddedPal, &nbColors) != 0) {
|
||||
if (png_get_tRNS(png, info, &transparencyPal, &nbTransparentEntries, nullptr)) {
|
||||
assert(nbTransparentEntries <= nbColors);
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_INTERM, "Embedded palette has %d colors: [",
|
||||
nbColors);
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM, "Embedded palette has %d colors: [", nbColors
|
||||
);
|
||||
for (int i = 0; i < nbColors; ++i) {
|
||||
auto const &color = embeddedPal[i];
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM, "#%02x%02x%02x%02x%s", color.red, color.green, color.blue,
|
||||
Options::VERB_INTERM,
|
||||
"#%02x%02x%02x%02x%s",
|
||||
color.red,
|
||||
color.green,
|
||||
color.blue,
|
||||
transparencyPal && i < nbTransparentEntries ? transparencyPal[i] : 0xFF,
|
||||
i != nbColors - 1 ? ", " : "]\n");
|
||||
i != nbColors - 1 ? ", " : "]\n"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n");
|
||||
@@ -329,40 +350,51 @@ public:
|
||||
std::vector<uint32_t> indeterminates;
|
||||
|
||||
// Assign a color to the given position, and register it in the image palette as well
|
||||
auto assignColor = [this, &conflicts, &indeterminates](png_uint_32 x, png_uint_32 y,
|
||||
Rgba &&color) {
|
||||
if (!color.isTransparent() && !color.isOpaque()) {
|
||||
uint32_t css = color.toCSS();
|
||||
if (std::find(RANGE(indeterminates), css)
|
||||
== indeterminates.end()) {
|
||||
error("Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= "
|
||||
"%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]",
|
||||
css, Rgba::transparency_threshold, Rgba::opacity_threshold, x, y);
|
||||
indeterminates.push_back(css);
|
||||
}
|
||||
} else if (Rgba const *other = colors.registerColor(color); other) {
|
||||
std::tuple conflicting{color.toCSS(), other->toCSS()};
|
||||
// Do not report combinations twice
|
||||
if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) {
|
||||
warning("Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen "
|
||||
auto assignColor =
|
||||
[this, &conflicts, &indeterminates](png_uint_32 x, png_uint_32 y, Rgba &&color) {
|
||||
if (!color.isTransparent() && !color.isOpaque()) {
|
||||
uint32_t css = color.toCSS();
|
||||
if (std::find(RANGE(indeterminates), css) == indeterminates.end()) {
|
||||
error(
|
||||
"Color #%08x is neither transparent (alpha < %u) nor opaque (alpha >= "
|
||||
"%u) [first seen at x: %" PRIu32 ", y: %" PRIu32 "]",
|
||||
css,
|
||||
Rgba::transparency_threshold,
|
||||
Rgba::opacity_threshold,
|
||||
x,
|
||||
y
|
||||
);
|
||||
indeterminates.push_back(css);
|
||||
}
|
||||
} else if (Rgba const *other = colors.registerColor(color); other) {
|
||||
std::tuple conflicting{color.toCSS(), other->toCSS()};
|
||||
// Do not report combinations twice
|
||||
if (std::find(RANGE(conflicts), conflicting) == conflicts.end()) {
|
||||
warning(
|
||||
"Fusing colors #%08x and #%08x into Game Boy color $%04x [first seen "
|
||||
"at x: %" PRIu32 ", y: %" PRIu32 "]",
|
||||
std::get<0>(conflicting), std::get<1>(conflicting), color.cgbColor(), x,
|
||||
y);
|
||||
// Do not report this combination again
|
||||
conflicts.emplace_back(conflicting);
|
||||
}
|
||||
}
|
||||
std::get<0>(conflicting),
|
||||
std::get<1>(conflicting),
|
||||
color.cgbColor(),
|
||||
x,
|
||||
y
|
||||
);
|
||||
// Do not report this combination again
|
||||
conflicts.emplace_back(conflicting);
|
||||
}
|
||||
}
|
||||
|
||||
pixel(x, y) = color;
|
||||
};
|
||||
pixel(x, y) = color;
|
||||
};
|
||||
|
||||
if (interlaceType == PNG_INTERLACE_NONE) {
|
||||
for (png_uint_32 y = 0; y < height; ++y) {
|
||||
png_read_row(png, row.data(), nullptr);
|
||||
|
||||
for (png_uint_32 x = 0; x < width; ++x) {
|
||||
assignColor(x, y,
|
||||
Rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3]));
|
||||
assignColor(
|
||||
x, y, Rgba(row[x * 4], row[x * 4 + 1], row[x * 4 + 2], row[x * 4 + 3])
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -454,14 +486,17 @@ public:
|
||||
iterator begin() const { return {*this, _limit, 0, 0}; }
|
||||
iterator end() const {
|
||||
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
|
||||
return ++it; // ...now one-past-last!
|
||||
return ++it; // ...now one-past-last!
|
||||
}
|
||||
};
|
||||
public:
|
||||
TilesVisitor visitAsTiles() const {
|
||||
return {*this, options.columnMajor,
|
||||
options.inputSlice.width ? options.inputSlice.width * 8 : width,
|
||||
options.inputSlice.height ? options.inputSlice.height * 8 : height};
|
||||
return {
|
||||
*this,
|
||||
options.columnMajor,
|
||||
options.inputSlice.width ? options.inputSlice.width * 8 : width,
|
||||
options.inputSlice.height ? options.inputSlice.height * 8 : height,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -497,7 +532,7 @@ struct AttrmapEntry {
|
||||
* attrmap entry while correctly handling the above, use `getPalID`.
|
||||
*/
|
||||
size_t protoPaletteID; // Only this field is used when outputting "unoptimized" data
|
||||
uint8_t tileID; // This is the ID as it will be output to the tilemap
|
||||
uint8_t tileID; // This is the ID as it will be output to the tilemap
|
||||
bool bank;
|
||||
bool yFlip;
|
||||
bool xFlip;
|
||||
@@ -523,8 +558,12 @@ static void generatePalSpec(Png const &png) {
|
||||
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 && i < embPalAlphaSize ? embPalAlpha[i] : 0xFF);
|
||||
options.palSpec[0][i] = Rgba(
|
||||
embPalRGB[i].red,
|
||||
embPalRGB[i].green,
|
||||
embPalRGB[i].blue,
|
||||
embPalAlpha && i < embPalAlphaSize ? embPalAlpha[i] : 0xFF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,8 +575,12 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
assert(mappings.size() == protoPalettes.size());
|
||||
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
fprintf(stderr, "Proto-palette mappings: (%zu palette%s)\n", nbPalettes,
|
||||
nbPalettes != 1 ? "s" : "");
|
||||
fprintf(
|
||||
stderr,
|
||||
"Proto-palette mappings: (%zu palette%s)\n",
|
||||
nbPalettes,
|
||||
nbPalettes != 1 ? "s" : ""
|
||||
);
|
||||
for (size_t i = 0; i < mappings.size(); ++i) {
|
||||
fprintf(stderr, "%zu -> %zu\n", i, mappings[i]);
|
||||
}
|
||||
@@ -615,8 +658,11 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
||||
mappings[i] = iter - palettes.begin(); // Bogus value, but whatever
|
||||
}
|
||||
if (bad) {
|
||||
fprintf(stderr, "note: The following palette%s specified:\n",
|
||||
palettes.size() == 1 ? " was" : "s were");
|
||||
fprintf(
|
||||
stderr,
|
||||
"note: The following palette%s specified:\n",
|
||||
palettes.size() == 1 ? " was" : "s were"
|
||||
);
|
||||
for (Palette const &pal : palettes) {
|
||||
fprintf(stderr, " [%s]\n", listColors(pal));
|
||||
}
|
||||
@@ -640,15 +686,17 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||
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);
|
||||
fatal(
|
||||
"Generated %zu palettes, over the maximum of %" PRIu8,
|
||||
palettes.size(),
|
||||
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));
|
||||
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
||||
}
|
||||
|
||||
for (Palette const &palette : palettes) {
|
||||
@@ -675,8 +723,8 @@ public:
|
||||
// of altering the element's hash, but the tile ID is not part of it.
|
||||
mutable uint16_t tileID;
|
||||
|
||||
static uint16_t rowBitplanes(Png::TilesVisitor::Tile const &tile, Palette const &palette,
|
||||
uint32_t y) {
|
||||
static uint16_t
|
||||
rowBitplanes(Png::TilesVisitor::Tile const &tile, Palette const &palette, uint32_t y) {
|
||||
uint16_t row = 0;
|
||||
for (uint32_t x = 0; x < 8; ++x) {
|
||||
row <<= 1;
|
||||
@@ -734,8 +782,9 @@ public:
|
||||
}
|
||||
|
||||
// Check if we have horizontal mirroring, which scans the array forward again
|
||||
if (std::equal(RANGE(_data), other._data.begin(),
|
||||
[](uint8_t lhs, uint8_t rhs) { return lhs == flipTable[rhs]; })) {
|
||||
if (std::equal(RANGE(_data), other._data.begin(), [](uint8_t lhs, uint8_t rhs) {
|
||||
return lhs == flipTable[rhs];
|
||||
})) {
|
||||
return MatchType::HFLIP;
|
||||
}
|
||||
|
||||
@@ -773,16 +822,20 @@ struct std::hash<TileData> {
|
||||
|
||||
namespace unoptimized {
|
||||
|
||||
static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static void outputTileData(
|
||||
Png const &png,
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||
}
|
||||
|
||||
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
|
||||
uint16_t heightTiles = options.inputSlice.height ? options.inputSlice.height : png.getHeight() / 8;
|
||||
uint16_t heightTiles =
|
||||
options.inputSlice.height ? options.inputSlice.height : png.getHeight() / 8;
|
||||
uint64_t remainingTiles = widthTiles * heightTiles;
|
||||
if (remainingTiles <= options.trim) {
|
||||
return;
|
||||
@@ -808,15 +861,15 @@ static void outputTileData(Png const &png, DefaultInitVec<AttrmapEntry> const &a
|
||||
assert(remainingTiles == 0);
|
||||
}
|
||||
|
||||
static void outputMaps(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static void outputMaps(
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
|
||||
auto autoOpenPath = [](std::string const &path, std::optional<File> &file) {
|
||||
if (!path.empty()) {
|
||||
file.emplace();
|
||||
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap),
|
||||
strerror(errno));
|
||||
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -864,8 +917,8 @@ struct UniqueTiles {
|
||||
/*
|
||||
* Adds a tile to the collection, and returns its ID
|
||||
*/
|
||||
std::tuple<uint16_t, TileData::MatchType> addTile(Png::TilesVisitor::Tile const &tile,
|
||||
Palette const &palette) {
|
||||
std::tuple<uint16_t, TileData::MatchType>
|
||||
addTile(Png::TilesVisitor::Tile const &tile, Palette const &palette) {
|
||||
TileData newTile(tile, palette);
|
||||
auto [tileData, inserted] = tileset.insert(newTile);
|
||||
|
||||
@@ -893,9 +946,12 @@ struct UniqueTiles {
|
||||
* 8-bit tile IDs + the bank bit; this will save the work when we output the data later (potentially
|
||||
* twice)
|
||||
*/
|
||||
static UniqueTiles dedupTiles(Png const &png, DefaultInitVec<AttrmapEntry> &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static UniqueTiles dedupTiles(
|
||||
Png const &png,
|
||||
DefaultInitVec<AttrmapEntry> &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
// Iterate throughout the image, generating tile data as we go
|
||||
// (We don't need the full tile data to be able to dedup tiles, but we don't lose anything
|
||||
// by caching the full tile data anyway, so we might as well.)
|
||||
@@ -941,8 +997,9 @@ static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
|
||||
}
|
||||
}
|
||||
|
||||
static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static void outputAttrmap(
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
|
||||
@@ -956,8 +1013,9 @@ static void outputAttrmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
}
|
||||
}
|
||||
|
||||
static void outputPalmap(DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
DefaultInitVec<size_t> const &mappings) {
|
||||
static void outputPalmap(
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
File output;
|
||||
if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
|
||||
fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno));
|
||||
@@ -1063,21 +1121,33 @@ void process() {
|
||||
}
|
||||
|
||||
if (nbColorsInTile > options.maxOpaqueColors()) {
|
||||
fatal("Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8 "!",
|
||||
tile.x, tile.y, nbColorsInTile, options.maxOpaqueColors());
|
||||
fatal(
|
||||
"Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8
|
||||
"!",
|
||||
tile.x,
|
||||
tile.y,
|
||||
nbColorsInTile,
|
||||
options.maxOpaqueColors()
|
||||
);
|
||||
}
|
||||
|
||||
attrs.protoPaletteID = protoPalettes.size();
|
||||
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
|
||||
fatal("Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
|
||||
AttrmapEntry::transparent);
|
||||
fatal(
|
||||
"Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
|
||||
AttrmapEntry::transparent
|
||||
);
|
||||
}
|
||||
protoPalettes.push_back(tileColors);
|
||||
contained:;
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_INTERM, "Image contains %zu proto-palette%s\n",
|
||||
protoPalettes.size(), protoPalettes.size() != 1 ? "s" : "");
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM,
|
||||
"Image contains %zu proto-palette%s\n",
|
||||
protoPalettes.size(),
|
||||
protoPalettes.size() != 1 ? "s" : ""
|
||||
);
|
||||
if (options.verbosity >= Options::VERB_INTERM) {
|
||||
for (auto const &protoPal : protoPalettes) {
|
||||
fputs("[ ", stderr);
|
||||
@@ -1102,8 +1172,12 @@ contained:;
|
||||
|
||||
// Check the tile count
|
||||
if (nbTilesW * nbTilesH > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||
fatal("Image contains %" PRIu32 " tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTilesW * nbTilesH, options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||
fatal(
|
||||
"Image contains %" PRIu32 " tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTilesW * nbTilesH,
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
}
|
||||
|
||||
if (!options.output.empty()) {
|
||||
@@ -1114,7 +1188,8 @@ contained:;
|
||||
if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) {
|
||||
options.verbosePrint(
|
||||
Options::VERB_LOG_ACT,
|
||||
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n");
|
||||
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
|
||||
);
|
||||
unoptimized::outputMaps(attrmap, mappings);
|
||||
}
|
||||
} else {
|
||||
@@ -1123,8 +1198,12 @@ contained:;
|
||||
optimized::UniqueTiles tiles = optimized::dedupTiles(png, attrmap, palettes, mappings);
|
||||
|
||||
if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||
fatal("Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||
tiles.size(), options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||
fatal(
|
||||
"Image contains %zu tiles, exceeding the limit of %" PRIu16 " + %" PRIu16,
|
||||
tiles.size(),
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
}
|
||||
|
||||
if (!options.output.empty()) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "gfx/proto_palette.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -10,6 +8,8 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
bool ProtoPalette::add(uint16_t color) {
|
||||
size_t i = 0;
|
||||
|
||||
|
||||
@@ -53,13 +53,19 @@ static DefaultInitVec<uint8_t> readInto(const std::string &path) {
|
||||
}
|
||||
|
||||
[[noreturn]] static void pngError(png_structp png, char const *msg) {
|
||||
fatal("Error writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)), msg);
|
||||
fatal(
|
||||
"Error writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)),
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
static void pngWarning(png_structp png, char const *msg) {
|
||||
warning("While writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)), msg);
|
||||
warning(
|
||||
"While writing reversed image (\"%s\"): %s",
|
||||
static_cast<char const *>(png_get_error_ptr(png)),
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
void writePng(png_structp png, png_bytep data, size_t length) {
|
||||
@@ -94,17 +100,23 @@ void reverse() {
|
||||
warning("\"Sliced-off\" pixels are ignored in reverse mode");
|
||||
}
|
||||
if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) {
|
||||
warning("Specified input slice width (%" PRIu16
|
||||
") doesn't match provided reversing width (%" PRIu16 " * 8)",
|
||||
options.inputSlice.width, options.reversedWidth);
|
||||
warning(
|
||||
"Specified input slice width (%" PRIu16
|
||||
") doesn't match provided reversing width (%" PRIu16 " * 8)",
|
||||
options.inputSlice.width,
|
||||
options.reversedWidth
|
||||
);
|
||||
}
|
||||
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
|
||||
auto const tiles = readInto(options.output);
|
||||
uint8_t tileSize = 8 * options.bitDepth;
|
||||
if (tiles.size() % tileSize != 0) {
|
||||
fatal("Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes",
|
||||
tiles.size(), tileSize);
|
||||
fatal(
|
||||
"Tile data size (%zu bytes) is not a multiple of %" PRIu8 " bytes",
|
||||
tiles.size(),
|
||||
tileSize
|
||||
);
|
||||
}
|
||||
|
||||
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
|
||||
@@ -121,25 +133,33 @@ void reverse() {
|
||||
fatal("Cannot generate empty image");
|
||||
}
|
||||
if (nbTileInstances > options.maxNbTiles[0] + options.maxNbTiles[1]) {
|
||||
warning("Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTileInstances, options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||
warning(
|
||||
"Read %zu tiles, more than the limit of %" PRIu16 " + %" PRIu16,
|
||||
nbTileInstances,
|
||||
options.maxNbTiles[0],
|
||||
options.maxNbTiles[1]
|
||||
);
|
||||
}
|
||||
|
||||
size_t width = options.reversedWidth, height; // In tiles
|
||||
if (nbTileInstances % width != 0) {
|
||||
fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
||||
nbTileInstances, width);
|
||||
fatal(
|
||||
"Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
||||
nbTileInstances,
|
||||
width
|
||||
);
|
||||
}
|
||||
height = nbTileInstances / width;
|
||||
|
||||
options.verbosePrint(Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width,
|
||||
height);
|
||||
options.verbosePrint(
|
||||
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
|
||||
);
|
||||
|
||||
// TODO: `-U` to configure tile size beyond 8x8px ("deduplication units")
|
||||
|
||||
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
|
||||
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
|
||||
};
|
||||
};
|
||||
// If a palette file is used as input, it overrides the default colors.
|
||||
if (!options.palettes.empty()) {
|
||||
File file;
|
||||
@@ -155,27 +175,37 @@ void reverse() {
|
||||
if (nbRead == buf.size()) {
|
||||
// Expand the colors
|
||||
auto &palette = palettes.emplace_back();
|
||||
std::generate(palette.begin(), palette.begin() + options.nbColorsPerPal,
|
||||
[&buf, i = 0]() mutable {
|
||||
i += 2;
|
||||
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
|
||||
});
|
||||
std::generate(
|
||||
palette.begin(),
|
||||
palette.begin() + options.nbColorsPerPal,
|
||||
[&buf, i = 0]() mutable {
|
||||
i += 2;
|
||||
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
|
||||
}
|
||||
);
|
||||
} else if (nbRead != 0) {
|
||||
fatal("Palette data size (%zu) is not a multiple of %zu bytes!\n",
|
||||
palettes.size() * buf.size() + nbRead, buf.size());
|
||||
fatal(
|
||||
"Palette data size (%zu) is not a multiple of %zu bytes!\n",
|
||||
palettes.size() * buf.size() + nbRead,
|
||||
buf.size()
|
||||
);
|
||||
}
|
||||
} while (nbRead != 0);
|
||||
|
||||
if (palettes.size() > options.nbPalettes) {
|
||||
warning("Read %zu palettes, more than the specified limit of %" PRIu8,
|
||||
palettes.size(), options.nbPalettes);
|
||||
warning(
|
||||
"Read %zu palettes, more than the specified limit of %" PRIu8,
|
||||
palettes.size(),
|
||||
options.nbPalettes
|
||||
);
|
||||
}
|
||||
|
||||
if (options.palSpecType == Options::EXPLICIT && palettes != options.palSpec) {
|
||||
warning("Colors in the palette file do not match those specified with `-c`!");
|
||||
}
|
||||
} else if (options.palSpecType == Options::EMBEDDED) {
|
||||
warning("An embedded palette was requested, but no palette file was specified; ignoring request.");
|
||||
warning("An embedded palette was requested, but no palette file was specified; ignoring "
|
||||
"request.");
|
||||
} else if (options.palSpecType == Options::EXPLICIT) {
|
||||
palettes = std::move(options.palSpec); // We won't be using it again.
|
||||
}
|
||||
@@ -184,8 +214,11 @@ void reverse() {
|
||||
if (!options.attrmap.empty()) {
|
||||
attrmap = readInto(options.attrmap);
|
||||
if (attrmap->size() != nbTileInstances) {
|
||||
fatal("Attribute map size (%zu tiles) doesn't match image's (%zu)", attrmap->size(),
|
||||
nbTileInstances);
|
||||
fatal(
|
||||
"Attribute map size (%zu tiles) doesn't match image's (%zu)",
|
||||
attrmap->size(),
|
||||
nbTileInstances
|
||||
);
|
||||
}
|
||||
|
||||
// Scan through the attributes for inconsistencies
|
||||
@@ -195,8 +228,9 @@ void reverse() {
|
||||
bool bad = false;
|
||||
for (auto attr : *attrmap) {
|
||||
if ((attr & 0b111) > palettes.size()) {
|
||||
error("Referencing palette %u, but there are only %zu!",
|
||||
attr & 0b111, palettes.size());
|
||||
error(
|
||||
"Referencing palette %u, but there are only %zu!", attr & 0b111, palettes.size()
|
||||
);
|
||||
bad = true;
|
||||
}
|
||||
if (attr & 0x08 && !tilemap) {
|
||||
@@ -213,16 +247,22 @@ void reverse() {
|
||||
for (auto [id, attr] : zip(*tilemap, *attrmap)) {
|
||||
bool bank = attr & 1 << 3;
|
||||
if (id >= options.maxNbTiles[bank]) {
|
||||
warning("Tile #%" PRIu8
|
||||
" was referenced, but the limit for bank %u is %" PRIu16,
|
||||
id, bank, options.maxNbTiles[bank]);
|
||||
warning(
|
||||
"Tile #%" PRIu8 " was referenced, but the limit for bank %u is %" PRIu16,
|
||||
id,
|
||||
bank,
|
||||
options.maxNbTiles[bank]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto id : *tilemap) {
|
||||
if (id >= options.maxNbTiles[0]) {
|
||||
warning("Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16, id,
|
||||
options.maxNbTiles[0]);
|
||||
warning(
|
||||
"Tile #%" PRIu8 " was referenced, but the limit is %" PRIu16,
|
||||
id,
|
||||
options.maxNbTiles[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,8 +272,11 @@ void reverse() {
|
||||
if (!options.palmap.empty()) {
|
||||
palmap = readInto(options.palmap);
|
||||
if (palmap->size() != nbTileInstances) {
|
||||
fatal("Palette map size (%zu tiles) doesn't match image's (%zu)", palmap->size(),
|
||||
nbTileInstances);
|
||||
fatal(
|
||||
"Palette map size (%zu tiles) doesn't match image's (%zu)",
|
||||
palmap->size(),
|
||||
nbTileInstances
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,8 +287,10 @@ void reverse() {
|
||||
}
|
||||
png_structp png = png_create_write_struct(
|
||||
PNG_LIBPNG_VER_STRING,
|
||||
const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))), pngError,
|
||||
pngWarning);
|
||||
const_cast<png_voidp>(static_cast<void const *>(pngFile.c_str(options.input))),
|
||||
pngError,
|
||||
pngWarning
|
||||
);
|
||||
if (!png) {
|
||||
fatal("Failed to create PNG write struct: %s", strerror(errno));
|
||||
}
|
||||
@@ -255,8 +300,17 @@ void reverse() {
|
||||
}
|
||||
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
||||
|
||||
png_set_IHDR(png, pngInfo, options.reversedWidth * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
png_set_IHDR(
|
||||
png,
|
||||
pngInfo,
|
||||
options.reversedWidth * 8,
|
||||
height * 8,
|
||||
8,
|
||||
PNG_COLOR_TYPE_RGB_ALPHA,
|
||||
PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT
|
||||
);
|
||||
png_write_info(png, pngInfo);
|
||||
|
||||
png_color_8 sbitChunk;
|
||||
@@ -270,10 +324,14 @@ void reverse() {
|
||||
size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL;
|
||||
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
|
||||
uint8_t * const rowPtrs[8] = {
|
||||
&tileRow.data()[0 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW],
|
||||
&tileRow.data()[2 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW],
|
||||
&tileRow.data()[4 * SIZEOF_ROW], &tileRow.data()[5 * SIZEOF_ROW],
|
||||
&tileRow.data()[6 * SIZEOF_ROW], &tileRow.data()[7 * SIZEOF_ROW],
|
||||
&tileRow.data()[0 * SIZEOF_ROW],
|
||||
&tileRow.data()[1 * SIZEOF_ROW],
|
||||
&tileRow.data()[2 * SIZEOF_ROW],
|
||||
&tileRow.data()[3 * SIZEOF_ROW],
|
||||
&tileRow.data()[4 * SIZEOF_ROW],
|
||||
&tileRow.data()[5 * SIZEOF_ROW],
|
||||
&tileRow.data()[6 * SIZEOF_ROW],
|
||||
&tileRow.data()[7 * SIZEOF_ROW],
|
||||
};
|
||||
|
||||
for (size_t ty = 0; ty < height; ++ty) {
|
||||
@@ -294,8 +352,22 @@ void reverse() {
|
||||
|
||||
// We do not have data for tiles trimmed with `-x`, so assume they are "blank"
|
||||
static std::array<uint8_t, 16> const trimmedTile{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
uint8_t const *tileData = tileID > nbTileInstances - options.trim
|
||||
? trimmedTile.data()
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
* with gaps in the scale curve filled by polynomial interpolation.
|
||||
*/
|
||||
static std::array<uint8_t, 256> reverse_curve{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
|
||||
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
|
||||
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
|
||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
|
||||
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // These
|
||||
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, // comments
|
||||
3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, // prevent
|
||||
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, // clang-format
|
||||
7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, // from
|
||||
10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, // reflowing
|
||||
13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, // these
|
||||
16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, // sixteen
|
||||
|
||||
Reference in New Issue
Block a user