From 2988ca16c1bfa8f00518411d2a711113fe88d13f Mon Sep 17 00:00:00 2001 From: Bobby Sudekum Date: Mon, 9 Jan 2017 13:03:17 -0800 Subject: Add addImage, removeImage API (#7610) --- platform/node/src/node_map.cpp | 79 ++++++++++++++ platform/node/src/node_map.hpp | 2 + platform/node/test/js/map.test.js | 160 +++++++++++++++++++++++++++++ platform/node/test/suite_implementation.js | 13 +++ 4 files changed, 254 insertions(+) (limited to 'platform/node') diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index acf83eef66..ea3517f2a6 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -58,6 +59,8 @@ void NodeMap::Init(v8::Local target) { Nan::SetPrototypeMethod(tpl, "addSource", AddSource); Nan::SetPrototypeMethod(tpl, "addLayer", AddLayer); Nan::SetPrototypeMethod(tpl, "removeLayer", RemoveLayer); + Nan::SetPrototypeMethod(tpl, "addImage", AddImage); + Nan::SetPrototypeMethod(tpl, "removeImage", RemoveLayer); Nan::SetPrototypeMethod(tpl, "setLayoutProperty", SetLayoutProperty); Nan::SetPrototypeMethod(tpl, "setPaintProperty", SetPaintProperty); Nan::SetPrototypeMethod(tpl, "setFilter", SetFilter); @@ -579,6 +582,82 @@ void NodeMap::RemoveLayer(const Nan::FunctionCallbackInfo& info) { nodeMap->map->removeLayer(*Nan::Utf8String(info[0])); } + +void NodeMap::AddImage(const Nan::FunctionCallbackInfo& info) { + using namespace mbgl::style; + using namespace mbgl::style::conversion; + + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + if (info.Length() != 3) { + return Nan::ThrowTypeError("Three arguments required"); + } + + if (!info[0]->IsString()) { + return Nan::ThrowTypeError("First argument must be a string"); + } + + if (!info[1]->IsObject()) { + return Nan::ThrowTypeError("Second argument must be an object"); + } + + if (!info[2]->IsObject()) { + return Nan::ThrowTypeError("Third argument must be an object"); + } + + auto optionObject = Nan::To(info[2]).ToLocalChecked(); + + if (!Nan::Get(optionObject, Nan::New("height").ToLocalChecked()).ToLocalChecked()->IsUint32()) { + return Nan::ThrowTypeError("height parameter required"); + } + + if (!Nan::Get(optionObject, Nan::New("width").ToLocalChecked()).ToLocalChecked()->IsUint32()) { + return Nan::ThrowTypeError("width parameter required"); + } + + if (!Nan::Get(optionObject, Nan::New("pixelRatio").ToLocalChecked()).ToLocalChecked()->IsUint32()) { + return Nan::ThrowTypeError("pixelRatio parameter required"); + } + + uint32_t imageHeight = Nan::Get(optionObject, Nan::New("height").ToLocalChecked()).ToLocalChecked()->Uint32Value(); + uint32_t imageWidth = Nan::Get(optionObject, Nan::New("width").ToLocalChecked()).ToLocalChecked()->Uint32Value(); + + if (imageWidth > 1024 || imageHeight > 1024) { + return Nan::ThrowTypeError("Max height and width is 1024"); + } + + uint32_t pixelRatio = Nan::Get(optionObject, Nan::New("pixelRatio").ToLocalChecked()).ToLocalChecked()->Uint32Value(); + auto imageBuffer = Nan::To(info[1]).ToLocalChecked()->ToObject(); + + if (node::Buffer::Length(imageBuffer) != imageHeight * imageWidth * 4) { + return Nan::ThrowTypeError("Image size does not match buffer size"); + } + + std::unique_ptr data = std::make_unique(node::Buffer::Length(imageBuffer)); + std::copy(node::Buffer::Data(imageBuffer), node::Buffer::Data(imageBuffer) + node::Buffer::Length(imageBuffer), data.get()); + mbgl::PremultipliedImage cPremultipliedImage({ imageWidth, imageHeight}, std::move(data)); + + nodeMap->map->addImage(*Nan::Utf8String(info[0]), std::make_unique(std::move(cPremultipliedImage), pixelRatio)); +} + +void NodeMap::RemoveImage(const Nan::FunctionCallbackInfo& info) { + using namespace mbgl::style; + using namespace mbgl::style::conversion; + + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + if (info.Length() != 1) { + return Nan::ThrowTypeError("One argument required"); + } + + if (!info[0]->IsString()) { + return Nan::ThrowTypeError("First argument must be a string"); + } + + nodeMap->map->removeImage(*Nan::Utf8String(info[0])); +} void NodeMap::SetLayoutProperty(const Nan::FunctionCallbackInfo& info) { using namespace mbgl::style; diff --git a/platform/node/src/node_map.hpp b/platform/node/src/node_map.hpp index c68f543b02..47a5385ad6 100644 --- a/platform/node/src/node_map.hpp +++ b/platform/node/src/node_map.hpp @@ -37,6 +37,8 @@ public: static void AddSource(const Nan::FunctionCallbackInfo&); static void AddLayer(const Nan::FunctionCallbackInfo&); static void RemoveLayer(const Nan::FunctionCallbackInfo&); + static void AddImage(const Nan::FunctionCallbackInfo&); + static void RemoveImage(const Nan::FunctionCallbackInfo&); static void SetLayoutProperty(const Nan::FunctionCallbackInfo&); static void SetPaintProperty(const Nan::FunctionCallbackInfo&); static void SetFilter(const Nan::FunctionCallbackInfo&); diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js index e3b287f68f..145f62ef5f 100644 --- a/platform/node/test/js/map.test.js +++ b/platform/node/test/js/map.test.js @@ -111,6 +111,8 @@ test('Map', function(t) { 'addSource', 'addLayer', 'removeLayer', + 'addImage', + 'removeImage', 'setLayoutProperty', 'setPaintProperty', 'setFilter', @@ -129,6 +131,164 @@ test('Map', function(t) { t.end(); }); + t.test('.addImage', function(t) { + var options = { + request: function() {}, + ratio: 1 + }; + + t.test('requires 3 arguments', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.addImage(); + }, /Three arguments required/); + + map.release(); + t.end(); + }); + + t.test('requires image argument to be an object', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.addImage('foo', '', {}); + }, /Second argument must be an object/); + + map.release(); + t.end(); + }); + + t.test('requires options argument to have a height param', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.addImage('foo', {}, { + width: 40, + pixelRatio: 2 + }); + }, /height parameter required/); + + map.release(); + t.end(); + }); + + t.test('requires options argument to have a pixelRatio param', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.addImage('foo', {}, { + width: 40, + height: 40 + }); + }, /pixelRatio parameter required/); + + map.release(); + t.end(); + }); + + t.test('requires specified height to be actual height of image', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.addImage('foo', new Buffer(''), { + width: 401, + height: 400, + pixelRatio: 1 + }, 'Image size does not match buffer size'); + }); + + map.release(); + t.end(); + }); + + t.test('requires height and width to be less than 1024', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.addImage('foo', new Buffer(''), { + width: 1025, + height: 1025, + pixelRatio: 1 + }, 'Max height and width is 1024'); + }); + + map.release(); + t.end(); + }); + + + t.test('requires specified height to be actual height of image', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.addImage('foo', new Buffer(' '), { + width: 401, + height: 400, + pixelRatio: 1 + }, 'Image size does not match buffer size'); + }); + + map.release(); + t.end(); + }); + + t.test('No error', function(t) { + var map = new mbgl.Map(options); + + t.doesNotThrow(function() { + map.addImage('foo', new Buffer(' '), { + width: 1, + height: 1, + pixelRatio: 1 + }); + }); + + map.release(); + t.end(); + }); + }); + + t.test('.removeImage', function(t) { + var options = { + request: function() {}, + ratio: 1 + }; + + t.test('requires one argument', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.removeImage(); + }, /One argument required/); + + map.release(); + t.end(); + }); + + t.test('requires string as first argument', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.removeImage({}); + }, /First argument must be a string/); + + map.release(); + t.end(); + }); + + t.test('removes image', function(t) { + var map = new mbgl.Map(options); + + t.doesNotThrow(function() { + map.removeImage('fooBar'); + }); + + map.release(); + t.end(); + }); + }); + t.test('.load', function(t) { var options = { request: function() {}, diff --git a/platform/node/test/suite_implementation.js b/platform/node/test/suite_implementation.js index 3abf4136c4..55d7c64d2a 100644 --- a/platform/node/test/suite_implementation.js +++ b/platform/node/test/suite_implementation.js @@ -2,6 +2,9 @@ var mbgl = require('../index'); var request = require('request'); +var PNG = require('pngjs').PNG; +var fs = require('fs'); +var path = require('path'); mbgl.on('message', function(msg) { console.log('%s (%s): %s', msg.severity, msg.class, msg.text); @@ -65,6 +68,16 @@ module.exports = function (style, options, callback) { applyOperations(operations.slice(1), callback); }); + } else if (operation[0] === 'addImage') { + var img = PNG.sync.read(fs.readFileSync(path.join(__dirname, '../../../node_modules', 'mapbox-gl-test-suite', operation[2]))); + + map.addImage(operation[1], img.data, { + height: img.height, + width: img.width, + pixelRatio: 1 + }); + + applyOperations(operations.slice(1), callback); } else { // Ensure that the next `map.render(options)` does not overwrite this change. if (operation[0] === 'setCenter') { -- cgit v1.2.1