(function() { var degreesToRadians = fabric.util.degreesToRadians; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { /** * Object containing coordinates of object's controls * @type Object * @default */ oCoords: null, /** * Checks if object intersects with an area formed by 2 points * @param {Object} pointTL top-left point of area * @param {Object} pointBR bottom-right point of area * @return {Boolean} true if object intersects with an area formed by 2 points */ intersectsWithRect: function(pointTL, pointBR) { var oCoords = this.oCoords, tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), br = new fabric.Point(oCoords.br.x, oCoords.br.y), intersection = fabric.Intersection.intersectPolygonRectangle( [tl, tr, br, bl], pointTL, pointBR ); return intersection.status === 'Intersection'; }, /** * Checks if object intersects with another object * @param {Object} other Object to test * @return {Boolean} true if object intersects with another object */ intersectsWithObject: function(other) { // extracts coords function getCoords(oCoords) { return { tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), br: new fabric.Point(oCoords.br.x, oCoords.br.y) }; } var thisCoords = getCoords(this.oCoords), otherCoords = getCoords(other.oCoords), intersection = fabric.Intersection.intersectPolygonPolygon( [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] ); return intersection.status === 'Intersection'; }, /** * Checks if object is fully contained within area of another object * @param {Object} other Object to test * @return {Boolean} true if object is fully contained within area of another object */ isContainedWithinObject: function(other) { var boundingRect = other.getBoundingRect(), point1 = new fabric.Point(boundingRect.left, boundingRect.top), point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); return this.isContainedWithinRect(point1, point2); }, /** * Checks if object is fully contained within area formed by 2 points * @param {Object} pointTL top-left point of area * @param {Object} pointBR bottom-right point of area * @return {Boolean} true if object is fully contained within area formed by 2 points */ isContainedWithinRect: function(pointTL, pointBR) { var boundingRect = this.getBoundingRect(); return ( boundingRect.left >= pointTL.x && boundingRect.left + boundingRect.width <= pointBR.x && boundingRect.top >= pointTL.y && boundingRect.top + boundingRect.height <= pointBR.y ); }, /** * Checks if point is inside the object * @param {fabric.Point} point Point to check against * @return {Boolean} true if point is inside the object */ containsPoint: function(point) { var lines = this._getImageLines(this.oCoords), xPoints = this._findCrossPoints(point, lines); // if xPoints is odd then point is inside the object return (xPoints !== 0 && xPoints % 2 === 1); }, /** * Method that returns an object with the object edges in it, given the coordinates of the corners * @private * @param {Object} oCoords Coordinates of the object corners */ _getImageLines: function(oCoords) { return { topline: { o: oCoords.tl, d: oCoords.tr }, rightline: { o: oCoords.tr, d: oCoords.br }, bottomline: { o: oCoords.br, d: oCoords.bl }, leftline: { o: oCoords.bl, d: oCoords.tl } }; }, /** * Helper method to determine how many cross points are between the 4 object edges * and the horizontal line determined by a point on canvas * @private * @param {fabric.Point} point Point to check * @param {Object} oCoords Coordinates of the object being evaluated */ _findCrossPoints: function(point, oCoords) { var b1, b2, a1, a2, xi, yi, xcount = 0, iLine; for (var lineKey in oCoords) { iLine = oCoords[lineKey]; // optimisation 1: line below point. no cross if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { continue; } // optimisation 2: line above point. no cross if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { continue; } // optimisation 3: vertical line case if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { xi = iLine.o.x; yi = point.y; } // calculate the intersection point else { b1 = 0; b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); a1 = point.y - b1 * point.x; a2 = iLine.o.y - b2 * iLine.o.x; xi = - (a1 - a2) / (b1 - b2); yi = a1 + b1 * xi; } // dont count xi < point.x cases if (xi >= point.x) { xcount += 1; } // optimisation 4: specific for square images if (xcount === 2) { break; } } return xcount; }, /** * Returns width of an object's bounding rectangle * @deprecated since 1.0.4 * @return {Number} width value */ getBoundingRectWidth: function() { return this.getBoundingRect().width; }, /** * Returns height of an object's bounding rectangle * @deprecated since 1.0.4 * @return {Number} height value */ getBoundingRectHeight: function() { return this.getBoundingRect().height; }, /** * Returns coordinates of object's bounding rectangle (left, top, width, height) * @return {Object} Object with left, top, width, height properties */ getBoundingRect: function() { this.oCoords || this.setCoords(); var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], minX = fabric.util.array.min(xCoords), maxX = fabric.util.array.max(xCoords), width = Math.abs(minX - maxX), yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], minY = fabric.util.array.min(yCoords), maxY = fabric.util.array.max(yCoords), height = Math.abs(minY - maxY); return { left: minX, top: minY, width: width, height: height }; }, /** * Returns width of an object * @return {Number} width value */ getWidth: function() { return this.width * this.scaleX; }, /** * Returns height of an object * @return {Number} height value */ getHeight: function() { return this.height * this.scaleY; }, /** * Makes sure the scale is valid and modifies it if necessary * @private * @param {Number} value * @return {Number} */ _constrainScale: function(value) { if (Math.abs(value) < this.minScaleLimit) { if (value < 0) { return -this.minScaleLimit; } else { return this.minScaleLimit; } } return value; }, /** * Scales an object (equally by x and y) * @param {Number} value Scale factor * @return {fabric.Object} thisArg * @chainable */ scale: function(value) { value = this._constrainScale(value); if (value < 0) { this.flipX = !this.flipX; this.flipY = !this.flipY; value *= -1; } this.scaleX = value; this.scaleY = value; this.setCoords(); return this; }, /** * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) * @param {Number} value New width value * @return {fabric.Object} thisArg * @chainable */ scaleToWidth: function(value) { // adjust to bounding rect factor so that rotated shapes would fit as well var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); return this.scale(value / this.width / boundingRectFactor); }, /** * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) * @param {Number} value New height value * @return {fabric.Object} thisArg * @chainable */ scaleToHeight: function(value) { // adjust to bounding rect factor so that rotated shapes would fit as well var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); return this.scale(value / this.height / boundingRectFactor); }, /** * Sets corner position coordinates based on current angle, width and height * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords * @return {fabric.Object} thisArg * @chainable */ setCoords: function() { var strokeWidth = this.strokeWidth, theta = degreesToRadians(this.angle), vpt = this.getViewportTransform(), f = function (p) { return fabric.util.transformPoint(p, vpt); }, w = this.width, currentWidth, h = this.height, currentHeight, capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', vLine = this.type === 'line' && this.width === 0, hLine = this.type === 'line' && this.height === 0, sLine = vLine || hLine, strokeW = (capped && hLine) || !sLine, strokeH = (capped && vLine) || !sLine; if (vLine) { w = strokeWidth; } else if (hLine) { h = strokeWidth; } if (strokeW) { w += w > 0 ? strokeWidth : -strokeWidth; } if (strokeH) { h += h > 0 ? strokeWidth : -strokeWidth; } currentWidth = w * this.scaleX + 2 * this.padding; currentHeight = h * this.scaleY + 2 * this.padding; // If width is negative, make postive. Fixes path selection issue if (currentWidth < 0) { currentWidth = Math.abs(currentWidth); } var _hypotenuse = Math.sqrt( Math.pow(currentWidth / 2, 2) + Math.pow(currentHeight / 2, 2)), _angle = Math.atan( isFinite(currentHeight / currentWidth) ? currentHeight / currentWidth : 0), // offset added for rotate and scale actions offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), cosTh = Math.cos(theta), coords = this.getCenterPoint(), wh = new fabric.Point(currentWidth, currentHeight), _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), tl = f(_tl), tr = f(_tr), br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), bl = f(_bl), ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), mt = f(_mt), mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), mtr = f(new fabric.Point(_mt.x, _mt.y)); // debugging // setTimeout(function() { // canvas.contextTop.fillStyle = 'green'; // canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); // canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); // canvas.contextTop.fillRect(br.x, br.y, 3, 3); // canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); // canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); // canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); // canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); // canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); // }, 50); this.oCoords = { // corners tl: tl, tr: tr, br: br, bl: bl, // middle ml: ml, mt: mt, mr: mr, mb: mb, // rotating point mtr: mtr }; // set coordinates of the draggable boxes in the corners used to scale/rotate the image this._setCornerCoords && this._setCornerCoords(); return this; } }); })();