From a6e464b9a7487615adb421519bd3c3df3f517bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 3 Feb 2014 17:38:40 +0100 Subject: [PATCH] add sprite object and parse image refs #15 --- include/llmr/map/map.hpp | 4 + include/llmr/style/sprite.hpp | 43 ++++++- src/CMakeLists.txt | 4 + src/map/map.cpp | 12 +- src/style/sprite.cpp | 228 +++++++++++++++++++++++++++------- 5 files changed, 231 insertions(+), 60 deletions(-) diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index bf283098d..2d950afe4 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -12,6 +12,8 @@ namespace llmr { +class Sprite; + class Map { public: Map(Settings& settings); @@ -67,6 +69,8 @@ private: int32_t min_zoom; int32_t max_zoom; + std::shared_ptr sprite; + std::forward_list tiles; std::forward_list historic_tiles; }; diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index b8cb70574..67a4d2ec7 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -1,23 +1,58 @@ #ifndef LLMR_STYLE_SPRITE #define LLMR_STYLE_SPRITE + #include #include +#include +#include + +#include namespace llmr { -class Sprite { +class SpritePosition { public: - Sprite(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t pixelRatio = 1); + SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t pixelRatio = 1); uint16_t x = 0, y = 0; uint16_t width = 0, height = 0; uint8_t pixelRatio = 1; }; -typedef std::map Texture; +class ImagePosition { +public: + ImagePosition() {} + ImagePosition(const vec2& size, vec2 tl, vec2 br); -Texture createTexture(const std::string& data); + vec2 size = { 0, 0 }; + vec2 tl = { 0, 0 }; + vec2 br = { 0, 0 }; +}; + + +class Sprite : public std::enable_shared_from_this { +public: + void load(const std::string& base_url); + void bind(bool linear = false); + + ImagePosition getPosition(const std::string& name, bool repeating = false); + + operator bool() const; + +private: + void parseJSON(const std::string& data); + void loadImage(const std::string& data); + +private: + mutable std::mutex mtx; + bool loaded = false; + uint32_t filter = 0; + uint32_t texture = 0; + std::map pos; + uint32_t width = 0, height = 0; + std::string img; +}; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3c9d9c0d5..66fea4cd1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(PNG REQUIRED) + SET(llmr_SOURCES geometry/debug_font_buffer.cpp geometry/fill_buffer.cpp @@ -54,6 +56,7 @@ SET(llmr_HEADERS INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIR} + ${PNG_INCLUDE_DIR} ../include ) @@ -67,4 +70,5 @@ LINK_DIRECTORIES( TARGET_LINK_LIBRARIES(llmr ${Boost_LIBRARIES} + ${PNG_LIBRARY} ) diff --git a/src/map/map.cpp b/src/map/map.cpp index 6d69adf0d..db4805462 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -20,7 +20,9 @@ Map::Map(Settings& settings) painter(transform, settings, style), min_zoom(0), max_zoom(14) { - loadSprite("http://localhost:3333/gl/debug/img/sprite.json"); + + sprite = std::make_shared(); + sprite->load("http://localhost:3333/gl/debug/img/sprite"); } Map::~Map() { @@ -34,13 +36,7 @@ void Map::setup() { } void Map::loadSprite(const std::string& url) { - platform::request_http(url, [](const platform::Response& res) { - if (res.code == 200) { - createTexture(res.body); - } else { - fprintf(stderr, "failed to load sprite\n"); - } - }); + } void Map::loadStyle(const uint8_t *const data, uint32_t bytes) { diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index 7f23354ca..b7291102b 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -1,22 +1,64 @@ #include #include +#include + +#include #include -// #include using namespace llmr; -Sprite::Sprite(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t pixelRatio) +SpritePosition::SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t pixelRatio) : x(x), y(y), width(width), height(height), pixelRatio(pixelRatio) {} +ImagePosition::ImagePosition(const vec2& size, vec2 tl, vec2 br) + : size(size), + tl(tl), + br(br) {} -Texture llmr::createTexture(const std::string& data) { - Texture texture; + + +Sprite::operator bool() const { + std::lock_guard lock(mtx); + return loaded; +} + +void Sprite::load(const std::string& base_url) { + std::shared_ptr sprite = shared_from_this(); + + auto complete = [sprite]() { + std::lock_guard lock(sprite->mtx); + if (sprite->img.size() && sprite->pos.size()) { + sprite->loaded = true; + fprintf(stderr, "sprite loaded\n"); + } + }; + + platform::request_http(base_url + ".json", [sprite](const platform::Response & res) { + if (res.code == 200) { + sprite->parseJSON(res.body); + } else { + fprintf(stderr, "failed to load sprite\n"); + } + }, complete); + + platform::request_http(base_url + ".png", [sprite](const platform::Response & res) { + if (res.code == 200) { + sprite->loadImage(res.body); + } else { + fprintf(stderr, "failed to load sprite image\n"); + } + }, complete); +} + + +void Sprite::parseJSON(const std::string& data) { + std::lock_guard lock(mtx); rapidjson::Document d; d.Parse<0>(data.c_str()); @@ -39,55 +81,145 @@ Texture llmr::createTexture(const std::string& data) { if (value.HasMember("height")) height = value["height"].GetInt(); if (value.HasMember("pixelRatio")) pixelRatio = value["height"].GetInt(); - texture.insert({ name, { x, y, width, height, pixelRatio } }); + fprintf(stderr, ""); + + pos.insert({ name, { x, y, width, height, pixelRatio } }); } } } - - return texture; } -// Texture llmr::createTexture(const char *data, size_t length) { -// Texture texture; - -// json_t *root; -// json_error_t error; -// root = json_loadb(data, length, 0, &error); -// // TODO: handle error - -// if (json_is_object(root)) { -// const char *key; -// json_t *value; - -// json_object_foreach(root, key, value) { -// if (json_is_object(value)) { -// uint16_t x = 0; -// uint16_t y = 0; -// uint16_t width = 0; -// uint16_t height = 0; -// uint8_t pixelRatio = 1; - -// json_t *property; -// if ((property = json_object_get(value, "x"))) x = json_integer_value(property); -// if ((property = json_object_get(value, "y"))) y = json_integer_value(property); -// if ((property = json_object_get(value, "width"))) width = json_integer_value(property); -// if ((property = json_object_get(value, "height"))) height = json_integer_value(property); -// if ((property = json_object_get(value, "pixelRatio"))) pixelRatio = json_integer_value(property); - -// fprintf(stderr, "%s: %d %d | %d %d | %d\n", key, x, y, width, height, pixelRatio); - -// texture.insert({ key, { x, y, width, height, pixelRatio } }); -// } else { -// fprintf(stderr, "unknown value for key %s\n", key); -// } -// } -// } else { -// fprintf(stderr, "root element should be an object\n"); -// } - -// json_decref(root); +struct Buffer { + Buffer(const std::string& data) + : data(data.data()), length(data.size()) {} + const char *const data = 0; + const size_t length = 0; + size_t pos = 0; +}; -// return texture; -// } +void readCallback(png_structp png, png_bytep data, png_size_t length) { + Buffer *reader = static_cast(png_get_io_ptr(png)); + // Read `length` bytes into `data`. + if (reader->pos + length > reader->length) { + png_error(png, "Read Error"); + } else { + memcpy(data, reader->data + reader->pos, length); + reader->pos += length; + } +} + +void errorHandler(png_structp png, png_const_charp error_msg) { + throw std::runtime_error(error_msg); +} + +void warningHandler(png_structp png, png_const_charp error_msg) { + fprintf(stderr, "PNG: %s\n", error_msg); +} + +void Sprite::loadImage(const std::string& data) { + std::lock_guard lock(mtx); + + Buffer buffer(data); + + if (buffer.length < 8 || !png_check_sig((png_const_bytep)buffer.data, 8)) { + fprintf(stderr, "image is not a valid PNG image\n"); + return; + } + + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, errorHandler, warningHandler); + assert(png); + + png_infop info = png_create_info_struct(png); + assert(info); + + int depth, color, interlace; + + try { + png_set_read_fn(png, (png_voidp)&buffer, readCallback); + png_read_info(png, info); + png_get_IHDR(png, info, &width, &height, &depth, &color, &interlace, NULL, NULL); + bool alpha = (color & PNG_COLOR_MASK_ALPHA) || png_get_valid(png, info, PNG_INFO_tRNS); + + // From http://trac.mapnik.org/browser/trunk/src/png_reader.cpp + if (color == PNG_COLOR_TYPE_PALETTE) + png_set_expand(png); + if (color == PNG_COLOR_TYPE_GRAY) + png_set_expand(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_expand(png); + if (depth == 16) + png_set_strip_16(png); + if (depth < 8) + png_set_packing(png); + if (color == PNG_COLOR_TYPE_GRAY || + color == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + if (interlace == PNG_INTERLACE_ADAM7) + png_set_interlace_handling(png); + + // Always add an alpha channel. + if (!alpha) { + png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER); + } + + double gamma; + if (png_get_gAMA(png, info, &gamma)) + png_set_gamma(png, 2.2, gamma); + + png_read_update_info(png, info); + + unsigned int rowbytes = png_get_rowbytes(png, info); + assert(width * 4 == rowbytes); + + img.resize(width * height * 4); + char *surface = const_cast(img.data()); + assert(surface); + + png_bytep row_pointers[height]; + for (unsigned i = 0; i < height; i++) { + row_pointers[i] = (png_bytep)(surface + (i * rowbytes)); + } + + // Read image data + png_read_image(png, row_pointers); + + png_read_end(png, NULL); + } catch (std::exception& e) { + fprintf(stderr, "loading PNG failed: %s\n", e.what()); + png_destroy_read_struct(&png, &info, NULL); + img.clear(); + width = 0; + height = 0; + } +} + +ImagePosition Sprite::getPosition(const std::string& name, bool repeating) { + if (!*this) return {}; + + // `repeating` indicates that the image will be used in a repeating pattern + // repeating pattern images are assumed to have a 1px padding that mirrors the opposite edge + // positions for repeating images are adjusted to exclude the edge + int8_t offset = repeating ? 1 : 0; + + auto it = pos.find(name); + if (it == pos.end()) return {}; + + SpritePosition& pos = it->second; + return { + { + pos.width, + pos.height + }, + { + (float)(pos.x + offset) / width, + (float)(pos.y + offset) / height + }, + { + (float)(pos.x + pos.width - 2 * offset) / width, + (float)(pos.y + pos.height - 2 * offset) / height + } + }; +}