summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2015-03-16 19:30:08 +0100
committerKonstantin Käfer <mail@kkaefer.com>2015-03-17 12:30:40 +0100
commit48c55af5c1d39e9a29cdb2e0c8203e6767f76c57 (patch)
treeb81115238586a172be3acc487822cd27f12efe79 /src
parent0b1faba36920be2c9343c912b4817448f5659f71 (diff)
downloadqtlocation-mapboxgl-48c55af5c1d39e9a29cdb2e0c8203e6767f76c57.tar.gz
fix sprites for pixel ratios that are not 1 and 2
- OpenGL ES 2 doesn't allow NPOT textures with wrap-around - The Sprite object reported the map's pixelRatio, even though it loaded @2x assets - Copying icons from the sprite into the atlas now uses bilinear scaling to scale up to the actual size
Diffstat (limited to 'src')
-rw-r--r--src/mbgl/geometry/sprite_atlas.cpp111
-rw-r--r--src/mbgl/map/map.cpp2
-rw-r--r--src/mbgl/map/sprite.cpp7
-rw-r--r--src/mbgl/map/sprite.hpp2
-rw-r--r--src/mbgl/util/compression.cpp96
-rw-r--r--src/mbgl/util/compression.hpp15
-rw-r--r--src/mbgl/util/scaling.cpp111
-rw-r--r--src/mbgl/util/scaling.hpp23
8 files changed, 312 insertions, 55 deletions
diff --git a/src/mbgl/geometry/sprite_atlas.cpp b/src/mbgl/geometry/sprite_atlas.cpp
index 077550ff74..dce772f2e4 100644
--- a/src/mbgl/geometry/sprite_atlas.cpp
+++ b/src/mbgl/geometry/sprite_atlas.cpp
@@ -5,6 +5,7 @@
#include <mbgl/util/math.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/constants.hpp>
+#include <mbgl/util/scaling.hpp>
#include <mbgl/map/sprite.hpp>
@@ -66,34 +67,6 @@ bool SpriteAtlas::resize(const float newRatio) {
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_height, const int dst_x, const int dst_y,
- const int width, const int height, const bool wrap) {
- if (wrap) {
-
- for (int y = -1; y <= height; y++) {
- int dst_y_wrapped = (y + dst_y + dst_height) % dst_height;
- int src_y_wrapped = ((y + height) % height) + src_y;
- int srcI = src_y_wrapped * src_stride + src_x;
- int dstI = dst_y_wrapped * dst_stride;
- for (int x = -1; x <= width; x++) {
- int dst_x_wrapped = (x + dst_x + dst_stride) % dst_stride;
- int src_x_wrapped = (x + width) % width;
- dst[dstI + dst_x_wrapped] = src[srcI + src_x_wrapped];
- }
- }
-
- } else {
- dst += dst_y * dst_stride + dst_x;
- src += src_y * src_stride + src_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(const size_t pixel_width, const size_t pixel_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
@@ -176,25 +149,53 @@ void SpriteAtlas::allocate() {
void SpriteAtlas::copy(const Rect<dimension>& dst, const SpritePosition& src, const bool wrap) {
if (!sprite->raster) return;
- const uint32_t *src_img = reinterpret_cast<const uint32_t *>(sprite->raster->getData());
- if (!src_img) return;
+
+ const uint32_t *srcData = reinterpret_cast<const uint32_t *>(sprite->raster->getData());
+ if (!srcData) return;
+ const vec2<uint32_t> srcSize { sprite->raster->getWidth(), sprite->raster->getHeight() };
+ const Rect<uint32_t> srcPos { src.x, src.y, src.width, src.height };
+
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 height */ height * pixelRatio,
- /* dest x */ dst.x * pixelRatio,
- /* dest y */ dst.y * pixelRatio,
- /* icon dimension */ src.width,
- /* icon dimension */ src.height,
- /* wrap padding */ wrap
- );
+ uint32_t *dstData = reinterpret_cast<uint32_t *>(data);
+ const vec2<uint32_t> dstSize { static_cast<unsigned int>(width * pixelRatio),
+ static_cast<unsigned int>(height * pixelRatio) };
+ const Rect<uint32_t> dstPos { static_cast<uint32_t>(dst.x * pixelRatio),
+ static_cast<uint32_t>(dst.y * pixelRatio),
+ static_cast<uint32_t>(dst.originalW * pixelRatio),
+ static_cast<uint32_t>(dst.originalH * pixelRatio) };
+
+ util::bilinearScale(srcData, srcSize, srcPos, dstData, dstSize, dstPos);
+
+ // Add borders around the copied image if required.
+ if (wrap) {
+ // We're copying from the same image so we don't have to scale again.
+ const uint32_t border = 1;
+ // Left border
+ if (dstPos.x >= border) {
+ util::nearestNeighborScale(
+ dstData, dstSize, { dstPos.x + dstPos.w - border - 1, dstPos.y, border, dstPos.h },
+ dstData, dstSize, { dstPos.x - border, dstPos.y, border, dstPos.h });
+ }
+ // Right border
+ util::nearestNeighborScale(dstData, dstSize, { dstPos.x, dstPos.y, border, dstPos.h },
+ dstData, dstSize,
+ { dstPos.x + dstPos.w, dstPos.y, border, dstPos.h });
+
+ // Top border
+ if (dstPos.y >= border) {
+ util::nearestNeighborScale(
+ dstData, dstSize, { dstPos.x - border, dstPos.y + dstPos.h - border - 1,
+ dstPos.w + 2 * border, border },
+ dstData, dstSize,
+ { dstPos.x - border, dstPos.y - border, dstPos.w + 2 * border, border });
+ }
+
+ // Bottom border
+ util::nearestNeighborScale(
+ dstData, dstSize, { dstPos.x - border, dstPos.y, dstPos.w + 2 * border, border },
+ dstData, dstSize,
+ { dstPos.x - border, dstPos.y + dstPos.h, dstPos.w + 2 * border, border });
+ }
dirty = true;
}
@@ -236,8 +237,10 @@ void SpriteAtlas::bind(bool linear) {
#ifndef GL_ES_VERSION_2_0
MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
#endif
- MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
- MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
+ // We are using clamp to edge here since OpenGL ES doesn't allow GL_REPEAT on NPOT textures.
+ // We use those when the pixelRatio isn't a power of two, e.g. on iPhone 6 Plus.
+ MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
first = true;
} else {
MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture));
@@ -255,7 +258,7 @@ void SpriteAtlas::bind(bool linear) {
allocate();
if (first) {
- glTexImage2D(
+ MBGL_CHECK_ERROR(glTexImage2D(
GL_TEXTURE_2D, // GLenum target
0, // GLint level
GL_RGBA, // GLint internalformat
@@ -265,9 +268,9 @@ void SpriteAtlas::bind(bool linear) {
GL_RGBA, // GLenum format
GL_UNSIGNED_BYTE, // GLenum type
data // const GLvoid * data
- );
+ ));
} else {
- glTexSubImage2D(
+ MBGL_CHECK_ERROR(glTexSubImage2D(
GL_TEXTURE_2D, // GLenum target
0, // GLint level
0, // GLint xoffset
@@ -277,12 +280,14 @@ void SpriteAtlas::bind(bool linear) {
GL_RGBA, // GLenum format
GL_UNSIGNED_BYTE, // GLenum type
data // const GLvoid *pixels
- );
+ ));
}
dirty = false;
- // platform::show_color_debug_image("Sprite Atlas", reinterpret_cast<const char *>(data), width, height, width * pixelRatio, height * pixelRatio);
+#ifndef GL_ES_VERSION_2_0
+ // platform::showColorDebugImage("Sprite Atlas", reinterpret_cast<const char *>(data), width * pixelRatio, height * pixelRatio, width * pixelRatio, height * pixelRatio);
+#endif
}
};
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index 29521f9499..d12afc9143 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -374,7 +374,7 @@ std::string Map::getStyleJSON() const {
util::ptr<Sprite> Map::getSprite() {
const float pixelRatio = state.getPixelRatio();
const std::string &sprite_url = style->getSpriteURL();
- if (!sprite || sprite->pixelRatio != pixelRatio) {
+ if (!sprite || !sprite->hasPixelRatio(pixelRatio)) {
sprite = Sprite::Create(sprite_url, pixelRatio, *env);
}
diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp
index 114e8f45f5..8883ee092d 100644
--- a/src/mbgl/map/sprite.cpp
+++ b/src/mbgl/map/sprite.cpp
@@ -32,7 +32,7 @@ util::ptr<Sprite> Sprite::Create(const std::string &base_url, float pixelRatio,
Sprite::Sprite(const Key &, const std::string& base_url, float pixelRatio_)
: valid(base_url.length() > 0),
- pixelRatio(pixelRatio_),
+ pixelRatio(pixelRatio_ > 1 ? 2 : 1),
spriteURL(base_url + (pixelRatio_ > 1 ? "@2x" : "") + ".png"),
jsonURL(base_url + (pixelRatio_ > 1 ? "@2x" : "") + ".json"),
raster(),
@@ -41,6 +41,11 @@ Sprite::Sprite(const Key &, const std::string& base_url, float pixelRatio_)
future(promise.get_future()) {
}
+bool Sprite::hasPixelRatio(float ratio) const {
+ return pixelRatio == (ratio > 1 ? 2 : 1);
+}
+
+
void Sprite::waitUntilLoaded() const {
future.wait();
}
diff --git a/src/mbgl/map/sprite.hpp b/src/mbgl/map/sprite.hpp
index cb0c274dee..6c1b3ba8e3 100644
--- a/src/mbgl/map/sprite.hpp
+++ b/src/mbgl/map/sprite.hpp
@@ -43,6 +43,8 @@ public:
const SpritePosition &getSpritePosition(const std::string& name) const;
+ bool hasPixelRatio(float ratio) const;
+
void waitUntilLoaded() const;
bool isLoaded() const;
diff --git a/src/mbgl/util/compression.cpp b/src/mbgl/util/compression.cpp
new file mode 100644
index 0000000000..3a1658b8f6
--- /dev/null
+++ b/src/mbgl/util/compression.cpp
@@ -0,0 +1,96 @@
+#include "compression.hpp"
+
+#include <zlib.h>
+
+#include <cstring>
+#include <stdexcept>
+
+
+// Check zlib library version.
+const static bool zlibVersionCheck = []() {
+ const char *const version = zlibVersion();
+ if (version[0] != ZLIB_VERSION[0]) {
+ char message[96];
+ snprintf(message, 96, "zlib version mismatch: headers report %s, but library reports %s",
+ ZLIB_VERSION, version);
+ throw std::runtime_error(message);
+ }
+
+ return true;
+}();
+
+
+namespace mbgl {
+namespace util {
+
+std::string compress(const std::string &raw) {
+ z_stream deflate_stream;
+ memset(&deflate_stream, 0, sizeof(deflate_stream));
+
+ // TODO: reuse z_streams
+ if (deflateInit(&deflate_stream, Z_DEFAULT_COMPRESSION) != Z_OK) {
+ throw std::runtime_error("failed to initialize deflate");
+ }
+
+ deflate_stream.next_in = (Bytef *)raw.data();
+ deflate_stream.avail_in = uInt(raw.size());
+
+ std::string result;
+ char out[16384];
+
+ int code;
+ do {
+ deflate_stream.next_out = reinterpret_cast<Bytef *>(out);
+ deflate_stream.avail_out = sizeof(out);
+ code = deflate(&deflate_stream, Z_FINISH);
+ if (result.size() < deflate_stream.total_out) {
+ // append the block to the output string
+ result.append(out, deflate_stream.total_out - result.size());
+ }
+ } while (code == Z_OK);
+
+ deflateEnd(&deflate_stream);
+
+ if (code != Z_STREAM_END) {
+ throw std::runtime_error(deflate_stream.msg);
+ }
+
+ return result;
+}
+
+std::string decompress(const std::string &raw) {
+ z_stream inflate_stream;
+ memset(&inflate_stream, 0, sizeof(inflate_stream));
+
+ // TODO: reuse z_streams
+ if (inflateInit(&inflate_stream) != Z_OK) {
+ throw std::runtime_error("failed to initialize inflate");
+ }
+
+ inflate_stream.next_in = (Bytef *)raw.data();
+ inflate_stream.avail_in = uInt(raw.size());
+
+ std::string result;
+ char out[15384];
+
+ int code;
+ do {
+ inflate_stream.next_out = reinterpret_cast<Bytef *>(out);
+ inflate_stream.avail_out = sizeof(out);
+ code = inflate(&inflate_stream, 0);
+ // result.append(out, sizeof(out) - inflate_stream.avail_out);
+ if (result.size() < inflate_stream.total_out) {
+ result.append(out, inflate_stream.total_out - result.size());
+ }
+ } while (code == Z_OK);
+
+ inflateEnd(&inflate_stream);
+
+ if (code != Z_STREAM_END) {
+ throw std::runtime_error(inflate_stream.msg ? inflate_stream.msg : "decompression error");
+ }
+
+ return result;
+}
+}
+}
diff --git a/src/mbgl/util/compression.hpp b/src/mbgl/util/compression.hpp
new file mode 100644
index 0000000000..a33b2476a7
--- /dev/null
+++ b/src/mbgl/util/compression.hpp
@@ -0,0 +1,15 @@
+#ifndef MBGL_UTIL_COMPRESSION
+#define MBGL_UTIL_COMPRESSION
+
+#include <string>
+
+namespace mbgl {
+namespace util {
+
+std::string compress(const std::string &raw);
+std::string decompress(const std::string &raw);
+
+}
+}
+
+#endif
diff --git a/src/mbgl/util/scaling.cpp b/src/mbgl/util/scaling.cpp
new file mode 100644
index 0000000000..a554b2e137
--- /dev/null
+++ b/src/mbgl/util/scaling.cpp
@@ -0,0 +1,111 @@
+#include "scaling.hpp"
+
+namespace {
+
+using namespace mbgl;
+
+inline uint8_t bilinearInterpolate(uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, double dx, double dy) {
+ const double t = dx * (tr - tl) + tl;
+ const double b = dx * (br - bl) + bl;
+ return t + dy * (b - t);
+}
+
+template <size_t i>
+inline const uint8_t& b(const uint32_t& w) {
+ return reinterpret_cast<const uint8_t*>(&w)[i];
+}
+
+template <size_t i>
+inline uint8_t& b(uint32_t& w) {
+ return reinterpret_cast<uint8_t*>(&w)[i];
+}
+
+vec2<double> getFactor(const Rect<uint32_t>& srcPos, const Rect<uint32_t>& dstPos) {
+ return {
+ double(srcPos.w) / dstPos.w,
+ double(srcPos.h) / dstPos.h
+ };
+}
+
+vec2<uint32_t> getBounds(const vec2<uint32_t>& srcSize, const Rect<uint32_t>& srcPos,
+ const vec2<uint32_t>& dstSize, const Rect<uint32_t>& dstPos,
+ const vec2<double>& factor) {
+ if (srcPos.x > srcSize.x || srcPos.y > srcSize.y ||
+ dstPos.x > dstSize.x || dstPos.y > dstSize.y) {
+ // Source or destination position is out of range.
+ return { 0, 0 };
+ }
+
+ // Make sure we don't read/write values out of range.
+ return { std::min(uint32_t(double(srcSize.x - srcPos.x) / factor.x),
+ std::min(dstSize.x - dstPos.x, dstPos.w)),
+ std::min(uint32_t(double(srcSize.y - srcPos.y) / factor.y),
+ std::min(dstSize.y - dstPos.y, dstPos.h)) };
+}
+}
+
+namespace mbgl {
+namespace util {
+
+void bilinearScale(const uint32_t* srcData, const vec2<uint32_t>& srcSize,
+ const Rect<uint32_t>& srcPos, uint32_t* dstData, const vec2<uint32_t>& dstSize,
+ const Rect<uint32_t>& dstPos) {
+ const auto factor = getFactor(srcPos, dstPos);
+ const auto bounds = getBounds(srcSize, srcPos, dstSize, dstPos, factor);
+
+ double fractSrcY = srcPos.y;
+ double fractSrcX;
+ uint32_t x, y;
+ size_t i = dstSize.x * dstPos.y + dstPos.x;
+ for (y = 0; y < bounds.y; y++) {
+ fractSrcX = srcPos.x;
+ const uint32_t srcY0 = fractSrcY;
+ const uint32_t srcY1 = std::min(srcY0 + 1, srcSize.y - 1);
+ for (x = 0; x < bounds.x; x++) {
+ const uint32_t srcX0 = fractSrcX;
+ const uint32_t srcX1 = std::min(srcX0 + 1, srcSize.x - 1);
+
+ const uint32_t tl = srcData[srcSize.x * srcY0 + srcX0];
+ const uint32_t tr = srcData[srcSize.x * srcY0 + srcX1];
+ const uint32_t bl = srcData[srcSize.x * srcY1 + srcX0];
+ const uint32_t br = srcData[srcSize.x * srcY1 + srcX1];
+
+ const double dx = fractSrcX - srcX0;
+ const double dy = fractSrcY - srcY0;
+ uint32_t& dst = dstData[i + x];
+ b<0>(dst) = bilinearInterpolate(b<0>(tl), b<0>(tr), b<0>(bl), b<0>(br), dx, dy);
+ b<1>(dst) = bilinearInterpolate(b<1>(tl), b<1>(tr), b<1>(bl), b<1>(br), dx, dy);
+ b<2>(dst) = bilinearInterpolate(b<2>(tl), b<2>(tr), b<2>(bl), b<2>(br), dx, dy);
+ b<3>(dst) = bilinearInterpolate(b<3>(tl), b<3>(tr), b<3>(bl), b<3>(br), dx, dy);
+ fractSrcX += factor.x;
+ }
+ i += dstSize.x;
+ fractSrcY += factor.y;
+ }
+}
+
+void nearestNeighborScale(const uint32_t* srcData, const vec2<uint32_t>& srcSize,
+ const Rect<uint32_t>& srcPos, uint32_t* dstData,
+ const vec2<uint32_t>& dstSize, const Rect<uint32_t>& dstPos) {
+ const auto factor = getFactor(srcPos, dstPos);
+ const auto bounds = getBounds(srcSize, srcPos, dstSize, dstPos, factor);
+
+ double fractSrcY = srcPos.y;
+ double fractSrcX;
+ size_t i = dstSize.x * dstPos.y + dstPos.x;
+ uint32_t srcY;
+ uint32_t x, y;
+ for (y = 0; y < bounds.y; y++) {
+ fractSrcX = srcPos.x;
+ srcY = srcSize.x * uint32_t(fractSrcY);
+ for (x = 0; x < bounds.x; x++) {
+ dstData[i + x] = srcData[srcY + uint32_t(fractSrcX)];
+ fractSrcX += factor.x;
+ }
+ i += dstSize.x;
+ fractSrcY += factor.y;
+ }
+}
+
+}
+} \ No newline at end of file
diff --git a/src/mbgl/util/scaling.hpp b/src/mbgl/util/scaling.hpp
new file mode 100644
index 0000000000..d2625e9219
--- /dev/null
+++ b/src/mbgl/util/scaling.hpp
@@ -0,0 +1,23 @@
+#ifndef MBGL_UTIL_SCALING
+#define MBGL_UTIL_SCALING
+
+
+#include <mbgl/util/vec.hpp>
+#include <mbgl/util/rect.hpp>
+
+#include <cstdint>
+
+namespace mbgl {
+namespace util {
+
+void bilinearScale(const uint32_t* srcData, const vec2<uint32_t>& srcSize,
+ const Rect<uint32_t>& srcPos, uint32_t* dstData, const vec2<uint32_t>& dstSize,
+ const Rect<uint32_t>& dstPos);
+
+void nearestNeighborScale(const uint32_t* srcData, const vec2<uint32_t>& srcSize,
+ const Rect<uint32_t>& srcPos, uint32_t* dstData,
+ const vec2<uint32_t>& dstSize, const Rect<uint32_t>& dstPos);
+}
+}
+
+#endif