diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2014-02-04 16:22:13 +0100 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2014-03-14 11:36:09 +0100 |
commit | f0b821dff9cc0e74744640e5069e67e01909c59d (patch) | |
tree | 64e9feb7f52906bbaa82faac54cf45b0b4e5d909 | |
parent | bdfa761c39218234d9ce7419b3b323e8f25a1476 (diff) | |
download | qtlocation-mapboxgl-f0b821dff9cc0e74744640e5069e67e01909c59d.tar.gz |
add glyph atlas class
-rw-r--r-- | include/llmr/geometry/glyph_atlas.hpp | 53 | ||||
-rw-r--r-- | src/geometry/glyph_atlas.cpp | 130 |
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; + } +}; |