diff --git a/binding.gyp b/binding.gyp
index 10ca1d18fe..f9c651aef8 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -13,6 +13,20 @@
         "src/mapnik_geometry.cpp",
         "src/mapnik_feature.cpp",
         "src/mapnik_image.cpp",
+        "src/mapnik_image_clear.cpp",
+        "src/mapnik_image_composite.cpp",
+        "src/mapnik_image_copy.cpp",
+        "src/mapnik_image_encode.cpp",
+        "src/mapnik_image_fill.cpp",
+        "src/mapnik_image_filter.cpp",
+        "src/mapnik_image_from_buffer.cpp",
+        "src/mapnik_image_from_bytes.cpp",
+        "src/mapnik_image_open.cpp",
+        "src/mapnik_image_premultiply.cpp",
+        "src/mapnik_image_resize.cpp",
+        "src/mapnik_image_save.cpp",
+        "src/mapnik_image_solid.cpp",
+        "src/mapnik_image_svg.cpp",
         "src/mapnik_image_view.cpp",
         "src/mapnik_grid.cpp",
         "src/mapnik_grid_view.cpp",
diff --git a/src/mapnik_image.cpp b/src/mapnik_image.cpp
index 19c256098a..a518b3c52a 100644
--- a/src/mapnik_image.cpp
+++ b/src/mapnik_image.cpp
@@ -2,48 +2,16 @@
 #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 "visitor_get_pixel.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;
 
@@ -310,105 +278,6 @@ NAN_METHOD(Image::getType)
     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
@@ -648,3132 +517,124 @@ NAN_METHOD(Image::compare)
 }
 
 /**
- * Apply a filter to this image. This changes all pixel values. (synchronous)
+ * Convert all grayscale values to alpha values. Great for creating
+ * a mask layer based on alpha values.
  *
- * @name filterSync
- * @instance
+ * @name setGrayScaleToAlpha
  * @memberof Image
- * @param {string} filter - can be `blur`, `emboss`, `sharpen`,
- * `sobel`, or `gray`.
+ * @instance
+ * @param {mapnik.Color} color
  * @example
- * var img = new mapnik.Image(5, 5);
- * img.filter('blur');
- * // your custom code with `img` having blur applied
+ * 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::filterSync)
+NAN_METHOD(Image::setGrayScaleToAlpha)
 {
-    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;
+    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;
+        }
 
-/**
- * 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;
-    }
+        v8::Local<v8::Object> obj = info[0]->ToObject();
 
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
+        if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Color::constructor)->HasInstance(obj)) {
+            Nan::ThrowTypeError("mapnik.Color expected as first arg");
+            return;
+        }
 
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    if (!info[0]->IsString())
-    {
-        Nan::ThrowTypeError("A string is expected for filter argument");
-        return;
+        Color * color = Nan::ObjectWrap::Unwrap<Color>(obj);
+        mapnik::set_grayscale_to_alpha(*im->this_, *color->get());
     }
-    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)
+ * 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 fillSync
+ * @name painted
  * @instance
  * @memberof Image
- * @param {mapnik.Color|number} color
+ * @returns {boolean} whether it is painted or not
  * @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
+ * console.log(img.painted()); // false
  */
-NAN_METHOD(Image::fillSync)
+NAN_METHOD(Image::painted)
 {
-    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());
+    info.GetReturnValue().Set(Nan::New<v8::Boolean>(im->this_->painted()));
 }
 
-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.
+ * Get this image's width in pixels
  *
- * @name fill
+ * @name width
  * @instance
  * @memberof Image
- * @param {mapnik.Color|number} color
- * @param {Function} callback - `function(err, img)`
+ * @returns {number} width
  * @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) { ... });
+ * var img = new mapnik.Image(4,4);
+ * console.log(img.width()); // 4
  */
-NAN_METHOD(Image::fill)
+NAN_METHOD(Image::width)
 {
-    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;
+    info.GetReturnValue().Set(Nan::New<v8::Int32>(static_cast<std::int32_t>(im->this_->width())));
 }
 
 /**
- * Make this image transparent. (synchronous)
+ * Get this image's height in pixels
  *
- * @name clearSync
+ * @name height
  * @instance
  * @memberof Image
+ * @returns {number} height
  * @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
+ * var img = new mapnik.Image(4,4);
+ * console.log(img.height()); // 4
  */
-NAN_METHOD(Image::clearSync)
+NAN_METHOD(Image::height)
 {
-    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());
+    info.GetReturnValue().Set(Nan::New<v8::Int32>(static_cast<std::int32_t>(im->this_->height())));
 }
 
-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
+ * Get a constrained view of this image given x, y, width, height parameters.
  * @memberof Image
+ * @instance
+ * @name view
+ * @param {number} x
+ * @param {number} y
+ * @param {number} width
+ * @param {number} height
+ * @returns {mapnik.Image} an image constrained to this new view
  * @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
- * });
+ * var img = new mapnik.Image(10, 10);
+ * // This function says "starting from the 0/0 pixel, grab 5 pixels along
+ * // the x-axis and 5 along the y-axis" which gives us a quarter of the original
+ * // 10x10 pixel image
+ * var img2 = img.view(0, 0, 5, 5);
+ * console.log(img.width(), img2.width()); // 10, 5
  */
