Skip to content

Added: Image can now load SVG files and data #406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
'with_jpeg%': 'false',
'with_gif%': 'false',
'with_pango%': 'false',
'with_freetype%': 'false'
'with_freetype%': 'false',
# disable svg as it is uncertain if GTK provides librsvg
'with_rsvg%': 'false'
}
}, { # 'OS!="win"'
'variables': {
'with_jpeg%': '<!(./util/has_lib.sh jpeg)',
'with_gif%': '<!(./util/has_lib.sh gif)',
# disable pango as it causes issues with freetype.
'with_pango%': 'false',
'with_freetype%': '<!(./util/has_cairo_freetype.sh)'
'with_freetype%': '<!(./util/has_cairo_freetype.sh)',
'with_rsvg%': '<!(./util/has_lib.sh rsvg)'
}
}]
],
Expand Down Expand Up @@ -126,7 +129,24 @@
]
}]
]
}]
}],
['with_rsvg=="true"', {
'defines': [
'HAVE_RSVG'
],
'conditions': [
['OS=="win"', {
# No support for windows right now.
}, { # 'OS!="win"'
'libraries': [
'<!@(pkg-config librsvg-2.0 --libs)'
],
'include_dirs': [
'<!@(pkg-config librsvg-2.0 --cflags-only-I | sed s/-I//g)'
]
}]
]
}]
]
}
]
Expand Down
2 changes: 2 additions & 0 deletions install
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ PIXMAN="http://www.cairographics.org/releases/pixman-0.28.0.tar.gz"
CAIRO="http://cairographics.org/releases/cairo-1.12.8.tar.xz"
FREETYPE="http://download.savannah.gnu.org/releases/freetype/freetype-2.4.10.tar.gz"
LIBPNG="ftp://ftp.simplesystems.org/pub/libpng/png/src/libpng-1.5.13.tar.gz"
LIBRSVG="http://ftp.gnome.org/pub/GNOME/sources/librsvg/2.40/librsvg-2.40.2.tar.xz"
PREFIX=${1-/usr/local}

require() {
Expand Down Expand Up @@ -61,3 +62,4 @@ fetch $LIBPNG
fetch $FREETYPE
fetch $PIXMAN
fetch_xz $CAIRO
fetch_xz $LIBRSVG
106 changes: 106 additions & 0 deletions src/Image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ Image::loadFromBuffer(uint8_t *buf, unsigned len) {
}
}
#endif
#endif
#ifdef HAVE_RSVG
if (isSVG(buf)) return loadSVGFromBuffer(buf, len);
#endif
return CAIRO_STATUS_READ_ERROR;
}
Expand Down Expand Up @@ -417,6 +420,11 @@ Image::loadSurface() {
if (isJPEG(buf)) return loadJPEG(stream);
#endif

// svg
#ifdef HAVE_RSVG
if (isSVG(buf)) return loadSVG(stream);
#endif

fclose(stream);
return CAIRO_STATUS_READ_ERROR;
}
Expand Down Expand Up @@ -928,6 +936,88 @@ Image::loadJPEG(FILE *stream) {

#endif /* HAVE_JPEG */

#ifdef HAVE_RSVG

/*
* Load svg from buffer.
*/

cairo_status_t
Image::loadSVGFromBuffer(uint8_t *buf, unsigned len) {
cairo_status_t status;
GError *gerr = NULL;
RsvgHandle *rsvg;

if (NULL == (rsvg = rsvg_handle_new_from_data(buf, len, &gerr))) {
return CAIRO_STATUS_READ_ERROR;
}

RsvgDimensionData dimensions;
rsvg_handle_get_dimensions(rsvg, &dimensions);

_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
dimensions.width, dimensions.height);
status = cairo_surface_status(_surface);
if (status) { // CAIRO_STATUS_SUCCESS = 0
g_object_unref(rsvg);
return status;
}

// create new cairo rendering device
cairo_t *cr = cairo_create(_surface);
status = cairo_status(cr);
if (status) {
g_object_unref(rsvg);
return status;
}

if (!rsvg_handle_render_cairo(rsvg, cr)) {
g_object_unref(rsvg);
cairo_destroy(cr);
return CAIRO_STATUS_WRITE_ERROR;
}

// Cleanup
g_object_unref(rsvg);
cairo_destroy(cr);

return CAIRO_STATUS_SUCCESS;
}

