diff --git a/Readme.md b/Readme.md index 37513ff..87abec2 100644 --- a/Readme.md +++ b/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 diff --git a/lib/canvas.js b/lib/canvas.js index 7962bcd..9f72fb3 100644 --- a/lib/canvas.js +++ b/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'); } }; diff --git a/test/canvas.test.js b/test/canvas.test.js index 9e60656..64c31c5 100644 --- a/test/canvas.test.js +++ b/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(); + }); }); }); diff --git a/test/server.js b/test/server.js index 5c6d1f6..ded5c9b 100644 --- a/test/server.js +++ b/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}); }); }