Skip to content
Permalink
699a2724d6
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
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);