Skip to content

Commit

Permalink
Add shadow option to toDataURL and cloneAsImage (fabricjs#5308)
Browse files Browse the repository at this point in the history
* first test

* less accuracy
  • Loading branch information
asturur authored Oct 14, 2018
1 parent 7f2409b commit b02305a
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 20 deletions.
5 changes: 0 additions & 5 deletions src/mixins/canvas_dataurl_exporter.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,6 @@
__toDataURL: function(format, quality) {

var canvasEl = this.contextContainer.canvas;
// to avoid common confusion https://github.com/kangax/fabric.js/issues/806
if (format === 'jpg') {
format = 'jpeg';
}

var data = supportQuality
? canvasEl.toDataURL('image/' + format, quality)
: canvasEl.toDataURL('image/' + format);
Expand Down
45 changes: 32 additions & 13 deletions src/shapes/object.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -1528,7 +1528,16 @@
* Creates an instance of fabric.Image out of an object
* @param {Function} callback callback, invoked with an instance as a first argument
* @param {Object} [options] for clone as image, passed to toDataURL
* @param {Boolean} [options.enableRetinaScaling] enable retina scaling for the cloned image
* @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 {fabric.Object} thisArg
*/
cloneAsImage: function(callback, options) {
Expand All @@ -1553,42 +1562,52 @@
* @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 = { });

var origParams = fabric.util.saveObjectTransform(this);
var utils = fabric.util, origParams = utils.saveObjectTransform(this),
originalShadow = this.shadow, abs = Math.abs;

if (options.withoutTransform) {
fabric.util.resetObjectTransform(this);
utils.resetObjectTransform(this);
}
if (options.withoutShadow) {
this.shadow = null;
}

var el = fabric.util.createCanvasElement(),
// skip canvas zoom and calculate with setCoords now.
boundingRect = this.getBoundingRect(true, true);

el.width = boundingRect.width;
el.height = boundingRect.height;
boundingRect = this.getBoundingRect(true, true),
shadow = this.shadow, scaling,
shadowOffset = { x: 0, y: 0 }, shadowBlur;

if (shadow) {
shadowBlur = shadow.blur;
scaling = this.getObjectScaling();
shadowOffset.x = 2 * Math.round((abs(shadow.offsetX) + shadowBlur) * abs(scaling.scaleX));
shadowOffset.y = 2 * Math.round((abs(shadow.offsetY) + shadowBlur) * abs(scaling.scaleY));
}
el.width = boundingRect.width + shadowOffset.x;
el.height = boundingRect.height + shadowOffset.y;
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,
renderOnAddRemove: false,
skipOffscreen: false,
});
// to avoid common confusion https://github.com/kangax/fabric.js/issues/806
if (options.format === 'jpg') {
options.format = 'jpeg';
}

if (options.format === 'jpeg') {
canvas.backgroundColor = '#fff';
}

this.setPositionByOrigin(new fabric.Point(canvas.width / 2, canvas.height / 2), 'center', 'center');

var originalCanvas = this.canvas;
canvas.add(this);
var data = canvas.toDataURL(options);
this.shadow = originalShadow;
this.set(origParams).setCoords();
this.canvas = originalCanvas;
// canvas.dispose will call image.dispose that will nullify the elements
Expand Down
4 changes: 2 additions & 2 deletions test/unit/canvas_static.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,13 +576,13 @@
img.src = dataUrl;
});

QUnit.test('toDataURL jpg', function(assert) {
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.');
}
else {
try {
var dataURL = canvas.toDataURL({ format: 'jpg' });
var dataURL = canvas.toDataURL({ format: 'jpeg' });
assert.equal(dataURL.substring(0, 22), 'data:image/jpeg;base64');
}
// node-canvas does not support jpeg data urls
Expand Down
Binary file added test/visual/golden/dataurl1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/visual/golden/dataurl2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/visual/golden/dataurl3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 106 additions & 0 deletions test/visual/toDataURL.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
(function() {
if ((fabric.isLikelyNode && process.env.launcher === 'Firefox') || navigator.userAgent.indexOf('Firefox') !== -1) {
fabric.browserShadowBlurConstant = 0.9;
}
if ((fabric.isLikelyNode && process.env.launcher === 'Node')) {
fabric.browserShadowBlurConstant = 1;
}
if ((fabric.isLikelyNode && process.env.launcher === 'Chrome') || navigator.userAgent.indexOf('Chrome') !== -1) {
fabric.browserShadowBlurConstant = 1.5;
}
if ((fabric.isLikelyNode && process.env.launcher === 'Edge') || navigator.userAgent.indexOf('Edge') !== -1) {
fabric.browserShadowBlurConstant = 1.75;
}
fabric.enableGLFiltering = false;
fabric.isWebglSupported = false;
fabric.Object.prototype.objectCaching = true;
var visualTestLoop;
if (fabric.isLikelyNode) {
visualTestLoop = global.visualTestLoop;
}
else {
visualTestLoop = window.visualTestLoop;
}
var fabricCanvas = this.canvas = new fabric.Canvas(null, {
enableRetinaScaling: false, renderOnAddRemove: false, width: 200, height: 200,
});

var tests = [];

function toDataURL1(canvas, callback) {
var text = new fabric.Text('Hi i m an image',
{ strokeWidth: 2, stroke: 'red', fontSize: 60, objectCaching: false }
);
callback(text.toDataURL());
}

tests.push({
test: 'Text to DataURL',
code: toDataURL1,
golden: 'dataurl1.png',
newModule: 'DataURL exports',
percentage: 0.09,
});

function toDataURL2(canvas, callback) {
var text = new fabric.Text('Hi i m an image',
{ strokeWidth: 0, fontSize: 60, objectCaching: false }
);
var shadow = new fabric.Shadow({
color: 'purple',
offsetX: 0,
offsetY: 0,
blur: 6,
});
text.shadow = shadow;
callback(text.toDataURL());
}

tests.push({
test: 'Text to DataURL with shadow no offset',
code: toDataURL2,
golden: 'dataurl2.png',
percentage: 0.09,
});

function toDataURL3(canvas, callback) {
var text = new fabric.Text('Hi i m an image',
{ strokeWidth: 0, fontSize: 60, objectCaching: false }
);
var shadow = new fabric.Shadow({
color: 'purple',
offsetX: -30,
offsetY: +40,
blur: 10,
});
text.shadow = shadow;
callback(text.toDataURL());
}

tests.push({
test: 'Text to DataURL with shadow large offset',
code: toDataURL3,
golden: 'dataurl3.png',
percentage: 0.09,
});

function testWrapper(test) {
var actualTest = test.code;
test.code = function(canvas, callback) {
actualTest(canvas, function(dataURL) {
var img = fabric.document.createElement('img');
var canvas = fabric.document.createElement('canvas');
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0);
callback(canvas);
};
img.src = dataURL;
});
};
visualTestLoop(fabricCanvas, QUnit)(test);
}

tests.forEach(testWrapper);
})();

0 comments on commit b02305a

Please sign in to comment.