/*
* Load svg
*/

cairo_status_t
Image::loadSVG(FILE *stream) {
struct stat s;
int fd = fileno(stream);

// stat
if (fstat(fd, &s) < 0) {
fclose(stream);
return CAIRO_STATUS_READ_ERROR;
}

uint8_t *buf = (uint8_t *) malloc(s.st_size);

if (!buf) {
fclose(stream);
return CAIRO_STATUS_NO_MEMORY;
}

size_t read = fread(buf, s.st_size, 1, stream);
fclose(stream);

cairo_status_t result = CAIRO_STATUS_READ_ERROR;
if (1 == read) result = loadSVGFromBuffer(buf, s.st_size);
free(buf);

return result;
}

#endif

/*
* Return UNKNOWN, JPEG, or PNG based on the filename.
*/
Expand All @@ -940,6 +1030,7 @@ Image::extension(const char *filename) {
if (len >= 4 && 0 == strcmp(".gif", filename - 4)) return Image::GIF;
if (len >= 4 && 0 == strcmp(".jpg", filename - 4)) return Image::JPEG;
if (len >= 4 && 0 == strcmp(".png", filename - 4)) return Image::PNG;
if (len >= 4 && 0 == strcmp(".svg", filename - 4)) return Image::SVG;
return Image::UNKNOWN;
}

Expand Down Expand Up @@ -969,3 +1060,18 @@ int
Image::isPNG(uint8_t *data) {
return 'P' == data[1] && 'N' == data[2] && 'G' == data[3];
}

/*
* Sniff bytes 1..3 for "svg".
* Sniff bytes 2..4 for "xml"
*/

int
Image::isSVG(uint8_t *data) {
return ('s' == data[1] && 'v' == data[2] && 'g' == data[3]) ||
/*
* TODO: Sniffing just for 'xml' might not be good enough!
* We should probably consider checking also the 'svg' bytes.
*/
('x' == data[2] && 'm' == data[3] && 'l' == data[4]);
}
12 changes: 11 additions & 1 deletion src/Image.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include <gif_lib.h>
#endif

#ifdef HAVE_RSVG
#include "librsvg/rsvg.h"
#endif