-NAN_METHOD(Image::clear)
+NAN_METHOD(Image::view)
 {
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-
-    if (info.Length() == 0) {
-        info.GetReturnValue().Set(_clearSync(info));
-        return;
-    }
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length()-1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-    clear_image_baton_t *closure = new clear_image_baton_t();
-    closure->request.data = closure;
-    closure->im = im;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Clear, (uv_after_work_cb)EIO_AfterClear);
-    im->Ref();
-    return;
-}
-
-void Image::EIO_Clear(uv_work_t* req)
-{
-    clear_image_baton_t *closure = static_cast<clear_image_baton_t *>(req->data);
-    try
-    {
-        mapnik::fill(*closure->im->this_, 0);
-    }
-    catch(std::exception const& ex)
-    {
-        closure->error = true;
-        closure->error_name = ex.what();
-    }
-}
-
-void Image::EIO_AfterClear(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    clear_image_baton_t *closure = static_cast<clear_image_baton_t *>(req->data);
-    if (closure->error)
-    {
-        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    else
-    {
-        v8::Local<v8::Value> argv[2] = { Nan::Null(), closure->im->handle() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-    closure->im->Unref();
-    closure->cb.Reset();
-    delete closure;
-}
-
-/**
- * Convert all grayscale values to alpha values. Great for creating
- * a mask layer based on alpha values.
- *
- * @name setGrayScaleToAlpha
- * @memberof Image
- * @instance
- * @param {mapnik.Color} color
- * @example
- * var image = new mapnik.Image(2,2);
- * image.fillSync(new mapnik.Color('rgba(0,0,0,255)'));
- * console.log(image.getPixel(0,0, {get_color:true})); // { premultiplied: false, a: 255, b: 0, g: 0, r: 0 }
- *
- * image.setGrayScaleToAlpha();
- * // turns a black pixel into a completely transparent mask
- * console.log(image.getPixel(0,0, {get_color:true})); // { premultiplied: false, a: 0, b: 255, g: 255, r: 255 }
- */
-NAN_METHOD(Image::setGrayScaleToAlpha)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    if (info.Length() == 0) {
-        mapnik::set_grayscale_to_alpha(*im->this_);
-    } else {
-        if (!info[0]->IsObject()) {
-            Nan::ThrowTypeError("optional first arg must be a mapnik.Color");
-            return;
-        }
-
-        v8::Local<v8::Object> obj = info[0]->ToObject();
-
-        if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Color::constructor)->HasInstance(obj)) {
-            Nan::ThrowTypeError("mapnik.Color expected as first arg");
-            return;
-        }
-
-        Color * color = Nan::ObjectWrap::Unwrap<Color>(obj);
-        mapnik::set_grayscale_to_alpha(*im->this_, *color->get());
-    }
-
-    return;
-}
-
-typedef struct {
-    uv_work_t request;
-    Image* im;
-    Nan::Persistent<v8::Function> cb;
-} image_op_baton_t;
-
-/**
- * Determine whether the given image is premultiplied.
- * https://en.wikipedia.org/wiki/Alpha_compositing
- *
- * @name premultiplied
- * @memberof Image
- * @instance
- * @returns {boolean} premultiplied `true` if the image is premultiplied
- * @example
- * var img = new mapnik.Image(5,5);
- * console.log(img.premultiplied()); // false
- * img.premultiplySync()
- * console.log(img.premultiplied()); // true
- */
-NAN_METHOD(Image::premultiplied)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    bool premultiplied = im->this_->get_premultiplied();
-    info.GetReturnValue().Set(Nan::New<v8::Boolean>(premultiplied));
-}
-
-/**
- * Premultiply the pixels in this image.
- *
- * @name premultiplySync
- * @instance
- * @memberof Image
- * @example
- * var img = new mapnik.Image(5,5);
- * img.premultiplySync();
- * console.log(img.premultiplied()); // true
- */
-NAN_METHOD(Image::premultiplySync)
-{
-    info.GetReturnValue().Set(_premultiplySync(info));
-}
-
-v8::Local<v8::Value> Image::_premultiplySync(Nan::NAN_METHOD_ARGS_TYPE info) {
-    Nan::EscapableHandleScope scope;
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    mapnik::premultiply_alpha(*im->this_);
-    return scope.Escape(Nan::Undefined());
-}
-
-/**
- * Premultiply the pixels in this image, asynchronously
- *
- * @name premultiply
- * @memberof Image
- * @instance
- * @param {Function} callback
- * @example
- * var img = new mapnik.Image(5,5);
- * img.premultiply(function(err, img) {
- *   if (err) throw err;
- *   // your custom code with premultiplied img
- * })
- */
-NAN_METHOD(Image::premultiply)
-{
-    if (info.Length() == 0) {
-        info.GetReturnValue().Set(_premultiplySync(info));
-        return;
-    }
-
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length()-1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    image_op_baton_t *closure = new image_op_baton_t();
-    closure->request.data = closure;
-    closure->im = im;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Premultiply, (uv_after_work_cb)EIO_AfterMultiply);
-    im->Ref();
-    return;
-}
-
-void Image::EIO_Premultiply(uv_work_t* req)
-{
-    image_op_baton_t *closure = static_cast<image_op_baton_t *>(req->data);
-    mapnik::premultiply_alpha(*closure->im->this_);
-}
-
-void Image::EIO_AfterMultiply(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    image_op_baton_t *closure = static_cast<image_op_baton_t *>(req->data);
-    v8::Local<v8::Value> argv[2] = { Nan::Null(), closure->im->handle() };
-    Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    closure->im->Unref();
-    closure->cb.Reset();
-    delete closure;
-}
-
-/**
- * Demultiply the pixels in this image. The opposite of
- * premultiplying.
- *
- * @name demultiplySync
- * @instance
- * @memberof Image
- */
-NAN_METHOD(Image::demultiplySync)
-{
-    Nan::HandleScope scope;
-    info.GetReturnValue().Set(_demultiplySync(info));
-}
-
-v8::Local<v8::Value> Image::_demultiplySync(Nan::NAN_METHOD_ARGS_TYPE info) {
-    Nan::EscapableHandleScope scope;
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    mapnik::demultiply_alpha(*im->this_);
-    return scope.Escape(Nan::Undefined());
-}
-
-/**
- * Demultiply the pixels in this image, asynchronously. The opposite of
- * premultiplying
- *
- * @name demultiply
- * @param {Function} callback
- * @instance
- * @memberof Image
- */
-NAN_METHOD(Image::demultiply)
-{
-    if (info.Length() == 0) {
-        info.GetReturnValue().Set(_demultiplySync(info));
-        return;
-    }
-
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length()-1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    image_op_baton_t *closure = new image_op_baton_t();
-    closure->request.data = closure;
-    closure->im = im;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Demultiply, (uv_after_work_cb)EIO_AfterMultiply);
-    im->Ref();
-    return;
-}
-
-void Image::EIO_Demultiply(uv_work_t* req)
-{
-    image_op_baton_t *closure = static_cast<image_op_baton_t *>(req->data);
-    mapnik::demultiply_alpha(*closure->im->this_);
-}
-
-typedef struct {
-    uv_work_t request;
-    Image* im;
-    Nan::Persistent<v8::Function> cb;
-    bool error;
-    std::string error_name;
-    bool result;
-} is_solid_image_baton_t;
-
-/**
- * Test if an image's pixels are all exactly the same
- * @name isSolid
- * @memberof Image
- * @instance
- * @returns {boolean} `true` means all pixels are exactly the same
- * @example
- * var img = new mapnik.Image(2,2);
- * console.log(img.isSolid()); // true
- *
- * // change a pixel
- * img.setPixel(0,0, new mapnik.Color('green'));
- * console.log(img.isSolid()); // false
- */
-NAN_METHOD(Image::isSolid)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-
-    if (info.Length() == 0) {
-        info.GetReturnValue().Set(_isSolidSync(info));
-        return;
-    }
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length() - 1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-
-    is_solid_image_baton_t *closure = new is_solid_image_baton_t();
-    closure->request.data = closure;
-    closure->im = im;
-    closure->result = true;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_IsSolid, (uv_after_work_cb)EIO_AfterIsSolid);
-    im->Ref();
-    return;
-}
-
-void Image::EIO_IsSolid(uv_work_t* req)
-{
-    is_solid_image_baton_t *closure = static_cast<is_solid_image_baton_t *>(req->data);
-    if (closure->im->this_->width() > 0 && closure->im->this_->height() > 0)
-    {
-        closure->result = mapnik::is_solid(*(closure->im->this_));
-    }
-    else
-    {
-        closure->error = true;
-        closure->error_name = "image does not have valid dimensions";
-    }
-}
-
-void Image::EIO_AfterIsSolid(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    is_solid_image_baton_t *closure = static_cast<is_solid_image_baton_t *>(req->data);
-    if (closure->error) {
-        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    else
-    {
-        if (closure->result)
-        {
-            v8::Local<v8::Value> argv[3] = { Nan::Null(),
-                                     Nan::New(closure->result),
-                                     mapnik::util::apply_visitor(visitor_get_pixel(0,0),*(closure->im->this_)),
-            };
-            Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 3, argv);
-        }
-        else
-        {
-            v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::New(closure->result) };
-            Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-        }
-    }
-    closure->im->Unref();
-    closure->cb.Reset();
-    delete closure;
-}
-
-/**
- * Determine whether the image is solid - whether it has alpha values of greater
- * than one.
- *
- * @name isSolidSync
- * @returns {boolean} whether the image is solid
- * @instance
- * @memberof Image
- * @example
- * var img = new mapnik.Image(256, 256);
- * var view = img.view(0, 0, 256, 256);
- * console.log(view.isSolidSync()); // true
- */
-NAN_METHOD(Image::isSolidSync)
-{
-    info.GetReturnValue().Set(_isSolidSync(info));
-}
-
-v8::Local<v8::Value> Image::_isSolidSync(Nan::NAN_METHOD_ARGS_TYPE info)
-{
-    Nan::EscapableHandleScope scope;
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    if (im->this_->width() > 0 && im->this_->height() > 0)
-    {
-        return scope.Escape(Nan::New<v8::Boolean>(mapnik::is_solid(*(im->this_))));
-    }
-    Nan::ThrowError("image does not have valid dimensions");
-    return scope.Escape(Nan::Undefined());
-}
-
-typedef struct {
-    uv_work_t request;
-    Image* im1;
-    image_ptr im2;
-    mapnik::image_dtype type;
-    double offset;
-    double scaling;
-    Nan::Persistent<v8::Function> cb;
-    bool error;
-    std::string error_name;
-} copy_image_baton_t;
-
-/**
- * Copy an image into a new image by creating a clone
- * @name copy
- * @instance
- * @memberof Image
- * @param {number} type - image type to clone into, can be any mapnik.imageType number
- * @param {Object} [options={}]
- * @param {number} [options.scaling] - scale the image
- * @param {number} [options.offset] - offset this image
- * @param {Function} callback
- * @example
- * var img = new mapnik.Image(4, 4, {type: mapnik.imageType.gray16});
- * var img2 = img.copy(mapnik.imageType.gray8, function(err, img2) {
- *   if (err) throw err;
- *   // custom code with `img2` converted into gray8 type
- * });
- */
-NAN_METHOD(Image::copy)
-{
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length() - 1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        info.GetReturnValue().Set(_copySync(info));
-        return;
-    }
-
-    Image* im1 = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    double offset = 0.0;
-    bool scaling_or_offset_set = false;
-    double scaling = 1.0;
-    mapnik::image_dtype type = im1->this_->get_dtype();
-    v8::Local<v8::Object> options = Nan::New<v8::Object>();
-
-    if (info.Length() >= 2)
-    {
-        if (info[0]->IsNumber())
-        {
-            type = static_cast<mapnik::image_dtype>(info[0]->IntegerValue());
-            if (type >= mapnik::image_dtype::IMAGE_DTYPE_MAX)
-            {
-                Nan::ThrowTypeError("Image 'type' must be a valid image type");
-                return;
-            }
-        }
-        else if (info[0]->IsObject())
-        {
-            options = info[0]->ToObject();
-        }
-        else
-        {
-            Nan::ThrowTypeError("Unknown parameters passed");
-            return;
-        }
-    }
-    if (info.Length() >= 3)
-    {
-        if (info[1]->IsObject())
-        {
-            options = info[1]->ToObject();
-        }
-        else
-        {
-            Nan::ThrowTypeError("Expected options object as second argument");
-            return;
-        }
-    }
-
-    if (options->Has(Nan::New("scaling").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> scaling_val = options->Get(Nan::New("scaling").ToLocalChecked());
-        if (scaling_val->IsNumber())
-        {
-            scaling = scaling_val->NumberValue();
-            scaling_or_offset_set = true;
-        }
-        else
-        {
-            Nan::ThrowTypeError("scaling argument must be a number");
-            return;
-        }
-    }
-
-    if (options->Has(Nan::New("offset").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> offset_val = options->Get(Nan::New("offset").ToLocalChecked());
-        if (offset_val->IsNumber())
-        {
-            offset = offset_val->NumberValue();
-            scaling_or_offset_set = true;
-        }
-        else
-        {
-            Nan::ThrowTypeError("offset argument must be a number");
-            return;
-        }
-    }
-
-    if (!scaling_or_offset_set && type == im1->this_->get_dtype())
-    {
-        scaling = im1->this_->get_scaling();
-        offset = im1->this_->get_offset();
-    }
-
-    copy_image_baton_t *closure = new copy_image_baton_t();
-    closure->request.data = closure;
-    closure->im1 = im1;
-    closure->offset = offset;
-    closure->scaling = scaling;
-    closure->type = type;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Copy, (uv_after_work_cb)EIO_AfterCopy);
-    closure->im1->Ref();
-    return;
-}
-
-void Image::EIO_Copy(uv_work_t* req)
-{
-    copy_image_baton_t *closure = static_cast<copy_image_baton_t *>(req->data);
-    try
-    {
-        closure->im2 = std::make_shared<mapnik::image_any>(
-                               mapnik::image_copy(*(closure->im1->this_),
-                                                            closure->type,
-                                                            closure->offset,
-                                                            closure->scaling)
-                               );
-    }
-    catch (std::exception const& ex)
-    {
-        closure->error = true;
-        closure->error_name = ex.what();
-    }
-}
-
-void Image::EIO_AfterCopy(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    copy_image_baton_t *closure = static_cast<copy_image_baton_t *>(req->data);
-    if (closure->error)
-    {
-        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    else if (!closure->im2)
-    {
-        // Not quite sure if this is even required or ever can be reached, but leaving it
-        // and simply removing it from coverage tests.
-        /* LCOV_EXCL_START */
-        v8::Local<v8::Value> argv[1] = { Nan::Error("could not render to image") };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-        /* LCOV_EXCL_STOP */
-    }
-    else
-    {
-        Image* im = new Image(closure->im2);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-    closure->im1->Unref();
-    closure->cb.Reset();
-    delete closure;
-}
-
-/**
- * Copy an image into a new image by creating a clone
- * @name copySync
- * @instance
- * @memberof Image
- * @param {number} type - image type to clone into, can be any mapnik.imageType number
- * @param {Object} [options={}]
- * @param {number} [options.scaling] - scale the image
- * @param {number} [options.offset] - offset this image
- * @returns {mapnik.Image} copy
- * @example
- * var img = new mapnik.Image(4, 4, {type: mapnik.imageType.gray16});
- * var img2 = img.copy(mapnik.imageType.gray8);
- * // custom code with `img2` as a gray8 type
- */
-NAN_METHOD(Image::copySync)
-{
-    info.GetReturnValue().Set(_copySync(info));
-}
-
-v8::Local<v8::Value> Image::_copySync(Nan::NAN_METHOD_ARGS_TYPE info)
-{
-    Nan::EscapableHandleScope scope;
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    double offset = 0.0;
-    bool scaling_or_offset_set = false;
-    double scaling = 1.0;
-    mapnik::image_dtype type = im->this_->get_dtype();
-    v8::Local<v8::Object> options = Nan::New<v8::Object>();
-    if (info.Length() >= 1)
-    {
-        if (info[0]->IsNumber())
-        {
-            type = static_cast<mapnik::image_dtype>(info[0]->IntegerValue());
-            if (type >= mapnik::image_dtype::IMAGE_DTYPE_MAX)
-            {
-                Nan::ThrowTypeError("Image 'type' must be a valid image type");
-                return scope.Escape(Nan::Undefined());
-            }
-        }
-        else if (info[0]->IsObject())
-        {
-            options = info[0]->ToObject();
-        }
-        else
-        {
-            Nan::ThrowTypeError("Unknown parameters passed");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-    if (info.Length() >= 2)
-    {
-        if (info[1]->IsObject())
-        {
-            options = info[1]->ToObject();
-        }
-        else
-        {
-            Nan::ThrowTypeError("Expected options object as second argument");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-
-    if (options->Has(Nan::New("scaling").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> scaling_val = options->Get(Nan::New("scaling").ToLocalChecked());
-        if (scaling_val->IsNumber())
-        {
-            scaling = scaling_val->NumberValue();
-            scaling_or_offset_set = true;
-        }
-        else
-        {
-            Nan::ThrowTypeError("scaling argument must be a number");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-
-    if (options->Has(Nan::New("offset").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> offset_val = options->Get(Nan::New("offset").ToLocalChecked());
-        if (offset_val->IsNumber())
-        {
-            offset = offset_val->NumberValue();
-            scaling_or_offset_set = true;
-        }
-        else
-        {
-            Nan::ThrowTypeError("offset argument must be a number");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-
-    if (!scaling_or_offset_set && type == im->this_->get_dtype())
-    {
-        scaling = im->this_->get_scaling();
-        offset = im->this_->get_offset();
-    }
-
-    try
-    {
-        image_ptr imagep = std::make_shared<mapnik::image_any>(
-                                                       mapnik::image_copy(*(im->this_),
-                                                                            type,
-                                                                            offset,
-                                                                            scaling)
-                                                    );
-        Image* new_im = new Image(imagep);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(new_im);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        return scope.Escape(maybe_local.ToLocalChecked());
-    }
-    catch (std::exception const& ex)
-    {
-        Nan::ThrowError(ex.what());
-        return scope.Escape(Nan::Undefined());
-    }
-}
-
-typedef struct {
-    uv_work_t request;
-    Image* im1;
-    image_ptr im2;
-    mapnik::scaling_method_e scaling_method;
-    std::size_t size_x;
-    std::size_t size_y;
-    int offset_x;
-    int offset_y;
-    double filter_factor;
-    Nan::Persistent<v8::Function> cb;
-    bool error;
-    std::string error_name;
-} resize_image_baton_t;
-
-/**
- * Resize this image (makes a copy)
- *
- * @name resize
- * @instance
- * @memberof Image
- * @param {number} width - in pixels
- * @param {number} height - in pixels
- * @param {Object} [options={}]
- * @param {number} [options.offset_x=0] - offset the image horizontally in pixels
- * @param {number} [options.offset_y=0] - offset the image vertically in pixels
- * @param {mapnik.imageScaling} [options.scaling_method=mapnik.imageScaling.near] - scaling method
- * @param {number} [options.filter_factor=1.0]
- * @param {Function} callback - `function(err, result)`
- * @example
- * var img = new mapnik.Image(4, 4, {type: mapnik.imageType.gray8});
- * img.resize(8, 8, function(err, result) {
- *   if (err) throw err;
- *   // new image object as `result`
- * });
- */
-NAN_METHOD(Image::resize)
-{
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length() - 1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        info.GetReturnValue().Set(_resizeSync(info));
-        return;
-    }
-    Image* im1 = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    std::size_t width = 0;
-    std::size_t height = 0;
-    int offset_x = 0;
-    int offset_y = 0;
-    double filter_factor = 1.0;
-    mapnik::scaling_method_e scaling_method = mapnik::SCALING_NEAR;
-    v8::Local<v8::Object> options = Nan::New<v8::Object>();
-
-    if (info.Length() >= 3)
-    {
-        if (info[0]->IsNumber())
-        {
-            auto width_tmp = info[0]->IntegerValue();
-            if (width_tmp <= 0)
-            {
-                Nan::ThrowTypeError("Width must be a integer greater then zero");
-                return;
-            }
-            width = static_cast<std::size_t>(width_tmp);
-        }
-        else
-        {
-            Nan::ThrowTypeError("Width must be a number");
-            return;
-        }
-        if (info[1]->IsNumber())
-        {
-            auto height_tmp = info[1]->IntegerValue();
-            if (height_tmp <= 0)
-            {
-                Nan::ThrowTypeError("Height must be a integer greater then zero");
-                return;
-            }
-            height = static_cast<std::size_t>(height_tmp);
-        }
-        else
-        {
-            Nan::ThrowTypeError("Height must be a number");
-            return;
-        }
-    }
-    else
-    {
-        Nan::ThrowTypeError("resize requires a width and height parameter.");
-        return;
-    }
-    if (info.Length() >= 4)
-    {
-        if (info[2]->IsObject())
-        {
-            options = info[2]->ToObject();
-        }
-        else
-        {
-            Nan::ThrowTypeError("Expected options object as third argument");
-            return;
-        }
-    }
-    if (options->Has(Nan::New("offset_x").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> bind_opt = options->Get(Nan::New("offset_x").ToLocalChecked());
-        if (!bind_opt->IsNumber())
-        {
-            Nan::ThrowTypeError("optional arg 'offset_x' must be a number");
-            return;
-        }
-        offset_x = bind_opt->IntegerValue();
-    }
-    if (options->Has(Nan::New("offset_y").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> bind_opt = options->Get(Nan::New("offset_y").ToLocalChecked());
-        if (!bind_opt->IsNumber())
-        {
-            Nan::ThrowTypeError("optional arg 'offset_y' must be a number");
-            return;
-        }
-        offset_y = bind_opt->IntegerValue();
-    }
-    if (options->Has(Nan::New("scaling_method").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> scaling_val = options->Get(Nan::New("scaling_method").ToLocalChecked());
-        if (scaling_val->IsNumber())
-        {
-            std::int64_t scaling_int = scaling_val->IntegerValue();
-            if (scaling_int > mapnik::SCALING_BLACKMAN || scaling_int < 0)
-            {
-                Nan::ThrowTypeError("Invalid scaling_method");
-                return;
-            }
-            scaling_method = static_cast<mapnik::scaling_method_e>(scaling_int);
-        }
-        else
-        {
-            Nan::ThrowTypeError("scaling_method argument must be an integer");
-            return;
-        }
-    }
-
-    if (options->Has(Nan::New("filter_factor").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> ff_val = options->Get(Nan::New("filter_factor").ToLocalChecked());
-        if (ff_val->IsNumber())
-        {
-            filter_factor = ff_val->NumberValue();
-        }
-        else
-        {
-            Nan::ThrowTypeError("filter_factor argument must be a number");
-            return;
-        }
-    }
-    resize_image_baton_t *closure = new resize_image_baton_t();
-    closure->request.data = closure;
-    closure->im1 = im1;
-    closure->scaling_method = scaling_method;
-    closure->size_x = width;
-    closure->size_y = height;
-    closure->offset_x = offset_x;
-    closure->offset_y = offset_y;
-    closure->filter_factor = filter_factor;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Resize, (uv_after_work_cb)EIO_AfterResize);
-    closure->im1->Ref();
-    return;
-}
-
-struct resize_visitor
-{
-
-    resize_visitor(mapnik::image_any const& im1,
-                   mapnik::scaling_method_e scaling_method,
-                   double image_ratio_x,
-                   double image_ratio_y,
-                   double filter_factor,
-                   int offset_x,
-                   int offset_y) :
-        im1_(im1),
-        scaling_method_(scaling_method),
-        image_ratio_x_(image_ratio_x),
-        image_ratio_y_(image_ratio_y),
-        filter_factor_(filter_factor),
-        offset_x_(offset_x),
-        offset_y_(offset_y) {}
-
-    void operator()(mapnik::image_rgba8 & im2) const
-    {
-        if (!im1_.get_premultiplied())
-        {
-            throw std::runtime_error("RGBA8 images must be premultiplied prior to using resize");
-        }
-        mapnik::scale_image_agg(im2,
-                                mapnik::util::get<mapnik::image_rgba8>(im1_),
-                                scaling_method_,
-                                image_ratio_x_,
-                                image_ratio_y_,
-                                offset_x_,
-                                offset_y_,
-                                filter_factor_);
-    }
-
-    template <typename T>
-    void operator()(T & im2) const
-    {
-        mapnik::scale_image_agg(im2,
-                                mapnik::util::get<T>(im1_),
-                                scaling_method_,
-                                image_ratio_x_,
-                                image_ratio_y_,
-                                offset_x_,
-                                offset_y_,
-                                filter_factor_);
-    }
-
-    void operator()(mapnik::image_null &) const
-    {
-        // Should be caught earlier so no test coverage should reach here.
-        /* LCOV_EXCL_START */
-        throw std::runtime_error("Can not resize null images");
-        /* LCOV_EXCL_STOP */
-    }
-
-    void operator()(mapnik::image_gray8s &) const
-    {
-        throw std::runtime_error("Mapnik currently does not support resizing signed 8 bit integer rasters");
-    }
-
-    void operator()(mapnik::image_gray16s &) const
-    {
-        throw std::runtime_error("Mapnik currently does not support resizing signed 16 bit integer rasters");
-    }
-
-    void operator()(mapnik::image_gray32 &) const
-    {
-        throw std::runtime_error("Mapnik currently does not support resizing unsigned 32 bit integer rasters");
-    }
-
-    void operator()(mapnik::image_gray32s &) const
-    {
-        throw std::runtime_error("Mapnik currently does not support resizing signed 32 bit integer rasters");
-    }
-
-    void operator()(mapnik::image_gray64 &) const
-    {
-        throw std::runtime_error("Mapnik currently does not support resizing unsigned 64 bit integer rasters");
-    }
-
-    void operator()(mapnik::image_gray64s &) const
-    {
-        throw std::runtime_error("Mapnik currently does not support resizing signed 64 bit integer rasters");
-    }
-
-    void operator()(mapnik::image_gray64f &) const
-    {
-        throw std::runtime_error("Mapnik currently does not support resizing 64 bit floating point rasters");
-    }
-
-
-  private:
-    mapnik::image_any const & im1_;
-    mapnik::scaling_method_e scaling_method_;
-    double image_ratio_x_;
-    double image_ratio_y_;
-    double filter_factor_;
-    int offset_x_;
-    int offset_y_;
-
-};
-
-void Image::EIO_Resize(uv_work_t* req)
-{
-    resize_image_baton_t *closure = static_cast<resize_image_baton_t *>(req->data);
-    if (closure->im1->this_->is<mapnik::image_null>())
-    {
-        closure->error = true;
-        closure->error_name = "Can not resize a null image.";
-        return;
-    }
-    try
-    {
-        double offset = closure->im1->this_->get_offset();
-        double scaling = closure->im1->this_->get_scaling();
-
-        closure->im2 = std::make_shared<mapnik::image_any>(closure->size_x,
-                                                           closure->size_y,
-                                                           closure->im1->this_->get_dtype(),
-                                                           true,
-                                                           true,
-                                                           false);
-        closure->im2->set_offset(offset);
-        closure->im2->set_scaling(scaling);
-        int im_width = closure->im1->this_->width();
-        int im_height = closure->im1->this_->height();
-        if (im_width <= 0 || im_height <= 0)
-        {
-            closure->error = true;
-            closure->error_name = "Image width or height is zero or less then zero.";
-            return;
-        }
-        double image_ratio_x = static_cast<double>(closure->size_x) / im_width;
-        double image_ratio_y = static_cast<double>(closure->size_y) / im_height;
-        resize_visitor visit(*(closure->im1->this_),
-                             closure->scaling_method,
-                             image_ratio_x,
-                             image_ratio_y,
-                             closure->filter_factor,
-                             closure->offset_x,
-                             closure->offset_y);
-        mapnik::util::apply_visitor(visit, *(closure->im2));
-    }
-    catch (std::exception const& ex)
-    {
-        closure->error = true;
-        closure->error_name = ex.what();
-    }
-}
-
-void Image::EIO_AfterResize(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    resize_image_baton_t *closure = static_cast<resize_image_baton_t *>(req->data);
-    if (closure->error)
-    {
-        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    else
-    {
-        Image* im = new Image(closure->im2);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-    closure->im1->Unref();
-    closure->cb.Reset();
-    delete closure;
-}
-
-/**
- * Resize this image (makes a copy). Synchronous version of {@link mapnik.Image.resize}.
- *
- * @name resizeSync
- * @instance
- * @memberof Image
- * @param {number} width
- * @param {number} height
- * @param {Object} [options={}]
- * @param {number} [options.offset_x=0] - offset the image horizontally in pixels
- * @param {number} [options.offset_y=0] - offset the image vertically in pixels
- * @param {mapnik.imageScaling} [options.scaling_method=mapnik.imageScaling.near] - scaling method
- * @param {number} [options.filter_factor=1.0]
- * @returns {mapnik.Image} copy
- * @example
- * var img = new mapnik.Image(4, 4, {type: mapnik.imageType.gray8});
- * var img2 = img.resizeSync(8, 8);
- * // new copy as `img2`
- */
-NAN_METHOD(Image::resizeSync)
-{
-    info.GetReturnValue().Set(_resizeSync(info));
-}
-
-v8::Local<v8::Value> Image::_resizeSync(Nan::NAN_METHOD_ARGS_TYPE info)
-{
-    Nan::EscapableHandleScope scope;
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    std::size_t width = 0;
-    std::size_t height = 0;
-    double filter_factor = 1.0;
-    int offset_x = 0;
-    int offset_y = 0;
-    mapnik::scaling_method_e scaling_method = mapnik::SCALING_NEAR;
-    v8::Local<v8::Object> options = Nan::New<v8::Object>();
-    if (info.Length() >= 2)
-    {
-        if (info[0]->IsNumber())
-        {
-            int width_tmp = info[0]->IntegerValue();
-            if (width_tmp <= 0)
-            {
-                Nan::ThrowTypeError("Width parameter must be an integer greater then zero");
-                return scope.Escape(Nan::Undefined());
-            }
-            width = static_cast<std::size_t>(width_tmp);
-        }
-        else
-        {
-            Nan::ThrowTypeError("Width must be a number");
-            return scope.Escape(Nan::Undefined());
-        }
-        if (info[1]->IsNumber())
-        {
-            int height_tmp = info[1]->IntegerValue();
-            if (height_tmp <= 0)
-            {
-                Nan::ThrowTypeError("Height parameter must be an integer greater then zero");
-                return scope.Escape(Nan::Undefined());
-            }
-            height = static_cast<std::size_t>(height_tmp);
-        }
-        else
-        {
-            Nan::ThrowTypeError("Height must be a number");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-    else
-    {
-        Nan::ThrowTypeError("Resize requires at least a width and height parameter");
-        return scope.Escape(Nan::Undefined());
-    }
-    if (info.Length() >= 3)
-    {
-        if (info[2]->IsObject())
-        {
-            options = info[2]->ToObject();
-        }
-        else
-        {
-            Nan::ThrowTypeError("Expected options object as third argument");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-    if (options->Has(Nan::New("offset_x").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> bind_opt = options->Get(Nan::New("offset_x").ToLocalChecked());
-        if (!bind_opt->IsNumber())
-        {
-            Nan::ThrowTypeError("optional arg 'offset_x' must be a number");
-            return scope.Escape(Nan::Undefined());
-        }
-        offset_x = bind_opt->IntegerValue();
-    }
-    if (options->Has(Nan::New("offset_y").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> bind_opt = options->Get(Nan::New("offset_y").ToLocalChecked());
-        if (!bind_opt->IsNumber())
-        {
-            Nan::ThrowTypeError("optional arg 'offset_y' must be a number");
-            return scope.Escape(Nan::Undefined());
-        }
-        offset_y = bind_opt->IntegerValue();
-    }
-
-    if (options->Has(Nan::New("scaling_method").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> scaling_val = options->Get(Nan::New("scaling_method").ToLocalChecked());
-        if (scaling_val->IsNumber())
-        {
-            std::int64_t scaling_int = scaling_val->IntegerValue();
-            if (scaling_int > mapnik::SCALING_BLACKMAN || scaling_int < 0)
-            {
-                Nan::ThrowTypeError("Invalid scaling_method");
-                return scope.Escape(Nan::Undefined());
-            }
-            scaling_method = static_cast<mapnik::scaling_method_e>(scaling_int);
-        }
-        else
-        {
-            Nan::ThrowTypeError("scaling_method argument must be a number");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-
-    if (options->Has(Nan::New("filter_factor").ToLocalChecked()))
-    {
-        v8::Local<v8::Value> ff_val = options->Get(Nan::New("filter_factor").ToLocalChecked());
-        if (ff_val->IsNumber())
-        {
-            filter_factor = ff_val->NumberValue();
-        }
-        else
-        {
-            Nan::ThrowTypeError("filter_factor argument must be a number");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-
-    if (im->this_->is<mapnik::image_null>())
-    {
-        Nan::ThrowTypeError("Can not resize a null image");
-        return scope.Escape(Nan::Undefined());
-    }
-    int im_width = im->this_->width();
-    int im_height = im->this_->height();
-    if (im_width <= 0 || im_height <= 0)
-    {
-        Nan::ThrowTypeError("Image width or height is zero or less then zero.");
-        return scope.Escape(Nan::Undefined());
-    }
-    try
-    {
-        double offset = im->this_->get_offset();
-        double scaling = im->this_->get_scaling();
-
-        image_ptr imagep = std::make_shared<mapnik::image_any>(width,
-                                                           height,
-                                                           im->this_->get_dtype(),
-                                                           true,
-                                                           true,
-                                                           false);
-        imagep->set_offset(offset);
-        imagep->set_scaling(scaling);
-        double image_ratio_x = static_cast<double>(width) / im_width;
-        double image_ratio_y = static_cast<double>(height) / im_height;
-        resize_visitor visit(*(im->this_),
-                             scaling_method,
-                             image_ratio_x,
-                             image_ratio_y,
-                             filter_factor,
-                             offset_x,
-                             offset_y);
-        mapnik::util::apply_visitor(visit, *imagep);
-        Image* new_im = new Image(imagep);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(new_im);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        return scope.Escape(maybe_local.ToLocalChecked());
-    }
-    catch (std::exception const& ex)
-    {
-        Nan::ThrowError(ex.what());
-        return scope.Escape(Nan::Undefined());
-    }
-}
-
-/**
- * Check if this image is painted. "Painted" refers to if it has
- * data or not. An image created with `new mapnik.Image(4,4)` defaults to
- * `false` since we loaded a new image without rendering and have no idea
- * if it was painted or not. You can run `new mapnik.Image(4, 4, {painted: true})`
- * to manually set the `painted` value.
- *
- * @name painted
- * @instance
- * @memberof Image
- * @returns {boolean} whether it is painted or not
- * @example
- * var img = new mapnik.Image(5,5);
- * console.log(img.painted()); // false
- */
-NAN_METHOD(Image::painted)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    info.GetReturnValue().Set(Nan::New<v8::Boolean>(im->this_->painted()));
-}
-
-/**
- * Get this image's width in pixels
- *
- * @name width
- * @instance
- * @memberof Image
- * @returns {number} width
- * @example
- * var img = new mapnik.Image(4,4);
- * console.log(img.width()); // 4
- */
-NAN_METHOD(Image::width)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    info.GetReturnValue().Set(Nan::New<v8::Int32>(static_cast<std::int32_t>(im->this_->width())));
-}
-
-/**
- * Get this image's height in pixels
- *
- * @name height
- * @instance
- * @memberof Image
- * @returns {number} height
- * @example
- * var img = new mapnik.Image(4,4);
- * console.log(img.height()); // 4
- */
-NAN_METHOD(Image::height)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    info.GetReturnValue().Set(Nan::New<v8::Int32>(static_cast<std::int32_t>(im->this_->height())));
-}
-
-/**
- * Load in a pre-existing image as an image object
- * @name openSync
- * @memberof Image
- * @instance
- * @param {string} path - path to the image you want to load
- * @returns {mapnik.Image} new image object based on existing image
- * @example
- * var img = new mapnik.Image.open('./path/to/image.jpg');
- */
-NAN_METHOD(Image::openSync)
-{
-    info.GetReturnValue().Set(_openSync(info));
-}
-
-v8::Local<v8::Value> Image::_openSync(Nan::NAN_METHOD_ARGS_TYPE info)
-{
-    Nan::EscapableHandleScope scope;
-
-    if (info.Length() < 1) {
-        Nan::ThrowError("must provide a string argument");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    if (!info[0]->IsString()) {
-        Nan::ThrowTypeError("Argument must be a string");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    try
-    {
-        std::string filename = TOSTR(info[0]);
-        boost::optional<std::string> type = mapnik::type_from_filename(filename);
-        if (type)
-        {
-            std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(filename,*type));
-            if (reader.get())
-            {
-                image_ptr imagep = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(), reader->height()));
-                if (!reader->has_alpha())
-                {
-                    mapnik::set_premultiplied_alpha(*imagep, true);
-                }
-                Image* im = new Image(imagep);
-                v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-                Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-                if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-                return scope.Escape(maybe_local.ToLocalChecked());
-            }
-        }
-        Nan::ThrowTypeError(("Unsupported image format:" + filename).c_str());
-        return scope.Escape(Nan::Undefined());
-    }
-    catch (std::exception const& ex)
-    {
-        Nan::ThrowError(ex.what());
-        return scope.Escape(Nan::Undefined());
-    }
-}
-
-typedef struct {
-    uv_work_t request;
-    image_ptr im;
-    std::string error_name;
-    Nan::Persistent<v8::Object> buffer;
-    Nan::Persistent<v8::Function> cb;
-    bool premultiply;
-    std::uint32_t max_size;
-    const char *data;
-    size_t dataLength;
-    bool error;
-} image_mem_ptr_baton_t;
-
-typedef struct {
-    uv_work_t request;
-    image_ptr im;
-    std::string filename;
-    bool error;
-    std::string error_name;
-    Nan::Persistent<v8::Function> cb;
-} image_file_ptr_baton_t;
-
-/**
- * Load in a pre-existing image as an image object
- * @name open
- * @memberof Image
- * @static
- * @param {string} path - path to the image you want to load
- * @param {Function} callback -
- * @example
- * mapnik.Image.open('./path/to/image.jpg', function(err, img) {
- *   if (err) throw err;
- *   // img is now an Image object
- * });
- */
-NAN_METHOD(Image::open)
-{
-    if (info.Length() == 1) {
-        info.GetReturnValue().Set(_openSync(info));
-        return;
-    }
-
-    if (info.Length() < 2) {
-        Nan::ThrowError("must provide a string argument");
-        return;
-    }
-
-    if (!info[0]->IsString()) {
-        Nan::ThrowTypeError("Argument must be a string");
-        return;
-    }
-
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length()-1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-
-    image_file_ptr_baton_t *closure = new image_file_ptr_baton_t();
-    closure->request.data = closure;
-    closure->filename = TOSTR(info[0]);
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Open, (uv_after_work_cb)EIO_AfterOpen);
-    return;
-}
-
-void Image::EIO_Open(uv_work_t* req)
-{
-    image_file_ptr_baton_t *closure = static_cast<image_file_ptr_baton_t *>(req->data);
-
-    try
-    {
-        boost::optional<std::string> type = mapnik::type_from_filename(closure->filename);
-        if (!type)
-        {
-            closure->error = true;
-            closure->error_name = "Unsupported image format: " + closure->filename;
-        }
-        else
-        {
-            std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(closure->filename,*type));
-            if (reader.get())
-            {
-                closure->im = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(),reader->height()));
-                if (!reader->has_alpha())
-                {
-                    mapnik::set_premultiplied_alpha(*(closure->im), true);
-                }
-            }
-            else
-            {
-                // The only way this is ever reached is if the reader factory in
-                // mapnik was not providing an image type it should. This should never
-                // be occuring so marking this out from coverage
-                /* LCOV_EXCL_START */
-                closure->error = true;
-                closure->error_name = "Failed to load: " + closure->filename;
-                /* LCOV_EXCL_STOP */
-            }
-        }
-    }
-    catch (std::exception const& ex)
-    {
-        closure->error = true;
-        closure->error_name = ex.what();
-    }
-}
-
-void Image::EIO_AfterOpen(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    image_file_ptr_baton_t *closure = static_cast<image_file_ptr_baton_t *>(req->data);
-    if (closure->error || !closure->im)
-    {
-        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    else
-    {
-        Image* im = new Image(closure->im);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-    closure->cb.Reset();
-    delete closure;
-}
-
-/**
- * Load image from an SVG buffer (synchronous)
- * @name fromSVGBytesSync
- * @memberof Image
- * @static
- * @param {string} path - path to SVG image
- * @param {Object} [options]
- * @param {number} [options.scale] - scale the image. For example passing `0.5` as scale would render
- * your SVG at 50% the original size.
- * @param {number} [options.max_size] - the maximum allowed size of the svg dimensions * scale. The default is 2048.
- * This option can be passed a smaller or larger size in order to control the final size of the image allocated for
- * rasterizing the SVG.
- * @returns {mapnik.Image} Image object
- * @example
- * var buffer = fs.readFileSync('./path/to/image.svg');
- * var img = mapnik.Image.fromSVGBytesSync(buffer);
- */
-NAN_METHOD(Image::fromSVGBytesSync)
-{
-    info.GetReturnValue().Set(_fromSVGSync(false, info));
-}
-
-/**
- * Create a new image from an SVG file (synchronous)
- *
- * @name fromSVGSync
- * @param {string} filename
- * @param {Object} [options]
- * @param {number} [options.scale] - scale the image. For example passing `0.5` as scale would render
- * your SVG at 50% the original size.
- * @param {number} [options.max_size] - the maximum allowed size of the svg dimensions * scale. The default is 2048.
- * This option can be passed a smaller or larger size in order to control the final size of the image allocated for
- * rasterizing the SVG.
- * @returns {mapnik.Image} image object
- * @static
- * @memberof Image
- * @example
- * var img = mapnik.Image.fromSVG('./path/to/image.svg');
- */
-NAN_METHOD(Image::fromSVGSync)
-{
-    info.GetReturnValue().Set(_fromSVGSync(true, info));
-}
-
-v8::Local<v8::Value> Image::_fromSVGSync(bool fromFile, Nan::NAN_METHOD_ARGS_TYPE info)
-{
-    Nan::EscapableHandleScope scope;
-
-    if (!fromFile && (info.Length() < 1 || !info[0]->IsObject()))
-    {
-        Nan::ThrowTypeError("must provide a buffer argument");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    if (fromFile && (info.Length() < 1 || !info[0]->IsString()))
-    {
-        Nan::ThrowTypeError("must provide a filename argument");
-        return scope.Escape(Nan::Undefined());
-    }
-
-
-    double scale = 1.0;
-    std::uint32_t max_size = 2048;
-    if (info.Length() >= 2)
-    {
-        if (!info[1]->IsObject())
-        {
-            Nan::ThrowTypeError("optional second arg must be an options object");
-            return scope.Escape(Nan::Undefined());
-        }
-        v8::Local<v8::Object> options = info[1]->ToObject();
-        if (options->Has(Nan::New("scale").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> scale_opt = options->Get(Nan::New("scale").ToLocalChecked());
-            if (!scale_opt->IsNumber())
-            {
-                Nan::ThrowTypeError("'scale' must be a number");
-                return scope.Escape(Nan::Undefined());
-            }
-            scale = scale_opt->NumberValue();
-            if (scale <= 0)
-            {
-                Nan::ThrowTypeError("'scale' must be a positive non zero number");
-                return scope.Escape(Nan::Undefined());
-            }
-        }
-        if (options->Has(Nan::New("max_size").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
-            if (!opt->IsNumber())
-            {
-                Nan::ThrowTypeError("max_size must be a positive integer");
-                return scope.Escape(Nan::Undefined());
-            }
-            auto max_size_val = opt->IntegerValue();
-            if (max_size_val < 0 || max_size_val > 65535) {
-                Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
-                return scope.Escape(Nan::Undefined());
-            }
-            max_size = static_cast<std::uint32_t>(max_size_val);
-        }
-    }
-
-    try
-    {
-        using namespace mapnik::svg;
-        mapnik::svg_path_ptr marker_path(std::make_shared<mapnik::svg_storage_type>());
-        vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source());
-        svg_path_adapter svg_path(stl_storage);
-        svg_converter_type svg(svg_path, marker_path->attributes());
-        svg_parser p(svg);
-        if (fromFile)
-        {
-            if (!p.parse(TOSTR(info[0])))
-            {
-                std::ostringstream errorMessage("");
-                errorMessage << "SVG parse error:" << std::endl;
-                for (auto const& error : p.err_handler().error_messages()) {
-                    errorMessage <<  error << std::endl;
-                }
-                Nan::ThrowTypeError(errorMessage.str().c_str());
-                return scope.Escape(Nan::Undefined());
-            }
-        }
-        else
-        {
-            v8::Local<v8::Object> obj = info[0]->ToObject();
-            if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj))
-            {
-                Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
-                return scope.Escape(Nan::Undefined());
-            }
-            std::string svg_buffer(node::Buffer::Data(obj),node::Buffer::Length(obj));
-            if (!p.parse_from_string(svg_buffer))
-            {
-                std::ostringstream errorMessage("");
-                errorMessage << "SVG parse error:" << std::endl;
-                for (auto const& error : p.err_handler().error_messages()) {
-                    errorMessage <<  error << std::endl;
-                }
-                Nan::ThrowTypeError(errorMessage.str().c_str());
-                return scope.Escape(Nan::Undefined());
-            }
-        }
-
-        double lox,loy,hix,hiy;
-        svg.bounding_rect(&lox, &loy, &hix, &hiy);
-        marker_path->set_bounding_box(lox,loy,hix,hiy);
-        marker_path->set_dimensions(svg.width(),svg.height());
-
-        using pixfmt = agg::pixfmt_rgba32_pre;
-        using renderer_base = agg::renderer_base<pixfmt>;
-        using renderer_solid = agg::renderer_scanline_aa_solid<renderer_base>;
-        agg::rasterizer_scanline_aa<> ras_ptr;
-        agg::scanline_u8 sl;
-
-        double opacity = 1;
-        double svg_width = svg.width() * scale;
-        double svg_height = svg.height() * scale;
-
-        if (svg_width <= 0 || svg_height <= 0)
-        {
-            Nan::ThrowTypeError("image created from svg must have a width and height greater then zero");
-            return scope.Escape(Nan::Undefined());
-        }
-
-        if (svg_width > static_cast<double>(max_size) || svg_height > static_cast<double>(max_size))
-        {
-            std::stringstream s;
-            s << "image created from svg must be " << max_size << " pixels or fewer on each side";
-            Nan::ThrowTypeError(s.str().c_str());
-            return scope.Escape(Nan::Undefined());
-        }
-
-        mapnik::image_rgba8 im(static_cast<int>(svg_width), static_cast<int>(svg_height), true, true);
-        agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size());
-        pixfmt pixf(buf);
-        renderer_base renb(pixf);
-
-        mapnik::box2d<double> const& bbox = marker_path->bounding_box();
-        mapnik::coord<double,2> c = bbox.center();
-        // center the svg marker on '0,0'
-        agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y);
-        // Scale the image
-        mtx.scale(scale);
-        // render the marker at the center of the marker box
-        mtx.translate(0.5 * im.width(), 0.5 * im.height());
-
-        mapnik::svg::svg_renderer_agg<mapnik::svg::svg_path_adapter,
-            agg::pod_bvector<mapnik::svg::path_attributes>,
-            renderer_solid,
-            agg::pixfmt_rgba32_pre > svg_renderer_this(svg_path,
-                                                       marker_path->attributes());
-
-        svg_renderer_this.render(ras_ptr, sl, renb, mtx, opacity, bbox);
-        mapnik::demultiply_alpha(im);
-
-        image_ptr imagep = std::make_shared<mapnik::image_any>(im);
-        Image *im2 = new Image(imagep);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(im2);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        return scope.Escape(maybe_local.ToLocalChecked());
-    }
-    catch (std::exception const& ex)
-    {
-        // There is currently no known way to make these operations throw an exception, however,
-        // since the underlying agg library does possibly have some operation that might throw
-        // it is a good idea to keep this. Therefore, any exceptions thrown will fail gracefully.
-        // LCOV_EXCL_START
-        Nan::ThrowError(ex.what());
-        return scope.Escape(Nan::Undefined());
-        // LCOV_EXCL_STOP
-    }
-}
-
-typedef struct {
-    uv_work_t request;
-    image_ptr im;
-    std::string filename;
-    bool error;
-    double scale;
-    std::uint32_t max_size;
-    std::string error_name;
-    Nan::Persistent<v8::Function> cb;
-} svg_file_ptr_baton_t;
-
-typedef struct {
-    uv_work_t request;
-    image_ptr im;
-    const char *data;
-    size_t dataLength;
-    bool error;
-    double scale;
-    std::uint32_t max_size;
-    std::string error_name;
-    Nan::Persistent<v8::Object> buffer;
-    Nan::Persistent<v8::Function> cb;
-} svg_mem_ptr_baton_t;
-
-/**
- * Create a new image from an SVG file
- *
- * @name fromSVG
- * @param {string} filename
- * @param {Object} [options]
- * @param {number} [options.scale] - scale the image. For example passing `0.5` as scale would render
- * your SVG at 50% the original size.
- * @param {number} [options.max_size] - the maximum allowed size of the svg dimensions * scale. The default is 2048.
- * This option can be passed a smaller or larger size in order to control the final size of the image allocated for
- * rasterizing the SVG.
- * @param {Function} callback
- * @static
- * @memberof Image
- * @example
- * mapnik.Image.fromSVG('./path/to/image.svg', {scale: 0.5}, function(err, img) {
- *   if (err) throw err;
- *   // new img object (at 50% scale)
- * });
- */
-NAN_METHOD(Image::fromSVG)
-{
-    if (info.Length() == 1) {
-        info.GetReturnValue().Set(_fromSVGSync(true, info));
-        return;
-    }
-
-    if (info.Length() < 2 || !info[0]->IsString())
-    {
-        Nan::ThrowTypeError("must provide a filename argument");
-        return;
-    }
-
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length() - 1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-
-    double scale = 1.0;
-    std::uint32_t max_size = 2048;
-    if (info.Length() >= 3)
-    {
-        if (!info[1]->IsObject())
-        {
-            Nan::ThrowTypeError("optional second arg must be an options object");
-            return;
-        }
-        v8::Local<v8::Object> options = info[1]->ToObject();
-        if (options->Has(Nan::New("scale").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> scale_opt = options->Get(Nan::New("scale").ToLocalChecked());
-            if (!scale_opt->IsNumber())
-            {
-                Nan::ThrowTypeError("'scale' must be a number");
-                return;
-            }
-            scale = scale_opt->NumberValue();
-            if (scale <= 0)
-            {
-                Nan::ThrowTypeError("'scale' must be a positive non zero number");
-                return;
-            }
-        }
-        if (options->Has(Nan::New("max_size").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
-            if (!opt->IsNumber())
-            {
-                Nan::ThrowTypeError("max_size must be a positive integer");
-                return;
-            }
-            auto max_size_val = opt->IntegerValue();
-            if (max_size_val < 0 || max_size_val > 65535) {
-                Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
-                return;
-            }
-            max_size = static_cast<std::uint32_t>(max_size_val);
-        }
-    }
-
-    svg_file_ptr_baton_t *closure = new svg_file_ptr_baton_t();
-    closure->request.data = closure;
-    closure->filename = TOSTR(info[0]);
-    closure->error = false;
-    closure->scale = scale;
-    closure->max_size = max_size;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_FromSVG, (uv_after_work_cb)EIO_AfterFromSVG);
-    return;
-}
-
-void Image::EIO_FromSVG(uv_work_t* req)
-{
-    svg_file_ptr_baton_t *closure = static_cast<svg_file_ptr_baton_t *>(req->data);
-
-    try
-    {
-        using namespace mapnik::svg;
-        mapnik::svg_path_ptr marker_path(std::make_shared<mapnik::svg_storage_type>());
-        vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source());
-        svg_path_adapter svg_path(stl_storage);
-        svg_converter_type svg(svg_path, marker_path->attributes());
-        svg_parser p(svg);
-        if (!p.parse(closure->filename))
-        {
-            std::ostringstream errorMessage("");
-            errorMessage << "SVG parse error:" << std::endl;
-            for (auto const& error : p.err_handler().error_messages()) {
-                errorMessage <<  error << std::endl;
-            }
-            closure->error = true;
-            closure->error_name = errorMessage.str();
-            return;
-        }
-
-        double lox,loy,hix,hiy;
-        svg.bounding_rect(&lox, &loy, &hix, &hiy);
-        marker_path->set_bounding_box(lox,loy,hix,hiy);
-        marker_path->set_dimensions(svg.width(),svg.height());
-
-        using pixfmt = agg::pixfmt_rgba32_pre;
-        using renderer_base = agg::renderer_base<pixfmt>;
-        using renderer_solid = agg::renderer_scanline_aa_solid<renderer_base>;
-        agg::rasterizer_scanline_aa<> ras_ptr;
-        agg::scanline_u8 sl;
-
-        double opacity = 1;
-
-        double svg_width = svg.width() * closure->scale;
-        double svg_height = svg.height() * closure->scale;
-
-        if (svg_width <= 0 || svg_height <= 0)
-        {
-            closure->error = true;
-            closure->error_name = "image created from svg must have a width and height greater then zero";
-            return;
-        }
-
-        if (svg_width > static_cast<double>(closure->max_size) || svg_height > static_cast<double>(closure->max_size))
-        {
-            closure->error = true;
-            std::stringstream s;
-            s << "image created from svg must be " << closure->max_size << " pixels or fewer on each side";
-            closure->error_name = s.str();
-            return;
-        }
-
-        mapnik::image_rgba8 im(static_cast<int>(svg_width), static_cast<int>(svg_height), true, true);
-        agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size());
-        pixfmt pixf(buf);
-        renderer_base renb(pixf);
-
-        mapnik::box2d<double> const& bbox = marker_path->bounding_box();
-        mapnik::coord<double,2> c = bbox.center();
-        // center the svg marker on '0,0'
-        agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y);
-        // Scale the image
-        mtx.scale(closure->scale);
-        // render the marker at the center of the marker box
-        mtx.translate(0.5 * im.width(), 0.5 * im.height());
-
-        mapnik::svg::svg_renderer_agg<mapnik::svg::svg_path_adapter,
-            agg::pod_bvector<mapnik::svg::path_attributes>,
-            renderer_solid,
-            agg::pixfmt_rgba32_pre > svg_renderer_this(svg_path,
-                                                       marker_path->attributes());
-
-        svg_renderer_this.render(ras_ptr, sl, renb, mtx, opacity, bbox);
-        mapnik::demultiply_alpha(im);
-        closure->im = std::make_shared<mapnik::image_any>(im);
-    }
-    catch (std::exception const& ex)
-    {
-        // There is currently no known way to make these operations throw an exception, however,
-        // since the underlying agg library does possibly have some operation that might throw
-        // it is a good idea to keep this. Therefore, any exceptions thrown will fail gracefully.
-        // LCOV_EXCL_START
-        closure->error = true;
-        closure->error_name = "Failed to load: " + closure->filename;
-        // LCOV_EXCL_STOP
-    }
-}
-
-void Image::EIO_AfterFromSVG(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    svg_file_ptr_baton_t *closure = static_cast<svg_file_ptr_baton_t *>(req->data);
-    if (closure->error || !closure->im)
-    {
-        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    else
-    {
-        Image* im = new Image(closure->im);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-    closure->cb.Reset();
-    delete closure;
-}
-
-/**
- * Load image from an SVG buffer
- * @name fromSVGBytes
- * @memberof Image
- * @static
- * @param {string} path - path to SVG image
- * @param {Object} [options]
- * @param {number} [options.scale] - scale the image. For example passing `0.5` as scale would render
- * your SVG at 50% the original size.
- * @param {number} [options.max_size] - the maximum allowed size of the svg dimensions * scale. The default is 2048.
- * This option can be passed a smaller or larger size in order to control the final size of the image allocated for
- * rasterizing the SVG.
- * @param {Function} callback = `function(err, img)`
- * @example
- * var buffer = fs.readFileSync('./path/to/image.svg');
- * mapnik.Image.fromSVGBytesSync(buffer, function(err, img) {
- *   if (err) throw err;
- *   // your custom code with `img`
- * });
- */
-NAN_METHOD(Image::fromSVGBytes)
-{
-    if (info.Length() == 1) {
-        info.GetReturnValue().Set(_fromSVGSync(false, info));
-        return;
-    }
-
-    if (info.Length() < 2 || !info[0]->IsObject()) {
-        Nan::ThrowError("must provide a buffer argument");
-        return;
-    }
-
-    v8::Local<v8::Object> obj = info[0]->ToObject();
-    if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
-        Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
-        return;
-    }
-
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length() - 1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-
-    double scale = 1.0;
-    std::uint32_t max_size = 2048;
-    if (info.Length() >= 3)
-    {
-        if (!info[1]->IsObject())
-        {
-            Nan::ThrowTypeError("optional second arg must be an options object");
-            return;
-        }
-        v8::Local<v8::Object> options = info[1]->ToObject();
-        if (options->Has(Nan::New("scale").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> scale_opt = options->Get(Nan::New("scale").ToLocalChecked());
-            if (!scale_opt->IsNumber())
-            {
-                Nan::ThrowTypeError("'scale' must be a number");
-                return;
-            }
-            scale = scale_opt->NumberValue();
-            if (scale <= 0)
-            {
-                Nan::ThrowTypeError("'scale' must be a positive non zero number");
-                return;
-            }
-        }
-        if (options->Has(Nan::New("max_size").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
-            if (!opt->IsNumber())
-            {
-                Nan::ThrowTypeError("max_size must be a positive integer");
-                return;
-            }
-            auto max_size_val = opt->IntegerValue();
-            if (max_size_val < 0 || max_size_val > 65535) {
-                Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
-                return;
-            }
-            max_size = static_cast<std::uint32_t>(max_size_val);
-        }
-    }
-
-    svg_mem_ptr_baton_t *closure = new svg_mem_ptr_baton_t();
-    closure->request.data = closure;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    closure->buffer.Reset(obj.As<v8::Object>());
-    closure->data = node::Buffer::Data(obj);
-    closure->scale = scale;
-    closure->max_size = max_size;
-    closure->dataLength = node::Buffer::Length(obj);
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_FromSVGBytes, (uv_after_work_cb)EIO_AfterFromSVGBytes);
-    return;
-}
-
-void Image::EIO_FromSVGBytes(uv_work_t* req)
-{
-    svg_mem_ptr_baton_t *closure = static_cast<svg_mem_ptr_baton_t *>(req->data);
-
-    try
-    {
-        using namespace mapnik::svg;
-        mapnik::svg_path_ptr marker_path(std::make_shared<mapnik::svg_storage_type>());
-        vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source());
-        svg_path_adapter svg_path(stl_storage);
-        svg_converter_type svg(svg_path, marker_path->attributes());
-        svg_parser p(svg);
-
-        std::string svg_buffer(closure->data,closure->dataLength);
-        if (!p.parse_from_string(svg_buffer))
-        {
-            std::ostringstream errorMessage("");
-            errorMessage << "SVG parse error:" << std::endl;
-            for (auto const& error : p.err_handler().error_messages()) {
-                errorMessage <<  error << std::endl;
-            }
-            closure->error = true;
-            closure->error_name = errorMessage.str();
-            return;
-        }
-
-        double lox,loy,hix,hiy;
-        svg.bounding_rect(&lox, &loy, &hix, &hiy);
-        marker_path->set_bounding_box(lox,loy,hix,hiy);
-        marker_path->set_dimensions(svg.width(),svg.height());
-
-        using pixfmt = agg::pixfmt_rgba32_pre;
-        using renderer_base = agg::renderer_base<pixfmt>;
-        using renderer_solid = agg::renderer_scanline_aa_solid<renderer_base>;
-        agg::rasterizer_scanline_aa<> ras_ptr;
-        agg::scanline_u8 sl;
-
-        double opacity = 1;
-        double svg_width = svg.width() * closure->scale;
-        double svg_height = svg.height() * closure->scale;
-
-        if (svg_width <= 0 || svg_height <= 0)
-        {
-            closure->error = true;
-            closure->error_name = "image created from svg must have a width and height greater then zero";
-            return;
-        }
-
-        if (svg_width > static_cast<double>(closure->max_size) || svg_height > static_cast<double>(closure->max_size))
-        {
-            closure->error = true;
-            std::stringstream s;
-            s << "image created from svg must be " << closure->max_size << " pixels or fewer on each side";
-            closure->error_name = s.str();
-            return;
-        }
-
-        mapnik::image_rgba8 im(static_cast<int>(svg_width), static_cast<int>(svg_height), true, true);
-        agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size());
-        pixfmt pixf(buf);
-        renderer_base renb(pixf);
-
-        mapnik::box2d<double> const& bbox = marker_path->bounding_box();
-        mapnik::coord<double,2> c = bbox.center();
-        // center the svg marker on '0,0'
-        agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y);
-        // Scale the image
-        mtx.scale(closure->scale);
-        // render the marker at the center of the marker box
-        mtx.translate(0.5 * im.width(), 0.5 * im.height());
-
-        mapnik::svg::svg_renderer_agg<mapnik::svg::svg_path_adapter,
-            agg::pod_bvector<mapnik::svg::path_attributes>,
-            renderer_solid,
-            agg::pixfmt_rgba32_pre > svg_renderer_this(svg_path,
-                                                       marker_path->attributes());
-
-        svg_renderer_this.render(ras_ptr, sl, renb, mtx, opacity, bbox);
-        mapnik::demultiply_alpha(im);
-        closure->im = std::make_shared<mapnik::image_any>(im);
-    }
-    catch (std::exception const& ex)
-    {
-        // There is currently no known way to make these operations throw an exception, however,
-        // since the underlying agg library does possibly have some operation that might throw
-        // it is a good idea to keep this. Therefore, any exceptions thrown will fail gracefully.
-        // LCOV_EXCL_START
-        closure->error = true;
-        closure->error_name = ex.what();
-        // LCOV_EXCL_STOP
-    }
-}
-
-void Image::EIO_AfterFromSVGBytes(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    svg_mem_ptr_baton_t *closure = static_cast<svg_mem_ptr_baton_t *>(req->data);
-    if (closure->error || !closure->im)
-    {
-        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    else
-    {
-        Image* im = new Image(closure->im);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-        v8::Local<v8::Object> image_obj = Nan::New(constructor)->GetFunction()->NewInstance(1, &ext);
-        v8::Local<v8::Value> argv[2] = { Nan::Null(), image_obj };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-    closure->cb.Reset();
-    closure->buffer.Reset();
-    delete closure;
-}
-
-/**
- * Create an image of the existing buffer.
- *
- * Note: the buffer must live as long as the image.
- * It is recommended that you do not use this method. Be warned!
- *
- * @name fromBufferSync
- * @param {number} width
- * @param {number} height
- * @param {Buffer} buffer
- * @returns {mapnik.Image} image object
- * @static
- * @memberof Image
- * @example
- * var img = new mapnik.Image.open('./path/to/image.png');
- * var buffer = img.data(); // returns data as buffer
- * var img2 = mapnik.Image.fromBufferSync(img.width(), img.height(), buffer);
- */
-NAN_METHOD(Image::fromBufferSync)
-{
-    info.GetReturnValue().Set(_fromBufferSync(info));
-}
-
-v8::Local<v8::Value> Image::_fromBufferSync(Nan::NAN_METHOD_ARGS_TYPE info)
-{
-    Nan::EscapableHandleScope scope;
-
-    if (info.Length() < 3 || !info[0]->IsNumber() || !info[1]->IsNumber() || !info[2]->IsObject()) {
-        Nan::ThrowTypeError("must provide a width, height, and buffer argument");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    unsigned width = info[0]->IntegerValue();
-    unsigned height = info[1]->IntegerValue();
-
-    if (width <= 0 || height <= 0)
-    {
-        Nan::ThrowTypeError("width and height must be greater then zero");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    v8::Local<v8::Object> obj = info[2]->ToObject();
-    if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
-        Nan::ThrowTypeError("third argument is invalid, must be a Buffer");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    // TODO - support other image types?
-    auto im_size = mapnik::image_rgba8::pixel_size * width * height;
-    if (im_size != node::Buffer::Length(obj)) {
-        Nan::ThrowTypeError("invalid image size");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    bool premultiplied = false;
-    bool painted = false;
-
-    if (info.Length() >= 4)
-    {
-        if (info[3]->IsObject())
-        {
-            v8::Local<v8::Object> options = v8::Local<v8::Object>::Cast(info[3]);
-            if (options->Has(Nan::New("type").ToLocalChecked()))
-            {
-                Nan::ThrowTypeError("'type' option not supported (only rgba images currently viable)");
-                return scope.Escape(Nan::Undefined());
-            }
-
-            if (options->Has(Nan::New("premultiplied").ToLocalChecked()))
-            {
-                v8::Local<v8::Value> pre_val = options->Get(Nan::New("premultiplied").ToLocalChecked());
-                if (!pre_val.IsEmpty() && pre_val->IsBoolean())
-                {
-                    premultiplied = pre_val->BooleanValue();
-                }
-                else
-                {
-                    Nan::ThrowTypeError("premultiplied option must be a boolean");
-                    return scope.Escape(Nan::Undefined());
-                }
-            }
-
-            if (options->Has(Nan::New("painted").ToLocalChecked()))
-            {
-                v8::Local<v8::Value> painted_val = options->Get(Nan::New("painted").ToLocalChecked());
-                if (!painted_val.IsEmpty() && painted_val->IsBoolean())
-                {
-                    painted = painted_val->BooleanValue();
-                }
-                else
-                {
-                    Nan::ThrowTypeError("painted option must be a boolean");
-                    return scope.Escape(Nan::Undefined());
-                }
-            }
-        }
-        else
-        {
-            Nan::ThrowTypeError("Options parameter must be an object");
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-
-    try
-    {
-        mapnik::image_rgba8 im_wrapper(width, height, reinterpret_cast<unsigned char*>(node::Buffer::Data(obj)), premultiplied, painted);
-        image_ptr imagep = std::make_shared<mapnik::image_any>(im_wrapper);
-        Image* im = new Image(imagep);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        v8::Local<v8::Object> image_obj = maybe_local.ToLocalChecked()->ToObject();
-        image_obj->Set(Nan::New("_buffer").ToLocalChecked(),obj);
-        return scope.Escape(maybe_local.ToLocalChecked());
-    }
-    catch (std::exception const& ex)
-    {
-        // There is no known way for this exception to be reached currently.
-        // LCOV_EXCL_START
-        Nan::ThrowError(ex.what());
-        return scope.Escape(Nan::Undefined());
-        // LCOV_EXCL_STOP
-    }
-}
-
-/**
- * Create an image from a byte stream buffer. (synchronous)
- *
- * @name fromBytesSync
- * @param {Buffer} buffer - image buffer
- * @returns {mapnik.Image} image object
- * @instance
- * @memberof Image
- * @example
- * var buffer = fs.readFileSync('./path/to/image.png');
- * var img = new mapnik.Image.fromBytesSync(buffer);
- */
-NAN_METHOD(Image::fromBytesSync)
-{
-    info.GetReturnValue().Set(_fromBytesSync(info));
-}
-
-v8::Local<v8::Value> Image::_fromBytesSync(Nan::NAN_METHOD_ARGS_TYPE info)
-{
-    Nan::EscapableHandleScope scope;
-
-    if (info.Length() < 1 || !info[0]->IsObject()) {
-        Nan::ThrowTypeError("must provide a buffer argument");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    v8::Local<v8::Object> obj = info[0]->ToObject();
-    if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
-        Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    try
-    {
-        std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(node::Buffer::Data(obj),node::Buffer::Length(obj)));
-        if (reader.get())
-        {
-            image_ptr imagep = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(),reader->height()));
-            Image* im = new Image(imagep);
-            v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-            Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-            if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-            return scope.Escape(maybe_local.ToLocalChecked());
-        }
-        // The only way this is ever reached is if the reader factory in
-        // mapnik was not providing an image type it should. This should never
-        // be occuring so marking this out from coverage
-        /* LCOV_EXCL_START */
-        Nan::ThrowTypeError("Failed to load from buffer");
-        return scope.Escape(Nan::Undefined());
-        /* LCOV_EXCL_STOP */
-    }
-    catch (std::exception const& ex)
-    {
-        Nan::ThrowError(ex.what());
-        return scope.Escape(Nan::Undefined());
-    }
-}
-
-/**
- * Create an image from a byte stream buffer.
- *
- * @name fromBytes
- * @param {Buffer} buffer - image buffer
- * @param {Object} [options]
- * @param {Boolean} [options.premultiply] - Default false, if true, then the image will be premultiplied before being returned
- * @param {Number} [options.max_size] - the maximum allowed size of the image dimensions. The default is 2048.
- * @param {Function} callback - `function(err, img)`
- * @static
- * @memberof Image
- * @example
- * var buffer = fs.readFileSync('./path/to/image.png');
- * mapnik.Image.fromBytes(buffer, function(err, img) {
- *   if (err) throw err;
- *   // your custom code with `img` object
- * });
- */
-NAN_METHOD(Image::fromBytes)
-{
-    if (info.Length() == 1) {
-        info.GetReturnValue().Set(_fromBytesSync(info));
-        return;
-    }
-
-    if (info.Length() < 2) {
-        Nan::ThrowError("must provide a buffer argument");
-        return;
-    }
-
-    if (!info[0]->IsObject()) {
-        Nan::ThrowTypeError("must provide a buffer argument");
-        return;
-    }
-
-    v8::Local<v8::Object> obj = info[0]->ToObject();
-    if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
-        Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
-        return;
-    }
-
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length() - 1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-
-    bool premultiply = false;
-    std::uint32_t max_size = 2048;
-    if (info.Length() >= 2)
-    {
-        if (info[1]->IsObject())
-        {
-            v8::Local<v8::Object> options = v8::Local<v8::Object>::Cast(info[1]);
-            if (options->Has(Nan::New("premultiply").ToLocalChecked()))
-            {
-                v8::Local<v8::Value> opt = options->Get(Nan::New("premultiply").ToLocalChecked());
-                if (!opt.IsEmpty() && opt->IsBoolean())
-                {
-                    premultiply = opt->BooleanValue();
-                }
-                else
-                {
-                    Nan::ThrowTypeError("premultiply option must be a boolean");
-                    return;
-                }
-            }
-            if (options->Has(Nan::New("max_size").ToLocalChecked()))
-            {
-                v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
-                if (opt->IsNumber())
-                {
-                    auto max_size_val = opt->IntegerValue();
-                    if (max_size_val < 0 || max_size_val > 65535) {
-                        Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
-                        return;
-                    }
-                    max_size = static_cast<std::uint32_t>(max_size_val);
-                }
-                else
-                {
-                    Nan::ThrowTypeError("max_size option must be a number");
-                    return;
-                }
-            }
-        }
-    }
-
-    image_mem_ptr_baton_t *closure = new image_mem_ptr_baton_t();
-    closure->request.data = closure;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    closure->buffer.Reset(obj.As<v8::Object>());
-    closure->data = node::Buffer::Data(obj);
-    closure->dataLength = node::Buffer::Length(obj);
-    closure->premultiply = premultiply;
-    closure->max_size = max_size;
-    closure->im = nullptr;
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_FromBytes, (uv_after_work_cb)EIO_AfterFromBytes);
-    return;
-}
-
-void Image::EIO_FromBytes(uv_work_t* req)
-{
-    image_mem_ptr_baton_t *closure = static_cast<image_mem_ptr_baton_t *>(req->data);
-
-    try
-    {
-        std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(closure->data,closure->dataLength));
-        if (reader.get())
-        {
-            if (reader->width() > closure->max_size || reader->height() > closure->max_size) {
-                std::stringstream s;
-                s << "image created from bytes must be " << closure->max_size << " pixels or fewer on each side";
-                closure->error_name = s.str();
-            } else {
-                closure->im = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(),reader->height()));
-                if (closure->premultiply) {
-                    mapnik::premultiply_alpha(*closure->im);
-                }
-            }
-        }
-    }
-    catch (std::exception const& ex)
-    {
-        closure->error_name = ex.what();
-    }
-}
-
-void Image::EIO_AfterFromBytes(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    image_mem_ptr_baton_t *closure = static_cast<image_mem_ptr_baton_t *>(req->data);
-    if (!closure->error_name.empty())
-    {
-        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    else if (closure->im == nullptr)
-    {
-        /* LCOV_EXCL_START */
-        // The only way this is ever reached is if the reader factory in
-        // mapnik was not providing an image type it should. This should never
-        // be occuring so marking this out from coverage
-        v8::Local<v8::Value> argv[1] = { Nan::Error("Failed to load from buffer") };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-        /* LCOV_EXCL_STOP */
-    } else
-    {
-        Image* im = new Image(closure->im);
-        v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
-        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
-        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
-        v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-    closure->cb.Reset();
-    closure->buffer.Reset();
-    delete closure;
-}
-
-/**
- * Encode this image into a buffer of encoded data (synchronous)
- *
- * @name encodeSync
- * @param {string} [format=png] image format
- * @param {Object} [options]
- * @param {mapnik.Palette} [options.palette] - mapnik.Palette object
- * @returns {Buffer} buffer - encoded image data
- * @instance
- * @memberof Image
- * @example
- * var img = new mapnik.Image.open('./path/to/image.png');
- * var buffer = img.encodeSync('png');
- * // write buffer to a new file
- * fs.writeFileSync('myimage.png', buffer);
- */
-NAN_METHOD(Image::encodeSync)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-
-    std::string format = "png";
-    palette_ptr palette;
-
-    // accept custom format
-    if (info.Length() >= 1){
-        if (!info[0]->IsString()) {
-            Nan::ThrowTypeError("first arg, 'format' must be a string");
-            return;
-        }
-        format = TOSTR(info[0]);
-    }
-
-
-    // options hash
-    if (info.Length() >= 2) {
-        if (!info[1]->IsObject()) {
-            Nan::ThrowTypeError("optional second arg must be an options object");
-            return;
-        }
-        v8::Local<v8::Object> options = info[1]->ToObject();
-        if (options->Has(Nan::New("palette").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> format_opt = options->Get(Nan::New("palette").ToLocalChecked());
-            if (!format_opt->IsObject()) {
-                Nan::ThrowTypeError("'palette' must be an object");
-                return;
-            }
-
-            v8::Local<v8::Object> obj = format_opt->ToObject();
-            if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Palette::constructor)->HasInstance(obj)) {
-                Nan::ThrowTypeError("mapnik.Palette expected as second arg");
-                return;
-            }
-            palette = Nan::ObjectWrap::Unwrap<Palette>(obj)->palette();
-        }
-    }
-
-    try {
-        std::string s;
-        if (palette.get())
-        {
-            s = save_to_string(*(im->this_), format, *palette);
-        }
-        else {
-            s = save_to_string(*(im->this_), format);
-        }
-
-        info.GetReturnValue().Set(Nan::CopyBuffer((char*)s.data(), s.size()).ToLocalChecked());
-    }
-    catch (std::exception const& ex)
-    {
-        Nan::ThrowError(ex.what());
-        return;
-    }
-}
-
-typedef struct {
-    uv_work_t request;
-    Image* im;
-    std::string format;
-    palette_ptr palette;
-    bool error;
-    std::string error_name;
-    Nan::Persistent<v8::Function> cb;
-    std::string result;
-} encode_image_baton_t;
-
-/**
- * Encode this image into a buffer of encoded data
- *
- * @name encode
- * @param {string} [format=png] image format
- * @param {Object} [options]
- * @param {mapnik.Palette} [options.palette] - mapnik.Palette object
- * @param {Function} callback - `function(err, encoded)`
- * @returns {Buffer} encoded image data
- * @instance
- * @memberof Image
- * @example
- * var img = new mapnik.Image.open('./path/to/image.png');
- * myImage.encode('png', function(err, encoded) {
- *   if (err) throw err;
- *   // write buffer to new file
- *   fs.writeFileSync('myimage.png', encoded);
- * });
- *
- * // encoding an image object with a mapnik.Palette
- * var im = new mapnik.Image(256, 256);
- * var pal = new mapnik.Palette(new Buffer('\xff\x09\x93\xFF\x01\x02\x03\x04','ascii'));
- * im.encode('png', {palette: pal}, function(err, encode) {
- *   if (err) throw err;
- *   // your custom code with `encode` image buffer
- * });
- */
-NAN_METHOD(Image::encode)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-
-    std::string format = "png";
-    palette_ptr palette;
-
-    // accept custom format
-    if (info.Length() >= 1){
-        if (!info[0]->IsString()) {
-            Nan::ThrowTypeError("first arg, 'format' must be a string");
-            return;
-        }
-        format = TOSTR(info[0]);
-    }
-
-    // options hash
-    if (info.Length() >= 2) {
-        if (!info[1]->IsObject()) {
-            Nan::ThrowTypeError("optional second arg must be an options object");
-            return;
-        }
-
-        v8::Local<v8::Object> options = info[1].As<v8::Object>();
-
-        if (options->Has(Nan::New("palette").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> format_opt = options->Get(Nan::New("palette").ToLocalChecked());
-            if (!format_opt->IsObject()) {
-                Nan::ThrowTypeError("'palette' must be an object");
-                return;
-            }
-
-            v8::Local<v8::Object> obj = format_opt.As<v8::Object>();
-            if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Palette::constructor)->HasInstance(obj)) {
-                Nan::ThrowTypeError("mapnik.Palette expected as second arg");
-                return;
-            }
-
-            palette = Nan::ObjectWrap::Unwrap<Palette>(obj)->palette();
-        }
-    }
-
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length() - 1];
-    if (!info[info.Length()-1]->IsFunction()) {
-        Nan::ThrowTypeError("last argument must be a callback function");
-        return;
-    }
-
-    encode_image_baton_t *closure = new encode_image_baton_t();
-    closure->request.data = closure;
-    closure->im = im;
-    closure->format = format;
-    closure->palette = palette;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Encode, (uv_after_work_cb)EIO_AfterEncode);
-    im->Ref();
-
-    return;
-}
-
-void Image::EIO_Encode(uv_work_t* req)
-{
-    encode_image_baton_t *closure = static_cast<encode_image_baton_t *>(req->data);
-
-    try {
-        if (closure->palette.get())
-        {
-            closure->result = save_to_string(*(closure->im->this_), closure->format, *closure->palette);
-        }
-        else
-        {
-            closure->result = save_to_string(*(closure->im->this_), closure->format);
-        }
-    }
-    catch (std::exception const& ex)
-    {
-        closure->error = true;
-        closure->error_name = ex.what();
-    }
-}
-
-void Image::EIO_AfterEncode(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-
-    encode_image_baton_t *closure = static_cast<encode_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(), Nan::CopyBuffer((char*)closure->result.data(), closure->result.size()).ToLocalChecked() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-
-    closure->im->Unref();
-    closure->cb.Reset();
-    delete closure;
-}
-
-/**
- * Get a constrained view of this image given x, y, width, height parameters.
- * @memberof Image
- * @instance
- * @name view
- * @param {number} x
- * @param {number} y
- * @param {number} width
- * @param {number} height
- * @returns {mapnik.Image} an image constrained to this new view
- * @example
- * var img = new mapnik.Image(10, 10);
- * // This function says "starting from the 0/0 pixel, grab 5 pixels along
- * // the x-axis and 5 along the y-axis" which gives us a quarter of the original
- * // 10x10 pixel image
- * var img2 = img.view(0, 0, 5, 5);
- * console.log(img.width(), img2.width()); // 10, 5
- */
-NAN_METHOD(Image::view)
-{
-    if ( (info.Length() != 4) || (!info[0]->IsNumber() && !info[1]->IsNumber() && !info[2]->IsNumber() && !info[3]->IsNumber() )) {
-        Nan::ThrowTypeError("requires 4 integer arguments: x, y, width, height");
+    if ( (info.Length() != 4) || (!info[0]->IsNumber() && !info[1]->IsNumber() && !info[2]->IsNumber() && !info[3]->IsNumber() )) {
+        Nan::ThrowTypeError("requires 4 integer arguments: x, y, width, height");
         return;
     }
 
@@ -3787,403 +648,6 @@ NAN_METHOD(Image::view)
     info.GetReturnValue().Set(ImageView::NewInstance(im,x,y,w,h));
 }
 
-/**
- * Encode this image and save it to disk as a file.
- *
- * @name saveSync
- * @param {string} filename
- * @param {string} [format=png]
- * @instance
- * @memberof Image
- * @example
- * img.saveSync('foo.png');
- */
-NAN_METHOD(Image::saveSync)
-{
-    info.GetReturnValue().Set(_saveSync(info));
-}
-
-v8::Local<v8::Value> Image::_saveSync(Nan::NAN_METHOD_ARGS_TYPE info) {
-    Nan::EscapableHandleScope scope;
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-
-    if (info.Length() == 0 || !info[0]->IsString()){
-        Nan::ThrowTypeError("filename required to save file");
-        return scope.Escape(Nan::Undefined());
-    }
-
-    std::string filename = TOSTR(info[0]);
-    std::string format("");
-
-    if (info.Length() >= 2) {
-        if (!info[1]->IsString()) {
-            Nan::ThrowTypeError("both 'filename' and 'format' arguments must be strings");
-            return scope.Escape(Nan::Undefined());
-        }
-        format = TOSTR(info[1]);
-    }
-    else
-    {
-        format = mapnik::guess_type(filename);
-        if (format == "<unknown>") {
-            std::ostringstream s("");
-            s << "unknown output extension for: " << filename << "\n";
-            Nan::ThrowError(s.str().c_str());
-            return scope.Escape(Nan::Undefined());
-        }
-    }
-
-    try
-    {
-        mapnik::save_to_file(*(im->this_),filename, format);
-    }
-    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;
-    std::string filename;
-    bool error;
-    std::string error_name;
-    Nan::Persistent<v8::Function> cb;
-} save_image_baton_t;
-
-/**
- * Encode this image and save it to disk as a file.
- *
- * @name save
- * @param {string} filename
- * @param {string} [format=png]
- * @param {Function} callback
- * @instance
- * @memberof Image
- * @example
- * img.save('image.png', 'png', function(err) {
- *   if (err) throw err;
- *   // your custom code
- * });
- */
-NAN_METHOD(Image::save)
-{
-    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-
-    if (info.Length() == 0 || !info[0]->IsString()){
-        Nan::ThrowTypeError("filename required to save file");
-        return;
-    }
-
-    if (!info[info.Length()-1]->IsFunction()) {
-        info.GetReturnValue().Set(_saveSync(info));
-        return;
-    }
-    // ensure callback is a function
-    v8::Local<v8::Value> callback = info[info.Length()-1];
-
-    std::string filename = TOSTR(info[0]);
-    std::string format("");
-
-    if (info.Length() >= 3) {
-        if (!info[1]->IsString()) {
-            Nan::ThrowTypeError("both 'filename' and 'format' arguments must be strings");
-            return;
-        }
-        format = TOSTR(info[1]);
-    }
-    else
-    {
-        format = mapnik::guess_type(filename);
-        if (format == "<unknown>") {
-            std::ostringstream s("");
-            s << "unknown output extension for: " << filename << "\n";
-            Nan::ThrowError(s.str().c_str());
-            return;
-        }
-    }
-
-    save_image_baton_t *closure = new save_image_baton_t();
-    closure->request.data = closure;
-    closure->format = format;
-    closure->filename = filename;
-    closure->im = im;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Save, (uv_after_work_cb)EIO_AfterSave);
-    im->Ref();
-    return;
-}
-
-void Image::EIO_Save(uv_work_t* req)
-{
-    save_image_baton_t *closure = static_cast<save_image_baton_t *>(req->data);
-    try
-    {
-        mapnik::save_to_file(*(closure->im->this_),
-                             closure->filename,
-                             closure->format);
-    }
-    catch (std::exception const& ex)
-    {
-        closure->error = true;
-        closure->error_name = ex.what();
-    }
-}
-
-void Image::EIO_AfterSave(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-    save_image_baton_t *closure = static_cast<save_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[1] = { Nan::Null() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
-    }
-    closure->im->Unref();
-    closure->cb.Reset();
-    delete closure;
-}
-
-typedef struct {
-    uv_work_t request;
-    Image* im1;
-    Image* im2;
-    mapnik::composite_mode_e mode;
-    int dx;
-    int dy;
-    float opacity;
-    std::vector<mapnik::filter::filter_type> filters;
-    bool error;
-    std::string error_name;
-    Nan::Persistent<v8::Function> cb;
-} composite_image_baton_t;
-
-/**
- * Overlay this image with another image, creating a layered composite as
- * a new image
- *
- * @name composite
- * @param {mapnik.Image} image - image to composite with
- * @param {Object} [options]
- * @param {mapnik.compositeOp} [options.comp_op] - compositing operation. Must be an integer
- * value that relates to a compositing operation.
- * @param {number} [options.opacity] - opacity must be a floating point number between 0-1
- * @param {number} [options.dx]
- * @param {number} [options.dy]
- * @param {string} [options.image_filters] - a string of filter names
- * @param {Function} callback
- * @instance
- * @memberof Image
- * @example
- * var img1 = new mapnik.Image.open('./path/to/image.png');
- * var img2 = new mapnik.Image.open('./path/to/another-image.png');
- * img1.composite(img2, {
- *   comp_op: mapnik.compositeOp['multiply'],
- *   dx: 0,
- *   dy: 0,
- *   opacity: 0.5,
- *   image_filters: 'invert agg-stack-blur(10,10)'
- * }, function(err, result) {
- *   if (err) throw err;
- *   // new image with `result`
- * });
- */
-NAN_METHOD(Image::composite)
-{
-    if (info.Length() < 1){
-        Nan::ThrowTypeError("requires at least one argument: an image mask");
-        return;
-    }
-
-    if (!info[0]->IsObject()) {
-        Nan::ThrowTypeError("first argument must be an image mask");
-        return;
-    }
-
-    v8::Local<v8::Object> im2 = info[0].As<v8::Object>();
-    if (im2->IsNull() || im2->IsUndefined() || !Nan::New(Image::constructor)->HasInstance(im2))
-    {
-        Nan::ThrowTypeError("mapnik.Image expected as first arg");
-        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 * dest_image = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
-    Image * source_image = Nan::ObjectWrap::Unwrap<Image>(im2);
-
-    if (!dest_image->this_->get_premultiplied())
-    {
-        Nan::ThrowTypeError("destination image must be premultiplied");
-        return;
-    }
-
-    if (!source_image->this_->get_premultiplied())
-    {
-        Nan::ThrowTypeError("source image must be premultiplied");
-        return;
-    }
-
-    mapnik::composite_mode_e mode = mapnik::src_over;
-    float opacity = 1.0;
-    std::vector<mapnik::filter::filter_type> filters;
-    int dx = 0;
-    int dy = 0;
-    if (info.Length() >= 2) {
-        if (!info[1]->IsObject())
-        {
-            Nan::ThrowTypeError("optional second arg must be an options object");
-            return;
-        }
-
-        v8::Local<v8::Object> options = info[1].As<v8::Object>();
-
-        if (options->Has(Nan::New("comp_op").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> opt = options->Get(Nan::New("comp_op").ToLocalChecked());
-            if (!opt->IsNumber())
-            {
-                Nan::ThrowTypeError("comp_op must be a mapnik.compositeOp value");
-                return;
-            }
-            int mode_int = opt->IntegerValue();
-            if (mode_int > static_cast<int>(mapnik::composite_mode_e::divide) || mode_int < 0)
-            {
-                Nan::ThrowTypeError("Invalid comp_op value");
-                return;
-            }
-            mode = static_cast<mapnik::composite_mode_e>(mode_int);
-        }
-
-        if (options->Has(Nan::New("opacity").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> opt = options->Get(Nan::New("opacity").ToLocalChecked());
-            if (!opt->IsNumber()) {
-                Nan::ThrowTypeError("opacity must be a floating point number");
-                return;
-            }
-            opacity = opt->NumberValue();
-            if (opacity < 0 || opacity > 1) {
-                Nan::ThrowTypeError("opacity must be a floating point number between 0-1");
-                return;
-            }
-        }
-
-        if (options->Has(Nan::New("dx").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> opt = options->Get(Nan::New("dx").ToLocalChecked());
-            if (!opt->IsNumber()) {
-                Nan::ThrowTypeError("dx must be an integer");
-                return;
-            }
-            dx = opt->IntegerValue();
-        }
-
-        if (options->Has(Nan::New("dy").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> opt = options->Get(Nan::New("dy").ToLocalChecked());
-            if (!opt->IsNumber()) {
-                Nan::ThrowTypeError("dy must be an integer");
-                return;
-            }
-            dy = opt->IntegerValue();
-        }
-
-        if (options->Has(Nan::New("image_filters").ToLocalChecked()))
-        {
-            v8::Local<v8::Value> opt = options->Get(Nan::New("image_filters").ToLocalChecked());
-            if (!opt->IsString()) {
-                Nan::ThrowTypeError("image_filters argument must string of filter names");
-                return;
-            }
-            std::string filter_str = TOSTR(opt);
-            bool result = mapnik::filter::parse_image_filters(filter_str, filters);
-            if (!result)
-            {
-                Nan::ThrowTypeError("could not parse image_filters");
-                return;
-            }
-        }
-    }
-
-    composite_image_baton_t *closure = new composite_image_baton_t();
-    closure->request.data = closure;
-    closure->im1 = dest_image;
-    closure->im2 = source_image;
-    closure->mode = mode;
-    closure->opacity = opacity;
-    closure->filters = std::move(filters);
-    closure->dx = dx;
-    closure->dy = dy;
-    closure->error = false;
-    closure->cb.Reset(callback.As<v8::Function>());
-    uv_queue_work(uv_default_loop(), &closure->request, EIO_Composite, (uv_after_work_cb)EIO_AfterComposite);
-    closure->im1->Ref();
-    closure->im2->Ref();
-    return;
-}
-
-void Image::EIO_Composite(uv_work_t* req)
-{
-    composite_image_baton_t *closure = static_cast<composite_image_baton_t *>(req->data);
-
-    try
-    {
-        if (closure->filters.size() > 0)
-        {
-            // TODO: expose passing custom scale_factor: https://github.com/mapnik/mapnik/commit/b830469d2d574ac575ff24713935378894f8bdc9
-            mapnik::filter::filter_visitor<mapnik::image_any> visitor(*closure->im2->this_);
-            for (mapnik::filter::filter_type const& filter_tag : closure->filters)
-            {
-                mapnik::util::apply_visitor(visitor, filter_tag);
-            }
-            mapnik::premultiply_alpha(*closure->im2->this_);
-        }
-        mapnik::composite(*closure->im1->this_,*closure->im2->this_, closure->mode, closure->opacity, closure->dx, closure->dy);
-    }
-    catch (std::exception const& ex)
-    {
-        closure->error = true;
-        closure->error_name = ex.what();
-    }
-}
-
-void Image::EIO_AfterComposite(uv_work_t* req)
-{
-    Nan::HandleScope scope;
-
-    composite_image_baton_t *closure = static_cast<composite_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->im1->handle() };
-        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
-    }
-
-    closure->im1->Unref();
-    closure->im2->Unref();
-    closure->cb.Reset();
-    delete closure;
-}
-
 NAN_GETTER(Image::get_scaling)
 {
     Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
diff --git a/src/mapnik_image_clear.cpp b/src/mapnik_image_clear.cpp
new file mode 100644
index 0000000000..5b68a8b21b
--- /dev/null
+++ b/src/mapnik_image_clear.cpp
@@ -0,0 +1,125 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+
+#include "mapnik_image.hpp"
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+/**
+ * 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;
+}
+
diff --git a/src/mapnik_image_composite.cpp b/src/mapnik_image_composite.cpp
new file mode 100644
index 0000000000..60b066c5da
--- /dev/null
+++ b/src/mapnik_image_composite.cpp
@@ -0,0 +1,245 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+
+#include <mapnik/image_compositing.hpp>
+#include <mapnik/image_filter.hpp>      // filter_visitor
+
+#include "mapnik_image.hpp"
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+typedef struct {
+    uv_work_t request;
+    Image* im1;
+    Image* im2;
+    mapnik::composite_mode_e mode;
+    int dx;
+    int dy;
+    float opacity;
+    std::vector<mapnik::filter::filter_type> filters;
+    bool error;
+    std::string error_name;
+    Nan::Persistent<v8::Function> cb;
+} composite_image_baton_t;
+
+/**
+ * Overlay this image with another image, creating a layered composite as
+ * a new image
+ *
+ * @name composite
+ * @param {mapnik.Image} image - image to composite with
+ * @param {Object} [options]
+ * @param {mapnik.compositeOp} [options.comp_op] - compositing operation. Must be an integer
+ * value that relates to a compositing operation.
+ * @param {number} [options.opacity] - opacity must be a floating point number between 0-1
+ * @param {number} [options.dx]
+ * @param {number} [options.dy]
+ * @param {string} [options.image_filters] - a string of filter names
+ * @param {Function} callback
+ * @instance
+ * @memberof Image
+ * @example
+ * var img1 = new mapnik.Image.open('./path/to/image.png');
+ * var img2 = new mapnik.Image.open('./path/to/another-image.png');
+ * img1.composite(img2, {
+ *   comp_op: mapnik.compositeOp['multiply'],
+ *   dx: 0,
+ *   dy: 0,
+ *   opacity: 0.5,
+ *   image_filters: 'invert agg-stack-blur(10,10)'
+ * }, function(err, result) {
+ *   if (err) throw err;
+ *   // new image with `result`
+ * });
+ */
+NAN_METHOD(Image::composite)
+{
+    if (info.Length() < 1){
+        Nan::ThrowTypeError("requires at least one argument: an image mask");
+        return;
+    }
+
+    if (!info[0]->IsObject()) {
+        Nan::ThrowTypeError("first argument must be an image mask");
+        return;
+    }
+
+    v8::Local<v8::Object> im2 = info[0].As<v8::Object>();
+    if (im2->IsNull() || im2->IsUndefined() || !Nan::New(Image::constructor)->HasInstance(im2))
+    {
+        Nan::ThrowTypeError("mapnik.Image expected as first arg");
+        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 * dest_image = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
+    Image * source_image = Nan::ObjectWrap::Unwrap<Image>(im2);
+
+    if (!dest_image->this_->get_premultiplied())
+    {
+        Nan::ThrowTypeError("destination image must be premultiplied");
+        return;
+    }
+
+    if (!source_image->this_->get_premultiplied())
+    {
+        Nan::ThrowTypeError("source image must be premultiplied");
+        return;
+    }
+
+    mapnik::composite_mode_e mode = mapnik::src_over;
+    float opacity = 1.0;
+    std::vector<mapnik::filter::filter_type> filters;
+    int dx = 0;
+    int dy = 0;
+    if (info.Length() >= 2) {
+        if (!info[1]->IsObject())
+        {
+            Nan::ThrowTypeError("optional second arg must be an options object");
+            return;
+        }
+
+        v8::Local<v8::Object> options = info[1].As<v8::Object>();
+
+        if (options->Has(Nan::New("comp_op").ToLocalChecked()))
+        {
+            v8::Local<v8::Value> opt = options->Get(Nan::New("comp_op").ToLocalChecked());
+            if (!opt->IsNumber())
+            {
+                Nan::ThrowTypeError("comp_op must be a mapnik.compositeOp value");
+                return;
+            }
+            int mode_int = opt->IntegerValue();
+            if (mode_int > static_cast<int>(mapnik::composite_mode_e::divide) || mode_int < 0)
+            {
+                Nan::ThrowTypeError("Invalid comp_op value");
+                return;
+            }
+            mode = static_cast<mapnik::composite_mode_e>(mode_int);
+        }
+
+        if (options->Has(Nan::New("opacity").ToLocalChecked()))
+        {
+            v8::Local<v8::Value> opt = options->Get(Nan::New("opacity").ToLocalChecked());
+            if (!opt->IsNumber()) {
+                Nan::ThrowTypeError("opacity must be a floating point number");
+                return;
+            }
+            opacity = opt->NumberValue();
+            if (opacity < 0 || opacity > 1) {
+                Nan::ThrowTypeError("opacity must be a floating point number between 0-1");
+                return;
+            }
+        }
+
+        if (options->Has(Nan::New("dx").ToLocalChecked()))
+        {
+            v8::Local<v8::Value> opt = options->Get(Nan::New("dx").ToLocalChecked());
+            if (!opt->IsNumber()) {
+                Nan::ThrowTypeError("dx must be an integer");
+                return;
+            }
+            dx = opt->IntegerValue();
+        }
+
+        if (options->Has(Nan::New("dy").ToLocalChecked()))
+        {
+            v8::Local<v8::Value> opt = options->Get(Nan::New("dy").ToLocalChecked());
+            if (!opt->IsNumber()) {
+                Nan::ThrowTypeError("dy must be an integer");
+                return;
+            }
+            dy = opt->IntegerValue();
+        }
+
+        if (options->Has(Nan::New("image_filters").ToLocalChecked()))
+        {
+            v8::Local<v8::Value> opt = options->Get(Nan::New("image_filters").ToLocalChecked());
+            if (!opt->IsString()) {
+                Nan::ThrowTypeError("image_filters argument must string of filter names");
+                return;
+            }
+            std::string filter_str = TOSTR(opt);
+            bool result = mapnik::filter::parse_image_filters(filter_str, filters);
+            if (!result)
+            {
+                Nan::ThrowTypeError("could not parse image_filters");
+                return;
+            }
+        }
+    }
+
+    composite_image_baton_t *closure = new composite_image_baton_t();
+    closure->request.data = closure;
+    closure->im1 = dest_image;
+    closure->im2 = source_image;
+    closure->mode = mode;
+    closure->opacity = opacity;
+    closure->filters = std::move(filters);
+    closure->dx = dx;
+    closure->dy = dy;
+    closure->error = false;
+    closure->cb.Reset(callback.As<v8::Function>());
+    uv_queue_work(uv_default_loop(), &closure->request, EIO_Composite, (uv_after_work_cb)EIO_AfterComposite);
+    closure->im1->Ref();
+    closure->im2->Ref();
+    return;
+}
+
+void Image::EIO_Composite(uv_work_t* req)
+{
+    composite_image_baton_t *closure = static_cast<composite_image_baton_t *>(req->data);
+
+    try
+    {
+        if (closure->filters.size() > 0)
+        {
+            // TODO: expose passing custom scale_factor: https://github.com/mapnik/mapnik/commit/b830469d2d574ac575ff24713935378894f8bdc9
+            mapnik::filter::filter_visitor<mapnik::image_any> visitor(*closure->im2->this_);
+            for (mapnik::filter::filter_type const& filter_tag : closure->filters)
+            {
+                mapnik::util::apply_visitor(visitor, filter_tag);
+            }
+            mapnik::premultiply_alpha(*closure->im2->this_);
+        }
+        mapnik::composite(*closure->im1->this_,*closure->im2->this_, closure->mode, closure->opacity, closure->dx, closure->dy);
+    }
+    catch (std::exception const& ex)
+    {
+        closure->error = true;
+        closure->error_name = ex.what();
+    }
+}
+
+void Image::EIO_AfterComposite(uv_work_t* req)
+{
+    Nan::HandleScope scope;
+
+    composite_image_baton_t *closure = static_cast<composite_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->im1->handle() };
+        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
+    }
+
+    closure->im1->Unref();
+    closure->im2->Unref();
+    closure->cb.Reset();
+    delete closure;
+}
diff --git a/src/mapnik_image_copy.cpp b/src/mapnik_image_copy.cpp
new file mode 100644
index 0000000000..0dbcabe81b
--- /dev/null
+++ b/src/mapnik_image_copy.cpp
@@ -0,0 +1,311 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+#include <mapnik/image_copy.hpp>
+
+#include "mapnik_image.hpp"
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+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());
+    }
+}
diff --git a/src/mapnik_image_encode.cpp b/src/mapnik_image_encode.cpp
new file mode 100644
index 0000000000..9314bac5df
--- /dev/null
+++ b/src/mapnik_image_encode.cpp
@@ -0,0 +1,233 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+
+#include "mapnik_image.hpp"
+#include "mapnik_palette.hpp"
+
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+/**
+ * Encode this image into a buffer of encoded data (synchronous)
+ *
+ * @name encodeSync
+ * @param {string} [format=png] image format
+ * @param {Object} [options]
+ * @param {mapnik.Palette} [options.palette] - mapnik.Palette object
+ * @returns {Buffer} buffer - encoded image data
+ * @instance
+ * @memberof Image
+ * @example
+ * var img = new mapnik.Image.open('./path/to/image.png');
+ * var buffer = img.encodeSync('png');
+ * // write buffer to a new file
+ * fs.writeFileSync('myimage.png', buffer);
+ */
+NAN_METHOD(Image::encodeSync)
+{
+    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
+
+    std::string format = "png";
+    palette_ptr palette;
+
+    // accept custom format
+    if (info.Length() >= 1){
+        if (!info[0]->IsString()) {
+            Nan::ThrowTypeError("first arg, 'format' must be a string");
+            return;
+        }
+        format = TOSTR(info[0]);
+    }
+
+
+    // options hash
+    if (info.Length() >= 2) {
+        if (!info[1]->IsObject()) {
+            Nan::ThrowTypeError("optional second arg must be an options object");
+            return;
+        }
+        v8::Local<v8::Object> options = info[1]->ToObject();
+        if (options->Has(Nan::New("palette").ToLocalChecked()))
+        {
+            v8::Local<v8::Value> format_opt = options->Get(Nan::New("palette").ToLocalChecked());
+            if (!format_opt->IsObject()) {
+                Nan::ThrowTypeError("'palette' must be an object");
+                return;
+            }
+
+            v8::Local<v8::Object> obj = format_opt->ToObject();
+            if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Palette::constructor)->HasInstance(obj)) {
+                Nan::ThrowTypeError("mapnik.Palette expected as second arg");
+                return;
+            }
+            palette = Nan::ObjectWrap::Unwrap<Palette>(obj)->palette();
+        }
+    }
+
+    try {
+        std::string s;
+        if (palette.get())
+        {
+            s = save_to_string(*(im->this_), format, *palette);
+        }
+        else {
+            s = save_to_string(*(im->this_), format);
+        }
+
+        info.GetReturnValue().Set(Nan::CopyBuffer((char*)s.data(), s.size()).ToLocalChecked());
+    }
+    catch (std::exception const& ex)
+    {
+        Nan::ThrowError(ex.what());
+        return;
+    }
+}
+
+typedef struct {
+    uv_work_t request;
+    Image* im;
+    std::string format;
+    palette_ptr palette;
+    bool error;
+    std::string error_name;
+    Nan::Persistent<v8::Function> cb;
+    std::string result;
+} encode_image_baton_t;
+
+/**
+ * Encode this image into a buffer of encoded data
+ *
+ * @name encode
+ * @param {string} [format=png] image format
+ * @param {Object} [options]
+ * @param {mapnik.Palette} [options.palette] - mapnik.Palette object
+ * @param {Function} callback - `function(err, encoded)`
+ * @returns {Buffer} encoded image data
+ * @instance
+ * @memberof Image
+ * @example
+ * var img = new mapnik.Image.open('./path/to/image.png');
+ * myImage.encode('png', function(err, encoded) {
+ *   if (err) throw err;
+ *   // write buffer to new file
+ *   fs.writeFileSync('myimage.png', encoded);
+ * });
+ *
+ * // encoding an image object with a mapnik.Palette
+ * var im = new mapnik.Image(256, 256);
+ * var pal = new mapnik.Palette(new Buffer('\xff\x09\x93\xFF\x01\x02\x03\x04','ascii'));
+ * im.encode('png', {palette: pal}, function(err, encode) {
+ *   if (err) throw err;
+ *   // your custom code with `encode` image buffer
+ * });
+ */
+NAN_METHOD(Image::encode)
+{
+    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
+
+    std::string format = "png";
+    palette_ptr palette;
+
+    // accept custom format
+    if (info.Length() >= 1){
+        if (!info[0]->IsString()) {
+            Nan::ThrowTypeError("first arg, 'format' must be a string");
+            return;
+        }
+        format = TOSTR(info[0]);
+    }
+
+    // options hash
+    if (info.Length() >= 2) {
+        if (!info[1]->IsObject()) {
+            Nan::ThrowTypeError("optional second arg must be an options object");
+            return;
+        }
+
+        v8::Local<v8::Object> options = info[1].As<v8::Object>();
+
+        if (options->Has(Nan::New("palette").ToLocalChecked()))
+        {
+            v8::Local<v8::Value> format_opt = options->Get(Nan::New("palette").ToLocalChecked());
+            if (!format_opt->IsObject()) {
+                Nan::ThrowTypeError("'palette' must be an object");
+                return;
+            }
+
+            v8::Local<v8::Object> obj = format_opt.As<v8::Object>();
+            if (obj->IsNull() || obj->IsUndefined() || !Nan::New(Palette::constructor)->HasInstance(obj)) {
+                Nan::ThrowTypeError("mapnik.Palette expected as second arg");
+                return;
+            }
+
+            palette = Nan::ObjectWrap::Unwrap<Palette>(obj)->palette();
+        }
+    }
+
+    // ensure callback is a function
+    v8::Local<v8::Value> callback = info[info.Length() - 1];
+    if (!info[info.Length()-1]->IsFunction()) {
+        Nan::ThrowTypeError("last argument must be a callback function");
+        return;
+    }
+
+    encode_image_baton_t *closure = new encode_image_baton_t();
+    closure->request.data = closure;
+    closure->im = im;
+    closure->format = format;
+    closure->palette = palette;
+    closure->error = false;
+    closure->cb.Reset(callback.As<v8::Function>());
+    uv_queue_work(uv_default_loop(), &closure->request, EIO_Encode, (uv_after_work_cb)EIO_AfterEncode);
+    im->Ref();
+
+    return;
+}
+
+void Image::EIO_Encode(uv_work_t* req)
+{
+    encode_image_baton_t *closure = static_cast<encode_image_baton_t *>(req->data);
+
+    try {
+        if (closure->palette.get())
+        {
+            closure->result = save_to_string(*(closure->im->this_), closure->format, *closure->palette);
+        }
+        else
+        {
+            closure->result = save_to_string(*(closure->im->this_), closure->format);
+        }
+    }
+    catch (std::exception const& ex)
+    {
+        closure->error = true;
+        closure->error_name = ex.what();
+    }
+}
+
+void Image::EIO_AfterEncode(uv_work_t* req)
+{
+    Nan::HandleScope scope;
+
+    encode_image_baton_t *closure = static_cast<encode_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(), Nan::CopyBuffer((char*)closure->result.data(), closure->result.size()).ToLocalChecked() };
+        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
+    }
+
+    closure->im->Unref();
+    closure->cb.Reset();
+    delete closure;
+}
+
diff --git a/src/mapnik_image_fill.cpp b/src/mapnik_image_fill.cpp
new file mode 100644
index 0000000000..7e08d16279
--- /dev/null
+++ b/src/mapnik_image_fill.cpp
@@ -0,0 +1,236 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+
+#include "mapnik_image.hpp"
+#include "mapnik_color.hpp"
+
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+/**
+ * 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;
+}
diff --git a/src/mapnik_image_filter.cpp b/src/mapnik_image_filter.cpp
new file mode 100644
index 0000000000..08feb4bec5
--- /dev/null
+++ b/src/mapnik_image_filter.cpp
@@ -0,0 +1,148 @@
+// 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_filter_types.hpp>
+#include <mapnik/image_filter.hpp>      // filter_visitor
+
+#include "mapnik_image.hpp"
+
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+/**
+ * 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;
+}
+
diff --git a/src/mapnik_image_from_buffer.cpp b/src/mapnik_image_from_buffer.cpp
new file mode 100644
index 0000000000..123c82216a
--- /dev/null
+++ b/src/mapnik_image_from_buffer.cpp
@@ -0,0 +1,135 @@
+// mapnik
+#include <mapnik/image.hpp>             // for image types
+#include <mapnik/image_any.hpp>         // for image_any
+
+#include "mapnik_image.hpp"
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+/**
+ * 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
+    }
+}
+
diff --git a/src/mapnik_image_from_bytes.cpp b/src/mapnik_image_from_bytes.cpp
new file mode 100644
index 0000000000..033ed04dd3
--- /dev/null
+++ b/src/mapnik_image_from_bytes.cpp
@@ -0,0 +1,276 @@
+// 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_image.hpp"
+#include "mapnik_image_view.hpp"
+#include "mapnik_palette.hpp"
+#include "mapnik_color.hpp"
+#include "visitor_get_pixel.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>
+
+
+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;
+
+
+/**
+ * Create an image from a byte stream buffer.
+ *
+ * @name fromBytes
+ * @param {Buffer} buffer - image buffer
+ * @param {Object} [options]
+ * @param {Boolean} [options.premultiply] - Default false, if true, then the image will be premultiplied before being returned
+ * @param {Number} [options.max_size] - the maximum allowed size of the image dimensions. The default is 2048.
+ * @param {Function} callback - `function(err, img)`
+ * @static
+ * @memberof Image
+ * @example
+ * var buffer = fs.readFileSync('./path/to/image.png');
+ * mapnik.Image.fromBytes(buffer, function(err, img) {
+ *   if (err) throw err;
+ *   // your custom code with `img` object
+ * });
+ */
+NAN_METHOD(Image::fromBytes)
+{
+    if (info.Length() == 1) {
+        info.GetReturnValue().Set(_fromBytesSync(info));
+        return;
+    }
+
+    if (info.Length() < 2) {
+        Nan::ThrowError("must provide a buffer argument");
+        return;
+    }
+
+    if (!info[0]->IsObject()) {
+        Nan::ThrowTypeError("must provide a buffer argument");
+        return;
+    }
+
+    v8::Local<v8::Object> obj = info[0]->ToObject();
+    if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
+        Nan::ThrowTypeError("first argument is invalid, must be a Buffer");
+        return;
+    }
+
+    // ensure callback is a function
+    v8::Local<v8::Value> callback = info[info.Length() - 1];
+    if (!info[info.Length()-1]->IsFunction()) {
+        Nan::ThrowTypeError("last argument must be a callback function");
+        return;
+    }
+
+    bool premultiply = false;
+    std::uint32_t max_size = 2048;
+    if (info.Length() >= 2)
+    {
+        if (info[1]->IsObject())
+        {
+            v8::Local<v8::Object> options = v8::Local<v8::Object>::Cast(info[1]);
+            if (options->Has(Nan::New("premultiply").ToLocalChecked()))
+            {
+                v8::Local<v8::Value> opt = options->Get(Nan::New("premultiply").ToLocalChecked());
+                if (!opt.IsEmpty() && opt->IsBoolean())
+                {
+                    premultiply = opt->BooleanValue();
+                }
+                else
+                {
+                    Nan::ThrowTypeError("premultiply option must be a boolean");
+                    return;
+                }
+            }
+            if (options->Has(Nan::New("max_size").ToLocalChecked()))
+            {
+                v8::Local<v8::Value> opt = options->Get(Nan::New("max_size").ToLocalChecked());
+                if (opt->IsNumber())
+                {
+                    auto max_size_val = opt->IntegerValue();
+                    if (max_size_val < 0 || max_size_val > 65535) {
+                        Nan::ThrowTypeError("max_size must be a positive integer between 0 and 65535");
+                        return;
+                    }
+                    max_size = static_cast<std::uint32_t>(max_size_val);
+                }
+                else
+                {
+                    Nan::ThrowTypeError("max_size option must be a number");
+                    return;
+                }
+            }
+        }
+    }
+
+    image_mem_ptr_baton_t *closure = new image_mem_ptr_baton_t();
+    closure->request.data = closure;
+    closure->error = false;
+    closure->cb.Reset(callback.As<v8::Function>());
+    closure->buffer.Reset(obj.As<v8::Object>());
+    closure->data = node::Buffer::Data(obj);
+    closure->dataLength = node::Buffer::Length(obj);
+    closure->premultiply = premultiply;
+    closure->max_size = max_size;
+    closure->im = nullptr;
+    uv_queue_work(uv_default_loop(), &closure->request, EIO_FromBytes, (uv_after_work_cb)EIO_AfterFromBytes);
+    return;
+}
+
+void Image::EIO_FromBytes(uv_work_t* req)
+{
+    image_mem_ptr_baton_t *closure = static_cast<image_mem_ptr_baton_t *>(req->data);
+
+    try
+    {
+        std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(closure->data,closure->dataLength));
+        if (reader.get())
+        {
+            if (reader->width() > closure->max_size || reader->height() > closure->max_size) {
+                std::stringstream s;
+                s << "image created from bytes must be " << closure->max_size << " pixels or fewer on each side";
+                closure->error_name = s.str();
+            } else {
+                closure->im = std::make_shared<mapnik::image_any>(reader->read(0,0,reader->width(),reader->height()));
+                if (closure->premultiply) {
+                    mapnik::premultiply_alpha(*closure->im);
+                }
+            }
+        }
+    }
+    catch (std::exception const& ex)
+    {
+        closure->error_name = ex.what();
+    }
+}
+
+void Image::EIO_AfterFromBytes(uv_work_t* req)
+{
+    Nan::HandleScope scope;
+    image_mem_ptr_baton_t *closure = static_cast<image_mem_ptr_baton_t *>(req->data);
+    if (!closure->error_name.empty())
+    {
+        v8::Local<v8::Value> argv[1] = { Nan::Error(closure->error_name.c_str()) };
+        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
+    }
+    else if (closure->im == nullptr)
+    {
+        /* LCOV_EXCL_START */
+        // The only way this is ever reached is if the reader factory in
+        // mapnik was not providing an image type it should. This should never
+        // be occuring so marking this out from coverage
+        v8::Local<v8::Value> argv[1] = { Nan::Error("Failed to load from buffer") };
+        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
+        /* LCOV_EXCL_STOP */
+    } else
+    {
+        Image* im = new Image(closure->im);
+        v8::Local<v8::Value> ext = Nan::New<v8::External>(im);
+        Nan::MaybeLocal<v8::Object> maybe_local = Nan::NewInstance(Nan::New(constructor)->GetFunction(), 1, &ext);
+        if (maybe_local.IsEmpty()) Nan::ThrowError("Could not create new Image instance");
+        v8::Local<v8::Value> argv[2] = { Nan::Null(), maybe_local.ToLocalChecked() };
+        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv);
+    }
+    closure->cb.Reset();
+    closure->buffer.Reset();
+    delete closure;
+}
+
+/**
+ * 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());
+    }
+}
+
diff --git a/src/mapnik_image_open.cpp b/src/mapnik_image_open.cpp
new file mode 100644
index 0000000000..3da0e3390c
--- /dev/null
+++ b/src/mapnik_image_open.cpp
@@ -0,0 +1,191 @@
+// 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.hpp"
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+/**
+ * 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 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;
+}
diff --git a/src/mapnik_image_premultiply.cpp b/src/mapnik_image_premultiply.cpp
new file mode 100644
index 0000000000..63804ac922
--- /dev/null
+++ b/src/mapnik_image_premultiply.cpp
@@ -0,0 +1,176 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+
+#include "mapnik_image.hpp"
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+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_);
+}
diff --git a/src/mapnik_image_resize.cpp b/src/mapnik_image_resize.cpp
new file mode 100644
index 0000000000..fbfd97f666
--- /dev/null
+++ b/src/mapnik_image_resize.cpp
@@ -0,0 +1,545 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+#include <mapnik/image_scaling.hpp>
+
+#include "mapnik_image.hpp"
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+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());
+    }
+}
diff --git a/src/mapnik_image_save.cpp b/src/mapnik_image_save.cpp
new file mode 100644
index 0000000000..8832910d4e
--- /dev/null
+++ b/src/mapnik_image_save.cpp
@@ -0,0 +1,179 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+
+#include "mapnik_image.hpp"
+
+#include "utils.hpp"
+
+// std
+#include <exception>
+
+/**
+ * Encode this image and save it to disk as a file.
+ *
+ * @name saveSync
+ * @param {string} filename
+ * @param {string} [format=png]
+ * @instance
+ * @memberof Image
+ * @example
+ * img.saveSync('foo.png');
+ */
+NAN_METHOD(Image::saveSync)
+{
+    info.GetReturnValue().Set(_saveSync(info));
+}
+
+v8::Local<v8::Value> Image::_saveSync(Nan::NAN_METHOD_ARGS_TYPE info) {
+    Nan::EscapableHandleScope scope;
+    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
+
+    if (info.Length() == 0 || !info[0]->IsString()){
+        Nan::ThrowTypeError("filename required to save file");
+        return scope.Escape(Nan::Undefined());
+    }
+
+    std::string filename = TOSTR(info[0]);
+    std::string format("");
+
+    if (info.Length() >= 2) {
+        if (!info[1]->IsString()) {
+            Nan::ThrowTypeError("both 'filename' and 'format' arguments must be strings");
+            return scope.Escape(Nan::Undefined());
+        }
+        format = TOSTR(info[1]);
+    }
+    else
+    {
+        format = mapnik::guess_type(filename);
+        if (format == "<unknown>") {
+            std::ostringstream s("");
+            s << "unknown output extension for: " << filename << "\n";
+            Nan::ThrowError(s.str().c_str());
+            return scope.Escape(Nan::Undefined());
+        }
+    }
+
+    try
+    {
+        mapnik::save_to_file(*(im->this_),filename, format);
+    }
+    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;
+    std::string filename;
+    bool error;
+    std::string error_name;
+    Nan::Persistent<v8::Function> cb;
+} save_image_baton_t;
+
+/**
+ * Encode this image and save it to disk as a file.
+ *
+ * @name save
+ * @param {string} filename
+ * @param {string} [format=png]
+ * @param {Function} callback
+ * @instance
+ * @memberof Image
+ * @example
+ * img.save('image.png', 'png', function(err) {
+ *   if (err) throw err;
+ *   // your custom code
+ * });
+ */
+NAN_METHOD(Image::save)
+{
+    Image* im = Nan::ObjectWrap::Unwrap<Image>(info.Holder());
+
+    if (info.Length() == 0 || !info[0]->IsString()){
+        Nan::ThrowTypeError("filename required to save file");
+        return;
+    }
+
+    if (!info[info.Length()-1]->IsFunction()) {
+        info.GetReturnValue().Set(_saveSync(info));
+        return;
+    }
+    // ensure callback is a function
+    v8::Local<v8::Value> callback = info[info.Length()-1];
+
+    std::string filename = TOSTR(info[0]);
+    std::string format("");
+
+    if (info.Length() >= 3) {
+        if (!info[1]->IsString()) {
+            Nan::ThrowTypeError("both 'filename' and 'format' arguments must be strings");
+            return;
+        }
+        format = TOSTR(info[1]);
+    }
+    else
+    {
+        format = mapnik::guess_type(filename);
+        if (format == "<unknown>") {
+            std::ostringstream s("");
+            s << "unknown output extension for: " << filename << "\n";
+            Nan::ThrowError(s.str().c_str());
+            return;
+        }
+    }
+
+    save_image_baton_t *closure = new save_image_baton_t();
+    closure->request.data = closure;
+    closure->format = format;
+    closure->filename = filename;
+    closure->im = im;
+    closure->error = false;
+    closure->cb.Reset(callback.As<v8::Function>());
+    uv_queue_work(uv_default_loop(), &closure->request, EIO_Save, (uv_after_work_cb)EIO_AfterSave);
+    im->Ref();
+    return;
+}
+
+void Image::EIO_Save(uv_work_t* req)
+{
+    save_image_baton_t *closure = static_cast<save_image_baton_t *>(req->data);
+    try
+    {
+        mapnik::save_to_file(*(closure->im->this_),
+                             closure->filename,
+                             closure->format);
+    }
+    catch (std::exception const& ex)
+    {
+        closure->error = true;
+        closure->error_name = ex.what();
+    }
+}
+
+void Image::EIO_AfterSave(uv_work_t* req)
+{
+    Nan::HandleScope scope;
+    save_image_baton_t *closure = static_cast<save_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[1] = { Nan::Null() };
+        Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv);
+    }
+    closure->im->Unref();
+    closure->cb.Reset();
+    delete closure;
+}
+
diff --git a/src/mapnik_image_solid.cpp b/src/mapnik_image_solid.cpp
new file mode 100644
index 0000000000..2d1fe69b82
--- /dev/null
+++ b/src/mapnik_image_solid.cpp
@@ -0,0 +1,134 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+
+#include "mapnik_image.hpp"
+#include "utils.hpp"
+#include "visitor_get_pixel.hpp"
+
+// std
+#include <exception>
+
+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());
+}
diff --git a/src/mapnik_image_svg.cpp b/src/mapnik_image_svg.cpp
new file mode 100644
index 0000000000..b2e9fbdfdd
--- /dev/null
+++ b/src/mapnik_image_svg.cpp
@@ -0,0 +1,692 @@
+// 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_util.hpp>        // for save_to_string, guess_type, etc
+
+#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 "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"
+
+// std
+#include <exception>
+
+/**
+ * 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;
+}
+
diff --git a/src/visitor_get_pixel.hpp b/src/visitor_get_pixel.hpp
new file mode 100644
index 0000000000..1187e486f0
--- /dev/null
+++ b/src/visitor_get_pixel.hpp
@@ -0,0 +1,111 @@
+#ifndef __NODE_MAPNIK_VISITOR_GET_PIXEL_H__
+#define __NODE_MAPNIK_VISITOR_GET_PIXEL_H__
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wshadow"
+#include <nan.h>
+#pragma GCC diagnostic pop
+
+#include <mapnik/image.hpp>             // for image types
+
+struct visitor_get_pixel
+{
+    visitor_get_pixel(int x, int y)
+        : x_(x), y_(y) {}
+
+    inline 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 */
+
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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));
+    }
+
+    inline 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_;
+
+};
+
+#endif
