From 5ee36284ab2f2d85679f83ad7680a741bf7f6702 Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Fri, 30 Sep 2022 18:55:15 +0200 Subject: . --- CMakeLists.txt | 4 +- anim-crop-tool/CMakeLists.txt | 10 ++ anim-crop-tool/atlas.cpp | 63 ++++++++++ anim-crop-tool/atlas.hpp | 37 ++++++ crop-tool/CMakeLists.txt | 10 -- crop-tool/atlas.cpp | 63 ---------- crop-tool/atlas.hpp | 37 ------ crop-tool/crop-tool.cpp | 280 ------------------------------------------ tile-tool/CMakeLists.txt | 10 -- tile-tool/big-atlas.cpp | 67 ---------- tile-tool/big-atlas.hpp | 33 ----- tile-tool/tile-tool.cpp | 34 ----- 12 files changed, 112 insertions(+), 536 deletions(-) create mode 100644 anim-crop-tool/CMakeLists.txt create mode 100644 anim-crop-tool/atlas.cpp create mode 100644 anim-crop-tool/atlas.hpp delete mode 100644 crop-tool/CMakeLists.txt delete mode 100644 crop-tool/atlas.cpp delete mode 100644 crop-tool/atlas.hpp delete mode 100644 crop-tool/crop-tool.cpp delete mode 100644 tile-tool/CMakeLists.txt delete mode 100644 tile-tool/big-atlas.cpp delete mode 100644 tile-tool/big-atlas.hpp delete mode 100644 tile-tool/tile-tool.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fcb8b4e9..85fe9829 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,8 +57,8 @@ if(NOT BOOTSTRAP_DEPENDS) include_directories(.) - add_subdirectory(crop-tool) - add_subdirectory(tile-tool) + add_subdirectory(anim-crop-tool) + add_subdirectory(big-atlas-tool) add_subdirectory(anim) add_subdirectory(tile) diff --git a/anim-crop-tool/CMakeLists.txt b/anim-crop-tool/CMakeLists.txt new file mode 100644 index 00000000..7b6d22a8 --- /dev/null +++ b/anim-crop-tool/CMakeLists.txt @@ -0,0 +1,10 @@ +find_package(OpenCV QUIET REQUIRED COMPONENTS core imgcodecs imgproc) +set(self "${PROJECT_NAME}-anim-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/anim-crop-tool/atlas.cpp b/anim-crop-tool/atlas.cpp new file mode 100644 index 00000000..a588ff3c --- /dev/null +++ b/anim-crop-tool/atlas.cpp @@ -0,0 +1,63 @@ +#undef NDEBUG + +#include "atlas.hpp" +#include "anim/serialize.hpp" + +#include +#include +#include + +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/anim-crop-tool/atlas.hpp b/anim-crop-tool/atlas.hpp new file mode 100644 index 00000000..5c5e918f --- /dev/null +++ b/anim-crop-tool/atlas.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include + +struct anim_frame; + +namespace std::filesystem { class path; } + +struct anim_atlas_entry +{ + anim_frame* frame; + cv::Mat4b mat; +}; + +struct anim_atlas_row +{ + std::vector data; + int max_height = 0, xpos = 0, ypos = 0; + + void add_entry(const anim_atlas_entry& x) noexcept; +}; + +class anim_atlas +{ + std::vector 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/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 -#include -#include - -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 -#include -#include -#include - -struct anim_frame; - -namespace std::filesystem { class path; } - -struct anim_atlas_entry -{ - anim_frame* frame; - cv::Mat4b mat; -}; - -struct anim_atlas_row -{ - std::vector data; - int max_height = 0, xpos = 0, ypos = 0; - - void add_entry(const anim_atlas_entry& x) noexcept; -}; - -class anim_atlas -{ - std::vector 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 -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include - -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 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(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 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("width"); w != 0) - opts.width = w; - if (int h = args.value("height"); h != 0) - opts.height = h; - opts.input_file = args.value("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; -} diff --git a/tile-tool/CMakeLists.txt b/tile-tool/CMakeLists.txt deleted file mode 100644 index 8f148751..00000000 --- a/tile-tool/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -find_package(OpenCV QUIET REQUIRED COMPONENTS core imgcodecs imgproc) -set(self "${PROJECT_NAME}-tile-tool") - -include_directories(SYSTEM PRIVATE ${OpenCV_INCLUDE_DIRS}) -link_libraries(Corrade::Utility) -link_libraries(${PROJECT_NAME}-tile) - -file(GLOB sources "*.cpp" CONFIGURE_ARGS) -add_executable(${self} ${sources}) -install(TARGETS ${self} RUNTIME DESTINATION "bin") diff --git a/tile-tool/big-atlas.cpp b/tile-tool/big-atlas.cpp deleted file mode 100644 index a9f877bd..00000000 --- a/tile-tool/big-atlas.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#undef NDEBUG - -#include "big-atlas.hpp" -#include -#include -#include -#include -#include - -std::vector big_atlas_builder::add_atlas(const std::filesystem::path& filename) -{ - using Corrade::Utility::Error; - std::vector ret; - cv::Mat mat = cv::imread(filename.string(), cv::IMREAD_UNCHANGED); - if (mat.empty() || (mat.type() != CV_8UC4 && mat.type() != CV_8UC3)) - { - Error{} << "failed to load" << filename << "as RGBA32 image"; - return {}; - } - if (mat.type() == CV_8UC3) { - cv::Mat mat2; - cv::cvtColor(mat, mat2, cv::COLOR_RGB2RGBA); - mat = mat2.clone(); - } - - Error{} << "file" << filename; - - assert(mat.cols % TILE_SIZE[0] == 0 && mat.rows % TILE_SIZE[1] == 0); - - for (int y = 0; y + TILE_SIZE[1] <= mat.rows; y += TILE_SIZE[1]) - for (int x = 0; x + TILE_SIZE[0] <= mat.cols; x += TILE_SIZE[0]) - { - Error{} << "convert" << x << y; - cv::Rect roi { x, y, TILE_SIZE[0], TILE_SIZE[1] }; - auto frame = add_frame(mat(roi)); - ret.push_back(frame); - } - - return ret; -} - -big_atlas_frame big_atlas_builder::add_frame(const cv::Mat4b& frame) -{ - auto& row = maybe_next_row(); - big_atlas_frame ret { frame, { row.xpos, row.ypos } }; - row.frames.push_back(ret); - row.xpos += TILE_SIZE[0]; - maxx = std::max(maxx, row.xpos); - return ret; -} - -big_atlas_row& big_atlas_builder::maybe_next_row() -{ - auto& row = rows.back(); - - if (row.xpos + TILE_SIZE[0] > MAX_TEXTURE_SIZE[0]) - { - ypos += TILE_SIZE[1]; - rows.emplace_back(); - auto& row = rows.back(); - row.ypos = ypos; - - return row; - } - else - return row; -} diff --git a/tile-tool/big-atlas.hpp b/tile-tool/big-atlas.hpp deleted file mode 100644 index 77961be0..00000000 --- a/tile-tool/big-atlas.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace std::filesystem { class path; } - -struct big_atlas_frame { - cv::Mat4b frame; - Magnum::Vector2i position; -}; - -struct big_atlas_row { - std::vector frames; - int xpos = 0, ypos = 0; -}; - -struct big_atlas_builder { - [[nodiscard]] std::vector add_atlas(const std::filesystem::path& filename); - big_atlas_frame add_frame(const cv::Mat4b& frame); - big_atlas_row& maybe_next_row(); - -private: - static constexpr Magnum::Vector2i TILE_SIZE = { 100, 100 }, - MAX_TEXTURE_SIZE = { 512, 512 }; - - std::vector rows = {{}}; - int ypos = 0, maxx = 0; - - static_assert(!!TILE_SIZE[0] && !!TILE_SIZE[1] && !!MAX_TEXTURE_SIZE[0] && !!MAX_TEXTURE_SIZE[1]); - static_assert(MAX_TEXTURE_SIZE[0] >= TILE_SIZE[0] && MAX_TEXTURE_SIZE[1] >= TILE_SIZE[1]); -}; diff --git a/tile-tool/tile-tool.cpp b/tile-tool/tile-tool.cpp deleted file mode 100644 index edc882ad..00000000 --- a/tile-tool/tile-tool.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "big-atlas.hpp" -#include "tile/serialize.hpp" -#include -#include -#include - -using Corrade::Utility::Arguments; - -struct options final { - std::filesystem::path input_dir, output_file; -}; - -static std::tuple parse_cmdline(int argc, const char* const* argv) noexcept -{ - Corrade::Utility::Arguments args{}; - args.addOption('o', "output") - .addArrayArgument("input"); - args.parse(argc, argv); - options opts; - opts.input_dir = args.value("input"); - - if (opts.input_dir.empty()) - opts.output_file = opts.input_dir.parent_path() / "big-atlas.json"; - - return { std::move(opts), std::move(args), true }; -} - -int main(int argc, char** argv) -{ - big_atlas_builder builder; - builder.add_atlas("images/metal1.png"); - builder.add_atlas("images/metal2.png"); - return 0; -} -- cgit v1.2.3