From de7b92bda53ecbc327385824b58fd7a674a85009 Mon Sep 17 00:00:00 2001 From: kangax Date: Sat, 16 Nov 2013 14:59:34 +0100 Subject: [PATCH] Some refactoring; move arc into a separate file --- build.js | 1 + src/filters/convolute_filter.class.js | 39 +- src/shapes/text.class.js | 3 +- src/static_canvas.class.js | 12 +- src/util/anim_ease.js | 73 +- src/util/arc.js | 137 ++++ src/util/lang_string.js | 8 +- src/util/misc.js | 983 +++++++++++--------------- 8 files changed, 639 insertions(+), 617 deletions(-) create mode 100644 src/util/arc.js diff --git a/build.js b/build.js index 7dbb5f18..1c06c2d2 100644 --- a/build.js +++ b/build.js @@ -148,6 +148,7 @@ var filesToInclude = [ 'src/mixins/collection.mixin.js', 'src/util/misc.js', + 'src/util/arc.js', 'src/util/lang_array.js', 'src/util/lang_object.js', 'src/util/lang_string.js', diff --git a/src/filters/convolute_filter.class.js b/src/filters/convolute_filter.class.js index 0565313e..a63e7175 100644 --- a/src/filters/convolute_filter.class.js +++ b/src/filters/convolute_filter.class.js @@ -86,15 +86,16 @@ * @param {Object} canvasEl Canvas element to apply filter to */ applyTo: function(canvasEl) { - var weights = this.matrix; - var context = canvasEl.getContext('2d'); - var pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height); - var side = Math.round(Math.sqrt(weights.length)); - var halfSide = Math.floor(side/2); - var src = pixels.data; - var sw = pixels.width; - var sh = pixels.height; + var weights = this.matrix, + context = canvasEl.getContext('2d'), + pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + + side = Math.round(Math.sqrt(weights.length)), + halfSide = Math.floor(side/2), + src = pixels.data, + sw = pixels.width, + sh = pixels.height; // pad output by the convolution matrix var w = sw; @@ -105,6 +106,7 @@ // go through the destination image pixels var alphaFac = this.opaque ? 1 : 0; + for (var y=0; y= 0 && scy < sh && scx >= 0 && scx < sw) { - var srcOff = (scy*sw+scx)*4; - var wt = weights[cy*side+cx]; - r += src[srcOff] * wt; - g += src[srcOff+1] * wt; - b += src[srcOff+2] * wt; - a += src[srcOff+3] * wt; - } + + /* jshint maxdepth:5 */ + if (scy < 0 || scy > sh || scx < 0 || scx > sw) continue; + + var srcOff = (scy*sw+scx)*4; + var wt = weights[cy*side+cx]; + + r += src[srcOff] * wt; + g += src[srcOff+1] * wt; + b += src[srcOff+2] * wt; + a += src[srcOff+3] * wt; } } dst[dstOff] = r; diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 72965126..be285c6c 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -282,7 +282,8 @@ useNative: true, /** - * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) + * List of properties to consider when checking if + * state of an object is changed ({@link fabric.Object#hasStateChanged}) * as well as for history (undo/redo) purposes * @type Array */ diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js index 784ccb9d..ebb662c4 100644 --- a/src/static_canvas.class.js +++ b/src/static_canvas.class.js @@ -53,7 +53,8 @@ /** * Background image of canvas instance. * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. - * Backwards incompatibility note: The "backgroundImageOpacity" and "backgroundImageStretch" properties are deprecated since 1.3.9. + * Backwards incompatibility note: The "backgroundImageOpacity" + * and "backgroundImageStretch" properties are deprecated since 1.3.9. * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. * @type fabric.Image * @default @@ -72,7 +73,8 @@ /** * Overlay image of canvas instance. * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. - * Backwards incompatibility note: The "overlayImageLeft" and "overlayImageTop" properties are deprecated since 1.3.9. + * Backwards incompatibility note: The "overlayImageLeft" + * and "overlayImageTop" properties are deprecated since 1.3.9. * Use {@link fabric.Image#left} and {@link fabric.Image#top}. * @type fabric.Image * @default @@ -303,7 +305,8 @@ /** * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} or {@link fabric.StaticCanvas#overlayImage|overlayImage}) + * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} + * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background or overlay to * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. @@ -325,7 +328,8 @@ /** * @private - * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} or {@link fabric.StaticCanvas#overlayColor|overlayColor}) + * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} + * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) * @param {(Object|String)} color Object with pattern information or color value * @param {Function} [callback] Callback is invoked when color is set */ diff --git a/src/util/anim_ease.js b/src/util/anim_ease.js index ccd547b5..bed3b7e9 100644 --- a/src/util/anim_ease.js +++ b/src/util/anim_ease.js @@ -12,40 +12,6 @@ Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); } - /** - * Quadratic easing in - * @memberOf fabric.util.ease - */ - function easeInQuad(t, b, c, d) { - return c*(t/=d)*t + b; - } - - /** - * Quadratic easing out - * @memberOf fabric.util.ease - */ - function easeOutQuad(t, b, c, d) { - return -c *(t/=d)*(t-2) + b; - } - - /** - * Quadratic easing in and out - * @memberOf fabric.util.ease - */ - function easeInOutQuad(t, b, c, d) { - t /= (d/2); - if (t < 1) return c/2*t*t + b; - return -c/2 * ((--t)*(t-2) - 1) + b; - } - - /** - * Cubic easing in - * @memberOf fabric.util.ease - */ - function easeInCubic(t, b, c, d) { - return c*(t/=d)*t*t + b; - } - /** * Cubic easing out * @memberOf fabric.util.ease @@ -305,10 +271,41 @@ * @namespace fabric.util.ease */ fabric.util.ease = { - easeInQuad: easeInQuad, - easeOutQuad: easeOutQuad, - easeInOutQuad: easeInOutQuad, - easeInCubic: easeInCubic, + + /** + * Quadratic easing in + * @memberOf fabric.util.ease + */ + easeInQuad: function(t, b, c, d) { + return c*(t/=d)*t + b; + }, + + /** + * Quadratic easing out + * @memberOf fabric.util.ease + */ + easeOutQuad: function(t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + }, + + /** + * Quadratic easing in and out + * @memberOf fabric.util.ease + */ + easeInOutQuad: function(t, b, c, d) { + t /= (d/2); + if (t < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + }, + + /** + * Cubic easing in + * @memberOf fabric.util.ease + */ + easeInCubic: function(t, b, c, d) { + return c*(t/=d)*t*t + b; + }, + easeOutCubic: easeOutCubic, easeInOutCubic: easeInOutCubic, easeInQuart: easeInQuart, diff --git a/src/util/arc.js b/src/util/arc.js new file mode 100644 index 00000000..36c567ec --- /dev/null +++ b/src/util/arc.js @@ -0,0 +1,137 @@ +(function() { + + var arcToSegmentsCache = { }, + segmentToBezierCache = { }, + _join = Array.prototype.join, + argsString; + + // Generous contribution by Raph Levien, from libsvg-0.1.0.tar.gz + function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) { + + argsString = _join.call(arguments); + + if (arcToSegmentsCache[argsString]) { + return arcToSegmentsCache[argsString]; + } + + var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y); + + var d = (coords.x1-coords.x0) * (coords.x1-coords.x0) + + (coords.y1-coords.y0) * (coords.y1-coords.y0); + + var sfactor_sq = 1 / d - 0.25; + if (sfactor_sq < 0) sfactor_sq = 0; + + var sfactor = Math.sqrt(sfactor_sq); + if (sweep === large) sfactor = -sfactor; + + var xc = 0.5 * (coords.x0 + coords.x1) - sfactor * (coords.y1-coords.y0); + var yc = 0.5 * (coords.y0 + coords.y1) + sfactor * (coords.x1-coords.x0); + + var th0 = Math.atan2(coords.y0-yc, coords.x0-xc); + var th1 = Math.atan2(coords.y1-yc, coords.x1-xc); + + var th_arc = th1-th0; + if (th_arc < 0 && sweep === 1) { + th_arc += 2*Math.PI; + } + else if (th_arc > 0 && sweep === 0) { + th_arc -= 2 * Math.PI; + } + + var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001))); + var result = []; + for (var i=0; i 1) { + pl = Math.sqrt(pl); + rx *= pl; + ry *= pl; + } + + var a00 = cos_th / rx; + var a01 = sin_th / rx; + var a10 = (-sin_th) / ry; + var a11 = (cos_th) / ry; + + return { + x0: a00 * ox + a01 * oy, + y0: a10 * ox + a11 * oy, + x1: a00 * x + a01 * y, + y1: a10 * x + a11 * y, + sin_th: sin_th, + cos_th: cos_th + }; + } + + function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) { + argsString = _join.call(arguments); + if (segmentToBezierCache[argsString]) { + return segmentToBezierCache[argsString]; + } + + var a00 = cos_th * rx; + var a01 = -sin_th * ry; + var a10 = sin_th * rx; + var a11 = cos_th * ry; + + var th_half = 0.5 * (th1 - th0); + var t = (8/3) * Math.sin(th_half * 0.5) * + Math.sin(th_half * 0.5) / Math.sin(th_half); + + var x1 = cx + Math.cos(th0) - t * Math.sin(th0); + var y1 = cy + Math.sin(th0) + t * Math.cos(th0); + var x3 = cx + Math.cos(th1); + var y3 = cy + Math.sin(th1); + var x2 = x3 + t * Math.sin(th1); + var y2 = y3 - t * Math.cos(th1); + + segmentToBezierCache[argsString] = [ + a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, + a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, + a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 + ]; + + return segmentToBezierCache[argsString]; + } + + /** + * Draws arc + * @param {CanvasRenderingContext2D} ctx + * @param {Number} x + * @param {Number} y + * @param {Array} coords + */ + fabric.util.drawArc = function(ctx, x, y, coords) { + var rx = coords[0]; + var ry = coords[1]; + var rot = coords[2]; + var large = coords[3]; + var sweep = coords[4]; + var ex = coords[5]; + var ey = coords[6]; + var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y); + for (var i=0; i 1) { + object = new fabric.PathGroup(elements, options); } else { - enlivenedObjects[index] = klass.fromObject(o); - reviver && reviver(o, enlivenedObjects[index]); - onLoaded(); + object = elements[0]; } - }); - } - /** - * Groups SVG elements (usually those retrieved from SVG document) - * @static - * @memberOf fabric.util - * @param {Array} elements SVG elements to group - * @param {Object} [options] Options object - * @return {fabric.Object|fabric.PathGroup} - */ - function groupSVGElements(elements, options, path) { - var object; + if (typeof path !== 'undefined') { + object.setSourcePath(path); + } + return object; + }, - if (elements.length > 1) { - object = new fabric.PathGroup(elements, options); - } - else { - object = elements[0]; - } - - if (typeof path !== 'undefined') { - object.setSourcePath(path); - } - return object; - } - - /** - * Populates an object with properties of another object - * @static - * @memberOf fabric.util - * @param {Object} source Source object - * @param {Object} destination Destination object - * @return {Array} properties Propertie names to include - */ - function populateWithProperties(source, destination, properties) { - if (properties && Object.prototype.toString.call(properties) === '[object Array]') { - for (var i = 0, len = properties.length; i < len; i++) { - if (properties[i] in source) { - destination[properties[i]] = source[properties[i]]; + /** + * Populates an object with properties of another object + * @static + * @memberOf fabric.util + * @param {Object} source Source object + * @param {Object} destination Destination object + * @return {Array} properties Propertie names to include + */ + populateWithProperties: function(source, destination, properties) { + if (properties && Object.prototype.toString.call(properties) === '[object Array]') { + for (var i = 0, len = properties.length; i < len; i++) { + if (properties[i] in source) { + destination[properties[i]] = source[properties[i]]; + } } } - } - } + }, - /** - * Draws a dashed line between two points - * - * This method is used to draw dashed line around selection area. - * See dotted stroke in canvas - * - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x start x coordinate - * @param {Number} y start y coordinate - * @param {Number} x2 end x coordinate - * @param {Number} y2 end y coordinate - * @param {Array} da dash array pattern - */ - function drawDashedLine(ctx, x, y, x2, y2, da) { - var dx = x2 - x, - dy = y2 - y, - len = sqrt(dx*dx + dy*dy), - rot = atan2(dy, dx), - dc = da.length, - di = 0, - draw = true; + /** + * Draws a dashed line between two points + * + * This method is used to draw dashed line around selection area. + * See dotted stroke in canvas + * + * @param {CanvasRenderingContext2D} ctx context + * @param {Number} x start x coordinate + * @param {Number} y start y coordinate + * @param {Number} x2 end x coordinate + * @param {Number} y2 end y coordinate + * @param {Array} da dash array pattern + */ + drawDashedLine: function(ctx, x, y, x2, y2, da) { + var dx = x2 - x, + dy = y2 - y, + len = sqrt(dx*dx + dy*dy), + rot = atan2(dy, dx), + dc = da.length, + di = 0, + draw = true; - ctx.save(); - ctx.translate(x, y); - ctx.moveTo(0, 0); - ctx.rotate(rot); + ctx.save(); + ctx.translate(x, y); + ctx.moveTo(0, 0); + ctx.rotate(rot); - x = 0; - while (len > x) { - x += da[di++ % dc]; - if (x > len) { - x = len; - } - ctx[draw ? 'lineTo' : 'moveTo'](x, 0); - draw = !draw; - } - - ctx.restore(); - } - - /** - * Creates canvas element and initializes it via excanvas if necessary - * @static - * @memberOf fabric.util - * @param {CanvasElement} [canvasEl] optional canvas element to initialize; when not given, element is created implicitly - * @return {CanvasElement} initialized canvas element - */ - function createCanvasElement(canvasEl) { - canvasEl || (canvasEl = fabric.document.createElement('canvas')); - if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { - G_vmlCanvasManager.initElement(canvasEl); - } - return canvasEl; - } - - /** - * Creates image element (works on client and node) - * @static - * @memberOf fabric.util - * @return {HTMLImageElement} HTML image element - */ - function createImage() { - return fabric.isLikelyNode - ? new (require('canvas').Image)() - : fabric.document.createElement('img'); - } - - /** - * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array - * @static - * @memberOf fabric.util - * @param {Object} klass "Class" to create accessors for - */ - function createAccessors(klass) { - var proto = klass.prototype; - - for (var i = proto.stateProperties.length; i--; ) { - - var propName = proto.stateProperties[i], - capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), - setterName = 'set' + capitalizedPropName, - getterName = 'get' + capitalizedPropName; - - // using `new Function` for better introspection - if (!proto[getterName]) { - proto[getterName] = (function(property) { - return new Function('return this.get("' + property + '")'); - })(propName); - } - if (!proto[setterName]) { - proto[setterName] = (function(property) { - return new Function('value', 'return this.set("' + property + '", value)'); - })(propName); - } - } - } - - /** - * @static - * @memberOf fabric.util - * @param {fabric.Object} receiver Object implementing `clipTo` method - * @param {CanvasRenderingContext2D} ctx Context to clip - */ - function clipContext(receiver, ctx) { - ctx.save(); - ctx.beginPath(); - receiver.clipTo(ctx); - ctx.clip(); - } - - /** - * Multiply matrix A by matrix B to nest transformations - * @static - * @memberOf fabric.util - * @param {Array} matrixA First transformMatrix - * @param {Array} matrixB Second transformMatrix - * @return {Array} The product of the two transform matrices - */ - function multiplyTransformMatrices(matrixA, matrixB) { - // Matrix multiply matrixA * matrixB - var a = [ - [matrixA[0], matrixA[2], matrixA[4]], - [matrixA[1], matrixA[3], matrixA[5]], - [0 , 0 , 1 ] - ]; - - var b = [ - [matrixB[0], matrixB[2], matrixB[4]], - [matrixB[1], matrixB[3], matrixB[5]], - [0 , 0 , 1 ] - ]; - - var result = []; - for (var r=0; r<3; r++) { - result[r] = []; - for (var c=0; c<3; c++) { - var sum = 0; - for (var k=0; k<3; k++) { - sum += a[r][k]*b[k][c]; + x = 0; + while (len > x) { + x += da[di++ % dc]; + if (x > len) { + x = len; } - - result[r][c] = sum; + ctx[draw ? 'lineTo' : 'moveTo'](x, 0); + draw = !draw; } - } - return [ - result[0][0], - result[1][0], - result[0][1], - result[1][1], - result[0][2], - result[1][2] - ]; - } + ctx.restore(); + }, - function getFunctionBody(fn) { - return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; - } - - function drawArc(ctx, x, y, coords) { - var rx = coords[0]; - var ry = coords[1]; - var rot = coords[2]; - var large = coords[3]; - var sweep = coords[4]; - var ex = coords[5]; - var ey = coords[6]; - var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y); - for (var i=0; i 0 && sweep === 0) { - th_arc -= 2 * Math.PI; - } - - var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001))); - var result = []; - for (var i=0; i 1) { - pl = Math.sqrt(pl); - rx *= pl; - ry *= pl; - } - - var a00 = cos_th / rx; - var a01 = sin_th / rx; - var a10 = (-sin_th) / ry; - var a11 = (cos_th) / ry; - - return { - x0: a00 * ox + a01 * oy, - y0: a10 * ox + a11 * oy, - x1: a00 * x + a01 * y, - y1: a10 * x + a11 * y, - sin_th: sin_th, - cos_th: cos_th - }; - } - - function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) { - argsString = _join.call(arguments); - if (segmentToBezierCache[argsString]) { - return segmentToBezierCache[argsString]; - } - - var a00 = cos_th * rx; - var a01 = -sin_th * ry; - var a10 = sin_th * rx; - var a11 = cos_th * ry; - - var th_half = 0.5 * (th1 - th0); - var t = (8/3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half); - var x1 = cx + Math.cos(th0) - t * Math.sin(th0); - var y1 = cy + Math.sin(th0) + t * Math.cos(th0); - var x3 = cx + Math.cos(th1); - var y3 = cy + Math.sin(th1); - var x2 = x3 + t * Math.sin(th1); - var y2 = y3 - t * Math.cos(th1); - - segmentToBezierCache[argsString] = [ - a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, - a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, - a00 * x3 + a01 * y3, a10 * x3 + a11 * y3 - ]; - - return segmentToBezierCache[argsString]; - } - - function normalizePoints(points, options) { - var minX = fabric.util.array.min(points, 'x'), - minY = fabric.util.array.min(points, 'y'); - - minX = minX < 0 ? minX : 0; - minY = minX < 0 ? minY : 0; - - for (var i = 0, len = points.length; i < len; i++) { - // normalize coordinates, according to containing box (dimensions of which are passed via `options`) - points[i].x -= (options.width / 2 + minX) || 0; - points[i].y -= (options.height / 2 + minY) || 0; - } - } - - /** - * Returns true if context has transparent pixel at specified location (taking tolerance into account) - * @param {CanvasRenderingContext2D} ctx context - * @param {Number} x x coordinate - * @param {Number} y y coordinate - * @param {Number} tolerance Tolerance - */ - function isTransparent(ctx, x, y, tolerance) { - - // If tolerance is > 0 adjust start coords to take into account. If moves off Canvas fix to 0 - if (tolerance > 0) { - if (x > tolerance) { - x -= tolerance; + /** + * Creates canvas element and initializes it via excanvas if necessary + * @static + * @memberOf fabric.util + * @param {CanvasElement} [canvasEl] optional canvas element to initialize; + * when not given, element is created implicitly + * @return {CanvasElement} initialized canvas element + */ + createCanvasElement: function(canvasEl) { + canvasEl || (canvasEl = fabric.document.createElement('canvas')); + if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { + G_vmlCanvasManager.initElement(canvasEl); } - else { - x = 0; + return canvasEl; + }, + + /** + * Creates image element (works on client and node) + * @static + * @memberOf fabric.util + * @return {HTMLImageElement} HTML image element + */ + createImage: function() { + return fabric.isLikelyNode + ? new (require('canvas').Image)() + : fabric.document.createElement('img'); + }, + + /** + * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array + * @static + * @memberOf fabric.util + * @param {Object} klass "Class" to create accessors for + */ + createAccessors: function(klass) { + var proto = klass.prototype; + + for (var i = proto.stateProperties.length; i--; ) { + + var propName = proto.stateProperties[i], + capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), + setterName = 'set' + capitalizedPropName, + getterName = 'get' + capitalizedPropName; + + // using `new Function` for better introspection + if (!proto[getterName]) { + proto[getterName] = (function(property) { + return new Function('return this.get("' + property + '")'); + })(propName); + } + if (!proto[setterName]) { + proto[setterName] = (function(property) { + return new Function('value', 'return this.set("' + property + '", value)'); + })(propName); + } } - if (y > tolerance) { - y -= tolerance; + }, + + /** + * @static + * @memberOf fabric.util + * @param {fabric.Object} receiver Object implementing `clipTo` method + * @param {CanvasRenderingContext2D} ctx Context to clip + */ + clipContext: function(receiver, ctx) { + ctx.save(); + ctx.beginPath(); + receiver.clipTo(ctx); + ctx.clip(); + }, + + /** + * Multiply matrix A by matrix B to nest transformations + * @static + * @memberOf fabric.util + * @param {Array} matrixA First transformMatrix + * @param {Array} matrixB Second transformMatrix + * @return {Array} The product of the two transform matrices + */ + multiplyTransformMatrices: function(matrixA, matrixB) { + // Matrix multiply matrixA * matrixB + var a = [ + [matrixA[0], matrixA[2], matrixA[4]], + [matrixA[1], matrixA[3], matrixA[5]], + [0 , 0 , 1 ] + ]; + + var b = [ + [matrixB[0], matrixB[2], matrixB[4]], + [matrixB[1], matrixB[3], matrixB[5]], + [0 , 0 , 1 ] + ]; + + var result = []; + for (var r=0; r<3; r++) { + result[r] = []; + for (var c=0; c<3; c++) { + var sum = 0; + for (var k=0; k<3; k++) { + sum += a[r][k]*b[k][c]; + } + + result[r][c] = sum; + } } - else { - y = 0; + + return [ + result[0][0], + result[1][0], + result[0][1], + result[1][1], + result[0][2], + result[1][2] + ]; + }, + + /** + * Returns string representation of function body + * @param {Function} fn Function to get body of + * @return {String} Function body + */ + getFunctionBody: function(fn) { + return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; + }, + + /** + * Normalizes polygon/polyline points according to their dimensions + * @param {Array} points + * @param {Object} options + */ + normalizePoints: function(points, options) { + var minX = fabric.util.array.min(points, 'x'), + minY = fabric.util.array.min(points, 'y'); + + minX = minX < 0 ? minX : 0; + minY = minX < 0 ? minY : 0; + + for (var i = 0, len = points.length; i < len; i++) { + // normalize coordinates, according to containing box + // (dimensions of which are passed via `options`) + points[i].x -= (options.width / 2 + minX) || 0; + points[i].y -= (options.height / 2 + minY) || 0; } + }, + + /** + * Returns true if context has transparent pixel + * at specified location (taking tolerance into account) + * @param {CanvasRenderingContext2D} ctx context + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @param {Number} tolerance Tolerance + */ + isTransparent: function(ctx, x, y, tolerance) { + + // If tolerance is > 0 adjust start coords to take into account. + // If moves off Canvas fix to 0 + if (tolerance > 0) { + if (x > tolerance) { + x -= tolerance; + } + else { + x = 0; + } + if (y > tolerance) { + y -= tolerance; + } + else { + y = 0; + } + } + + var _isTransparent = true; + var imageData = ctx.getImageData( + x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); + + // Split image data - for tolerance > 1, pixelDataSize = 4; + for (var i = 3, l = imageData.data.length; i < l; i += 4) { + var temp = imageData.data[i]; + _isTransparent = temp <= 0; + if (_isTransparent === false) break; // Stop if colour found + } + + imageData = null; + + return _isTransparent; } - - var _isTransparent = true; - var imageData = ctx.getImageData( - x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); - - // Split image data - for tolerance > 1, pixelDataSize = 4; - for (var i = 3, l = imageData.data.length; i < l; i += 4) { - var temp = imageData.data[i]; - _isTransparent = temp <= 0; - if (_isTransparent === false) break; // Stop if colour found - } - - imageData = null; - - return _isTransparent; - } - - fabric.util.removeFromArray = removeFromArray; - fabric.util.degreesToRadians = degreesToRadians; - fabric.util.radiansToDegrees = radiansToDegrees; - fabric.util.rotatePoint = rotatePoint; - fabric.util.toFixed = toFixed; - fabric.util.getRandomInt = getRandomInt; - fabric.util.falseFunction = falseFunction; - fabric.util.getKlass = getKlass; - fabric.util.resolveNamespace = resolveNamespace; - fabric.util.loadImage = loadImage; - fabric.util.enlivenObjects = enlivenObjects; - fabric.util.groupSVGElements = groupSVGElements; - fabric.util.populateWithProperties = populateWithProperties; - fabric.util.drawDashedLine = drawDashedLine; - fabric.util.createCanvasElement = createCanvasElement; - fabric.util.createImage = createImage; - fabric.util.createAccessors = createAccessors; - fabric.util.clipContext = clipContext; - fabric.util.multiplyTransformMatrices = multiplyTransformMatrices; - fabric.util.getFunctionBody = getFunctionBody; - fabric.util.drawArc = drawArc; - fabric.util.normalizePoints = normalizePoints; - fabric.util.isTransparent = isTransparent; + }; })(typeof exports !== 'undefined' ? exports : this);