Skip to content

Commit

Permalink
Support canvas.toDataURL("image/jpeg") (sync)
Browse files Browse the repository at this point in the history
Also fixes a bug I introduced in #1152 (JPEG quality needs to go from 0 to 1, not 0 to 100).

Fixes #1146
  • Loading branch information
zbjornson authored and chearon committed Jul 5, 2018
1 parent b10d204 commit 0f74eed
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 64 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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)
==================
Expand Down
62 changes: 23 additions & 39 deletions lib/canvas.js
Expand Up @@ -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]
Expand All @@ -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 = {};
Expand All @@ -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();
}

Expand All @@ -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) {
Expand All @@ -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')}`
}
};
36 changes: 11 additions & 25 deletions test/canvas.test.js
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 0f74eed

Please sign in to comment.