mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-04-25 07:54:44 +00:00
Add support for finding target per-pixel (ignoring transparent ones). This allows to drag shapes by non-transparent pixels only. Thanks Steve Pemberton for initial work. Version 0.9.13.
This commit is contained in:
parent
eacc459cf0
commit
07698a22ae
7 changed files with 180 additions and 23 deletions
|
|
@ -1,6 +1,6 @@
|
|||
/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */
|
||||
|
||||
var fabric = fabric || { version: "0.9.12" };
|
||||
var fabric = fabric || { version: "0.9.13" };
|
||||
|
||||
if (typeof exports != 'undefined') {
|
||||
exports.fabric = fabric;
|
||||
|
|
|
|||
112
dist/all.js
vendored
112
dist/all.js
vendored
|
|
@ -1,7 +1,7 @@
|
|||
/* build: `node build.js modules=ALL` */
|
||||
/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */
|
||||
|
||||
var fabric = fabric || { version: "0.9.12" };
|
||||
var fabric = fabric || { version: "0.9.13" };
|
||||
|
||||
if (typeof exports != 'undefined') {
|
||||
exports.fabric = fabric;
|
||||
|
|
@ -6025,6 +6025,7 @@ fabric.util.string = {
|
|||
|
||||
this._initStatic(el, options);
|
||||
this._initInteractive();
|
||||
this._createCacheCanvas();
|
||||
|
||||
fabric.Canvas.activeInstance = this;
|
||||
};
|
||||
|
|
@ -6119,6 +6120,10 @@ fabric.util.string = {
|
|||
*/
|
||||
containerClass: 'canvas-container',
|
||||
|
||||
perPixelTargetFind: false,
|
||||
|
||||
targetFindTolerance: 0,
|
||||
|
||||
_initInteractive: function() {
|
||||
this._currentTransform = null;
|
||||
this._groupSelector = null;
|
||||
|
|
@ -6507,6 +6512,49 @@ fabric.util.string = {
|
|||
return { x: x, y: y };
|
||||
},
|
||||
|
||||
_isTargetTransparent: function (target, x, y) {
|
||||
var cacheContext = this.contextCache;
|
||||
|
||||
var hasBorders = target.hasBorders, transparentCorners = target.transparentCorners;
|
||||
target.hasBorders = target.transparentCorners = false;
|
||||
|
||||
this._draw(cacheContext, target);
|
||||
|
||||
target.hasBorders = hasBorders;
|
||||
target.transparentCorners = transparentCorners;
|
||||
|
||||
// If tolerance is > 0 adjust start coords to take into account. If moves off Canvas fix to 0
|
||||
if (this.targetFindTolerance > 0) {
|
||||
if (x > this.targetFindTolerance) {
|
||||
x -= this.targetFindTolerance;
|
||||
}
|
||||
else {
|
||||
x = 0;
|
||||
}
|
||||
if (y > this.targetFindTolerance) {
|
||||
y -= this.targetFindTolerance;
|
||||
}
|
||||
else {
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var isTransparent = true;
|
||||
var imageData = cacheContext.getImageData(
|
||||
x, y, (this.targetFindTolerance * 2) || 1, (this.targetFindTolerance * 2) || 1);
|
||||
|
||||
// Split image data - for tolerance > 1, pixelDataSize = 4;
|
||||
for (var i = 3; i < imageData.data.length; i += 4) {
|
||||
var temp = imageData.data[i];
|
||||
isTransparent = temp <= 0;
|
||||
if (isTransparent === false) break; //Stop if colour found
|
||||
}
|
||||
|
||||
imageData = null;
|
||||
this.clearContext(cacheContext);
|
||||
return isTransparent;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _shouldClearSelection
|
||||
|
|
@ -6899,9 +6947,25 @@ fabric.util.string = {
|
|||
}
|
||||
|
||||
// then check all of the objects on canvas
|
||||
// Cache all targets where their bounding box contains point.
|
||||
var possibleTargets = [];
|
||||
for (var i = this._objects.length; i--; ) {
|
||||
if (this._objects[i] && this.containsPoint(e, this._objects[i])) {
|
||||
target = this._objects[i];
|
||||
if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) {
|
||||
possibleTargets[possibleTargets.length] = this._objects[i];
|
||||
}
|
||||
else {
|
||||
target = this._objects[i];
|
||||
this.relatedTarget = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0, len = possibleTargets.length; i < len; i++) {
|
||||
var pointer = this.getPointer(e);
|
||||
var isTransparent = this._isTargetTransparent(possibleTargets[i], pointer.x, pointer.y);
|
||||
if (!isTransparent) {
|
||||
target = possibleTargets[i];
|
||||
this.relatedTarget = target;
|
||||
break;
|
||||
}
|
||||
|
|
@ -6939,6 +7003,13 @@ fabric.util.string = {
|
|||
this.contextTop = this.upperCanvasEl.getContext('2d');
|
||||
},
|
||||
|
||||
_createCacheCanvas: function () {
|
||||
this.cacheCanvasEl = this._createCanvasElement();
|
||||
this.cacheCanvasEl.setAttribute('width', this.width);
|
||||
this.cacheCanvasEl.setAttribute('height', this.height);
|
||||
this.contextCache = this.cacheCanvasEl.getContext('2d');
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _initWrapperElement
|
||||
|
|
@ -7605,6 +7676,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
*/
|
||||
cornersize: 12,
|
||||
|
||||
/**
|
||||
* @property
|
||||
* @type Boolean
|
||||
*/
|
||||
transparentCorners: true,
|
||||
|
||||
/**
|
||||
* @property
|
||||
* @type Number
|
||||
|
|
@ -7718,9 +7795,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
* @property
|
||||
* @type Number
|
||||
*/
|
||||
_theta: 0,
|
||||
_theta: 0,
|
||||
|
||||
includeDefaultValues: true,
|
||||
perPixelTargetFind: false,
|
||||
|
||||
includeDefaultValues: true,
|
||||
|
||||
/**
|
||||
* List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged);
|
||||
|
|
@ -7824,7 +7903,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
selectable: this.selectable,
|
||||
hasControls: this.hasControls,
|
||||
hasBorders: this.hasBorders,
|
||||
hasRotatingPoint: this.hasRotatingPoint
|
||||
hasRotatingPoint: this.hasRotatingPoint,
|
||||
transparentCorners: this.transparentCorners,
|
||||
perPixelTargetFind: this.perPixelTargetFind
|
||||
};
|
||||
|
||||
if (!this.includeDefaultValues) {
|
||||
|
|
@ -8391,7 +8472,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
scaleOffsetSizeX = (size2 - size) / this.scaleX,
|
||||
scaleOffsetSizeY = (size2 - size) / this.scaleY,
|
||||
height = this.height,
|
||||
width = this.width;
|
||||
width = this.width,
|
||||
methodName = this.transparentCorners ? 'strokeRect' : 'fillRect';
|
||||
|
||||
ctx.save();
|
||||
|
||||
|
|
@ -8405,28 +8487,28 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// top-right
|
||||
_left = left + width - scaleOffsetX + strokeWidth2 + paddingX;
|
||||
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// bottom-left
|
||||
_left = left - scaleOffsetX - strokeWidth2 - paddingX;
|
||||
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// bottom-right
|
||||
_left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
|
||||
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
if (!this.lockUniScaling) {
|
||||
// middle-top
|
||||
|
|
@ -8434,28 +8516,28 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// middle-bottom
|
||||
_left = left + width/2 - scaleOffsetX;
|
||||
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// middle-right
|
||||
_left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
|
||||
_top = top + height/2 - scaleOffsetY;
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// middle-left
|
||||
_left = left - scaleOffsetX - strokeWidth2 - paddingX;
|
||||
_top = top + height/2 - scaleOffsetY;
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
}
|
||||
|
||||
// middle-top-rotate
|
||||
|
|
@ -8468,7 +8550,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
: (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY);
|
||||
|
||||
ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx.strokeRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
|
|
|||
4
dist/all.min.js
vendored
4
dist/all.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
dist/all.min.js.gz
vendored
BIN
dist/all.min.js.gz
vendored
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fabric",
|
||||
"description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.",
|
||||
"version": "0.9.12",
|
||||
"version": "0.9.13",
|
||||
"author": "Juriy Zaytsev <kangax@gmail.com>",
|
||||
"keywords": ["canvas", "graphic", "graphics", "SVG", "node-canvas", "parser", "HTML5", "object model"],
|
||||
"repository": "git://github.com/kangax/fabric.js",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
|
||||
this._initStatic(el, options);
|
||||
this._initInteractive();
|
||||
this._createCacheCanvas();
|
||||
|
||||
fabric.Canvas.activeInstance = this;
|
||||
};
|
||||
|
|
@ -133,6 +134,10 @@
|
|||
*/
|
||||
containerClass: 'canvas-container',
|
||||
|
||||
perPixelTargetFind: false,
|
||||
|
||||
targetFindTolerance: 0,
|
||||
|
||||
_initInteractive: function() {
|
||||
this._currentTransform = null;
|
||||
this._groupSelector = null;
|
||||
|
|
@ -521,6 +526,49 @@
|
|||
return { x: x, y: y };
|
||||
},
|
||||
|
||||
_isTargetTransparent: function (target, x, y) {
|
||||
var cacheContext = this.contextCache;
|
||||
|
||||
var hasBorders = target.hasBorders, transparentCorners = target.transparentCorners;
|
||||
target.hasBorders = target.transparentCorners = false;
|
||||
|
||||
this._draw(cacheContext, target);
|
||||
|
||||
target.hasBorders = hasBorders;
|
||||
target.transparentCorners = transparentCorners;
|
||||
|
||||
// If tolerance is > 0 adjust start coords to take into account. If moves off Canvas fix to 0
|
||||
if (this.targetFindTolerance > 0) {
|
||||
if (x > this.targetFindTolerance) {
|
||||
x -= this.targetFindTolerance;
|
||||
}
|
||||
else {
|
||||
x = 0;
|
||||
}
|
||||
if (y > this.targetFindTolerance) {
|
||||
y -= this.targetFindTolerance;
|
||||
}
|
||||
else {
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var isTransparent = true;
|
||||
var imageData = cacheContext.getImageData(
|
||||
x, y, (this.targetFindTolerance * 2) || 1, (this.targetFindTolerance * 2) || 1);
|
||||
|
||||
// Split image data - for tolerance > 1, pixelDataSize = 4;
|
||||
for (var i = 3; i < imageData.data.length; i += 4) {
|
||||
var temp = imageData.data[i];
|
||||
isTransparent = temp <= 0;
|
||||
if (isTransparent === false) break; //Stop if colour found
|
||||
}
|
||||
|
||||
imageData = null;
|
||||
this.clearContext(cacheContext);
|
||||
return isTransparent;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _shouldClearSelection
|
||||
|
|
@ -913,9 +961,25 @@
|
|||
}
|
||||
|
||||
// then check all of the objects on canvas
|
||||
// Cache all targets where their bounding box contains point.
|
||||
var possibleTargets = [];
|
||||
for (var i = this._objects.length; i--; ) {
|
||||
if (this._objects[i] && this.containsPoint(e, this._objects[i])) {
|
||||
target = this._objects[i];
|
||||
if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) {
|
||||
possibleTargets[possibleTargets.length] = this._objects[i];
|
||||
}
|
||||
else {
|
||||
target = this._objects[i];
|
||||
this.relatedTarget = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0, len = possibleTargets.length; i < len; i++) {
|
||||
var pointer = this.getPointer(e);
|
||||
var isTransparent = this._isTargetTransparent(possibleTargets[i], pointer.x, pointer.y);
|
||||
if (!isTransparent) {
|
||||
target = possibleTargets[i];
|
||||
this.relatedTarget = target;
|
||||
break;
|
||||
}
|
||||
|
|
@ -953,6 +1017,13 @@
|
|||
this.contextTop = this.upperCanvasEl.getContext('2d');
|
||||
},
|
||||
|
||||
_createCacheCanvas: function () {
|
||||
this.cacheCanvasEl = this._createCanvasElement();
|
||||
this.cacheCanvasEl.setAttribute('width', this.width);
|
||||
this.cacheCanvasEl.setAttribute('height', this.height);
|
||||
this.contextCache = this.cacheCanvasEl.getContext('2d');
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _initWrapperElement
|
||||
|
|
|
|||
|
|
@ -213,9 +213,11 @@
|
|||
* @property
|
||||
* @type Number
|
||||
*/
|
||||
_theta: 0,
|
||||
_theta: 0,
|
||||
|
||||
includeDefaultValues: true,
|
||||
perPixelTargetFind: false,
|
||||
|
||||
includeDefaultValues: true,
|
||||
|
||||
/**
|
||||
* List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged);
|
||||
|
|
@ -319,7 +321,9 @@
|
|||
selectable: this.selectable,
|
||||
hasControls: this.hasControls,
|
||||
hasBorders: this.hasBorders,
|
||||
hasRotatingPoint: this.hasRotatingPoint
|
||||
hasRotatingPoint: this.hasRotatingPoint,
|
||||
transparentCorners: this.transparentCorners,
|
||||
perPixelTargetFind: this.perPixelTargetFind
|
||||
};
|
||||
|
||||
if (!this.includeDefaultValues) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue