mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-04-05 23:11:02 +00:00
496 lines
No EOL
15 KiB
JavaScript
496 lines
No EOL
15 KiB
JavaScript
(function(){
|
|
|
|
var getPointer = fabric.util.getPointer,
|
|
degreesToRadians = fabric.util.degreesToRadians;
|
|
|
|
fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ {
|
|
|
|
/**
|
|
* Determines which one of the four corners has been clicked
|
|
* @method _findTargetCorner
|
|
* @private
|
|
* @param e {Event} event object
|
|
* @param offset {Object} canvas offset
|
|
* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
|
|
*/
|
|
_findTargetCorner: function(e, offset) {
|
|
if (!this.hasControls || !this.active) return false;
|
|
|
|
var pointer = getPointer(e, this.canvas.upperCanvasEl),
|
|
ex = pointer.x - offset.left,
|
|
ey = pointer.y - offset.top,
|
|
xpoints,
|
|
lines;
|
|
|
|
for (var i in this.oCoords) {
|
|
|
|
if (i === 'mtr' && !this.hasRotatingPoint) {
|
|
continue;
|
|
}
|
|
|
|
if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
|
|
continue;
|
|
}
|
|
|
|
lines = this._getImageLines(this.oCoords[i].corner, i);
|
|
|
|
// debugging
|
|
|
|
// canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
|
|
// canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
|
|
|
|
// canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
|
|
// canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
|
|
|
|
// canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
|
|
// canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
|
|
|
|
// canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
|
|
// canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
|
|
|
|
xpoints = this._findCrossPoints(ex, ey, lines);
|
|
if (xpoints % 2 === 1 && xpoints !== 0) {
|
|
this.__corner = i;
|
|
return i;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Helper method to determine how many cross points are between the 4 image edges
|
|
* and the horizontal line determined by the position of our mouse when clicked on canvas
|
|
* @method _findCrossPoints
|
|
* @private
|
|
* @param ex {Number} x coordinate of the mouse
|
|
* @param ey {Number} y coordinate of the mouse
|
|
* @param oCoords {Object} Coordinates of the image being evaluated
|
|
*/
|
|
_findCrossPoints: function(ex, ey, oCoords) {
|
|
var b1, b2, a1, a2, xi, yi,
|
|
xcount = 0,
|
|
iLine;
|
|
|
|
for (var lineKey in oCoords) {
|
|
iLine = oCoords[lineKey];
|
|
// optimisation 1: line below dot. no cross
|
|
if ((iLine.o.y < ey) && (iLine.d.y < ey)) {
|
|
continue;
|
|
}
|
|
// optimisation 2: line above dot. no cross
|
|
if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) {
|
|
continue;
|
|
}
|
|
// optimisation 3: vertical line case
|
|
if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= ex)) {
|
|
xi = iLine.o.x;
|
|
yi = ey;
|
|
}
|
|
// calculate the intersection point
|
|
else {
|
|
b1 = 0;
|
|
b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);
|
|
a1 = ey-b1*ex;
|
|
a2 = iLine.o.y-b2*iLine.o.x;
|
|
|
|
xi = - (a1-a2)/(b1-b2);
|
|
yi = a1+b1*xi;
|
|
}
|
|
// dont count xi < ex cases
|
|
if (xi >= ex) {
|
|
xcount += 1;
|
|
}
|
|
// optimisation 4: specific for square images
|
|
if (xcount === 2) {
|
|
break;
|
|
}
|
|
}
|
|
return xcount;
|
|
},
|
|
|
|
/**
|
|
* Method that returns an object with the image lines in it given the coordinates of the corners
|
|
* @method _getImageLines
|
|
* @private
|
|
* @param oCoords {Object} coordinates of the image 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
|
|
}
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Sets the coordinates of the draggable boxes in the corners of
|
|
* the image used to scale/rotate it.
|
|
* @method _setCornerCoords
|
|
* @private
|
|
*/
|
|
_setCornerCoords: function() {
|
|
var coords = this.oCoords,
|
|
theta = degreesToRadians(this.angle),
|
|
newTheta = degreesToRadians(45 - this.angle),
|
|
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
|
|
cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
|
|
sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
|
|
sinTh = Math.sin(theta),
|
|
cosTh = Math.cos(theta);
|
|
|
|
coords.tl.corner = {
|
|
tl: {
|
|
x: coords.tl.x - sinHalfOffset,
|
|
y: coords.tl.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.tl.x + cosHalfOffset,
|
|
y: coords.tl.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.tl.x - cosHalfOffset,
|
|
y: coords.tl.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.tl.x + sinHalfOffset,
|
|
y: coords.tl.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.tr.corner = {
|
|
tl: {
|
|
x: coords.tr.x - sinHalfOffset,
|
|
y: coords.tr.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.tr.x + cosHalfOffset,
|
|
y: coords.tr.y - sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.tr.x + sinHalfOffset,
|
|
y: coords.tr.y + cosHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.tr.x - cosHalfOffset,
|
|
y: coords.tr.y + sinHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.bl.corner = {
|
|
tl: {
|
|
x: coords.bl.x - sinHalfOffset,
|
|
y: coords.bl.y - cosHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.bl.x - cosHalfOffset,
|
|
y: coords.bl.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.bl.x + sinHalfOffset,
|
|
y: coords.bl.y + cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.bl.x + cosHalfOffset,
|
|
y: coords.bl.y - sinHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.br.corner = {
|
|
tr: {
|
|
x: coords.br.x + cosHalfOffset,
|
|
y: coords.br.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.br.x - cosHalfOffset,
|
|
y: coords.br.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.br.x + sinHalfOffset,
|
|
y: coords.br.y + cosHalfOffset
|
|
},
|
|
tl: {
|
|
x: coords.br.x - sinHalfOffset,
|
|
y: coords.br.y - cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.ml.corner = {
|
|
tl: {
|
|
x: coords.ml.x - sinHalfOffset,
|
|
y: coords.ml.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.ml.x + cosHalfOffset,
|
|
y: coords.ml.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.ml.x - cosHalfOffset,
|
|
y: coords.ml.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.ml.x + sinHalfOffset,
|
|
y: coords.ml.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.mt.corner = {
|
|
tl: {
|
|
x: coords.mt.x - sinHalfOffset,
|
|
y: coords.mt.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.mt.x + cosHalfOffset,
|
|
y: coords.mt.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.mt.x - cosHalfOffset,
|
|
y: coords.mt.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.mt.x + sinHalfOffset,
|
|
y: coords.mt.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.mr.corner = {
|
|
tl: {
|
|
x: coords.mr.x - sinHalfOffset,
|
|
y: coords.mr.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.mr.x + cosHalfOffset,
|
|
y: coords.mr.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.mr.x - cosHalfOffset,
|
|
y: coords.mr.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.mr.x + sinHalfOffset,
|
|
y: coords.mr.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.mb.corner = {
|
|
tl: {
|
|
x: coords.mb.x - sinHalfOffset,
|
|
y: coords.mb.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.mb.x + cosHalfOffset,
|
|
y: coords.mb.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.mb.x - cosHalfOffset,
|
|
y: coords.mb.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.mb.x + sinHalfOffset,
|
|
y: coords.mb.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.mtr.corner = {
|
|
tl: {
|
|
x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset),
|
|
y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset)
|
|
},
|
|
tr: {
|
|
x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset),
|
|
y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset)
|
|
},
|
|
bl: {
|
|
x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset),
|
|
y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset)
|
|
},
|
|
br: {
|
|
x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset),
|
|
y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset)
|
|
}
|
|
};
|
|
},
|
|
/**
|
|
* Draws borders of an object's bounding box.
|
|
* Requires public properties: width, height
|
|
* Requires public options: padding, borderColor
|
|
* @method drawBorders
|
|
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
drawBorders: function(ctx) {
|
|
if (!this.hasBorders) return;
|
|
|
|
var padding = this.padding,
|
|
padding2 = padding * 2,
|
|
strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0;
|
|
|
|
ctx.save();
|
|
|
|
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
ctx.strokeStyle = this.borderColor;
|
|
|
|
var scaleX = 1 / this._constrainScale(this.scaleX),
|
|
scaleY = 1 / this._constrainScale(this.scaleY);
|
|
|
|
ctx.lineWidth = 1 / this.borderScaleFactor;
|
|
|
|
ctx.scale(scaleX, scaleY);
|
|
|
|
var w = this.getWidth(),
|
|
h = this.getHeight();
|
|
|
|
ctx.strokeRect(
|
|
~~(-(w / 2) - padding - strokeWidth / 2 * this.scaleX) + 0.5, // offset needed to make lines look sharper
|
|
~~(-(h / 2) - padding - strokeWidth / 2 * this.scaleY) + 0.5,
|
|
~~(w + padding2 + strokeWidth * this.scaleX),
|
|
~~(h + padding2 + strokeWidth * this.scaleY)
|
|
);
|
|
|
|
if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) {
|
|
|
|
var rotateHeight = (
|
|
this.flipY
|
|
? h + (strokeWidth * this.scaleY) + (padding * 2)
|
|
: -h - (strokeWidth * this.scaleY) - (padding * 2)
|
|
) / 2;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, rotateHeight);
|
|
ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset));
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Draws corners of an object's bounding box.
|
|
* Requires public properties: width, height, scaleX, scaleY
|
|
* Requires public options: cornerSize, padding
|
|
* @method drawControls
|
|
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
drawControls: function(ctx) {
|
|
if (!this.hasControls) return;
|
|
|
|
var size = this.cornerSize,
|
|
size2 = size / 2,
|
|
strokeWidth2 = this.strokeWidth / 2,
|
|
left = -(this.width / 2),
|
|
top = -(this.height / 2),
|
|
_left,
|
|
_top,
|
|
sizeX = size / this.scaleX,
|
|
sizeY = size / this.scaleY,
|
|
paddingX = this.padding / this.scaleX,
|
|
paddingY = this.padding / this.scaleY,
|
|
scaleOffsetY = size2 / this.scaleY,
|
|
scaleOffsetX = size2 / this.scaleX,
|
|
scaleOffsetSizeX = (size2 - size) / this.scaleX,
|
|
scaleOffsetSizeY = (size2 - size) / this.scaleY,
|
|
height = this.height,
|
|
width = this.width,
|
|
methodName = this.transparentCorners ? 'strokeRect' : 'fillRect',
|
|
isVML = typeof G_vmlCanvasManager !== 'undefined';
|
|
|
|
ctx.save();
|
|
|
|
ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY);
|
|
|
|
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
|
|
|
|
// top-left
|
|
_left = left - scaleOffsetX - strokeWidth2 - paddingX;
|
|
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
|
|
// top-right
|
|
_left = left + width - scaleOffsetX + strokeWidth2 + paddingX;
|
|
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
|
|
// bottom-left
|
|
_left = left - scaleOffsetX - strokeWidth2 - paddingX;
|
|
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
|
|
// bottom-right
|
|
_left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
|
|
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
|
|
if (!this.get('lockUniScaling')) {
|
|
// middle-top
|
|
_left = left + width/2 - scaleOffsetX;
|
|
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
|
|
// middle-bottom
|
|
_left = left + width/2 - scaleOffsetX;
|
|
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
|
|
// middle-right
|
|
_left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
|
|
_top = top + height/2 - scaleOffsetY;
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
|
|
// middle-left
|
|
_left = left - scaleOffsetX - strokeWidth2 - paddingX;
|
|
_top = top + height/2 - scaleOffsetY;
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
}
|
|
|
|
// middle-top-rotate
|
|
if (this.hasRotatingPoint) {
|
|
|
|
_left = left + width/2 - scaleOffsetX;
|
|
_top = this.flipY ?
|
|
(top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY)
|
|
: (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY);
|
|
|
|
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
|
ctx[methodName](_left, _top, sizeX, sizeY);
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
return this;
|
|
}
|
|
});
|
|
})(); |