diff options
m--------- | corrade | 0 | ||||
-rw-r--r-- | crop-tool/crop-tool.cpp | 139 | ||||
-rw-r--r-- | crop-tool/serialize.cpp | 132 | ||||
-rw-r--r-- | crop-tool/serialize.hpp | 27 | ||||
-rw-r--r-- | doc/atlas.json | 18 | ||||
-rw-r--r-- | doc/atlas.json.example | 18 | ||||
m--------- | magnum | 0 | ||||
m--------- | magnum-plugins | 0 |
8 files changed, 190 insertions, 144 deletions
diff --git a/corrade b/corrade -Subproject d44ca683c12ed28fce1d27662f24edee2d7e2c6 +Subproject 9f559f59af07fee2a36b1377b6a7a1e8a6e6e81 diff --git a/crop-tool/crop-tool.cpp b/crop-tool/crop-tool.cpp index 6de44a96..0e92946b 100644 --- a/crop-tool/crop-tool.cpp +++ b/crop-tool/crop-tool.cpp @@ -21,32 +21,18 @@ #ifdef _WIN32 # define EX_OK 0 /* successful termination */ # define EX_USAGE 64 /* command line usage error */ +# define EX_DATAERR 65 /* data format error */ # define EX_SOFTWARE 70 /* internal software error */ +# define EX_CANTCREAT 73 /* can't create (user) output file */ # define EX_IOERR 74 /* input/output error */ #else # include <sysexits.h> #endif -#if 0 -static std::string fix_path_separators(const std::filesystem::path& path) -{ - auto str = path.string(); - std::replace(str.begin(), str.end(), '\\', '/'); - return str; -} -#endif - struct file { - std::filesystem::path name; cv::Mat4b mat; - cv::Point2i offset; -}; - -struct dir -{ - std::filesystem::path name; - std::vector<file> files; + Magnum::Vector2i ground_offset; }; using Corrade::Utility::Error; @@ -55,12 +41,13 @@ using Corrade::Utility::Debug; static struct options_ { std::optional<unsigned> width, height; std::optional<double> scale; - cv::Vec2i offset{-1, -1}; } options; +using std::filesystem::path; + static std::tuple<cv::Vec2i, cv::Vec2i, bool> -find_image_bounds(const std::filesystem::path& path, const cv::Mat4b& mat) +find_image_bounds(const path& path, const cv::Mat4b& mat) { cv::Vec2i start{mat.cols, mat.rows}, end{0, 0}; for (int y = 0; y < mat.rows; y++) @@ -81,27 +68,27 @@ find_image_bounds(const std::filesystem::path& path, const cv::Mat4b& mat) } if (start[0] >= end[0] || start[1] >= end[1]) { - Error{} << "image" << path.string() << "contains only fully transparent pixels!"; + Error{} << "image" << path << "contains only fully transparent pixels!"; return {{}, {}, false}; } return {start, end, true}; } -static std::optional<file> load_file(const std::filesystem::path& filename) +static bool load_file(anim_group& group, const path& filename, const path& output_filename) { auto mat = progn( cv::Mat mat_ = cv::imread(filename.string(), cv::IMREAD_UNCHANGED); if (mat_.empty() || mat_.type() != CV_8UC4) { - Error{} << "failed to load" << filename.string() << "as RGBA32 image"; + Error{} << "failed to load" << filename << "as RGBA32 image"; return cv::Mat4b{}; } return cv::Mat4b(std::move(mat_)); ); if (mat.empty()) - return {}; + return false; auto [start, end, bounds_ok] = find_image_bounds(filename, mat); @@ -120,55 +107,56 @@ static std::optional<file> load_file(const std::filesystem::path& filename) std::abort(); } - cv::Vec2i offset = { - (int)std::round((options.offset[0] - start[0]) * *options.scale), - (int)std::round((options.offset[1] - start[1]) * *options.scale), - }; - dest_size = {(int)std::round(*options.scale * size.width), (int)std::round(*options.scale * size.height)}; if (size.width < dest_size.width || size.height < dest_size.height) { - Error{} << "refusing to upscale image" << filename.string(); + Error{} << "refusing to upscale image" << filename; return {}; } - Debug{} << "file" << filename.string() << offset[0] << offset[1]; - cv::Mat4b resized{size}; cv::resize(mat({start, size}), resized, dest_size, 0, 0, cv::INTER_LANCZOS4); - file ret { filename, resized.clone(), offset }; - return std::make_optional(std::move(ret)); + if (!cv::imwrite(output_filename.string(), resized)) + { + Error{} << "failed writing image" << output_filename; + return false; + } + Magnum::Vector2i ground = { + (int)std::round((group.ground[0] - start[0]) * *options.scale), + (int)std::round((group.ground[1] - start[1]) * *options.scale), + }; + group.frames.push_back({ground}); + return true; } -static std::optional<dir> load_directory(const std::filesystem::path& dirname) +static bool load_directory(anim_group& group, const path& dirname, const path& output_dir) { if (std::error_code ec{}; !std::filesystem::exists(dirname / ".", ec)) { - Error{} << "can't open directory" << dirname.string() << ":" << ec.message(); - return std::nullopt; + Error{} << "can't open directory" << dirname << ':' << ec.message(); + return {}; } - dir ret; - for (int i = 1; i <= 9999; i++) + int i; + for (i = 1; i <= 9999; i++) { char buf[9]; sprintf(buf, "%04d.png", i); - auto path = dirname / buf; - if (!std::filesystem::exists(path)) + if (!std::filesystem::exists(dirname/buf)) break; - auto file = load_file(path); - if (!file) - return std::nullopt; - ret.files.push_back(std::move(*file)); + if (!load_file(group, dirname/buf, output_dir/buf)) + return false; } - if (ret.files.empty()) + + if (i == 1) { - Error{} << "directory" << dirname.string() << "is empty!"; - return std::nullopt; + Error{} << "no files in anim group directory" << dirname; + return false; } - return std::make_optional(std::move(ret)); + + return true; } int main(int argc, char** argv) @@ -188,14 +176,12 @@ int main(int argc, char** argv) args.addOption('o', "output", "./output") .addArgument("directory") .addOption('W', "width", "") - .addOption('H', "height", "") - .addOption('x', "offset") - .setHelp("offset", {}, "WxH"); + .addOption('H', "height", ""); args.parse(argc, argv); - std::filesystem::path output = args.value<std::string>("output"); - std::filesystem::path pathname = args.value<std::string>("directory"); - std::vector<dir> dirs; - auto anim_info = anim::from_json(pathname / "atlas.json"); + const path output_dir = args.value<std::string>("output"); + const path input_dir = args.value<std::string>("directory"); + auto anim_info = anim::from_json(input_dir / "atlas.json"); + //std::vector<dir> dirs; dirs.reserve((std::size_t)anim_direction::COUNT); if (!anim_info) goto usage; @@ -210,34 +196,33 @@ int main(int argc, char** argv) goto usage; } - { - auto str = args.value<std::string>("offset"); - if (str.empty()) - { - Error{} << "offset argument is required"; - goto usage; - } - int x, y; - int ret = std::sscanf(str.c_str(), "%dx%d", &x, &y); - if (ret != 2) - { - Error{} << "can't parse offset --" << str; - goto usage; - } - options.offset = {x, y}; + try { + std::filesystem::create_directory(output_dir); + } catch (const std::filesystem::filesystem_error& error) { + Error{} << "failed to create output directory" << output_dir << ':' << error.what(); + return EX_CANTCREAT; } - using anim_dir_t = std::underlying_type_t<anim_direction>; - for (auto i = (anim_dir_t)anim_direction::MIN; i < (anim_dir_t)anim_direction::MAX; i++) + for (std::size_t i = 0; i < (std::size_t)anim_direction::COUNT; i++) { - auto name = anim_direction_group::anim_direction_string((anim_direction)i); - auto result = load_directory(pathname / name); - if (!result) - goto usage; - dirs.push_back(std::move(*result)); + auto group_name = anim_group::direction_to_string((anim_direction)i); + try { + std::filesystem::remove_all(output_dir/group_name); + std::filesystem::create_directory(output_dir/group_name); + } catch (const std::filesystem::filesystem_error& e) { + Error{} << "failed creating output directory" << group_name << ':' << e.what(); + return EX_CANTCREAT; + } + auto& group = anim_info->groups[i]; + group.frames.clear(); group.frames.reserve(64); + if (!load_directory(group, input_dir/group_name, input_dir/group_name)) + return EX_DATAERR; + if (!anim_info->to_json(output_dir/"atlas.json")) + return EX_CANTCREAT; } return 0; + usage: Error{Error::Flag::NoNewlineAtTheEnd} << Corrade::Containers::StringView{args.usage()}; return EX_USAGE; diff --git a/crop-tool/serialize.cpp b/crop-tool/serialize.cpp index 8d3a5cf7..34d70faa 100644 --- a/crop-tool/serialize.cpp +++ b/crop-tool/serialize.cpp @@ -12,72 +12,87 @@ using Corrade::Utility::Error; static constexpr std::pair<anim_direction, const char*> anim_direction_map[] = { - { anim_direction::Invalid, "x" }, - { anim_direction::N, "N" }, - { anim_direction::NE, "NE" }, - { anim_direction::E, "E" }, - { anim_direction::SE, "SE" }, - { anim_direction::S, "S" }, - { anim_direction::SW, "SW" }, - { anim_direction::W, "W" }, - { anim_direction::NW, "NW" }, + { anim_direction::N, "n" }, + { anim_direction::NE, "ne" }, + { anim_direction::E, "e" }, + { anim_direction::SE, "se" }, + { anim_direction::S, "s" }, + { anim_direction::SW, "sw" }, + { anim_direction::W, "w" }, + { anim_direction::NW, "nw" }, }; +const char* anim_group::direction_to_string(anim_direction group) +{ + auto it = std::find_if(std::cbegin(anim_direction_map), std::cend(anim_direction_map), + [=](const auto& pair) { return group == pair.first; }); + if (it != std::cend(anim_direction_map)) + return it->second; + else + return "(unknown)"; +} + +anim_direction anim_group::string_to_direction(const std::string& str) +{ + auto it = std::find_if(std::cbegin(anim_direction_map), std::cend(anim_direction_map), + [&](const auto& pair) { return str == pair.second; }); + if (it != std::cend(anim_direction_map)) + return it->first; + else + return (anim_direction)0; +} + namespace nlohmann { template<> -struct adl_serializer<Magnum::Vector2i> final { - static void to_json(json& j, const Magnum::Vector2i& x) - { - j["x"] = x[0]; - j["y"] = x[1]; - } - - static void from_json(const json& j, Magnum::Vector2i& x) - { - j.at("x").get_to(x[0]); - j.at("y").get_to(x[1]); - } +struct adl_serializer<anim_direction> final { + static void to_json(json& j, anim_direction x); + static void from_json(const json& j, anim_direction& x); }; template<> -struct adl_serializer<anim_direction> final { - static void to_json(json& j, anim_direction x) - { - auto it = std::find_if(std::cbegin(anim_direction_map), std::cend(anim_direction_map), - [=](const auto& pair) { return x == pair.first; }); - if (it != std::cend(anim_direction_map)) - j = it->second; - else - j = anim_direction_map[0].second; - } - static void from_json(const json& j, anim_direction& x) - { - std::string str = j; - auto it = std::find_if(std::cbegin(anim_direction_map), std::cend(anim_direction_map), - [&](const auto& pair) { return str == pair.second; }); - if (it != std::cend(anim_direction_map)) - x = it->first; - else - x = anim_direction::Invalid; - } +struct adl_serializer<Magnum::Vector2i> final { + static void to_json(json& j, const Magnum::Vector2i& x); + static void from_json(const json& j, Magnum::Vector2i& x); }; +void adl_serializer<Magnum::Vector2i>::to_json(json& j, const Magnum::Vector2i& x) +{ + j["x"] = x[0]; + j["y"] = x[1]; +} + +void adl_serializer<Magnum::Vector2i>::from_json(const json& j, Magnum::Vector2i& x) +{ + j.at("x").get_to(x[0]); + j.at("y").get_to(x[1]); +} + +void adl_serializer<anim_direction>::to_json(json& j, anim_direction x) +{ + j = anim_group::direction_to_string(x); +} + +void adl_serializer<anim_direction>::from_json(const json& j, anim_direction& x) +{ + x = anim_group::string_to_direction(j); +} + } // namespace nlohmann NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim_frame, ground); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim_direction_group, group, frames); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim, name, nframes, actionframe, fps, directions); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim_group, direction, frames, ground); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim, name, nframes, actionframe, fps, groups); std::optional<anim> anim::from_json(const std::filesystem::path& pathname) { using namespace nlohmann; std::ifstream s; - s.exceptions(s.exceptions() | std::ios::failbit); + s.exceptions(s.exceptions() | std::ios::failbit | std::ios::badbit); try { s.open(pathname, std::ios_base::in); - } catch (const std::ios_base::failure& e) { - Error{} << "failed to open" << pathname << ":" << e.what(); + } catch (const std::ios::failure& e) { + Error{} << "failed to open" << pathname << ':' << e.what(); return std::nullopt; } anim ret; @@ -87,17 +102,26 @@ std::optional<anim> anim::from_json(const std::filesystem::path& pathname) using nlohmann::from_json; from_json(j, ret); } catch (const std::exception& e) { - Error{} << "failed to parse" << pathname.string() << ":" << e.what(); + Error{} << "failed to parse" << pathname << ':' << e.what(); + return std::nullopt; } return std::make_optional(std::move(ret)); } -const char* anim_direction_group::anim_direction_string(anim_direction group) +bool anim::to_json(const std::filesystem::path& pathname) { - auto it = std::find_if(std::cbegin(anim_direction_map), std::cend(anim_direction_map), - [=](const auto& pair) { return group == pair.first; }); - if (it != std::cend(anim_direction_map)) - return it->second; - else - return "(unknown)"; + nlohmann::json j = *this; + + std::ofstream s; + s.exceptions(s.exceptions() | std::ios::failbit | std::ios::badbit); + try { + s.open(pathname, std::ios_base::out | std::ios_base::trunc); + } catch (const std::ios::failure& e) { + Error{} << "failed to open" << pathname << "for writing:" << e.what(); + return false; + } + s << j.dump(4); + s.flush(); + + return true; } diff --git a/crop-tool/serialize.hpp b/crop-tool/serialize.hpp index e93cf6f4..1b98d1bb 100644 --- a/crop-tool/serialize.hpp +++ b/crop-tool/serialize.hpp @@ -1,5 +1,6 @@ #pragma once +#include "../defs.hpp" #include <string> #include <array> #include <vector> @@ -7,34 +8,34 @@ #include <filesystem> #include <Magnum/Trade/ImageData.h> -struct anim_frame +struct anim_frame final { Magnum::Vector2i ground = {}; }; enum class anim_direction : unsigned char { - Invalid, N, NE, E, SE, S, SW, W, NW, - MIN = N, MAX = NW, + COUNT = NW + 1, }; -struct anim_direction_group +struct anim_group final { - static const char* anim_direction_string(anim_direction group); - anim_direction group; + anim_direction direction = (anim_direction)-1; std::vector<anim_frame> frames; - Magnum::Vector2i ground = {}; + + static const char* direction_to_string(anim_direction group); + static anim_direction string_to_direction(const std::string& str); }; -struct anim +struct anim final { - std::string name; - std::array<anim_direction_group, (unsigned)anim_direction::MAX> directions; - int nframes = 0, actionframe = 0, fps = default_fps; - static std::optional<anim> from_json(const std::filesystem::path& pathname); - + bool to_json(const std::filesystem::path& pathname); static constexpr int default_fps = 24; + + std::string name; + std::array<anim_group, (std::size_t)anim_direction::COUNT> groups; + int nframes = 0, actionframe = -1, fps = default_fps; }; diff --git a/doc/atlas.json b/doc/atlas.json new file mode 100644 index 00000000..53f9722c --- /dev/null +++ b/doc/atlas.json @@ -0,0 +1,18 @@ +{ + "name": "character", + "nframes": 24, + "actionframe": 0, + "fps": 1, + "groups": [ + { "direction": "N", "frames": [], "ground": {"x":484, "y":488} }, + { "direction": "NE", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "E", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "SE", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "S", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "SW", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "W", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "NW", "frames": [], "ground": {"x": 0, "y": 0} } + ] +} + +// vim: ft=javascript diff --git a/doc/atlas.json.example b/doc/atlas.json.example new file mode 100644 index 00000000..9d54b5be --- /dev/null +++ b/doc/atlas.json.example @@ -0,0 +1,18 @@ +{ + "name": "character", + "nframes": 24, + "actionframe": 0, + "fps": 24, + "directions": [ + { "direction": "N", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "NE", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "E", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "SE", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "S", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "SW", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "W", "frames": [], "ground": {"x": 0, "y": 0} }, + { "direction": "NW", "frames": [], "ground": {"x": 0, "y": 0} } + ] +} + +// vim: ft=javascript diff --git a/magnum b/magnum -Subproject a218ddfa9434201aaca207ea9c8e87fea91a075 +Subproject b6739751d2ab957bfc171177bb60d87ed4deb06 diff --git a/magnum-plugins b/magnum-plugins -Subproject 4100de3b63beac723690adfa4a3fd368b5d664b +Subproject d8b016d033944175f3530a369506debe51eebd4 |