summaryrefslogtreecommitdiffhomepage
path: root/crop-tool
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2022-09-30 18:55:15 +0200
committerStanislaw Halik <sthalik@misaki.pl>2022-09-30 18:55:15 +0200
commit5ee36284ab2f2d85679f83ad7680a741bf7f6702 (patch)
tree825566e0a21b6b7b8d0ad3f81880ebe4c3f3ce3d /crop-tool
parentc7ce9f57f53325e47574bf0fcdaa495146656289 (diff)
.
Diffstat (limited to 'crop-tool')
-rw-r--r--crop-tool/CMakeLists.txt10
-rw-r--r--crop-tool/atlas.cpp63
-rw-r--r--crop-tool/atlas.hpp37
-rw-r--r--crop-tool/crop-tool.cpp280
4 files changed, 0 insertions, 390 deletions
diff --git a/crop-tool/CMakeLists.txt b/crop-tool/CMakeLists.txt
deleted file mode 100644
index 251bcac3..00000000
--- a/crop-tool/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-find_package(OpenCV QUIET REQUIRED COMPONENTS core imgcodecs imgproc)
-set(self "${PROJECT_NAME}-crop-tool")
-
-include_directories(SYSTEM PRIVATE ${OpenCV_INCLUDE_DIRS})
-link_libraries(Corrade::Utility)
-link_libraries(${PROJECT_NAME}-anim)
-
-file(GLOB sources "*.cpp" CONFIGURE_ARGS)
-add_executable(${self} ${sources})
-install(TARGETS ${self} RUNTIME DESTINATION "bin")
diff --git a/crop-tool/atlas.cpp b/crop-tool/atlas.cpp
deleted file mode 100644
index a588ff3c..00000000
--- a/crop-tool/atlas.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-#undef NDEBUG
-
-#include "atlas.hpp"
-#include "anim/serialize.hpp"
-
-#include <cassert>
-#include <filesystem>
-#include <opencv2/imgcodecs.hpp>
-
-void anim_atlas_row::add_entry(const anim_atlas_entry& x) noexcept
-{
- 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() noexcept
-{
- 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 noexcept
-{
- const auto& row = rows.back();
- return {row.xpos, row.ypos};
-}
-
-Magnum::Vector2i anim_atlas::size() const noexcept
-{
- 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 noexcept
-{
- 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
deleted file mode 100644
index 5c5e918f..00000000
--- a/crop-tool/atlas.hpp
+++ /dev/null
@@ -1,37 +0,0 @@
-#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;
- 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) noexcept;
-};
-
-class anim_atlas
-{
- std::vector<anim_atlas_row> rows = {{}};
- int ypos = 0, maxx = 0;
-
-public:
- void add_entry(const anim_atlas_entry& x) noexcept { rows.back().add_entry(x); }
- void advance_row() noexcept;
- Magnum::Vector2i offset() const noexcept;
- Magnum::Vector2i size() const noexcept;
- [[nodiscard]] bool dump(const std::filesystem::path& filename) const noexcept;
-};
diff --git a/crop-tool/crop-tool.cpp b/crop-tool/crop-tool.cpp
deleted file mode 100644
index 42d54baa..00000000
--- a/crop-tool/crop-tool.cpp
+++ /dev/null
@@ -1,280 +0,0 @@
-#undef NDEBUG
-
-#include "defs.hpp"
-#include "atlas.hpp"
-#include "anim/serialize.hpp"
-
-#include <cassert>
-#include <cmath>
-#include <cstring>
-
-#include <algorithm>
-#include <utility>
-#include <tuple>
-
-#include <filesystem>
-
-#include <Corrade/Utility/Arguments.h>
-#include <Corrade/Utility/Debug.h>
-#include <Corrade/Utility/DebugStl.h>
-
-#include <opencv2/core/mat.hpp>
-#include <opencv2/imgcodecs/imgcodecs.hpp>
-#include <opencv2/imgproc/imgproc.hpp>
-
-using Corrade::Utility::Error;
-using Corrade::Utility::Debug;
-
-using std::filesystem::path;
-
-struct options
-{
- double scale = 0;
- path input_dir, input_file, output_dir;
- int width = 0, height = 0, nframes = 0;
-};
-
-[[nodiscard]]
-static std::tuple<cv::Vec2i, cv::Vec2i, bool> find_image_bounds(const cv::Mat4b& mat) noexcept
-{
- cv::Vec2i start{mat.cols, mat.rows}, end{0, 0};
- for (int y = 0; y < mat.rows; y++)
- {
- const auto* ptr = mat.ptr<cv::Vec4b>(y);
- for (int x = 0; x < mat.cols; x++)
- {
- enum {R, G, B, A};
- if (cv::Vec4b px = ptr[x]; px[A] != 0)
- {
- start[0] = std::min(x, start[0]);
- start[1] = std::min(y, start[1]);
- end[0] = std::max(x+1, end[0]);
- end[1] = std::max(y+1, end[1]);
- }
- }
- }
- if (start[0] < end[0] && start[1] < end[1])
- return {start, end, true};
- else
- return {{}, {}, false};
-}
-
-[[nodiscard]]
-static bool load_file(anim_group& group, options& opts, anim_atlas& atlas, const path& filename) noexcept
-{
- 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 << "as RGBA32 image";
- return cv::Mat4b{};
- }
- return cv::Mat4b(std::move(mat));
- );
-
- if (mat.empty())
- return false;
-
- auto [start, end, bounds_ok] = find_image_bounds(mat);
-
- if (!bounds_ok)
- {
- Error{} << "no valid image data in" << filename;
- return false;
- }
-
- cv::Size size{end - start};
-
- if (opts.scale == 0.0)
- {
- assert(opts.width || opts.height);
- if (opts.width)
- opts.scale = (double)opts.width / size.width;
- else
- opts.scale = (double)opts.height / size.height;
- assert(opts.scale > 1e-6);
- }
-
- const cv::Size 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 false;
- }
-
- cv::Mat4b resized{size};
- cv::resize(mat({start, size}), resized, dest_size, 0, 0, cv::INTER_LANCZOS4);
-
- const Magnum::Vector2i ground = {
- (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, atlas.offset(), {dest_size.width, dest_size.height}});
- atlas.add_entry({&group.frames.back(), std::move(resized)});
- return true;
-}
-
-[[nodiscard]]
-static bool load_directory(anim_group& group, options& opts, anim_atlas& atlas) noexcept
-{
- const auto input_dir = opts.input_dir/group.name;
-
- if (std::error_code ec; !std::filesystem::exists(input_dir/".", ec))
- {
- Error{Error::Flag::NoSpace} << "can't open directory " << input_dir << ": " << ec.message();
- return false;
- }
-
- int max;
- for (max = 1; max <= 9999; max++)
- {
- char filename[9];
- sprintf(filename, "%04d.png", max);
- if (std::error_code ec; !std::filesystem::exists(input_dir/filename, ec))
- break;
- }
-
- if (max == 1)
- {
- Error{Error::Flag::NoSpace} << "no files in directory " << input_dir << "!";
- return false;
- }
-
- if (!opts.nframes)
- opts.nframes = max-1;
- else if (opts.nframes != max-1)
- {
- Error{Error::Flag::NoSpace} << "wrong frame count for direction '"
- << group.name << "' -- " << max-1
- << " should be " << opts.nframes;
- return false;
- }
-
- group.frames.clear();
- // atlas stores its entries through a pointer.
- // vector::reserve() is necessary to avoid use-after-free.
- group.frames.reserve((std::size_t)max-1);
-
- for (int i = 1; i < max; i++)
- {
- char filename[9];
- sprintf(filename, "%04d.png", i);
- if (!load_file(group, opts, atlas, input_dir/filename))
- return false;
- }
-
- atlas.advance_row();
-
- return true;
-}
-
-static char* fix_argv0(char* argv0) noexcept
-{
-#ifdef _WIN32
- if (auto* c = strrchr(argv0, '\\'); c && c[1])
- {
- if (auto* s = strrchr(c, '.'); s && !strcmp(".exe", s))
- *s = '\0';
- return c+1;
- }
-#else
- if (auto* c = strrchr(argv[0], '/'); c && c[1])
- return c+1;
-#endif
- return argv0;
-}
-
-using Corrade::Utility::Arguments;
-
-static std::tuple<options, Arguments, bool> parse_cmdline(int argc, const char* const* argv) noexcept
-{
- Corrade::Utility::Arguments args{};
- args.addOption('o', "output")
- .addArgument("input")
- .addOption('W', "width", "")
- .addOption('H', "height", "");
- args.parse(argc, argv);
- options opts;
- if (int w = args.value<int>("width"); w != 0)
- opts.width = w;
- if (int h = args.value<int>("height"); h != 0)
- opts.height = h;
- opts.input_file = args.value<std::string>("input");
- opts.input_dir = opts.input_file.parent_path();
-
- if (opts.output_dir.empty())
- opts.output_dir = opts.input_dir;
-
- return { std::move(opts), std::move(args), true };
-}
-
-[[nodiscard]] static int usage(const Arguments& args) noexcept
-{
- Error{Error::Flag::NoNewlineAtTheEnd} << args.usage();
- return EX_USAGE;
-}
-
-[[nodiscard]] static bool check_atlas_name(const std::string& str) noexcept
-{
- constexpr auto npos = std::string::npos;
-
- if (str.empty())
- return false;
- if (str[0] == '.' || str[0] == '\\' || str[0] == '/')
- return false;
- if (str.find('"') != npos || str.find('\'') != npos)
- return false;
- if (str.find("/.") != npos || str.find("\\.") != npos)
- return false; // NOLINT(readability-simplify-boolean-expr)
-
- return true;
-}
-
-int main(int argc, char** argv)
-{
- argv[0] = fix_argv0(argv[0]);
- auto [opts, args, opts_ok] = parse_cmdline(argc, argv);
- if (!opts_ok)
- return usage(args);
-
- auto [anim_info, anim_ok] = anim::from_json(opts.input_file);
-
- if (!anim_ok)
- return EX_DATAERR;
-
- if (!check_atlas_name(anim_info.name))
- {
- Error{Error::Flag::NoSpace} << "atlas name '" << anim_info.name << "' contains invalid characters";
- return EX_DATAERR;
- }
-
- if (!opts.width)
- opts.width = anim_info.width;
- if (!opts.height)
- opts.height = anim_info.height;
- opts.nframes = anim_info.nframes;
-
- if (!(opts.width ^ opts.height) || opts.width < 0 || opts.height < 0)
- {
- Error{} << "exactly one of --width, --height must be specified";
- return usage(args);
- }
-
- anim_atlas atlas;
-
- for (anim_group& group : anim_info.groups)
- if (!load_directory(group, opts, atlas))
- return EX_DATAERR;
-
- if (!atlas.dump(opts.output_dir/(anim_info.name + ".png")))
- return EX_CANTCREAT;
- if (!anim_info.to_json(opts.output_dir/(anim_info.name + ".json")))
- return EX_CANTCREAT;
-
- return 0;
-}