Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add toCanvasElement for object, fixes a bug, speedUp cloneAsImage #5481

Merged
merged 2 commits into from
Jan 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions src/mixins/canvas_dataurl_exporter.mixin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
(function () {

var supportQuality = fabric.StaticCanvas.supports('toDataURLWithQuality');

fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {

/**
Expand Down Expand Up @@ -43,7 +40,7 @@
quality = options.quality || 1,
multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? this.getRetinaScaling() : 1),
canvasEl = this.toCanvasElement(multiplier, options);
return this.__toDataURL(canvasEl, format, quality);
return fabric.util.toDataURL(canvasEl, format, quality);
},

/**
Expand Down Expand Up @@ -93,17 +90,6 @@
this.interactive = originalInteractive;
return canvasEl;
},

/**
* since 2.5.0 does not need to be on canvas instance anymore.
* leave it here for context;
* @private
*/
__toDataURL: function(canvasEl, format, quality) {
return supportQuality
? canvasEl.toDataURL('image/' + format, quality)
: canvasEl.toDataURL('image/' + format);
}
});

})();
48 changes: 33 additions & 15 deletions src/shapes/object.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,7 @@

/**
* Creates an instance of fabric.Image out of an object
* could make use of both toDataUrl or toCanvasElement.
* @param {Function} callback callback, invoked with an instance as a first argument
* @param {Object} [options] for clone as image, passed to toDataURL
* @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
Expand All @@ -1559,20 +1560,16 @@
* @return {fabric.Object} thisArg
*/
cloneAsImage: function(callback, options) {
var dataUrl = this.toDataURL(options);
fabric.util.loadImage(dataUrl, function(img) {
if (callback) {
callback(new fabric.Image(img));
}
});
var canvasEl = this.toCanvasElement(options);
if (callback) {
callback(new fabric.Image(canvasEl));
}
return this;
},

