Merge branch 'master' of github.com:mapbox/llmr-native
This commit is contained in:
186
bin/style.js
186
bin/style.js
@@ -373,7 +373,8 @@ module.exports = {
|
||||
"layer": "place_label",
|
||||
"field": "type",
|
||||
"value": "city",
|
||||
"type": "point"
|
||||
"type": "point",
|
||||
"size": 4
|
||||
},
|
||||
"place_label_city": {
|
||||
"source": "mapbox streets",
|
||||
@@ -452,116 +453,13 @@ module.exports = {
|
||||
"feature_type": "point",
|
||||
"type": "text"
|
||||
},
|
||||
"poi_airport": {
|
||||
"poi": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "airport",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"icon": "maki",
|
||||
"size": 12,
|
||||
"type": "point"
|
||||
},
|
||||
"poi_rail": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "rail",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
},
|
||||
"poi_fire_station": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "fire-station",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
},
|
||||
"poi_bus": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "bus",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
},
|
||||
"poi_restaurant": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "restaurant",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
},
|
||||
"poi_park": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "park",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
},
|
||||
"poi_playground": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "playground",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
},
|
||||
"poi_hospital": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "hospital",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
},
|
||||
"poi_cafe": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "cafe",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
},
|
||||
"poi_beer": {
|
||||
"source": "mapbox streets",
|
||||
"layer": "poi_label",
|
||||
"field": "maki",
|
||||
"value": "beer",
|
||||
"size": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
},
|
||||
"type": "point"
|
||||
}
|
||||
},
|
||||
"constants": {
|
||||
"land": "#f8f4f0",
|
||||
@@ -801,42 +699,12 @@ module.exports = {
|
||||
}, {
|
||||
"name": "road_label",
|
||||
"bucket": "road_label"
|
||||
}, {
|
||||
"name": "poi_label",
|
||||
"bucket": "poi_label"
|
||||
}, {
|
||||
"name": "water_label",
|
||||
"bucket": "water_label"
|
||||
}, {
|
||||
"name": "poi_airport",
|
||||
"bucket": "poi_airport"
|
||||
}, {
|
||||
"name": "poi_rail",
|
||||
"bucket": "poi_rail"
|
||||
}, {
|
||||
"name": "poi_bus",
|
||||
"bucket": "poi_bus"
|
||||
}, {
|
||||
"name": "poi_fire_station",
|
||||
"bucket": "poi_fire_station"
|
||||
}, {
|
||||
"name": "poi_restaurant",
|
||||
"bucket": "poi_restaurant"
|
||||
}, {
|
||||
"name": "poi_park",
|
||||
"bucket": "poi_park"
|
||||
}, {
|
||||
"name": "poi_hospital",
|
||||
"bucket": "poi_hospital"
|
||||
}, {
|
||||
"name": "poi_playground",
|
||||
"bucket": "poi_playground"
|
||||
}, {
|
||||
"name": "poi_cafe",
|
||||
"bucket": "poi_cafe"
|
||||
}, {
|
||||
"name": "poi_beer",
|
||||
"bucket": "poi_beer"
|
||||
"name": "poi",
|
||||
"bucket": "poi"
|
||||
}],
|
||||
"classes": [{
|
||||
"name": "default",
|
||||
@@ -2010,45 +1878,7 @@ module.exports = {
|
||||
0.75
|
||||
],
|
||||
},
|
||||
"poi_airport": {
|
||||
"color": "maki",
|
||||
"image": "airport-12",
|
||||
},
|
||||
"poi_restaurant": {
|
||||
"color": "maki",
|
||||
"image": "restaurant-12",
|
||||
},
|
||||
"poi_bus": {
|
||||
"color": "maki",
|
||||
"image": "bus-12",
|
||||
},
|
||||
"poi_rail": {
|
||||
"color": "maki",
|
||||
"image": "rail-12",
|
||||
},
|
||||
"poi_fire_station": {
|
||||
"color": "maki",
|
||||
"image": "fire-station-12",
|
||||
},
|
||||
"poi_park": {
|
||||
"color": "maki",
|
||||
"image": "park-12",
|
||||
},
|
||||
"poi_hospital": {
|
||||
"color": "maki",
|
||||
"image": "hospital-12",
|
||||
},
|
||||
"poi_playground": {
|
||||
"color": "maki",
|
||||
"image": "playground-12",
|
||||
},
|
||||
"poi_cafe": {
|
||||
"color": "maki",
|
||||
"image": "cafe-12",
|
||||
},
|
||||
"poi_beer": {
|
||||
"color": "maki",
|
||||
"image": "beer-12",
|
||||
"poi": {
|
||||
},
|
||||
"satellite": {
|
||||
"type": "raster",
|
||||
|
||||
@@ -204,6 +204,8 @@ namespace platform {
|
||||
double elapsed() { return glfwGetTime(); }
|
||||
|
||||
void show_debug_image(std::string name, const char *data, size_t width, size_t height) {
|
||||
glfwInit();
|
||||
|
||||
static GLFWwindow *debug_window = nullptr;
|
||||
if (!debug_window) {
|
||||
debug_window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr);
|
||||
@@ -231,5 +233,37 @@ void show_debug_image(std::string name, const char *data, size_t width, size_t h
|
||||
glfwMakeContextCurrent(current_window);
|
||||
}
|
||||
|
||||
|
||||
void show_color_debug_image(std::string name, const char *data, size_t logical_width, size_t logical_height, size_t width, size_t height) {
|
||||
glfwInit();
|
||||
|
||||
static GLFWwindow *debug_window = nullptr;
|
||||
if (!debug_window) {
|
||||
debug_window = glfwCreateWindow(logical_width, logical_height, name.c_str(), nullptr, nullptr);
|
||||
if (!debug_window) {
|
||||
glfwTerminate();
|
||||
fprintf(stderr, "Failed to initialize window\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
GLFWwindow *current_window = glfwGetCurrentContext();
|
||||
|
||||
glfwSetWindowSize(debug_window, logical_width, logical_height);
|
||||
glfwMakeContextCurrent(debug_window);
|
||||
|
||||
int fb_width, fb_height;
|
||||
glfwGetFramebufferSize(debug_window, &fb_width, &fb_height);
|
||||
float x_scale = (float)fb_width / (float)width;
|
||||
float y_scale = (float)fb_height / (float)height;
|
||||
|
||||
glPixelZoom(x_scale, -y_scale);
|
||||
glRasterPos2f(-1.0f, 1.0f);
|
||||
glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
|
||||
glfwSwapBuffers(debug_window);
|
||||
|
||||
glfwMakeContextCurrent(current_window);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
#ifndef LLMR_GEOMETRY_BUFFER
|
||||
#define LLMR_GEOMETRY_BUFFER
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
template <
|
||||
size_t item_size,
|
||||
int bufferType = GL_ARRAY_BUFFER,
|
||||
size_t defaultLength = 8192
|
||||
size_t defaultLength = 8192,
|
||||
bool retainAfterUpload = false
|
||||
>
|
||||
class Buffer : private util::noncopyable {
|
||||
public:
|
||||
~Buffer() {
|
||||
if (array) {
|
||||
free(array);
|
||||
array = nullptr;
|
||||
}
|
||||
cleanup();
|
||||
if (buffer != 0) {
|
||||
glDeleteBuffers(1, &buffer);
|
||||
buffer = 0;
|
||||
@@ -33,18 +33,27 @@ public:
|
||||
}
|
||||
|
||||
// Transfers this buffer to the GPU and binds the buffer to the GL context.
|
||||
void bind() {
|
||||
void bind(bool force = false) {
|
||||
if (buffer == 0) {
|
||||
glGenBuffers(1, &buffer);
|
||||
glBindBuffer(bufferType, buffer);
|
||||
force = true;
|
||||
}
|
||||
glBindBuffer(bufferType, buffer);
|
||||
if (force) {
|
||||
assert("Buffer was already deleted" && array != nullptr);
|
||||
glBufferData(bufferType, pos, array, GL_STATIC_DRAW);
|
||||
free(array);
|
||||
array = nullptr;
|
||||
} else {
|
||||
glBindBuffer(bufferType, buffer);
|
||||
if (!retainAfterUpload) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (array) {
|
||||
free(array);
|
||||
array = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// increase the buffer size by at least /required/ bytes.
|
||||
@@ -59,6 +68,17 @@ protected:
|
||||
return static_cast<char *>(array) + (pos - itemSize);
|
||||
}
|
||||
|
||||
// Get a pointer to the item at a given index.
|
||||
inline void *getElement(size_t index) {
|
||||
assert("Buffer was deleted" && array != nullptr);
|
||||
|
||||
if (index * itemSize >= pos) {
|
||||
throw new std::runtime_error("Can't get element after array bounds");
|
||||
} else {
|
||||
return static_cast<char *>(array) + (index * itemSize);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static const size_t itemSize = item_size;
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
namespace llmr {
|
||||
|
||||
class IconVertexBuffer : public Buffer<
|
||||
4 // 2 coordinates per vertex (== 4 bytes)
|
||||
4 + // int16 x/y coordinates per vertex (== 4 bytes)
|
||||
4 // uint16 x/y coordinates of icon in sprite (== 4 bytes)
|
||||
> {
|
||||
public:
|
||||
typedef int16_t vertex_type;
|
||||
|
||||
void add(vertex_type x, vertex_type y);
|
||||
void add(vertex_type x, vertex_type y, uint16_t tx, uint16_t ty);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
74
include/llmr/geometry/sprite_atlas.hpp
Normal file
74
include/llmr/geometry/sprite_atlas.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef LLMR_GEOMETRY_SPRITE_ATLAS
|
||||
#define LLMR_GEOMETRY_SPRITE_ATLAS
|
||||
|
||||
#include <llmr/geometry/binpack.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <set>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Sprite;
|
||||
class SpritePosition;
|
||||
|
||||
class SpriteAtlas {
|
||||
public:
|
||||
typedef uint16_t dimension;
|
||||
|
||||
public:
|
||||
// Add way to construct this from another SpriteAtlas (e.g. with another pixelRatio)
|
||||
SpriteAtlas(dimension width, dimension height);
|
||||
~SpriteAtlas();
|
||||
|
||||
// Changes the pixel ratio.
|
||||
bool resize(float newRatio);
|
||||
|
||||
// Update uninitialized sprites in this atlas from the given sprite.
|
||||
void update(const Sprite &sprite);
|
||||
|
||||
// Returns the coordinates of a square icon. The getter also *creates* new square icons in the
|
||||
// atlas if they don't exist, but they'll be default-initialized with a a black circle.
|
||||
Rect<dimension> getIcon(int size, const std::string &name);
|
||||
|
||||
// Returns the coordinates of an image that is sourced from the sprite image.
|
||||
// This getter does not create images, as the dimension of the texture us unknown if the
|
||||
// sprite is not yet loaded. Instead, it returns a 0/0/0/0 rect.
|
||||
Rect<dimension> getImage(const std::string &name, const Sprite &sprite);
|
||||
|
||||
// Binds the image buffer of this sprite atlas to the GPU, and uploads data if it is out
|
||||
// of date.
|
||||
void bind(bool linear = false);
|
||||
|
||||
inline float getWidth() const { return width; }
|
||||
inline float getHeight() const { return height; }
|
||||
inline float getTextureWidth() const { return width * pixelRatio; }
|
||||
inline float getTextureHeight() const { return height * pixelRatio; }
|
||||
|
||||
private:
|
||||
void allocate();
|
||||
Rect<SpriteAtlas::dimension> allocateImage(size_t width, size_t height);
|
||||
void copy(const Rect<dimension> &dst, const SpritePosition &src, const Sprite &sprite);
|
||||
|
||||
public:
|
||||
const dimension width = 0;
|
||||
const dimension height = 0;
|
||||
|
||||
private:
|
||||
std::mutex mtx;
|
||||
float pixelRatio = 1.0f;
|
||||
BinPack<dimension> bin;
|
||||
std::map<std::string, Rect<dimension>> images;
|
||||
std::set<std::string> uninitialized;
|
||||
char *data = nullptr;
|
||||
std::atomic<bool> dirty;
|
||||
uint32_t texture = 0;
|
||||
uint32_t filter = 0;
|
||||
static const int buffer = 1;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -92,6 +92,7 @@ public:
|
||||
inline const TransformState &getState() const { return state; }
|
||||
inline const Style &getStyle() const { return style; }
|
||||
inline GlyphAtlas &getGlyphAtlas() { return glyphAtlas; }
|
||||
inline SpriteAtlas &getSpriteAtlas() { return spriteAtlas; }
|
||||
inline uv_loop_t *getLoop() { return loop; }
|
||||
inline time getAnimationTime() const { return animationTime; }
|
||||
inline Texturepool &getTexturepool() { return texturepool; }
|
||||
@@ -143,6 +144,7 @@ private:
|
||||
Texturepool texturepool;
|
||||
Style style;
|
||||
GlyphAtlas glyphAtlas;
|
||||
SpriteAtlas spriteAtlas;
|
||||
Painter painter;
|
||||
|
||||
std::map<std::string, const std::unique_ptr<Source>> sources;
|
||||
|
||||
@@ -9,13 +9,14 @@ namespace llmr {
|
||||
|
||||
class Style;
|
||||
class GlyphAtlas;
|
||||
class SpriteAtlas;
|
||||
|
||||
class LayerDescription;
|
||||
class Bucket;
|
||||
|
||||
class TileParser {
|
||||
public:
|
||||
TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas);
|
||||
TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas);
|
||||
|
||||
private:
|
||||
bool obsolete() const;
|
||||
@@ -27,12 +28,14 @@ private:
|
||||
std::unique_ptr<Bucket> createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
std::unique_ptr<Bucket> createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
template <class Bucket> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
template <class Bucket, typename ...Args> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args&& ...args);
|
||||
|
||||
private:
|
||||
const VectorTile vector_data;
|
||||
VectorTileData& tile;
|
||||
const Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
SpriteAtlas &spriteAtlas;
|
||||
Faces faces;
|
||||
Placement placement;
|
||||
};
|
||||
|
||||
@@ -32,8 +32,11 @@ std::shared_ptr<Request> request_http(const std::string &url,
|
||||
// Cancels an HTTP request.
|
||||
void cancel_request_http(const std::shared_ptr<Request> &req);
|
||||
|
||||
// Shows an RGBA image with the specified dimensions in a named window.
|
||||
// Shows an alpha image with the specified dimensions in a named window.
|
||||
void show_debug_image(std::string name, const char *data, size_t width, size_t height);
|
||||
|
||||
// Shows an alpha image with the specified dimensions in a named window.
|
||||
void show_color_debug_image(std::string name, const char *data, size_t logical_width, size_t logical_height, size_t width, size_t height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ class IconVertexBuffer;
|
||||
class BucketDescription;
|
||||
class IconShader;
|
||||
class DotShader;
|
||||
struct pbf;
|
||||
class SpriteAtlas;
|
||||
class VectorTileFeature;
|
||||
|
||||
class IconBucket : public Bucket {
|
||||
public:
|
||||
@@ -30,7 +31,7 @@ public:
|
||||
virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id);
|
||||
virtual bool hasData() const;
|
||||
|
||||
void addGeometry(pbf& data);
|
||||
void addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas);
|
||||
|
||||
void drawIcons(IconShader& shader);
|
||||
void drawIcons(DotShader& shader);
|
||||
|
||||
@@ -13,12 +13,13 @@ public:
|
||||
|
||||
void setImage(int32_t image);
|
||||
void setColor(const std::array<float, 4>& color);
|
||||
void setPosition(const std::array<float, 2>& pos);
|
||||
void setDimension(const std::array<float, 2>& dimension);
|
||||
void setSize(float size);
|
||||
void setRatio(float ratio);
|
||||
|
||||
private:
|
||||
int32_t a_pos = -1;
|
||||
int32_t a_tex = -1;
|
||||
|
||||
int32_t image = -1;
|
||||
int32_t u_image = -1;
|
||||
@@ -26,14 +27,14 @@ private:
|
||||
std::array<float, 4> color = {{}};
|
||||
int32_t u_color = -1;
|
||||
|
||||
std::array<float, 2> pos = {{}};
|
||||
int32_t u_pos = -1;
|
||||
|
||||
std::array<float, 2> dimension = {{}};
|
||||
int32_t u_dimension = -1;
|
||||
|
||||
float size = 0;
|
||||
int32_t u_size = -1;
|
||||
|
||||
float ratio = 0;
|
||||
int32_t u_ratio = -1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -73,8 +73,9 @@ public:
|
||||
CapType cap = CapType::None;
|
||||
JoinType join = JoinType::None;
|
||||
std::string font;
|
||||
std::string text_field;
|
||||
float font_size = 0.0f;
|
||||
std::string field;
|
||||
std::string icon;
|
||||
float size = 0.0f;
|
||||
float miter_limit = 2.0f;
|
||||
float round_limit = 1.0f;
|
||||
TextPathType path = TextPathType::Horizontal;
|
||||
|
||||
@@ -65,7 +65,7 @@ struct IconClass {
|
||||
std::array<FunctionProperty, 2> translate = {{ 0, 0 }};
|
||||
TranslateAnchor translateAnchor = TranslateAnchor::Map;
|
||||
FunctionProperty size;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
Color color = {{ 1, 1, 1, 1 }};
|
||||
FunctionProperty opacity = 1;
|
||||
std::string image;
|
||||
FunctionProperty radius = 0;
|
||||
@@ -77,7 +77,7 @@ struct IconProperties {
|
||||
std::array<float, 2> translate = {{ 0, 0 }};
|
||||
TranslateAnchor translateAnchor = TranslateAnchor::Map;
|
||||
float size = 0;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
Color color = {{ 1, 1, 1, 1 }};
|
||||
float opacity = 1.0;
|
||||
std::string image;
|
||||
float radius = 0;
|
||||
|
||||
@@ -19,36 +19,28 @@ public:
|
||||
explicit SpritePosition() {}
|
||||
explicit SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t pixelRatio = 1);
|
||||
|
||||
operator bool() const {
|
||||
return !(width == 0 && height == 0 && x == 0 && y == 0);
|
||||
}
|
||||
|
||||
uint16_t x = 0, y = 0;
|
||||
uint16_t width = 0, height = 0;
|
||||
uint8_t pixelRatio = 1;
|
||||
};
|
||||
|
||||
class ImagePosition {
|
||||
public:
|
||||
explicit ImagePosition() {}
|
||||
explicit ImagePosition(const vec2<uint16_t>& size, vec2<float> tl, vec2<float> br);
|
||||
|
||||
vec2<uint16_t> size = { 0, 0 };
|
||||
vec2<float> tl = { 0, 0 };
|
||||
vec2<float> br = { 0, 0 };
|
||||
};
|
||||
|
||||
|
||||
class Sprite : public std::enable_shared_from_this<Sprite> {
|
||||
public:
|
||||
Sprite(Map &map, float pixelRatio = 1);
|
||||
|
||||
void load(const std::string& base_url);
|
||||
|
||||
ImagePosition getPosition(const std::string& name, bool repeating = false) const;
|
||||
const SpritePosition &getSpritePosition(const std::string& name) const;
|
||||
|
||||
bool isLoaded() const;
|
||||
|
||||
public:
|
||||
const float pixelRatio;
|
||||
Raster raster;
|
||||
std::unique_ptr<util::Image> raster;
|
||||
|
||||
private:
|
||||
void asyncParseJSON();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <llmr/style/bucket_description.hpp>
|
||||
#include <llmr/style/layer_description.hpp>
|
||||
#include <llmr/style/class_description.hpp>
|
||||
#include <llmr/geometry/sprite_atlas.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
@@ -8,6 +8,26 @@ namespace util {
|
||||
|
||||
std::string compress_png(int width, int height, void *rgba, bool flip = false);
|
||||
|
||||
|
||||
class Image {
|
||||
public:
|
||||
Image(const std::string &img);
|
||||
~Image();
|
||||
|
||||
inline const char *getData() const { return img; }
|
||||
inline uint32_t getWidth() const { return width; }
|
||||
inline uint32_t getHeight() const { return height; }
|
||||
|
||||
private:
|
||||
// loaded image dimensions
|
||||
uint32_t width = 0, height = 0;
|
||||
|
||||
// the raw image data
|
||||
char *img = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,12 +87,28 @@ inline T dist(const S1& a, const S2& b) {
|
||||
return c;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T length(T a, T b) {
|
||||
return std::sqrt(a * a + b * b);
|
||||
}
|
||||
|
||||
// Take the magnitude of vector a.
|
||||
template <typename T = double, typename S>
|
||||
inline T mag(const S& a) {
|
||||
return std::sqrt(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T clamp(T value, T min, T max) {
|
||||
return value < min ? min : (value > max ? max : value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T smoothstep(T edge0, T edge1, T x) {
|
||||
T t = clamp((x - edge0) / (edge1 - edge0), T(0), T(1));
|
||||
return t * t * (T(3) - T(2) * t);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <llmr/util/transition.hpp>
|
||||
#include <llmr/util/texturepool.hpp>
|
||||
#include <llmr/util/image.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
@@ -45,10 +46,6 @@ public:
|
||||
// texture opacity
|
||||
double opacity = 0;
|
||||
|
||||
private:
|
||||
// load raw pixels
|
||||
void loadImage(const std::string& data);
|
||||
|
||||
private:
|
||||
mutable std::mutex mtx;
|
||||
|
||||
@@ -62,7 +59,7 @@ private:
|
||||
uint32_t filter = 0;
|
||||
|
||||
// the raw pixels
|
||||
char *img = nullptr;
|
||||
std::unique_ptr<util::Image> img;
|
||||
|
||||
// fade in transition
|
||||
std::shared_ptr<util::transition> fade_transition = nullptr;
|
||||
|
||||
@@ -9,6 +9,11 @@ struct Rect {
|
||||
T x = 0, y = 0;
|
||||
T w = 0, h = 0;
|
||||
|
||||
template <typename Number>
|
||||
Rect operator *(Number value) const {
|
||||
return Rect(x * value, y * value, w * value, h * value);
|
||||
}
|
||||
|
||||
operator bool() const { return w == 0 || h == 0; }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void IconVertexBuffer::add(vertex_type x, vertex_type y) {
|
||||
vertex_type *vertices = static_cast<vertex_type *>(addElement());
|
||||
void IconVertexBuffer::add(vertex_type x, vertex_type y, uint16_t tx, uint16_t ty) {
|
||||
void *data = addElement();
|
||||
|
||||
vertex_type *vertices = static_cast<vertex_type *>(data);
|
||||
vertices[0] = x;
|
||||
vertices[1] = y;
|
||||
|
||||
uint16_t *texture = static_cast<uint16_t *>(data);
|
||||
texture[2] = tx;
|
||||
texture[3] = ty;
|
||||
}
|
||||
|
||||
263
src/geometry/sprite_atlas.cpp
Normal file
263
src/geometry/sprite_atlas.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#include <llmr/geometry/sprite_atlas.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/util/math.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
|
||||
#include <llmr/style/sprite.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
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::mutex> lock(mtx);
|
||||
|
||||
const float oldRatio = pixelRatio;
|
||||
pixelRatio = newRatio;
|
||||
|
||||
if (data) {
|
||||
char *old_data = data;
|
||||
|
||||
data = nullptr;
|
||||
allocate();
|
||||
|
||||
dimension w = static_cast<dimension>(width * newRatio);
|
||||
dimension h = static_cast<dimension>(height * newRatio);
|
||||
float s = std::pow(oldRatio / newRatio, 2);
|
||||
|
||||
// Basic image scaling. TODO: Replace this with better image scaling.
|
||||
uint32_t *img_new = reinterpret_cast<uint32_t *>(data);
|
||||
uint32_t *img_old = reinterpret_cast<uint32_t *>(old_data);
|
||||
for (size_t i = 0, length = w * h; i < length; i++) {
|
||||
img_new[i] = img_old[static_cast<size_t>(s * i)];
|
||||
}
|
||||
|
||||
free(old_data);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
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) {
|
||||
const int stride = width * sizeof(uint32_t);
|
||||
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) {
|
||||
memcpy(dst, src, stride);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_circle(uint32_t *dst, const int dst_stride, const int dst_x, const int dst_y,
|
||||
const int width, const int height, const float blur,
|
||||
const uint8_t r = 0xFF, const uint8_t g = 0xFF, const uint8_t b = 0xFF) {
|
||||
const int sprite_stride = dst_stride;
|
||||
const int radius = util::min(width, height);
|
||||
for (int y = 0; y < height; y++) {
|
||||
const int img_y = (dst_y + y) * sprite_stride + dst_x;
|
||||
for (int x = 0; x < height; x++) {
|
||||
const float dist = util::length(float(x) / radius - 0.5f, float(y) / radius - 0.5f);
|
||||
const float t = util::smoothstep(0.5f, 0.5f - blur, dist);
|
||||
const uint8_t alpha = t * 255;
|
||||
|
||||
uint32_t color = (uint32_t(r * t) << 0) |
|
||||
(uint32_t(g * t) << 8) |
|
||||
(uint32_t(b * t) << 16) |
|
||||
(uint32_t(alpha) << 24);
|
||||
dst[img_y + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect<SpriteAtlas::dimension> SpriteAtlas::allocateImage(size_t width, size_t 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(width + 2 * buffer, 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::getIcon(const int size, const std::string &name) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
const std::string id = name + "-" + std::to_string(size);
|
||||
|
||||
auto rect_it = images.find(id);
|
||||
if (rect_it != images.end()) {
|
||||
return rect_it->second;
|
||||
}
|
||||
|
||||
Rect<dimension> rect = allocateImage(size, size);
|
||||
if (rect.w == 0) {
|
||||
fprintf(stderr, "sprite atlas bitmap overflow\n");
|
||||
return rect;
|
||||
}
|
||||
|
||||
images.emplace(id, rect);
|
||||
|
||||
allocate();
|
||||
|
||||
// Draw an antialiased circle.
|
||||
draw_circle(
|
||||
reinterpret_cast<uint32_t *>(data),
|
||||
width * pixelRatio,
|
||||
rect.x * pixelRatio,
|
||||
rect.y * pixelRatio,
|
||||
size * pixelRatio,
|
||||
size * pixelRatio,
|
||||
1.0f / size / pixelRatio
|
||||
);
|
||||
|
||||
uninitialized.emplace(id);
|
||||
|
||||
dirty = true;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
Rect<SpriteAtlas::dimension> SpriteAtlas::getImage(const std::string &name, const Sprite &sprite) {
|
||||
std::lock_guard<std::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) {
|
||||
fprintf(stderr, "sprite atlas bitmap overflow\n");
|
||||
return rect;
|
||||
}
|
||||
|
||||
images.emplace(name, rect);
|
||||
|
||||
copy(rect, pos, sprite);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
void SpriteAtlas::allocate() {
|
||||
if (!data) {
|
||||
dimension w = static_cast<dimension>(width * pixelRatio);
|
||||
dimension h = static_cast<dimension>(height * pixelRatio);
|
||||
data = (char *)calloc(w * h, sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteAtlas::copy(const Rect<dimension> &dst, const SpritePosition &src, const Sprite &sprite) {
|
||||
if (!sprite.raster) return;
|
||||
const uint32_t *src_img = reinterpret_cast<const uint32_t *>(sprite.raster->getData());
|
||||
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::update(const Sprite &sprite) {
|
||||
if (!sprite.isLoaded()) return;
|
||||
|
||||
SpriteAtlas &atlas = *this;
|
||||
std::erase_if(uninitialized, [&sprite, &atlas](const std::string &name) {
|
||||
Rect<dimension> dst = atlas.getImage(name, sprite);
|
||||
const SpritePosition &src = sprite.getSpritePosition(name);
|
||||
if (!src) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (src.width == dst.w * atlas.pixelRatio && src.height == dst.h * atlas.pixelRatio && src.pixelRatio == atlas.pixelRatio) {
|
||||
atlas.copy(dst, src, sprite);
|
||||
return true;
|
||||
} else {
|
||||
fprintf(stderr, "sprite icon dimension mismatch\n");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SpriteAtlas::bind(bool linear) {
|
||||
if (!texture) {
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
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);
|
||||
}
|
||||
|
||||
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::mutex> lock(mtx);
|
||||
allocate();
|
||||
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
|
||||
);
|
||||
|
||||
// platform::show_color_debug_image("Sprite Atlas", data, width, height, width * pixelRatio, height * pixelRatio);
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
};
|
||||
|
||||
SpriteAtlas::~SpriteAtlas() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (data) {
|
||||
free(data);
|
||||
data = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ Map::Map(View& view)
|
||||
texturepool(),
|
||||
style(),
|
||||
glyphAtlas(1024, 1024),
|
||||
spriteAtlas(512, 512),
|
||||
painter(*this),
|
||||
loop(uv_loop_new()) {
|
||||
|
||||
@@ -175,7 +176,16 @@ void Map::resize(uint16_t width, uint16_t height, float ratio) {
|
||||
}
|
||||
|
||||
void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height) {
|
||||
bool changed = false;
|
||||
|
||||
if (transform.resize(width, height, ratio, fb_width, fb_height)) {
|
||||
changed = true;
|
||||
}
|
||||
if (spriteAtlas.resize(ratio)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -393,6 +403,11 @@ void Map::prepare() {
|
||||
style.sprite->load(kSpriteURL);
|
||||
}
|
||||
|
||||
// Allow the sprite atlas to potentially pull new sprite images if needed.
|
||||
if (style.sprite && style.sprite->isLoaded()) {
|
||||
spriteAtlas.update(*style.sprite);
|
||||
}
|
||||
|
||||
updateTiles();
|
||||
|
||||
updateClippingIDs();
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
using namespace llmr;
|
||||
|
||||
|
||||
TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas)
|
||||
TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas)
|
||||
: vector_data(pbf((const uint8_t *)data.data(), data.size())),
|
||||
tile(tile),
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas),
|
||||
spriteAtlas(spriteAtlas),
|
||||
placement(tile.id.z) {
|
||||
parseGlyphs();
|
||||
parseStyleLayers(style.layers);
|
||||
@@ -120,6 +121,15 @@ void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer,
|
||||
}
|
||||
}
|
||||
|
||||
template <class Bucket, typename ...Args>
|
||||
void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args&& ...args) {
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (const pbf& feature_pbf : filtered_layer) {
|
||||
if (obsolete()) return;
|
||||
bucket->addFeature({ feature_pbf, layer }, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::unique_ptr<FillBucket> bucket = std::make_unique<FillBucket>(
|
||||
tile.fillVertexBuffer, tile.triangleElementsBuffer, tile.lineElementsBuffer, bucket_desc);
|
||||
@@ -137,7 +147,7 @@ std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer& laye
|
||||
std::unique_ptr<Bucket> TileParser::createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::unique_ptr<IconBucket> bucket = std::make_unique<IconBucket>(
|
||||
tile.iconVertexBuffer, bucket_desc);
|
||||
addBucketFeatures(bucket, layer, bucket_desc);
|
||||
addBucketFeatures(bucket, layer, bucket_desc, spriteAtlas);
|
||||
return obsolete() ? nullptr : std::move(bucket);
|
||||
}
|
||||
|
||||
@@ -165,12 +175,6 @@ std::unique_ptr<Bucket> TileParser::createTextBucket(const VectorTileLayer& laye
|
||||
|
||||
std::unique_ptr<TextBucket> bucket = std::make_unique<TextBucket>(
|
||||
tile.textVertexBuffer, tile.triangleElementsBuffer, bucket_desc, placement);
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (const pbf& feature_pbf : filtered_layer) {
|
||||
if (obsolete()) return nullptr;
|
||||
bucket->addFeature({ feature_pbf, layer }, faces, shaping);
|
||||
}
|
||||
|
||||
addBucketFeatures(bucket, layer, bucket_desc, faces, shaping);
|
||||
return std::move(bucket);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ void VectorTileData::parse() {
|
||||
// Parsing creates state that is encapsulated in TileParser. While parsing,
|
||||
// the TileParser object writes results into this objects. All other state
|
||||
// is going to be discarded afterwards.
|
||||
TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas());
|
||||
TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getSpriteAtlas());
|
||||
} catch (const std::exception& ex) {
|
||||
fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
|
||||
cancel();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/style/sprite.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
|
||||
#include <llmr/platform/gl.hpp>
|
||||
@@ -22,13 +23,32 @@ IconBucket::IconBucket(IconVertexBuffer& vertexBuffer,
|
||||
vertex_start(vertexBuffer.index()) {
|
||||
}
|
||||
|
||||
void IconBucket::addGeometry(pbf& geom) {
|
||||
void IconBucket::addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas) {
|
||||
std::string field;
|
||||
|
||||
if (geometry.field.size()) {
|
||||
auto field_it = feature.properties.find(geometry.field);
|
||||
if (field_it == feature.properties.end()) {
|
||||
fprintf(stderr, "feature doesn't contain field '%s'\n", geometry.field.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
field = toString(field_it->second);
|
||||
} else {
|
||||
field = "<circle>";
|
||||
}
|
||||
|
||||
const Rect<uint16_t> rect = sprite_atlas.getIcon(geometry.size, field);
|
||||
const uint16_t tx = rect.x + rect.w / 2;
|
||||
const uint16_t ty = rect.y + rect.h / 2;
|
||||
|
||||
Geometry::command cmd;
|
||||
pbf geom = feature.geometry;
|
||||
Geometry geometry(geom);
|
||||
int32_t x, y;
|
||||
while ((cmd = geometry.next(x, y)) != Geometry::end) {
|
||||
if (cmd == Geometry::move_to) {
|
||||
vertexBuffer.add(x, y);
|
||||
vertexBuffer.add(x, y, tx, ty);
|
||||
} else {
|
||||
fprintf(stderr, "other command than move_to in icon geometry\n");
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/style/sprite.hpp>
|
||||
#include <llmr/geometry/sprite_atlas.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
@@ -73,16 +74,17 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
|
||||
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
if (properties.image.size() && sprite && sprite->isLoaded()) {
|
||||
// Draw texture fill
|
||||
ImagePosition imagePos = sprite->getPosition(properties.image, true);
|
||||
if (properties.image.size() && sprite) {
|
||||
auto &spriteAtlas = map.getSpriteAtlas();
|
||||
Rect<uint16_t> imagePos = spriteAtlas.getImage(properties.image, *sprite);
|
||||
|
||||
|
||||
float factor = 8.0 / std::pow(2, map.getState().getIntegerZoom() - id.z);
|
||||
float mix = std::fmod(map.getState().getZoom(), 1.0);
|
||||
|
||||
std::array<float, 2> imageSize = {{
|
||||
imagePos.size.x * factor,
|
||||
imagePos.size.y *factor
|
||||
imagePos.w * factor,
|
||||
imagePos.h * factor
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,11 +98,17 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
patternShader->setMatrix(vtxMatrix);
|
||||
patternShader->setOffset(offset);
|
||||
patternShader->setPatternSize(imageSize);
|
||||
patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
|
||||
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
|
||||
patternShader->setPatternTopLeft({{
|
||||
float(imagePos.x) / spriteAtlas.getWidth(),
|
||||
float(imagePos.y) / spriteAtlas.getHeight(),
|
||||
}});
|
||||
patternShader->setPatternBottomRight({{
|
||||
float(imagePos.x + imagePos.w) / spriteAtlas.getWidth(),
|
||||
float(imagePos.y + imagePos.h) / spriteAtlas.getHeight(),
|
||||
}});
|
||||
patternShader->setColor(fill_color);
|
||||
patternShader->setMix(mix);
|
||||
sprite->raster.bind(true);
|
||||
spriteAtlas.bind(true);
|
||||
|
||||
// Draw the actual triangles into the color & stencil buffer.
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
|
||||
@@ -24,60 +24,29 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons
|
||||
color[2] *= properties.opacity;
|
||||
color[3] *= properties.opacity;
|
||||
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
SpritePosition spritePos;
|
||||
|
||||
if (properties.image.length() && sprite && sprite->isLoaded()) {
|
||||
std::string sized_image = properties.image;
|
||||
if (properties.size) {
|
||||
sized_image.append("-");
|
||||
sized_image.append(std::to_string(static_cast<int>(std::round(properties.size))));
|
||||
}
|
||||
spritePos = sprite->getSpritePosition(sized_image);
|
||||
}
|
||||
|
||||
const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor);
|
||||
|
||||
if (!spritePos.width || !spritePos.height) {
|
||||
useProgram(dotShader->program);
|
||||
dotShader->setMatrix(vtxMatrix);
|
||||
dotShader->setColor(color);
|
||||
SpriteAtlas &spriteAtlas = map.getSpriteAtlas();
|
||||
|
||||
const float iconSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio();
|
||||
dotShader->setSize(iconSize);
|
||||
useProgram(iconShader->program);
|
||||
iconShader->setMatrix(vtxMatrix);
|
||||
iconShader->setColor(color);
|
||||
iconShader->setImage(0);
|
||||
iconShader->setRatio(map.getState().getPixelRatio());
|
||||
iconShader->setDimension({{
|
||||
spriteAtlas.getTextureWidth(),
|
||||
spriteAtlas.getTextureHeight(),
|
||||
}});
|
||||
|
||||
spriteAtlas.bind(map.getState().isChanging());
|
||||
|
||||
const float iconSize = bucket.geometry.size * map.getState().getPixelRatio();
|
||||
iconShader->setSize(iconSize);
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
glPointSize(iconSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
#endif
|
||||
dotShader->setBlur((properties.blur ? properties.blur : 1.5) / iconSize);
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawIcons(*dotShader);
|
||||
} else {
|
||||
useProgram(iconShader->program);
|
||||
iconShader->setMatrix(vtxMatrix);
|
||||
iconShader->setColor(color);
|
||||
iconShader->setImage(0);
|
||||
iconShader->setPosition({{
|
||||
spritePos.x + (float)spritePos.width / 2.0f,
|
||||
spritePos.y + (float)spritePos.height / 2.0f,
|
||||
}});
|
||||
|
||||
iconShader->setDimension({{
|
||||
static_cast<float>(sprite->raster.width),
|
||||
static_cast<float>(sprite->raster.height)
|
||||
}});
|
||||
|
||||
sprite->raster.bind(map.getState().isChanging());
|
||||
|
||||
const float iconSize = util::max(spritePos.width, spritePos.height) + 2;
|
||||
iconShader->setSize(iconSize);
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
glPointSize(iconSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
glPointSize(iconSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
#endif
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawIcons(*iconShader);
|
||||
}
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawIcons(*iconShader);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
}
|
||||
|
||||
// If layerStyle.size > bucket.info.fontSize then labels may collide
|
||||
float fontSize = std::fmin(properties.size, bucket.geom_desc.font_size);
|
||||
float fontSize = std::fmin(properties.size, bucket.geom_desc.size);
|
||||
matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f);
|
||||
|
||||
const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor);
|
||||
@@ -47,7 +47,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
float angle = std::round((map.getState().getAngle() + rotate) / M_PI * 128);
|
||||
|
||||
// adjust min/max zooms for variable font sies
|
||||
float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2);
|
||||
float zoomAdjust = log(fontSize / bucket.geom_desc.size) / log(2);
|
||||
|
||||
textShader->setAngle((int32_t)(angle + 256) % 256);
|
||||
textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0);
|
||||
|
||||
@@ -102,7 +102,7 @@ void TextBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom,
|
||||
void TextBucket::addFeature(const VectorTileFeature &feature,
|
||||
const IndexedFaces &faces,
|
||||
const std::map<Value, Shaping> &shapings) {
|
||||
auto it_prop = feature.properties.find(geom_desc.text_field);
|
||||
auto it_prop = feature.properties.find(geom_desc.field);
|
||||
if (it_prop == feature.properties.end()) {
|
||||
// feature does not have the correct property
|
||||
return;
|
||||
|
||||
@@ -32,7 +32,7 @@ DotShader::DotShader()
|
||||
|
||||
void DotShader::bind(char *offset) {
|
||||
glEnableVertexAttribArray(a_pos);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset);
|
||||
}
|
||||
|
||||
void DotShader::setColor(const std::array<float, 4>& new_color) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
uniform sampler2D u_image;
|
||||
uniform vec2 u_pos;
|
||||
uniform vec2 u_dimension;
|
||||
uniform vec4 u_color;
|
||||
uniform float u_size;
|
||||
|
||||
varying vec2 v_tex;
|
||||
|
||||
void main() {
|
||||
vec2 pos = (u_pos + (gl_PointCoord - 0.5) * u_size) / u_dimension;
|
||||
gl_FragColor = texture2D(u_image, pos) * u_color;
|
||||
vec2 pos = (v_tex + (gl_PointCoord - 0.5) * u_size) / u_dimension;
|
||||
gl_FragColor = u_color * texture2D(u_image, pos);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
attribute vec2 a_pos;
|
||||
attribute vec2 a_tex;
|
||||
|
||||
uniform mat4 u_matrix;
|
||||
uniform float u_size;
|
||||
uniform float u_ratio;
|
||||
|
||||
varying vec2 v_tex;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_matrix * vec4(a_pos, 0, 1);
|
||||
gl_PointSize = u_size;
|
||||
v_tex = a_tex * u_ratio;
|
||||
}
|
||||
|
||||
@@ -17,25 +17,29 @@ IconShader::IconShader()
|
||||
}
|
||||
|
||||
a_pos = glGetAttribLocation(program, "a_pos");
|
||||
a_tex = glGetAttribLocation(program, "a_tex");
|
||||
|
||||
u_matrix = glGetUniformLocation(program, "u_matrix");
|
||||
u_color = glGetUniformLocation(program, "u_color");
|
||||
u_size = glGetUniformLocation(program, "u_size");
|
||||
u_pos = glGetUniformLocation(program, "u_pos");
|
||||
u_ratio = glGetUniformLocation(program, "u_ratio");
|
||||
u_dimension = glGetUniformLocation(program, "u_dimension");
|
||||
|
||||
// fprintf(stderr, "IconShader:\n");
|
||||
// fprintf(stderr, " - u_matrix: %d\n", u_matrix);
|
||||
// fprintf(stderr, " - u_color: %d\n", u_color);
|
||||
// fprintf(stderr, " - u_size: %d\n", u_size);
|
||||
// fprintf(stderr, " - u_pos: %d\n", u_pos);
|
||||
// fprintf(stderr, " - u_ratio: %d\n", u_ratio);
|
||||
// fprintf(stderr, " - u_dimension: %d\n", u_dimension);
|
||||
// fprintf(stderr, " - u_image: %d\n", u_image);
|
||||
}
|
||||
|
||||
void IconShader::bind(char *offset) {
|
||||
glEnableVertexAttribArray(a_pos);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset);
|
||||
|
||||
glEnableVertexAttribArray(a_tex);
|
||||
glVertexAttribPointer(a_tex, 2, GL_UNSIGNED_SHORT, false, 8, offset + 4);
|
||||
}
|
||||
|
||||
void IconShader::setImage(int32_t new_image) {
|
||||
@@ -59,10 +63,10 @@ void IconShader::setSize(float new_size) {
|
||||
}
|
||||
}
|
||||
|
||||
void IconShader::setPosition(const std::array<float, 2>& new_pos) {
|
||||
if (pos != new_pos) {
|
||||
glUniform2fv(u_pos, 1, new_pos.data());
|
||||
pos = new_pos;
|
||||
void IconShader::setRatio(float new_ratio) {
|
||||
if (ratio != new_ratio) {
|
||||
glUniform1f(u_ratio, new_ratio);
|
||||
ratio = new_ratio;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ const shader_source llmr::shaders[SHADER_COUNT] = {
|
||||
"#version 120\nuniform vec4 u_color;\nuniform float u_blur;\nvoid main ()\n{\n vec2 x_1;\n x_1 = (gl_PointCoord - 0.5);\n float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 0.5) / (\n (0.5 - u_blur)\n - 0.5)), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n",
|
||||
},
|
||||
{
|
||||
"#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n}\n\n",
|
||||
"#version 120\nuniform sampler2D u_image;\nuniform vec2 u_pos;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvoid main ()\n{\n gl_FragColor = (texture2D (u_image, ((u_pos + \n ((gl_PointCoord - 0.5) * u_size)\n ) / u_dimension)) * u_color);\n}\n\n",
|
||||
"#version 120\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nuniform float u_ratio;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = (a_tex * u_ratio);\n}\n\n",
|
||||
"#version 120\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n gl_FragColor = (u_color * texture2D (u_image, ((v_tex + \n ((gl_PointCoord - 0.5) * u_size)\n ) / u_dimension)));\n}\n\n",
|
||||
},
|
||||
{
|
||||
"#version 120\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n",
|
||||
|
||||
@@ -11,8 +11,8 @@ const shader_source llmr::shaders[SHADER_COUNT] = {
|
||||
"precision highp float;\nuniform vec4 u_color;\nuniform float u_blur;\nvoid main ()\n{\n mediump vec2 x_1;\n x_1 = (gl_PointCoord - 0.5);\n mediump float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 0.5) / (\n (0.5 - u_blur)\n - 0.5)), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n",
|
||||
},
|
||||
{
|
||||
"precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n}\n\n",
|
||||
"precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_pos;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((u_pos + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = (texture2D (u_image, tmpvar_1) * u_color);\n gl_FragColor = tmpvar_2;\n}\n\n",
|
||||
"precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nuniform float u_ratio;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = (a_tex * u_ratio);\n}\n\n",
|
||||
"precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = (u_color * texture2D (u_image, tmpvar_1));\n gl_FragColor = tmpvar_2;\n}\n\n",
|
||||
},
|
||||
{
|
||||
"precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n",
|
||||
|
||||
@@ -14,6 +14,6 @@ std::ostream& llmr::operator<<(std::ostream& os, const BucketDescription& bucket
|
||||
os << " - cap: " << (uint32_t)bucket.geometry.cap << std::endl;
|
||||
os << " - join: " << (uint32_t)bucket.geometry.join << std::endl;
|
||||
os << " - font: " << bucket.geometry.font << std::endl;
|
||||
os << " - font_size: " << bucket.geometry.font_size << std::endl;
|
||||
os << " - size: " << bucket.geometry.size << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/util/uv.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
@@ -18,48 +19,43 @@ SpritePosition::SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t
|
||||
pixelRatio(pixelRatio) {
|
||||
}
|
||||
|
||||
ImagePosition::ImagePosition(const vec2<uint16_t>& size, vec2<float> tl, vec2<float> br)
|
||||
: size(size),
|
||||
tl(tl),
|
||||
br(br) {}
|
||||
|
||||
Sprite::Sprite(Map &map, float pixelRatio)
|
||||
: pixelRatio(pixelRatio),
|
||||
raster(map.getTexturepool()),
|
||||
raster(),
|
||||
map(map),
|
||||
loaded(false) {
|
||||
}
|
||||
|
||||
void Sprite::load(const std::string& base_url) {
|
||||
std::shared_ptr<Sprite> sprite = shared_from_this();
|
||||
std::shared_ptr<Sprite> sprite = shared_from_this();
|
||||
|
||||
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
|
||||
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
|
||||
|
||||
platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->body.swap(res->body);
|
||||
sprite->asyncParseJSON();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->body.swap(res->body);
|
||||
sprite->asyncParseJSON();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
|
||||
platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->image.swap(res->body);
|
||||
sprite->asyncParseImage();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite image\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) {
|
||||
if (res->code == 200) {
|
||||
sprite->image.swap(res->body);
|
||||
sprite->asyncParseImage();
|
||||
} else {
|
||||
fprintf(stderr, "failed to load sprite image\n");
|
||||
}
|
||||
}, map.getLoop());
|
||||
}
|
||||
|
||||
void Sprite::complete(std::shared_ptr<Sprite> &sprite) {
|
||||
if (sprite->raster.isLoaded() && sprite->pos.size()) {
|
||||
sprite->loaded = true;
|
||||
sprite->map.update();
|
||||
fprintf(stderr, "sprite loaded\n");
|
||||
}
|
||||
if (sprite->raster && sprite->pos.size()) {
|
||||
sprite->loaded = true;
|
||||
sprite->map.update();
|
||||
fprintf(stderr, "sprite loaded\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool Sprite::isLoaded() const {
|
||||
@@ -75,7 +71,7 @@ void Sprite::asyncParseJSON() {
|
||||
}
|
||||
|
||||
void Sprite::parseImage(std::shared_ptr<Sprite> &sprite) {
|
||||
sprite->raster.load(sprite->image);
|
||||
sprite->raster = std::make_unique<util::Image>(sprite->image);
|
||||
sprite->image.clear();
|
||||
}
|
||||
|
||||
@@ -107,34 +103,6 @@ void Sprite::parseJSON(std::shared_ptr<Sprite> &sprite) {
|
||||
}
|
||||
}
|
||||
|
||||
ImagePosition Sprite::getPosition(const std::string& name, bool repeating) const {
|
||||
if (!isLoaded()) return ImagePosition {};
|
||||
|
||||
// `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 ImagePosition {};
|
||||
|
||||
const SpritePosition& pos = it->second;
|
||||
return ImagePosition {
|
||||
{
|
||||
pos.width,
|
||||
pos.height
|
||||
},
|
||||
{
|
||||
(float)(pos.x + offset) / raster.width,
|
||||
(float)(pos.y + offset) / raster.height
|
||||
},
|
||||
{
|
||||
(float)(pos.x + pos.width - 2 * offset) / raster.width,
|
||||
(float)(pos.y + pos.height - 2 * offset) / raster.height
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const SpritePosition &Sprite::getSpritePosition(const std::string& name) const {
|
||||
if (!isLoaded()) return empty;
|
||||
auto it = pos.find(name);
|
||||
|
||||
@@ -83,15 +83,15 @@ BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
} else {
|
||||
throw Style::exception("font stack must be a string");
|
||||
}
|
||||
} else if (name == "fontSize") {
|
||||
} else if (name == "fontSize" || name == "size") {
|
||||
if (value.IsNumber()) {
|
||||
bucket.geometry.font_size = value.GetDouble();
|
||||
bucket.geometry.size = value.GetDouble();
|
||||
} else {
|
||||
throw Style::exception("font size must be a number");
|
||||
}
|
||||
} else if (name == "text_field") {
|
||||
} else if (name == "text_field" || name == "icon") {
|
||||
if (value.IsString()) {
|
||||
bucket.geometry.text_field = { value.GetString(), value.GetStringLength() };
|
||||
bucket.geometry.field = { value.GetString(), value.GetStringLength() };
|
||||
} else {
|
||||
throw Style::exception("text field must be a string");
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ void Placement::addFeature(TextBucket& bucket,
|
||||
const float textMinDistance = info.textMinDistance;
|
||||
const float rotate = info.rotate;
|
||||
const float fontScale =
|
||||
(tileExtent / util::tileSize) / (glyphSize / info.font_size);
|
||||
(tileExtent / util::tileSize) / (glyphSize / info.size);
|
||||
|
||||
const float advance = measureText(faces, shaping);
|
||||
Anchors anchors;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <llmr/util/image.hpp>
|
||||
#include <png.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
|
||||
std::string llmr::util::compress_png(int width, int height, void *rgba, bool flip) {
|
||||
png_voidp error_ptr = 0;
|
||||
@@ -48,3 +50,127 @@ std::string llmr::util::compress_png(int width, int height, void *rgba, bool fli
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
using namespace llmr::util;
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
void readCallback(png_structp png, png_bytep data, png_size_t length) {
|
||||
Buffer *reader = static_cast<Buffer *>(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_const_charp error_msg) {
|
||||
throw std::runtime_error(error_msg);
|
||||
}
|
||||
|
||||
void warningHandler(png_structp, png_const_charp error_msg) {
|
||||
fprintf(stderr, "PNG: %s\n", error_msg);
|
||||
}
|
||||
|
||||
Image::Image(const std::string &data) {
|
||||
Buffer buffer(data);
|
||||
|
||||
if (buffer.length < 8 || !png_check_sig((const png_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, nullptr, 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, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color, &interlace, nullptr, nullptr);
|
||||
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);
|
||||
|
||||
png_size_t rowbytes = png_get_rowbytes(png, info);
|
||||
assert(width * 4 == rowbytes);
|
||||
|
||||
img = (char *)malloc(width * height * 4);
|
||||
char *surface = img;
|
||||
assert(surface);
|
||||
|
||||
struct ptrs {
|
||||
ptrs(size_t count) : rows(new png_bytep[count]) {}
|
||||
~ptrs() { delete[] rows; }
|
||||
png_bytep *rows = nullptr;
|
||||
} pointers(height);
|
||||
for (unsigned i = 0; i < height; ++i) {
|
||||
pointers.rows[i] = (png_bytep)(surface + (i * rowbytes));
|
||||
}
|
||||
|
||||
// Read image data
|
||||
png_read_image(png, pointers.rows);
|
||||
|
||||
png_read_end(png, nullptr);
|
||||
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
} catch (std::exception& e) {
|
||||
fprintf(stderr, "loading PNG failed: %s\n", e.what());
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
if (img) {
|
||||
free(img);
|
||||
img = nullptr;
|
||||
}
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Image::~Image() {
|
||||
if (img) {
|
||||
free(img);
|
||||
img = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
#include <llmr/util/uv.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
@@ -18,9 +19,7 @@ Raster::Raster(Texturepool &texturepool)
|
||||
{}
|
||||
|
||||
Raster::~Raster() {
|
||||
if (img && !textured) {
|
||||
free(img);
|
||||
} else if (textured) {
|
||||
if (textured) {
|
||||
texturepool.removeTextureID(texture);
|
||||
}
|
||||
}
|
||||
@@ -31,130 +30,17 @@ bool Raster::isLoaded() const {
|
||||
}
|
||||
|
||||
bool Raster::load(const std::string &data) {
|
||||
loadImage(data);
|
||||
img = std::make_unique<util::Image>(data);
|
||||
width = img->getWidth();
|
||||
height = img->getHeight();
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (img) {
|
||||
if (img->getData()) {
|
||||
loaded = true;
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
void readCallback(png_structp png, png_bytep data, png_size_t length) {
|
||||
Buffer *reader = static_cast<Buffer *>(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_const_charp error_msg) {
|
||||
throw std::runtime_error(error_msg);
|
||||
}
|
||||
|
||||
void warningHandler(png_structp, png_const_charp error_msg) {
|
||||
fprintf(stderr, "PNG: %s\n", error_msg);
|
||||
}
|
||||
|
||||
void Raster::loadImage(const std::string& data) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
Buffer buffer(data);
|
||||
|
||||
if (buffer.length < 8 || !png_check_sig((const png_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, nullptr, 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, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color, &interlace, nullptr, nullptr);
|
||||
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);
|
||||
|
||||
png_size_t rowbytes = png_get_rowbytes(png, info);
|
||||
assert(width * 4 == rowbytes);
|
||||
|
||||
img = (char *)malloc(width * height * 4);
|
||||
char *surface = img;
|
||||
assert(surface);
|
||||
|
||||
struct ptrs {
|
||||
ptrs(size_t count) : rows(new png_bytep[count]) {}
|
||||
~ptrs() { delete[] rows; }
|
||||
png_bytep *rows = nullptr;
|
||||
} pointers(height);
|
||||
for (unsigned i = 0; i < height; ++i) {
|
||||
pointers.rows[i] = (png_bytep)(surface + (i * rowbytes));
|
||||
}
|
||||
|
||||
// Read image data
|
||||
png_read_image(png, pointers.rows);
|
||||
|
||||
png_read_end(png, nullptr);
|
||||
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
} catch (std::exception& e) {
|
||||
fprintf(stderr, "loading PNG failed: %s\n", e.what());
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
if (img) {
|
||||
free(img);
|
||||
img = nullptr;
|
||||
}
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Raster::bind(bool linear) {
|
||||
if (!width || !height) {
|
||||
@@ -167,9 +53,8 @@ void Raster::bind(bool linear) {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
|
||||
free(img);
|
||||
img = nullptr;
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img->getData());
|
||||
img.reset();
|
||||
textured = true;
|
||||
} else if (textured) {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
Reference in New Issue
Block a user