summaryrefslogtreecommitdiff
path: root/tools/generate-tiles/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tools/generate-tiles/main.cc')
-rw-r--r--tools/generate-tiles/main.cc297
1 files changed, 297 insertions, 0 deletions
diff --git a/tools/generate-tiles/main.cc b/tools/generate-tiles/main.cc
new file mode 100644
index 0000000..82cf38a
--- /dev/null
+++ b/tools/generate-tiles/main.cc
@@ -0,0 +1,297 @@
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <optional>
+#include <filesystem>
+#include <cassert>
+#include <cstdlib>
+#include <cstdint>
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
+
+namespace
+{
+ enum class type
+ {
+ mask,
+ image,
+ };
+
+ enum class status
+ {
+ ok,
+ invalid,
+ };
+
+ struct rgba
+ {
+ explicit rgba() = default;
+
+ explicit rgba(std::uint8_t const r, std::uint8_t const g, std::uint8_t const b, std::uint8_t const a)
+ : r{ r }
+ , g{ g }
+ , b{ b }
+ , a{ a }
+ {
+ }
+
+ static auto from_hex(std::uint32_t const hex) -> rgba
+ {
+ auto res = rgba{};
+ res.r = (hex & 0xFF000000) >> 24;
+ res.g = (hex & 0x00FF0000) >> 16;
+ res.b = (hex & 0x0000FF00) >> 8;
+ res.a = (hex & 0x000000FF) >> 0;
+ return res;
+ }
+
+ std::uint8_t r{ 0 };
+ std::uint8_t g{ 0 };
+ std::uint8_t b{ 0 };
+ std::uint8_t a{ 0 };
+ };
+
+ auto operator==(rgba const lhs, rgba const rhs) -> bool
+ {
+ return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a;
+ }
+
+ /// A decoded image.
+ class image
+ {
+ std::string name_;
+ std::unique_ptr<unsigned char[], void(*)(void*)> data_;
+ unsigned width_{ 0 };
+ unsigned height_{ 0 };
+
+ explicit image(std::string name, unsigned char* data, unsigned const width, unsigned const height)
+ : name_{ std::move(name) }
+ , data_{ data, stbi_image_free }
+ , width_{ width }
+ , height_{ height }
+ {
+ }
+
+ public:
+ static auto load(char const* path) -> std::optional<image>
+ {
+ int x, y;
+ auto const data = stbi_load(path, &x, &y, nullptr, 4);
+ if (!data) { return std::nullopt; }
+ assert(x >= 0);
+ assert(y >= 0);
+
+ auto name = std::string{ std::filesystem::path(path).stem() };
+ for (auto& c : name)
+ {
+ if (c >= 'a' && c <= 'z') { continue; }
+ if (c >= '0' && c <= '9') { continue; }
+ if (c >= 'A' && c <= 'Z')
+ {
+ c = (c - 'A') + 'a';
+ continue;
+ }
+ c = '_';
+ }
+
+ return image{ std::move(name), data, static_cast<unsigned>(x), static_cast<unsigned>(y) };
+ }
+
+ auto get_name() const -> std::string_view { return name_; }
+ auto get_width() const -> unsigned { return width_; }
+ auto get_height() const -> unsigned { return height_; }
+ auto get(unsigned const x, unsigned const y) const -> rgba
+ {
+ assert(x < width_);
+ assert(y < height_);
+
+ auto const offset = y * width_ + x;
+ return rgba{ data_[offset * 4 + 0], data_[offset * 4 + 1], data_[offset * 4 + 2], data_[offset * 4 + 3] };
+ }
+ };
+
+ auto decode_type(std::string_view const t) -> std::optional<type>
+ {
+ if (t == "mask") { return type::mask; }
+ if (t == "image") { return type::image; }
+ return std::nullopt;
+ }
+
+ auto encode_mask(std::stringstream& header, std::stringstream& source, image const& img) -> status
+ {
+ if (img.get_width() != 8) { return status::invalid; }
+ if (img.get_height() != 8) { return status::invalid; }
+
+ header << "\textern mask_tile const " << img.get_name() << ";\n";
+
+ source << "\textern auto const " << img.get_name() << " = mask_tile\n";
+ source << "\t{\n";
+ for (auto y = unsigned{ 0 }; y < img.get_height(); ++y)
+ {
+ source << "\t\t0b";
+ for (auto x = unsigned{ 0 }; x < img.get_width(); ++x)
+ {
+ if (img.get(x, y).a > 0)
+ {
+ source << '1';
+ }
+ else
+ {
+ source << '0';
+ }
+ }
+ source << ",\n";
+ }
+ source << "\t};\n\n";
+
+ return status::ok;
+ }
+
+ /// Generate the constructor call for a single image tile.
+ auto generate_image_tile(std::stringstream& str, image const& img, unsigned const x, unsigned const y) -> status
+ {
+ str << "\t{\n";
+ for (auto y_px = unsigned{ 0 }; y_px < 8; ++y_px)
+ {
+ str << "\t\t";
+ for (auto x_px = unsigned{ 0 }; x_px < 8; ++x_px)
+ {
+ auto const color = img.get(x * 8 + x_px, y * 8 + y_px);
+ str << "color{ ";
+ if (color.a == 0) { str << "0x0"; }
+ else if (color == rgba::from_hex(0x004058FF)) { str << "0x1"; }
+ else if (color == rgba::from_hex(0x002B3BFF)) { str << "0x2"; }
+ else if (color == rgba::from_hex(0xFCFCFCFF)) { str << "0x3"; }
+ else if (color == rgba::from_hex(0x008888FF)) { str << "0x4"; }
+ else if (color == rgba::from_hex(0xE45C10FF)) { str << "0x5"; }
+ else if (color == rgba::from_hex(0x881400FF)) { str << "0x6"; }
+ else if (color == rgba::from_hex(0xFCFCFCFF)) { str << "0x7"; }
+ else if (color == rgba::from_hex(0xBCBCBCFF)) { str << "0x8"; }
+ else if (color == rgba::from_hex(0x000000FF)) { str << "0x9"; }
+ else { return status::invalid; }
+ str << " }, ";
+ }
+ str << "\n";
+ }
+ str << "\t}";
+
+ return status::ok;
+ }
+
+ auto encode_image(std::stringstream& header, std::stringstream& source, image const& img) -> status
+ {
+ if (img.get_width() % 8 != 0) { return status::invalid; }
+ if (img.get_height() % 8 != 0) { return status::invalid; }
+
+ if (img.get_width() == 8 && img.get_height() == 8)
+ {
+ header << "\textern image_tile const " << img.get_name() << ";\n";
+
+ source << "\textern auto const " << img.get_name() << " = image_tile\n";
+ if (generate_image_tile(source, img, 0, 0) != status::ok) { return status::invalid; }
+ source << ";\n\n";
+ }
+ else
+ {
+ auto const width_tiles = img.get_width() / 8;
+ auto const height_tiles = img.get_height() / 8;
+
+ header << "\textern image_view const " << img.get_name() << ";\n";
+
+ source << "\tstatic image_tile const " << img.get_name() << "_DATA[] = \n";
+ source << "\t{\n";
+ for (auto y = unsigned{ 0 }; y < height_tiles; ++y)
+ {
+ for (auto x = unsigned{ 0 }; x < width_tiles; ++x)
+ {
+ if (generate_image_tile(source, img, x, y) != status::ok) { return status::invalid; }
+ source << ",\n";
+ }
+ }
+ source << "\t};\n";
+ source << "\textern auto const " << img.get_name()
+ << " = image_view{ " << img.get_name() << "_DATA, " << width_tiles << ", " << height_tiles << " };\n\n";
+ }
+
+ return status::ok;
+ }
+} // namespace
+
+int main(int const argc, char** const argv)
+{
+ if (argc < 5)
+ {
+ std::cerr << "Usage:\n";
+ std::cerr << " " << argv[0] << " <mask|image> <output-header> <output-source> <files...>" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ auto const t = decode_type(argv[1]);
+ if (!t)
+ {
+ std::cerr << "Invalid type: " << argv[1] << std::endl;
+ return EXIT_FAILURE;
+ }
+ auto const output_header = std::string_view{ argv[2] };
+ auto const output_source = std::string_view{ argv[3] };
+
+ auto header = std::stringstream{};
+ auto source = std::stringstream{};
+
+ header << "#pragma once\n";
+ header << "\n";
+ header << "#include \"nes/app/graphics/mask-tile.hh\"\n";
+ header << "#include \"nes/app/graphics/image-tile.hh\"\n";
+ header << "#include \"nes/app/graphics/image-view.hh\"\n";
+ header << "\n";
+ header << "namespace nes::app::tiles\n";
+ header << "{\n";
+
+ source << "#include \"nes/app/graphics/mask-tile.hh\"\n";
+ source << "#include \"nes/app/graphics/image-tile.hh\"\n";
+ source << "#include \"nes/app/graphics/image-view.hh\"\n";
+ source << "\n";
+ source << "namespace nes::app::tiles\n";
+ source << "{\n\n";
+
+ for (auto i = 4; i < argc; ++i)
+ {
+ auto const img = image::load(argv[i]);
+ if (!img)
+ {
+ std::cerr << "Unable to load file: " << argv[i] << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ auto res = status::invalid;
+ switch (*t)
+ {
+ case type::mask:
+ res = encode_mask(header, source, *img);
+ break;
+ case type::image:
+ res = encode_image(header, source, *img);
+ break;
+ }
+
+ if (res != status::ok)
+ {
+ std::cerr << "Input has invalid dimensions: " << argv[i] << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+
+ header << "} // namespace nes::app::tiles\n";
+
+ source << "} // namespace nes::app::tiles\n";
+
+ auto header_file = std::ofstream{ output_header };
+ header_file << header.str();
+ header_file.flush();
+
+ auto source_file = std::ofstream{ output_source };
+ source_file << source.str();
+ source_file.flush();
+
+ return EXIT_SUCCESS;
+} \ No newline at end of file