Skip to content
Permalink
699a2724d6
Go to file
 
 
Cannot retrieve contributors at this time
4248 lines (3921 sloc) 139 KB
// mapnik
#include <mapnik/color.hpp> // for color
#include <mapnik/image.hpp> // for image types
#include <mapnik/image_any.hpp> // for image_any
#include <mapnik/image_reader.hpp> // for get_image_reader, etc
#include <mapnik/image_util.hpp> // for save_to_string, guess_type, etc
#include <mapnik/image_copy.hpp>
#include <mapnik/image_compositing.hpp>
#include <mapnik/image_filter_types.hpp>
#include <mapnik/image_filter.hpp> // filter_visitor
#include <mapnik/image_scaling.hpp>
#include <mapnik/marker.hpp>
#include <mapnik/marker_cache.hpp>
#include <mapnik/svg/svg_parser.hpp>
#include <mapnik/svg/svg_storage.hpp>
#include <mapnik/svg/svg_converter.hpp>
#include <mapnik/svg/svg_path_adapter.hpp>
#include <mapnik/svg/svg_path_attributes.hpp>
#include <mapnik/svg/svg_path_adapter.hpp>
#include <mapnik/svg/svg_renderer_agg.hpp>
#include <mapnik/svg/svg_path_attributes.hpp>
#include "mapnik_image.hpp"
#include "mapnik_image_view.hpp"
#include "mapnik_palette.hpp"
#include "mapnik_color.hpp"
#include "utils.hpp"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_basics.h"
#include "agg_rendering_buffer.h"
#include "agg_renderer_base.h"
#include "agg_pixfmt_rgba.h"
#include "agg_scanline_u.h"
// boost
#include <boost/optional/optional.hpp>
// std
#include <exception>
#include <ostream> // for operator<<, basic_ostream
#include <sstream> // for basic_ostringstream, etc
#include <cstdlib>
Nan::Persistent<v8::FunctionTemplate> Image::constructor;
/**
* **`mapnik.Image`**
*
* Create a new image object (surface) that can be used for rendering data to.
* @class Image
* @param {number} width - width in pixels
* @param {number} height - height in pixels
* @param {Object} [options]
* @param {Object<mapnik.imageType>} [options.type=mapnik.imageType.rgb8] - a {@link mapnik.imageType} object
* @param {boolean} [options.initialize=true]
* @param {boolean} [options.premultiplied=false]
* @param {boolean} [options.painted=false]
* @property {number} offset - offset number
* @property {number} scaling - scaling number
* @throws {TypeError} if any argument is the wrong type, like if width
* or height is not numeric.
* @example
* var im = new mapnik.Image(256, 256, {
* premultiplied: true,
* type: mapnik.imageType.gray8
* });
*/
void Image::Initialize(v8::Local<v8::Object> target) {
Nan::HandleScope scope;
v8::Local<v8::FunctionTemplate> lcons = Nan::New<v8::FunctionTemplate>(Image::New);
lcons->InstanceTemplate()->SetInternalFieldCount(1);
lcons->SetClassName(Nan::New("Image").ToLocalChecked());
Nan::SetPrototypeMethod(lcons, "getType", getType);
Nan::SetPrototypeMethod(lcons, "getPixel", getPixel);
Nan::SetPrototypeMethod(lcons, "setPixel", setPixel);
Nan::SetPrototypeMethod(lcons, "encodeSync", encodeSync);
Nan::SetPrototypeMethod(lcons, "encode", encode);
Nan::SetPrototypeMethod(lcons, "view", view);
Nan::SetPrototypeMethod(lcons, "saveSync", saveSync);
Nan::SetPrototypeMethod(lcons, "save", save);
Nan::SetPrototypeMethod(lcons, "setGrayScaleToAlpha", setGrayScaleToAlpha);
Nan::SetPrototypeMethod(lcons, "width", width);
Nan::SetPrototypeMethod(lcons, "height", height);
Nan::SetPrototypeMethod(lcons, "painted", painted);
Nan::SetPrototypeMethod(lcons, "composite", composite);
Nan::SetPrototypeMethod(lcons, "filter", filter);
Nan::SetPrototypeMethod(lcons, "filterSync", filterSync);
Nan::SetPrototypeMethod(lcons, "fillSync", fillSync);
Nan::SetPrototypeMethod(lcons, "fill", fill);
Nan::SetPrototypeMethod(lcons, "premultiplySync", premultiplySync);
Nan::SetPrototypeMethod(lcons, "premultiply", premultiply);
Nan::SetPrototypeMethod(lcons, "premultiplied", premultiplied);
Nan::SetPrototypeMethod(lcons, "demultiplySync", demultiplySync);
Nan::SetPrototypeMethod(lcons, "demultiply", demultiply);
Nan::SetPrototypeMethod(lcons, "clear", clear);
Nan::SetPrototypeMethod(lcons, "clearSync", clearSync);
Nan::SetPrototypeMethod(lcons, "compare", compare);
Nan::SetPrototypeMethod(lcons, "isSolid", isSolid);
Nan::SetPrototypeMethod(lcons, "isSolidSync", isSolidSync);
Nan::SetPrototypeMethod(lcons, "copy", copy);
Nan::SetPrototypeMethod(lcons, "copySync", copySync);
Nan::SetPrototypeMethod(lcons, "resize", resize);
Nan::SetPrototypeMethod(lcons, "resizeSync", resizeSync);
Nan::SetPrototypeMethod(lcons, "data", data);
// properties
ATTR(lcons, "scaling", get_scaling, set_scaling);
ATTR(lcons, "offset", get_offset, set_offset);
// This *must* go after the ATTR setting
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"open",
Image::open);
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"fromBytes",
Image::fromBytes);
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"openSync",
Image::openSync);
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"fromBytesSync",
Image::fromBytesSync);
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"fromBufferSync",
Image::fromBufferSync);
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"fromSVG",
Image::fromSVG);
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"fromSVGSync",
Image::fromSVGSync);
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"fromSVGBytes",
Image::fromSVGBytes);
Nan::SetMethod(lcons->GetFunction().As<v8::Object>(),
"fromSVGBytesSync",
Image::fromSVGBytesSync);
target->Set(Nan::New("Image").ToLocalChecked(),lcons->GetFunction());
constructor.Reset(lcons);
}
Image::Image(unsigned int width, unsigned int height, mapnik::image_dtype type, bool initialized, bool premultiplied, bool painted) :
Nan::ObjectWrap(),
this_(std::make_shared<mapnik::image_any>(width,height,type,initialized,premultiplied,painted))
{
}
Image::Image(image_ptr _this) :
Nan::ObjectWrap(),
this_(_this)
{
}
Image::~Image()
{
}
NAN_METHOD(Image::New)
{
if (!info.IsConstructCall())
{
Nan::ThrowError("Cannot call constructor as function, you need to use 'new' keyword");
return;
}
if (info[0]->IsExternal())
{
v8::Local<v8::External> ext = info[0].As<v8::External>();
void* ptr = ext->Value();
Image* im = static_cast<Image*>(ptr);
im->Wrap(info.This());
info.GetReturnValue().Set(info.This());
return;
}
if (info.Length() >= 2)
{
mapnik::image_dtype type = mapnik::image_dtype_rgba8;
bool initialize = true;
bool premultiplied = false;
bool painted = false;
if (!info[0]->IsNumber() || !info[1]->IsNumber())
{
Nan::ThrowTypeError("Image 'width' and 'height' must be a integers");
return;
}
if (info.Length() >= 3)
{
if (info[2]->IsObject())
{
v8::Local<v8::Object> options = v8::Local<v8::Object>::Cast(info[2]);
if (options->Has(Nan::New("type").ToLocalChecked()))
{
v8::Local<v8::Value> init_val = options->Get(Nan::New("type").ToLocalChecked());
if (!init_val.IsEmpty() && init_val->IsNumber())
{
int int_val = init_val->IntegerValue();
if (int_val >= mapnik::image_dtype::IMAGE_DTYPE_MAX || int_val < 0)
{
Nan::ThrowTypeError("Image 'type' must be a valid image type");
return;
}
type = static_cast<mapnik::image_dtype>(init_val->IntegerValue());
}
else
{
Nan::ThrowTypeError("'type' option must be a valid 'mapnik.imageType'");
return;
}
}
if (options->Has(Nan::New("initialize").ToLocalChecked()))
{
v8::Local<v8::Value> init_val = options->Get(Nan::New("initialize").ToLocalChecked());
if (!init_val.IsEmpty() && init_val->IsBoolean())
{
initialize = init_val->BooleanValue();
}
else
{
Nan::ThrowTypeError("initialize option must be a boolean");
return;
}
}
if (options->Has(Nan::New("premultiplied").ToLocalChecked()))
{
v8::Local<v8::Value> pre_val = options->Get(Nan::New("premultiplied").ToLocalChecked());
if (!pre_val.IsEmpty() && pre_val->IsBoolean())
{
premultiplied = pre_val->BooleanValue();
}
else
{
Nan::ThrowTypeError("premultiplied option must be a boolean");
return;
}
}
if (options->Has(Nan::New("painted").ToLocalChecked()))
{
v8::Local<v8::Value> painted_val = options->Get(Nan::New("painted").ToLocalChecked());
if (!painted_val.IsEmpty() && painted_val->IsBoolean())
{
painted = painted_val->BooleanValue();
}
else
{
Nan::ThrowTypeError("painted option must be a boolean");
return;
}
}
}
else
{
Nan::ThrowTypeError("Options parameter must be an object");
return;
}
}
try {
Image* im = new Image(info[0]->IntegerValue(),
info[1]->IntegerValue(),
type,
initialize,
premultiplied,
painted);
im->Wrap(info.This());
info.GetReturnValue().Set(info.This());
} catch (std::exception const& ex) {
Nan::ThrowError(ex.what());
}
return;
}
else
{
Nan::ThrowError("please provide at least Image width and height");
return;
}
return;
}
/**
* Determine the image type
*
* @name getType
* @instance
* @returns {number} Number of the image type
* @memberof Image
* @example
* var img = new mapnik.Image(256, 256, {
* type: mapnik.imageType.gray8
* });
* var type = img.getType();
* var typeCheck = mapnik.imageType.gray8;
* console.log(type, typeCheck); // 1, 1
*/
NAN_METHOD(Image::getType)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
unsigned type = im->this_->get_dtype();
info.GetReturnValue().Set(Nan::New<v8::Number>(type));
}
struct visitor_get_pixel
{
visitor_get_pixel(int x, int y)
: x_(x), y_(y) {}
v8::Local<v8::Value> operator() (mapnik::image_null const&)
{
// This should never be reached because the width and height of 0 for a null
// image will prevent the visitor from being called.
/* LCOV_EXCL_START */
Nan::EscapableHandleScope scope;
return scope.Escape(Nan::Undefined());
/* LCOV_EXCL_STOP */
}
v8::Local<v8::Value> operator() (mapnik::image_gray8 const& data)
{
Nan::EscapableHandleScope scope;
std::uint32_t val = mapnik::get_pixel<std::uint32_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Uint32>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray8s const& data)
{
Nan::EscapableHandleScope scope;
std::int32_t val = mapnik::get_pixel<std::int32_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Int32>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray16 const& data)
{
Nan::EscapableHandleScope scope;
std::uint32_t val = mapnik::get_pixel<std::uint32_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Uint32>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray16s const& data)
{
Nan::EscapableHandleScope scope;
std::int32_t val = mapnik::get_pixel<std::int32_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Int32>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray32 const& data)
{
Nan::EscapableHandleScope scope;
std::uint32_t val = mapnik::get_pixel<std::uint32_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Uint32>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray32s const& data)
{
Nan::EscapableHandleScope scope;
std::int32_t val = mapnik::get_pixel<std::int32_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Int32>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray32f const& data)
{
Nan::EscapableHandleScope scope;
double val = mapnik::get_pixel<double>(data, x_, y_);
return scope.Escape(Nan::New<v8::Number>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray64 const& data)
{
Nan::EscapableHandleScope scope;
std::uint64_t val = mapnik::get_pixel<std::uint64_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Number>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray64s const& data)
{
Nan::EscapableHandleScope scope;
std::int64_t val = mapnik::get_pixel<std::int64_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Number>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_gray64f const& data)
{
Nan::EscapableHandleScope scope;
double val = mapnik::get_pixel<double>(data, x_, y_);
return scope.Escape(Nan::New<v8::Number>(val));
}
v8::Local<v8::Value> operator() (mapnik::image_rgba8 const& data)
{
Nan::EscapableHandleScope scope;
std::uint32_t val = mapnik::get_pixel<std::uint32_t>(data, x_, y_);
return scope.Escape(Nan::New<v8::Number>(val));
}
private:
int x_;
int y_;
};
/**
* Get a specific pixel and its value
* @name getPixel
* @instance
* @memberof Image
* @param {number} x - position within image from top left
* @param {number} y - position within image from top left
* @param {Object} [options] the only valid option is `get_color`, which
* should be a `boolean`. If set, the return is an Object with `rgba` values
* instead of a pixel number.
* @returns {number|Object} color number or object of rgba values
* @example
* // check for color after rendering image
* var img = new mapnik.Image(4, 4);
* var map = new mapnik.Map(4, 4);
* map.background = new mapnik.Color('green');
* map.render(img, {},function(err, img) {
* console.log(img.painted()); // false
* var pixel = img.getPixel(0,0);
* var values = img.getPixel(0,0, {get_color: true});
* console.log(pixel); // 4278222848
* console.log(values); // { premultiplied: false, a: 255, b: 0, g: 128, r: 0 }
* });
*/
NAN_METHOD(Image::getPixel)
{
int x = 0;
int y = 0;
bool get_color = false;
if (info.Length() >= 3) {
if (!info[2]->IsObject()) {
Nan::ThrowTypeError("optional third argument must be an options object");
return;
}
v8::Local<v8::Object> options = info[2]->ToObject();
if (options->Has(Nan::New("get_color").ToLocalChecked())) {
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("get_color").ToLocalChecked());
if (!bind_opt->IsBoolean()) {
Nan::ThrowTypeError("optional arg 'color' must be a boolean");
return;
}
get_color = bind_opt->BooleanValue();
}
}
if (info.Length() >= 2) {
if (!info[0]->IsNumber()) {
Nan::ThrowTypeError("first arg, 'x' must be an integer");
return;
}
if (!info[1]->IsNumber()) {
Nan::ThrowTypeError("second arg, 'y' must be an integer");
return;
}
x = info[0]->IntegerValue();
y = info[1]->IntegerValue();
} else {
Nan::ThrowError("must supply x,y to query pixel color");
return;
}
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
if (x >= 0 && x < static_cast<int>(im->this_->width())
&& y >= 0 && y < static_cast<int>(im->this_->height()))
{
if (get_color)
{
mapnik::color val = mapnik::get_pixel<mapnik::color>(*im->this_, x, y);
info.GetReturnValue().Set(Color::NewInstance(val));
} else {
visitor_get_pixel visitor(x, y);
info.GetReturnValue().Set(mapnik::util::apply_visitor(visitor, *im->this_));
}
}
return;
}
/**
* Set a pixels value
* @name setPixel
* @instance
* @memberof Image
* @param {number} x position within image from top left
* @param {number} y position within image from top left
* @param {Object|number} numeric or object representation of a color, typically used with {@link mapnik.Color}
* @example
* var gray = new mapnik.Image(256, 256);
* gray.setPixel(0,0,new mapnik.Color('white'));
* var pixel = gray.getPixel(0,0,{get_color:true});
* console.log(pixel); // { premultiplied: false, a: 255, b: 255, g: 255, r: 255 }
*/
NAN_METHOD(Image::setPixel)
{
if (info.Length() < 3 || (!info[0]->IsNumber() && !info[1]->IsNumber())) {
Nan::ThrowTypeError("expects three arguments: x, y, and pixel value");
return;
}
int x = info[0]->IntegerValue();
int y = info[1]->IntegerValue();
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
if (x < 0 || x >= static_cast<int>(im->this_->width()) || y < 0 || y >= static_cast<int>(im->this_->height()))
{
Nan::ThrowTypeError("invalid pixel requested");
return;
}
if (info[2]->IsUint32())
{
std::uint32_t val = info[2]->Uint32Value();
mapnik::set_pixel<std::uint32_t>(*im->this_,x,y,val);
}
else if (info[2]->IsInt32())
{
std::int32_t val = info[2]->Int32Value();
mapnik::set_pixel<std::int32_t>(*im->this_,x,y,val);
}
else if (info[2]->IsNumber())
{
double val = info[2]->NumberValue();
mapnik::set_pixel<double>(*im->this_,x,y,val);
}
else if (info[2]->IsObject())
{
v8::Local<v8::Object> obj = info[2]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Color::constructor)->HasInstance(obj))
{
Nan::ThrowTypeError("A numeric or color value is expected as third arg");
}
else
{
Color * color = Nan::ObjectWrap::Unwrap<Color>(obj);
mapnik::set_pixel(*im->this_,x,y,*(color->get()));
}
}
else
{
Nan::ThrowTypeError("A numeric or color value is expected as third arg");
}
return;
}
/**
* Compare the pixels of one image to the pixels of another. Returns the number
* of pixels that are different. So, if the images are identical then it returns `0`.
* And if the images share no common pixels it returns the total number of pixels
* in an image which is equivalent to `im.width()*im.height()`.
*
* @name compare
* @instance
* @memberof Image
* @param {mapnik.Image} image - another {@link mapnik.Image} instance to compare to
* @param {Object} [options]
* @param {number} [options.threshold=16] - A value that should be `0` or greater to
* determine if the pixels match. Defaults to 16 which means that `rgba(0,0,0,0)`
* would be considered the same as `rgba(15,15,15,0)`.
* @param {boolean} [options.alpha=true] - `alpha` value, along with `rgb`, is considered
* when comparing pixels
* @returns {number} quantified visual difference between these two images in "number of
* pixels" (i.e. `80` pixels are different);
* @example
* // start with the exact same images
* var img1 = new mapnik.Image(2,2);
* var img2 = new mapnik.Image(2,2);
* console.log(img1.compare(img2)); // 0
*
* // change 1 pixel in img2
* img2.setPixel(0,0, new mapnik.Color('green'));
* console.log(img1.compare(img2)); // 1
*
* // difference in color at first pixel
* img1.setPixel(0,0, new mapnik.Color('red'));
* console.log(img1.compare(img2)); // 1
*
* // two pixels different
* img2.setPixel(0,1, new mapnik.Color('red'));
* console.log(img1.compare(img2)); // 2
*
* // all pixels different
* img2.setPixel(1,1, new mapnik.Color('blue'));
* img2.setPixel(1,0, new mapnik.Color('blue'));
* console.log(img1.compare(img2)); // 4
*/
NAN_METHOD(Image::compare)
{
if (info.Length() < 1 || !info[0]->IsObject()) {
Nan::ThrowTypeError("first argument should be a mapnik.Image");
return;
}
v8::Local<v8::Object> obj = info[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Image::constructor)->HasInstance(obj)) {
Nan::ThrowTypeError("mapnik.Image expected as first arg");
return;
}
int threshold = 16;
unsigned alpha = true;
if (info.Length() > 1) {
if (!info[1]->IsObject()) {
Nan::ThrowTypeError("optional second argument must be an options object");
return;
}
v8::Local<v8::Object> options = info[1]->ToObject();
if (options->Has(Nan::New("threshold").ToLocalChecked())) {
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("threshold").ToLocalChecked());
if (!bind_opt->IsNumber()) {
Nan::ThrowTypeError("optional arg 'threshold' must be a number");
return;
}
threshold = bind_opt->IntegerValue();
}
if (options->Has(Nan::New("alpha").ToLocalChecked())) {
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("alpha").ToLocalChecked());
if (!bind_opt->IsBoolean()) {
Nan::ThrowTypeError("optional arg 'alpha' must be a boolean");
return;
}
alpha = bind_opt->BooleanValue();
}
}
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.This());
Image* im2 = Nan::ObjectWrap::Unwrap<Image>(obj);
if (im->this_->width() != im2->this_->width() ||
im->this_->height() != im2->this_->height()) {
Nan::ThrowTypeError("image dimensions do not match");
return;
}
unsigned difference = mapnik::compare(*im->this_, *im2->this_, threshold, alpha);
info.GetReturnValue().Set(Nan::New<v8::Integer>(difference));
}
/**
* Apply a filter to this image. This changes all pixel values. (synchronous)
*
* @name filterSync
* @instance
* @memberof Image
* @param {string} filter - can be `blur`, `emboss`, `sharpen`,
* `sobel`, or `gray`.
* @example
* var img = new mapnik.Image(5, 5);
* img.filter('blur');
* // your custom code with `img` having blur applied
*/
NAN_METHOD(Image::filterSync)
{
info.GetReturnValue().Set(_filterSync(info));
}
v8::Local<v8::Value> Image::_filterSync(Nan::NAN_METHOD_ARGS_TYPE info) {
Nan::EscapableHandleScope scope;
if (info.Length() < 1) {
Nan::ThrowTypeError("expects one argument: string filter argument");
return scope.Escape(Nan::Undefined());
}
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
if (!info[0]->IsString())
{
Nan::ThrowTypeError("A string is expected for filter argument");
return scope.Escape(Nan::Undefined());
}
std::string filter = TOSTR(info[0]);
try
{
mapnik::filter::filter_image(*im->this_,filter);
}
catch(std::exception const& ex)
{
Nan::ThrowError(ex.what());
}
return scope.Escape(Nan::Undefined());
}
typedef struct {
uv_work_t request;
Image* im;
std::string filter;
bool error;
std::string error_name;
Nan::Persistent<v8::Function> cb;
} filter_image_baton_t;
/**
* Apply a filter to this image. Changes all pixel values.
*
* @name filter
* @instance
* @memberof Image
* @param {string} filter - can be `blur`, `emboss`, `sharpen`,
* `sobel`, or `gray`.
* @param {Function} callback - `function(err, img)`
* @example
* var img = new mapnik.Image(5, 5);
* img.filter('sobel', function(err, img) {
* if (err) throw err;
* // your custom `img` with sobel filter
* // https://en.wikipedia.org/wiki/Sobel_operator
* });
*/
NAN_METHOD(Image::filter)
{
if (info.Length() <= 1) {
info.GetReturnValue().Set(_filterSync(info));
return;
}
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
if (!info[0]->IsString())
{
Nan::ThrowTypeError("A string is expected for filter argument");
return;
}
filter_image_baton_t *closure = new filter_image_baton_t();
closure->filter = TOSTR(info[0]);
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length()-1];
closure->request.data = closure;
closure->im = im;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Filter, (uv_after_work_cb)EIO_AfterFilter);
im->Ref();
return;
}
void Image::EIO_Filter(uv_work_t* req)
{
filter_image_baton_t *closure = static_cast<filter_image_baton_t *>(req->data);
try
{
mapnik::filter::filter_image(*closure->im->this_,closure->filter);
}
catch(std::exception const& ex)
{
closure->error = true;
closure->error_name = ex.what();
}
}
void Image::EIO_AfterFilter(uv_work_t* req)
{
Nan::HandleScope scope;
filter_image_baton_t *closure = static_cast<filter_image_baton_t *>(req->data);
if (closure->error)
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else
{
v8::Local<v8::Value> argv[2] = { Nan::Null(), closure->im->handle() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->im->Unref();
closure->cb.Reset();
delete closure;
}
/**
* Fill this image with a given color. Changes all pixel values. (synchronous)
*
* @name fillSync
* @instance
* @memberof Image
* @param {mapnik.Color|number} color
* @example
* var img = new mapnik.Image(5,5);
* // blue pixels
* img.fillSync(new mapnik.Color('blue'));
* var colors = img.getPixel(0,0, {get_color: true});
* // blue value is filled
* console.log(colors.b); // 255
*/
NAN_METHOD(Image::fillSync)
{
info.GetReturnValue().Set(_fillSync(info));
}
v8::Local<v8::Value> Image::_fillSync(Nan::NAN_METHOD_ARGS_TYPE info) {
Nan::EscapableHandleScope scope;
if (info.Length() < 1 ) {
Nan::ThrowTypeError("expects one argument: Color object or a number");
return scope.Escape(Nan::Undefined());
}
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
try
{
if (info[0]->IsUint32())
{
std::uint32_t val = info[0]->Uint32Value();
mapnik::fill<std::uint32_t>(*im->this_,val);
}
else if (info[0]->IsInt32())
{
std::int32_t val = info[0]->Int32Value();
mapnik::fill<std::int32_t>(*im->this_,val);
}
else if (info[0]->IsNumber())
{
double val = info[0]->NumberValue();
mapnik::fill<double>(*im->this_,val);
}
else if (info[0]->IsObject())
{
v8::Local<v8::Object> obj = info[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Color::constructor)->HasInstance(obj))
{
Nan::ThrowTypeError("A numeric or color value is expected");
}
else
{
Color * color = Nan::ObjectWrap::Unwrap<Color>(obj);
mapnik::fill(*im->this_,*(color->get()));
}
}
else
{
Nan::ThrowTypeError("A numeric or color value is expected");
}
}
catch(std::exception const& ex)
{
Nan::ThrowError(ex.what());
}
return scope.Escape(Nan::Undefined());
}
enum fill_type : std::uint8_t
{
FILL_COLOR = 0,
FILL_UINT32,
FILL_INT32,
FILL_DOUBLE
};
typedef struct {
uv_work_t request;
Image* im;
fill_type type;
mapnik::color c;
std::uint32_t val_u32;
std::int32_t val_32;
double val_double;
//std::string format;
bool error;
std::string error_name;
Nan::Persistent<v8::Function> cb;
} fill_image_baton_t;
/**
* Fill this image with a given color. Changes all pixel values.
*
* @name fill
* @instance
* @memberof Image
* @param {mapnik.Color|number} color
* @param {Function} callback - `function(err, img)`
* @example
* var img = new mapnik.Image(5,5);
* img.fill(new mapnik.Color('blue'), function(err, img) {
* if (err) throw err;
* var colors = img.getPixel(0,0, {get_color: true});
* pixel is colored blue
* console.log(color.b); // 255
* });
*
* // or fill with rgb string
* img.fill('rgba(255,255,255,0)', function(err, img) { ... });
*/
NAN_METHOD(Image::fill)
{
if (info.Length() <= 1) {
info.GetReturnValue().Set(_fillSync(info));
return;
}
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
fill_image_baton_t *closure = new fill_image_baton_t();
if (info[0]->IsUint32())
{
closure->val_u32 = info[0]->Uint32Value();
closure->type = FILL_UINT32;
}
else if (info[0]->IsInt32())
{
closure->val_32 = info[0]->Int32Value();
closure->type = FILL_INT32;
}
else if (info[0]->IsNumber())
{
closure->val_double = info[0]->NumberValue();
closure->type = FILL_DOUBLE;
}
else if (info[0]->IsObject())
{
v8::Local<v8::Object> obj = info[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Color::constructor)->HasInstance(obj))
{
delete closure;
Nan::ThrowTypeError("A numeric or color value is expected");
return;
}
else
{
Color * color = Nan::ObjectWrap::Unwrap<Color>(obj);
closure->c = *(color->get());
}
}
else
{
delete closure;
Nan::ThrowTypeError("A numeric or color value is expected");
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length()-1];
if (!info[info.Length()-1]->IsFunction()) {
delete closure;
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
else
{
closure->request.data = closure;
closure->im = im;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Fill, (uv_after_work_cb)EIO_AfterFill);
im->Ref();
}
return;
}
void Image::EIO_Fill(uv_work_t* req)
{
fill_image_baton_t *closure = static_cast<fill_image_baton_t *>(req->data);
try
{
switch (closure->type)
{
case FILL_UINT32:
mapnik::fill(*closure->im->this_, closure->val_u32);
break;
case FILL_INT32:
mapnik::fill(*closure->im->this_, closure->val_32);
break;
default:
case FILL_DOUBLE:
mapnik::fill(*closure->im->this_, closure->val_double);
break;
case FILL_COLOR:
mapnik::fill(*closure->im->this_,closure->c);
break;
}
}
catch(std::exception const& ex)
{
closure->error = true;
closure->error_name = ex.what();
}
}
void Image::EIO_AfterFill(uv_work_t* req)
{
Nan::HandleScope scope;
fill_image_baton_t *closure = static_cast<fill_image_baton_t *>(req->data);
if (closure->error)
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else
{
v8::Local<v8::Value> argv[2] = { Nan::Null(), closure->im->handle() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->im->Unref();
closure->cb.Reset();
delete closure;
}
/**
* Make this image transparent. (synchronous)
*
* @name clearSync
* @instance
* @memberof Image
* @example
* var img = new mapnik.Image(5,5);
* img.fillSync(1);
* console.log(img.getPixel(0, 0)); // 1
* img.clearSync();
* console.log(img.getPixel(0, 0)); // 0
*/
NAN_METHOD(Image::clearSync)
{
info.GetReturnValue().Set(_clearSync(info));
}
v8::Local<v8::Value> Image::_clearSync(Nan::NAN_METHOD_ARGS_TYPE info) {
Nan::EscapableHandleScope scope;
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
try
{
mapnik::fill(*im->this_, 0);
}
catch(std::exception const& ex)
{
Nan::ThrowError(ex.what());
}
return scope.Escape(Nan::Undefined());
}
typedef struct {
uv_work_t request;
Image* im;
//std::string format;
bool error;
std::string error_name;
Nan::Persistent<v8::Function> cb;
} clear_image_baton_t;
/**
* Make this image transparent, removing all image data from it.
*
* @name clear
* @instance
* @param {Function} callback
* @memberof Image
* @example
* var img = new mapnik.Image(5,5);
* img.fillSync(1);
* console.log(img.getPixel(0, 0)); // 1
* img.clear(function(err, result) {
* console.log(result.getPixel(0,0)); // 0
* });
*/
NAN_METHOD(Image::clear)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
if (info.Length() == 0) {
info.GetReturnValue().Set(_clearSync(info));
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length()-1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
clear_image_baton_t *closure = new clear_image_baton_t();
closure->request.data = closure;
closure->im = im;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Clear, (uv_after_work_cb)EIO_AfterClear);
im->Ref();
return;
}
void Image::EIO_Clear(uv_work_t* req)
{
clear_image_baton_t *closure = static_cast<clear_image_baton_t *>(req->data);
try
{
mapnik::fill(*closure->im->this_, 0);
}
catch(std::exception const& ex)
{
closure->error = true;
closure->error_name = ex.what();
}
}
void Image::EIO_AfterClear(uv_work_t* req)
{
Nan::HandleScope scope;
clear_image_baton_t *closure = static_cast<clear_image_baton_t *>(req->data);
if (closure->error)
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else
{
v8::Local<v8::Value> argv[2] = { Nan::Null(), closure->im->handle() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->im->Unref();
closure->cb.Reset();
delete closure;
}
/**
* Convert all grayscale values to alpha values. Great for creating
* a mask layer based on alpha values.
*
* @name setGrayScaleToAlpha
* @memberof Image
* @instance
* @param {mapnik.Color} color
* @example
* var image = new mapnik.Image(2,2);
* image.fillSync(new mapnik.Color('rgba(0,0,0,255)'));
* console.log(image.getPixel(0,0, {get_color:true})); // { premultiplied: false, a: 255, b: 0, g: 0, r: 0 }
*
* image.setGrayScaleToAlpha();
* // turns a black pixel into a completely transparent mask
* console.log(image.getPixel(0,0, {get_color:true})); // { premultiplied: false, a: 0, b: 255, g: 255, r: 255 }
*/
NAN_METHOD(Image::setGrayScaleToAlpha)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
if (info.Length() == 0) {
mapnik::set_grayscale_to_alpha(*im->this_);
} else {
if (!info[0]->IsObject()) {
Nan::ThrowTypeError("optional first arg must be a mapnik.Color");
return;
}
v8::Local<v8::Object> obj = info[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Color::constructor)->HasInstance(obj)) {
Nan::ThrowTypeError("mapnik.Color expected as first arg");
return;
}
Color * color = Nan::ObjectWrap::Unwrap<Color>(obj);
mapnik::set_grayscale_to_alpha(*im->this_, *color->get());
}
return;
}
typedef struct {
uv_work_t request;
Image* im;
Nan::Persistent<v8::Function> cb;
} image_op_baton_t;
/**
* Determine whether the given image is premultiplied.
* https://en.wikipedia.org/wiki/Alpha_compositing
*
* @name premultiplied
* @memberof Image
* @instance
* @returns {boolean} premultiplied `true` if the image is premultiplied
* @example
* var img = new mapnik.Image(5,5);
* console.log(img.premultiplied()); // false
* img.premultiplySync()
* console.log(img.premultiplied()); // true
*/
NAN_METHOD(Image::premultiplied)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
bool premultiplied = im->this_->get_premultiplied();
info.GetReturnValue().Set(Nan::New<v8::Boolean>(premultiplied));
}
/**
* Premultiply the pixels in this image.
*
* @name premultiplySync
* @instance
* @memberof Image
* @example
* var img = new mapnik.Image(5,5);
* img.premultiplySync();
* console.log(img.premultiplied()); // true
*/
NAN_METHOD(Image::premultiplySync)
{
info.GetReturnValue().Set(_premultiplySync(info));
}
v8::Local<v8::Value> Image::_premultiplySync(Nan::NAN_METHOD_ARGS_TYPE info) {
Nan::EscapableHandleScope scope;
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
mapnik::premultiply_alpha(*im->this_);
return scope.Escape(Nan::Undefined());
}
/**
* Premultiply the pixels in this image, asynchronously
*
* @name premultiply
* @memberof Image
* @instance
* @param {Function} callback
* @example
* var img = new mapnik.Image(5,5);
* img.premultiply(function(err, img) {
* if (err) throw err;
* // your custom code with premultiplied img
* })
*/
NAN_METHOD(Image::premultiply)
{
if (info.Length() == 0) {
info.GetReturnValue().Set(_premultiplySync(info));
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length()-1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
image_op_baton_t *closure = new image_op_baton_t();
closure->request.data = closure;
closure->im = im;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Premultiply, (uv_after_work_cb)EIO_AfterMultiply);
im->Ref();
return;
}
void Image::EIO_Premultiply(uv_work_t* req)
{
image_op_baton_t *closure = static_cast<image_op_baton_t *>(req->data);
mapnik::premultiply_alpha(*closure->im->this_);
}
void Image::EIO_AfterMultiply(uv_work_t* req)
{
Nan::HandleScope scope;
image_op_baton_t *closure = static_cast<image_op_baton_t *>(req->data);
v8::Local<v8::Value> argv[2] = { Nan::Null(), closure->im->handle() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
closure->im->Unref();
closure->cb.Reset();
delete closure;
}
/**
* Demultiply the pixels in this image. The opposite of
* premultiplying.
*
* @name demultiplySync
* @instance
* @memberof Image
*/
NAN_METHOD(Image::demultiplySync)
{
Nan::HandleScope scope;
info.GetReturnValue().Set(_demultiplySync(info));
}
v8::Local<v8::Value> Image::_demultiplySync(Nan::NAN_METHOD_ARGS_TYPE info) {
Nan::EscapableHandleScope scope;
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
mapnik::demultiply_alpha(*im->this_);
return scope.Escape(Nan::Undefined());
}
/**
* Demultiply the pixels in this image, asynchronously. The opposite of
* premultiplying
*
* @name demultiply
* @param {Function} callback
* @instance
* @memberof Image
*/
NAN_METHOD(Image::demultiply)
{
if (info.Length() == 0) {
info.GetReturnValue().Set(_demultiplySync(info));
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length()-1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
image_op_baton_t *closure = new image_op_baton_t();
closure->request.data = closure;
closure->im = im;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Demultiply, (uv_after_work_cb)EIO_AfterMultiply);
im->Ref();
return;
}
void Image::EIO_Demultiply(uv_work_t* req)
{
image_op_baton_t *closure = static_cast<image_op_baton_t *>(req->data);
mapnik::demultiply_alpha(*closure->im->this_);
}
typedef struct {
uv_work_t request;
Image* im;
Nan::Persistent<v8::Function> cb;
bool error;
std::string error_name;
bool result;
} is_solid_image_baton_t;
/**
* Test if an image's pixels are all exactly the same
* @name isSolid
* @memberof Image
* @instance
* @returns {boolean} `true` means all pixels are exactly the same
* @example
* var img = new mapnik.Image(2,2);
* console.log(img.isSolid()); // true
*
* // change a pixel
* img.setPixel(0,0, new mapnik.Color('green'));
* console.log(img.isSolid()); // false
*/
NAN_METHOD(Image::isSolid)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
if (info.Length() == 0) {
info.GetReturnValue().Set(_isSolidSync(info));
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length() - 1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
is_solid_image_baton_t *closure = new is_solid_image_baton_t();
closure->request.data = closure;
closure->im = im;
closure->result = true;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_IsSolid, (uv_after_work_cb)EIO_AfterIsSolid);
im->Ref();
return;
}
void Image::EIO_IsSolid(uv_work_t* req)
{
is_solid_image_baton_t *closure = static_cast<is_solid_image_baton_t *>(req->data);
if (closure->im->this_->width() > 0 && closure->im->this_->height() > 0)
{
closure->result = mapnik::is_solid(*(closure->im->this_));
}
else
{
closure->error = true;
closure->error_name = "image does not have valid dimensions";
}
}
void Image::EIO_AfterIsSolid(uv_work_t* req)
{
Nan::HandleScope scope;
is_solid_image_baton_t *closure = static_cast<is_solid_image_baton_t *>(req->data);
if (closure->error) {
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else
{
if (closure->result)
{
v8::Local<v8::Value> argv[3] = { Nan::Null(),
Nan::New(closure->result),
mapnik::util::apply_visitor(visitor_get_pixel(0,0),*(closure->im->this_)),
};
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 3, argv);
}
else
{
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::New(closure->result) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
}
closure->im->Unref();
closure->cb.Reset();
delete closure;
}
/**
* Determine whether the image is solid - whether it has alpha values of greater
* than one.
*
* @name isSolidSync
* @returns {boolean} whether the image is solid
* @instance
* @memberof Image
* @example
* var img = new mapnik.Image(256, 256);
* var view = img.view(0, 0, 256, 256);
* console.log(view.isSolidSync()); // true
*/
NAN_METHOD(Image::isSolidSync)
{
info.GetReturnValue().Set(_isSolidSync(info));
}
v8::Local<v8::Value> Image::_isSolidSync(Nan::NAN_METHOD_ARGS_TYPE info)
{
Nan::EscapableHandleScope scope;
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
if (im->this_->width() > 0 && im->this_->height() > 0)
{
return scope.Escape(Nan::New<v8::Boolean>(mapnik::is_solid(*(im->this_))));
}
Nan::ThrowError("image does not have valid dimensions");
return scope.Escape(Nan::Undefined());
}
typedef struct {
uv_work_t request;
Image* im1;
image_ptr im2;
mapnik::image_dtype type;
double offset;
double scaling;
Nan::Persistent<v8::Function> cb;
bool error;
std::string error_name;
} copy_image_baton_t;
/**
* Copy an image into a new image by creating a clone
* @name copy
* @instance
* @memberof Image
* @param {number} type - image type to clone into, can be any mapnik.imageType number
* @param {Object} [options={}]
* @param {number} [options.scaling] - scale the image
* @param {number} [options.offset] - offset this image
* @param {Function} callback
* @example
* var img = new mapnik.Image(4, 4, {type: mapnik.imageType.gray16});
* var img2 = img.copy(mapnik.imageType.gray8, function(err, img2) {
* if (err) throw err;
* // custom code with `img2` converted into gray8 type
* });
*/
NAN_METHOD(Image::copy)
{
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length() - 1];
if (!info[info.Length()-1]->IsFunction()) {
info.GetReturnValue().Set(_copySync(info));
return;
}
Image* im1 = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
double offset = 0.0;
bool scaling_or_offset_set = false;
double scaling = 1.0;
mapnik::image_dtype type = im1->this_->get_dtype();
v8::Local<v8::Object> options = Nan::New<v8::Object>();
if (info.Length() >= 2)
{
if (info[0]->IsNumber())
{
type = static_cast<mapnik::image_dtype>(info[0]->IntegerValue());
if (type >= mapnik::image_dtype::IMAGE_DTYPE_MAX)
{
Nan::ThrowTypeError("Image 'type' must be a valid image type");
return;
}
}
else if (info[0]->IsObject())
{
options = info[0]->ToObject();
}
else
{
Nan::ThrowTypeError("Unknown parameters passed");
return;
}
}
if (info.Length() >= 3)
{
if (info[1]->IsObject())
{
options = info[1]->ToObject();
}
else
{
Nan::ThrowTypeError("Expected options object as second argument");
return;
}
}
if (options->Has(Nan::New("scaling").ToLocalChecked()))
{
v8::Local<v8::Value> scaling_val = options->Get(Nan::New("scaling").ToLocalChecked());
if (scaling_val->IsNumber())
{
scaling = scaling_val->NumberValue();
scaling_or_offset_set = true;
}
else
{
Nan::ThrowTypeError("scaling argument must be a number");
return;
}
}
if (options->Has(Nan::New("offset").ToLocalChecked()))
{
v8::Local<v8::Value> offset_val = options->Get(Nan::New("offset").ToLocalChecked());
if (offset_val->IsNumber())
{
offset = offset_val->NumberValue();
scaling_or_offset_set = true;
}
else
{
Nan::ThrowTypeError("offset argument must be a number");
return;
}
}
if (!scaling_or_offset_set && type == im1->this_->get_dtype())
{
scaling = im1->this_->get_scaling();
offset = im1->this_->get_offset();
}
copy_image_baton_t *closure = new copy_image_baton_t();
closure->request.data = closure;
closure->im1 = im1;
closure->offset = offset;
closure->scaling = scaling;
closure->type = type;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Copy, (uv_after_work_cb)EIO_AfterCopy);
closure->im1->Ref();
return;
}
void Image::EIO_Copy(uv_work_t* req)
{
copy_image_baton_t *closure = static_cast<copy_image_baton_t *>(req->data);
try
{
closure->im2 = std::make_shared<mapnik::image_any>(
mapnik::image_copy(*(closure->im1->this_),
closure->type,
closure->offset,
closure->scaling)
);
}
catch (std::exception const& ex)
{
closure->error = true;
closure->error_name = ex.what();
}
}
void Image::EIO_AfterCopy(uv_work_t* req)
{
Nan::HandleScope scope;
copy_image_baton_t *closure = static_cast<copy_image_baton_t *>(req->data);
if (closure->error)
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else if (!closure->im2)
{
// Not quite sure if this is even required or ever can be reached, but leaving it
// and simply removing it from coverage tests.
/* LCOV_EXCL_START */
v8::Local<v8::Value> argv[1] = { Nan::Error("could not render to image") };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
/* LCOV_EXCL_STOP */
}
else
{
Image* im = new Image(closure->im2);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->im1->Unref();
closure->cb.Reset();
delete closure;
}
/**
* Copy an image into a new image by creating a clone
* @name copySync
* @instance
* @memberof Image
* @param {number} type - image type to clone into, can be any mapnik.imageType number
* @param {Object} [options={}]
* @param {number} [options.scaling] - scale the image
* @param {number} [options.offset] - offset this image
* @returns {mapnik.Image} copy
* @example
* var img = new mapnik.Image(4, 4, {type: mapnik.imageType.gray16});
* var img2 = img.copy(mapnik.imageType.gray8);
* // custom code with `img2` as a gray8 type
*/
NAN_METHOD(Image::copySync)
{
info.GetReturnValue().Set(_copySync(info));
}
v8::Local<v8::Value> Image::_copySync(Nan::NAN_METHOD_ARGS_TYPE info)
{
Nan::EscapableHandleScope scope;
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
double offset = 0.0;
bool scaling_or_offset_set = false;
double scaling = 1.0;
mapnik::image_dtype type = im->this_->get_dtype();
v8::Local<v8::Object> options = Nan::New<v8::Object>();
if (info.Length() >= 1)
{
if (info[0]->IsNumber())
{
type = static_cast<mapnik::image_dtype>(info[0]->IntegerValue());
if (type >= mapnik::image_dtype::IMAGE_DTYPE_MAX)
{
Nan::ThrowTypeError("Image 'type' must be a valid image type");
return scope.Escape(Nan::Undefined());
}
}
else if (info[0]->IsObject())
{
options = info[0]->ToObject();
}
else
{
Nan::ThrowTypeError("Unknown parameters passed");
return scope.Escape(Nan::Undefined());
}
}
if (info.Length() >= 2)
{
if (info[1]->IsObject())
{
options = info[1]->ToObject();
}
else
{
Nan::ThrowTypeError("Expected options object as second argument");
return scope.Escape(Nan::Undefined());
}
}
if (options->Has(Nan::New("scaling").ToLocalChecked()))
{
v8::Local<v8::Value> scaling_val = options->Get(Nan::New("scaling").ToLocalChecked());
if (scaling_val->IsNumber())
{
scaling = scaling_val->NumberValue();
scaling_or_offset_set = true;
}
else
{
Nan::ThrowTypeError("scaling argument must be a number");
return scope.Escape(Nan::Undefined());
}
}
if (options->Has(Nan::New("offset").ToLocalChecked()))
{
v8::Local<v8::Value> offset_val = options->Get(Nan::New("offset").ToLocalChecked());
if (offset_val->IsNumber())
{
offset = offset_val->NumberValue();
scaling_or_offset_set = true;
}
else
{
Nan::ThrowTypeError("offset argument must be a number");
return scope.Escape(Nan::Undefined());
}
}
if (!scaling_or_offset_set && type == im->this_->get_dtype())
{
scaling = im->this_->get_scaling();
offset = im->this_->get_offset();
}
try
{
image_ptr imagep = std::make_shared<mapnik::image_any>(
mapnik::image_copy(*(im->this_),
type,
offset,
scaling)
);
Image* new_im = new Image(imagep);
v8::Local<v8::Value> ext = Nan::New<v8::External>(new_im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
return scope.Escape(maybe_local.ToLocalChecked());
}
catch (std::exception const& ex)
{
Nan::ThrowError(ex.what());
return scope.Escape(Nan::Undefined());
}
}
typedef struct {
uv_work_t request;
Image* im1;
image_ptr im2;
mapnik::scaling_method_e scaling_method;
std::size_t size_x;
std::size_t size_y;
int offset_x;
int offset_y;
double filter_factor;
Nan::Persistent<v8::Function> cb;
bool error;
std::string error_name;
} resize_image_baton_t;
/**
* Resize this image (makes a copy)
*
* @name resize
* @instance
* @memberof Image
* @param {number} width - in pixels
* @param {number} height - in pixels
* @param {Object} [options={}]
* @param {number} [options.offset_x=0] - offset the image horizontally in pixels
* @param {number} [options.offset_y=0] - offset the image vertically in pixels
* @param {mapnik.imageScaling} [options.scaling_method=mapnik.imageScaling.near] - scaling method
* @param {number} [options.filter_factor=1.0]
* @param {Function} callback - `function(err, result)`
* @example
* var img = new mapnik.Image(4, 4, {type: mapnik.imageType.gray8});
* img.resize(8, 8, function(err, result) {
* if (err) throw err;
* // new image object as `result`
* });
*/
NAN_METHOD(Image::resize)
{
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length() - 1];
if (!info[info.Length()-1]->IsFunction()) {
info.GetReturnValue().Set(_resizeSync(info));
return;
}
Image* im1 = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
std::size_t width = 0;
std::size_t height = 0;
int offset_x = 0;
int offset_y = 0;
double filter_factor = 1.0;
mapnik::scaling_method_e scaling_method = mapnik::SCALING_NEAR;
v8::Local<v8::Object> options = Nan::New<v8::Object>();
if (info.Length() >= 3)
{
if (info[0]->IsNumber())
{
auto width_tmp = info[0]->IntegerValue();
if (width_tmp <= 0)
{
Nan::ThrowTypeError("Width must be a integer greater then zero");
return;
}
width = static_cast<std::size_t>(width_tmp);
}
else
{
Nan::ThrowTypeError("Width must be a number");
return;
}
if (info[1]->IsNumber())
{
auto height_tmp = info[1]->IntegerValue();
if (height_tmp <= 0)
{
Nan::ThrowTypeError("Height must be a integer greater then zero");
return;
}
height = static_cast<std::size_t>(height_tmp);
}
else
{
Nan::ThrowTypeError("Height must be a number");
return;
}
}
else
{
Nan::ThrowTypeError("resize requires a width and height parameter.");
return;
}
if (info.Length() >= 4)
{
if (info[2]->IsObject())
{
options = info[2]->ToObject();
}
else
{
Nan::ThrowTypeError("Expected options object as third argument");
return;
}
}
if (options->Has(Nan::New("offset_x").ToLocalChecked()))
{
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("offset_x").ToLocalChecked());
if (!bind_opt->IsNumber())
{
Nan::ThrowTypeError("optional arg 'offset_x' must be a number");
return;
}
offset_x = bind_opt->IntegerValue();
}
if (options->Has(Nan::New("offset_y").ToLocalChecked()))
{
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("offset_y").ToLocalChecked());
if (!bind_opt->IsNumber())
{
Nan::ThrowTypeError("optional arg 'offset_y' must be a number");
return;
}
offset_y = bind_opt->IntegerValue();
}
if (options->Has(Nan::New("scaling_method").ToLocalChecked()))
{
v8::Local<v8::Value> scaling_val = options->Get(Nan::New("scaling_method").ToLocalChecked());
if (scaling_val->IsNumber())
{
std::int64_t scaling_int = scaling_val->IntegerValue();
if (scaling_int > mapnik::SCALING_BLACKMAN || scaling_int < 0)
{
Nan::ThrowTypeError("Invalid scaling_method");
return;
}
scaling_method = static_cast<mapnik::scaling_method_e>(scaling_int);
}
else
{
Nan::ThrowTypeError("scaling_method argument must be an integer");
return;
}
}
if (options->Has(Nan::New("filter_factor").ToLocalChecked()))
{
v8::Local<v8::Value> ff_val = options->Get(Nan::New("filter_factor").ToLocalChecked());
if (ff_val->IsNumber())
{
filter_factor = ff_val->NumberValue();
}
else
{
Nan::ThrowTypeError("filter_factor argument must be a number");
return;
}
}
resize_image_baton_t *closure = new resize_image_baton_t();
closure->request.data = closure;
closure->im1 = im1;
closure->scaling_method = scaling_method;
closure->size_x = width;
closure->size_y = height;
closure->offset_x = offset_x;
closure->offset_y = offset_y;
closure->filter_factor = filter_factor;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Resize, (uv_after_work_cb)EIO_AfterResize);
closure->im1->Ref();
return;
}
struct resize_visitor
{
resize_visitor(mapnik::image_any const& im1,
mapnik::scaling_method_e scaling_method,
double image_ratio_x,
double image_ratio_y,
double filter_factor,
int offset_x,
int offset_y) :
im1_(im1),
scaling_method_(scaling_method),
image_ratio_x_(image_ratio_x),
image_ratio_y_(image_ratio_y),
filter_factor_(filter_factor),
offset_x_(offset_x),
offset_y_(offset_y) {}
void operator()(mapnik::image_rgba8 & im2) const
{
if (!im1_.get_premultiplied())
{
throw std::runtime_error("RGBA8 images must be premultiplied prior to using resize");
}
mapnik::scale_image_agg(im2,
mapnik::util::get<mapnik::image_rgba8>(im1_),
scaling_method_,
image_ratio_x_,
image_ratio_y_,
offset_x_,
offset_y_,
filter_factor_);
}
template <typename T>
void operator()(T & im2) const
{
mapnik::scale_image_agg(im2,
mapnik::util::get<T>(im1_),
scaling_method_,
image_ratio_x_,
image_ratio_y_,
offset_x_,
offset_y_,
filter_factor_);
}
void operator()(mapnik::image_null &) const
{
// Should be caught earlier so no test coverage should reach here.
/* LCOV_EXCL_START */
throw std::runtime_error("Can not resize null images");
/* LCOV_EXCL_STOP */
}
void operator()(mapnik::image_gray8s &) const
{
throw std::runtime_error("Mapnik currently does not support resizing signed 8 bit integer rasters");
}
void operator()(mapnik::image_gray16s &) const
{
throw std::runtime_error("Mapnik currently does not support resizing signed 16 bit integer rasters");
}
void operator()(mapnik::image_gray32 &) const
{
throw std::runtime_error("Mapnik currently does not support resizing unsigned 32 bit integer rasters");
}
void operator()(mapnik::image_gray32s &) const
{
throw std::runtime_error("Mapnik currently does not support resizing signed 32 bit integer rasters");
}
void operator()(mapnik::image_gray64 &) const
{
throw std::runtime_error("Mapnik currently does not support resizing unsigned 64 bit integer rasters");
}
void operator()(mapnik::image_gray64s &) const
{
throw std::runtime_error("Mapnik currently does not support resizing signed 64 bit integer rasters");
}
void operator()(mapnik::image_gray64f &) const
{
throw std::runtime_error("Mapnik currently does not support resizing 64 bit floating point rasters");
}
private:
mapnik::image_any const & im1_;
mapnik::scaling_method_e scaling_method_;
double image_ratio_x_;
double image_ratio_y_;
double filter_factor_;
int offset_x_;
int offset_y_;
};
void Image::EIO_Resize(uv_work_t* req)
{
resize_image_baton_t *closure = static_cast<resize_image_baton_t *>(req->data);
if (closure->im1->this_->is<mapnik::image_null>())
{
closure->error = true;
closure->error_name = "Can not resize a null image.";
return;
}
try
{
double offset = closure->im1->this_->get_offset();
double scaling = closure->im1->this_->get_scaling();
closure->im2 = std::make_shared<mapnik::image_any>(closure->size_x,
closure->size_y,
closure->im1->this_->get_dtype(),
true,
true,
false);
closure->im2->set_offset(offset);
closure->im2->set_scaling(scaling);
int im_width = closure->im1->this_->width();
int im_height = closure->im1->this_->height();
if (im_width <= 0 || im_height <= 0)
{
closure->error = true;
closure->error_name = "Image width or height is zero or less then zero.";
return;
}
double image_ratio_x = static_cast<double>(closure->size_x) / im_width;
double image_ratio_y = static_cast<double>(closure->size_y) / im_height;
resize_visitor visit(*(closure->im1->this_),
closure->scaling_method,
image_ratio_x,
image_ratio_y,
closure->filter_factor,
closure->offset_x,
closure->offset_y);
mapnik::util::apply_visitor(visit, *(closure->im2));
}
catch (std::exception const& ex)
{
closure->error = true;
closure->error_name = ex.what();
}
}
void Image::EIO_AfterResize(uv_work_t* req)
{
Nan::HandleScope scope;
resize_image_baton_t *closure = static_cast<resize_image_baton_t *>(req->data);
if (closure->error)
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else
{
Image* im = new Image(closure->im2);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->im1->Unref();
closure->cb.Reset();
delete closure;
}
/**
* Resize this image (makes a copy). Synchronous version of {@link mapnik.Image.resize}.
*
* @name resizeSync
* @instance
* @memberof Image
* @param {number} width
* @param {number} height
* @param {Object} [options={}]
* @param {number} [options.offset_x=0] - offset the image horizontally in pixels
* @param {number} [options.offset_y=0] - offset the image vertically in pixels
* @param {mapnik.imageScaling} [options.scaling_method=mapnik.imageScaling.near] - scaling method
* @param {number} [options.filter_factor=1.0]
* @returns {mapnik.Image} copy
* @example
* var img = new mapnik.Image(4, 4, {type: mapnik.imageType.gray8});
* var img2 = img.resizeSync(8, 8);
* // new copy as `img2`
*/
NAN_METHOD(Image::resizeSync)
{
info.GetReturnValue().Set(_resizeSync(info));
}
v8::Local<v8::Value> Image::_resizeSync(Nan::NAN_METHOD_ARGS_TYPE info)
{
Nan::EscapableHandleScope scope;
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
std::size_t width = 0;
std::size_t height = 0;
double filter_factor = 1.0;
int offset_x = 0;
int offset_y = 0;
mapnik::scaling_method_e scaling_method = mapnik::SCALING_NEAR;
v8::Local<v8::Object> options = Nan::New<v8::Object>();
if (info.Length() >= 2)
{
if (info[0]->IsNumber())
{
int width_tmp = info[0]->IntegerValue();
if (width_tmp <= 0)
{
Nan::ThrowTypeError("Width parameter must be an integer greater then zero");
return scope.Escape(Nan::Undefined());
}
width = static_cast<std::size_t>(width_tmp);
}
else
{
Nan::ThrowTypeError("Width must be a number");
return scope.Escape(Nan::Undefined());
}
if (info[1]->IsNumber())
{
int height_tmp = info[1]->IntegerValue();
if (height_tmp <= 0)
{
Nan::ThrowTypeError("Height parameter must be an integer greater then zero");
return scope.Escape(Nan::Undefined());
}
height = static_cast<std::size_t>(height_tmp);
}
else
{
Nan::ThrowTypeError("Height must be a number");
return scope.Escape(Nan::Undefined());
}
}
else
{
Nan::ThrowTypeError("Resize requires at least a width and height parameter");
return scope.Escape(Nan::Undefined());
}
if (info.Length() >= 3)
{
if (info[2]->IsObject())
{
options = info[2]->ToObject();
}
else
{
Nan::ThrowTypeError("Expected options object as third argument");
return scope.Escape(Nan::Undefined());
}
}
if (options->Has(Nan::New("offset_x").ToLocalChecked()))
{
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("offset_x").ToLocalChecked());
if (!bind_opt->IsNumber())
{
Nan::ThrowTypeError("optional arg 'offset_x' must be a number");
return scope.Escape(Nan::Undefined());
}
offset_x = bind_opt->IntegerValue();
}
if (options->Has(Nan::New("offset_y").ToLocalChecked()))
{
v8::Local<v8::Value> bind_opt = options->Get(Nan::New("offset_y").ToLocalChecked());
if (!bind_opt->IsNumber())
{
Nan::ThrowTypeError("optional arg 'offset_y' must be a number");
return scope.Escape(Nan::Undefined());
}
offset_y = bind_opt->IntegerValue();
}
if (options->Has(Nan::New("scaling_method").ToLocalChecked()))
{
v8::Local<v8::Value> scaling_val = options->Get(Nan::New("scaling_method").ToLocalChecked());
if (scaling_val->IsNumber())
{
std::int64_t scaling_int = scaling_val->IntegerValue();
if (scaling_int > mapnik::SCALING_BLACKMAN || scaling_int < 0)
{
Nan::ThrowTypeError("Invalid scaling_method");
return scope.Escape(Nan::Undefined());
}
scaling_method = static_cast<mapnik::scaling_method_e>(scaling_int);
}
else
{
Nan::ThrowTypeError("scaling_method argument must be a number");
return scope.Escape(Nan::Undefined());
}
}
if (options->Has(Nan::New("filter_factor").ToLocalChecked()))
{
v8::Local<v8::Value> ff_val = options->Get(Nan::New("filter_factor").ToLocalChecked());
if (ff_val->IsNumber())
{
filter_factor = ff_val->NumberValue();
}
else
{
Nan::ThrowTypeError("filter_factor argument must be a number");
return scope.Escape(Nan::Undefined());
}
}
if (im->this_->is<mapnik::image_null>())
{
Nan::ThrowTypeError("Can not resize a null image");
return scope.Escape(Nan::Undefined());
}
int im_width = im->this_->width();
int im_height = im->this_->height();
if (im_width <= 0 || im_height <= 0)
{
Nan::ThrowTypeError("Image width or height is zero or less then zero.");
return scope.Escape(Nan::Undefined());
}
try
{
double offset = im->this_->get_offset();
double scaling = im->this_->get_scaling();
image_ptr imagep = std::make_shared<mapnik::image_any>(width,
height,
im->this_->get_dtype(),
true,
true,
false);
imagep->set_offset(offset);
imagep->set_scaling(scaling);
double image_ratio_x = static_cast<double>(width) / im_width;
double image_ratio_y = static_cast<double>(height) / im_height;
resize_visitor visit(*(im->this_),
scaling_method,
image_ratio_x,
image_ratio_y,
filter_factor,
offset_x,
offset_y);
mapnik::util::apply_visitor(visit, *imagep);
Image* new_im = new Image(imagep);
v8::Local<v8::Value> ext = Nan::New<v8::External>(new_im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
return scope.Escape(maybe_local.ToLocalChecked());
}
catch (std::exception const& ex)
{
Nan::ThrowError(ex.what());
return scope.Escape(Nan::Undefined());
}
}
/**
* Check if this image is painted. "Painted" refers to if it has
* data or not. An image created with `new mapnik.Image(4,4)` defaults to
* `false` since we loaded a new image without rendering and have no idea
* if it was painted or not. You can run `new mapnik.Image(4, 4, {painted: true})`
* to manually set the `painted` value.
*
* @name painted
* @instance
* @memberof Image
* @returns {boolean} whether it is painted or not
* @example
* var img = new mapnik.Image(5,5);
* console.log(img.painted()); // false
*/
NAN_METHOD(Image::painted)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
info.GetReturnValue().Set(Nan::New<v8::Boolean>(im->this_->painted()));
}
/**
* Get this image's width in pixels
*
* @name width
* @instance
* @memberof Image
* @returns {number} width
* @example
* var img = new mapnik.Image(4,4);
* console.log(img.width()); // 4
*/
NAN_METHOD(Image::width)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
info.GetReturnValue().Set(Nan::New<v8::Int32>(static_cast<std::int32_t>(im->this_->width())));
}
/**
* Get this image's height in pixels
*
* @name height
* @instance
* @memberof Image
* @returns {number} height
* @example
* var img = new mapnik.Image(4,4);
* console.log(img.height()); // 4
*/
NAN_METHOD(Image::height)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
info.GetReturnValue().Set(Nan::New<v8::Int32>(static_cast<std::int32_t>(im->this_->height())));
}
/**
* Load in a pre-existing image as an image object
* @name openSync
* @memberof Image
* @instance
* @param {string} path - path to the image you want to load
* @returns {mapnik.Image} new image object based on existing image
* @example
* var img = new mapnik.Image.open('./path/to/image.jpg');
*/
NAN_METHOD(Image::openSync)
{
info.GetReturnValue().Set(_openSync(info));
}
v8::Local<v8::Value> Image::_openSync(Nan::NAN_METHOD_ARGS_TYPE info)
{
Nan::EscapableHandleScope scope;
if (info.Length() < 1) {
Nan::ThrowError("must provide a string argument");
return scope.Escape(Nan::Undefined());
}
if (!info[0]->IsString()) {
Nan::ThrowTypeError("Argument must be a string");
return scope.Escape(Nan::Undefined());
}
try
{
std::string filename = TOSTR(info[0]);
boost::optional<std::string> type = mapnik::type_from_filename(filename);
if (type)
{
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(filename,*type));
if (reader.get())
{
image_ptr imagep = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(), reader->height()));
if (!reader->has_alpha())
{
mapnik::set_premultiplied_alpha(*imagep, true);
}
Image* im = new Image(imagep);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
return scope.Escape(maybe_local.ToLocalChecked());
}
}
Nan::ThrowTypeError(("Unsupported image format:" + filename).c_str());
return scope.Escape(Nan::Undefined());
}
catch (std::exception const& ex)
{
Nan::ThrowError(ex.what());
return scope.Escape(Nan::Undefined());
}
}
typedef struct {
uv_work_t request;
image_ptr im;
std::string error_name;
Nan::Persistent<v8::Object> buffer;
Nan::Persistent<v8::Function> cb;
bool premultiply;
std::uint32_t max_size;
const char *data;
size_t dataLength;
bool error;
} image_mem_ptr_baton_t;
typedef struct {
uv_work_t request;
image_ptr im;
std::string filename;
bool error;
std::string error_name;
Nan::Persistent<v8::Function> cb;
} image_file_ptr_baton_t;
/**
* Load in a pre-existing image as an image object
* @name open
* @memberof Image
* @static
* @param {string} path - path to the image you want to load
* @param {Function} callback -
* @example
* mapnik.Image.open('./path/to/image.jpg', function(err, img) {
* if (err) throw err;
* // img is now an Image object
* });
*/
NAN_METHOD(Image::open)
{
if (info.Length() == 1) {
info.GetReturnValue().Set(_openSync(info));
return;
}
if (info.Length() < 2) {
Nan::ThrowError("must provide a string argument");
return;
}
if (!info[0]->IsString()) {
Nan::ThrowTypeError("Argument must be a string");
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length()-1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
image_file_ptr_baton_t *closure = new image_file_ptr_baton_t();
closure->request.data = closure;
closure->filename = TOSTR(info[0]);
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Open, (uv_after_work_cb)EIO_AfterOpen);
return;
}
void Image::EIO_Open(uv_work_t* req)
{
image_file_ptr_baton_t *closure = static_cast<image_file_ptr_baton_t *>(req->data);
try
{
boost::optional<std::string> type = mapnik::type_from_filename(closure->filename);
if (!type)
{
closure->error = true;
closure->error_name = "Unsupported image format: " + closure->filename;
}
else
{
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(closure->filename,*type));
if (reader.get())
{
closure->im = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(),reader->height()));
if (!reader->has_alpha())
{
mapnik::set_premultiplied_alpha(*(closure->im), true);
}
}
else
{
// The only way this is ever reached is if the reader factory in
// mapnik was not providing an image type it should. This should never
// be occuring so marking this out from coverage
/* LCOV_EXCL_START */
closure->error = true;
closure->error_name = "Failed to load: " + closure->filename;
/* LCOV_EXCL_STOP */
}
}
}
catch (std::exception const& ex)
{
closure->error = true;
closure->error_name = ex.what();
}
}
void Image::EIO_AfterOpen(uv_work_t* req)
{
Nan::HandleScope scope;
image_file_ptr_baton_t *closure = static_cast<image_file_ptr_baton_t *>(req->data);
if (closure->error || !closure->im)
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else
{
Image* im = new Image(closure->im);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->cb.Reset();
delete closure;
}
/**
* Load image from an SVG buffer (synchronous)
* @name fromSVGBytesSync
* @memberof Image
* @static
* @param {string} path - path to SVG image
* @param {Object} [options]
* @param {number} [options.scale] - scale the image. For example passing `0.5` as scale would render
* your SVG at 50% the original size.
* @param {number} [options.max_size] - the maximum allowed size of the svg dimensions * scale. The default is 2048.
* This option can be passed a smaller or larger size in order to control the final size of the image allocated for
* rasterizing the SVG.
* @returns {mapnik.Image} Image object
* @example
* var buffer = fs.readFileSync('./path/to/image.svg');
* var img = mapnik.Image.fromSVGBytesSync(buffer);
*/
NAN_METHOD(Image::fromSVGBytesSync)
{
info.GetReturnValue().Set(_fromSVGSync(false, info));
}
/**
* Create a new image from an SVG file (synchronous)
*
* @name fromSVGSync
* @param {string} filename
* @param {Object} [options]
* @param {number} [options.scale] - scale the image. For example passing `0.5` as scale would render
* your SVG at 50% the original size.
* @param {number} [options.max_size] - the maximum allowed size of the svg dimensions * scale. The default is 2048.
* This option can be passed a smaller or larger size in order to control the final size of the image allocated for
* rasterizing the SVG.
* @returns {mapnik.Image} image object
* @static
* @memberof Image
* @example
* var img = mapnik.Image.fromSVG('./path/to/image.svg');
*/
NAN_METHOD(Image::fromSVGSync)
{
info.GetReturnValue().Set(_fromSVGSync(true, info));
}
v8::Local<v8::Value> Image::_fromSVGSync(bool fromFile, Nan::NAN_METHOD_ARGS_TYPE info)
{
Nan::EscapableHandleScope scope;
if (!fromFile && (info.Length() < 1 || !info[0]->IsObject()))
{
Nan::ThrowTypeError("must provide a buffer argument");
return scope.Escape(Nan::Undefined());
}
if (fromFile && (info.Length() < 1 || !info[0]->IsString()))
{
Nan::ThrowTypeError("must provide a filename argument");
return scope.Escape(Nan::Undefined());
}
double scale = 1.0;
std::uint32_t max_size = 2048;
if (info.Length() >= 2)
{
if (!info[1]->IsObject())
{
Nan::ThrowTypeError("optional second arg must be an options object");
return scope.Escape(Nan::Undefined());
}
v8::Local<v8::Object> options = info[1]->ToObject();
if (options->Has(Nan::New("scale").ToLocalChecked()))
{
v8::Local<v8::Value> scale_opt = options->Get(Nan::New("scale").ToLocalChecked());
if (!scale_opt->IsNumber())
{
Nan::ThrowTypeError("'scale' must be a number");
return scope.Escape(Nan::Undefined());
}
scale = scale_opt->NumberValue();
if (scale <= 0)
{
Nan::ThrowTypeError("'scale' must be a positive non zero number");
return scope.Escape(Nan::Undefined());
}
}
if (options->Has(Nan::New("max_size").ToLocalChecked()))
{
v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
if (!opt->IsNumber())
{
Nan::ThrowTypeError("max_size must be a positive integer");
return scope.Escape(Nan::Undefined());
}
auto max_size_val = opt->IntegerValue();
if (max_size_val < 0 || max_size_val > 65535) {
Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
return scope.Escape(Nan::Undefined());
}
max_size = static_cast<std::uint32_t>(max_size_val);
}
}
try
{
using namespace mapnik::svg;
mapnik::svg_path_ptr marker_path(std::make_shared<mapnik::svg_storage_type>());
vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source());
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, marker_path->attributes());
svg_parser p(svg);
if (fromFile)
{
if (!p.parse(TOSTR(info[0])))
{
std::ostringstream errorMessage("");
errorMessage << "SVG parse error:" << std::endl;
for (auto const& error : p.err_handler().error_messages()) {
errorMessage << error << std::endl;
}
Nan::ThrowTypeError(errorMessage.str().c_str());
return scope.Escape(Nan::Undefined());
}
}
else
{
v8::Local<v8::Object> obj = info[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj))
{
Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
return scope.Escape(Nan::Undefined());
}
std::string svg_buffer(node::Buffer::Data(obj),node::Buffer::Length(obj));
if (!p.parse_from_string(svg_buffer))
{
std::ostringstream errorMessage("");
errorMessage << "SVG parse error:" << std::endl;
for (auto const& error : p.err_handler().error_messages()) {
errorMessage << error << std::endl;
}
Nan::ThrowTypeError(errorMessage.str().c_str());
return scope.Escape(Nan::Undefined());
}
}
double lox,loy,hix,hiy;
svg.bounding_rect(&lox, &loy, &hix, &hiy);
marker_path->set_bounding_box(lox,loy,hix,hiy);
marker_path->set_dimensions(svg.width(),svg.height());
using pixfmt = agg::pixfmt_rgba32_pre;
using renderer_base = agg::renderer_base<pixfmt>;
using renderer_solid = agg::renderer_scanline_aa_solid<renderer_base>;
agg::rasterizer_scanline_aa<> ras_ptr;
agg::scanline_u8 sl;
double opacity = 1;
double svg_width = svg.width() * scale;
double svg_height = svg.height() * scale;
if (svg_width <= 0 || svg_height <= 0)
{
Nan::ThrowTypeError("image created from svg must have a width and height greater then zero");
return scope.Escape(Nan::Undefined());
}
if (svg_width > static_cast<double>(max_size) || svg_height > static_cast<double>(max_size))
{
std::stringstream s;
s << "image created from svg must be " << max_size << " pixels or fewer on each side";
Nan::ThrowTypeError(s.str().c_str());
return scope.Escape(Nan::Undefined());
}
mapnik::image_rgba8 im(static_cast<int>(svg_width), static_cast<int>(svg_height), true, true);
agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size());
pixfmt pixf(buf);
renderer_base renb(pixf);
mapnik::box2d<double> const& bbox = marker_path->bounding_box();
mapnik::coord<double,2> c = bbox.center();
// center the svg marker on '0,0'
agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y);
// Scale the image
mtx.scale(scale);
// render the marker at the center of the marker box
mtx.translate(0.5 * im.width(), 0.5 * im.height());
mapnik::svg::svg_renderer_agg<mapnik::svg::svg_path_adapter,
agg::pod_bvector<mapnik::svg::path_attributes>,
renderer_solid,
agg::pixfmt_rgba32_pre > svg_renderer_this(svg_path,
marker_path->attributes());
svg_renderer_this.render(ras_ptr, sl, renb, mtx, opacity, bbox);
mapnik::demultiply_alpha(im);
image_ptr imagep = std::make_shared<mapnik::image_any>(im);
Image *im2 = new Image(imagep);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im2);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
return scope.Escape(maybe_local.ToLocalChecked());
}
catch (std::exception const& ex)
{
// There is currently no known way to make these operations throw an exception, however,
// since the underlying agg library does possibly have some operation that might throw
// it is a good idea to keep this. Therefore, any exceptions thrown will fail gracefully.
// LCOV_EXCL_START
Nan::ThrowError(ex.what());
return scope.Escape(Nan::Undefined());
// LCOV_EXCL_STOP
}
}
typedef struct {
uv_work_t request;
image_ptr im;
std::string filename;
bool error;
double scale;
std::uint32_t max_size;
std::string error_name;
Nan::Persistent<v8::Function> cb;
} svg_file_ptr_baton_t;
typedef struct {
uv_work_t request;
image_ptr im;
const char *data;
size_t dataLength;
bool error;
double scale;
std::uint32_t max_size;
std::string error_name;
Nan::Persistent<v8::Object> buffer;
Nan::Persistent<v8::Function> cb;
} svg_mem_ptr_baton_t;
/**
* Create a new image from an SVG file
*
* @name fromSVG
* @param {string} filename
* @param {Object} [options]
* @param {number} [options.scale] - scale the image. For example passing `0.5` as scale would render
* your SVG at 50% the original size.
* @param {number} [options.max_size] - the maximum allowed size of the svg dimensions * scale. The default is 2048.
* This option can be passed a smaller or larger size in order to control the final size of the image allocated for
* rasterizing the SVG.
* @param {Function} callback
* @static
* @memberof Image
* @example
* mapnik.Image.fromSVG('./path/to/image.svg', {scale: 0.5}, function(err, img) {
* if (err) throw err;
* // new img object (at 50% scale)
* });
*/
NAN_METHOD(Image::fromSVG)
{
if (info.Length() == 1) {
info.GetReturnValue().Set(_fromSVGSync(true, info));
return;
}
if (info.Length() < 2 || !info[0]->IsString())
{
Nan::ThrowTypeError("must provide a filename argument");
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length() - 1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
double scale = 1.0;
std::uint32_t max_size = 2048;
if (info.Length() >= 3)
{
if (!info[1]->IsObject())
{
Nan::ThrowTypeError("optional second arg must be an options object");
return;
}
v8::Local<v8::Object> options = info[1]->ToObject();
if (options->Has(Nan::New("scale").ToLocalChecked()))
{
v8::Local<v8::Value> scale_opt = options->Get(Nan::New("scale").ToLocalChecked());
if (!scale_opt->IsNumber())
{
Nan::ThrowTypeError("'scale' must be a number");
return;
}
scale = scale_opt->NumberValue();
if (scale <= 0)
{
Nan::ThrowTypeError("'scale' must be a positive non zero number");
return;
}
}
if (options->Has(Nan::New("max_size").ToLocalChecked()))
{
v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
if (!opt->IsNumber())
{
Nan::ThrowTypeError("max_size must be a positive integer");
return;
}
auto max_size_val = opt->IntegerValue();
if (max_size_val < 0 || max_size_val > 65535) {
Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
return;
}
max_size = static_cast<std::uint32_t>(max_size_val);
}
}
svg_file_ptr_baton_t *closure = new svg_file_ptr_baton_t();
closure->request.data = closure;
closure->filename = TOSTR(info[0]);
closure->error = false;
closure->scale = scale;
closure->max_size = max_size;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_FromSVG, (uv_after_work_cb)EIO_AfterFromSVG);
return;
}
void Image::EIO_FromSVG(uv_work_t* req)
{
svg_file_ptr_baton_t *closure = static_cast<svg_file_ptr_baton_t *>(req->data);
try
{
using namespace mapnik::svg;
mapnik::svg_path_ptr marker_path(std::make_shared<mapnik::svg_storage_type>());
vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source());
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, marker_path->attributes());
svg_parser p(svg);
if (!p.parse(closure->filename))
{
std::ostringstream errorMessage("");
errorMessage << "SVG parse error:" << std::endl;
for (auto const& error : p.err_handler().error_messages()) {
errorMessage << error << std::endl;
}
closure->error = true;
closure->error_name = errorMessage.str();
return;
}
double lox,loy,hix,hiy;
svg.bounding_rect(&lox, &loy, &hix, &hiy);
marker_path->set_bounding_box(lox,loy,hix,hiy);
marker_path->set_dimensions(svg.width(),svg.height());
using pixfmt = agg::pixfmt_rgba32_pre;
using renderer_base = agg::renderer_base<pixfmt>;
using renderer_solid = agg::renderer_scanline_aa_solid<renderer_base>;
agg::rasterizer_scanline_aa<> ras_ptr;
agg::scanline_u8 sl;
double opacity = 1;
double svg_width = svg.width() * closure->scale;
double svg_height = svg.height() * closure->scale;
if (svg_width <= 0 || svg_height <= 0)
{
closure->error = true;
closure->error_name = "image created from svg must have a width and height greater then zero";
return;
}
if (svg_width > static_cast<double>(closure->max_size) || svg_height > static_cast<double>(closure->max_size))
{
closure->error = true;
std::stringstream s;
s << "image created from svg must be " << closure->max_size << " pixels or fewer on each side";
closure->error_name = s.str();
return;
}
mapnik::image_rgba8 im(static_cast<int>(svg_width), static_cast<int>(svg_height), true, true);
agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size());
pixfmt pixf(buf);
renderer_base renb(pixf);
mapnik::box2d<double> const& bbox = marker_path->bounding_box();
mapnik::coord<double,2> c = bbox.center();
// center the svg marker on '0,0'
agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y);
// Scale the image
mtx.scale(closure->scale);
// render the marker at the center of the marker box
mtx.translate(0.5 * im.width(), 0.5 * im.height());
mapnik::svg::svg_renderer_agg<mapnik::svg::svg_path_adapter,
agg::pod_bvector<mapnik::svg::path_attributes>,
renderer_solid,
agg::pixfmt_rgba32_pre > svg_renderer_this(svg_path,
marker_path->attributes());
svg_renderer_this.render(ras_ptr, sl, renb, mtx, opacity, bbox);
mapnik::demultiply_alpha(im);
closure->im = std::make_shared<mapnik::image_any>(im);
}
catch (std::exception const& ex)
{
// There is currently no known way to make these operations throw an exception, however,
// since the underlying agg library does possibly have some operation that might throw
// it is a good idea to keep this. Therefore, any exceptions thrown will fail gracefully.
// LCOV_EXCL_START
closure->error = true;
closure->error_name = "Failed to load: " + closure->filename;
// LCOV_EXCL_STOP
}
}
void Image::EIO_AfterFromSVG(uv_work_t* req)
{
Nan::HandleScope scope;
svg_file_ptr_baton_t *closure = static_cast<svg_file_ptr_baton_t *>(req->data);
if (closure->error || !closure->im)
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else
{
Image* im = new Image(closure->im);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->cb.Reset();
delete closure;
}
/**
* Load image from an SVG buffer
* @name fromSVGBytes
* @memberof Image
* @static
* @param {string} path - path to SVG image
* @param {Object} [options]
* @param {number} [options.scale] - scale the image. For example passing `0.5` as scale would render
* your SVG at 50% the original size.
* @param {number} [options.max_size] - the maximum allowed size of the svg dimensions * scale. The default is 2048.
* This option can be passed a smaller or larger size in order to control the final size of the image allocated for
* rasterizing the SVG.
* @param {Function} callback = `function(err, img)`
* @example
* var buffer = fs.readFileSync('./path/to/image.svg');
* mapnik.Image.fromSVGBytesSync(buffer, function(err, img) {
* if (err) throw err;
* // your custom code with `img`
* });
*/
NAN_METHOD(Image::fromSVGBytes)
{
if (info.Length() == 1) {
info.GetReturnValue().Set(_fromSVGSync(false, info));
return;
}
if (info.Length() < 2 || !info[0]->IsObject()) {
Nan::ThrowError("must provide a buffer argument");
return;
}
v8::Local<v8::Object> obj = info[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length() - 1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
double scale = 1.0;
std::uint32_t max_size = 2048;
if (info.Length() >= 3)
{
if (!info[1]->IsObject())
{
Nan::ThrowTypeError("optional second arg must be an options object");
return;
}
v8::Local<v8::Object> options = info[1]->ToObject();
if (options->Has(Nan::New("scale").ToLocalChecked()))
{
v8::Local<v8::Value> scale_opt = options->Get(Nan::New("scale").ToLocalChecked());
if (!scale_opt->IsNumber())
{
Nan::ThrowTypeError("'scale' must be a number");
return;
}
scale = scale_opt->NumberValue();
if (scale <= 0)
{
Nan::ThrowTypeError("'scale' must be a positive non zero number");
return;
}
}
if (options->Has(Nan::New("max_size").ToLocalChecked()))
{
v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
if (!opt->IsNumber())
{
Nan::ThrowTypeError("max_size must be a positive integer");
return;
}
auto max_size_val = opt->IntegerValue();
if (max_size_val < 0 || max_size_val > 65535) {
Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
return;
}
max_size = static_cast<std::uint32_t>(max_size_val);
}
}
svg_mem_ptr_baton_t *closure = new svg_mem_ptr_baton_t();
closure->request.data = closure;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
closure->buffer.Reset(obj.As<v8::Object>());
closure->data = node::Buffer::Data(obj);
closure->scale = scale;
closure->max_size = max_size;
closure->dataLength = node::Buffer::Length(obj);
uv_queue_work(uv_default_loop(), &closure->request, EIO_FromSVGBytes, (uv_after_work_cb)EIO_AfterFromSVGBytes);
return;
}
void Image::EIO_FromSVGBytes(uv_work_t* req)
{
svg_mem_ptr_baton_t *closure = static_cast<svg_mem_ptr_baton_t *>(req->data);
try
{
using namespace mapnik::svg;
mapnik::svg_path_ptr marker_path(std::make_shared<mapnik::svg_storage_type>());
vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source());
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, marker_path->attributes());
svg_parser p(svg);
std::string svg_buffer(closure->data,closure->dataLength);
if (!p.parse_from_string(svg_buffer))
{
std::ostringstream errorMessage("");
errorMessage << "SVG parse error:" << std::endl;
for (auto const& error : p.err_handler().error_messages()) {
errorMessage << error << std::endl;
}
closure->error = true;
closure->error_name = errorMessage.str();
return;
}
double lox,loy,hix,hiy;
svg.bounding_rect(&lox, &loy, &hix, &hiy);
marker_path->set_bounding_box(lox,loy,hix,hiy);
marker_path->set_dimensions(svg.width(),svg.height());
using pixfmt = agg::pixfmt_rgba32_pre;
using renderer_base = agg::renderer_base<pixfmt>;
using renderer_solid = agg::renderer_scanline_aa_solid<renderer_base>;
agg::rasterizer_scanline_aa<> ras_ptr;
agg::scanline_u8 sl;
double opacity = 1;
double svg_width = svg.width() * closure->scale;
double svg_height = svg.height() * closure->scale;
if (svg_width <= 0 || svg_height <= 0)
{
closure->error = true;
closure->error_name = "image created from svg must have a width and height greater then zero";
return;
}
if (svg_width > static_cast<double>(closure->max_size) || svg_height > static_cast<double>(closure->max_size))
{
closure->error = true;
std::stringstream s;
s << "image created from svg must be " << closure->max_size << " pixels or fewer on each side";
closure->error_name = s.str();
return;
}
mapnik::image_rgba8 im(static_cast<int>(svg_width), static_cast<int>(svg_height), true, true);
agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size());
pixfmt pixf(buf);
renderer_base renb(pixf);
mapnik::box2d<double> const& bbox = marker_path->bounding_box();
mapnik::coord<double,2> c = bbox.center();
// center the svg marker on '0,0'
agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y);
// Scale the image
mtx.scale(closure->scale);
// render the marker at the center of the marker box
mtx.translate(0.5 * im.width(), 0.5 * im.height());
mapnik::svg::svg_renderer_agg<mapnik::svg::svg_path_adapter,
agg::pod_bvector<mapnik::svg::path_attributes>,
renderer_solid,
agg::pixfmt_rgba32_pre > svg_renderer_this(svg_path,
marker_path->attributes());
svg_renderer_this.render(ras_ptr, sl, renb, mtx, opacity, bbox);
mapnik::demultiply_alpha(im);
closure->im = std::make_shared<mapnik::image_any>(im);
}
catch (std::exception const& ex)
{
// There is currently no known way to make these operations throw an exception, however,
// since the underlying agg library does possibly have some operation that might throw
// it is a good idea to keep this. Therefore, any exceptions thrown will fail gracefully.
// LCOV_EXCL_START
closure->error = true;
closure->error_name = ex.what();
// LCOV_EXCL_STOP
}
}
void Image::EIO_AfterFromSVGBytes(uv_work_t* req)
{
Nan::HandleScope scope;
svg_mem_ptr_baton_t *closure = static_cast<svg_mem_ptr_baton_t *>(req->data);
if (closure->error || !closure->im)
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else
{
Image* im = new Image(closure->im);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
v8::Local<v8::Object> image_obj = Nan::New(constructor)->GetFunction()->NewInstance(1, &ext);
v8::Local<v8::Value> argv[2] = { Nan::Null(), image_obj };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->cb.Reset();
closure->buffer.Reset();
delete closure;
}
/**
* Create an image of the existing buffer.
*
* Note: the buffer must live as long as the image.
* It is recommended that you do not use this method. Be warned!
*
* @name fromBufferSync
* @param {number} width
* @param {number} height
* @param {Buffer} buffer
* @returns {mapnik.Image} image object
* @static
* @memberof Image
* @example
* var img = new mapnik.Image.open('./path/to/image.png');
* var buffer = img.data(); // returns data as buffer
* var img2 = mapnik.Image.fromBufferSync(img.width(), img.height(), buffer);
*/
NAN_METHOD(Image::fromBufferSync)
{
info.GetReturnValue().Set(_fromBufferSync(info));
}
v8::Local<v8::Value> Image::_fromBufferSync(Nan::NAN_METHOD_ARGS_TYPE info)
{
Nan::EscapableHandleScope scope;
if (info.Length() < 3 || !info[0]->IsNumber() || !info[1]->IsNumber() || !info[2]->IsObject()) {
Nan::ThrowTypeError("must provide a width, height, and buffer argument");
return scope.Escape(Nan::Undefined());
}
unsigned width = info[0]->IntegerValue();
unsigned height = info[1]->IntegerValue();
if (width <= 0 || height <= 0)
{
Nan::ThrowTypeError("width and height must be greater then zero");
return scope.Escape(Nan::Undefined());
}
v8::Local<v8::Object> obj = info[2]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
Nan::ThrowTypeError("third argument is invalid, must be a Buffer");
return scope.Escape(Nan::Undefined());
}
// TODO - support other image types?
auto im_size = mapnik::image_rgba8::pixel_size * width * height;
if (im_size != node::Buffer::Length(obj)) {
Nan::ThrowTypeError("invalid image size");
return scope.Escape(Nan::Undefined());
}
bool premultiplied = false;
bool painted = false;
if (info.Length() >= 4)
{
if (info[3]->IsObject())
{
v8::Local<v8::Object> options = v8::Local<v8::Object>::Cast(info[3]);
if (options->Has(Nan::New("type").ToLocalChecked()))
{
Nan::ThrowTypeError("'type' option not supported (only rgba images currently viable)");
return scope.Escape(Nan::Undefined());
}
if (options->Has(Nan::New("premultiplied").ToLocalChecked()))
{
v8::Local<v8::Value> pre_val = options->Get(Nan::New("premultiplied").ToLocalChecked());
if (!pre_val.IsEmpty() && pre_val->IsBoolean())
{
premultiplied = pre_val->BooleanValue();
}
else
{
Nan::ThrowTypeError("premultiplied option must be a boolean");
return scope.Escape(Nan::Undefined());
}
}
if (options->Has(Nan::New("painted").ToLocalChecked()))
{
v8::Local<v8::Value> painted_val = options->Get(Nan::New("painted").ToLocalChecked());
if (!painted_val.IsEmpty() && painted_val->IsBoolean())
{
painted = painted_val->BooleanValue();
}
else
{
Nan::ThrowTypeError("painted option must be a boolean");
return scope.Escape(Nan::Undefined());
}
}
}
else
{
Nan::ThrowTypeError("Options parameter must be an object");
return scope.Escape(Nan::Undefined());
}
}
try
{
mapnik::image_rgba8 im_wrapper(width, height, reinterpret_cast<unsigned char*>(node::Buffer::Data(obj)), premultiplied, painted);
image_ptr imagep = std::make_shared<mapnik::image_any>(im_wrapper);
Image* im = new Image(imagep);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
v8::Local<v8::Object> image_obj = maybe_local.ToLocalChecked()->ToObject();
image_obj->Set(Nan::New("_buffer").ToLocalChecked(),obj);
return scope.Escape(maybe_local.ToLocalChecked());
}
catch (std::exception const& ex)
{
// There is no known way for this exception to be reached currently.
// LCOV_EXCL_START
Nan::ThrowError(ex.what());
return scope.Escape(Nan::Undefined());
// LCOV_EXCL_STOP
}
}
/**
* Create an image from a byte stream buffer. (synchronous)
*
* @name fromBytesSync
* @param {Buffer} buffer - image buffer
* @returns {mapnik.Image} image object
* @instance
* @memberof Image
* @example
* var buffer = fs.readFileSync('./path/to/image.png');
* var img = new mapnik.Image.fromBytesSync(buffer);
*/
NAN_METHOD(Image::fromBytesSync)
{
info.GetReturnValue().Set(_fromBytesSync(info));
}
v8::Local<v8::Value> Image::_fromBytesSync(Nan::NAN_METHOD_ARGS_TYPE info)
{
Nan::EscapableHandleScope scope;
if (info.Length() < 1 || !info[0]->IsObject()) {
Nan::ThrowTypeError("must provide a buffer argument");
return scope.Escape(Nan::Undefined());
}
v8::Local<v8::Object> obj = info[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
return scope.Escape(Nan::Undefined());
}
try
{
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(node::Buffer::Data(obj),node::Buffer::Length(obj)));
if (reader.get())
{
image_ptr imagep = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(),reader->height()));
Image* im = new Image(imagep);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
return scope.Escape(maybe_local.ToLocalChecked());
}
// The only way this is ever reached is if the reader factory in
// mapnik was not providing an image type it should. This should never
// be occuring so marking this out from coverage
/* LCOV_EXCL_START */
Nan::ThrowTypeError("Failed to load from buffer");
return scope.Escape(Nan::Undefined());
/* LCOV_EXCL_STOP */
}
catch (std::exception const& ex)
{
Nan::ThrowError(ex.what());
return scope.Escape(Nan::Undefined());
}
}
/**
* Create an image from a byte stream buffer.
*
* @name fromBytes
* @param {Buffer} buffer - image buffer
* @param {Object} [options]
* @param {Boolean} [options.premultiply] - Default false, if true, then the image will be premultiplied before being returned
* @param {Number} [options.max_size] - the maximum allowed size of the image dimensions. The default is 2048.
* @param {Function} callback - `function(err, img)`
* @static
* @memberof Image
* @example
* var buffer = fs.readFileSync('./path/to/image.png');
* mapnik.Image.fromBytes(buffer, function(err, img) {
* if (err) throw err;
* // your custom code with `img` object
* });
*/
NAN_METHOD(Image::fromBytes)
{
if (info.Length() == 1) {
info.GetReturnValue().Set(_fromBytesSync(info));
return;
}
if (info.Length() < 2) {
Nan::ThrowError("must provide a buffer argument");
return;
}
if (!info[0]->IsObject()) {
Nan::ThrowTypeError("must provide a buffer argument");
return;
}
v8::Local<v8::Object> obj = info[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
return;
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length() - 1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
bool premultiply = false;
std::uint32_t max_size = 2048;
if (info.Length() >= 2)
{
if (info[1]->IsObject())
{
v8::Local<v8::Object> options = v8::Local<v8::Object>::Cast(info[1]);
if (options->Has(Nan::New("premultiply").ToLocalChecked()))
{
v8::Local<v8::Value> opt = options->Get(Nan::New("premultiply").ToLocalChecked());
if (!opt.IsEmpty() && opt->IsBoolean())
{
premultiply = opt->BooleanValue();
}
else
{
Nan::ThrowTypeError("premultiply option must be a boolean");
return;
}
}
if (options->Has(Nan::New("max_size").ToLocalChecked()))
{
v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
if (opt->IsNumber())
{
auto max_size_val = opt->IntegerValue();
if (max_size_val < 0 || max_size_val > 65535) {
Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
return;
}
max_size = static_cast<std::uint32_t>(max_size_val);
}
else
{
Nan::ThrowTypeError("max_size option must be a number");
return;
}
}
}
}
image_mem_ptr_baton_t *closure = new image_mem_ptr_baton_t();
closure->request.data = closure;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
closure->buffer.Reset(obj.As<v8::Object>());
closure->data = node::Buffer::Data(obj);
closure->dataLength = node::Buffer::Length(obj);
closure->premultiply = premultiply;
closure->max_size = max_size;
closure->im = nullptr;
uv_queue_work(uv_default_loop(), &closure->request, EIO_FromBytes, (uv_after_work_cb)EIO_AfterFromBytes);
return;
}
void Image::EIO_FromBytes(uv_work_t* req)
{
image_mem_ptr_baton_t *closure = static_cast<image_mem_ptr_baton_t *>(req->data);
try
{
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(closure->data,closure->dataLength));
if (reader.get())
{
if (reader->width() > closure->max_size || reader->height() > closure->max_size) {
std::stringstream s;
s << "image created from bytes must be " << closure->max_size << " pixels or fewer on each side";
closure->error_name = s.str();
} else {
closure->im = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(),reader->height()));
if (closure->premultiply) {
mapnik::premultiply_alpha(*closure->im);
}
}
}
}
catch (std::exception const& ex)
{
closure->error_name = ex.what();
}
}
void Image::EIO_AfterFromBytes(uv_work_t* req)
{
Nan::HandleScope scope;
image_mem_ptr_baton_t *closure = static_cast<image_mem_ptr_baton_t *>(req->data);
if (!closure->error_name.empty())
{
v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
}
else if (closure->im == nullptr)
{
/* LCOV_EXCL_START */
// The only way this is ever reached is if the reader factory in
// mapnik was not providing an image type it should. This should never
// be occuring so marking this out from coverage
v8::Local<v8::Value> argv[1] = { Nan::Error("Failed to load from buffer") };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
/* LCOV_EXCL_STOP */
} else
{
Image* im = new Image(closure->im);
v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
}
closure->cb.Reset();
closure->buffer.Reset();
delete closure;
}
/**
* Encode this image into a buffer of encoded data (synchronous)
*
* @name encodeSync
* @param {string} [format=png] image format
* @param {Object} [options]
* @param {mapnik.Palette} [options.palette] - mapnik.Palette object
* @returns {Buffer} buffer - encoded image data
* @instance
* @memberof Image
* @example
* var img = new mapnik.Image.open('./path/to/image.png');
* var buffer = img.encodeSync('png');
* // write buffer to a new file
* fs.writeFileSync('myimage.png', buffer);
*/
NAN_METHOD(Image::encodeSync)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
std::string format = "png";
palette_ptr palette;
// accept custom format
if (info.Length() >= 1){
if (!info[0]->IsString()) {
Nan::ThrowTypeError("first arg, 'format' must be a string");
return;
}
format = TOSTR(info[0]);
}
// options hash
if (info.Length() >= 2) {
if (!info[1]->IsObject()) {
Nan::ThrowTypeError("optional second arg must be an options object");
return;
}
v8::Local<v8::Object> options = info[1]->ToObject();
if (options->Has(Nan::New("palette").ToLocalChecked()))
{
v8::Local<v8::Value> format_opt = options->Get(Nan::New("palette").ToLocalChecked());
if (!format_opt->IsObject()) {
Nan::ThrowTypeError("'palette' must be an object");
return;
}
v8::Local<v8::Object> obj = format_opt->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Palette::constructor)->HasInstance(obj)) {
Nan::ThrowTypeError("mapnik.Palette expected as second arg");
return;
}
palette = Nan::ObjectWrap::Unwrap<Palette>(obj)->palette();
}
}
try {
std::string s;
if (palette.get())
{
s = save_to_string(*(im->this_), format, *palette);
}
else {
s = save_to_string(*(im->this_), format);
}
info.GetReturnValue().Set(Nan::CopyBuffer((char*)s.data(), s.size()).ToLocalChecked());
}
catch (std::exception const& ex)
{
Nan::ThrowError(ex.what());
return;
}
}
typedef struct {
uv_work_t request;
Image* im;
std::string format;
palette_ptr palette;
bool error;
std::string error_name;
Nan::Persistent<v8::Function> cb;
std::string result;
} encode_image_baton_t;
/**
* Encode this image into a buffer of encoded data
*
* @name encode
* @param {string} [format=png] image format
* @param {Object} [options]
* @param {mapnik.Palette} [options.palette] - mapnik.Palette object
* @param {Function} callback - `function(err, encoded)`
* @returns {Buffer} encoded image data
* @instance
* @memberof Image
* @example
* var img = new mapnik.Image.open('./path/to/image.png');
* myImage.encode('png', function(err, encoded) {
* if (err) throw err;
* // write buffer to new file
* fs.writeFileSync('myimage.png', encoded);
* });
*
* // encoding an image object with a mapnik.Palette
* var im = new mapnik.Image(256, 256);
* var pal = new mapnik.Palette(new Buffer('\xff\x09\x93\xFF\x01\x02\x03\x04','ascii'));
* im.encode('png', {palette: pal}, function(err, encode) {
* if (err) throw err;
* // your custom code with `encode` image buffer
* });
*/
NAN_METHOD(Image::encode)
{
Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
std::string format = "png";
palette_ptr palette;
// accept custom format
if (info.Length() >= 1){
if (!info[0]->IsString()) {
Nan::ThrowTypeError("first arg, 'format' must be a string");
return;
}
format = TOSTR(info[0]);
}
// options hash
if (info.Length() >= 2) {
if (!info[1]->IsObject()) {
Nan::ThrowTypeError("optional second arg must be an options object");
return;
}
v8::Local<v8::Object> options = info[1].As<v8::Object>();
if (options->Has(Nan::New("palette").ToLocalChecked()))
{
v8::Local<v8::Value> format_opt = options->Get(Nan::New("palette").ToLocalChecked());
if (!format_opt->IsObject()) {
Nan::ThrowTypeError("'palette' must be an object");
return;
}
v8::Local<v8::Object> obj = format_opt.As<v8::Object>();
if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Palette::constructor)->HasInstance(obj)) {
Nan::ThrowTypeError("mapnik.Palette expected as second arg");
return;
}
palette = Nan::ObjectWrap::Unwrap<Palette>(obj)->palette();
}
}
// ensure callback is a function
v8::Local<v8::Value> callback = info[info.Length() - 1];
if (!info[info.Length()-1]->IsFunction()) {
Nan::ThrowTypeError("last argument must be a callback function");
return;
}
encode_image_baton_t *closure = new encode_image_baton_t();
closure->request.data = closure;
closure->im = im;
closure->format = format;
closure->palette = palette;
closure->error = false;
closure->cb.Reset(callback.As<v8::Function>());
uv_queue_work(uv_default_loop(), &closure->request, EIO_Encode, (uv_after_work_cb)EIO_AfterEncode);
im->Ref();
return;
}
void Image::EIO_Encode(uv_work_t* req)
{
encode_image_baton_t *closure = static_cast<encode_image_baton_t *>(req->data);