diff --git a/tools/gxc/src/SpriteManifest.cpp b/tools/gxc/src/SpriteManifest.cpp index 1b2f1eb..50b1a56 100644 --- a/tools/gxc/src/SpriteManifest.cpp +++ b/tools/gxc/src/SpriteManifest.cpp @@ -31,15 +31,17 @@ struct Range } }; -static SpriteManifest::Format parseFormat(const nlohmann::json& jFormat) +static SpriteManifest::Format parseFormat(const nlohmann::json& jFormat, const bool hasImageFile) { auto value = jFormat.get(); if (value == "bmp" || value == "raw") return SpriteManifest::Format::bmp; else if (value == "rle") return SpriteManifest::Format::rle; + else if (value == "palette" && hasImageFile) + return SpriteManifest::Format::paletteImage; else if (value == "palette") - return SpriteManifest::Format::palette; + return SpriteManifest::Format::paletteArray; else throw std::runtime_error("Unknown format"); } @@ -115,6 +117,43 @@ static std::optional parseRange(std::string_view s) return std::nullopt; } +static BGRColour parseJSONPaletteColour(const std::string& s) +{ + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + if (s[0] == '#' && s.size() == 7) + { + // Expect #RRGGBB + r = std::stoul(s.substr(1, 2), nullptr, 16) & 0xFF; + g = std::stoul(s.substr(3, 2), nullptr, 16) & 0xFF; + b = std::stoul(s.substr(5, 2), nullptr, 16) & 0xFF; + } + return { b, g, r }; +} + +static std::vector parseColours(const nlohmann::json& jPalette) +{ + if (!jPalette.is_object()) + throw std::runtime_error("parseColours expects parameter jPalette to be an object"); + + auto jColours = jPalette["colours"]; + std::vector out; + + auto numColours = jColours.size(); + out.reserve(numColours * 3); + + for (auto& jColour : jColours) + { + if (!jColour.is_string()) + throw std::runtime_error("Encountered a value that is not a string!"); + + out.push_back(parseJSONPaletteColour(jColour.get())); + } + + return out; +} + static SpriteManifest::Entry parseEntry(const nlohmann::json& jEntry) { SpriteManifest::Entry result; @@ -145,21 +184,33 @@ static SpriteManifest::Entry parseEntry(const nlohmann::json& jEntry) } else { - result.path = jEntry.at("path").get(); + if (jEntry.contains("path")) + result.path = jEntry.at("path").get(); if (jEntry.contains("format")) - result.format = parseFormat(jEntry["format"]); + result.format = parseFormat(jEntry["format"], result.path.empty()); + if (jEntry.contains("palette")) result.palette = parsePalette(jEntry["palette"]); + if (jEntry.contains("colours")) + result.colours = parseColours(jEntry); + + if (result.format == SpriteManifest::Format::automatic && !result.colours.empty()) + result.format = SpriteManifest::Format::paletteArray; + if (jEntry.contains("x")) result.offsetX = jEntry["x"]; else if (jEntry.contains("x_offset")) result.offsetX = jEntry["x_offset"]; + else if (jEntry.contains("index")) // palette startIndex + result.offsetX = jEntry["index"]; // palette startIndex + if (jEntry.contains("y")) result.offsetY = jEntry["y"]; else if (jEntry.contains("y_offset")) result.offsetY = jEntry["y_offset"]; else if (jEntry.contains("zoom")) result.zoomOffset = jEntry["zoom"]; + if (jEntry.contains("srcX")) result.srcX = jEntry["srcX"]; if (jEntry.contains("srcY")) diff --git a/tools/gxc/src/SpriteManifest.h b/tools/gxc/src/SpriteManifest.h index 960b5c0..fa07684 100644 --- a/tools/gxc/src/SpriteManifest.h +++ b/tools/gxc/src/SpriteManifest.h @@ -7,6 +7,15 @@ namespace gxc { + struct BGRColour + { + uint8_t blue{}; + uint8_t green{}; + uint8_t red{}; + }; + + using ColourVector = std::vector; + class SpriteArchive; class SpriteManifest @@ -17,7 +26,8 @@ namespace gxc automatic, bmp, rle, - palette, + paletteImage, + paletteArray, empty }; @@ -32,6 +42,7 @@ namespace gxc fs::path path; Format format{}; PaletteKind palette{}; + ColourVector colours{}; int32_t offsetX{}; int32_t offsetY{}; int32_t zoomOffset{}; diff --git a/tools/gxc/src/gxc.cpp b/tools/gxc/src/gxc.cpp index 7845aad..3907830 100644 --- a/tools/gxc/src/gxc.cpp +++ b/tools/gxc/src/gxc.cpp @@ -32,7 +32,7 @@ static void convertPaletteToBmp(const GxEntry& entry, void* dst) } } -static void encodePalette(Stream& stream, const Image& image) +static void encodePaletteFromImage(Stream& stream, const Image& image) { const auto* src = image.pixels.data(); for (uint32_t x = 0; x < image.width; x++) @@ -60,6 +60,14 @@ static void encodePalette(Stream& stream, const Image& image) } } +static void encodePaletteFromArray(Stream& stream, const std::vector& colours) +{ + for (auto& colour : colours) + { + stream.write(&colour, 3); + } +} + static std::string stringifyFlags(uint16_t flags) { std::string result; @@ -136,6 +144,21 @@ int runBuild(const CommandLineOptions& options) } try { + // Palette arrays don't have an image to load, so start with those + if (manifestEntry.format == SpriteManifest::Format::paletteArray) + { + MemoryStream ms; + encodePaletteFromArray(ms, manifestEntry.colours); + + SpriteArchive::Entry entry; + entry.width = manifestEntry.colours.size(); + entry.height = 1; + entry.offsetX = manifestEntry.offsetX; + entry.flags = GxFlags::isPalette; + archive.addEntry(entry, ms.asSpan()); + continue; + } + auto imageCacheIterator = imageCache.find(manifestEntry.path); if (imageCacheIterator == imageCache.end()) { @@ -151,10 +174,10 @@ int runBuild(const CommandLineOptions& options) img = img.crop(manifestEntry.srcX, manifestEntry.srcY, manifestEntry.srcWidth, manifestEntry.srcHeight); } - if (manifestEntry.format == SpriteManifest::Format::palette) + if (manifestEntry.format == SpriteManifest::Format::paletteImage) { MemoryStream ms; - encodePalette(ms, img); + encodePaletteFromImage(ms, img); SpriteArchive::Entry entry; entry.width = img.width;