Browse Source

Merge branch 'master' into dont-remove-callbacks-after-call

Conflicts:
	test/image.test.js
v1.x
Raul Ochoa 9 years ago
parent
commit
6a90ed5c15
  1. 0
      .gitmodules
  2. 26
      .travis.yml
  3. 57
      History.md
  4. 24
      Makefile
  5. 44
      Readme.md
  6. 99
      benchmarks/run.js
  7. 8
      binding.gyp
  8. 65
      install
  9. 100
      lib/canvas.js
  10. 23
      lib/context2d.js
  11. 6
      lib/jpegstream.js
  12. 29
      lib/pixelarray.js
  13. 21
      package.json
  14. 254
      src/Canvas.cc
  15. 8
      src/Canvas.h
  16. 73
      src/CanvasGradient.cc
  17. 6
      src/CanvasGradient.h
  18. 36
      src/CanvasPattern.cc
  19. 6
      src/CanvasPattern.h
  20. 1114
      src/CanvasRenderingContext2d.cc
  21. 7
      src/CanvasRenderingContext2d.h
  22. 38
      src/FontFace.cc
  23. 6
      src/FontFace.h
  24. 103
      src/Image.cc
  25. 10
      src/Image.h
  26. 110
      src/ImageData.cc
  27. 22
      src/ImageData.h
  28. 53
      src/JPEGStream.h
  29. 163
      src/PixelArray.cc
  30. 33
      src/PixelArray.h
  31. 6
      src/closure.h
  32. 5
      src/color.cc
  33. 19
      src/init.cc
  34. 311
      test/canvas.test.js
  35. 68
      test/image.test.js
  36. 58
      test/imageData.test.js
  37. 30
      test/public/app.js
  38. 15
      test/public/tests.js
  39. 41
      test/server.js
  40. 2
      test/views/layout.jade
  41. 1
      test/views/tests.jade

0
.gitmodules

26
.travis.yml

@ -5,7 +5,27 @@ node_js:
- "0.12"
- "iojs-v1.8.4"
- "iojs-v2.5.0"
- "iojs-v3.3.0"
- "4"
- "5"
matrix:
allow_failures:
- node_js: "5"
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libcairo2-dev
- libjpeg8-dev
- libpango1.0-dev
- libgif-dev
- g++-4.8
env:
- CXX=g++-4.8
before_install:
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
- sudo chown -R $USER /usr/local
- sh install
- if [[ $TRAVIS_NODE_VERSION == 0.8 ]]; then npm install -g npm@1.4.28; fi
- npm explore npm -g -- npm install node-gyp@latest
after_script:
- npm run benchmark
sudo: false

57
History.md

