Browse Source

Make toDataURL more spec-compliant:

* Use default arguments when undefined arguments are passed.
* Throw TypeError on first invalid argument
* Accept a number 'encoderOptions' as the quality
* Fall through to image/png if an unsupported encoding is requested
* Return "data:," if the canvas has no pixels
* Lower-case the format before testing for support
v1.x
Zach Bjornson 9 years ago
parent
commit
e54183d8d4
  1. 5
      History.md
  2. 1
      Readme.md
  3. 77
      lib/canvas.js
  4. 79
      test/canvas.test.js

5
History.md

@ -1,3 +1,8 @@
future / future
==================
* Allow optional arguments in `toDataURL` to be `undefined` and improve `toDataURL`'s spec compliance (#690)
1.3.5 / 2015-12-07
==================

1
Readme.md

@ -167,6 +167,7 @@ canvas.toDataURL(function(err, png){ }); // defaults to PNG
canvas.toDataURL('image/png', function(err, png){ });
canvas.toDataURL('image/jpeg', function(err, jpeg){ }); // sync JPEG is not supported
canvas.toDataURL('image/jpeg', {opts...}, function(err, jpeg){ }); // see Canvas#jpegStream for valid options
canvas.toDataURL('image/jpeg', quality, function(err, jpeg){ }); // spec-following; quality from 0 to 1
```
### CanvasRenderingContext2d#patternQuality

77
lib/canvas.js

@ -18,7 +18,8 @@ var canvas = require('./bindings')
, JPEGStream = require('./jpegstream')
, FontFace = canvas.FontFace
, fs = require('fs')
, packageJson = require("../package.json");
, packageJson = require("../package.json")
, FORMATS = ['image/png', 'image/jpeg'];
/**
* Export `Canvas` as the module.
@ -186,61 +187,59 @@ Canvas.prototype.createSyncJPEGStream = function(options){
* Return a data url. Pass a function for async support (required for "image/jpeg").
*
* @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 {Object|Number} encoderOptions, optional, options for jpeg compression (see documentation for Canvas#jpegStream) or the JPEG encoding quality from 0 to 1.
* @param {Function} fn, optional, callback for asynchronous operation. Required for type "image/jpeg".
* @return {String} data URL if synchronous (callback omitted)
* @api public
*/
Canvas.prototype.toDataURL = function(a1, a2, a3){
// valid arg patterns (args -> type, opts, fn):
// valid arg patterns (args -> [type, opts, fn]):
// [] -> ['image/png', null, null]
// [qual] -> ['image/png', null, null]
// [undefined] -> ['image/png', null, null]
// ['image/png'] -> ['image/png', null, null]
// ['image/png', qual] -> ['image/png', null, null]
// [fn] -> ['image/png', null, fn]
// [type, fn] -> [type, null, fn]
// [undefined, fn] -> ['image/png', null, fn]
// ['image/png', qual, fn] -> ['image/png', null, fn]
// ['image/jpeg', fn] -> ['image/jpeg', null, fn]
// ['image/jpeg', opts, fn] -> ['image/jpeg', opts, fn]
// ['image/jpeg', qual, fn] -> ['image/jpeg', {quality: qual}, fn]
// ['image/jpeg', undefined, fn] -> ['image/jpeg', null, fn]
var type;
if (this.width === 0 || this.height === 0) {
// Per spec, if the bitmap has no pixels, return this string:
return "data:,";
}
var type = 'image/png';
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;
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 {
throw new Error('invalid arguments');
} else if (undefined !== a3) {
throw new TypeError(typeof a3 + ' is not a function');
}
}
}
if ('image/png' === type) {
@ -254,6 +253,10 @@ Canvas.prototype.toDataURL = function(a1, a2, a3){
}
} 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 = [];

79
test/canvas.test.js

@ -385,17 +385,33 @@ describe('Canvas', function () {
assert.ok(0 == canvas.toDataURL().indexOf('data:image/png;base64,'));
});
it('toDataURL(0.5) works and defaults to PNG', function () {
assert.ok(0 == canvas.toDataURL(0.5).indexOf('data:image/png;base64,'));
});
it('toDataURL(undefined) works and defaults to PNG', function () {
assert.ok(0 == canvas.toDataURL(undefined).indexOf('data:image/png;base64,'));
});
it('toDataURL("image/png") works', function () {
assert.ok(0 == canvas.toDataURL('image/png').indexOf('data:image/png;base64,'));
});
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 === 'type "image/jpeg" only supports asynchronous operation';
return err.message === 'Missing required callback function for format "image/jpeg"';
}
);
});
@ -408,6 +424,22 @@ describe('Canvas', function () {
});
});
it('toDataURL(0.5, function (err, str) {...}) works and defaults to PNG', function (done) {
new Canvas(200,200).toDataURL(0.5, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/png;base64,'));
done();
});
});
it('toDataURL(undefined, function (err, str) {...}) works and defaults to PNG', function (done) {
new Canvas(200,200).toDataURL(undefined, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/png;base64,'));
done();
});
});
it('toDataURL("image/png", function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/png', function(err, str){
assert.ifError(err);
@ -416,15 +448,16 @@ describe('Canvas', function () {
});
});
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/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 () {
@ -433,7 +466,7 @@ describe('Canvas', function () {
canvas.toDataURL('image/jpeg', {});
},
function (err) {
return err.message === 'type "image/jpeg" only supports asynchronous operation';
return err.message === 'Missing required callback function for format "image/jpeg"';
}
);
});
@ -446,6 +479,30 @@ describe('Canvas', function () {
});
});
it('toDataURL("iMAge/JPEG", function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('iMAge/JPEG', function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/jpeg;base64,'));
done();
});
});
it('toDataURL("image/jpeg", undefined, function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/jpeg', undefined, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/jpeg;base64,'));
done();
});
});
it('toDataURL("image/jpeg", 0.5, function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/jpeg', 0.5, function(err, str){
assert.ifError(err);
assert.ok(0 === str.indexOf('data:image/jpeg;base64,'));
done();
});
});
it('toDataURL("image/jpeg", opts, function (err, str) {...}) works', function (done) {
new Canvas(200,200).toDataURL('image/jpeg', {quality: 100}, function(err, str){
assert.ifError(err);

Loading…
Cancel
Save