class Image: public node::ObjectWrap {
public:
char *filename;
Expand All @@ -46,6 +50,7 @@ class Image: public node::ObjectWrap {
static int isPNG(uint8_t *data);
static int isJPEG(uint8_t *data);
static int isGIF(uint8_t *data);
static int isSVG(uint8_t *data);
static cairo_status_t readPNG(void *closure, unsigned char *data, unsigned len);
inline int isComplete(){ return COMPLETE == state; }
cairo_status_t loadSurface();
Expand All @@ -64,8 +69,12 @@ class Image: public node::ObjectWrap {
#if CAIRO_VERSION_MINOR >= 10
cairo_status_t decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len);
cairo_status_t assignDataAsMime(uint8_t *data, int len, const char *mime_type);
#endif
#endif
#endif
#ifdef HAVE_RSVG
cairo_status_t loadSVGFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadSVG(FILE *stream);
#endif
void error(Local<Value> error);
void loaded();
cairo_status_t load();
Expand All @@ -87,6 +96,7 @@ class Image: public node::ObjectWrap {
, GIF
, JPEG
, PNG
, SVG
} type;

static type extension(const char *filename);
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/1398275113_SDKs_copy_nodeJS.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/fixtures/black_7_music_node.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 64 additions & 2 deletions test/image.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@

var Canvas = require('../')
, Image = Canvas.Image
, assert = require('assert');
, assert = require('assert')
, fs = require('fs');

var png = __dirname + '/fixtures/clock.png';
var svg01 = __dirname + '/fixtures/1398275113_SDKs_copy_nodeJS.svg';
var svg02 = __dirname + '/fixtures/black_7_music_node.svg';

module.exports = {
'tset Image': function(){
'test Image': function(){
assert.ok(Image instanceof Function);
},

Expand All @@ -35,6 +38,11 @@ module.exports = {
assert.strictEqual(320, img.height);
assert.equal(1, n);
},

'test Image#onload svg': function(){
test_onLoad(svg01, 100, 100);
test_onLoad(svg02, 32, 32);
},

'test Image#onerror': function(){
var img = new Image
Expand Down Expand Up @@ -80,5 +88,59 @@ module.exports = {
img.src = png;

assert.equal(1, n);
},

'test Image#onload svg2png': function(){
test_svg2png(svg01, 100, 100);
test_svg2png(svg02, 32, 32);
}
};

function test_onLoad(imgSrc, w, h) {
var img = new Image
, n = 0;

assert.strictEqual(null, img.onload);

assert.strictEqual(false, img.complete);
img.onload = function(){
++n;
assert.equal(img.src, imgSrc);
};

img.src = imgSrc;
assert.equal(img.src, imgSrc);

assert.equal(img.src, imgSrc);
assert.strictEqual(true, img.complete);
assert.strictEqual(w, img.width);
assert.strictEqual(h, img.height);
assert.equal(1, n);
}

function test_svg2png(imgSrc, w, h) {
var data = fs.readFileSync(imgSrc);
assert.notStrictEqual(null, data);

var b64data = data.toString('base64');
var src = "data:image/svg+xml;base64," + b64data;

var canvas = new Canvas(w, h)
, ctx = canvas.getContext('2d');

var img = new Image;
img.onload = function(){
assert.equal(img.src, '');
assert.strictEqual(w, img.width);
assert.strictEqual(h, img.height);

ctx.drawImage(img, 0, 0);
assert.ok(-1 != canvas.toBuffer().toString().indexOf('PNG'));
assert.ok(0 == canvas.toDataURL().indexOf('data:image/png;base64,'));
assert.ok(0 == canvas.toDataURL('image/png').indexOf('data:image/png;base64,'));
};

img.src = src;
// since we read from buffer the 'src' attribute will be an empty string
assert.equal(img.src, '');
}
1 change: 1 addition & 0 deletions test/public/1398275113_SDKs_copy_nodeJS.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/public/black_7_music_node.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions test/public/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1554,6 +1554,26 @@ tests['drawImage(img) jpeg'] = function(ctx, done){
img.src = 'face.jpeg';
};

tests['drawImage(img) svg01'] = function(ctx, done){
var img = new Image;
img.onload = function(){
ctx.drawImage(img,0,0);
done();
};
img.onerror = function(err){console.log(err);}
img.src = '1398275113_SDKs_copy_nodeJS.svg';
};

tests['drawImage(img) svg02'] = function(ctx, done){
var img = new Image;
img.onload = function(){
ctx.drawImage(img,0,0);
done();
};
img.onerror = function(err){console.log(err);}
img.src = 'black_7_music_node.svg';
};

tests['drawImage(img,x,y)'] = function(ctx, done){
var img = new Image;
img.onload = function(){
Expand Down
6 changes: 5 additions & 1 deletion test/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ app.post('/render', function(req, res, next){
// no good way around this at the moment
req.body.fn = req.body.fn
.replace("'state.png'", "'" + __dirname + "/public/state.png'")
.replace("'face.jpeg'", "'" + __dirname + "/public/face.jpeg'");
.replace("'face.jpeg'", "'" + __dirname + "/public/face.jpeg'")
.replace("'1398275113_SDKs_copy_nodeJS.svg'",
"'" + __dirname + "/public/1398275113_SDKs_copy_nodeJS.svg'")
.replace("'black_7_music_node.svg'",
"'" + __dirname + "/public/black_7_music_node.svg'");

// Do not try this at home :)
var fn = eval('(' + req.body.fn + ')')
Expand Down