diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2022-06-11 10:22:52 +0200 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2022-06-11 10:22:52 +0200 |
commit | b0925a6a5e16dce10cb9b5bd99b62f96b2953441 (patch) | |
tree | 46c5e2fd241f136977f8048b1781a2d8e9af1b86 | |
parent | 1a3bfe8f4dd7eb2cfbe898bfbe82931f9fc273ec (diff) |
.
m--------- | corrade | 0 | ||||
-rw-r--r-- | crop-tool/atlas.cpp | 61 | ||||
-rw-r--r-- | crop-tool/atlas.hpp | 36 | ||||
-rw-r--r-- | crop-tool/crop-tool.cpp | 150 | ||||
-rw-r--r-- | crop-tool/serialize.cpp | 4 | ||||
-rw-r--r-- | crop-tool/serialize.hpp | 6 | ||||
-rw-r--r-- | main.cpp | 2 |
7 files changed, 192 insertions, 67 deletions
diff --git a/corrade b/corrade -Subproject 9f559f59af07fee2a36b1377b6a7a1e8a6e6e81 +Subproject 77b1d14af6e6afa7798b9240ba49cf303a38148 diff --git a/crop-tool/atlas.cpp b/crop-tool/atlas.cpp new file mode 100644 index 00000000..ca05cd15 --- /dev/null +++ b/crop-tool/atlas.cpp @@ -0,0 +1,61 @@ +#include "atlas.hpp" +#include "serialize.hpp" +#include "../defs.hpp" + +#include <filesystem> +#include <opencv2/imgcodecs.hpp> + +void anim_atlas_row::add_entry(const anim_atlas_entry& x) +{ + auto& frame = *x.frame; + const auto& mat = x.mat; + frame.offset = {xpos, ypos}; + frame.size = {mat.cols, mat.rows}; + + ASSERT(mat.rows > 0 && mat.cols > 0); + data.push_back(x); + xpos += mat.cols; + max_height = std::max(mat.rows, max_height); +} + +void anim_atlas::advance_row() +{ + auto& row = rows.back(); + if (row.data.empty()) + return; + ASSERT(row.xpos); ASSERT(row.max_height); + ypos += row.max_height; + maxx = std::max(row.xpos, maxx); + rows.push_back({{}, 0, 0, ypos}); +} + +Magnum::Vector2i anim_atlas::offset() const +{ + const auto& row = rows.back(); + return {row.xpos, row.ypos}; +} + +Magnum::Vector2i anim_atlas::size() const +{ + const anim_atlas_row& row = rows.back(); + // prevent accidentally writing out of bounds by forgetting to call + // anim_atlas::advance_row() one last time prior to anim_atlas::size() + return {std::max(maxx, row.xpos), ypos + row.max_height}; +} + +bool anim_atlas::dump(const std::filesystem::path& filename) const +{ + auto sz = size(); + cv::Mat4b mat(sz[1], sz[0]); + mat.setTo(0); + + for (const anim_atlas_row& row : rows) + for (const anim_atlas_entry& x : row.data) + { + auto offset = x.frame->offset, size = x.frame->size; + cv::Rect roi = {offset[0], offset[1], size[0], size[1]}; + x.mat.copyTo(mat(roi)); + } + + return cv::imwrite(filename.string(), mat); +} diff --git a/crop-tool/atlas.hpp b/crop-tool/atlas.hpp new file mode 100644 index 00000000..675b6ff9 --- /dev/null +++ b/crop-tool/atlas.hpp @@ -0,0 +1,36 @@ +#pragma once +#include <vector> +#include <Magnum/Magnum.h> +#include <Magnum/Math/Vector2.h> +#include <opencv2/core/mat.hpp> + +struct anim_frame; + +namespace std::filesystem { class path; } + +struct anim_atlas_entry +{ + anim_frame* frame; + const cv::Mat4b mat; +}; + +struct anim_atlas_row +{ + std::vector<anim_atlas_entry> data; + int max_height = 0, xpos = 0, ypos = 0; + + void add_entry(const anim_atlas_entry& x); +}; + +class anim_atlas +{ + std::vector<anim_atlas_row> rows = {{}}; + int ypos = 0, maxx = 0; + +public: + void add_entry(const anim_atlas_entry& x) { rows.back().add_entry(x); } + void advance_row(); + Magnum::Vector2i offset() const; + Magnum::Vector2i size() const; + [[nodiscard]] bool dump(const std::filesystem::path& filename) const; +}; diff --git a/crop-tool/crop-tool.cpp b/crop-tool/crop-tool.cpp index b6273380..35089dd6 100644 --- a/crop-tool/crop-tool.cpp +++ b/crop-tool/crop-tool.cpp @@ -1,4 +1,5 @@ #include "../defs.hpp" +#include "atlas.hpp" #include "serialize.hpp" #include <Corrade/Utility/Arguments.h> #include <Corrade/Utility/Debug.h> @@ -32,16 +33,16 @@ using Corrade::Utility::Error; using Corrade::Utility::Debug; -static struct options_ { +using std::filesystem::path; + +struct options_ { std::optional<unsigned> width, height; std::optional<double> scale; -} options; - -using std::filesystem::path; + path input_dir, output_dir; +}; -static -std::tuple<cv::Vec2i, cv::Vec2i, bool> -find_image_bounds(const path& path, const cv::Mat4b& mat) +[[nodiscard]] +static std::tuple<cv::Vec2i, cv::Vec2i, bool> 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++) @@ -69,7 +70,8 @@ find_image_bounds(const path& path, const cv::Mat4b& mat) return {start, end, true}; } -static bool load_file(anim_group& group, const path& filename, const path& output_filename) +[[nodiscard]] +static bool load_file(anim_group& group, options_& opts, anim_atlas& atlas, const path& filename) { auto mat = progn( cv::Mat mat_ = cv::imread(filename.string(), cv::IMREAD_UNCHANGED); @@ -87,98 +89,110 @@ static bool load_file(anim_group& group, const path& filename, const path& outpu auto [start, end, bounds_ok] = find_image_bounds(filename, mat); if (!bounds_ok) - return {}; + return false; cv::Size size{end - start}, dest_size; - if (!options.scale) + if (!opts.scale) { - if (options.width) - options.scale = (double)*options.width / size.width; - else if (options.height) - options.scale = (double)*options.height / size.height; + if (opts.width) + opts.scale = (double)*opts.width / size.width; + else if (opts.height) + opts.scale = (double)*opts.height / size.height; else std::abort(); } - dest_size = {(int)std::round(*options.scale * size.width), - (int)std::round(*options.scale * size.height)}; + dest_size = {(int)std::round(*opts.scale * size.width), + (int)std::round(*opts.scale * size.height)}; if (size.width < dest_size.width || size.height < dest_size.height) { Error{} << "refusing to upscale image" << filename; - return {}; + return false; } cv::Mat4b resized{size}; cv::resize(mat({start, size}), resized, dest_size, 0, 0, cv::INTER_LANCZOS4); +#if 0 if (!cv::imwrite(output_filename.string(), resized)) { Error{} << "failed writing image" << output_filename; return false; } +#endif Magnum::Vector2i ground = { - (int)std::round((group.ground[0] - start[0]) * *options.scale), - (int)std::round((group.ground[1] - start[1]) * *options.scale), + (int)std::round((group.ground[0] - start[0]) * *opts.scale), + (int)std::round((group.ground[1] - start[1]) * *opts.scale), }; - group.frames.push_back({ground}); + + auto offset = atlas.offset(); + + group.frames.push_back({ground, offset, {dest_size.width, dest_size.height}}); + atlas.add_entry({&group.frames.back(), std::move(mat)}); return true; } -static bool load_directory(anim_group& group, const path& input_dir, const path& output_dir) +[[nodiscard]] +static bool load_directory(anim_group& group, options_& opts, anim_atlas& atlas, const path& input_dir) { if (std::error_code ec{}; !std::filesystem::exists(input_dir/".", ec)) { Error{} << "can't open directory" << input_dir << ':' << ec.message(); - return {}; + return false; } - int i; - for (i = 1; i <= 9999; i++) + std::size_t max; + for (max = 1; max <= 9999; max++) { char filename[9]; - sprintf(filename, "%04d.png", i); + sprintf(filename, "%04zu.png", max); if (!std::filesystem::exists(input_dir/filename)) break; - if (!load_file(group, input_dir/filename, output_dir/filename)) - return false; } + group.frames.clear(); + // atlas stores its entries through a pointer. + // vector::reserve() is necessary to avoid use-after-free. + group.frames.reserve(max-1); - if (i == 1) + for (std::size_t i = 1; i < max; i++) { - Error{} << "no files in anim group directory" << input_dir; - return false; + char filename[9]; + sprintf(filename, "%04zu.png", i); + if (!load_file(group, opts, atlas, input_dir/filename)) + return false; } + atlas.advance_row(); + return true; } -int main(int argc, char** argv) +static char* fix_argv0(char* argv0) { - Corrade::Utility::Arguments args{}; #ifdef _WIN32 - if (auto* c = strrchr(argv[0], '\\'); c && c[1]) + if (auto* c = strrchr(argv0, '\\'); c && c[1]) { if (auto* s = strrchr(c, '.'); s && !strcmp(".exe", s)) *s = '\0'; - args.setCommand(c+1); + return c+1; } #else if (auto* c = strrchr(argv[0], '/'); c && c[1]) - args.setCommand(c+1); + return c+1; #endif - args.addOption('o', "output", "./output") + return argv0; +} + +static std::tuple<options_, int> parse_cmdline(int argc, const char* const* argv) +{ + Corrade::Utility::Arguments args{}; + args.addOption('o', "output") .addArgument("directory") .addOption('W', "width", "") .addOption('H', "height", ""); args.parse(argc, argv); - 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"); - - if (!anim_info) - goto usage; - + options_ options; if (unsigned w = args.value<unsigned>("width"); w != 0) options.width = w; if (unsigned h = args.value<unsigned>("height"); h != 0) @@ -188,35 +202,49 @@ int main(int argc, char** argv) Error{} << "exactly one of --width, --height must be given"; goto usage; } + options.output_dir = args.value<std::string>("output"); + options.input_dir = args.value<std::string>("directory"); + + if (options.output_dir.empty()) + options.output_dir = options.input_dir; + + return {options, 0}; +usage: + Error{Error::Flag::NoNewlineAtTheEnd} << Corrade::Containers::StringView{args.usage()}; + return {{}, EX_USAGE}; +} + +int main(int argc, char** argv) +{ + argv[0] = fix_argv0(argv[0]); + auto [opts, error_code] = parse_cmdline(argc, argv); + if (error_code) + return error_code; + + auto anim_info = anim::from_json(opts.input_dir/"atlas.json"); + + if (!anim_info) + return EX_DATAERR; try { - std::filesystem::create_directory(output_dir); + std::filesystem::create_directory(opts.output_dir); } catch (const std::filesystem::filesystem_error& error) { - Error{} << "failed to create output directory" << output_dir << ':' << error.what(); + Error{} << "failed to create output directory" << opts.output_dir << ':' << error.what(); return EX_CANTCREAT; } - for (std::size_t i = 0; i < (std::size_t)anim_direction::COUNT; i++) + anim_atlas atlas; + + for (anim_group& group : anim_info->groups) { - 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, output_dir/group_name)) + if (!load_directory(group, opts, atlas, opts.input_dir/group.name)) return EX_DATAERR; - if (!anim_info->to_json(output_dir/"atlas.json")) + if (!atlas.dump(opts.output_dir/"atlas.png")) + return EX_CANTCREAT; + if (!anim_info->to_json(opts.output_dir/"atlas.json.new")) 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 34d70faa..acefe436 100644 --- a/crop-tool/serialize.cpp +++ b/crop-tool/serialize.cpp @@ -80,8 +80,8 @@ void adl_serializer<anim_direction>::from_json(const json& j, anim_direction& x) } // namespace nlohmann -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim_frame, ground); -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim_group, direction, frames, ground); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim_frame, ground, offset, size); +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim_group, name, frames, ground); NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(anim, name, nframes, actionframe, fps, groups); std::optional<anim> anim::from_json(const std::filesystem::path& pathname) diff --git a/crop-tool/serialize.hpp b/crop-tool/serialize.hpp index 1b98d1bb..10fd9e18 100644 --- a/crop-tool/serialize.hpp +++ b/crop-tool/serialize.hpp @@ -10,7 +10,7 @@ struct anim_frame final { - Magnum::Vector2i ground = {}; + Magnum::Vector2i ground, offset, size; }; enum class anim_direction : unsigned char @@ -21,9 +21,9 @@ enum class anim_direction : unsigned char struct anim_group final { - anim_direction direction = (anim_direction)-1; + std::string name; std::vector<anim_frame> frames; - Magnum::Vector2i ground = {}; + Magnum::Vector2i ground; static const char* direction_to_string(anim_direction group); static anim_direction string_to_direction(const std::string& str); @@ -168,7 +168,7 @@ application::application(const Arguments& arguments): k++; } - //auto positions = atlas->floor_quad({(float)(sz[0]*0), (float)(sz[1]*0), sz[1]*2}, sz); + //auto positions = anim_atlas->floor_quad({(float)(sz[0]*0), (float)(sz[1]*0), sz[1]*2}, sz); } _mesh2.setCount((int)indices.size()) |