@ -1,3 +1,60 @@
1.3.6 / 2016-01-06
==================
* Allow optional arguments in `toDataURL` to be `undefined` and improve `toDataURL`'s spec compliance (#690)
1.3.5 / 2015-12-07
==================
* Add image/jpeg support to `toDataUrl` (#685)
1.3.4 / 2015-11-21
==================
* Upgrade nan to 2.1.0 (#671)
1.3.3 / 2015-11-21
==================
* Fix compilation on Visual Studio 2015 (#670)
1.3.2 / 2015-11-18
==================
* Fix incorrect Y offset and scaling for shadows (#669)
1.3.1 / 2015-11-09
==================
* Wrap std::min calls in paranthesis to prevent macro expansion on windows (#660)
1.3.0 / 2015-10-26
==================
* Expose ImageData constructor and make it more spec-compliant (#569)
1.2.11 / 2015-10-20
===================
* Implement blur on images (#648)
1.2.10 / 2015-10-12
===================
* Fix segfault in Canvas#jpegStream (#629)
1.2.9 / 2015-09-14
==================
* Upgrade to Nan 2.x with support for iojs 3.x and Node.js 4.x (#622)
1.2.8 / 2015-08-30
==================
* Clean up the tests (#612)
* Replace CanvasPixelArray with Uint8ClampedArray to be API-compliant (#604)
* Specify travis iojs versions (#611)
1.2.7 / 2015-07-29
==================

24
Makefile

@ -1,24 +0,0 @@
ADDON = build/Release/canvas.node
REPORTER = dot
$(ADDON): src/*.cc
npm install
test: $(ADDON)
@./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--ui exports \
--require should \
test/*.test.js
test-server: $(ADDON)
@node test/server.js
benchmark:
@node benchmarks/run.js
clean:
rm -fr build
.PHONY: test test-server benchmark clean

44
Readme.md

@ -19,19 +19,19 @@ node-canvas
$ npm install canvas
```
Unless previously installed you'll _need_ __Cairo__. For system-specific installation view the [Wiki](https://github.com/LearnBoost/node-canvas/wiki/_pages).
Unless previously installed you'll _need_ __Cairo__. For system-specific installation view the [Wiki](https://github.com/Automattic/node-canvas/wiki/_pages).
You can quickly install Cairo and its dependencies for OS X using the one liner below:
You can quickly install the dependencies by using the command for your OS:
```bash
$ wget https://raw.githubusercontent.com/LearnBoost/node-canvas/master/install -O - | sh
```
OS | Command
----- | -----
OS X | `brew install pkg-config cairo libpng jpeg giflib`
Ubuntu | `sudo apt-get install libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++`
Fedora | `sudo yum install cairo cairo-devel cairomm-devel libjpeg-turbo-devel pango pango-devel pangomm pangomm-devel giflib-devel`
Solaris | `pkgin install cairo pkg-config xproto renderproto kbproto xextproto`
Windows | [Instructions on our wiki](https://github.com/Automattic/node-canvas/wiki/Installation---Windows)
or if you use MacPorts
```bash
sudo port install pkgconfig libpng giflib freetype libpixman cairo
```
**El Capitan users:** If you have recently updated to El Capitan and are experiencing trouble when compiling, run the following command: `xcode-select --install`. Read more about the problem [on Stack Overflow](http://stackoverflow.com/a/32929012/148072).
## Screencasts
@ -156,22 +156,18 @@ canvas.toBuffer(function(err, buf){
});
```
### Canvas#toDataURL() async
### Canvas#toDataURL() sync and async
Optionally we may pass a callback function to `Canvas#toDataURL()`, and this process will be performed asynchronously, and will `callback(err, str)`.
The following syntax patterns are supported:
```javascript
canvas.toDataURL(function(err, str){
});
```
or specify the mime type:
```javascript
canvas.toDataURL('image/png', function(err, str){
});
var dataUrl = canvas.toDataURL(); // defaults to PNG
var dataUrl = canvas.toDataURL('image/png');
canvas.toDataURL(function(err, png){ }); // defaults to PNG
canvas.toDataURL('image/png', function(err, png){ });
canvas.toDataURL('image/jpeg', function(err, jpeg){ }); // sync JPEG is not supported
canvas.toDataURL('image/jpeg', {opts...}, function(err, jpeg){ }); // see Canvas#jpegStream for valid options
canvas.toDataURL('image/jpeg', quality, function(err, jpeg){ }); // spec-following; quality from 0 to 1
```
### CanvasRenderingContext2d#patternQuality
@ -274,7 +270,7 @@ fs.writeFile('out.svg', canvas.toBuffer());
## Contribute
Want to contribute to node-canvas? patches for features, bug fixes, documentation, examples and others are certainly welcome. Take a look at the [issue queue](https://github.com/LearnBoost/node-canvas/issues) for existing issues.
Want to contribute to node-canvas? patches for features, bug fixes, documentation, examples and others are certainly welcome. Take a look at the [issue queue](https://github.com/Automattic/node-canvas/issues) for existing issues.
## Examples

99
benchmarks/run.js

@ -1,6 +1,7 @@
/**
* Module dependencies.
* Adaptive benchmarking. Starts with `initialTimes` iterations, increasing by
* a power of two each time until the benchmark takes at least `minDuration_ms`
* milliseconds to complete.
*/
var Canvas = require('../lib/canvas')
@ -8,34 +9,50 @@ var Canvas = require('../lib/canvas')
, largeCanvas = new Canvas(1000, 1000)
, ctx = canvas.getContext('2d');
var times = 10000;
console.log('\n \x1b[33m%s\x1b[0m times\n', times);
var initialTimes = 10;
var minDuration_ms = 2000;
function bm(label, overrideTimes, fn) {
var start = new Date
, n = times;
var queue = [], running = false;
if ('function' == typeof overrideTimes) {
fn = overrideTimes;
} else {
n = overrideTimes;
label += ' (' + n + ' times)';
function bm(label, fn) {
queue.push({label: label, fn: fn});
next();
}
var pending = n;
function done(){
var duration = (new Date - start) / 1000;
console.log(' - \x1b[33m%s\x1b[0m %ss', label, duration);
function next() {
if (queue.length && !running) {
run(queue.pop(), initialTimes, Date.now());
}
}
if (fn.length) {
function run(benchmark, n, start) {
running = true;
var originalN = n;
var fn = benchmark.fn;
if (fn.length) { // async
var pending = n;
while (n--) fn(function () {
--pending || done();
--pending || done(benchmark, originalN, start, true);
});
} else {
while (n--) fn();
done();
done(benchmark, originalN, start);
}
}
function done(benchmark, times, start, async) {
var duration = Date.now() - start;
if (duration < minDuration_ms) {
run(benchmark, times * 2, Date.now());
} else {
var opsSec = times / duration * 1000
if (async) {
console.log(' - \x1b[33m%s\x1b[0m %s ops/sec (%s times, async)', benchmark.label, opsSec.toLocaleString(), times);
} else {
console.log(' - \x1b[33m%s\x1b[0m %s ops/sec (%s times)', benchmark.label, opsSec.toLocaleString(), times);
}
running = false;
next();
}
}
@ -57,6 +74,8 @@ bm('fillStyle= rgba()', function(){
ctx.fillStyle = 'rgba(0,255,80,1)';
});
// Apparently there's a bug in cairo by which the fillRect and strokeRect are
// slow only after a ton of arcs have been drawn.
bm('fillRect()', function(){
ctx.fillRect(50, 50, 100, 100);
});
@ -73,19 +92,31 @@ bm('linear gradients', function(){
ctx.fillRect(10,10,130,130);
});
bm('toBuffer() 200x200', 50, function(){
bm('toBuffer() 200x200', function(){
canvas.toBuffer();
});
bm('toBuffer() 1000x1000', 50, function(){
bm('toBuffer() 1000x1000', function(){
largeCanvas.toBuffer();
});
bm('toBuffer().toString("base64") 200x200', 50, function(){
bm('toBuffer() async 200x200', function(done){
canvas.toBuffer(function (err, buf) {
done();
});
});
bm('toBuffer() async 1000x1000', function(done){
largeCanvas.toBuffer(function (err, buf) {
done();
});
});
bm('toBuffer().toString("base64") 200x200', function(){
canvas.toBuffer().toString('base64');
});
bm('toDataURL() 200x200', 50, function(){
bm('toDataURL() 200x200', function(){
canvas.toDataURL();
});
@ -109,14 +140,12 @@ bm('getImageData(0,0,100,100)', function(){
ctx.getImageData(0,0,100,100);
});
// bm('PNGStream 200x200', 50, function(done){
// var stream = canvas.createSyncPNGStream();
// stream.on('data', function(chunk){
// // whatever
// });
// stream.on('end', function(){
// done();
// });
// });
console.log();
bm('PNGStream 200x200', function(done){
var stream = canvas.createSyncPNGStream();
stream.on('data', function(chunk){
// whatever
});
stream.on('end', function(){
done();
});
});

8
binding.gyp

@ -49,8 +49,7 @@
'src/color.cc',
'src/Image.cc',
'src/ImageData.cc',
'src/init.cc',
'src/PixelArray.cc'
'src/init.cc'
],
'conditions': [
['OS=="win"', {
@ -63,7 +62,6 @@
'<(GTK_Root)/include/cairo',
],
'defines': [
'snprintf=_snprintf',
'_USE_MATH_DEFINES' # for M_PI
],
'configurations': {
@ -72,7 +70,7 @@
'VCCLCompilerTool': {
'WarningLevel': 4,
'ExceptionHandling': 1,
'DisableSpecificWarnings': [4100, 4127, 4201, 4244, 4267, 4506, 4611, 4714]
'DisableSpecificWarnings': [4100, 4127, 4201, 4244, 4267, 4506, 4611, 4714, 4512]
}
}
},
@ -81,7 +79,7 @@
'VCCLCompilerTool': {
'WarningLevel': 4,
'ExceptionHandling': 1,
'DisableSpecificWarnings': [4100, 4127, 4201, 4244, 4267, 4506, 4611, 4714]
'DisableSpecificWarnings': [4100, 4127, 4201, 4244, 4267, 4506, 4611, 4714, 4512]
}
}
}

65
install

@ -1,65 +0,0 @@
#!/bin/sh
PKG_CONFIG="http://pkgconfig.freedesktop.org/releases/pkg-config-0.28.tar.gz"
PIXMAN="http://www.cairographics.org/releases/pixman-0.32.4.tar.gz"
CAIRO="http://cairographics.org/releases/cairo-1.12.16.tar.xz"
FREETYPE="http://download.savannah.gnu.org/releases/freetype/freetype-2.4.10.tar.gz"
LIBPNG="http://downloads.sourceforge.net/project/libpng/libpng16/1.6.10/libpng-1.6.10.tar.gz"
GIF_LIB="https://downloads.sourceforge.net/project/giflib/giflib-4.x/giflib-4.1.6/giflib-4.1.6.tar.gz"
PREFIX=${1-/usr/local}
require() {
echo "... checking for $1"
if test `which $1`; then
echo "... found"
else
echo "... not found"
exit 1
fi
}
fetch() {
local tarball=`basename $1`
echo "... downloading $tarball"
local dir=`basename $tarball .tar.gz`
curl -# -L $1 -o $tarball \
&& echo "... unpacking" \
&& tar -zxf $tarball \
&& echo "... removing tarball" \
&& rm -fr $tarball \
&& install $dir
}
fetch_xz() {
local tarball=`basename $1`
echo "... downloading $tarball"
local dir=`basename $tarball .tar.xz`
curl -# -L $1 -o $tarball \
&& echo "... unpacking" \
&& tar -xJf $tarball \
&& echo "... removing tarball" \
&& rm -fr $tarball \
&& install $dir
}
install() {
local dir=$1
echo "... installing $1"
cd $dir \
&& ./configure --disable-dependency-tracking --prefix=$PREFIX \
&& make \
&& make install \
&& echo "... removing $dir" \
&& cd .. && rm -fr $dir
}
echo "... installing to $PREFIX"
require curl
require tar
test `which pkg-config` || fetch $PKG_CONFIG
require 'pkg-config'
fetch $LIBPNG
fetch $GIF_LIB
fetch $FREETYPE
fetch $PIXMAN
fetch_xz $CAIRO

100
lib/canvas.js

@ -13,13 +13,13 @@ var canvas = require('./bindings')
, Canvas = canvas.Canvas
, Image = canvas.Image
, cairoVersion = canvas.cairoVersion
, PixelArray = canvas.CanvasPixelArray
, Context2d = require('./context2d')
, PNGStream = require('./pngstream')
, JPEGStream = require('./jpegstream')
, FontFace = canvas.FontFace
, fs = require('fs')
, packageJson = require("../package.json");
, packageJson = require("../package.json")
, FORMATS = ['image/png', 'image/jpeg'];
/**
* Export `Canvas` as the module.
@ -62,8 +62,8 @@ if (canvas.gifVersion) {
exports.Context2d = Context2d;
exports.PNGStream = PNGStream;
exports.JPEGStream = JPEGStream;
exports.PixelArray = PixelArray;
exports.Image = Image;
exports.ImageData = canvas.ImageData;
if (FontFace) {
function Font(name, path, idx) {
@ -100,12 +100,6 @@ require('./context2d');
require('./image');
/**
* PixelArray implementation.
*/
require('./pixelarray');
/**
* Inspect canvas.
*
@ -190,33 +184,91 @@ Canvas.prototype.createSyncJPEGStream = function(options){
};
/**
* Return a data url. Pass a function for async support.
* Return a data url. Pass a function for async support (required for "image/jpeg").
*
* @param {String|Function} type
* @param {Function} fn
* @return {String}
* @param {String} type, optional, one of "image/png" or "image/jpeg", defaults to "image/png"
* @param {Object|Number} encoderOptions, optional, options for jpeg compression (see documentation for Canvas#jpegStream) or the JPEG encoding quality from 0 to 1.
* @param {Function} fn, optional, callback for asynchronous operation. Required for type "image/jpeg".
* @return {String} data URL if synchronous (callback omitted)
* @api public
*/
Canvas.prototype.toDataURL = function(type, fn){
// Default to png
type = type || 'image/png';
Canvas.prototype.toDataURL = function(a1, a2, a3){
// valid arg patterns (args -> [type, opts, fn]):
// [] -> ['image/png', null, null]
// [qual] -> ['image/png', null, null]
// [undefined] -> ['image/png', null, null]
// ['image/png'] -> ['image/png', null, null]
// ['image/png', qual] -> ['image/png', null, null]
// [fn] -> ['image/png', null, fn]
// [type, fn] -> [type, null, fn]
// [undefined, fn] -> ['image/png', null, fn]
// ['image/png', qual, fn] -> ['image/png', null, fn]
// ['image/jpeg', fn] -> ['image/jpeg', null, fn]
// ['image/jpeg', opts, fn] -> ['image/jpeg', opts, fn]
// ['image/jpeg', qual, fn] -> ['image/jpeg', {quality: qual}, fn]
// ['image/jpeg', undefined, fn] -> ['image/jpeg', null, fn]
if (this.width === 0 || this.height === 0) {
// Per spec, if the bitmap has no pixels, return this string:
return "data:,";
}
// Allow callback as first arg
if ('function' == typeof type) fn = type, type = 'image/png';
var type = 'image/png';
var opts = {};
var fn;
// Throw on non-png
if ('image/png' != type) throw new Error('currently only image/png is supported');
if ('function' === typeof a1) {
fn = a1;
} else {
if ('string' === typeof a1 && FORMATS.indexOf(a1.toLowerCase()) !== -1) {
type = a1.toLowerCase();
}
var prefix = 'data:' + type + ';base64,';
if ('function' === typeof a2) {
fn = a2;
} else {
if ('object' === typeof a2) {
opts = a2;
} else if ('number' === typeof a2) {
opts = {quality: Math.min(0, Math.max(1, a2)) * 100};
}
if ('function' === typeof a3) {
fn = a3;
} else if (undefined !== a3) {
throw new TypeError(typeof a3 + ' is not a function');
}
}
}
if ('image/png' === type) {
if (fn) {
this.toBuffer(function(err, buf){
if (err) return fn(err);
var str = 'data:' + type
fn(null, prefix + buf.toString('base64'));
fn(null, 'data:image/png;base64,' + buf.toString('base64'));
});
} else {
return prefix + this.toBuffer().toString('base64');
return 'data:image/png;base64,' + this.toBuffer().toString('base64');
}
} else if ('image/jpeg' === type) {
if (undefined === fn) {
throw new Error('Missing required callback function for format "image/jpeg"');
}
var stream = this.jpegStream(opts);
// note that jpegStream is synchronous
var buffers = [];
stream.on('data', function (chunk) {
buffers.push(chunk);
});
stream.on('error', function (err) {
fn(err);
});
stream.on('end', function() {
var result = 'data:image/jpeg;base64,' + Buffer.concat(buffers).toString('base64');
fn(null, result);
});
}
};

23
lib/context2d.js

@ -12,8 +12,7 @@ var canvas = require('./bindings')
, Context2d = canvas.CanvasRenderingContext2d
, CanvasGradient = canvas.CanvasGradient
, CanvasPattern = canvas.CanvasPattern
, ImageData = canvas.ImageData
, PixelArray = canvas.CanvasPixelArray;
, ImageData = canvas.ImageData;
/**
* Export `Context2d` as the module.
@ -76,7 +75,7 @@ var parseFont = exports.parseFont = function(str){
font.style = captures[2] || 'normal';
font.size = parseFloat(captures[3]);
font.unit = captures[4];
font.family = captures[5].replace(/["']/g, '').split(',')[0];
font.family = captures[5].replace(/["']/g, '').split(',')[0].trim();
// TODO: dpi
// TODO: remaining unit conversion
@ -351,22 +350,6 @@ Context2d.prototype.__defineGetter__('textAlign', function(){
return this.lastTextAlignment || 'start';
});
/**
* Get `ImageData` with the given rect.
*
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
* @return {ImageData}
* @api public
*/
Context2d.prototype.getImageData = function(x, y, width, height){
var arr = new PixelArray(this.canvas, x, y, width, height);
return new ImageData(arr);
};
/**
* Create `ImageData` with the given dimensions or
* `ImageData` instance for dimensions.
@ -382,5 +365,5 @@ Context2d.prototype.createImageData = function(width, height){
height = width.height;
width = width.width;
}
return new ImageData(new PixelArray(width, height));
return new ImageData(new Uint8ClampedArray(width * height * 4), width, height);
};

6
lib/jpegstream.js

@ -40,12 +40,12 @@ var JPEGStream = module.exports = function JPEGStream(canvas, options, sync) {
// TODO: implement async
if ('streamJPEG' == method) method = 'streamJPEGSync';
process.nextTick(function(){
canvas[method](options.bufsize, options.quality, options.progressive, function(err, chunk, len){
canvas[method](options.bufsize, options.quality, options.progressive, function(err, chunk){
if (err) {
self.emit('error', err);
self.readable = false;
} else if (len) {
self.emit('data', chunk, len);
} else if (chunk) {
self.emit('data', chunk);
} else {
self.emit('end');
self.readable = false;

29
lib/pixelarray.js

@ -1,29 +0,0 @@
/*!
* Canvas - PixelArray
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Canvas = require('./bindings')
, PixelArray = Canvas.CanvasPixelArray;
/**
* Custom inspect.
*/
PixelArray.prototype.inspect = function(){
var buf = '[PixelArray ';
for (var i = 0, len = this.length; i < len; i += 4) {
buf += '\n ' + i + ': rgba('
+ this[i + 0] + ','
+ this[i + 1] + ','
+ this[i + 2] + ','
+ this[i + 3] + ')';
}
return buf + '\n]';
};

21
package.json

@ -1,7 +1,7 @@
{
"name": "canvas",
"description": "Canvas graphics API backed by Cairo",
"version": "1.2.7",
"version": "1.3.6",
"author": "TJ Holowaychuk <tj@learnboost.com>",
"contributors": [
"Nathan Rajlich <nathan@tootallnate.net>",
@ -21,19 +21,24 @@
"homepage": "https://github.com/Automattic/node-canvas",
"repository": "git://github.com/Automattic/node-canvas.git",
"scripts": {
"test": "make test"
"prebenchmark": "node-gyp build",
"benchmark": "node benchmarks/run.js",
"pretest": "node-gyp build",
"test": "mocha test/*.test.js",
"pretest-server": "node-gyp build",
"test-server": "node test/server.js"
},
"dependencies": {
"nan": "^1.8.4"
"nan": "^2.1.0"
},
"devDependencies": {
"express": "3.0",
"jade": "0.28.1",
"mocha": "*",
"should": "*"
"body-parser": "^1.13.3",
"express": "^4.13.2",
"jade": "^1.11.0",
"mocha": "*"
},
"engines": {
"node": ">= 0.6.0"
"node": ">=0.8.0"
},
"main": "./lib/canvas.js",
"license": "MIT"

254
src/Canvas.cc

@ -20,42 +20,42 @@
#include "JPEGStream.h"
#endif
Persistent<FunctionTemplate> Canvas::constructor;
Nan::Persistent<FunctionTemplate> Canvas::constructor;
/*
* Initialize Canvas.
*/
void
Canvas::Initialize(Handle<Object> target) {
NanScope();
Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(Canvas::New);
NanAssignPersistent(constructor, ctor);
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Canvas::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("Canvas"));
ctor->SetClassName(Nan::New("Canvas").ToLocalChecked());
// Prototype
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
NODE_SET_PROTOTYPE_METHOD(ctor, "toBuffer", ToBuffer);
NODE_SET_PROTOTYPE_METHOD(ctor, "streamPNGSync", StreamPNGSync);
Nan::SetPrototypeMethod(ctor, "toBuffer", ToBuffer);
Nan::SetPrototypeMethod(ctor, "streamPNGSync", StreamPNGSync);
#ifdef HAVE_JPEG
NODE_SET_PROTOTYPE_METHOD(ctor, "streamJPEGSync", StreamJPEGSync);
Nan::SetPrototypeMethod(ctor, "streamJPEGSync", StreamJPEGSync);
#endif
proto->SetAccessor(NanNew("type"), GetType);
proto->SetAccessor(NanNew("width"), GetWidth, SetWidth);
proto->SetAccessor(NanNew("height"), GetHeight, SetHeight);
Nan::SetAccessor(proto, Nan::New("type").ToLocalChecked(), GetType);
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth);
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight);
NanSetTemplate(proto, "PNG_NO_FILTERS", NanNew<Uint32>(PNG_NO_FILTERS));
NanSetTemplate(proto, "PNG_FILTER_NONE", NanNew<Uint32>(PNG_FILTER_NONE));
NanSetTemplate(proto, "PNG_FILTER_SUB", NanNew<Uint32>(PNG_FILTER_SUB));
NanSetTemplate(proto, "PNG_FILTER_UP", NanNew<Uint32>(PNG_FILTER_UP));
NanSetTemplate(proto, "PNG_FILTER_AVG", NanNew<Uint32>(PNG_FILTER_AVG));
NanSetTemplate(proto, "PNG_FILTER_PAETH", NanNew<Uint32>(PNG_FILTER_PAETH));
NanSetTemplate(proto, "PNG_ALL_FILTERS", NanNew<Uint32>(PNG_ALL_FILTERS));
Nan::SetTemplate(proto, "PNG_NO_FILTERS", Nan::New<Uint32>(PNG_NO_FILTERS));
Nan::SetTemplate(proto, "PNG_FILTER_NONE", Nan::New<Uint32>(PNG_FILTER_NONE));
Nan::SetTemplate(proto, "PNG_FILTER_SUB", Nan::New<Uint32>(PNG_FILTER_SUB));
Nan::SetTemplate(proto, "PNG_FILTER_UP", Nan::New<Uint32>(PNG_FILTER_UP));
Nan::SetTemplate(proto, "PNG_FILTER_AVG", Nan::New<Uint32>(PNG_FILTER_AVG));
Nan::SetTemplate(proto, "PNG_FILTER_PAETH", Nan::New<Uint32>(PNG_FILTER_PAETH));
Nan::SetTemplate(proto, "PNG_ALL_FILTERS", Nan::New<Uint32>(PNG_ALL_FILTERS));
target->Set(NanNew("Canvas"), ctor->GetFunction());
Nan::Set(target, Nan::New("Canvas").ToLocalChecked(), ctor->GetFunction());
}
/*
@ -63,19 +63,18 @@ Canvas::Initialize(Handle<Object> target) {
*/
NAN_METHOD(Canvas::New) {
NanScope();
int width = 0, height = 0;
canvas_type_t type = CANVAS_TYPE_IMAGE;
if (args[0]->IsNumber()) width = args[0]->Uint32Value();
if (args[1]->IsNumber()) height = args[1]->Uint32Value();
if (args[2]->IsString()) type = !strcmp("pdf", *String::Utf8Value(args[2]))
if (info[0]->IsNumber()) width = info[0]->Uint32Value();
if (info[1]->IsNumber()) height = info[1]->Uint32Value();
if (info[2]->IsString()) type = !strcmp("pdf", *String::Utf8Value(info[2]))
? CANVAS_TYPE_PDF
: !strcmp("svg", *String::Utf8Value(args[2]))
: !strcmp("svg", *String::Utf8Value(info[2]))
? CANVAS_TYPE_SVG
: CANVAS_TYPE_IMAGE;
Canvas *canvas = new Canvas(width, height, type);
canvas->Wrap(args.This());
NanReturnValue(args.This());
canvas->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
/*
@ -83,9 +82,8 @@ NAN_METHOD(Canvas::New) {
*/
NAN_GETTER(Canvas::GetType) {
NanScope();
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
NanReturnValue(NanNew<String>(canvas->isPDF() ? "pdf" : canvas->isSVG() ? "svg" : "image"));
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
info.GetReturnValue().Set(Nan::New<String>(canvas->isPDF() ? "pdf" : canvas->isSVG() ? "svg" : "image").ToLocalChecked());
}
/*
@ -93,9 +91,8 @@ NAN_GETTER(Canvas::GetType) {
*/
NAN_GETTER(Canvas::GetWidth) {
NanScope();
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
NanReturnValue(NanNew<Number>(canvas->width));
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(canvas->width));
}
/*
@ -103,11 +100,10 @@ NAN_GETTER(Canvas::GetWidth) {
*/
NAN_SETTER(Canvas::SetWidth) {
NanScope();
if (value->IsNumber()) {
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
canvas->width = value->Uint32Value();
canvas->resurface(args.This());
canvas->resurface(info.This());
}
}
@ -116,9 +112,8 @@ NAN_SETTER(Canvas::SetWidth) {
*/
NAN_GETTER(Canvas::GetHeight) {
NanScope();
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
NanReturnValue(NanNew<Number>(canvas->height));
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(canvas->height));
}
/*
@ -126,11 +121,10 @@ NAN_GETTER(Canvas::GetHeight) {
*/
NAN_SETTER(Canvas::SetHeight) {
NanScope();
if (value->IsNumber()) {
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
canvas->height = value->Uint32Value();
canvas->resurface(args.This());
canvas->resurface(info.This());
}
}
@ -200,7 +194,7 @@ int
Canvas::EIO_AfterToBuffer(eio_req *req) {
#endif
NanScope();
Nan::HandleScope scope;
closure_t *closure = (closure_t *) req->data;
#if NODE_VERSION_AT_LEAST(0, 6, 0)
delete req;
@ -212,9 +206,9 @@ Canvas::EIO_AfterToBuffer(eio_req *req) {
Local<Value> argv[1] = { Canvas::Error(closure->status) };
closure->pfn->Call(1, argv);
} else {
Local<Object> buf = NanNewBufferHandle((char*)closure->data, closure->len);
Local<Object> buf = Nan::CopyBuffer((char*)closure->data, closure->len).ToLocalChecked();
memcpy(Buffer::Data(buf), closure->data, closure->len);
Local<Value> argv[2] = { NanNew(NanNull()), buf };
Local<Value> argv[2] = { Nan::Null(), buf };
closure->pfn->Call(2, argv);
}
@ -234,31 +228,31 @@ Canvas::EIO_AfterToBuffer(eio_req *req) {
*/
NAN_METHOD(Canvas::ToBuffer) {
NanScope();
cairo_status_t status;
uint32_t compression_level = 6;
uint32_t filter = PNG_ALL_FILTERS;
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
// TODO: async / move this out
if (canvas->isPDF() || canvas->isSVG()) {
cairo_surface_finish(canvas->surface());
closure_t *closure = (closure_t *) canvas->closure();
Local<Object> buf = NanNewBufferHandle((char*) closure->data, closure->len);
NanReturnValue(buf);
Local<Object> buf = Nan::CopyBuffer((char*) closure->data, closure->len).ToLocalChecked();
info.GetReturnValue().Set(buf);
return;
}
if (args.Length() > 1 && !(args[1]->StrictEquals(NanUndefined()) && args[2]->StrictEquals(NanUndefined()))) {
if (!args[1]->StrictEquals(NanUndefined())) {
if (info.Length() > 1 && !(info[1]->IsUndefined() && info[2]->IsUndefined())) {
if (!info[1]->IsUndefined()) {
bool good = true;
if (args[1]->IsNumber()) {
compression_level = args[1]->Uint32Value();
} else if (args[1]->IsString()) {
if (args[1]->StrictEquals(NanNew<String>("0"))) {
if (info[1]->IsNumber()) {
compression_level = info[1]->Uint32Value();
} else if (info[1]->IsString()) {
if (info[1]->StrictEquals(Nan::New<String>("0").ToLocalChecked())) {
compression_level = 0;
} else {
uint32_t tmp = args[1]->Uint32Value();
uint32_t tmp = info[1]->Uint32Value();
if (tmp == 0) {
good = false;
} else {
@ -271,24 +265,24 @@ NAN_METHOD(Canvas::ToBuffer) {
if (good) {
if (compression_level > 9) {
return NanThrowRangeError("Allowed compression levels lie in the range [0, 9].");
return Nan::ThrowRangeError("Allowed compression levels lie in the range [0, 9].");
}
} else {
return NanThrowTypeError("Compression level must be a number.");
return Nan::ThrowTypeError("Compression level must be a number.");
}
}
if (!args[2]->StrictEquals(NanUndefined())) {
if (args[2]->IsUint32()) {
filter = args[2]->Uint32Value();
if (!info[2]->IsUndefined()) {
if (info[2]->IsUint32()) {
filter = info[2]->Uint32Value();
} else {
return NanThrowTypeError("Invalid filter value.");
return Nan::ThrowTypeError("Invalid filter value.");
}
}
}
// Async
if (args[0]->IsFunction()) {
if (info[0]->IsFunction()) {
closure_t *closure = (closure_t *) malloc(sizeof(closure_t));
status = closure_init(closure, canvas, compression_level, filter);
@ -296,12 +290,12 @@ NAN_METHOD(Canvas::ToBuffer) {
if (status) {
closure_destroy(closure);
free(closure);
return NanThrowError(Canvas::Error(status));
return Nan::ThrowError(Canvas::Error(status));
}
// TODO: only one callback fn in closure
canvas->Ref();
closure->pfn = new NanCallback(args[0].As<Function>());
closure->pfn = new Nan::Callback(info[0].As<Function>());
#if NODE_VERSION_AT_LEAST(0, 6, 0)
uv_work_t* req = new uv_work_t;
@ -312,7 +306,7 @@ NAN_METHOD(Canvas::ToBuffer) {
ev_ref(EV_DEFAULT_UC);
#endif
NanReturnUndefined();
return;
// Sync
} else {
closure_t closure;
@ -321,7 +315,7 @@ NAN_METHOD(Canvas::ToBuffer) {
// ensure closure is ok
if (status) {
closure_destroy(&closure);
return NanThrowError(Canvas::Error(status));
return Nan::ThrowError(Canvas::Error(status));
}
TryCatch try_catch;
@ -329,14 +323,16 @@ NAN_METHOD(Canvas::ToBuffer) {
if (try_catch.HasCaught()) {
closure_destroy(&closure);
NanReturnValue(try_catch.ReThrow());
try_catch.ReThrow();
return;
} else if (status) {
closure_destroy(&closure);
return NanThrowError(Canvas::Error(status));
return Nan::ThrowError(Canvas::Error(status));
} else {
Local<Object> buf = NanNewBufferHandle((char *)closure.data, closure.len);
Local<Object> buf = Nan::CopyBuffer((char *)closure.data, closure.len).ToLocalChecked();
closure_destroy(&closure);
NanReturnValue(buf);
info.GetReturnValue().Set(buf);
return;
}
}
}
@ -347,14 +343,14 @@ NAN_METHOD(Canvas::ToBuffer) {
static cairo_status_t
streamPNG(void *c, const uint8_t *data, unsigned len) {
NanScope();
Nan::HandleScope scope;
closure_t *closure = (closure_t *) c;
Local<Object> buf = NanNewBufferHandle((char *)data, len);
Local<Object> buf = Nan::CopyBuffer((char *)data, len).ToLocalChecked();
Local<Value> argv[3] = {
NanNew(NanNull())
Nan::Null()
, buf
, NanNew<Number>(len) };
NanMakeCallback(NanGetCurrentContext()->Global(), closure->fn, 3, argv);
, Nan::New<Number>(len) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure->fn, 3, argv);
return CAIRO_STATUS_SUCCESS;
}
@ -363,23 +359,22 @@ streamPNG(void *c, const uint8_t *data, unsigned len) {
*/
NAN_METHOD(Canvas::StreamPNGSync) {
NanScope();
uint32_t compression_level = 6;
uint32_t filter = PNG_ALL_FILTERS;
// TODO: async as well
if (!args[0]->IsFunction())
return NanThrowTypeError("callback function required");
if (!info[0]->IsFunction())
return Nan::ThrowTypeError("callback function required");
if (args.Length() > 1 && !(args[1]->StrictEquals(NanUndefined()) && args[2]->StrictEquals(NanUndefined()))) {
if (!args[1]->StrictEquals(NanUndefined())) {
if (info.Length() > 1 && !(info[1]->IsUndefined() && info[2]->IsUndefined())) {
if (!info[1]->IsUndefined()) {
bool good = true;
if (args[1]->IsNumber()) {
compression_level = args[1]->Uint32Value();
} else if (args[1]->IsString()) {
if (args[1]->StrictEquals(NanNew<String>("0"))) {
if (info[1]->IsNumber()) {
compression_level = info[1]->Uint32Value();
} else if (info[1]->IsString()) {
if (info[1]->StrictEquals(Nan::New<String>("0").ToLocalChecked())) {
compression_level = 0;
} else {
uint32_t tmp = args[1]->Uint32Value();
uint32_t tmp = info[1]->Uint32Value();
if (tmp == 0) {
good = false;
} else {
@ -392,26 +387,26 @@ NAN_METHOD(Canvas::StreamPNGSync) {
if (good) {
if (compression_level > 9) {
return NanThrowRangeError("Allowed compression levels lie in the range [0, 9].");
return Nan::ThrowRangeError("Allowed compression levels lie in the range [0, 9].");
}
} else {
return NanThrowTypeError("Compression level must be a number.");
return Nan::ThrowTypeError("Compression level must be a number.");
}
}
if (!args[2]->StrictEquals(NanUndefined())) {
if (args[2]->IsUint32()) {
filter = args[2]->Uint32Value();
if (!info[2]->IsUndefined()) {
if (info[2]->IsUint32()) {
filter = info[2]->Uint32Value();
} else {
return NanThrowTypeError("Invalid filter value.");
return Nan::ThrowTypeError("Invalid filter value.");
}
}
}
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
closure_t closure;
closure.fn = Handle<Function>::Cast(args[0]);
closure.fn = Local<Function>::Cast(info[0]);
closure.compression_level = compression_level;
closure.filter = filter;
@ -420,18 +415,19 @@ NAN_METHOD(Canvas::StreamPNGSync) {
cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure);
if (try_catch.HasCaught()) {
NanReturnValue(try_catch.ReThrow());
try_catch.ReThrow();
return;
} else if (status) {
Local<Value> argv[1] = { Canvas::Error(status) };
NanMakeCallback(NanGetCurrentContext()->Global(), closure.fn, 1, argv);
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure.fn, 1, argv);
} else {
Local<Value> argv[3] = {
NanNew(NanNull())
, NanNew(NanNull())
, NanNew<Uint32>(0) };
NanMakeCallback(NanGetCurrentContext()->Global(), closure.fn, 1, argv);
Nan::Null()
, Nan::Null()
, Nan::New<Uint32>(0) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure.fn, 1, argv);
}
NanReturnUndefined();
return;
}
/*
@ -441,27 +437,27 @@ NAN_METHOD(Canvas::StreamPNGSync) {
#ifdef HAVE_JPEG
NAN_METHOD(Canvas::StreamJPEGSync) {
NanScope();
// TODO: async as well
if (!args[0]->IsNumber())
return NanThrowTypeError("buffer size required");
if (!args[1]->IsNumber())
return NanThrowTypeError("quality setting required");
if (!args[2]->IsBoolean())
return NanThrowTypeError("progressive setting required");
if (!args[3]->IsFunction())
return NanThrowTypeError("callback function required");
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
if (!info[0]->IsNumber())
return Nan::ThrowTypeError("buffer size required");
if (!info[1]->IsNumber())
return Nan::ThrowTypeError("quality setting required");
if (!info[2]->IsBoolean())
return Nan::ThrowTypeError("progressive setting required");
if (!info[3]->IsFunction())
return Nan::ThrowTypeError("callback function required");
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
closure_t closure;
closure.fn = Handle<Function>::Cast(args[3]);
closure.fn = Local<Function>::Cast(info[3]);
TryCatch try_catch;
write_to_jpeg_stream(canvas->surface(), args[0]->NumberValue(), args[1]->NumberValue(), args[2]->BooleanValue(), &closure);
write_to_jpeg_stream(canvas->surface(), info[0]->NumberValue(), info[1]->NumberValue(), info[2]->BooleanValue(), &closure);
if (try_catch.HasCaught())
NanReturnValue(try_catch.ReThrow());
NanReturnUndefined();
if (try_catch.HasCaught()) {
try_catch.ReThrow();
}
return;
}
#endif
@ -470,7 +466,7 @@ NAN_METHOD(Canvas::StreamJPEGSync) {
* Initialize cairo surface.
*/
Canvas::Canvas(int w, int h, canvas_type_t t): ObjectWrap() {
Canvas::Canvas(int w, int h, canvas_type_t t): Nan::ObjectWrap() {
type = t;
width = w;
height = h;
@ -492,7 +488,7 @@ Canvas::Canvas(int w, int h, canvas_type_t t): ObjectWrap() {
} else {
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
assert(_surface);
NanAdjustExternalMemory(4 * w * h);
Nan::AdjustExternalMemory(4 * w * h);
}
}
@ -511,7 +507,7 @@ Canvas::~Canvas() {
break;
case CANVAS_TYPE_IMAGE:
cairo_surface_destroy(_surface);
NanAdjustExternalMemory(-4 * width * height);
Nan::AdjustExternalMemory(-4 * width * height);
break;
}
}
@ -521,9 +517,9 @@ Canvas::~Canvas() {
*/
void
Canvas::resurface(Handle<Object> canvas) {
NanScope();
Handle<Value> context;
Canvas::resurface(Local<Object> canvas) {
Nan::HandleScope scope;
Local<Value> context;
switch (type) {
case CANVAS_TYPE_PDF:
cairo_pdf_surface_set_size(_surface, width, height);
@ -537,9 +533,9 @@ Canvas::resurface(Handle<Object> canvas) {
_surface = cairo_svg_surface_create_for_stream(toBuffer, _closure, width, height);
// Reset context
context = canvas->Get(NanNew<String>("context"));
context = canvas->Get(Nan::New<String>("context").ToLocalChecked());
if (!context->IsUndefined()) {
Context2d *context2d = ObjectWrap::Unwrap<Context2d>(context->ToObject());
Context2d *context2d = Nan::ObjectWrap::Unwrap<Context2d>(context->ToObject());
cairo_t *prev = context2d->context();
context2d->setContext(cairo_create(surface()));
cairo_destroy(prev);
@ -551,12 +547,12 @@ Canvas::resurface(Handle<Object> canvas) {
int old_height = cairo_image_surface_get_height(_surface);
cairo_surface_destroy(_surface);
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
NanAdjustExternalMemory(4 * (width * height - old_width * old_height));
Nan::AdjustExternalMemory(4 * (width * height - old_width * old_height));
// Reset context
context = canvas->Get(NanNew<String>("context"));
context = canvas->Get(Nan::New<String>("context").ToLocalChecked());
if (!context->IsUndefined()) {
Context2d *context2d = ObjectWrap::Unwrap<Context2d>(context->ToObject());
Context2d *context2d = Nan::ObjectWrap::Unwrap<Context2d>(context->ToObject());
cairo_t *prev = context2d->context();
context2d->setContext(cairo_create(surface()));
cairo_destroy(prev);
@ -571,5 +567,5 @@ Canvas::resurface(Handle<Object> canvas) {
Local<Value>
Canvas::Error(cairo_status_t status) {
return Exception::Error(NanNew<String>(cairo_status_to_string(status)));
return Exception::Error(Nan::New<String>(cairo_status_to_string(status)).ToLocalChecked());
}

8
src/Canvas.h

@ -47,13 +47,13 @@ typedef enum {
* Canvas.
*/
class Canvas: public node::ObjectWrap {
class Canvas: public Nan::ObjectWrap {
public:
int width;
int height;
canvas_type_t type;
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static Nan::Persistent<FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_METHOD(ToBuffer);
static NAN_GETTER(GetType);
@ -85,7 +85,7 @@ class Canvas: public node::ObjectWrap {
inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); }
inline int stride(){ return cairo_image_surface_get_stride(_surface); }
Canvas(int width, int height, canvas_type_t type);
void resurface(Handle<Object> canvas);
void resurface(Local<Object> canvas);
private:
~Canvas();

73
src/CanvasGradient.cc

@ -9,25 +9,25 @@
#include "Canvas.h"
#include "CanvasGradient.h"
Persistent<FunctionTemplate> Gradient::constructor;
Nan::Persistent<FunctionTemplate> Gradient::constructor;
/*
* Initialize CanvasGradient.
*/
void
Gradient::Initialize(Handle<Object> target) {
NanScope();
Gradient::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(Gradient::New);
NanAssignPersistent(constructor, ctor);
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Gradient::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("CanvasGradient"));
ctor->SetClassName(Nan::New("CanvasGradient").ToLocalChecked());
// Prototype
NODE_SET_PROTOTYPE_METHOD(ctor, "addColorStop", AddColorStop);
target->Set(NanNew("CanvasGradient"), ctor->GetFunction());
Nan::SetPrototypeMethod(ctor, "addColorStop", AddColorStop);
Nan::Set(target, Nan::New("CanvasGradient").ToLocalChecked(), ctor->GetFunction());
}
/*
@ -35,33 +35,33 @@ Gradient::Initialize(Handle<Object> target) {
*/
NAN_METHOD(Gradient::New) {
NanScope();
// Linear
if (4 == args.Length()) {
if (4 == info.Length()) {
Gradient *grad = new Gradient(
args[0]->NumberValue()
, args[1]->NumberValue()
, args[2]->NumberValue()
, args[3]->NumberValue());
grad->Wrap(args.This());
NanReturnValue(args.This());
info[0]->NumberValue()
, info[1]->NumberValue()
, info[2]->NumberValue()
, info[3]->NumberValue());
grad->Wrap(info.This());
info.GetReturnValue().Set(info.This());
return;
}
// Radial
if (6 == args.Length()) {
if (6 == info.Length()) {
Gradient *grad = new Gradient(
args[0]->NumberValue()
, args[1]->NumberValue()
, args[2]->NumberValue()
, args[3]->NumberValue()
, args[4]->NumberValue()
, args[5]->NumberValue());
grad->Wrap(args.This());
NanReturnValue(args.This());
info[0]->NumberValue()
, info[1]->NumberValue()
, info[2]->NumberValue()
, info[3]->NumberValue()
, info[4]->NumberValue()
, info[5]->NumberValue());
grad->Wrap(info.This());
info.GetReturnValue().Set(info.This());
return;
}
return NanThrowTypeError("invalid arguments");
return Nan::ThrowTypeError("invalid arguments");
}
/*
@ -69,31 +69,28 @@ NAN_METHOD(Gradient::New) {
*/
NAN_METHOD(Gradient::AddColorStop) {
NanScope();
if (!args[0]->IsNumber())
return NanThrowTypeError("offset required");
if (!args[1]->IsString())
return NanThrowTypeError("color string required");
if (!info[0]->IsNumber())
return Nan::ThrowTypeError("offset required");
if (!info[1]->IsString())
return Nan::ThrowTypeError("color string required");
Gradient *grad = ObjectWrap::Unwrap<Gradient>(args.This());
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(info.This());
short ok;
String::Utf8Value str(args[1]);
String::Utf8Value str(info[1]);
uint32_t rgba = rgba_from_string(*str, &ok);
if (ok) {
rgba_t color = rgba_create(rgba);
cairo_pattern_add_color_stop_rgba(
grad->pattern()
, args[0]->NumberValue()
, info[0]->NumberValue()
, color.r
, color.g
, color.b
, color.a);
} else {
return NanThrowTypeError("parse color failed");
return Nan::ThrowTypeError("parse color failed");
}
NanReturnUndefined();
}
/*

6
src/CanvasGradient.h

@ -10,10 +10,10 @@
#include "Canvas.h"
class Gradient: public node::ObjectWrap {
class Gradient: public Nan::ObjectWrap {
public:
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static Nan::Persistent<FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_METHOD(AddColorStop);
Gradient(double x0, double y0, double x1, double y1);

36
src/CanvasPattern.cc

@ -9,27 +9,27 @@
#include "Image.h"
#include "CanvasPattern.h"
Persistent<FunctionTemplate> Pattern::constructor;
Nan::Persistent<FunctionTemplate> Pattern::constructor;
/*
* Initialize CanvasPattern.
*/
void
Pattern::Initialize(Handle<Object> target) {
NanScope();
Pattern::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(Pattern::New);
NanAssignPersistent(constructor, ctor);
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Pattern::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("CanvasPattern"));
ctor->SetClassName(Nan::New("CanvasPattern").ToLocalChecked());
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("CanvasPattern"));
ctor->SetClassName(Nan::New("CanvasPattern").ToLocalChecked());
// Prototype
target->Set(NanNew("CanvasPattern"), ctor->GetFunction());
Nan::Set(target, Nan::New("CanvasPattern").ToLocalChecked(), ctor->GetFunction());
}
/*
@ -37,39 +37,37 @@ Pattern::Initialize(Handle<Object> target) {
*/
NAN_METHOD(Pattern::New) {
NanScope();
int w = 0
, h = 0;
cairo_surface_t *surface;
Local<Object> obj = args[0]->ToObject();
Local<Object> obj = info[0]->ToObject();
// Image
if (NanHasInstance(Image::constructor, obj)) {
Image *img = ObjectWrap::Unwrap<Image>(obj);
if (Nan::New(Image::constructor)->HasInstance(obj)) {
Image *img = Nan::ObjectWrap::Unwrap<Image>(obj);
if (!img->isComplete()) {
return NanThrowError("Image given has not completed loading");
return Nan::ThrowError("Image given has not completed loading");
}
w = img->width;
h = img->height;
surface = img->surface();
// Canvas
} else if (NanHasInstance(Canvas::constructor, obj)) {
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(obj);
} else if (Nan::New(Canvas::constructor)->HasInstance(obj)) {
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
w = canvas->width;
h = canvas->height;
surface = canvas->surface();
// Invalid
} else {
return NanThrowTypeError("Image or Canvas expected");
return Nan::ThrowTypeError("Image or Canvas expected");
}
Pattern *pattern = new Pattern(surface,w,h);
pattern->Wrap(args.This());
NanReturnValue(args.This());
pattern->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}

6
src/CanvasPattern.h

@ -10,10 +10,10 @@
#include "Canvas.h"
class Pattern: public node::ObjectWrap {
class Pattern: public Nan::ObjectWrap {
public:
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static Nan::Persistent<FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
Pattern(cairo_surface_t *surface, int w, int h);
inline cairo_pattern_t *pattern(){ return _pattern; }

1114
src/CanvasRenderingContext2d.cc

File diff suppressed because it is too large

7
src/CanvasRenderingContext2d.h

@ -63,14 +63,14 @@ typedef struct {
void state_assign_fontFamily(canvas_state_t *state, const char *str);
#endif
class Context2d: public node::ObjectWrap {
class Context2d: public Nan::ObjectWrap {
public:
short stateno;
canvas_state_t *states[CANVAS_MAX_STATES];
canvas_state_t *state;
Context2d(Canvas *canvas);
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static Nan::Persistent<FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_METHOD(DrawImage);
static NAN_METHOD(PutImageData);
@ -113,6 +113,7 @@ class Context2d: public node::ObjectWrap {
static NAN_METHOD(Rect);
static NAN_METHOD(Arc);
static NAN_METHOD(ArcTo);
static NAN_METHOD(GetImageData);
static NAN_GETTER(GetPatternQuality);
static NAN_GETTER(GetGlobalCompositeOperation);
static NAN_GETTER(GetGlobalAlpha);

38
src/FontFace.cc

@ -8,7 +8,7 @@
#include <fontconfig/fontconfig.h>
Persistent<FunctionTemplate> FontFace::constructor;
Nan::Persistent<FunctionTemplate> FontFace::constructor;
/*
* Destroy ft_face.
@ -26,17 +26,17 @@ FontFace::~FontFace() {
*/
void
FontFace::Initialize(Handle<Object> target) {
NanScope();
FontFace::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(FontFace::New);
NanAssignPersistent(constructor, ctor);
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(FontFace::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("FontFace"));
ctor->SetClassName(Nan::New("FontFace").ToLocalChecked());
// Prototype
target->Set(NanNew("FontFace"), ctor->GetFunction());
Nan::Set(target, Nan::New("FontFace").ToLocalChecked(), ctor->GetFunction());
}
/*
@ -53,15 +53,13 @@ static cairo_user_data_key_t key;
*/
NAN_METHOD(FontFace::New) {
NanScope();
if (!args[0]->IsString()
|| !args[1]->IsNumber()) {
return NanThrowError("Wrong argument types passed to FontFace constructor");
if (!info[0]->IsString()
|| !info[1]->IsNumber()) {
return Nan::ThrowError("Wrong argument types passed to FontFace constructor");
}
String::Utf8Value filePath(args[0]);
int faceIdx = int(args[1]->NumberValue());
String::Utf8Value filePath(info[0]);
int faceIdx = int(info[1]->NumberValue());
FT_Face ftFace;
FT_Error ftError;
@ -71,21 +69,21 @@ NAN_METHOD(FontFace::New) {
_initLibrary = false;
ftError = FT_Init_FreeType(&library);
if (ftError) {
return NanThrowError("Could not load library");
return Nan::ThrowError("Could not load library");
}
}
// Create new freetype font face.
ftError = FT_New_Face(library, *filePath, faceIdx, &ftFace);
if (ftError) {
return NanThrowError("Could not load font file");
return Nan::ThrowError("Could not load font file");
}
#if HAVE_PANGO
// Load the font file in fontconfig
FcBool ok = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(*filePath));
if (!ok) {
return NanThrowError("Could not load font in FontConfig");
return Nan::ThrowError("Could not load font in FontConfig");
}
#endif
@ -98,7 +96,7 @@ NAN_METHOD(FontFace::New) {
if (status) {
cairo_font_face_destroy (crFace);
FT_Done_Face (ftFace);
return NanThrowError("Failed to setup cairo font face user data");
return Nan::ThrowError("Failed to setup cairo font face user data");
}
// Explicit reference count the cairo font face. Otherwise the font face might
@ -106,7 +104,7 @@ NAN_METHOD(FontFace::New) {
cairo_font_face_reference(crFace);
FontFace *face = new FontFace(ftFace, crFace);
face->Wrap(args.This());
NanReturnValue(args.This());
face->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}

6
src/FontFace.h

@ -13,10 +13,10 @@
#include <cairo-ft.h>
#include FT_FREETYPE_H
class FontFace: public node::ObjectWrap {
class FontFace: public Nan::ObjectWrap {
public:
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static Nan::Persistent<FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
FontFace(FT_Face ftFace, cairo_font_face_t *crFace)
:_ftFace(ftFace), _crFace(crFace) {}

103
src/Image.cc

@ -28,38 +28,38 @@ typedef struct {
uint8_t *buf;
} read_closure_t;
Persistent<FunctionTemplate> Image::constructor;
Nan::Persistent<FunctionTemplate> Image::constructor;
/*
* Initialize Image.
*/
void
Image::Initialize(Handle<Object> target) {
NanScope();
Image::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(Image::New);
NanAssignPersistent(constructor, ctor);
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Image::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("Image"));
ctor->SetClassName(Nan::New("Image").ToLocalChecked());
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("Image"));
ctor->SetClassName(Nan::New("Image").ToLocalChecked());
// Prototype
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
proto->SetAccessor(NanNew("source"), GetSource, SetSource);
proto->SetAccessor(NanNew("complete"), GetComplete);
proto->SetAccessor(NanNew("width"), GetWidth);
proto->SetAccessor(NanNew("height"), GetHeight);
proto->SetAccessor(NanNew("onload"), GetOnload, SetOnload);
proto->SetAccessor(NanNew("onerror"), GetOnerror, SetOnerror);
Nan::SetAccessor(proto, Nan::New("source").ToLocalChecked(), GetSource, SetSource);
Nan::SetAccessor(proto, Nan::New("complete").ToLocalChecked(), GetComplete);
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth);
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight);
Nan::SetAccessor(proto, Nan::New("onload").ToLocalChecked(), GetOnload, SetOnload);
Nan::SetAccessor(proto, Nan::New("onerror").ToLocalChecked(), GetOnerror, SetOnerror);
#if CAIRO_VERSION_MINOR >= 10
proto->SetAccessor(NanNew("dataMode"), GetDataMode, SetDataMode);
ctor->Set(NanNew("MODE_IMAGE"), NanNew<Number>(DATA_IMAGE));
ctor->Set(NanNew("MODE_MIME"), NanNew<Number>(DATA_MIME));
Nan::SetAccessor(proto, Nan::New("dataMode").ToLocalChecked(), GetDataMode, SetDataMode);
ctor->Set(Nan::New("MODE_IMAGE").ToLocalChecked(), Nan::New<Number>(DATA_IMAGE));
ctor->Set(Nan::New("MODE_MIME").ToLocalChecked(), Nan::New<Number>(DATA_MIME));
#endif
target->Set(NanNew("Image"), ctor->GetFunction());
Nan::Set(target, Nan::New("Image").ToLocalChecked(), ctor->GetFunction());
}
/*
@ -67,11 +67,10 @@ Image::Initialize(Handle<Object> target) {
*/
NAN_METHOD(Image::New) {
NanScope();
Image *img = new Image;
img->data_mode = DATA_IMAGE;
img->Wrap(args.This());
NanReturnValue(args.This());
img->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
/*
@ -79,9 +78,8 @@ NAN_METHOD(Image::New) {
*/
NAN_GETTER(Image::GetComplete) {
NanScope();
Image *img = ObjectWrap::Unwrap<Image>(args.This());
NanReturnValue(NanNew<Boolean>(Image::COMPLETE == img->state));
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
info.GetReturnValue().Set(Nan::New<Boolean>(Image::COMPLETE == img->state));
}
#if CAIRO_VERSION_MINOR >= 10
@ -91,9 +89,8 @@ NAN_GETTER(Image::GetComplete) {
*/
NAN_GETTER(Image::GetDataMode) {
NanScope();
Image *img = ObjectWrap::Unwrap<Image>(args.This());
NanReturnValue(NanNew<Number>(img->data_mode));
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(img->data_mode));
}
/*
@ -102,7 +99,7 @@ NAN_GETTER(Image::GetDataMode) {
NAN_SETTER(Image::SetDataMode) {
if (value->IsNumber()) {
Image *img = ObjectWrap::Unwrap<Image>(args.This());
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
int mode = value->Uint32Value();
img->data_mode = (data_mode_t) mode;
}
@ -115,18 +112,16 @@ NAN_SETTER(Image::SetDataMode) {
*/
NAN_GETTER(Image::GetWidth) {
NanScope();
Image *img = ObjectWrap::Unwrap<Image>(args.This());
NanReturnValue(NanNew<Number>(img->width));
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(img->width));
}
/*
* Get height.
*/
NAN_GETTER(Image::GetHeight) {
NanScope();
Image *img = ObjectWrap::Unwrap<Image>(args.This());
NanReturnValue(NanNew<Number>(img->height));
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(img->height));
}
/*
@ -134,9 +129,8 @@ NAN_GETTER(Image::GetHeight) {
*/
NAN_GETTER(Image::GetSource) {
NanScope();
Image *img = ObjectWrap::Unwrap<Image>(args.This());
NanReturnValue(NanNew<String>(img->filename ? img->filename : ""));
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
info.GetReturnValue().Set(Nan::New<String>(img->filename ? img->filename : "").ToLocalChecked());
}
/*
@ -147,7 +141,7 @@ void
Image::clearData() {
if (_surface) {
cairo_surface_destroy(_surface);
NanAdjustExternalMemory(-_data_len);
Nan::AdjustExternalMemory(-_data_len);
_data_len = 0;
_surface = NULL;
}
@ -167,8 +161,7 @@ Image::clearData() {
*/
NAN_SETTER(Image::SetSource) {
NanScope();
Image *img = ObjectWrap::Unwrap<Image>(args.This());
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
cairo_status_t status = CAIRO_STATUS_READ_ERROR;
img->clearData();
@ -259,12 +252,11 @@ Image::readPNG(void *c, uint8_t *data, unsigned int len) {
*/
NAN_GETTER(Image::GetOnload) {
NanScope();
Image *img = ObjectWrap::Unwrap<Image>(args.This());
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
if (img->onload) {
NanReturnValue(img->onload->GetFunction());
info.GetReturnValue().Set(img->onload->GetFunction());
} else {
NanReturnNull();
info.GetReturnValue().SetNull();
}
}
@ -274,8 +266,8 @@ NAN_GETTER(Image::GetOnload) {
NAN_SETTER(Image::SetOnload) {
if (value->IsFunction()) {
Image *img = ObjectWrap::Unwrap<Image>(args.This());
img->onload = new NanCallback(value.As<Function>());
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
img->onload = new Nan::Callback(value.As<Function>());
}
}
@ -284,12 +276,11 @@ NAN_SETTER(Image::SetOnload) {
*/
NAN_GETTER(Image::GetOnerror) {
NanScope();
Image *img = ObjectWrap::Unwrap<Image>(args.This());
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
if (img->onerror) {
NanReturnValue(img->onerror->GetFunction());
info.GetReturnValue().Set(img->onerror->GetFunction());
} else {
NanReturnNull();
info.GetReturnValue().SetNull();
}
}
@ -299,8 +290,8 @@ NAN_GETTER(Image::GetOnerror) {
NAN_SETTER(Image::SetOnerror) {
if (value->IsFunction()) {
Image *img = ObjectWrap::Unwrap<Image>(args.This());
img->onerror = new NanCallback(value.As<Function>());
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
img->onerror = new Nan::Callback(value.As<Function>());
}
}
@ -356,13 +347,13 @@ Image::load() {
void
Image::loaded() {
NanScope();
Nan::HandleScope scope;
state = COMPLETE;
width = cairo_image_surface_get_width(_surface);
height = cairo_image_surface_get_height(_surface);
_data_len = height * cairo_image_surface_get_stride(_surface);
NanAdjustExternalMemory(_data_len);
Nan::AdjustExternalMemory(_data_len);
if (onload != NULL) {
onload->Call(0, NULL);
@ -375,7 +366,7 @@ Image::loaded() {
void
Image::error(Local<Value> err) {
NanScope();
Nan::HandleScope scope;
if (onerror != NULL) {
Local<Value> argv[1] = { err };
onerror->Call(1, argv);
@ -806,7 +797,7 @@ Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) {
void
clearMimeData(void *closure) {
NanAdjustExternalMemory(-((read_closure_t *)closure)->len);
Nan::AdjustExternalMemory(-((read_closure_t *)closure)->len);
free(((read_closure_t *) closure)->buf);
free(closure);
}
@ -833,7 +824,7 @@ Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) {
mime_closure->buf = mime_data;
mime_closure->len = len;
NanAdjustExternalMemory(len);
Nan::AdjustExternalMemory(len);
return cairo_surface_set_mime_data(_surface
, mime_type

10
src/Image.h

@ -27,14 +27,14 @@
class Image: public node::ObjectWrap {
class Image: public Nan::ObjectWrap {
public:
char *filename;
int width, height;
NanCallback *onload;
NanCallback *onerror;
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
Nan::Callback *onload;
Nan::Callback *onerror;
static Nan::Persistent<FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_GETTER(GetSource);
static NAN_GETTER(GetOnload);

110
src/ImageData.cc

@ -7,27 +7,27 @@
#include "ImageData.h"
Persistent<FunctionTemplate> ImageData::constructor;
Nan::Persistent<FunctionTemplate> ImageData::constructor;
/*
* Initialize ImageData.
*/
void
ImageData::Initialize(Handle<Object> target) {
NanScope();
ImageData::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(ImageData::New);
NanAssignPersistent(constructor, ctor);
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(ImageData::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("ImageData"));
ctor->SetClassName(Nan::New("ImageData").ToLocalChecked());
// Prototype
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
proto->SetAccessor(NanNew("width"), GetWidth);
proto->SetAccessor(NanNew("height"), GetHeight);
target->Set(NanNew("ImageData"), ctor->GetFunction());
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth);
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight);
Nan::Set(target, Nan::New("ImageData").ToLocalChecked(), ctor->GetFunction());
}
/*
@ -35,17 +35,81 @@ ImageData::Initialize(Handle<Object> target) {
*/
NAN_METHOD(ImageData::New) {
NanScope();
Local<Object> obj = args[0]->ToObject();
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
Local<v8::Object> clampedArray;
Local<Object> global = Context::GetCurrent()->Global();
#else
Local<Uint8ClampedArray> clampedArray;
#endif
if (!NanHasInstance(PixelArray::constructor, obj))
return NanThrowTypeError("CanvasPixelArray expected");
uint32_t width;
uint32_t height;
int length;
PixelArray *arr = ObjectWrap::Unwrap<PixelArray>(obj);
ImageData *imageData = new ImageData(arr);
args.This()->Set(NanNew("data"), args[0]);
imageData->Wrap(args.This());
NanReturnValue(args.This());
if (info[0]->IsUint32() && info[1]->IsUint32()) {
width = info[0]->Uint32Value();
if (width == 0) {
Nan::ThrowRangeError("The source width is zero.");
return;
}
height = info[1]->Uint32Value();
if (height == 0) {
Nan::ThrowRangeError("The source height is zero.");
return;
}
length = width * height * 4;
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
Local<Int32> sizeHandle = Nan::New(length);
Local<Value> caargv[] = { sizeHandle };
clampedArray = global->Get(Nan::New("Uint8ClampedArray").ToLocalChecked()).As<Function>()->NewInstance(1, caargv);
#else
clampedArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), length), 0, length);
#endif
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
} else if (info[0]->ToObject()->GetIndexedPropertiesExternalArrayDataType() == kExternalPixelArray && info[1]->IsUint32()) {
clampedArray = info[0]->ToObject();
length = clampedArray->GetIndexedPropertiesExternalArrayDataLength();
#else
} else if (info[0]->IsUint8ClampedArray() && info[1]->IsUint32()) {
clampedArray = info[0].As<Uint8ClampedArray>();
length = clampedArray->Length();
#endif
if (length == 0) {
Nan::ThrowRangeError("The input data has a zero byte length.");
return;
}
if (length % 4 != 0) {
Nan::ThrowRangeError("The input data byte length is not a multiple of 4.");
return;
}
width = info[1]->Uint32Value();
int size = length / 4;
if (width == 0) {
Nan::ThrowRangeError("The source width is zero.");
return;
}
if (size % width != 0) {
Nan::ThrowRangeError("The input data byte length is not a multiple of (4 * width).");
return;
}
height = size / width;
if (info[2]->IsUint32() && info[2]->Uint32Value() != height) {
Nan::ThrowRangeError("The input data byte length is not equal to (4 * width * height).");
return;
}
} else {
Nan::ThrowTypeError("Expected (Uint8ClampedArray, width[, height]) or (width, height)");
return;
}
Nan::TypedArrayContents<uint8_t> dataPtr(clampedArray);
ImageData *imageData = new ImageData(reinterpret_cast<uint8_t*>(*dataPtr), width, height);
imageData->Wrap(info.This());
info.This()->Set(Nan::New("data").ToLocalChecked(), clampedArray);
info.GetReturnValue().Set(info.This());
}
/*
@ -53,9 +117,8 @@ NAN_METHOD(ImageData::New) {
*/
NAN_GETTER(ImageData::GetWidth) {
NanScope();
ImageData *imageData = ObjectWrap::Unwrap<ImageData>(args.This());
NanReturnValue(NanNew<Number>(imageData->pixelArray()->width()));
ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(imageData->width()));
}
/*
@ -63,7 +126,6 @@ NAN_GETTER(ImageData::GetWidth) {
*/
NAN_GETTER(ImageData::GetHeight) {
NanScope();
ImageData *imageData = ObjectWrap::Unwrap<ImageData>(args.This());
NanReturnValue(NanNew<Number>(imageData->pixelArray()->height()));
ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(imageData->height()));
}

22
src/ImageData.h

@ -9,20 +9,28 @@
#define __NODE_IMAGE_DATA_H__
#include "Canvas.h"
#include "PixelArray.h"
#include <stdlib.h>
#include "v8.h"
class ImageData: public node::ObjectWrap {
class ImageData: public Nan::ObjectWrap {
public:
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static Nan::Persistent<FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_GETTER(GetWidth);
static NAN_GETTER(GetHeight);
inline PixelArray *pixelArray(){ return _arr; }
ImageData(PixelArray *arr): _arr(arr) {}
inline int width() { return _width; }
inline int height() { return _height; }
inline uint8_t *data() { return _data; }
inline int stride() { return _width * 4; }
ImageData(uint8_t *data, int width, int height) : _width(width), _height(height), _data(data) {}
private:
PixelArray *_arr;
int _width;
int _height;
uint8_t *_data;
};
#endif

53
src/JPEGStream.h

@ -29,15 +29,19 @@ init_closure_destination(j_compress_ptr cinfo){
boolean
empty_closure_output_buffer(j_compress_ptr cinfo){
NanScope();
Nan::HandleScope scope;
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
Local<Object> buf = NanNewBufferHandle((char *)dest->buffer, dest->bufsize);
Local<Value> argv[3] = {
NanNew(NanNull())
, NanNew(buf)
, NanNew<Integer>(dest->bufsize)
Local<Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize).ToLocalChecked();
// emit "data"
Local<Value> argv[2] = {
Nan::Null()
, buf
};
NanMakeCallback(NanGetCurrentContext()->Global(), dest->closure->fn, 3, argv);
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)dest->closure->fn, 2, argv);
dest->buffer = (JOCTET *)malloc(dest->bufsize);
cinfo->dest->next_output_byte = dest->buffer;
cinfo->dest->free_in_buffer = dest->bufsize;
return true;
@ -45,28 +49,26 @@ empty_closure_output_buffer(j_compress_ptr cinfo){
void
term_closure_destination(j_compress_ptr cinfo){
NanScope();
Nan::HandleScope scope;
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
/* emit remaining data */
size_t remaining = dest->bufsize - cinfo->dest->free_in_buffer;
Local<Object> buf = NanNewBufferHandle((char *)dest->buffer, remaining);
Local<Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize).ToLocalChecked();
Local<Value> data_argv[3] = {
NanNew(NanNull())
, NanNew(buf)
, NanNew<Number>(remaining)
Local<Value> data_argv[2] = {
Nan::Null()
, buf
};
NanMakeCallback(NanGetCurrentContext()->Global(), dest->closure->fn, 3, data_argv);
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)dest->closure->fn, 2, data_argv);
// emit "end"
Local<Value> end_argv[3] = {
NanNew(NanNull())
, NanNew(NanNull())
, NanNew<Integer>(0)
Local<Value> end_argv[2] = {
Nan::Null()
, Nan::Null()
};
NanMakeCallback(NanGetCurrentContext()->Global(), dest->closure->fn, 3, end_argv);
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)dest->closure->fn, 2, end_argv);
}
void
@ -96,16 +98,6 @@ jpeg_closure_dest(j_compress_ptr cinfo, closure_t * closure, int bufsize){
cinfo->dest->free_in_buffer = dest->bufsize;
}
void
jpeg_free_custom_allocations(j_compress_ptr cinfo){
closure_destination_mgr * dest;
dest = (closure_destination_mgr *) cinfo->dest;
if (dest->buffer) {
free(dest->buffer);
dest->buffer = NULL;
}
}
void
write_to_jpeg_stream(cairo_surface_t *surface, int bufsize, int quality, bool progressive, closure_t *closure){
int w = cairo_image_surface_get_width(surface);
@ -148,7 +140,6 @@ write_to_jpeg_stream(cairo_surface_t *surface, int bufsize, int quality, bool pr
}
free(dst);
jpeg_finish_compress(&cinfo);
jpeg_free_custom_allocations(&cinfo);
jpeg_destroy_compress(&cinfo);
}

163
src/PixelArray.cc

@ -1,163 +0,0 @@
//
// PixelArray.cc
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "PixelArray.h"
#include <stdlib.h>
#include <string.h>
Persistent<FunctionTemplate> PixelArray::constructor;
/*
* Initialize PixelArray.
*/
void
PixelArray::Initialize(Handle<Object> target) {
NanScope();
// Constructor
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(PixelArray::New);
NanAssignPersistent(constructor, ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("CanvasPixelArray"));
// Prototype
Local<ObjectTemplate> proto = ctor->InstanceTemplate();
proto->SetAccessor(NanNew("length"), GetLength);
target->Set(NanNew("CanvasPixelArray"), ctor->GetFunction());
}
/*
* Initialize a new PixelArray.
*/
NAN_METHOD(PixelArray::New) {
NanScope();
PixelArray *arr;
Local<Object> obj = args[0]->ToObject();
switch (args.Length()) {
// width, height
case 2:
arr = new PixelArray(
args[0]->Int32Value()
, args[1]->Int32Value());
break;
// canvas, x, y, width, height
case 5: {
if (!NanHasInstance(Canvas::constructor, obj))
return NanThrowTypeError("Canvas expected");
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(obj);
arr = new PixelArray(
canvas
, args[1]->Int32Value()
, args[2]->Int32Value()
, args[3]->Int32Value()
, args[4]->Int32Value());
}
break;
default:
return NanThrowTypeError("invalid arguments");
}
// Let v8 handle accessors (and clamping)
args.This()->SetIndexedPropertiesToPixelData(
arr->data()
, arr->length());
arr->Wrap(args.This());
NanReturnValue(args.This());
}
/*
* Get length.
*/
NAN_GETTER(PixelArray::GetLength) {
NanScope();
NanReturnValue(NanNew<Number>(args.This()->GetIndexedPropertiesPixelDataLength()));
}
/*
* Initialize a new PixelArray copying data
* from the canvas surface using the given rect.
*/
PixelArray::PixelArray(Canvas *canvas, int sx, int sy, int width, int height):
_width(width), _height(height) {
// Alloc space for our new data
uint8_t *dst = alloc();
uint8_t *src = canvas->data();
int srcStride = canvas->stride()
, dstStride = stride();
if (sx < 0) width += sx, sx = 0;
if (sy < 0) height += sy, sy = 0;
if (sx + width > canvas->width) width = canvas->width - sx;
if (sy + height > canvas->height) height = canvas->height - sy;
if (width <= 0 || height <= 0) return;
// Normalize data (argb -> rgba)
for (int y = 0; y < height; ++y) {
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
for (int x = 0; x < width; ++x) {
int bx = x * 4;
uint32_t *pixel = row + x + sx;
uint8_t a = *pixel >> 24;
uint8_t r = *pixel >> 16;
uint8_t g = *pixel >> 8;
uint8_t b = *pixel;
dst[bx + 3] = a;
// Performance optimization: fully transparent/opaque pixels
// can be processed more efficiently
if (a != 0 && a != 255) {
float alpha = (float) a / 255;
dst[bx + 0] = (int)((float) r / alpha);
dst[bx + 1] = (int)((float) g / alpha);
dst[bx + 2] = (int)((float) b / alpha);
} else {
dst[bx + 0] = r;
dst[bx + 1] = g;
dst[bx + 2] = b;
}
}
dst += dstStride;
}
}
/*
* Initialize an empty PixelArray with the given dimensions.
*/
PixelArray::PixelArray(int width, int height):
_width(width), _height(height) {
alloc();
}
/*
* Allocate / zero data buffer. Hint mem adjustment.
*/
uint8_t *
PixelArray::alloc() {
int len = length();
_data = (uint8_t *) calloc(1, len);
NanAdjustExternalMemory(len);
return _data;
}
/*
* Hint mem adjustment.
*/
PixelArray::~PixelArray() {
NanAdjustExternalMemory(-length());
free(_data);
}

33
src/PixelArray.h

@ -1,33 +0,0 @@
//
// PixelArray.h
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#ifndef __NODE_PIXEL_ARRAY_H__
#define __NODE_PIXEL_ARRAY_H__
#include "Canvas.h"
class PixelArray: public node::ObjectWrap {
public:
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static NAN_METHOD(New);
static NAN_GETTER(GetLength);
inline int length(){ return _width * _height * 4; }
inline int width(){ return _width; }
inline int height(){ return _height; }
inline int stride(){ return _width * 4; }
inline uint8_t *data(){ return _data; }
PixelArray(Canvas *canvas, int x, int y, int width, int height);
PixelArray(int width, int height);
~PixelArray();
private:
uint8_t *alloc();
uint8_t *_data;
int _width, _height;
};
#endif

6
src/closure.h

@ -23,8 +23,8 @@
*/
typedef struct {
NanCallback *pfn;
Handle<Function> fn;
Nan::Callback *pfn;
Local<Function> fn;
unsigned len;
unsigned max_len;
uint8_t *data;
@ -58,7 +58,7 @@ void
closure_destroy(closure_t *closure) {
if (closure->len) {
free(closure->data);
NanAdjustExternalMemory(-((intptr_t) closure->max_len));
Nan::AdjustExternalMemory(-((intptr_t) closure->max_len));
}
}

5
src/color.cc

@ -10,6 +10,11 @@
#include <cmath>
#include <limits>
// Compatibility with Visual Studio versions prior to VS2015
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
/*
* Parse integer value
*/

19
src/init.cc

@ -9,7 +9,6 @@
#include "Canvas.h"
#include "Image.h"
#include "ImageData.h"
#include "PixelArray.h"
#include "CanvasGradient.h"
#include "CanvasPattern.h"
#include "CanvasRenderingContext2d.h"
@ -18,13 +17,15 @@
#include "FontFace.h"
#endif
extern "C" void
init (Handle<Object> target) {
NanScope();
// Compatibility with Visual Studio versions prior to VS2015
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
NAN_MODULE_INIT(init) {
Canvas::Initialize(target);
Image::Initialize(target);
ImageData::Initialize(target);
PixelArray::Initialize(target);
Context2d::Initialize(target);
Gradient::Initialize(target);
Pattern::Initialize(target);
@ -32,7 +33,7 @@ init (Handle<Object> target) {
FontFace::Initialize(target);
#endif
target->Set(NanNew<String>("cairoVersion"), NanNew<String>(cairo_version_string()));
target->Set(Nan::New<String>("cairoVersion").ToLocalChecked(), Nan::New<String>(cairo_version_string()).ToLocalChecked());
#ifdef HAVE_JPEG
#ifndef JPEG_LIB_VERSION_MAJOR
@ -57,16 +58,16 @@ init (Handle<Object> target) {
} else {
snprintf(jpeg_version, 10, "%d", JPEG_LIB_VERSION_MAJOR);
}
target->Set(NanNew<String>("jpegVersion"), NanNew<String>(jpeg_version));
target->Set(Nan::New<String>("jpegVersion").ToLocalChecked(), Nan::New<String>(jpeg_version).ToLocalChecked());
#endif
#ifdef HAVE_GIF
#ifndef GIF_LIB_VERSION
char gif_version[10];
snprintf(gif_version, 10, "%d.%d.%d", GIFLIB_MAJOR, GIFLIB_MINOR, GIFLIB_RELEASE);
target->Set(NanNew<String>("gifVersion"), NanNew<String>(gif_version));
target->Set(Nan::New<String>("gifVersion").ToLocalChecked(), Nan::New<String>(gif_version).ToLocalChecked());
#else
target->Set(NanNew<String>("gifVersion"), NanNew<String>(GIF_LIB_VERSION));
target->Set(Nan::New<String>("gifVersion").ToLocalChecked(), Nan::New<String>(GIF_LIB_VERSION).ToLocalChecked());
#endif
#endif
}

311
test/canvas.test.js

@ -11,16 +11,16 @@ console.log();
console.log(' canvas: %s', Canvas.version);
console.log(' cairo: %s', Canvas.cairoVersion);
module.exports = {
'test .version': function(){
Canvas.version.should.match(/^\d+\.\d+\.\d+$/);
},
describe('Canvas', function () {
it('.version', function () {
assert.ok(/^\d+\.\d+\.\d+$/.test(Canvas.version));
});
'test .cairoVersion': function(){
Canvas.cairoVersion.should.match(/^\d+\.\d+\.\d+$/);
},
it('.cairoVersion', function () {
assert.ok(/^\d+\.\d+\.\d+$/.test(Canvas.cairoVersion));
});
'test .parseFont()': function(){
it('.parseFont()', function () {
var tests = [
'20px Arial'
, { size: 20, unit: 'px', family: 'Arial' }
@ -39,17 +39,17 @@ module.exports = {
, '20px monospace'
, { size: 20, unit: 'px', family: 'monospace' }
, '50px Arial, sans-serif'
, { size: 50, unit: 'px', family: 'Arial, sans-serif' }
, { size: 50, unit: 'px', family: 'Arial' }
, 'bold italic 50px Arial, sans-serif'
, { style: 'italic', weight: 'bold', size: 50, unit: 'px', family: 'Arial, sans-serif' }
, { style: 'italic', weight: 'bold', size: 50, unit: 'px', family: 'Arial' }
, '50px Helvetica , Arial, sans-serif'
, { size: 50, unit: 'px', family: 'Helvetica , Arial, sans-serif' }
, { size: 50, unit: 'px', family: 'Helvetica' }
, '50px "Helvetica Neue", sans-serif'
, { size: 50, unit: 'px', family: 'Helvetica Neue, sans-serif' }
, { size: 50, unit: 'px', family: 'Helvetica Neue' }
, '50px "Helvetica Neue", "foo bar baz" , sans-serif'
, { size: 50, unit: 'px', family: 'Helvetica Neue, foo bar baz , sans-serif' }
, { size: 50, unit: 'px', family: 'Helvetica Neue' }
, "50px 'Helvetica Neue'"
, { size: 50, unit: 'px', family: "Helvetica Neue" }
, { size: 50, unit: 'px', family: 'Helvetica Neue' }
, 'italic 20px Arial'
, { size: 20, unit: 'px', style: 'italic', family: 'Arial' }
, 'oblique 20px Arial'
@ -70,17 +70,15 @@ module.exports = {
var str = tests[i++]
, obj = tests[i]
, actual = parseFont(str);
if (!obj.style) obj.style = 'normal';
if (!obj.weight) obj.weight = 'normal';
// actual.should.eql(obj);
}
},
'test .PixelArray': function(){
assert.equal(typeof Canvas.PixelArray, 'function');
},
assert.deepEqual(obj, actual);
}
});
'test color serialization': function(){
it('color serialization', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
@ -106,9 +104,9 @@ module.exports = {
ctx[prop] = grad;
assert.strictEqual(grad, ctx[prop], prop + ' pattern getter failed');
});
},
});
'test color parser': function(){
it('color parser', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
@ -208,9 +206,9 @@ module.exports = {
ctx.fillStyle = 'hsl(1.24e2, 760e-1%, 4.7e1%)';
assert.equal('#1dd329', ctx.fillStyle);
},
});
'test Canvas#type': function(){
it('Canvas#type', function () {
var canvas = new Canvas(10, 10);
assert('image' == canvas.type);
var canvas = new Canvas(10, 10, 'pdf');
@ -219,17 +217,17 @@ module.exports = {
assert('svg' == canvas.type);
var canvas = new Canvas(10, 10, 'hey');
assert('image' == canvas.type);
},
});
'test Canvas#getContext("2d")': function(){
it('Canvas#getContext("2d")', function () {
var canvas = new Canvas(200, 300)
, ctx = canvas.getContext('2d');
assert.ok('object' == typeof ctx);
assert.equal(canvas, ctx.canvas, 'context.canvas is not canvas');
assert.equal(ctx, canvas.context, 'canvas.context is not context');
},
});
'test Canvas#{width,height}=': function(){
it('Canvas#{width,height}=', function () {
var canvas = new Canvas(100, 200);
assert.equal(100, canvas.width);
assert.equal(200, canvas.height);
@ -242,13 +240,13 @@ module.exports = {
canvas.height = 50;
assert.equal(50, canvas.width);
assert.equal(50, canvas.height);
},
});
'test Canvas#getContext("invalid")': function(){
it('Canvas#getContext("invalid")', function () {
assert.equal(null, new Canvas(200, 300).getContext('invalid'));
},
});
'test Context2d#patternQuality': function(){
it('Context2d#patternQuality', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
@ -257,32 +255,34 @@ module.exports = {
assert.equal('best', ctx.patternQuality);
ctx.patternQuality = 'invalid';
assert.equal('best', ctx.patternQuality);
},
});
'test Context2d#font=': function(){
it('Context2d#font=', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
assert.equal('10px sans-serif', ctx.font);
ctx.font = '15px Arial, sans-serif';
assert.equal('15px Arial, sans-serif', ctx.font);
},
});
'test Context2d#lineWidth=': function(){
it('Context2d#lineWidth=', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
ctx.lineWidth = 10.0;
assert.equal(10, ctx.lineWidth);
// ctx.lineWidth = Infinity;
ctx.lineWidth = Infinity;
assert.equal(10, ctx.lineWidth);
ctx.lineWidth = -Infinity;
assert.equal(10, ctx.lineWidth);
ctx.lineWidth = -5;
assert.equal(10, ctx.lineWidth);
ctx.lineWidth = 0;
assert.equal(10, ctx.lineWidth);
},
});
'test Context2d#antiAlias=': function(){
it('Context2d#antiAlias=', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
@ -297,37 +297,36 @@ module.exports = {
assert.equal('subpixel', ctx.antialias);
ctx.antialias = 1;
assert.equal('subpixel', ctx.antialias);
},
});
'test Context2d#lineCap=': function(){
it('Context2d#lineCap=', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
assert.equal('butt', ctx.lineCap);
ctx.lineCap = 'round';
assert.equal('round', ctx.lineCap);
},
});
'test Context2d#lineJoin=': function(){
it('Context2d#lineJoin=', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
assert.equal('miter', ctx.lineJoin);
ctx.lineJoin = 'round';
assert.equal('round', ctx.lineJoin);
},
});
'test Context2d#globalAlpha=': function(){
it('Context2d#globalAlpha=', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
assert.equal(1, ctx.globalAlpha);
ctx.globalAlpha = 0.5
assert.equal(0.5, ctx.globalAlpha);
},
});
'test Context2d#isPointInPath()': function(){
it('Context2d#isPointInPath()', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
@ -344,9 +343,9 @@ module.exports = {
assert.ok(ctx.isPointInPath(60,110));
assert.ok(!ctx.isPointInPath(70,110));
assert.ok(!ctx.isPointInPath(50,120));
},
});
'test Context2d#textAlign': function(){
it('Context2d#textAlign', function () {
var canvas = new Canvas(200,200)
, ctx = canvas.getContext('2d');
@ -359,21 +358,22 @@ module.exports = {
assert.equal('end', ctx.textAlign);
ctx.textAlign = 'fail';
assert.equal('end', ctx.textAlign);
},
});
'test Canvas#toBuffer()': function(){
it('Canvas#toBuffer()', function () {
var buf = new Canvas(200,200).toBuffer();
assert.equal('PNG', buf.slice(1,4).toString());
},
});
'test Canvas#toBuffer() async': function(){
it('Canvas#toBuffer() async', function (done) {
new Canvas(200, 200).toBuffer(function(err, buf){
assert.ok(!err);
assert.equal('PNG', buf.slice(1,4).toString());
done();
});
});
},
'test Canvas#toDataURL()': function(){
describe('#toDataURL()', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
@ -381,33 +381,138 @@ module.exports = {
ctx.fillStyle = 'red';
ctx.fillRect(100,0,100,100);
it('toDataURL() works and defaults to PNG', function () {
assert.ok(0 == canvas.toDataURL().indexOf('data:image/png;base64,'));
});
it('toDataURL(0.5) works and defaults to PNG', function () {
assert.ok(0 == canvas.toDataURL(0.5).indexOf('data:image/png;base64,'));
});
it('toDataURL(undefined) works and defaults to PNG', function () {
assert.ok(0 == canvas.toDataURL(undefined).indexOf('data:image/png;base64,'));
});
it('toDataURL("image/png") works', function () {
assert.ok(0 == canvas.toDataURL('image/png').indexOf('data:image/png;base64,'));
});
var err;
try {
it('toDataURL("image/png", 0.5) works', function () {
assert.ok(0 == canvas.toDataURL('image/png').indexOf('data:image/png;base64,'));
});
it('toDataURL("iMaGe/PNg") works', function () {
assert.ok(0 == canvas.toDataURL('iMaGe/PNg').indexOf('data:image/png;base64,'));
});
it('toDataURL("image/jpeg") throws', function () {
assert.throws(
function () {
canvas.toDataURL('image/jpeg');
} catch (e) {
err = e;
}
assert.equal('currently only image/png is supported', err.message);
},
function (err) {
return err.message === 'Missing required callback function for format "image/jpeg"';
}
);
});
'test Canvas#toDataURL() async': function(){
it('toDataURL(function (err, str) {...}) works and defaults to PNG', function (done) {
new Canvas(200,200).toDataURL(function(err, str){
assert.ok(!err);
assert.ok(0 == str.indexOf('data:image/png;base64,'));
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/png;base64,'));
done();
});
});
},
'test Canvas#toDataURL() async with type': function(){
it('toDataURL(0.5, function (err, str) {...}) works and defaults to PNG', function (done) {
new Canvas(200,200).toDataURL(0.5, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/png;base64,'));
done();
});
});
it('toDataURL(undefined, function (err, str) {...}) works and defaults to PNG', function (done) {
new Canvas(200,200).toDataURL(undefined, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/png;base64,'));
done();
});
});
it('toDataURL("image/png", function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/png', function(err, str){
assert.ok(!err);
assert.ok(0 == str.indexOf('data:image/png;base64,'));
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/png;base64,'));
done();
});
});
it('toDataURL("image/png", 0.5, function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/png', 0.5, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/png;base64,'));
done();
});
});
it('toDataURL("image/png", {}) works', function () {
assert.ok(0 == canvas.toDataURL('image/png', {}).indexOf('data:image/png;base64,'));
});
it('toDataURL("image/jpeg", {}) throws', function () {
assert.throws(
function () {
canvas.toDataURL('image/jpeg', {});
},
function (err) {
return err.message === 'Missing required callback function for format "image/jpeg"';
}
);
});
it('toDataURL("image/jpeg", function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/jpeg', function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/jpeg;base64,'));
done();
});
});
'test Context2d#createImageData(width, height)': function(){
it('toDataURL("iMAge/JPEG", function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('iMAge/JPEG', function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/jpeg;base64,'));
done();
});
});
it('toDataURL("image/jpeg", undefined, function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/jpeg', undefined, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/jpeg;base64,'));
done();
});
});
it('toDataURL("image/jpeg", 0.5, function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/jpeg', 0.5, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/jpeg;base64,'));
done();
});
});
it('toDataURL("image/jpeg", opts, function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/jpeg', {quality: 100}, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/jpeg;base64,'));
done();
});
});
});
it('Context2d#createImageData(width, height)', function () {
var canvas = new Canvas(20, 20)
, ctx = canvas.getContext('2d');
@ -420,18 +525,18 @@ module.exports = {
assert.equal(0, imageData.data[1]);
assert.equal(0, imageData.data[2]);
assert.equal(0, imageData.data[3]);
},
});
'test Context2d#measureText().width': function(){
it('Context2d#measureText().width', function () {
var canvas = new Canvas(20, 20)
, ctx = canvas.getContext('2d');
assert.ok(ctx.measureText('foo').width);
assert.ok(ctx.measureText('foo').width != ctx.measureText('foobar').width);
assert.ok(ctx.measureText('foo').width != ctx.measureText(' foo').width);
},
});
'test Context2d#createImageData(ImageData)': function(){
it('Context2d#createImageData(ImageData)', function () {
var canvas = new Canvas(20, 20)
, ctx = canvas.getContext('2d');
@ -439,9 +544,9 @@ module.exports = {
assert.equal(2, imageData.width);
assert.equal(6, imageData.height);
assert.equal(2 * 6 * 4, imageData.data.length);
},
});
'test Context2d#getImageData()': function(){
it('Context2d#getImageData()', function () {
var canvas = new Canvas(3, 6)
, ctx = canvas.getContext('2d');
@ -499,9 +604,9 @@ module.exports = {
assert.equal(255, data[0]);
data[0] = -4444;
assert.equal(0, data[0]);
},
});
'test Context2d#createPattern(Canvas)': function(){
it('Context2d#createPattern(Canvas)', function () {
var pattern = new Canvas(2,2)
, checkers = pattern.getContext('2d');
@ -571,9 +676,9 @@ module.exports = {
// alternate b, except when moving to a new row
b = i % (imageData.width*4) == 0 ? b : !b;
}
},
});
'test Context2d#createPattern(Image)': function(){
it('Context2d#createPattern(Image)', function () {
var img = new Canvas.Image();
img.src = __dirname + '/fixtures/checkers.png';
@ -605,9 +710,9 @@ module.exports = {
// alternate b, except when moving to a new row
b = i % (imageData.width*4) == 0 ? b : !b;
}
},
});
'test Context2d#createLinearGradient()': function(){
it('Context2d#createLinearGradient()', function () {
var canvas = new Canvas(20, 1)
, ctx = canvas.getContext('2d')
, gradient = ctx.createLinearGradient(1,1,19,1);
@ -635,6 +740,46 @@ module.exports = {
assert.equal(0, imageData.data[i+1]);
assert.equal(0, imageData.data[i+2]);
assert.equal(255, imageData.data[i+3]);
});
it('Canvas#createSyncPNGStream()', function (done) {
var canvas = new Canvas(20, 20);
var stream = canvas.createSyncPNGStream();
var firstChunk = true;
stream.on('data', function(chunk){
if (firstChunk) {
firstChunk = false;
assert.equal('PNG', chunk.slice(1,4).toString());
}
});
stream.on('end', function(){
done();
});
stream.on('error', function(err) {
done(err);
});
});
it('Canvas#jpegStream()', function (done) {
var canvas = new Canvas(640, 480);
var stream = canvas.jpegStream();
var firstChunk = true;
var bytes = 0;
stream.on('data', function(chunk){
if (firstChunk) {
firstChunk = false;
assert.equal(0xFF, chunk[0]);
assert.equal(0xD8, chunk[1]);
assert.equal(0xFF, chunk[2]);
}
bytes += chunk.length;
});
stream.on('end', function(){
assert.equal(bytes, 8192);
done();
});
stream.on('error', function(err) {
done(err);
});
});
});

68
test/image.test.js

@ -10,34 +10,33 @@ var Canvas = require('../')
var png_checkers = __dirname + '/fixtures/checkers.png';
var png = __dirname + '/fixtures/clock.png';
module.exports = {
'tset Image': function(){
describe('Image', function () {
it('Image', function () {
assert.ok(Image instanceof Function);
},
});
'test Image#onload': function(){
it('Image#onload', function () {
var img = new Image
, n = 0;
, onloadCalled = 0;
assert.strictEqual(null, img.onload);
assert.strictEqual(false, img.complete);
img.onload = function () {
++n;
assert.equal(img.src, png);
onloadCalled += 1;
assert.strictEqual(img.src, png);
};
img.src = png;
assert.equal(img.src, png);
assert.strictEqual(1, onloadCalled);
assert.strictEqual(img.src, png);
assert.equal(img.src, png);
assert.strictEqual(true, img.complete);
assert.strictEqual(320, img.width);
assert.strictEqual(320, img.height);
assert.equal(1, n);
},
});
'test Image#onload multiple times': function() {
it('test Image#onload multiple times', function() {
var img = new Image
, n = 0;
@ -65,22 +64,22 @@ module.exports = {
};
img.src = png;
assert.equal(n, 1);
},
});
'test Image#onerror': function(){
it('Image#onerror', function () {
var img = new Image
, error
, n = 0;
, onerrorCalled = 0;
assert.strictEqual(null, img.onerror);
assert.strictEqual(false, img.complete);
img.onload = function () {
assert.fail('called onload');
};
img.onerror = function (err) {
++n;
onerrorCalled += 1;
error = err;
};
@ -90,14 +89,14 @@ module.exports = {
assert.fail('error did not invoke onerror(): ' + err);
}
assert.equal(img.src, png + 's');
assert.strictEqual(1, onerrorCalled);
assert.strictEqual(img.src, png + 's');
assert.strictEqual(false, img.complete);
assert.ok(error instanceof Error, 'did not invoke onerror() with error');
assert.strictEqual(false, img.complete);
assert.equal(1, n);
},
});
'test Image#onerror multiple calls': function() {
it('test Image#onerror multiple calls', function() {
var img = new Image
, n = 0;
@ -125,27 +124,30 @@ module.exports = {
img.src = png + 's3';
assert.equal(img.src, png + 's3');
assert.equal(n, 1);
},
});
'test Image#{width,height}': function(){
it('Image#{width,height}', function () {
var img = new Image
, n = 0;
, onloadCalled = 0;
assert.strictEqual(0, img.width);
assert.strictEqual(0, img.height);
img.onload = function () {
++n;
onloadCalled += 1;
assert.strictEqual(320, img.width);
assert.strictEqual(320, img.height);
};
img.src = png;
assert.equal(1, n);
},
img.src = png;
assert.strictEqual(1, onloadCalled);
assert.strictEqual(320, img.width);
assert.strictEqual(320, img.height);
});
'test Image#src set empty buffer': function(){
it('Image#src set empty buffer', function () {
var image = new Canvas.Image();
image.src = new Buffer(0);
image.src = new Buffer('');
}
};
});
});

58
test/imageData.test.js

@ -0,0 +1,58 @@
'use strict';
var Canvas = require('../')
, ImageData = Canvas.ImageData
, assert = require('assert');
describe('ImageData', function () {
it('should throw with invalid numeric arguments', function () {
assert.throws(function () {
new ImageData(0, 0);
}, /width is zero/);
assert.throws(function () {
new ImageData(1, 0);
}, /height is zero/);
assert.throws(function () {
new ImageData(0);
}, TypeError);
});
it('should construct with width and height', function () {
var imagedata = new ImageData(2, 3);
assert.strictEqual(imagedata.width, 2);
assert.strictEqual(imagedata.height, 3);
assert(imagedata.data instanceof Uint8ClampedArray);
assert.strictEqual(imagedata.data.length, 24);
});
it('should throw with invalid typed array', function () {
assert.throws(function () {
new ImageData(new Uint8ClampedArray(0), 0);
}, /input data has a zero byte length/);
assert.throws(function () {
new ImageData(new Uint8ClampedArray(3), 0);
}, /input data byte length is not a multiple of 4/);
assert.throws(function () {
new ImageData(new Uint8ClampedArray(16), 3);
}, RangeError);
assert.throws(function () {
new ImageData(new Uint8ClampedArray(12), 3, 5);
}, RangeError);
});
it('should construct with typed array', function () {
var data = new Uint8ClampedArray(2 * 3 * 4);
var imagedata = new ImageData(data, 2);
assert.strictEqual(imagedata.width, 2);
assert.strictEqual(imagedata.height, 3);
assert(imagedata.data instanceof Uint8ClampedArray);
assert.strictEqual(imagedata.data.length, 24);
data = new Uint8ClampedArray(3 * 4 * 4);
imagedata = new ImageData(data, 3, 4);
assert.strictEqual(imagedata.width, 3);
assert.strictEqual(imagedata.height, 4);
assert(imagedata.data instanceof Uint8ClampedArray);
assert.strictEqual(imagedata.data.length, 48);
});
});

30
test/public/app.js

@ -65,27 +65,28 @@ function runTests() {
var fn = tests[name]
, canvas = create('canvas')
, tr = create('tr')
, tds = [create('td'), create('td'), create('td')];
, tds = [create('td'), create('td'), create('td'), create('td')];
canvas.width = 200;
canvas.height = 200;
canvas.title = name;
tds[1].appendChild(canvas);
tds[2].appendChild(create('h3', name));
tds[2].appendChild(pdfForm(fn, canvas));
tds[2].appendChild(canvas);
tds[3].appendChild(create('h3', name));
tds[3].appendChild(pdfForm(fn, canvas));
tr.appendChild(tds[0]);
tr.appendChild(tds[1]);
tr.appendChild(tds[2]);
tr.appendChild(tds[3]);
tbody.appendChild(tr);
table.appendChild(tbody);
runTest(name, canvas, tds[0], tds[2]);
runTest(name, canvas, tds[0], tds[1], tds[3]);
}
}
function runTest(name, canvas, dest, stats) {
function runTest(name, canvas, dest, jpegDest, stats) {
var fn = tests[name]
, start = new Date;
try {
@ -96,7 +97,7 @@ function runTest(name, canvas, dest, stats) {
var duration = new Date - start;
stats.appendChild(create('p', 'browser: ' + duration + 'ms'));
stats.appendChild(create('p', 'fps: ' + (1000 / duration).toFixed(0)));
renderOnServer(name, canvas, function(res){
renderOnServer('/render', name, canvas, function(res){
if (res.error) {
var p = create('p');
p.innerText = res.error;
@ -109,16 +110,27 @@ function runTest(name, canvas, dest, stats) {
dest.appendChild(img);
}
});
renderOnServer('/jpeg', name, canvas, function(res){
if (res.error) {
var p = create('p');
p.innerText = res.error;
jpegDest.appendChild(p);
} else if (res.data) {
var img = create('img');
img.src = res.data;
jpegDest.appendChild(img);
}
});
}
function renderOnServer(name, canvas, fn) {
function renderOnServer(url, name, canvas, fn) {
var req = new XMLHttpRequest
, json = JSON.stringify({
fn: tests[name].toString()
, width: canvas.width
, height: canvas.height
});
req.open('POST', '/render');
req.open('POST', url);
req.setRequestHeader('Content-Type', 'application/json');
req.onreadystatechange = function(){
if (4 == req.readyState) {

15
test/public/tests.js

@ -1537,6 +1537,20 @@ tests['shadow image'] = function(ctx, done){
img.src = 'star.png';
};
tests['scaled shadow image'] = function(ctx, done){
var img = new Image;
img.onload = function(){
ctx.shadowColor = '#f3ac22';
ctx.shadowBlur = 2;
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 8;
ctx.drawImage(img, 10, 10, 80, 80);
done();
};
img.onerror = function(){}
img.src = 'star.png';
};
tests['shadow integration'] = function(ctx){
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 10;
@ -1992,7 +2006,6 @@ tests['fillStyle=\'hsla(...)\''] = function(ctx){
for (j=0;j<6;j++){
ctx.fillStyle = 'hsla(' + (360-60*i) + ',' +
(100-16.66*j) + '%,50%,' + (1-0.16*j) + ')';
console.log((100-16.66*j));
ctx.fillRect(j*25,i*25,25,25);
}
}

41
test/server.js

@ -6,6 +6,7 @@
var express = require('express')
, Canvas = require('../lib/canvas')
, Image = Canvas.Image
, bodyParser = require('body-parser')
, app = express();
// Config
@ -15,12 +16,8 @@ app.set('view engine', 'jade');
// Middleware
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(app.router);
app.use(bodyParser.json());
app.use(express.static(__dirname + '/public'));
app.use(express.errorHandler());
// Routes
@ -40,6 +37,15 @@ function testFn(req){
return eval('(' + req.body.fn + ')');
}
function executeTestFn(ctx, fn, done) {
if(2 === fn.length) {
fn(ctx, done);
} else {
fn(ctx);
done();
}
}
function createCanvas(req, type){
var width = req.body.width
, height = req.body.height;
@ -55,13 +61,12 @@ app.post('/render', function(req, res, next){
function done(){
var duration = new Date - start;
canvas.toDataURL(function(err, str){
if (err) throw err;
res.send({ data: str, duration: duration });
});
}
2 == fn.length
? fn(ctx, done)
: fn(ctx), done();
executeTestFn(ctx, fn, done);
});
app.post('/pdf', function(req, res, next){
@ -76,11 +81,25 @@ app.post('/pdf', function(req, res, next){
res.end();
}
2 == fn.length
? fn(ctx, done)
: fn(ctx), done();
executeTestFn(ctx, fn, done);
});
app.post('/jpeg', function(req, res, next){
// Send nothing if jpeg isn't available.
if (!Canvas.jpegVersion) { return res.send({}).end(); }
var fn = testFn(req)
, canvas = createCanvas(req)
, ctx = canvas.getContext('2d');
function done(){
canvas.toDataURL('image/jpeg', function (err, str){
if (err) throw err;
res.send({data: str});
});
}
executeTestFn(ctx, fn, done);
});
var port = parseInt(process.argv[2] || '4000', 10);
app.listen(port);

2
test/views/layout.jade

@ -1,4 +1,4 @@
!!!
doctype
html
head
title node-canvas

1
test/views/tests.jade

@ -18,6 +18,7 @@ block content
thead
tr
th node-canvas
th node-canvas (JPEG)
th target
th
tbody

Loading…
Cancel
Save