summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2022-06-11 10:22:52 +0200
committerStanislaw Halik <sthalik@misaki.pl>2022-06-11 10:22:52 +0200
commitb0925a6a5e16dce10cb9b5bd99b62f96b2953441 (patch)
tree46c5e2fd241f136977f8048b1781a2d8e9af1b86
parent1a3bfe8f4dd7eb2cfbe898bfbe82931f9fc273ec (diff)
.
m---------corrade0
-rw-r--r--crop-tool/atlas.cpp61
-rw-r--r--crop-tool/atlas.hpp36
-rw-r--r--crop-tool/crop-tool.cpp150
-rw-r--r--crop-tool/serialize.cpp4
-rw-r--r--crop-tool/serialize.hpp6
-rw-r--r--main.cpp2
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);
diff --git a/main.cpp b/main.cpp
index 2e5403b2..9d3bf91d 100644
--- a/main.cpp
+++ b/main.cpp
@@ -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())