Mercurial > malikania
view tools/map/main.cpp @ 158:4b292c20124c
Misc: update copyrights
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 09 Jan 2018 13:15:07 +0100 |
parents | 0bedc450a9d2 |
children | 3107ce017c3a |
line wrap: on
line source
/* * main.cpp -- create malikania maps from tiled * * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * For information about the tiled .tmx format, check the following link: * * http://doc.mapeditor.org/reference/tmx-map-format/ */ #include <algorithm> #include <cerrno> #include <cstdint> #include <cstring> #include <fstream> #include <iostream> #include <vector> #include <stdexcept> #include <string> #include <boost/algorithm/string/trim.hpp> #include <boost/endian/conversion.hpp> #include <boost/filesystem/path.hpp> #include <boost/format.hpp> #include <boost/iostreams/copy.hpp> #include <boost/iostreams/device/back_inserter.hpp> #include <boost/iostreams/filter/gzip.hpp> #include <boost/iostreams/filter/zlib.hpp> #include <boost/iostreams/filtering_streambuf.hpp> #include <boost/property_tree/xml_parser.hpp> #include <json.hpp> #include "base64.hpp" namespace fs = boost::filesystem; namespace io = boost::iostreams; namespace ptree = boost::property_tree; namespace mlk { namespace { /* * data * ------------------------------------------------------------------ * * Store map data. */ class data { public: std::string title; std::string tileset; std::uint64_t width{0}; std::uint64_t height{0}; std::uint8_t cell_width{0}; std::uint8_t cell_height{0}; std::vector<std::uint32_t> background; std::vector<std::uint32_t> foreground; }; /* * load_properties * ------------------------------------------------------------------ * * Extract the following properties from the map: * * <map> * <properties> * <property name="title" value="..." /> * </properties> * </map> */ void load_properties(data& result, const ptree::ptree& tree) { for (const auto& p : tree) { if (p.first != "property") { continue; } auto name = p.second.get<std::string>("<xmlattr>.name"); if (name == "title") { result.title = p.second.get<std::string>("<xmlattr>.value"); } } } /* * load_layer_data_xml * ------------------------------------------------------------------ * * Convert the <data> node when saved as XML. */ std::vector<std::uint32_t> load_layer_data_xml(const ptree::ptree& tree) { std::vector<std::uint32_t> data; for (const auto& t : tree) { data.push_back(t.second.get<int>("<xmlattr>.gid")); } return data; } /* * load_layer_data_csv * ------------------------------------------------------------------ * * Convert the <data> node when saved as CSV. */ std::vector<std::uint32_t> load_layer_data_csv(const std::string& csv) { std::istringstream iss(csv); std::vector<std::uint32_t> data; for (std::string line; std::getline(iss, line, ','); ) { data.push_back(std::stoi(line)); } return data; } /* * load_layer_data_array * ------------------------------------------------------------------ * * Convert the <data> node when saved as uncompressed base64 data. * * The data is an array of 32 bits unsigned int in the little endian format. */ std::vector<std::uint32_t> load_layer_data_array(std::string bytes) { std::uint32_t* array = reinterpret_cast<std::uint32_t*>(&bytes[0]); std::vector<std::uint32_t> data; for (auto ptr = array; ptr != &array[bytes.length() / 4]; ++ptr) { data.push_back(*ptr); } return data; } /* * load_layer_data_array_compressed * ------------------------------------------------------------------ * * Decompress the data before parsing, supports zlib and gzip. */ std::vector<std::uint32_t> load_layer_data_array_compressed(const std::string& bytes, const std::string& compression) { io::filtering_streambuf<io::input> in; std::string out; if (compression == "zlib") { in.push(io::zlib_decompressor()); } else { in.push(io::gzip_decompressor()); } in.push(boost::make_iterator_range(bytes)); io::copy(in, io::back_inserter(out)); return load_layer_data_array(std::move(out)); } /* * load_layer_data * ------------------------------------------------------------------ * * Extract the <data> node from a <layer> */ std::vector<std::uint32_t> load_layer_data(const ptree::ptree& tree) { auto encoding = tree.get_optional<std::string>("<xmlattr>.encoding"); auto compression = tree.get_optional<std::string>("<xmlattr>.compression"); std::vector<std::uint32_t> data; if (!encoding) { data = load_layer_data_xml(tree); } else if (*encoding == "csv") { data = load_layer_data_csv(boost::trim_copy(tree.get_value<std::string>())); } else if (*encoding == "base64") { auto b64 = base64::decode(boost::trim_copy(tree.get_value<std::string>())); if (!compression) { data = load_layer_data_array(std::move(b64)); } else if (*compression == "zlib" || *compression == "gzip") { data = load_layer_data_array_compressed(b64, *compression); } // Convert endianness if system is not little endian. if (boost::endian::order::native != boost::endian::order::little) { std::transform(data.begin(), data.end(), data.begin(), [] (auto c) { return boost::endian::endian_reverse(c); }); } } return data; } /* * load_layer * ------------------------------------------------------------------ * * Extract the following properties from the map: * * <map> * <layer> * <data></data> * </layer> * </map> */ void load_layer(data& map, const ptree::ptree& tree) { auto name = tree.get<std::string>("<xmlattr>.name"); if (name != "background" && name != "foreground") { throw std::runtime_error(boost::str(boost::format( "invalid layer name given '%s'\n" "(only 'foreground' and 'background' is supported)" ) % name)); } auto data = load_layer_data(tree.get_child("data")); if (name == "background") { map.background = std::move(data); } else { map.foreground = std::move(data); } } /* * load * ------------------------------------------------------------------ * * Load the .tmx file from the path and return it. */ data load(const std::string& path) { data result; ptree::ptree tree; ptree::read_xml(path, tree); result.width = tree.get<int>("map.<xmlattr>.width"); result.height = tree.get<int>("map.<xmlattr>.height"); result.cell_height = tree.get<int>("map.<xmlattr>.tilewidth"); result.cell_width = tree.get<int>("map.<xmlattr>.tileheight"); result.tileset = tree.get<std::string>("map.tileset.image.<xmlattr>.source"); for (const auto& pair : tree.get_child("map")) { if (pair.first == "properties") { load_properties(result, pair.second); } else if (pair.first == "layer") { load_layer(result, pair.second); } } return result; } /* * save_meta * ------------------------------------------------------------------ * * Save map metadata into the specified file. */ void save_meta(const data& map, const fs::path& path) { auto json = nlohmann::json::object({ { "title", map.title }, { "tileset", map.tileset }, { "size", { map.width, map.height } }, { "cell", { map.cell_width, map.cell_height } }, }); std::ofstream out(path.string(), std::ofstream::out | std::ofstream::trunc); if (!out) { throw std::runtime_error(std::strerror(errno)); } out << json.dump(4) << std::endl; } /* * save_map * ------------------------------------------------------------------ * * Save the map tileset info the specified path. */ void save_map(const data& map, const fs::path& path) { std::ofstream out(path.string(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); if (!out) { throw std::runtime_error(std::strerror(errno)); } out.write( reinterpret_cast<const char*>(map.background.data()), sizeof (map.background[0]) * map.background.size() ); out.write( reinterpret_cast<const char*>(map.foreground.data()), sizeof (map.foreground[0]) * map.foreground.size() ); } /* * save * ------------------------------------------------------------------ * * Save the map data into the JSON file and map data. * * Check MAP.md for the specifications of the JSON file. */ void save(const data& map, const fs::path& meta, const fs::path& pack) { save_meta(map, meta); save_map(map, pack); } } // !namespace } // !mlk int main(int argc, char** argv) { -- argc; ++ argv; if (argc < 1) { std::cerr << "usage mlk-map input.xml [output.json [output.map]]" << std::endl; return 1; } try { fs::path meta, map; if (argc >= 3) { meta = argv[1]; map = argv[2]; } else if (argc >= 2) { meta = argv[1]; map = meta.replace_extension(".map"); } else { meta = fs::path(argv[0]).replace_extension(".json"); map = fs::path(argv[0]).replace_extension(".map"); } mlk::save(mlk::load(argv[0]), meta, map); } catch (const std::exception& ex) { std::cerr << ex.what() << std::endl; return 1; } }