From 0f74eed1eba4611cba649615eb9c220b0ca23a77 Mon Sep 17 00:00:00 2001 From: Zach Bjornson Date: Sun, 1 Jul 2018 19:05:21 -0700 Subject: [PATCH] Support canvas.toDataURL("image/jpeg") (sync) Also fixes a bug I introduced in #1152 (JPEG quality needs to go from 0 to 1, not 0 to 100). Fixes #1146 --- CHANGELOG.md | 1 + lib/canvas.js | 62 +++++++++++++++++---------------------------- test/canvas.test.js | 36 ++++++++------------------ 3 files changed, 35 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c94364e0..076b46516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ canvas.createJPEGStream() // new and `canvas.jpegStream()` * Added `resolution` option for `canvas.toBuffer("image/png")` and `canvas.createPNGStream()` + * Support for `canvas.toDataURI("image/jpeg")` (sync) 1.6.x (unreleased) ================== diff --git a/lib/canvas.js b/lib/canvas.js index 748929df6..ec16b92ea 100644 --- a/lib/canvas.js +++ b/lib/canvas.js @@ -99,15 +99,17 @@ Canvas.prototype.createJPEGStream = function(options){ }; /** - * Return a data url. Pass a function for async support (required for "image/jpeg"). + * Returns a data URI. Pass a function for async operation (non-standard). * - * @param {String} type, optional, one of "image/png" or "image/jpeg", defaults to "image/png" - * @param {Object|Number} encoderOptions, optional, options for jpeg compression (see documentation for Canvas#jpegStream) or the JPEG encoding quality from 0 to 1. - * @param {Function} fn, optional, callback for asynchronous operation. Required for type "image/jpeg". + * @param {"image/png"|"image/jpeg"} [type="image/png"] Type. + * @param {Object|Number} [encoderOptions] A number between 0 and 1 indicating + * image quality if the requested type is image/jpeg (standard), or an options + * object for image encoding (see documentation for Canvas#toBuffer) + * (non-standard). + * @param {Function} [fn] Callback for asynchronous operation (non-standard). * @return {String} data URL if synchronous (callback omitted) * @api public */ - Canvas.prototype.toDataURL = function(a1, a2, a3){ // valid arg patterns (args -> [type, opts, fn]): // [] -> ['image/png', null, null] @@ -123,6 +125,9 @@ Canvas.prototype.toDataURL = function(a1, a2, a3){ // ['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] + // ['image/jpeg'] -> ['image/jpeg', null, fn] + // ['image/jpeg', opts] -> ['image/jpeg', opts, fn] + // ['image/jpeg', qual] -> ['image/jpeg', {quality: qual}, fn] var type = 'image/png'; var opts = {}; @@ -131,7 +136,7 @@ Canvas.prototype.toDataURL = function(a1, a2, a3){ if ('function' === typeof a1) { fn = a1; } else { - if ('string' === typeof a1 && FORMATS.indexOf(a1.toLowerCase()) !== -1) { + if ('string' === typeof a1 && FORMATS.includes(a1.toLowerCase())) { type = a1.toLowerCase(); } @@ -141,7 +146,7 @@ Canvas.prototype.toDataURL = function(a1, a2, a3){ if ('object' === typeof a2) { opts = a2; } else if ('number' === typeof a2) { - opts = {quality: Math.max(0, Math.min(1, a2)) * 100}; + opts = {quality: Math.max(0, Math.min(1, a2))}; } if ('function' === typeof a3) { @@ -156,40 +161,19 @@ Canvas.prototype.toDataURL = function(a1, a2, a3){ // Per spec, if the bitmap has no pixels, return this string: var str = "data:,"; if (fn) { - setTimeout(function() { - fn(null, str); - }); - } - return str; - } - - 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')); - }); + setTimeout(() => fn(null, str)); + return; } 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"'); + return str; } + } - 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); - }); + if (fn) { + this.toBuffer((err, buf) => { + if (err) return fn(err); + fn(null, `data:${type};base64,${buf.toString('base64')}`); + }, type, opts) + } else { + return `data:${type};base64,${this.toBuffer(type).toString('base64')}` } }; diff --git a/test/canvas.test.js b/test/canvas.test.js index b5480d155..d2f042702 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -663,38 +663,31 @@ describe('Canvas', function () { ctx.fillRect(100,0,100,100); it('toDataURL() works and defaults to PNG', function () { - assert.ok(0 == canvas.toDataURL().indexOf('data:image/png;base64,')); + assert.ok(canvas.toDataURL().startsWith('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,')); + assert.ok(canvas.toDataURL(0.5).startsWith('data:image/png;base64,')); }); it('toDataURL(undefined) works and defaults to PNG', function () { - assert.ok(0 == canvas.toDataURL(undefined).indexOf('data:image/png;base64,')); + assert.ok(canvas.toDataURL(undefined).startsWith('data:image/png;base64,')); }); it('toDataURL("image/png") works', function () { - assert.ok(0 == canvas.toDataURL('image/png').indexOf('data:image/png;base64,')); + assert.ok(canvas.toDataURL('image/png').startsWith('data:image/png;base64,')); }); it('toDataURL("image/png", 0.5) works', function () { - assert.ok(0 == canvas.toDataURL('image/png').indexOf('data:image/png;base64,')); + assert.ok(canvas.toDataURL('image/png').startsWith('data:image/png;base64,')); }); it('toDataURL("iMaGe/PNg") works', function () { - assert.ok(0 == canvas.toDataURL('iMaGe/PNg').indexOf('data:image/png;base64,')); + assert.ok(canvas.toDataURL('iMaGe/PNg').startsWith('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") works', function () { + assert.ok(canvas.toDataURL('image/jpeg').startsWith('data:image/jpeg;base64,')); }); it('toDataURL(function (err, str) {...}) works and defaults to PNG', function (done) { @@ -746,18 +739,11 @@ describe('Canvas', function () { }); it('toDataURL("image/png", {}) works', function () { - assert.ok(0 == canvas.toDataURL('image/png', {}).indexOf('data:image/png;base64,')); + assert.ok(canvas.toDataURL('image/png', {}).startsWith('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", {}) works', function () { + assert.ok(canvas.toDataURL('image/jpeg', {}).startsWith('data:image/jpeg;base64,')); }); it('toDataURL("image/jpeg", function (err, str) {...}) works', function (done) {