diff --git a/CHANGELOG.md b/CHANGELOG.md index 64172d8a070..9c8423fbe22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +**Version 1.6.6** + +- Add: Contrast and Saturate filters [#3341](https://github.com/kangax/fabric.js/pull/3341) +- Fix: Correct registering and removal of events to handle iText objects. [#3349](https://github.com/kangax/fabric.js/pull/3349) +- Fix: Corrected 2 regression of 1.6.5 (dataurl export and itext clicks) +- Fix: Corrected path boundaries calculation for Arcs ( a and A ) [#3347](https://github.com/kangax/fabric.js/pull/3347) + **Version 1.6.5** - Fix: charspacing, do not get subzero with charwidth. diff --git a/HEADER.js b/HEADER.js index 577cb842d7f..51e45d5c950 100644 --- a/HEADER.js +++ b/HEADER.js @@ -1,6 +1,6 @@ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: "1.6.5" }; +var fabric = fabric || { version: "1.6.6" }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } diff --git a/dist/fabric.js b/dist/fabric.js index 27bdd479ac0..9057c3257c6 100644 --- a/dist/fabric.js +++ b/dist/fabric.js @@ -1,7 +1,7 @@ /* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: "1.6.5" }; +var fabric = fabric || { version: "1.6.6" }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } @@ -1174,18 +1174,13 @@ fabric.Collection = { */ fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) { - var fromX = 0, fromY = 0, bound = [], bounds = [], - segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot), - boundCopy = [[], []]; + var fromX = 0, fromY = 0, bound, bounds = [], + segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); for (var i = 0, len = segs.length; i < len; i++) { bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]); - boundCopy[0].x = bound[0].x + fx; - boundCopy[0].y = bound[0].y + fy; - boundCopy[1].x = bound[1].x + fx; - boundCopy[1].y = bound[1].y + fy; - bounds.push(boundCopy[0]); - bounds.push(boundCopy[1]); + bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy }); + bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy }); fromX = segs[i][4]; fromY = segs[i][5]; } @@ -1549,7 +1544,6 @@ fabric.Collection = { (function() { - /** * Copies all enumerable properties of one object to another * @memberOf fabric.util.object @@ -1557,12 +1551,17 @@ fabric.Collection = { * @param {Object} source Where to copy from * @return {Object} */ + function extend(destination, source, deep) { // JScript DontEnum bug is not taken care of // the deep clone is for internal use, is not meant to avoid // javascript traps or cloning html element or self referenced objects. if (deep) { - if (source instanceof Array) { + if (!fabric.isLikelyNode && source instanceof Element) { + // avoid cloning deep images, canvases, + destination = source; + } + else if (source instanceof Array) { destination = source.map(function(v) { return clone(v, deep) }) @@ -6779,6 +6778,13 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ this.overlayImage = null; this.backgroundColor = ''; this.overlayColor = '' + if (this._hasITextHandlers) { + this.off('selection:cleared', this._canvasITextSelectionClearedHanlder); + this.off('object:selected', this._canvasITextSelectionClearedHanlder); + this.off('mouse:up', this._mouseUpITextHandler); + this._iTextInstances = null; + this._hasITextHandlers = false; + } this.clearContext(this.contextContainer); this.fire('canvas:cleared'); this.renderAll(); @@ -10958,7 +10964,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var origWidth = this.getWidth(), origHeight = this.getHeight(), scaledWidth = (cropping.width || this.getWidth()) * multiplier, - scaledHeight = (cropping.width || this.getHeight()) * multiplier, + scaledHeight = (cropping.height || this.getHeight()) * multiplier, zoom = this.getZoom(), newZoom = zoom * multiplier, vp = this.viewportTransform, @@ -13897,7 +13903,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } function _isEqual(origValue, currentValue) { - if (origValue instanceof Array) { + if (!fabric.isLikelyNode && origValue instanceof Element) { + // avoid checking deep html elements + return origValue === currentValue; + } + else if (origValue instanceof Array) { if (origValue.length !== currentValue.length) { return false } @@ -18944,7 +18954,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * Constructor * @memberOf fabric.Image.filters.Brightness.prototype * @param {Object} [options] Options object - * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) + * @param {Number} [options.brightness=0] Value to brighten the image up (-255..255) */ initialize: function(options) { options = options || { }; @@ -20746,6 +20756,180 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag })(typeof exports !== 'undefined' ? exports : this); +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Contrast filter class + * @class fabric.Image.filters.Contrast + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Contrast#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Contrast({ + * contrast: 40 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + filters.Contrast = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Contrast.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Contrast', + + /** + * Constructor + * @memberOf fabric.Image.filters.Contrast.prototype + * @param {Object} [options] Options object + * @param {Number} [options.contrast=0] Value to contrast the image up (-255...255) + */ + initialize: function(options) { + options = options || { }; + this.contrast = options.contrast || 0; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + contrastF = 259 * (this.contrast + 255) / (255 * (259 - this.contrast)); + + for (var i = 0, len = data.length; i < len; i += 4) { + data[i] = contrastF * (data[i] - 128) + 128; + data[i + 1] = contrastF * (data[i + 1] - 128) + 128; + data[i + 2] = contrastF * (data[i + 2] - 128) + 128; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + contrast: this.contrast + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast + */ + fabric.Image.filters.Contrast.fromObject = function(object) { + return new fabric.Image.filters.Contrast(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Saturate filter class + * @class fabric.Image.filters.Saturate + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Saturate#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Saturate({ + * saturate: 100 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + filters.Saturate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Saturate.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Saturate', + + /** + * Constructor + * @memberOf fabric.Image.filters.Saturate.prototype + * @param {Object} [options] Options object + * @param {Number} [options.contrast=0] Value to saturate the image (-255...255) + */ + initialize: function(options) { + options = options || { }; + this.saturate = options.saturate || 0; + this.loadProgram(); + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + max, adjust = -this.saturate * 0.01; + + for (var i = 0, len = data.length; i < len; i += 4) { + max = Math.max(data[i], data[i + 1], data[i + 2]); + data[i] += max !== data[i] ? (max - data[i]) * adjust : 0; + data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0; + data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + saturate: this.saturate + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Saturate} Instance of fabric.Image.filters.Saturate + */ + fabric.Image.filters.Saturate.fromObject = function(object) { + return new fabric.Image.filters.Saturate(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + (function(global) { 'use strict'; @@ -23132,6 +23316,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag this.initRemovedHandler(); this.initCursorSelectionHandlers(); this.initDoubleClickSimulation(); + this.mouseMoveHandler = this.mouseMoveHandler.bind(this); }, /** @@ -23153,18 +23338,14 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag initAddedHandler: function() { var _this = this; this.on('added', function() { - if (this.canvas && !this.canvas._hasITextHandlers) { - this.canvas._hasITextHandlers = true; - this._initCanvasHandlers(); - } - - // Track IText instances per-canvas. Only register in this array once added - // to a canvas; we don't want to leak a reference to the instance forever - // simply because it existed at some point. - // (Might be added to a collection, but not on a canvas.) - if (_this.canvas) { - _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; - _this.canvas._iTextInstances.push(_this); + var canvas = _this.canvas; + if (canvas) { + if (!canvas._hasITextHandlers) { + canvas._hasITextHandlers = true; + _this._initCanvasHandlers(canvas); + } + canvas._iTextInstances = canvas._iTextInstances || []; + canvas._iTextInstances.push(_this); } }); }, @@ -23172,35 +23353,46 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag initRemovedHandler: function() { var _this = this; this.on('removed', function() { - // (Might be removed from a collection, but not on a canvas.) - if (_this.canvas) { - _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; - fabric.util.removeFromArray(_this.canvas._iTextInstances, _this); + var canvas = _this.canvas; + if (canvas) { + canvas._iTextInstances = canvas._iTextInstances || []; + fabric.util.removeFromArray(canvas._iTextInstances, _this); + if (canvas._iTextInstances.length === 0) { + canvas._hasITextHandlers = false; + _this._removeCanvasHandlers(canvas); + } } }); }, /** + * register canvas event to manage exiting on other instances * @private */ - _initCanvasHandlers: function() { - var _this = this; - - this.canvas.on('selection:cleared', function() { - fabric.IText.prototype.exitEditingOnOthers(_this.canvas); - }); - - this.canvas.on('mouse:up', function() { - if (_this.canvas._iTextInstances) { - _this.canvas._iTextInstances.forEach(function(obj) { + _initCanvasHandlers: function(canvas) { + canvas._canvasITextSelectionClearedHanlder = (function() { + fabric.IText.prototype.exitEditingOnOthers(canvas); + }).bind(this); + canvas._mouseUpITextHandler = (function() { + if (canvas._iTextInstances) { + canvas._iTextInstances.forEach(function(obj) { obj.__isMousedown = false; }); } - }); + }).bind(this); + canvas.on('selection:cleared', canvas._canvasITextSelectionClearedHanlder); + canvas.on('object:selected', canvas._canvasITextSelectionClearedHanlder); + canvas.on('mouse:up', canvas._mouseUpITextHandler); + }, - this.canvas.on('object:selected', function() { - fabric.IText.prototype.exitEditingOnOthers(_this.canvas); - }); + /** + * remove canvas event to manage exiting on other instances + * @private + */ + _removeCanvasHandlers: function(canvas) { + canvas.off('selection:cleared', canvas._canvasITextSelectionClearedHanlder); + canvas.off('object:selected', canvas._canvasITextSelectionClearedHanlder); + canvas.off('mouse:up', canvas._mouseUpITextHandler); }, /** @@ -23487,8 +23679,8 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag return this; } this.canvas.fire('text:editing:entered', { target: this }); - this.canvas.renderAll(); this.initMouseMoveHandler(); + this.canvas.renderAll(); return this; }, @@ -23507,7 +23699,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * Initializes "mousemove" event handler */ initMouseMoveHandler: function() { - this.canvas.on('mouse:move', this.mouseMoveHandler.bind(this)); + this.canvas.on('mouse:move', this.mouseMoveHandler); }, /** diff --git a/dist/fabric.min.js b/dist/fabric.min.js index 8ed014f07ba..ae1a1e80ea9 100644 --- a/dist/fabric.min.js +++ b/dist/fabric.min.js @@ -1,8 +1,8 @@ -var fabric=fabric||{version:"1.6.5"};"undefined"!=typeof exports&&(exports.fabric=fabric),"undefined"!=typeof document&&"undefined"!=typeof window?(fabric.document=document,fabric.window=window,window.fabric=fabric):(fabric.document=require("jsdom").jsdom("
"),fabric.document.createWindow?fabric.window=fabric.document.createWindow():fabric.window=fabric.document.parentWindow),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode="undefined"!=typeof Buffer&&"undefined"==typeof window,fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","id"],fabric.DPI=96,fabric.reNum="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",fabric.fontPaths={},fabric.charWidthsCache={},fabric.devicePixelRatio=fabric.window.devicePixelRatio||fabric.window.webkitDevicePixelRatio||fabric.window.mozDevicePixelRatio||1,function(){function t(t,e){if(this.__eventListeners[t]){var i=this.__eventListeners[t];e?i[i.indexOf(e)]=!1:fabric.util.array.fill(i,!1)}}function e(t,e){if(this.__eventListeners||(this.__eventListeners={}),1===arguments.length)for(var i in t)this.on(i,t[i]);else this.__eventListeners[t]||(this.__eventListeners[t]=[]),this.__eventListeners[t].push(e);return this}function i(e,i){if(this.__eventListeners){if(0===arguments.length)for(e in this.__eventListeners)t.call(this,e);else if(1===arguments.length&&"object"==typeof arguments[0])for(var r in e)t.call(this,r,e[r]);else t.call(this,e,i);return this}}function r(t,e){if(this.__eventListeners){var i=this.__eventListeners[t];if(i){for(var r=0,n=i.length;r