diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | atlas.cpp | 2 | ||||
-rw-r--r-- | atlas.hpp | 6 | ||||
m--------- | corrade | 0 | ||||
-rw-r--r-- | crop-tool/CMakeLists.txt | 6 | ||||
-rw-r--r-- | crop-tool/atlas.cpp | 2 | ||||
-rw-r--r-- | crop-tool/atlas.hpp | 31 | ||||
-rw-r--r-- | crop-tool/crop-tool.cpp | 235 | ||||
-rw-r--r-- | loader-impl.cpp | 13 | ||||
m--------- | magnum | 0 | ||||
m--------- | magnum-integration | 0 | ||||
m--------- | magnum-plugins | 0 |
12 files changed, 289 insertions, 8 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a17a7baa..ac9065fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,8 @@ if(NOT BOOTSTRAP_DEPENDS) find_package(MagnumPlugins QUIET REQUIRED) find_package(MagnumIntegration QUIET REQUIRED COMPONENTS Glm) + add_subdirectory(crop-tool) + corrade_add_resource(game_RESOURCES resources.conf) file(GLOB sources "*.cpp" CONFIGURE_ARGS) @@ -5,7 +5,7 @@ namespace Magnum::Examples { -atlas_texture::atlas_texture(const Trade::ImageData2D& image, Vector2i dims) : +atlas_texture::atlas_texture(const ImageView2D& image, Vector2i dims) : size_{image.size()}, dims_{dims}, tile_size_{size_ / dims} @@ -1,8 +1,6 @@ #pragma once -#include <Magnum/Math/Vector.h> -#include <Magnum/Math/Vector2.h> +#include <Magnum/Magnum.h> #include <Magnum/GL/Texture.h> -#include <Magnum/Trade/ImageData.h> #include <array> namespace Magnum::Examples { @@ -11,7 +9,7 @@ struct atlas_texture final { using vertex_array_type = std::array<Vector3, 4>; - atlas_texture(const Trade::ImageData2D& img, Vector2i dims); + atlas_texture(const ImageView2D& img, Vector2i dims); std::array<Vector2, 4> texcoords_for_id(int id) const; static vertex_array_type floor_quad(Vector3 center, Vector2 size); static vertex_array_type wall_quad_W(Vector3 center, Vector3 size); diff --git a/corrade b/corrade -Subproject f2d56f700ac1d1d0d0b665fba4a7846ce4c168f +Subproject d44ca683c12ed28fce1d27662f24edee2d7e2c6 diff --git a/crop-tool/CMakeLists.txt b/crop-tool/CMakeLists.txt new file mode 100644 index 00000000..cc38c51b --- /dev/null +++ b/crop-tool/CMakeLists.txt @@ -0,0 +1,6 @@ +find_package(OpenCV QUIET REQUIRED COMPONENTS core imgcodecs imgproc highgui) +file(GLOB sources "*.cpp" CONFIGURE_ARGS) +add_executable(crop-tool ${sources}) +target_include_directories(crop-tool SYSTEM PRIVATE ${OpenCV_INCLUDE_DIRS}) +target_link_libraries(crop-tool opencv_highgui opencv_imgproc opencv_imgcodecs opencv_core) +target_link_libraries(crop-tool Corrade::Utility Magnum::Trade) diff --git a/crop-tool/atlas.cpp b/crop-tool/atlas.cpp new file mode 100644 index 00000000..0cb6eaf8 --- /dev/null +++ b/crop-tool/atlas.cpp @@ -0,0 +1,2 @@ +#include "atlas.hpp" +#include "../json.hpp" diff --git a/crop-tool/atlas.hpp b/crop-tool/atlas.hpp new file mode 100644 index 00000000..ad74fe4d --- /dev/null +++ b/crop-tool/atlas.hpp @@ -0,0 +1,31 @@ +#pragma once +#include <array> +#include <vector> +#include <Magnum/Trade/ImageData.h> + +struct anim_frame +{ + Magnum::Trade::ImageData2D image; + Magnum::Vector2us ground_offset = {}; +}; + +enum class anim_direction : unsigned char +{ + Invalid, + N, NE, E, SE, S, SW, W, NW, + MIN = N, MAX = NW, +}; + +struct anim_direction_group +{ + std::vector<anim_frame> frames; + anim_direction dir = anim_direction::Invalid; +}; + +struct anim +{ + std::array<anim_direction_group, (unsigned)anim_direction::MAX> directions; + int num_frames = 0, action_frame = 0, fps = default_fps; + + static constexpr int default_fps = 24; +}; diff --git a/crop-tool/crop-tool.cpp b/crop-tool/crop-tool.cpp new file mode 100644 index 00000000..1acbc397 --- /dev/null +++ b/crop-tool/crop-tool.cpp @@ -0,0 +1,235 @@ +#include "../defs.hpp" +#include "atlas.hpp" +#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> +#include <opencv2/highgui.hpp> +#include <optional> +#include <tuple> +#include <filesystem> +#include <algorithm> +#include <utility> +#include <cstring> +#include <cmath> + +#ifdef _WIN32 +# define EX_OK 0 /* successful termination */ +# define EX_USAGE 64 /* command line usage error */ +# define EX_SOFTWARE 70 /* internal software error */ +# define EX_IOERR 74 /* input/output error */ +#else +# include <sysexits.h> +#endif + +static std::string fix_path_separators(const std::filesystem::path& path) +{ + auto str = path.string(); + std::replace(str.begin(), str.end(), '\\', '/'); + return str; +} + +struct file +{ + std::filesystem::path name; + cv::Mat4b mat; + cv::Point2i offset, orig_size; +}; + +struct dir +{ + std::filesystem::path name; + std::vector<file> files; +}; + +using Corrade::Utility::Error; +using Corrade::Utility::Debug; + +static struct options_ { + std::optional<unsigned> width, height; + std::optional<double> scale; + cv::Vec2i offset{-1, -1}; +} options; + +static +std::tuple<cv::Vec2i, cv::Vec2i, bool> +find_image_bounds(const std::filesystem::path& path, const cv::Mat4b& mat) +{ + 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}; + cv::Vec4b px = ptr[x]; + if (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]) + { + Error{} << "image" << path.string() << "contains only fully transparent pixels!"; + return {{}, {}, false}; + } + + return {start, end, true}; +} + +static std::optional<file> load_file(const std::filesystem::path& filename) +{ + Debug{} << "load" << fix_path_separators(filename.string()); + + 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.string() << "as RGBA32 image"; + return cv::Mat4b{}; + } + return cv::Mat4b(std::move(mat_)); + ); + + if (mat.empty()) + return {}; + + auto [start, end, bounds_ok] = find_image_bounds(filename, mat); + + if (!bounds_ok) + return {}; + + cv::Size size{end - start}, dest_size; + + if (!options.scale) + { + if (options.width) + options.scale = (double)*options.width / size.width; + else if (options.height) + options.scale = (double)*options.height / size.height; + else + std::abort(); + } + + cv::Vec2i offset = { + (int)std::round((options.offset[0] - start[0]) * *options.scale), + (int)std::round((options.offset[1] - start[1]) * *options.scale), + }; + + dest_size = {(int)std::round(*options.scale * size.width), + (int)std::round(*options.scale * size.height)}; + + if (size.width < dest_size.width || size.height < dest_size.height) + { + Error{} << "refusing to upscale image" << filename.string(); + return {}; + } + + Debug{} << "file" << filename.string() << offset[0] << offset[1]; + + cv::Mat4b resized{size}; + cv::resize(mat({start, size}), resized, dest_size, 0, 0, cv::INTER_LANCZOS4); + file ret {filename, resized.clone(), start, size}; + return std::make_optional(std::move(ret)); +} + +static std::optional<dir> load_directory(const std::filesystem::path& dirname) +{ + if (std::error_code ec{}; !std::filesystem::exists(dirname / ".", ec)) + { + Error{} << "can't open directory" << dirname.string() << ":" << ec.message(); + return std::nullopt; + } + + Debug{} << "loading" << dirname.string(); + + dir ret; + for (int i = 1; i <= 9999; i++) + { + char buf[9]; + sprintf(buf, "%04d.png", i); + auto path = dirname / buf; + if (!std::filesystem::exists(path)) + break; + auto file = load_file(path); + if (!file) + return std::nullopt; + ret.files.push_back(std::move(*file)); + } + if (ret.files.empty()) + { + Error{} << "directory" << dirname.string() << "is empty!"; + return std::nullopt; + } + return std::make_optional(std::move(ret)); +} + +int main(int argc, char** argv) +{ + Corrade::Utility::Arguments args{}; +#ifdef _WIN32 + if (auto* c = strrchr(argv[0], '\\'); c && c[1]) + { + if (auto* s = strrchr(c, '.'); s && !strcmp(".exe", s)) + *s = '\0'; + args.setCommand(c+1); + } +#else + if (auto* c = strrchr(argv[0], '/'); c && c[1]) + args.setCommand(c+1); +#endif + args.addOption('o', "output", "output") + .addArrayArgument("directories") + .addOption('W', "width", "") + .addOption('H', "height", "") + .addOption('x', "offset") + .setHelp("offset", {}, "WxH"); + args.parse(argc, argv); + auto output = args.value<std::string>("output"); + std::vector<dir> dirs; + if (unsigned w = args.value<unsigned>("width"); w != 0) + options.width = w; + if (unsigned h = args.value<unsigned>("height"); h != 0) + options.height = h; + if (!(!options.width ^ !options.height)) + { + Error{} << "exactly one of --width, --height must be given"; + goto usage; + } + + { + auto str = args.value<std::string>("offset"); + if (str.empty()) + { + Error{} << "offset argument is required"; + goto usage; + } + int x, y; + int ret = std::sscanf(str.c_str(), "%dx%d", &x, &y); + if (ret != 2) + { + Error{} << "can't parse offset --" << str; + goto usage; + } + options.offset = {x, y}; + } + + for (std::size_t i = 0, cnt = args.arrayValueCount("directories"); i < cnt; i++) + { + auto dir = load_directory(args.arrayValue("directories", i)); + if (!dir) + goto usage; + dirs.push_back(std::move(*dir)); + } + + return 0; +usage: + Error{Error::Flag::NoNewlineAtTheEnd} << Corrade::Containers::StringView{args.usage()}; + return EX_USAGE; +} diff --git a/loader-impl.cpp b/loader-impl.cpp index a0f316c8..8e7cb081 100644 --- a/loader-impl.cpp +++ b/loader-impl.cpp @@ -5,8 +5,10 @@ #include <Corrade/Containers/StringView.h> #include <Corrade/PluginManager/PluginManager.h> #include <Corrade/Utility/Resource.h> +#include <Magnum/ImageView.h> #include <Magnum/Trade/AbstractImporter.h> #include <Magnum/Trade/ImageData.h> +#include <Magnum/Trade/AbstractImageConverter.h> #include <unordered_map> #include <utility> @@ -17,9 +19,13 @@ using atlas_ptr = std::shared_ptr<atlas_texture>; struct loader_impl final : loader_ { const Utility::Resource shader_res{"game/shaders"}; - PluginManager::Manager<Trade::AbstractImporter> plugins; + PluginManager::Manager<Trade::AbstractImporter> importer_plugins; Containers::Pointer<Trade::AbstractImporter> tga_importer = - plugins.loadAndInstantiate("TgaImporter"); + importer_plugins.loadAndInstantiate("TgaImporter"); + + PluginManager::Manager<Trade::AbstractImageConverter> image_converter_plugins; + Containers::Pointer<Trade::AbstractImageConverter> tga_converter = + image_converter_plugins.loadAndInstantiate("TgaImageConverter"); std::unordered_map<std::string, atlas_ptr> atlas_map; @@ -44,7 +50,8 @@ atlas_ptr loader_impl::tile_atlas(const Containers::StringView& name, Vector2i s auto it = atlas_map.find(name); if (it != atlas_map.end()) return it->second; - auto atlas = std::make_shared<atlas_texture>(tile_texture(name), size); + auto image = tile_texture(name); + auto atlas = std::make_shared<atlas_texture>(image, size); atlas_map[name] = atlas; return atlas; } diff --git a/magnum b/magnum -Subproject aef0ee4bfba8dd0e6b23bee4e170faf66c3e0e9 +Subproject a218ddfa9434201aaca207ea9c8e87fea91a075 diff --git a/magnum-integration b/magnum-integration -Subproject f29c091b0b699cdd092c9b80b4cb5535cf48175 +Subproject 6fa1a7754bf8bc612befdc9ec543f8420a0111c diff --git a/magnum-plugins b/magnum-plugins -Subproject 0e11d4110eebe8a5a23a206fd6183b6a4b34330 +Subproject 4100de3b63beac723690adfa4a3fd368b5d664b |