diff --git a/src/mixins/canvas_dataurl_exporter.mixin.js b/src/mixins/canvas_dataurl_exporter.mixin.js index 733ad34adb9..294b011e323 100644 --- a/src/mixins/canvas_dataurl_exporter.mixin.js +++ b/src/mixins/canvas_dataurl_exporter.mixin.js @@ -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); diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index 7eba9db4c3c..ffd55eccc0c 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -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) { @@ -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 diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index e730bad81d1..d2d637c2dee 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -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 diff --git a/test/visual/golden/dataurl1.png b/test/visual/golden/dataurl1.png new file mode 100644 index 00000000000..03db62af71e Binary files /dev/null and b/test/visual/golden/dataurl1.png differ diff --git a/test/visual/golden/dataurl2.png b/test/visual/golden/dataurl2.png new file mode 100644 index 00000000000..e58a168cfe0 Binary files /dev/null and b/test/visual/golden/dataurl2.png differ diff --git a/test/visual/golden/dataurl3.png b/test/visual/golden/dataurl3.png new file mode 100644 index 00000000000..4c3fd4d9683 Binary files /dev/null and b/test/visual/golden/dataurl3.png differ diff --git a/test/visual/toDataURL.js b/test/visual/toDataURL.js new file mode 100644 index 00000000000..78237301eaa --- /dev/null +++ b/test/visual/toDataURL.js @@ -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); +})();