summaryrefslogtreecommitdiff
path: root/src/mbgl/geometry/sprite_atlas.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/geometry/sprite_atlas.cpp')
-rw-r--r--src/mbgl/geometry/sprite_atlas.cpp261
1 files changed, 261 insertions, 0 deletions
diff --git a/src/mbgl/geometry/sprite_atlas.cpp b/src/mbgl/geometry/sprite_atlas.cpp
new file mode 100644
index 0000000000..7dc8f60ae6
--- /dev/null
+++ b/src/mbgl/geometry/sprite_atlas.cpp
@@ -0,0 +1,261 @@
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/constants.hpp>
+
+#include <mbgl/map/sprite.hpp>
+
+#include <cassert>
+#include <cmath>
+#include <algorithm>
+
+
+using namespace mbgl;
+
+SpriteAtlas::SpriteAtlas(dimension width_, dimension height_)
+ : width(width_),
+ height(height_),
+ bin(width_, height_),
+ dirty(true) {
+}
+
+bool SpriteAtlas::resize(const float newRatio) {
+ if (pixelRatio == newRatio) return false;
+
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ const float oldRatio = pixelRatio;
+ pixelRatio = newRatio;
+
+ if (data) {
+ uint32_t *old_data = data;
+
+ data = nullptr;
+ allocate();
+
+ const int old_w = width * oldRatio;
+ const int old_h = height * oldRatio;
+ const int new_w = width * newRatio;
+ const int new_h = height * newRatio;
+
+ // Basic image scaling. TODO: Replace this with better image scaling.
+ uint32_t *img_new = reinterpret_cast<uint32_t *>(data);
+ const uint32_t *img_old = reinterpret_cast<const uint32_t *>(old_data);
+
+ for (int y = 0; y < new_h; y++) {
+ const int old_yoffset = ((y * old_h) / new_h) * old_w;
+ const int new_yoffset = y * new_w;
+ for (int x = 0; x < new_w; x++) {
+ const int old_x = (x * old_w) / new_w;
+ img_new[new_yoffset + x] = img_old[old_yoffset + old_x];
+ }
+ }
+
+ ::operator delete(old_data);
+ dirty = true;
+
+ // Mark all sprite images as in need of update
+ for (const auto &pair : images) {
+ uninitialized.emplace(pair.first);
+ }
+ }
+
+ return dirty;
+}
+
+void copy_bitmap(const uint32_t *src, const int src_stride, const int src_x, const int src_y,
+ uint32_t *dst, const int dst_stride, const int dst_x, const int dst_y,
+ const int width, const int height) {
+ src += src_y * src_stride + src_x;
+ dst += dst_y * dst_stride + dst_x;
+ for (int y = 0; y < height; y++, src += src_stride, dst += dst_stride) {
+ for (int x = 0; x < width; x++) {
+ dst[x] = src[x];
+ }
+ }
+}
+
+Rect<SpriteAtlas::dimension> SpriteAtlas::allocateImage(size_t pixel_width, size_t pixel_height) {
+ // We have to allocate a new area in the bin, and store an empty image in it.
+ // Add a 1px border around every image.
+ Rect<dimension> rect = bin.allocate(pixel_width + 2 * buffer, pixel_height + 2 * buffer);
+ if (rect.w == 0) {
+ return rect;
+ }
+
+ rect.x += buffer;
+ rect.y += buffer;
+ rect.w -= 2 * buffer;
+ rect.h -= 2 * buffer;
+
+ return rect;
+}
+
+Rect<SpriteAtlas::dimension> SpriteAtlas::getImage(const std::string& name) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ auto rect_it = images.find(name);
+ if (rect_it != images.end()) {
+ return rect_it->second;
+ }
+
+ const SpritePosition &pos = sprite->getSpritePosition(name);
+ if (!pos.width || !pos.height) {
+ return Rect<dimension> { 0, 0, 0, 0 };
+ }
+
+ Rect<dimension> rect = allocateImage(pos.width / pos.pixelRatio, pos.height / pos.pixelRatio);
+ if (rect.w == 0) {
+ if (debug::spriteWarnings) {
+ fprintf(stderr, "[WARNING] sprite atlas bitmap overflow\n");
+ }
+ return rect;
+ }
+
+ images.emplace(name, rect);
+
+ copy(rect, pos);
+
+ return rect;
+}
+
+SpriteAtlasPosition SpriteAtlas::getPosition(const std::string& name, bool repeating) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+ // `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
+ Rect<dimension> rect = getImage(name);
+ const int r = repeating ? 1 : 0;
+ return SpriteAtlasPosition {
+ {{ float(rect.w) / pixelRatio, float(rect.h) / pixelRatio }},
+ {{ float(rect.x + r) / width, float(rect.y + r) / height }},
+ {{ float(rect.x + rect.w - 2*r) / width, float(rect.y + rect.h - 2*r) / height }}
+ };
+}
+
+void SpriteAtlas::allocate() {
+ if (!data) {
+ dimension w = static_cast<dimension>(width * pixelRatio);
+ dimension h = static_cast<dimension>(height * pixelRatio);
+ data = static_cast<uint32_t*>(::operator new(w * h * sizeof(uint32_t)));
+ std::fill(data, data + w * h, 0);
+ }
+}
+
+void SpriteAtlas::copy(const Rect<dimension>& dst, const SpritePosition& src) {
+ if (!sprite->raster) return;
+ const uint32_t *src_img = reinterpret_cast<const uint32_t *>(sprite->raster->getData());
+ if (!src_img) return;
+ allocate();
+ uint32_t *dst_img = reinterpret_cast<uint32_t *>(data);
+
+ copy_bitmap(
+ /* source buffer */ src_img,
+ /* source stride */ sprite->raster->getWidth(),
+ /* source x */ src.x,
+ /* source y */ src.y,
+ /* dest buffer */ dst_img,
+ /* dest stride */ width * pixelRatio,
+ /* dest x */ dst.x * pixelRatio,
+ /* dest y */ dst.y * pixelRatio,
+ /* icon dimension */ src.width,
+ /* icon dimension */ src.height
+ );
+
+ dirty = true;
+}
+
+void SpriteAtlas::setSprite(util::ptr<Sprite> sprite_) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ sprite = sprite_;
+
+ if (!sprite->isLoaded()) return;
+
+ util::erase_if(uninitialized, [this](const std::string &name) {
+ Rect<dimension> dst = getImage(name);
+ const SpritePosition& src = sprite->getSpritePosition(name);
+ if (!src) {
+ if (debug::spriteWarnings) {
+ fprintf(stderr, "[WARNING] sprite doesn't have image with name '%s'\n", name.c_str());
+ }
+ return true;
+ }
+
+ if (src.width == dst.w * pixelRatio && src.height == dst.h * pixelRatio && src.pixelRatio == pixelRatio) {
+ copy(dst, src);
+ return true;
+ } else {
+ if (debug::spriteWarnings) {
+ fprintf(stderr, "[WARNING] sprite icon dimension mismatch\n");
+ }
+ return false;
+ }
+ });
+}
+
+void SpriteAtlas::bind(bool linear) {
+ bool first = false;
+ if (!texture) {
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+#ifndef GL_ES_VERSION_2_0
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+#endif
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ first = true;
+ } else {
+ glBindTexture(GL_TEXTURE_2D, texture);
+ }
+
+ GLuint filter_val = linear ? GL_LINEAR : GL_NEAREST;
+ if (filter_val != filter) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_val);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_val);
+ filter = filter_val;
+ }
+
+ if (dirty) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+ allocate();
+
+ if (first) {
+ glTexImage2D(
+ GL_TEXTURE_2D, // GLenum target
+ 0, // GLint level
+ GL_RGBA, // GLint internalformat
+ width * pixelRatio, // GLsizei width
+ height * pixelRatio, // GLsizei height
+ 0, // GLint border
+ GL_RGBA, // GLenum format
+ GL_UNSIGNED_BYTE, // GLenum type
+ data // const GLvoid * data
+ );
+ } else {
+ glTexSubImage2D(
+ GL_TEXTURE_2D, // GLenum target
+ 0, // GLint level
+ 0, // GLint xoffset
+ 0, // GLint yoffset
+ width * pixelRatio, // GLsizei width
+ height * pixelRatio, // GLsizei height
+ GL_RGBA, // GLenum format
+ GL_UNSIGNED_BYTE, // GLenum type
+ data // const GLvoid *pixels
+ );
+ }
+
+ dirty = false;
+ }
+};
+
+SpriteAtlas::~SpriteAtlas() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ glDeleteTextures(1, &texture);
+ texture = 0;
+ ::operator delete(data), data = nullptr;
+}