#include "Rsvg.h" #include "RsvgCairo.h" #include #include #include "compat.h" using namespace v8; struct autocrop_region_t { double top; double bottom; double left; double right; }; static inline uint32_t pixel(uint8_t* data, int stride, int x, int y) { return *reinterpret_cast(data + stride * y + x * 4); } const uint32_t INVALID_COLOR = 0x00DEAD00; static uint32_t areaColor(uint8_t* data, int stride, int x0, int x1, int y0, int y1) { uint32_t color = pixel(data, stride, x0, y0); for (int x = x0; x <= x1; x++) { for (int y = y0; y <= y1; y++) { uint32_t current = pixel(data, stride, x, y); if (current != color) { return INVALID_COLOR; } } } return color; } static uint32_t findEdge(uint8_t* data, int stride, int width, int height, int xd, int yd) { int x0 = 0; int x1 = width - 1; int y0 = 0; int y1 = height - 1; if (xd > 0) { x1 = x0; } else if (xd < 0) { x0 = x1; } else if (yd > 0) { y1 = y0; } else if (yd < 0) { y0 = y1; } else { return 0; } x0 -= xd; x1 -= xd; y0 -= yd; y1 -= yd; uint32_t edge = INVALID_COLOR; while (true) { x0 += xd; x1 += xd; y0 += yd; y1 += yd; if (x0 < 0 || x1 < 0 || y0 < 0 || y1 < 0 || x0 >= width || x1 >= width || y0 >= height || y1 >= height) { x0 -= xd; x1 -= xd; y0 -= yd; y1 -= yd; break; } uint32_t color = areaColor(data, stride, x0, x1, y0, y1); if (edge == INVALID_COLOR) { edge = color; } if (color == INVALID_COLOR || color != edge) break; } return (xd != 0) ? x0 : y0; } static bool AutocropRecursive(RsvgHandle* handle, autocrop_region_t* region, int direction) { const int width = 100; const int height = 100; if (region->bottom - region->top < 0.0001 || region->right - region->left < 0.0001) { return true; } cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); cairo_t* cr = cairo_create(surface); cairo_scale(cr, width / (region->right - region->left), height / (region->bottom - region->top)); cairo_translate(cr, -region->left, -region->top); gboolean success = rsvg_handle_render_cairo(handle, cr); cairo_surface_flush(surface); cairo_status_t status = cairo_status(cr); if (status || !success) { cairo_destroy(cr); cairo_surface_destroy(surface); Nan::ThrowError( status ? cairo_status_to_string(status) : "Failed to render image." ); return false; } uint8_t* data = cairo_image_surface_get_data(surface); int stride = cairo_image_surface_get_stride(surface); if (areaColor(data, stride, 0, width - 1, 0, height - 1) != INVALID_COLOR) { cairo_destroy(cr); cairo_surface_destroy(surface); return true; } autocrop_region_t sub; if (direction == 1) { int top = findEdge(data, stride, width, height, 0, 1); sub.top = top; sub.bottom = top + 1; sub.left = 0; sub.right = width; cairo_device_to_user(cr, &sub.left, &sub.top); cairo_device_to_user(cr, &sub.right, &sub.bottom); success = AutocropRecursive(handle, &sub, direction); region->top = sub.top; } else if (direction == 2) { int bottom = findEdge(data, stride, width, height, 0, -1) + 1; sub.top = bottom - 1; sub.bottom = bottom; sub.left = 0; sub.right = width; cairo_device_to_user(cr, &sub.left, &sub.top); cairo_device_to_user(cr, &sub.right, &sub.bottom); success = AutocropRecursive(handle, &sub, direction); region->bottom = sub.bottom; } else if (direction == 3) { int left = findEdge(data, stride, width, height, 1, 0); sub.top = 0; sub.bottom = height; sub.left = left; sub.right = left + 1; cairo_device_to_user(cr, &sub.left, &sub.top); cairo_device_to_user(cr, &sub.right, &sub.bottom); success = AutocropRecursive(handle, &sub, direction); region->left = sub.left; } else if (direction == 4) { int right = findEdge(data, stride, width, height, -1, 0) + 1; sub.top = 0; sub.bottom = height; sub.left = right - 1; sub.right = right; cairo_device_to_user(cr, &sub.left, &sub.top); cairo_device_to_user(cr, &sub.right, &sub.bottom); success = AutocropRecursive(handle, &sub, direction); region->right = sub.right; } else { success = false; } cairo_destroy(cr); cairo_surface_destroy(surface); return success; } NAN_METHOD(Rsvg::Autocrop) { Nan::HandleScope scope; Rsvg* obj = node::ObjectWrap::Unwrap(ARGVAR.This()); RsvgDimensionData dimensions = { 0, 0, 0, 0 }; rsvg_handle_get_dimensions(obj->_handle, &dimensions); autocrop_region_t area = { 0, static_cast(dimensions.height), 0, static_cast(dimensions.width) }; if (AutocropRecursive(obj->_handle, &area, 1) && AutocropRecursive(obj->_handle, &area, 2) && AutocropRecursive(obj->_handle, &area, 3) && AutocropRecursive(obj->_handle, &area, 4)) { Handle dimensions = Nan::New(); dimensions->Set(Nan::New("x").ToLocalChecked(), Nan::New(area.left)); dimensions->Set(Nan::New("y").ToLocalChecked(), Nan::New(area.top)); dimensions->Set(Nan::New("width").ToLocalChecked(), Nan::New(area.right - area.left)); dimensions->Set(Nan::New("height").ToLocalChecked(), Nan::New(area.bottom - area.top)); ARGVAR.GetReturnValue().Set(dimensions->NewInstance()); } else { ARGVAR.GetReturnValue().Set(Nan::Undefined()); } }