summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format2
-rw-r--r--Makefile1
-rw-r--r--binding.gyp4
-rw-r--r--src/compress_png.cpp108
-rw-r--r--src/compress_png.hpp8
-rw-r--r--src/mbgl.cpp2
-rw-r--r--src/node_map.cpp168
-rw-r--r--src/node_map.hpp14
-rw-r--r--src/node_map_render_worker.cpp56
-rw-r--r--src/node_map_render_worker.hpp39
m---------vendor/mbgl0
11 files changed, 274 insertions, 128 deletions
diff --git a/.clang-format b/.clang-format
index 86d562e74e..109b562b59 100644
--- a/.clang-format
+++ b/.clang-format
@@ -2,7 +2,7 @@ Standard: Cpp11
IndentWidth: 4
AccessModifierOffset: -4
UseTab: Never
-BinPackParameters: false
+BinPackParameters: true
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortBlocksOnASingleLine: false
diff --git a/Makefile b/Makefile
index 035f1e381a..97b16cfea2 100644
--- a/Makefile
+++ b/Makefile
@@ -32,7 +32,6 @@ global: build
build: build/Makefile
@node-gyp build $(DEBUG_FLAG) --clang -- -j$(JOBS)
-.PHONY: vendor/mbgl
vendor/mbgl:
git submodule update --init
diff --git a/binding.gyp b/binding.gyp
index fb18393908..dc030205b3 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -11,12 +11,12 @@
'sources': [
'src/mbgl.cpp',
+ 'src/compress_png.hpp',
+ 'src/compress_png.cpp',
'src/node_file_source.hpp',
'src/node_file_source.cpp',
'src/node_map.hpp',
'src/node_map.cpp',
- 'src/node_map_render_worker.hpp',
- 'src/node_map_render_worker.cpp',
'src/node_request.hpp',
'src/node_request.cpp',
],
diff --git a/src/compress_png.cpp b/src/compress_png.cpp
new file mode 100644
index 0000000000..699df5346c
--- /dev/null
+++ b/src/compress_png.cpp
@@ -0,0 +1,108 @@
+#include <node.h>
+#include <nan.h>
+
+#include <mbgl/util/image.hpp>
+
+namespace node_mbgl {
+
+class CompressPNGWorker : public NanAsyncWorker {
+public:
+ CompressPNGWorker(NanCallback *callback, v8::Local<v8::Object> buffer_, uint32_t width_,
+ uint32_t height_)
+ : NanAsyncWorker(callback),
+ buffer(v8::Persistent<v8::Object>::New(buffer_)),
+ data(node::Buffer::Data(buffer_)),
+ width(width_),
+ height(height_) {
+ assert(width * height * 4 == node::Buffer::Length(buffer_));
+ }
+
+ ~CompressPNGWorker() {
+ buffer.Dispose();
+ }
+
+ void Execute() {
+ result = mbgl::util::compress_png(width, height, data);
+ }
+
+ void HandleOKCallback() {
+ NanScope();
+
+ auto img = new std::string(std::move(result));
+
+ v8::Local<v8::Value> argv[] = {
+ NanNull(),
+ NanNewBufferHandle(
+ const_cast<char *>(img->data()),
+ img->size(),
+ // Retain the std::string until the buffer is deleted.
+ [](char *, void *hint) {
+ delete reinterpret_cast<std::string *>(hint);
+ },
+ img
+ )
+ };
+
+ callback->Call(2, argv);
+ };
+
+private:
+ // Retains the buffer while this worker is processing. The user may not modify the buffer.
+ v8::Persistent<v8::Object> buffer;
+ void *data;
+ const uint32_t width;
+ const uint32_t height;
+
+ // Stores the compressed PNG.
+ std::string result;
+};
+
+NAN_METHOD(CompressPNG) {
+ NanScope();
+
+ if (args.Length() <= 0 || !args[0]->IsObject()) {
+ return NanThrowTypeError("First argument must be the data object");
+ }
+
+ uint32_t width = 0;
+ uint32_t height = 0;
+ v8::Local<v8::Object> buffer;
+
+ auto options = args[0]->ToObject();
+ if (options->Has(NanNew("width"))) {
+ width = options->Get(NanNew("width"))->Uint32Value();
+ }
+ if (!width) {
+ NanThrowRangeError("Image width must be greater than 0");
+ }
+ if (options->Has(NanNew("height"))) {
+ height = options->Get(NanNew("height"))->Uint32Value();
+ }
+ if (!height) {
+ NanThrowRangeError("Image height must be greater than 0");
+ }
+ if (options->Has(NanNew("pixels"))) {
+ buffer = options->Get(NanNew("pixels")).As<v8::Object>();
+ }
+ if (!node::Buffer::HasInstance(buffer)) {
+ NanThrowTypeError("Pixels must be a Buffer object");
+ }
+ if (width * height * 4 != node::Buffer::Length(buffer)) {
+ NanThrowError("Pixel buffer doesn't match image dimensions");
+ }
+
+ if (args.Length() < 2) {
+ NanThrowTypeError("Second argument must be a callback function");
+ }
+ NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
+
+ NanAsyncQueueWorker(new CompressPNGWorker(callback, buffer, width, height));
+ NanReturnUndefined();
+}
+
+void InitCompressPNG(v8::Handle<v8::Object> target) {
+ target->Set(NanNew<v8::String>("compressPNG"),
+ NanNew<v8::FunctionTemplate>(CompressPNG)->GetFunction());
+}
+
+}
diff --git a/src/compress_png.hpp b/src/compress_png.hpp
new file mode 100644
index 0000000000..754b383e70
--- /dev/null
+++ b/src/compress_png.hpp
@@ -0,0 +1,8 @@
+#include <node.h>
+#include <nan.h>
+
+namespace node_mbgl {
+
+void InitCompressPNG(v8::Handle<v8::Object> target);
+
+} \ No newline at end of file
diff --git a/src/mbgl.cpp b/src/mbgl.cpp
index 677e72b2d1..b218382ab1 100644
--- a/src/mbgl.cpp
+++ b/src/mbgl.cpp
@@ -4,6 +4,7 @@
#include "node_file_source.hpp"
#include "node_map.hpp"
#include "node_request.hpp"
+#include "compress_png.hpp"
void RegisterModule(v8::Handle<v8::Object> exports) {
NanScope();
@@ -11,6 +12,7 @@ void RegisterModule(v8::Handle<v8::Object> exports) {
node_mbgl::NodeFileSource::Init(exports);
node_mbgl::NodeMap::Init(exports);
node_mbgl::NodeRequest::Init(exports);
+ node_mbgl::InitCompressPNG(exports);
// Exports Resource constants.
auto ConstantProperty = static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
diff --git a/src/node_map.cpp b/src/node_map.cpp
index 8cf9ee9b6a..8a0634a90f 100644
--- a/src/node_map.cpp
+++ b/src/node_map.cpp
@@ -1,13 +1,25 @@
#include "node_map.hpp"
-#include "node_map_render_worker.hpp"
#include "node_file_source.hpp"
#include <mbgl/platform/default/headless_display.hpp>
+#include <mbgl/map/still_image.hpp>
+#include <mbgl/util/exception.hpp>
#include <unistd.h>
namespace node_mbgl {
+struct NodeMap::RenderOptions {
+ double zoom = 0;
+ double bearing = 0;
+ double latitude = 0;
+ double longitude = 0;
+ unsigned int width = 512;
+ unsigned int height = 512;
+ float ratio = 1.0f;
+ std::vector<std::string> classes;
+};
+
////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods
@@ -21,6 +33,7 @@ void NodeMap::Init(v8::Handle<v8::Object> target) {
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(NanNew("Map"));
+ NODE_SET_PROTOTYPE_METHOD(t, "setAccessToken", SetAccessToken);
NODE_SET_PROTOTYPE_METHOD(t, "load", Load);
NODE_SET_PROTOTYPE_METHOD(t, "render", Render);
@@ -56,6 +69,32 @@ NAN_METHOD(NodeMap::New) {
NanReturnValue(args.This());
}
+
+NAN_METHOD(NodeMap::SetAccessToken) {
+ NanScope();
+
+ if (args.Length() < 1) {
+ return NanThrowError("Requires a string as first argument");
+ }
+
+ NanUtf8String token(args[0]);
+
+ auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
+
+ if (nodeMap->map.isRendering()) {
+ return NanThrowError("Map object is currently in use");
+ }
+
+ try {
+ nodeMap->map.setAccessToken(std::string { *token, size_t(token.length()) });
+ } catch (const std::exception &ex) {
+ return NanThrowError(ex.what());
+ }
+
+
+ NanReturnUndefined();
+}
+
const std::string StringifyStyle(v8::Handle<v8::Value> styleHandle) {
NanScope();
@@ -77,19 +116,22 @@ NAN_METHOD(NodeMap::Load) {
if (args[0]->IsObject()) {
style = StringifyStyle(args[0]);
} else if (args[0]->IsString()) {
- v8::Local<v8::String> toStr = args[0]->ToString();
- style.resize(toStr->Utf8Length());
- toStr->WriteUtf8(const_cast<char *>(style.data()));
+ NanUtf8String string(args[0]);
+ style = { *string, size_t(string.length()) };
} else {
return NanThrowTypeError("First argument must be a string or object");
}
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
+ if (nodeMap->map.isRendering()) {
+ return NanThrowError("Map object is currently in use");
+ }
+
try {
nodeMap->map.setStyleJSON(style, ".");
} catch (const std::exception &ex) {
- NanThrowError(ex.what());
+ return NanThrowError(ex.what());
}
NanReturnUndefined();
@@ -110,9 +152,6 @@ std::unique_ptr<NodeMap::RenderOptions> NodeMap::ParseOptions(v8::Local<v8::Obje
if (obj->Has(NanNew("width"))) { options->width = obj->Get(NanNew("width"))->IntegerValue(); }
if (obj->Has(NanNew("height"))) { options->height = obj->Get(NanNew("height"))->IntegerValue(); }
if (obj->Has(NanNew("ratio"))) { options->ratio = obj->Get(NanNew("ratio"))->IntegerValue(); }
- if (obj->Has(NanNew("accessToken"))) {
- options->accessToken = *NanUtf8String(obj->Get(NanNew("accessToken")->ToString()));
- }
if (obj->Has(NanNew("classes"))) {
auto classes = obj->Get(NanNew("classes"))->ToObject().As<v8::Array>();
@@ -137,23 +176,99 @@ NAN_METHOD(NodeMap::Render) {
return NanThrowTypeError("Second argument must be a callback function");
}
+ auto options = ParseOptions(args[0]->ToObject());
+
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
- const bool wasEmpty = nodeMap->queue_.empty();
+ if (nodeMap->map.isRendering()) {
+ return NanThrowError("Map object is currently in use");
+ }
- nodeMap->queue_.push(mbgl::util::make_unique<RenderWorker>(
- nodeMap,
- ParseOptions(args[0]->ToObject()),
- new NanCallback(args[1].As<v8::Function>())));
+ assert(!nodeMap->callback);
+ assert(!nodeMap->image);
+ nodeMap->callback = std::unique_ptr<NanCallback>(new NanCallback(args[1].As<v8::Function>()));
- if (wasEmpty) {
- // When the queue was empty, there was no action in progress, so we can start a new one.
- NanAsyncQueueWorker(nodeMap->queue_.front().release());
+ try {
+ nodeMap->startRender(std::move(options));
+ } catch (mbgl::util::Exception &ex) {
+ return NanThrowError(ex.what());
}
NanReturnUndefined();
}
+void NodeMap::startRender(std::unique_ptr<NodeMap::RenderOptions> options) {
+ view.resize(options->width, options->height, options->ratio);
+ map.setClasses(options->classes);
+ map.setLonLatZoom(options->longitude, options->latitude, options->zoom);
+ map.setBearing(options->bearing);
+
+ map.renderStill([this](std::unique_ptr<const mbgl::StillImage> result) {
+ assert(!image);
+ image = std::move(result);
+ uv_async_send(async);
+ });
+
+ // Retain this object, otherwise it might get destructed before we are finished rendering the
+ // still image.
+ Ref();
+
+ // Similarly, we're now waiting for the async to be called, so we need to make sure that it
+ // keeps the loop alive.
+ uv_ref(reinterpret_cast<uv_handle_t *>(async));
+}
+
+void NodeMap::renderFinished() {
+ NanScope();
+
+ // We're done with this render call, so we're unrefing so that the loop could close.
+ uv_unref(reinterpret_cast<uv_handle_t *>(async));
+
+ // There is no render pending anymore, we the GC could now delete this object if it went out
+ // of scope.
+ Unref();
+
+ // Move the callback and image out of the way so that the callback can start a new render call.
+ auto cb = std::move(callback);
+ auto img = std::move(image);
+ assert(cb);
+
+ // These have to be empty to be prepared for the next render call.
+ assert(!callback);
+ assert(!image);
+
+ if (img) {
+ v8::Local<v8::Object> result = v8::Object::New();
+ result->Set(NanNew("width"), NanNew(img->width));
+ result->Set(NanNew("height"), NanNew(img->height));
+
+ v8::Local<v8::Object> pixels = NanNewBufferHandle(
+ reinterpret_cast<char *>(img->pixels.get()),
+ size_t(img->width) * size_t(img->height) * sizeof(mbgl::StillImage::Pixel),
+
+ // Retain the StillImage object until the buffer is deleted.
+ [](char *, void *hint) {
+ delete reinterpret_cast<std::unique_ptr<const mbgl::StillImage> *>(hint);
+ },
+ new std::unique_ptr<const mbgl::StillImage>(std::move(img))
+ );
+
+ result->Set(NanNew("pixels"), pixels);
+
+ v8::Local<v8::Value> argv[] = {
+ NanNull(),
+ result,
+ };
+ cb->Call(2, argv);
+ } else {
+ v8::Local<v8::Value> argv[] = {
+ NanError("Didn't get an image")
+ };
+ cb->Call(1, argv);
+ }
+}
+
+
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
@@ -165,21 +280,24 @@ std::shared_ptr<mbgl::HeadlessDisplay> sharedDisplay() {
NodeMap::NodeMap(v8::Handle<v8::Object> source_) :
view(sharedDisplay()),
fs(*ObjectWrap::Unwrap<NodeFileSource>(source_)),
- map(view, fs) {
+ map(view, fs, mbgl::Map::RenderMode::Still),
+ async(new uv_async_t) {
source = v8::Persistent<v8::Object>::New(source_);
+ async->data = this;
+ uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) {
+ reinterpret_cast<NodeMap *>(as->data)->renderFinished();
+ });
+
+ // Make sure the async handle doesn't keep the loop alive.
+ uv_unref(reinterpret_cast<uv_handle_t *>(async));
}
NodeMap::~NodeMap() {
source.Dispose();
-}
-void NodeMap::processNext() {
- assert(!queue_.empty());
- queue_.pop();
- if (!queue_.empty()) {
- // When the queue was empty, there was no action in progress, so we can start a new one.
- NanAsyncQueueWorker(queue_.front().release());
- }
+ uv_close(reinterpret_cast<uv_handle_t *>(async), [] (uv_handle_t *handle) {
+ delete reinterpret_cast<uv_async_t *>(handle);
+ });
}
}
diff --git a/src/node_map.hpp b/src/node_map.hpp
index fdc625c038..614eab2803 100644
--- a/src/node_map.hpp
+++ b/src/node_map.hpp
@@ -23,9 +23,13 @@ class NodeMap : public node::ObjectWrap {
public:
static void Init(v8::Handle<v8::Object> target);
static NAN_METHOD(New);
+ static NAN_METHOD(SetAccessToken);
static NAN_METHOD(Load);
static NAN_METHOD(Render);
+ void startRender(std::unique_ptr<NodeMap::RenderOptions> options);
+ void renderFinished();
+
static std::unique_ptr<NodeMap::RenderOptions> ParseOptions(v8::Local<v8::Object> obj);
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
@@ -36,17 +40,19 @@ private:
NodeMap(v8::Handle<v8::Object> source);
~NodeMap();
- void processNext();
-
private:
+ // For retaining the FileSource object.
v8::Persistent<v8::Object> source;
- v8::Persistent<v8::Object> self;
mbgl::HeadlessView view;
NodeFileSource &fs;
mbgl::Map map;
- std::queue<std::unique_ptr<RenderWorker>> queue_;
+ std::unique_ptr<const mbgl::StillImage> image;
+ std::unique_ptr<NanCallback> callback;
+
+ // Async for delivering the notifications of render completion.
+ uv_async_t *async;
};
} // end ns node_mbgl
diff --git a/src/node_map_render_worker.cpp b/src/node_map_render_worker.cpp
deleted file mode 100644
index 6079e8a8cd..0000000000
--- a/src/node_map_render_worker.cpp
+++ /dev/null
@@ -1,56 +0,0 @@
-#include "node_map_render_worker.hpp"
-#include "node_file_source.hpp"
-
-#include <mbgl/util/image.hpp>
-
-namespace node_mbgl {
-
-NodeMap::RenderWorker::RenderWorker(NodeMap *nodeMap_, std::unique_ptr<RenderOptions> options_,
- NanCallback *callback_)
- : NanAsyncWorker(callback_), nodeMap(nodeMap_), options(std::move(options_)) {
- nodeMap->Ref();
-}
-
-NodeMap::RenderWorker::~RenderWorker() {
- nodeMap->Unref();
-}
-
-void NodeMap::RenderWorker::Execute() {
- try {
- nodeMap->view.resize(options->width, options->height, options->ratio);
- nodeMap->map.setAccessToken(options->accessToken);
- nodeMap->map.setClasses(options->classes);
- nodeMap->map.setLonLatZoom(options->longitude, options->latitude, options->zoom);
- nodeMap->map.setBearing(options->bearing);
-
- // Run the loop. It will terminate when we don't have any further listeners.
- nodeMap->map.run();
-
- const unsigned int width = options->width * options->ratio;
- const unsigned int height = options->height * options->ratio;
- auto pixels = nodeMap->view.readPixels();
- image = mbgl::util::compress_png(width, height, pixels.get());
- } catch (mbgl::exception &ex) {
- SetErrorMessage(ex.what());
- }
-}
-
-void NodeMap::RenderWorker::WorkComplete() {
- NanAsyncWorker::WorkComplete();
-
- // Continue processing remaining items in the queue.
- nodeMap->processNext();
-}
-
-void NodeMap::RenderWorker::HandleOKCallback() {
- NanScope();
-
- v8::Local<v8::Value> argv[] = {
- NanNull(),
- NanNewBufferHandle(image.data(), image.length())
- };
-
- callback->Call(2, argv);
-};
-
-} // ns node_mbgl
diff --git a/src/node_map_render_worker.hpp b/src/node_map_render_worker.hpp
deleted file mode 100644
index 920332f7d9..0000000000
--- a/src/node_map_render_worker.hpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef NODE_MBGL_NODE_MAP_RENDER_WORKER
-#define NODE_MBGL_NODE_MAP_RENDER_WORKER
-
-#include <nan.h>
-
-#include "node_map.hpp"
-
-namespace node_mbgl {
-
-struct NodeMap::RenderOptions {
- double zoom = 0;
- double bearing = 0;
- double latitude = 0;
- double longitude = 0;
- unsigned int width = 512;
- unsigned int height = 512;
- float ratio = 1.0f;
- std::string accessToken;
- std::vector<std::string> classes;
-};
-
-class NodeMap::RenderWorker : public NanAsyncWorker {
-public:
- RenderWorker(NodeMap *map, std::unique_ptr<RenderOptions> options, NanCallback *callback);
- ~RenderWorker();
-
- void Execute();
- void HandleOKCallback();
- void WorkComplete();
-
-private:
- NodeMap *nodeMap;
- std::unique_ptr<RenderOptions> options;
- std::string image;
-};
-
-} // ns node_mbgl
-
-#endif // NODE_MBGL_RENDER_WORKER
diff --git a/vendor/mbgl b/vendor/mbgl
-Subproject eac5f6f4540d8adb29230ef07ac46a3306abb42
+Subproject 634de692d70a52706602b2516226ba404cf51ca