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. 105
      benchmarks/run.js
  7. 8
      binding.gyp
  8. 65
      install
  9. 110
      lib/canvas.js
  10. 23
      lib/context2d.js
  11. 6
      lib/jpegstream.js
  12. 29
      lib/pixelarray.js
  13. 21
      package.json
  14. 258
      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. 1122
      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. 55
      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. 331
      test/canvas.test.js
  35. 76
      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" - "0.12"
- "iojs-v1.8.4" - "iojs-v1.8.4"
- "iojs-v2.5.0" - "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: before_install:
- '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28' - if [[ $TRAVIS_NODE_VERSION == 0.8 ]]; then npm install -g npm@1.4.28; fi
- sudo chown -R $USER /usr/local - npm explore npm -g -- npm install node-gyp@latest
- sh install 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 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 $ 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 OS | Command
$ wget https://raw.githubusercontent.com/LearnBoost/node-canvas/master/install -O - | sh ----- | -----
``` 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 **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).
```bash
sudo port install pkgconfig libpng giflib freetype libpixman cairo
```
## Screencasts ## 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 ```javascript
canvas.toDataURL(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
or specify the mime type: 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
```javascript
canvas.toDataURL('image/png', function(err, str){
});
``` ```
### CanvasRenderingContext2d#patternQuality ### CanvasRenderingContext2d#patternQuality
@ -274,7 +270,7 @@ fs.writeFile('out.svg', canvas.toBuffer());
## Contribute ## 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 ## Examples

105
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') var Canvas = require('../lib/canvas')
@ -8,34 +9,50 @@ var Canvas = require('../lib/canvas')
, largeCanvas = new Canvas(1000, 1000) , largeCanvas = new Canvas(1000, 1000)
, ctx = canvas.getContext('2d'); , ctx = canvas.getContext('2d');
var times = 10000; var initialTimes = 10;
console.log('\n \x1b[33m%s\x1b[0m times\n', times); var minDuration_ms = 2000;
function bm(label, overrideTimes, fn) { var queue = [], running = false;
var start = new Date
, n = times;
if ('function' == typeof overrideTimes) { function bm(label, fn) {
fn = overrideTimes; queue.push({label: label, fn: fn});
} else { next();
n = overrideTimes; }
label += ' (' + n + ' times)';
}
var pending = n;
function done(){ function next() {
var duration = (new Date - start) / 1000; if (queue.length && !running) {
console.log(' - \x1b[33m%s\x1b[0m %ss', label, duration); run(queue.pop(), initialTimes, Date.now());
} }
}
if (fn.length) { function run(benchmark, n, start) {
while (n--) fn(function(){ running = true;
--pending || done(); var originalN = n;
var fn = benchmark.fn;
if (fn.length) { // async
var pending = n;
while (n--) fn(function () {
--pending || done(benchmark, originalN, start, true);
}); });
} else { } else {
while (n--) fn(); 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();
} }
} }
@ -46,7 +63,7 @@ bm('lineTo()', function(){
}); });
bm('arc()', function(){ bm('arc()', function(){
ctx.arc(75,75,50,0,Math.PI*2,true); ctx.arc(75,75,50,0,Math.PI*2,true);
}); });
bm('fillStyle= hex', function(){ bm('fillStyle= hex', function(){
@ -57,6 +74,8 @@ bm('fillStyle= rgba()', function(){
ctx.fillStyle = 'rgba(0,255,80,1)'; 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(){ bm('fillRect()', function(){
ctx.fillRect(50, 50, 100, 100); ctx.fillRect(50, 50, 100, 100);
}); });
@ -73,19 +92,31 @@ bm('linear gradients', function(){
ctx.fillRect(10,10,130,130); ctx.fillRect(10,10,130,130);
}); });
bm('toBuffer() 200x200', 50, function(){ bm('toBuffer() 200x200', function(){
canvas.toBuffer(); canvas.toBuffer();
}); });
bm('toBuffer() 1000x1000', 50, function(){ bm('toBuffer() 1000x1000', function(){
largeCanvas.toBuffer(); 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'); canvas.toBuffer().toString('base64');
}); });
bm('toDataURL() 200x200', 50, function(){ bm('toDataURL() 200x200', function(){
canvas.toDataURL(); canvas.toDataURL();
}); });
@ -109,14 +140,12 @@ bm('getImageData(0,0,100,100)', function(){
ctx.getImageData(0,0,100,100); ctx.getImageData(0,0,100,100);
}); });
// bm('PNGStream 200x200', 50, function(done){ bm('PNGStream 200x200', function(done){
// var stream = canvas.createSyncPNGStream(); var stream = canvas.createSyncPNGStream();
// stream.on('data', function(chunk){ stream.on('data', function(chunk){
// // whatever // whatever
// }); });
// stream.on('end', function(){ stream.on('end', function(){
// done(); done();
// }); });
// }); });
console.log();

8
binding.gyp

@ -49,8 +49,7 @@
'src/color.cc', 'src/color.cc',
'src/Image.cc', 'src/Image.cc',
'src/ImageData.cc', 'src/ImageData.cc',
'src/init.cc', 'src/init.cc'
'src/PixelArray.cc'
], ],
'conditions': [ 'conditions': [
['OS=="win"', { ['OS=="win"', {
@ -63,7 +62,6 @@
'<(GTK_Root)/include/cairo', '<(GTK_Root)/include/cairo',
], ],
'defines': [ 'defines': [
'snprintf=_snprintf',
'_USE_MATH_DEFINES' # for M_PI '_USE_MATH_DEFINES' # for M_PI
], ],
'configurations': { 'configurations': {
@ -72,7 +70,7 @@
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'WarningLevel': 4, 'WarningLevel': 4,
'ExceptionHandling': 1, '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': { 'VCCLCompilerTool': {
'WarningLevel': 4, 'WarningLevel': 4,
'ExceptionHandling': 1, '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

110
lib/canvas.js

@ -13,13 +13,13 @@ var canvas = require('./bindings')
, Canvas = canvas.Canvas , Canvas = canvas.Canvas
, Image = canvas.Image , Image = canvas.Image
, cairoVersion = canvas.cairoVersion , cairoVersion = canvas.cairoVersion
, PixelArray = canvas.CanvasPixelArray
, Context2d = require('./context2d') , Context2d = require('./context2d')
, PNGStream = require('./pngstream') , PNGStream = require('./pngstream')
, JPEGStream = require('./jpegstream') , JPEGStream = require('./jpegstream')
, FontFace = canvas.FontFace , FontFace = canvas.FontFace
, fs = require('fs') , fs = require('fs')
, packageJson = require("../package.json"); , packageJson = require("../package.json")
, FORMATS = ['image/png', 'image/jpeg'];
/** /**
* Export `Canvas` as the module. * Export `Canvas` as the module.
@ -62,8 +62,8 @@ if (canvas.gifVersion) {
exports.Context2d = Context2d; exports.Context2d = Context2d;
exports.PNGStream = PNGStream; exports.PNGStream = PNGStream;
exports.JPEGStream = JPEGStream; exports.JPEGStream = JPEGStream;
exports.PixelArray = PixelArray;
exports.Image = Image; exports.Image = Image;
exports.ImageData = canvas.ImageData;
if (FontFace) { if (FontFace) {
function Font(name, path, idx) { function Font(name, path, idx) {
@ -100,12 +100,6 @@ require('./context2d');
require('./image'); require('./image');
/**
* PixelArray implementation.
*/
require('./pixelarray');
/** /**
* Inspect canvas. * 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 {String} type, optional, one of "image/png" or "image/jpeg", defaults to "image/png"
* @param {Function} fn * @param {Object|Number} encoderOptions, optional, options for jpeg compression (see documentation for Canvas#jpegStream) or the JPEG encoding quality from 0 to 1.
* @return {String} * @param {Function} fn, optional, callback for asynchronous operation. Required for type "image/jpeg".
* @return {String} data URL if synchronous (callback omitted)
* @api public * @api public
*/ */
Canvas.prototype.toDataURL = function(type, fn){ Canvas.prototype.toDataURL = function(a1, a2, a3){
// Default to png // valid arg patterns (args -> [type, opts, fn]):
type = type || 'image/png'; // [] -> ['image/png', null, null]
// [qual] -> ['image/png', null, null]
// Allow callback as first arg // [undefined] -> ['image/png', null, null]
if ('function' == typeof type) fn = type, type = 'image/png'; // ['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:,";
}
// Throw on non-png var type = 'image/png';
if ('image/png' != type) throw new Error('currently only image/png is supported'); var opts = {};
var fn;
var prefix = 'data:' + type + ';base64,'; if ('function' === typeof a1) {
fn = a1;
} else {
if ('string' === typeof a1 && FORMATS.indexOf(a1.toLowerCase()) !== -1) {
type = a1.toLowerCase();
}
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 (fn) { if ('image/png' === type) {
this.toBuffer(function(err, buf){ if (fn) {
if (err) return fn(err); this.toBuffer(function(err, buf){
var str = 'data:' + type if (err) return fn(err);
fn(null, prefix + buf.toString('base64')); fn(null, 'data:image/png;base64,' + buf.toString('base64'));
});
} else {
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);
}); });
} else {
return prefix + this.toBuffer().toString('base64');
} }
}; };

23
lib/context2d.js

@ -12,8 +12,7 @@ var canvas = require('./bindings')
, Context2d = canvas.CanvasRenderingContext2d , Context2d = canvas.CanvasRenderingContext2d
, CanvasGradient = canvas.CanvasGradient , CanvasGradient = canvas.CanvasGradient
, CanvasPattern = canvas.CanvasPattern , CanvasPattern = canvas.CanvasPattern
, ImageData = canvas.ImageData , ImageData = canvas.ImageData;
, PixelArray = canvas.CanvasPixelArray;
/** /**
* Export `Context2d` as the module. * Export `Context2d` as the module.
@ -76,7 +75,7 @@ var parseFont = exports.parseFont = function(str){
font.style = captures[2] || 'normal'; font.style = captures[2] || 'normal';
font.size = parseFloat(captures[3]); font.size = parseFloat(captures[3]);
font.unit = captures[4]; font.unit = captures[4];
font.family = captures[5].replace(/["']/g, '').split(',')[0]; font.family = captures[5].replace(/["']/g, '').split(',')[0].trim();
// TODO: dpi // TODO: dpi
// TODO: remaining unit conversion // TODO: remaining unit conversion
@ -351,22 +350,6 @@ Context2d.prototype.__defineGetter__('textAlign', function(){
return this.lastTextAlignment || 'start'; 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 * Create `ImageData` with the given dimensions or
* `ImageData` instance for dimensions. * `ImageData` instance for dimensions.
@ -382,5 +365,5 @@ Context2d.prototype.createImageData = function(width, height){
height = width.height; height = width.height;
width = width.width; 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 // TODO: implement async
if ('streamJPEG' == method) method = 'streamJPEGSync'; if ('streamJPEG' == method) method = 'streamJPEGSync';
process.nextTick(function(){ 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) { if (err) {
self.emit('error', err); self.emit('error', err);
self.readable = false; self.readable = false;
} else if (len) { } else if (chunk) {
self.emit('data', chunk, len); self.emit('data', chunk);
} else { } else {
self.emit('end'); self.emit('end');
self.readable = false; 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", "name": "canvas",
"description": "Canvas graphics API backed by Cairo", "description": "Canvas graphics API backed by Cairo",
"version": "1.2.7", "version": "1.3.6",
"author": "TJ Holowaychuk <tj@learnboost.com>", "author": "TJ Holowaychuk <tj@learnboost.com>",
"contributors": [ "contributors": [
"Nathan Rajlich <nathan@tootallnate.net>", "Nathan Rajlich <nathan@tootallnate.net>",
@ -21,19 +21,24 @@
"homepage": "https://github.com/Automattic/node-canvas", "homepage": "https://github.com/Automattic/node-canvas",
"repository": "git://github.com/Automattic/node-canvas.git", "repository": "git://github.com/Automattic/node-canvas.git",
"scripts": { "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": { "dependencies": {
"nan": "^1.8.4" "nan": "^2.1.0"
}, },
"devDependencies": { "devDependencies": {
"express": "3.0", "body-parser": "^1.13.3",
"jade": "0.28.1", "express": "^4.13.2",
"mocha": "*", "jade": "^1.11.0",
"should": "*" "mocha": "*"
}, },
"engines": { "engines": {
"node": ">= 0.6.0" "node": ">=0.8.0"
}, },
"main": "./lib/canvas.js", "main": "./lib/canvas.js",
"license": "MIT" "license": "MIT"

258
src/Canvas.cc

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

73
src/CanvasGradient.cc

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

6
src/CanvasGradient.h

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

36
src/CanvasPattern.cc

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

6
src/CanvasPattern.h

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

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

38
src/FontFace.cc

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

6
src/FontFace.h

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

103
src/Image.cc

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

10
src/Image.h

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

110
src/ImageData.cc

@ -7,27 +7,27 @@
#include "ImageData.h" #include "ImageData.h"
Persistent<FunctionTemplate> ImageData::constructor; Nan::Persistent<FunctionTemplate> ImageData::constructor;
/* /*
* Initialize ImageData. * Initialize ImageData.
*/ */
void void
ImageData::Initialize(Handle<Object> target) { ImageData::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
NanScope(); Nan::HandleScope scope;
// Constructor // Constructor
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(ImageData::New); Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(ImageData::New);
NanAssignPersistent(constructor, ctor); constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1); ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("ImageData")); ctor->SetClassName(Nan::New("ImageData").ToLocalChecked());
// Prototype // Prototype
Local<ObjectTemplate> proto = ctor->PrototypeTemplate(); Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
proto->SetAccessor(NanNew("width"), GetWidth); Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth);
proto->SetAccessor(NanNew("height"), GetHeight); Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight);
target->Set(NanNew("ImageData"), ctor->GetFunction()); Nan::Set(target, Nan::New("ImageData").ToLocalChecked(), ctor->GetFunction());
} }
/* /*
@ -35,17 +35,81 @@ ImageData::Initialize(Handle<Object> target) {
*/ */
NAN_METHOD(ImageData::New) { NAN_METHOD(ImageData::New) {
NanScope(); #if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
Local<Object> obj = args[0]->ToObject(); Local<v8::Object> clampedArray;
Local<Object> global = Context::GetCurrent()->Global();
#else
Local<Uint8ClampedArray> clampedArray;
#endif
if (!NanHasInstance(PixelArray::constructor, obj)) uint32_t width;
return NanThrowTypeError("CanvasPixelArray expected"); uint32_t height;
int length;
PixelArray *arr = ObjectWrap::Unwrap<PixelArray>(obj); if (info[0]->IsUint32() && info[1]->IsUint32()) {
ImageData *imageData = new ImageData(arr); width = info[0]->Uint32Value();
args.This()->Set(NanNew("data"), args[0]); if (width == 0) {
imageData->Wrap(args.This()); Nan::ThrowRangeError("The source width is zero.");
NanReturnValue(args.This()); 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) { NAN_GETTER(ImageData::GetWidth) {
NanScope(); ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(info.This());
ImageData *imageData = ObjectWrap::Unwrap<ImageData>(args.This()); info.GetReturnValue().Set(Nan::New<Number>(imageData->width()));
NanReturnValue(NanNew<Number>(imageData->pixelArray()->width()));
} }
/* /*
@ -63,7 +126,6 @@ NAN_GETTER(ImageData::GetWidth) {
*/ */
NAN_GETTER(ImageData::GetHeight) { NAN_GETTER(ImageData::GetHeight) {
NanScope(); ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(info.This());
ImageData *imageData = ObjectWrap::Unwrap<ImageData>(args.This()); info.GetReturnValue().Set(Nan::New<Number>(imageData->height()));
NanReturnValue(NanNew<Number>(imageData->pixelArray()->height()));
} }

22
src/ImageData.h

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

55
src/JPEGStream.h

@ -29,15 +29,19 @@ init_closure_destination(j_compress_ptr cinfo){
boolean boolean
empty_closure_output_buffer(j_compress_ptr cinfo){ empty_closure_output_buffer(j_compress_ptr cinfo){
NanScope(); Nan::HandleScope scope;
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest; closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
Local<Object> buf = NanNewBufferHandle((char *)dest->buffer, dest->bufsize);
Local<Value> argv[3] = { Local<Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize).ToLocalChecked();
NanNew(NanNull())
, NanNew(buf) // emit "data"
, NanNew<Integer>(dest->bufsize) 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->next_output_byte = dest->buffer;
cinfo->dest->free_in_buffer = dest->bufsize; cinfo->dest->free_in_buffer = dest->bufsize;
return true; return true;
@ -45,28 +49,26 @@ empty_closure_output_buffer(j_compress_ptr cinfo){
void void
term_closure_destination(j_compress_ptr cinfo){ term_closure_destination(j_compress_ptr cinfo){
NanScope(); Nan::HandleScope scope;
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest; closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
/* emit remaining data */ /* emit remaining data */
size_t remaining = dest->bufsize - cinfo->dest->free_in_buffer; Local<Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize).ToLocalChecked();
Local<Object> buf = NanNewBufferHandle((char *)dest->buffer, remaining);
Local<Value> data_argv[3] = { Local<Value> data_argv[2] = {
NanNew(NanNull()) Nan::Null()
, NanNew(buf) , buf
, NanNew<Number>(remaining)
}; };
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" // emit "end"
Local<Value> end_argv[3] = { Local<Value> end_argv[2] = {
NanNew(NanNull()) Nan::Null()
, NanNew(NanNull()) , Nan::Null()
, NanNew<Integer>(0)
}; };
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 void
@ -82,7 +84,7 @@ jpeg_closure_dest(j_compress_ptr cinfo, closure_t * closure, int bufsize){
sizeof(closure_destination_mgr)); sizeof(closure_destination_mgr));
} }
dest = (closure_destination_mgr *) cinfo->dest; dest = (closure_destination_mgr *) cinfo->dest;
cinfo->dest->init_destination = &init_closure_destination; cinfo->dest->init_destination = &init_closure_destination;
cinfo->dest->empty_output_buffer = &empty_closure_output_buffer; cinfo->dest->empty_output_buffer = &empty_closure_output_buffer;
@ -96,16 +98,6 @@ jpeg_closure_dest(j_compress_ptr cinfo, closure_t * closure, int bufsize){
cinfo->dest->free_in_buffer = dest->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 void
write_to_jpeg_stream(cairo_surface_t *surface, int bufsize, int quality, bool progressive, closure_t *closure){ 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); 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); free(dst);
jpeg_finish_compress(&cinfo); jpeg_finish_compress(&cinfo);
jpeg_free_custom_allocations(&cinfo);
jpeg_destroy_compress(&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 { typedef struct {
NanCallback *pfn; Nan::Callback *pfn;
Handle<Function> fn; Local<Function> fn;
unsigned len; unsigned len;
unsigned max_len; unsigned max_len;
uint8_t *data; uint8_t *data;
@ -58,7 +58,7 @@ void
closure_destroy(closure_t *closure) { closure_destroy(closure_t *closure) {
if (closure->len) { if (closure->len) {
free(closure->data); 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 <cmath>
#include <limits> #include <limits>
// Compatibility with Visual Studio versions prior to VS2015
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
/* /*
* Parse integer value * Parse integer value
*/ */

19
src/init.cc

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

331
test/canvas.test.js

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

76
test/image.test.js

@ -10,34 +10,33 @@ var Canvas = require('../')
var png_checkers = __dirname + '/fixtures/checkers.png'; var png_checkers = __dirname + '/fixtures/checkers.png';
var png = __dirname + '/fixtures/clock.png'; var png = __dirname + '/fixtures/clock.png';
module.exports = { describe('Image', function () {
'tset Image': function(){ it('Image', function () {
assert.ok(Image instanceof Function); assert.ok(Image instanceof Function);
}, });
'test Image#onload': function(){ it('Image#onload', function () {
var img = new Image var img = new Image
, n = 0; , onloadCalled = 0;
assert.strictEqual(null, img.onload); assert.strictEqual(null, img.onload);
assert.strictEqual(false, img.complete); assert.strictEqual(false, img.complete);
img.onload = function(){
++n; img.onload = function () {
assert.equal(img.src, png); onloadCalled += 1;
assert.strictEqual(img.src, png);
}; };
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(true, img.complete);
assert.strictEqual(320, img.width); assert.strictEqual(320, img.width);
assert.strictEqual(320, img.height); 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 var img = new Image
, n = 0; , n = 0;
@ -65,22 +64,22 @@ module.exports = {
}; };
img.src = png; img.src = png;
assert.equal(n, 1); assert.equal(n, 1);
}, });
'test Image#onerror': function(){ it('Image#onerror', function () {
var img = new Image var img = new Image
, error , error
, n = 0; , onerrorCalled = 0;
assert.strictEqual(null, img.onerror); assert.strictEqual(null, img.onerror);
assert.strictEqual(false, img.complete); assert.strictEqual(false, img.complete);
img.onload = function(){
img.onload = function () {
assert.fail('called onload'); assert.fail('called onload');
}; };
img.onerror = function(err){ img.onerror = function (err) {
++n; onerrorCalled += 1;
error = err; error = err;
}; };
@ -90,14 +89,14 @@ module.exports = {
assert.fail('error did not invoke onerror(): ' + err); 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.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 var img = new Image
, n = 0; , n = 0;
@ -125,27 +124,30 @@ module.exports = {
img.src = png + 's3'; img.src = png + 's3';
assert.equal(img.src, png + 's3'); assert.equal(img.src, png + 's3');
assert.equal(n, 1); assert.equal(n, 1);
}, });
'test Image#{width,height}': function(){ it('Image#{width,height}', function () {
var img = new Image var img = new Image
, n = 0; , onloadCalled = 0;
assert.strictEqual(0, img.width); assert.strictEqual(0, img.width);
assert.strictEqual(0, img.height); assert.strictEqual(0, img.height);
img.onload = function(){
++n; img.onload = function () {
onloadCalled += 1;
assert.strictEqual(320, img.width); assert.strictEqual(320, img.width);
assert.strictEqual(320, img.height); 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(); var image = new Canvas.Image();
image.src = new Buffer(0); image.src = new Buffer(0);
image.src = new Buffer(''); 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] var fn = tests[name]
, canvas = create('canvas') , canvas = create('canvas')
, tr = create('tr') , tr = create('tr')
, tds = [create('td'), create('td'), create('td')]; , tds = [create('td'), create('td'), create('td'), create('td')];
canvas.width = 200; canvas.width = 200;
canvas.height = 200; canvas.height = 200;
canvas.title = name; canvas.title = name;
tds[1].appendChild(canvas); tds[2].appendChild(canvas);
tds[2].appendChild(create('h3', name)); tds[3].appendChild(create('h3', name));
tds[2].appendChild(pdfForm(fn, canvas)); tds[3].appendChild(pdfForm(fn, canvas));
tr.appendChild(tds[0]); tr.appendChild(tds[0]);
tr.appendChild(tds[1]); tr.appendChild(tds[1]);
tr.appendChild(tds[2]); tr.appendChild(tds[2]);
tr.appendChild(tds[3]);
tbody.appendChild(tr); tbody.appendChild(tr);
table.appendChild(tbody); 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] var fn = tests[name]
, start = new Date; , start = new Date;
try { try {
@ -96,7 +97,7 @@ function runTest(name, canvas, dest, stats) {
var duration = new Date - start; var duration = new Date - start;
stats.appendChild(create('p', 'browser: ' + duration + 'ms')); stats.appendChild(create('p', 'browser: ' + duration + 'ms'));
stats.appendChild(create('p', 'fps: ' + (1000 / duration).toFixed(0))); stats.appendChild(create('p', 'fps: ' + (1000 / duration).toFixed(0)));
renderOnServer(name, canvas, function(res){ renderOnServer('/render', name, canvas, function(res){
if (res.error) { if (res.error) {
var p = create('p'); var p = create('p');
p.innerText = res.error; p.innerText = res.error;
@ -109,16 +110,27 @@ function runTest(name, canvas, dest, stats) {
dest.appendChild(img); 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 var req = new XMLHttpRequest
, json = JSON.stringify({ , json = JSON.stringify({
fn: tests[name].toString() fn: tests[name].toString()
, width: canvas.width , width: canvas.width
, height: canvas.height , height: canvas.height
}); });
req.open('POST', '/render'); req.open('POST', url);
req.setRequestHeader('Content-Type', 'application/json'); req.setRequestHeader('Content-Type', 'application/json');
req.onreadystatechange = function(){ req.onreadystatechange = function(){
if (4 == req.readyState) { if (4 == req.readyState) {

15
test/public/tests.js

@ -1537,6 +1537,20 @@ tests['shadow image'] = function(ctx, done){
img.src = 'star.png'; 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){ tests['shadow integration'] = function(ctx){
ctx.shadowBlur = 5; ctx.shadowBlur = 5;
ctx.shadowOffsetX = 10; ctx.shadowOffsetX = 10;
@ -1992,7 +2006,6 @@ tests['fillStyle=\'hsla(...)\''] = function(ctx){
for (j=0;j<6;j++){ for (j=0;j<6;j++){
ctx.fillStyle = 'hsla(' + (360-60*i) + ',' + ctx.fillStyle = 'hsla(' + (360-60*i) + ',' +
(100-16.66*j) + '%,50%,' + (1-0.16*j) + ')'; (100-16.66*j) + '%,50%,' + (1-0.16*j) + ')';
console.log((100-16.66*j));
ctx.fillRect(j*25,i*25,25,25); ctx.fillRect(j*25,i*25,25,25);
} }
} }

41
test/server.js

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

2
test/views/layout.jade

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

1
test/views/tests.jade

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

Loading…
Cancel
Save