Skip to content

Commit

Permalink
finished pixel detection algo which can be applied to any Shape. This…
Browse files Browse the repository at this point in the history
… enables a lot of new functionality, such as image pixel detection, shape border detection, and custom shape detection that isn't drawn with a standard path
  • Loading branch information
ericdrowell committed Apr 1, 2012
1 parent 08e2b74 commit 95fabe9
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 17 deletions.
43 changes: 35 additions & 8 deletions dist/kinetic-core.js
Expand Up @@ -1280,7 +1280,7 @@ Kinetic.Stage.prototype = {
this.targetFound = true;
}

if(shape.visible && pos !== undefined && shape._isPointInPath(pos)) {
if(shape.visible && pos !== undefined && shape._isPointInShape(pos)) {
// handle onmousedown
if(!isDragging && this.mouseDown) {
this.mouseDown = false;
Expand Down Expand Up @@ -1482,8 +1482,8 @@ Kinetic.Stage.prototype = {
* clear default layers
*/
_clearDefaultLayers: function() {
var pathLayer = this.pathLayer;
pathLayer.clear();
this.bufferLayer.clear();
this.pathLayer.clear();
},
/**
* begin listening for events by adding event handlers
Expand Down Expand Up @@ -1902,6 +1902,8 @@ Kinetic.GlobalObject.extend(Kinetic.Group, Kinetic.Node);
* @config {Number} [strokeWidth] stroke width
* @config {String} [lineJoin] line join. Can be "miter", "round", or "bevel". The default
* is "miter"
* @config {String} [detectionType] shape detection type. Can be "path" or "pixel".
* The default is "path" because it performs better
*/
Kinetic.Shape = function(config) {
this.className = 'Shape';
Expand All @@ -1916,6 +1918,10 @@ Kinetic.Shape = function(config) {
}
}

if(config.detectionType === undefined) {
config.detectionType = 'path';
}

// required
this.drawFunc = config.drawFunc;

Expand Down Expand Up @@ -2066,12 +2072,33 @@ Kinetic.Shape.prototype = {
* custom isPointInPath method which can use path detection
* or pixel detection
*/
_isPointInPath: function(pos) {
_isPointInShape: function(pos) {
var stage = this.getStage();
var pathLayer = stage.pathLayer;
var pathLayerContext = pathLayer.getContext();
this._draw(pathLayer);
return pathLayerContext.isPointInPath(pos.x, pos.y);

if(this.detectionType === 'path') {
var pathLayer = stage.pathLayer;
var pathLayerContext = pathLayer.getContext();

this._draw(pathLayer);

return pathLayerContext.isPointInPath(pos.x, pos.y);
}
else {
var bufferLayer = stage.bufferLayer;
var bufferLayerContext = bufferLayer.getContext();

this._draw(bufferLayer);

var w = stage.width;
var h = stage.height;
var x = pos.x;
var y = pos.y;
var imageData = bufferLayerContext.getImageData(0, 0, w, h);
var data = imageData.data;
var alpha = data[((w * y) + x) * 4 + 3];

return (alpha !== undefined && alpha !== 0);
}
}
};
// extend Node
Expand Down
2 changes: 1 addition & 1 deletion dist/kinetic-core.min.js

Large diffs are not rendered by default.

37 changes: 32 additions & 5 deletions src/Shape.js
Expand Up @@ -12,6 +12,8 @@
* @config {Number} [strokeWidth] stroke width
* @config {String} [lineJoin] line join. Can be "miter", "round", or "bevel". The default
* is "miter"
* @config {String} [detectionType] shape detection type. Can be "path" or "pixel".
* The default is "path" because it performs better
*/
Kinetic.Shape = function(config) {
this.className = 'Shape';
Expand All @@ -26,6 +28,10 @@ Kinetic.Shape = function(config) {
}
}

if(config.detectionType === undefined) {
config.detectionType = 'path';
}

// required
this.drawFunc = config.drawFunc;

Expand Down Expand Up @@ -176,12 +182,33 @@ Kinetic.Shape.prototype = {
* custom isPointInPath method which can use path detection
* or pixel detection
*/
_isPointInPath: function(pos) {
_isPointInShape: function(pos) {
var stage = this.getStage();
var pathLayer = stage.pathLayer;
var pathLayerContext = pathLayer.getContext();
this._draw(pathLayer);
return pathLayerContext.isPointInPath(pos.x, pos.y);

if(this.detectionType === 'path') {
var pathLayer = stage.pathLayer;
var pathLayerContext = pathLayer.getContext();

this._draw(pathLayer);

return pathLayerContext.isPointInPath(pos.x, pos.y);
}
else {
var bufferLayer = stage.bufferLayer;
var bufferLayerContext = bufferLayer.getContext();

this._draw(bufferLayer);

var w = stage.width;
var h = stage.height;
var x = pos.x;
var y = pos.y;
var imageData = bufferLayerContext.getImageData(0, 0, w, h);
var data = imageData.data;
var alpha = data[((w * y) + x) * 4 + 3];

return (alpha !== undefined && alpha !== 0);
}
}
};
// extend Node
Expand Down
6 changes: 3 additions & 3 deletions src/Stage.js
Expand Up @@ -254,7 +254,7 @@ Kinetic.Stage.prototype = {
this.targetFound = true;
}

if(shape.visible && pos !== undefined && shape._isPointInPath(pos)) {
if(shape.visible && pos !== undefined && shape._isPointInShape(pos)) {
// handle onmousedown
if(!isDragging && this.mouseDown) {
this.mouseDown = false;
Expand Down Expand Up @@ -456,8 +456,8 @@ Kinetic.Stage.prototype = {
* clear default layers
*/
_clearDefaultLayers: function() {
var pathLayer = this.pathLayer;
pathLayer.clear();
this.bufferLayer.clear();
this.pathLayer.clear();
},
/**
* begin listening for events by adding event handlers
Expand Down
69 changes: 69 additions & 0 deletions tests/js/functionalTests.js
Expand Up @@ -519,6 +519,75 @@ Test.prototype.tests = {
};
imageObj.src = '../darth-vader.jpg';
},
/*
* WARNING: this functional test will only pass if it's hosted on
* a webserver due to cross domain security issues
*/
'EVENTS - image pixel detection': function(containerId) {
var imageObj = new Image();
imageObj.onload = function() {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var darth = new Kinetic.Image({
x: 200,
y: 40,
image: imageObj,
detectionType: 'pixel',
draggable: true
});

darth.on('mouseover', function() {
log('mouseover');
});

darth.on('mouseout', function() {
log('mouseout');
});

layer.add(darth);
stage.add(layer);
};
imageObj.src = '../lion.png';
},
'EVENTS - star pixel detection': function(containerId) {
var imageObj = new Image();
imageObj.onload = function() {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var star = new Kinetic.Star({
x: 200,
y: 100,
points: 10,
innerRadius: 40,
outerRadius: 70,
fill: 'green',
stroke: 'blue',
strokeWidth: 20,
detectionType: 'pixel',
draggable: true
});

star.on('mouseover', function() {
log('mouseover');
});

star.on('mouseout', function() {
log('mouseout');
});

layer.add(star);
stage.add(layer);
};
imageObj.src = '../lion.png';
},
'EVENTS - drag events click': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,
Expand Down
Binary file added tests/lion.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

1 comment on commit 95fabe9

@ericdrowell
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, a security error will be thrown if a developer uses the "pixel" detectionType on images not from on their domain. They will need to do defensive coding on their end because KineticJS won't know where the images are coming from. I don't want to use try/catch because the devs need to know when a problem occurs

Please sign in to comment.