/**
* Converts an object into a data-url-like string
* Converts an object into a HTMLCanvas element
* @param {Object} options Options object
* @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
* @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
* @param {Number} [options.multiplier=1] Multiplier to scale by
* @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
* @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
Expand All @@ -1583,11 +1580,12 @@
* @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2
* @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
*/
toDataURL: function(options) {
toCanvasElement: function(options) {
options || (options = { });

var utils = fabric.util, origParams = utils.saveObjectTransform(this),
originalShadow = this.shadow, abs = Math.abs;
originalShadow = this.shadow, abs = Math.abs,
multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? fabric.devicePixelRatio : 1);

if (options.withoutTransform) {
utils.resetObjectTransform(this);
Expand All @@ -1613,7 +1611,7 @@
el.width += el.width % 2 ? 2 - el.width % 2 : 0;
el.height += el.height % 2 ? 2 - el.height % 2 : 0;
var canvas = new fabric.StaticCanvas(el, {
enableRetinaScaling: options.enableRetinaScaling,
enableRetinaScaling: false,
renderOnAddRemove: false,
skipOffscreen: false,
});
Expand All @@ -1624,18 +1622,38 @@

var originalCanvas = this.canvas;
canvas.add(this);
var data = canvas.toDataURL(options);
var canvasEl = canvas.toCanvasElement(multiplier || 1, options);
this.shadow = originalShadow;
this.set(origParams).setCoords();
this.canvas = originalCanvas;
this.set(origParams).setCoords();
// canvas.dispose will call image.dispose that will nullify the elements
// since this canvas is a simple element for the process, we remove references
// to objects in this way in order to avoid object trashing.
canvas._objects = [];
canvas.dispose();
canvas = null;

return data;
return canvasEl;
},

/**
* Converts an object into a data-url-like string
* @param {Object} options Options object
* @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
* @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
* @param {Number} [options.multiplier=1] Multiplier to scale by
* @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
* @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
* @param {Number} [options.width] Cropping width. Introduced in v1.2.14
* @param {Number} [options.height] Cropping height. Introduced in v1.2.14
* @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4
* @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4
* @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2
* @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
*/
toDataURL: function(options) {
options || (options = { });
return fabric.util.toDataURL(this.toCanvasElement(options), options.format || 'png', options.quality || 1);
},

/**
Expand Down
16 changes: 1 addition & 15 deletions src/static_canvas.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -1795,7 +1795,7 @@
* (either those of HTMLCanvasElement itself, or rendering context)
*
* @param {String} methodName Method to check support for;
* Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash"
* Could be one of "setLineDash"
* @return {Boolean | null} `true` if method is supported (or at least exists),
* `null` if canvas element or context can not be initialized
*/
Expand All @@ -1813,23 +1813,9 @@

switch (methodName) {

case 'getImageData':
return typeof ctx.getImageData !== 'undefined';

case 'setLineDash':
return typeof ctx.setLineDash !== 'undefined';

case 'toDataURL':
return typeof el.toDataURL !== 'undefined';

case 'toDataURLWithQuality':
try {
el.toDataURL('image/jpeg', 0);
return true;
}
catch (e) { }
return false;

default:
return null;
}
Expand Down
14 changes: 14 additions & 0 deletions src/util/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@

/**
* Creates a canvas element that is a copy of another and is also painted
* @param {CanvasElement} canvas to copy size and content of
* @static
* @memberOf fabric.util
* @return {CanvasElement} initialized canvas element
Expand All @@ -595,6 +596,19 @@
return newCanvas;
},

/**
* since 2.6.0 moved from canvas instance to utility.
* @param {CanvasElement} canvasEl to copy size and content of
* @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too
* @param {Number} quality <= 1 and > 0
* @static
* @memberOf fabric.util
* @return {String} data url
*/
toDataURL: function(canvasEl, format, quality) {
return canvasEl.toDataURL('image/' + format, quality);
},

/**
* Creates image element (works on client and node)
* @static
Expand Down
15 changes: 5 additions & 10 deletions test/unit/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -1170,16 +1170,11 @@

QUnit.test('toDataURL', function(assert) {
assert.ok(typeof canvas.toDataURL === 'function');
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
}
else {
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
}
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
});

// QUnit.test('getPointer', function(assert) {
Expand Down
62 changes: 23 additions & 39 deletions test/unit/canvas_static.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,20 +482,15 @@

QUnit.test('toDataURL', function(assert) {
assert.ok(typeof canvas.toDataURL === 'function');
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
}
else {
var rect = new fabric.Rect({width: 100, height: 100, fill: 'red', top: 0, left: 0});
canvas.add(rect);
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
//we can just compare that the dataUrl generated differs from the dataURl of an empty canvas.
assert.equal(dataURL.substring(200, 210) !== 'AAAAAAAAAA', true);
}
var rect = new fabric.Rect({width: 100, height: 100, fill: 'red', top: 0, left: 0});
canvas.add(rect);
var dataURL = canvas.toDataURL();
// don't compare actual data url, as it is often browser-dependent
// this.assertIdentical(emptyImageCanvasData, canvas.toDataURL('png'));
assert.equal(typeof dataURL, 'string');
assert.equal(dataURL.substring(0, 21), 'data:image/png;base64');
//we can just compare that the dataUrl generated differs from the dataURl of an empty canvas.
assert.equal(dataURL.substring(200, 210) !== 'AAAAAAAAAA', true);
});

QUnit.test('toDataURL with enableRetinaScaling: true and no multiplier', function(assert) {
Expand Down Expand Up @@ -604,39 +599,28 @@
});

QUnit.test('toDataURL jpeg', function(assert) {
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
try {
var dataURL = canvas.toDataURL({ format: 'jpeg' });
assert.equal(dataURL.substring(0, 22), 'data:image/jpeg;base64');
}
else {
try {
var dataURL = canvas.toDataURL({ format: 'jpeg' });
assert.equal(dataURL.substring(0, 22), 'data:image/jpeg;base64');
}
// node-canvas does not support jpeg data urls
catch (err) {
assert.ok(true);
}
// node-canvas does not support jpeg data urls
catch (err) {
assert.ok(true);
}
});

QUnit.test('toDataURL cropping', function(assert) {
var done = assert.async();
assert.ok(typeof canvas.toDataURL === 'function');
if (!fabric.Canvas.supports('toDataURL')) {
window.alert('toDataURL is not supported by this environment. Some of the tests can not be run.');
done();
}
else {
var croppingWidth = 75,
croppingHeight = 50,
dataURL = canvas.toDataURL({width: croppingWidth, height: croppingHeight});
var croppingWidth = 75,
croppingHeight = 50,
dataURL = canvas.toDataURL({width: croppingWidth, height: croppingHeight});

fabric.Image.fromURL(dataURL, function (img) {
assert.equal(img.width, croppingWidth, 'Width of exported image should correspond to cropping width');
assert.equal(img.height, croppingHeight, 'Height of exported image should correspond to cropping height');
done();
});
}
fabric.Image.fromURL(dataURL, function (img) {
assert.equal(img.width, croppingWidth, 'Width of exported image should correspond to cropping width');
assert.equal(img.height, croppingHeight, 'Height of exported image should correspond to cropping height');
done();
});
});

QUnit.test('centerObjectH', function(assert) {
Expand Down
Loading