mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Implement some external palette specs
PSP, ACT, and ACO are complete
This commit is contained in:
@@ -9,8 +9,7 @@
|
|||||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
#define RGBDS_GFX_PAL_SPEC_HPP
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const arg);
|
void parseInlinePalSpec(char const * const arg);
|
||||||
|
void parseExternalPalSpec(char const *arg);
|
||||||
|
|
||||||
#endif /* RGBDS_GFX_PAL_SPEC_HPP */
|
#endif /* RGBDS_GFX_PAL_SPEC_HPP */
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include "extern/getopt.h"
|
#include "extern/getopt.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
using namespace std::literals::string_view_literals;
|
using namespace std::literals::string_view_literals;
|
||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
char const *externalPalSpec = nullptr;
|
||||||
static uintmax_t nbErrors;
|
static uintmax_t nbErrors;
|
||||||
|
|
||||||
void warning(char const *fmt, ...) {
|
void warning(char const *fmt, ...) {
|
||||||
@@ -222,21 +224,6 @@ static void skipWhitespace(char *&arg) {
|
|||||||
arg += strcspn(arg, " \t");
|
arg += strcspn(arg, " \t");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parsePaletteSpec(char const *arg) {
|
|
||||||
if (arg[0] == '#') {
|
|
||||||
options.palSpecType = Options::EXPLICIT;
|
|
||||||
parseInlinePalSpec(arg);
|
|
||||||
} else if (strcasecmp(arg, "embedded") == 0) {
|
|
||||||
// Use PLTE, error out if missing
|
|
||||||
options.palSpecType = Options::EMBEDDED;
|
|
||||||
} else {
|
|
||||||
// `fmt:path`, parse the file according to the given format
|
|
||||||
// TODO: split both parts, error out if malformed or file not found
|
|
||||||
options.palSpecType = Options::EXPLICIT;
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void registerInput(char const *arg) {
|
static void registerInput(char const *arg) {
|
||||||
if (!options.input.empty()) {
|
if (!options.input.empty()) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -375,7 +362,20 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
|||||||
options.useColorCurve = true;
|
options.useColorCurve = true;
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
parsePaletteSpec(musl_optarg);
|
if (musl_optarg[0] == '#') {
|
||||||
|
options.palSpecType = Options::EXPLICIT;
|
||||||
|
parseInlinePalSpec(musl_optarg);
|
||||||
|
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
||||||
|
// Use PLTE, error out if missing
|
||||||
|
options.palSpecType = Options::EMBEDDED;
|
||||||
|
} else {
|
||||||
|
options.palSpecType = Options::EXPLICIT;
|
||||||
|
// Can't parse the file yet, as "flat" color collections need to know the palette
|
||||||
|
// size to be split; thus, we defer that
|
||||||
|
// TODO: this does not validate the `fmt` part of any external spec but the last
|
||||||
|
// one, but I guess that's okay
|
||||||
|
externalPalSpec = musl_optarg;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'D':
|
case 'D':
|
||||||
warning("Ignoring retired option `-D`");
|
warning("Ignoring retired option `-D`");
|
||||||
@@ -611,6 +611,11 @@ int main(int argc, char *argv[]) {
|
|||||||
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
|
autoOutPath(autoTilemap, options.tilemap, ".tilemap");
|
||||||
autoOutPath(autoPalettes, options.palettes, ".pal");
|
autoOutPath(autoPalettes, options.palettes, ".pal");
|
||||||
|
|
||||||
|
// Execute deferred external pal spec parsing, now that all other params are known
|
||||||
|
if (externalPalSpec) {
|
||||||
|
parseExternalPalSpec(externalPalSpec);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_CFG) {
|
if (options.verbosity >= Options::VERB_CFG) {
|
||||||
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
||||||
|
|
||||||
@@ -706,7 +711,8 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// Do not do anything if option parsing went wrong
|
// Do not do anything if option parsing went wrong
|
||||||
if (nbErrors) {
|
if (nbErrors) {
|
||||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors,
|
||||||
|
nbErrors == 1 ? "" : "s");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,7 +723,8 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nbErrors) {
|
if (nbErrors) {
|
||||||
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors, nbErrors == 1 ? "" : "s");
|
fprintf(stderr, "Conversion aborted after %ju error%s\n", nbErrors,
|
||||||
|
nbErrors == 1 ? "" : "s");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -10,7 +10,21 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <streambuf>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
@@ -37,6 +51,11 @@ constexpr uint8_t singleToHex(char c) {
|
|||||||
return toHex(c, c);
|
return toHex(c, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Str> // Should be std::string or std::string_view
|
||||||
|
static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
|
||||||
|
pos = std::min(str.find_first_not_of(" \t", pos), str.length());
|
||||||
|
}
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const rawArg) {
|
void parseInlinePalSpec(char const * const rawArg) {
|
||||||
// List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
|
// List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
|
||||||
|
|
||||||
@@ -63,13 +82,8 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto skipWhitespace = [&arg](size_type &pos) {
|
|
||||||
pos = std::min(arg.find_first_not_of(" \t", pos), arg.length());
|
|
||||||
};
|
|
||||||
|
|
||||||
options.palSpec.clear();
|
options.palSpec.clear();
|
||||||
options.palSpec
|
options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros
|
||||||
.emplace_back(); // Not default-initialized, but value-initialized, so we get zeros
|
|
||||||
|
|
||||||
size_type n = 0; // Index into the argument
|
size_type n = 0; // Index into the argument
|
||||||
// TODO: store max `nbColors` ever reached, and compare against palette size later
|
// TODO: store max `nbColors` ever reached, and compare against palette size later
|
||||||
@@ -98,7 +112,7 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
n = pos;
|
n = pos;
|
||||||
|
|
||||||
// Skip whitespace, if any
|
// Skip whitespace, if any
|
||||||
skipWhitespace(n);
|
skipWhitespace(arg, n);
|
||||||
|
|
||||||
// Skip comma/colon, or end
|
// Skip comma/colon, or end
|
||||||
if (n == arg.length()) {
|
if (n == arg.length()) {
|
||||||
@@ -111,7 +125,7 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
++nbColors;
|
++nbColors;
|
||||||
|
|
||||||
// A trailing comma may be followed by a colon
|
// A trailing comma may be followed by a colon
|
||||||
skipWhitespace(n);
|
skipWhitespace(arg, n);
|
||||||
if (n == arg.length()) {
|
if (n == arg.length()) {
|
||||||
break;
|
break;
|
||||||
} else if (arg[n] != ':') {
|
} else if (arg[n] != ':') {
|
||||||
@@ -125,7 +139,7 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
|
|
||||||
case ':':
|
case ':':
|
||||||
++n;
|
++n;
|
||||||
skipWhitespace(n);
|
skipWhitespace(arg, n);
|
||||||
|
|
||||||
nbColors = 0; // Start a new palette
|
nbColors = 0; // Start a new palette
|
||||||
// Avoid creating a spurious empty palette
|
// Avoid creating a spurious empty palette
|
||||||
@@ -149,3 +163,289 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to read some magic bytes from the provided `file`.
|
||||||
|
* Returns whether the magic was correctly read.
|
||||||
|
*/
|
||||||
|
template<size_t n>
|
||||||
|
static bool readMagic(std::filebuf &file, char const *magic) {
|
||||||
|
assert(strlen(magic) == n);
|
||||||
|
|
||||||
|
char magicBuf[n];
|
||||||
|
return file.sgetn(magicBuf, n) == n && memcmp(magicBuf, magic, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like `readMagic`, but automatically determines the size from the string literal's length.
|
||||||
|
// Don't worry if you make a mistake, an `assert`'s got your back!
|
||||||
|
#define READ_MAGIC(file, magic) \
|
||||||
|
readMagic<sizeof(magic) - 1>(file, magic) // Don't count the terminator
|
||||||
|
|
||||||
|
template<typename T, typename U>
|
||||||
|
static T readBE(U const *bytes) {
|
||||||
|
T val = 0;
|
||||||
|
for (size_t i = 0; i < sizeof(val); ++i) {
|
||||||
|
val = val << 8 | static_cast<uint8_t>(bytes[i]);
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Appends** the first line read from `file` to the end of the provided `buffer`.
|
||||||
|
*/
|
||||||
|
static void readLine(std::filebuf &file, std::string &buffer) {
|
||||||
|
// TODO: maybe this can be optimized to bulk reads?
|
||||||
|
for (;;) {
|
||||||
|
auto c = file.sbumpc();
|
||||||
|
if (c == std::filebuf::traits_type::eof()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (c == '\n') {
|
||||||
|
// Discard a trailing CRLF
|
||||||
|
if (!buffer.empty() && buffer.back() == '\r') {
|
||||||
|
buffer.pop_back();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Normally we'd use `std::from_chars`, but that's not available with GCC 7
|
||||||
|
/**
|
||||||
|
* Parses the initial part of a string_view, advancing the "read index" as it does
|
||||||
|
*/
|
||||||
|
static uint16_t parseDec(std::string const &str, std::string::size_type &n) {
|
||||||
|
uint32_t value = 0; // Use a larger type to handle overflow more easily
|
||||||
|
for (auto end = std::min(str.length(), str.find_first_not_of("0123456789", n)); n < end; ++n) {
|
||||||
|
value = std::min<uint32_t>(value * 10 + (str[n] - '0'), UINT16_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parsePSPFile(std::filebuf &file) {
|
||||||
|
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
readLine(file, line);
|
||||||
|
if (line != "JASC-PAL") {
|
||||||
|
error("Palette file does not appear to be a PSP palette file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.clear();
|
||||||
|
readLine(file, line);
|
||||||
|
if (line != "0100") {
|
||||||
|
error("Unsupported PSP palette file version \"%s\"", line.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.clear();
|
||||||
|
readLine(file, line);
|
||||||
|
std::string::size_type n = 0;
|
||||||
|
uint16_t nbColors = parseDec(line, n);
|
||||||
|
if (n != line.length()) {
|
||||||
|
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
||||||
|
warning("PSP file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||||
|
"; ignoring extra",
|
||||||
|
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
||||||
|
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.palSpec.clear();
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < nbColors; ++i) {
|
||||||
|
line.clear();
|
||||||
|
readLine(file, line);
|
||||||
|
n = 0;
|
||||||
|
|
||||||
|
// TODO: parse R G B
|
||||||
|
uint8_t r = parseDec(line, n);
|
||||||
|
skipWhitespace(line, n);
|
||||||
|
if (n == line.length()) {
|
||||||
|
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
||||||
|
line.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t g = parseDec(line, n);
|
||||||
|
if (n == line.length()) {
|
||||||
|
error("Failed to parse color #%" PRIu16 " (\"%s\"): missing green component", i + 1,
|
||||||
|
line.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
skipWhitespace(line, n);
|
||||||
|
uint8_t b = parseDec(line, n);
|
||||||
|
if (n != line.length()) {
|
||||||
|
error("Failed to parse color #%" PRIu16
|
||||||
|
" (\"%s\"): trailing characters after blue component",
|
||||||
|
i + 1, line.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % options.nbColorsPerPal == 0) {
|
||||||
|
options.palSpec.emplace_back();
|
||||||
|
}
|
||||||
|
options.palSpec.back()[i % options.nbColorsPerPal] = Rgba(r, g, b, 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseACTFile(std::filebuf &file) {
|
||||||
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1070626
|
||||||
|
|
||||||
|
std::array<char, 772> buf;
|
||||||
|
auto len = file.sgetn(buf.data(), buf.size());
|
||||||
|
|
||||||
|
uint16_t nbColors = 256;
|
||||||
|
if (len == 772) {
|
||||||
|
nbColors = readBE<uint16_t>(&buf[768]);
|
||||||
|
// TODO: apparently there is a "transparent color index"? What?
|
||||||
|
if (nbColors > 256 || nbColors == 0) {
|
||||||
|
error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (len != 768) {
|
||||||
|
error("Invalid file size for ACT file (expected 768 or 772 bytes, got %zu", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
||||||
|
warning("ACT file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||||
|
"; ignoring extra",
|
||||||
|
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
||||||
|
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.palSpec.clear();
|
||||||
|
options.palSpec.emplace_back();
|
||||||
|
|
||||||
|
char const *ptr = buf.data();
|
||||||
|
size_t colorIdx = 0;
|
||||||
|
for (uint16_t i = 0; i < nbColors; ++i) {
|
||||||
|
Rgba &color = options.palSpec.back()[colorIdx];
|
||||||
|
color = Rgba(ptr[0], ptr[1], ptr[2], 0xFF);
|
||||||
|
|
||||||
|
ptr += 3;
|
||||||
|
++colorIdx;
|
||||||
|
if (colorIdx == options.nbColorsPerPal) {
|
||||||
|
options.palSpec.emplace_back();
|
||||||
|
colorIdx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the spurious empty palette if there is one
|
||||||
|
if (colorIdx == 0) {
|
||||||
|
options.palSpec.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseACOFile(std::filebuf &file) {
|
||||||
|
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819
|
||||||
|
// http://www.nomodes.com/aco.html
|
||||||
|
|
||||||
|
char buf[10];
|
||||||
|
|
||||||
|
if (file.sgetn(buf, 2) != 2) {
|
||||||
|
error("Couldn't read ACO file version");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (readBE<uint16_t>(buf) != 1) {
|
||||||
|
error("Palette file does not appear to be an ACO file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.sgetn(buf, 2) != 2) {
|
||||||
|
error("Couldn't read number of colors in palette file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint16_t nbColors = readBE<uint16_t>(buf);
|
||||||
|
|
||||||
|
if (nbColors > options.nbColorsPerPal * options.nbPalettes) {
|
||||||
|
warning("ACO file contains %" PRIu16 " colors, but there can only be %" PRIu16
|
||||||
|
"; ignoring extra",
|
||||||
|
nbColors, options.nbColorsPerPal * options.nbPalettes);
|
||||||
|
nbColors = options.nbColorsPerPal * options.nbPalettes;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.palSpec.clear();
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < nbColors; ++i) {
|
||||||
|
if (file.sgetn(buf, 10) != 10) {
|
||||||
|
error("Failed to read color #%" PRIu16 " from palette file", i + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % options.nbColorsPerPal == 0) {
|
||||||
|
options.palSpec.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
Rgba &color = options.palSpec.back()[i % options.nbColorsPerPal];
|
||||||
|
uint16_t colorType = readBE<uint16_t>(buf);
|
||||||
|
switch (colorType) {
|
||||||
|
case 0: // RGB
|
||||||
|
color = Rgba(buf[0], buf[2], buf[4], 0xFF);
|
||||||
|
break;
|
||||||
|
case 1: // HSB
|
||||||
|
error("Unsupported color type (HSB) for ACO file");
|
||||||
|
return;
|
||||||
|
case 2: // CMYK
|
||||||
|
error("Unsupported color type (CMYK) for ACO file");
|
||||||
|
return;
|
||||||
|
case 7: // Lab
|
||||||
|
error("Unsupported color type (lab) for ACO file");
|
||||||
|
return;
|
||||||
|
case 8: // Grayscale
|
||||||
|
error("Unsupported color type (grayscale) for ACO file");
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
error("Unknown color type (%" PRIu16 ") for ACO file", colorType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: maybe scan the v2 data instead (if present)
|
||||||
|
// `codecvt` can be used to convert from UTF-16 to UTF-8
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseExternalPalSpec(char const *arg) {
|
||||||
|
// `fmt:path`, parse the file according to the given format
|
||||||
|
|
||||||
|
// Split both parts, error out if malformed
|
||||||
|
char const *ptr = strchr(arg, ':');
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
error("External palette spec must have format `fmt:path` (missing colon)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char const *path = ptr + 1;
|
||||||
|
|
||||||
|
static std::array parsers{
|
||||||
|
std::tuple{"PSP", &parsePSPFile, std::ios::in },
|
||||||
|
std::tuple{"ACT", &parseACTFile, std::ios::binary},
|
||||||
|
std::tuple{"ACO", &parseACOFile, std::ios::binary},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto iter = std::find_if(parsers.begin(), parsers.end(),
|
||||||
|
[&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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filebuf file;
|
||||||
|
// Some parsers read the file in text mode, others in binary mode
|
||||||
|
if (!file.open(path, std::ios::in | std::get<2>(*iter))) {
|
||||||
|
error("Failed to open palette file \"%s\"", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::get<1> (*iter)(file);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user