Browse Source

Add data URI jpeg support (fixes #31)

v1.x
Zach Bjornson 9 years ago
parent
commit
d306aa3665
  1. 21
      Readme.md
  2. 100
      lib/canvas.js
  3. 90
      test/canvas.test.js
  4. 11
      test/server.js

21
Readme.md

@ -156,22 +156,17 @@ canvas.toBuffer(function(err, buf){
});
```
### Canvas#toDataURL() async
### Canvas#toDataURL() sync and async
Optionally we may pass a callback function to `Canvas#toDataURL()`, and this process will be performed asynchronously, and will `callback(err, str)`.
The following syntax patterns are supported:
```javascript
canvas.toDataURL(function(err, str){
});
```
or specify the mime type:
```javascript
canvas.toDataURL('image/png', function(err, str){
});
var dataUrl = canvas.toDataURL(); // defaults to PNG
var dataUrl = canvas.toDataURL('image/png');
canvas.toDataURL(function(err, png){ }); // defaults to PNG
canvas.toDataURL('image/png', function(err, png){ });
canvas.toDataURL('image/jpeg', function(err, jpeg){ }); // sync JPEG is not supported
canvas.toDataURL('image/jpeg', {opts...}, function(err, jpeg){ }); // see Canvas#jpegStream for valid options
```
### CanvasRenderingContext2d#patternQuality

100
lib/canvas.js

@ -183,33 +183,89 @@ Canvas.prototype.createSyncJPEGStream = function(options){
};
/**
* Return a data url. Pass a function for async support.
* Return a data url. Pass a function for async support (required for "image/jpeg").
*
* @param {String|Function} type
* @param {Function} fn
* @return {String}
* @param {String} type, optional, one of "image/png" or "image/jpeg", defaults to "image/png"
* @param {Object} opts, optional, options for jpeg compression (see documentation for Canvas#jpegStream)
* @param {Function} fn, optional, callback for asynchronous operation. Required for type "image/jpeg".
* @return {String} data URL if synchronous (callback omitted)
* @api public
*/
Canvas.prototype.toDataURL = function(type, fn){
// Default to png
type = type || 'image/png';
// Allow callback as first arg
if ('function' == typeof type) fn = type, type = 'image/png';
// Throw on non-png
if ('image/png' != type) throw new Error('currently only image/png is supported');
var prefix = 'data:' + type + ';base64,';
Canvas.prototype.toDataURL = function(a1, a2, a3){
// valid arg patterns (args -> type, opts, fn):
// [] -> ['image/png', null, null]
// ['image/png'] -> ['image/png', null, null]
// [fn] -> ['image/png', null, fn]
// [type, fn] -> [type, null, fn]
// ['image/jpeg', fn] -> ['image/jpeg', null, fn]
// ['image/jpeg', opts, fn] -> ['image/jpeg', opts, fn]
var type;
var opts = {};
var fn;
switch (arguments.length) {
case 0:
type = 'image/png';
break;
case 1:
if ('image/png' === a1) {
type = a1;
} else if ('function' === typeof a1) {
type = 'image/png';
fn = a1;
} else if ('image/jpeg' === a1) {
throw new Error('type "image/jpeg" only supports asynchronous operation');
} else {
throw new Error('invalid arguments');
}
break;
case 2:
if (('image/png' === a1 || 'image/jpeg' === a1) && 'function' === typeof a2) {
type = a1;
fn = a2;
} else if ('image/jpeg' === a1 && 'object' === typeof a2) {
throw new Error('type "image/jpeg" only supports asynchronous operation');
} else if ('image/png' === a1 && 'object' === typeof a2) {
throw new Error('type "image/png" does not accept an options object');
} else {
throw new Error('invalid arguments');
}
break;
case 3:
if ('image/jpeg' === a1 && 'object' === typeof a2 && 'function' === typeof a3) {
type = a1;
opts = a2;
fn = a3;
} else {
throw new Error('invalid arguments');
}
}
if (fn) {
this.toBuffer(function(err, buf){
if (err) return fn(err);
var str = 'data:' + type
fn(null, prefix + buf.toString('base64'));
if ('image/png' === type) {
if (fn) {
this.toBuffer(function(err, buf){
if (err) return fn(err);
fn(null, 'data:image/png;base64,' + buf.toString('base64'));
});
} else {
return 'data:image/png;base64,' + this.toBuffer().toString('base64');
}
} else if ('image/jpeg' === type) {
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');
}
};

90
test/canvas.test.js

@ -373,7 +373,7 @@ describe('Canvas', function () {
});
});
it('Canvas#toDataURL()', function () {
describe('#toDataURL()', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
@ -381,31 +381,77 @@ describe('Canvas', function () {
ctx.fillStyle = 'red';
ctx.fillRect(100,0,100,100);
assert.ok(0 == canvas.toDataURL().indexOf('data:image/png;base64,'));
assert.ok(0 == canvas.toDataURL('image/png').indexOf('data:image/png;base64,'));
it('toDataURL() works and defaults to PNG', function () {
assert.ok(0 == canvas.toDataURL().indexOf('data:image/png;base64,'));
});
var err;
try {
canvas.toDataURL('image/jpeg');
} catch (e) {
err = e;
}
assert.equal('currently only image/png is supported', err.message);
});
it('toDataURL("image/png") works', function () {
assert.ok(0 == canvas.toDataURL('image/png').indexOf('data:image/png;base64,'));
});
it('Canvas#toDataURL() async', function (done) {
new Canvas(200,200).toDataURL(function(err, str){
assert.ok(!err);
assert.ok(0 == str.indexOf('data:image/png;base64,'));
done();
it('toDataURL("image/jpeg") throws', function () {
assert.throws(
function () {
canvas.toDataURL('image/jpeg');
},
function (err) {
return err.message === 'type "image/jpeg" only supports asynchronous operation';
}
);
});
});
it('Canvas#toDataURL() async with type', function (done) {
new Canvas(200,200).toDataURL('image/png', function(err, str){
assert.ok(!err);
assert.ok(0 == str.indexOf('data:image/png;base64,'));
done();
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("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", {}) throws', function () {
assert.throws(
function () {
canvas.toDataURL('image/png', {});
},
function (err) {
return err.message === 'type "image/png" does not accept an options object';
}
);
});
it('toDataURL("image/jpeg", {}) throws', function () {
assert.throws(
function () {
canvas.toDataURL('image/jpeg', {});
},
function (err) {
return err.message === 'type "image/jpeg" only supports asynchronous operation';
}
);
});
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", 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();
});
});
});

11
test/server.js

@ -61,6 +61,7 @@ app.post('/render', function(req, res, next){
function done(){
var duration = new Date - start;
canvas.toDataURL(function(err, str){
if (err) throw err;
res.send({ data: str, duration: duration });
});
}
@ -89,13 +90,9 @@ app.post('/jpeg', function(req, res, next){
, ctx = canvas.getContext('2d');
function done(){
var stream = canvas.jpegStream();
var buffers = [];
stream.on('data', function (chunk) {
buffers.push(chunk);
});
stream.on('end', function() {
res.send({data: 'data:image/jpeg;base64,' + Buffer.concat(buffers).toString('base64')});
canvas.toDataURL('image/jpeg', function (err, str){
if (err) throw err;
res.send({data: str});
});
}

Loading…
Cancel
Save