Skip to content

Commit

Permalink
Add toCanvasElement for object, fixes a bug, speedUp cloneAsImage (#5481
Browse files Browse the repository at this point in the history
)

* a couple of changes

* removed unnecessary branching
  • Loading branch information
asturur authored Jan 13, 2019
1 parent 9c35ed2 commit 68a6af4
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 152 deletions.
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

0 comments on commit 68a6af4

Please sign in to comment.