summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2014-02-04 16:22:13 +0100
committerKonstantin Käfer <mail@kkaefer.com>2014-03-14 11:36:09 +0100
commitf0b821dff9cc0e74744640e5069e67e01909c59d (patch)
tree64e9feb7f52906bbaa82faac54cf45b0b4e5d909
parentbdfa761c39218234d9ce7419b3b323e8f25a1476 (diff)
downloadqtlocation-mapboxgl-f0b821dff9cc0e74744640e5069e67e01909c59d.tar.gz
add glyph atlas class
-rw-r--r--include/llmr/geometry/glyph_atlas.hpp53
-rw-r--r--src/geometry/glyph_atlas.cpp130
2 files changed, 183 insertions, 0 deletions
diff --git a/include/llmr/geometry/glyph_atlas.hpp b/include/llmr/geometry/glyph_atlas.hpp
new file mode 100644
index 0000000000..af159dbe70
--- /dev/null
+++ b/include/llmr/geometry/glyph_atlas.hpp
@@ -0,0 +1,53 @@
+#ifndef LLMR_GEOMETRY_GLYPH_ATLAS
+#define LLMR_GEOMETRY_GLYPH_ATLAS
+
+#include <llmr/geometry/binpack.hpp>
+
+#include <string>
+#include <set>
+#include <map>
+
+namespace llmr {
+
+class Glyph {
+public:
+ uint32_t id = 0;
+ std::string bitmap;
+ uint16_t width = 0;
+ uint16_t height = 0;
+};
+
+class GlyphAtlas {
+public:
+
+private:
+ struct GlyphValue {
+ GlyphValue(const Rect<uint16_t>& rect, uint64_t id)
+ : rect(rect), ids({ id }) {}
+ Rect<uint16_t> rect;
+ std::set<uint64_t> ids;
+ };
+
+public:
+ GlyphAtlas(uint16_t width, uint16_t height);
+ ~GlyphAtlas();
+
+
+ Rect<uint16_t> addGlyph(uint64_t tile_id, const std::string& face_name,
+ const Glyph& glyph, uint8_t buffer);
+ void removeGlyphs(uint64_t tile_id);
+ void bind();
+
+private:
+ const uint16_t width = 0;
+ const uint16_t height = 0;
+ BinPack<uint16_t> bin;
+ std::map<std::string, std::map<uint32_t, GlyphValue>> index;
+ char *const data = NULL;
+ bool dirty = true;
+ uint32_t texture = 0;
+};
+
+};
+
+#endif
diff --git a/src/geometry/glyph_atlas.cpp b/src/geometry/glyph_atlas.cpp
new file mode 100644
index 0000000000..2afe25c8ff
--- /dev/null
+++ b/src/geometry/glyph_atlas.cpp
@@ -0,0 +1,130 @@
+#include <llmr/geometry/glyph_atlas.hpp>
+#include <llmr/platform/gl.hpp>
+
+#include <cassert>
+#include <algorithm>
+
+
+using namespace llmr;
+
+GlyphAtlas::GlyphAtlas(uint16_t width, uint16_t height)
+ : width(width),
+ height(height),
+ bin(width, height),
+ data(new char[width *height]) {
+}
+
+GlyphAtlas::~GlyphAtlas() {
+ delete[] data;
+}
+
+Rect<uint16_t> GlyphAtlas::addGlyph(uint64_t tile_id, const std::string& face_name,
+ const Glyph& glyph, uint8_t buffer) {
+
+ std::map<uint32_t, GlyphValue>& face = index[face_name];
+ std::map<uint32_t, GlyphValue>::iterator it = face.find(glyph.id);
+
+ // The glyph is already in this texture.
+ if (it != face.end()) {
+ GlyphValue& value = it->second;
+ value.ids.insert(tile_id);
+ return value.rect;
+ }
+
+ // The glyph bitmap has zero width.
+ if (!glyph.width) {
+ return { 0, 0, 0, 0 };
+ }
+
+ uint16_t buffered_width = glyph.width + buffer * 2;
+ uint16_t buffered_height = glyph.height + buffer * 2;
+
+ // Add a 1px border around every image.
+ uint16_t pack_width = buffered_width;
+ uint16_t pack_height = buffered_height;
+
+ // Increase to next number divisible by 4, but at least 1.
+ // This is so we can scale down the texture coordinates and pack them
+ // into 2 bytes rather than 4 bytes.
+ pack_width += (4 - pack_width % 4);
+ pack_height += (4 - pack_height % 4);
+
+ Rect<uint16_t> rect = bin.allocate(pack_width, pack_height);
+ if (rect.w == 0) {
+ fprintf(stderr, "glyph bitmap overflow");
+ return rect;
+ }
+
+ assert(rect.x + rect.w <= width);
+ assert(rect.y + rect.h <= height);
+
+ face.emplace(glyph.id, GlyphValue { rect, tile_id });
+
+ // Copy the bitmap
+ char *target = data;
+ const char *source = glyph.bitmap.data();
+ for (uint32_t y = 0; y < buffered_height; y++) {
+ uint32_t y1 = width * (rect.y + y) + rect.x;
+ uint32_t y2 = buffered_width * y;
+ for (uint32_t x = 0; x < buffered_width; x++) {
+ target[y1 + x] = source[y2 + x];
+ }
+ }
+
+ dirty = true;
+
+ return rect;
+}
+
+void GlyphAtlas::removeGlyphs(uint64_t tile_id) {
+ for (auto& faces : index) {
+ std::map<uint32_t, GlyphValue>& face = faces.second;
+ for (auto it = face.begin(); it != face.end(); /* we advance in the body */) {
+ GlyphValue& value = it->second;
+ value.ids.erase(tile_id);
+
+ if (!value.ids.size()) {
+ const Rect<uint16_t>& rect = value.rect;
+
+ // Clear out the bitmap.
+ char *target = data;
+ for (uint32_t y = 0; y < rect.h; y++) {
+ uint32_t y1 = width * (rect.y + y) + rect.x;
+ for (uint32_t x = 0; x < rect.w; x++) {
+ target[y1 + x] = 0;
+ }
+ }
+
+ dirty = true;
+
+ bin.release(rect);
+
+ // Make sure to post-increment the iterator: This will return the
+ // current iterator, but will go to the next position before we
+ // erase the element from the map. That way, the iterator stays
+ // valid.
+ face.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+ }
+}
+
+void GlyphAtlas::bind() {
+ if (!texture) {
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ } else {
+ glBindTexture(GL_TEXTURE_2D, texture);
+ }
+
+ if (dirty) {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data);
+ dirty = false;
+ }
+};