summaryrefslogtreecommitdiffhomepage
path: root/crop-tool/crop-tool.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'crop-tool/crop-tool.cpp')
-rw-r--r--crop-tool/crop-tool.cpp150
1 files changed, 89 insertions, 61 deletions
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;
}