mirror of
https://github.com/konvajs/konva.git
synced 2026-01-09 20:14:41 +08:00
265 lines
9.1 KiB
JavaScript
265 lines
9.1 KiB
JavaScript
(function() {
|
|
// constants
|
|
var HASH = '#',
|
|
BEFORE_DRAW ='beforeDraw',
|
|
DRAW = 'draw',
|
|
|
|
/*
|
|
* 2 - 3 - 4
|
|
* | |
|
|
* 1 - 0 5
|
|
* |
|
|
* 8 - 7 - 6
|
|
*/
|
|
INTERSECTION_OFFSETS = [
|
|
{x: 0, y: 0}, // 0
|
|
{x: -1, y: 0}, // 1
|
|
{x: -1, y: -1}, // 2
|
|
{x: 0, y: -1}, // 3
|
|
{x: 1, y: -1}, // 4
|
|
{x: 1, y: 0}, // 5
|
|
{x: 1, y: 1}, // 6
|
|
{x: 0, y: 1}, // 7
|
|
{x: -1, y: 1} // 8
|
|
],
|
|
INTERSECTION_OFFSETS_LEN = INTERSECTION_OFFSETS.length;
|
|
|
|
|
|
Kinetic.Util.addMethods(Kinetic.Layer, {
|
|
____init: function(config) {
|
|
this.nodeType = 'Layer';
|
|
this.canvas = new Kinetic.SceneCanvas();
|
|
this.hitCanvas = new Kinetic.HitCanvas();
|
|
// call super constructor
|
|
Kinetic.BaseLayer.call(this, config);
|
|
},
|
|
_setCanvasSize: function(width, height) {
|
|
this.canvas.setSize(width, height);
|
|
this.hitCanvas.setSize(width, height);
|
|
},
|
|
_validateAdd: function(child) {
|
|
var type = child.getType();
|
|
if (type !== 'Group' && type !== 'Shape') {
|
|
Kinetic.Util.error('You may only add groups and shapes to a layer.');
|
|
}
|
|
},
|
|
/**
|
|
* get visible intersection shape. This is the preferred
|
|
* method for determining if a point intersects a shape or not
|
|
* @method
|
|
* @memberof Kinetic.Layer.prototype
|
|
* @param {Object} pos
|
|
* @param {Number} pos.x
|
|
* @param {Number} pos.y
|
|
* @returns {Kinetic.Shape}
|
|
*/
|
|
getIntersection: function(pos) {
|
|
var obj, i, intersectionOffset, shape;
|
|
|
|
if(this.hitGraphEnabled() && this.isVisible()) {
|
|
// in some cases antialiased area may be bigger than 1px
|
|
// it is possible if we will cache node, then scale it a lot
|
|
// TODO: check { 0; 0 } point before loop, and remove it from INTERSECTION_OFFSETS.
|
|
var spiralSearchDistance = 1;
|
|
var continueSearch = false;
|
|
while (true) {
|
|
for (i=0; i<INTERSECTION_OFFSETS_LEN; i++) {
|
|
intersectionOffset = INTERSECTION_OFFSETS[i];
|
|
obj = this._getIntersection({
|
|
x: pos.x + intersectionOffset.x * spiralSearchDistance,
|
|
y: pos.y + intersectionOffset.y * spiralSearchDistance
|
|
});
|
|
shape = obj.shape;
|
|
if (shape) {
|
|
return shape;
|
|
}
|
|
// we should continue search if we found antialiased pixel
|
|
// that means our node somewhere very close
|
|
else if (obj.antialiased) {
|
|
continueSearch = true;
|
|
}
|
|
}
|
|
// if no shape, and no antialiased pixel, we should end searching
|
|
if (!continueSearch) {
|
|
return;
|
|
} else {
|
|
spiralSearchDistance += 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
},
|
|
/**
|
|
* Get ImageData.data array for an individual pixel on the hit canvas.
|
|
* Data is cached as getImageData is an expensive method to call often.
|
|
*
|
|
* @param {Number} x
|
|
* @param {Number} x
|
|
* @returns {Array} One-dimensional array containing the data in the RGBA order, with integer values between 0 and 255
|
|
*/
|
|
_getImageData: function(x, y) {
|
|
var width = this.hitCanvas.width || 1,
|
|
height = this.hitCanvas.height || 1,
|
|
index = (y * width ) + x;
|
|
|
|
if (!this.imageData) {
|
|
this.imageData = this.hitCanvas.context._context.getImageData(0, 0, width, height);
|
|
}
|
|
|
|
return [
|
|
this.imageData.data[4 * index + 0] , // Red
|
|
this.imageData.data[4 * index + 1], // Green
|
|
this.imageData.data[4 * index + 2], // Blue
|
|
this.imageData.data[4 * index + 3] // Alpha
|
|
];
|
|
},
|
|
_getIntersection: function(pos) {
|
|
var p = this._getImageData(pos.x, pos.y),
|
|
p3 = p[3],
|
|
colorKey, shape;
|
|
|
|
// fully opaque pixel
|
|
if(p3 === 255) {
|
|
colorKey = Kinetic.Util._rgbToHex(p[0], p[1], p[2]);
|
|
shape = Kinetic.shapes[HASH + colorKey];
|
|
return {
|
|
shape: shape
|
|
};
|
|
}
|
|
// antialiased pixel
|
|
else if(p3 > 0) {
|
|
return {
|
|
antialiased: true
|
|
};
|
|
}
|
|
// empty pixel
|
|
else {
|
|
return {};
|
|
}
|
|
},
|
|
drawScene: function(can, top) {
|
|
var layer = this.getLayer(),
|
|
canvas = can || (layer && layer.getCanvas());
|
|
|
|
this._fire(BEFORE_DRAW, {
|
|
node: this
|
|
});
|
|
|
|
if(this.getClearBeforeDraw()) {
|
|
canvas.getContext().clear();
|
|
}
|
|
|
|
Kinetic.Container.prototype.drawScene.call(this, canvas, top);
|
|
|
|
this._fire(DRAW, {
|
|
node: this
|
|
});
|
|
|
|
return this;
|
|
},
|
|
// the apply transform method is handled by the Layer and FastLayer class
|
|
// because it is up to the layer to decide if an absolute or relative transform
|
|
// should be used
|
|
_applyTransform: function(shape, context, top) {
|
|
var m = shape.getAbsoluteTransform(top).getMatrix();
|
|
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
},
|
|
drawHit: function(can, top) {
|
|
var layer = this.getLayer(),
|
|
canvas = can || (layer && layer.hitCanvas);
|
|
|
|
if(layer && layer.getClearBeforeDraw()) {
|
|
layer.getHitCanvas().getContext().clear();
|
|
}
|
|
|
|
Kinetic.Container.prototype.drawHit.call(this, canvas, top);
|
|
return this;
|
|
},
|
|
/**
|
|
* clear scene and hit canvas contexts tied to the layer
|
|
* @method
|
|
* @memberof Kinetic.Layer.prototype
|
|
* @param {Object} [bounds]
|
|
* @param {Number} [bounds.x]
|
|
* @param {Number} [bounds.y]
|
|
* @param {Number} [bounds.width]
|
|
* @param {Number} [bounds.height]
|
|
* @example
|
|
* layer.clear();
|
|
* layer.clear(0, 0, 100, 100);
|
|
*/
|
|
clear: function(bounds) {
|
|
this.getContext().clear(bounds);
|
|
this.getHitCanvas().getContext().clear(bounds);
|
|
this.imageData = null; // Clear getImageData cache
|
|
return this;
|
|
},
|
|
// extend Node.prototype.setVisible
|
|
setVisible: function(visible) {
|
|
Kinetic.Node.prototype.setVisible.call(this, visible);
|
|
if(visible) {
|
|
this.getCanvas()._canvas.style.display = 'block';
|
|
this.hitCanvas._canvas.style.display = 'block';
|
|
}
|
|
else {
|
|
this.getCanvas()._canvas.style.display = 'none';
|
|
this.hitCanvas._canvas.style.display = 'none';
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* enable hit graph
|
|
* @name enableHitGraph
|
|
* @method
|
|
* @memberof Kinetic.Layer.prototype
|
|
* @returns {Node}
|
|
*/
|
|
enableHitGraph: function() {
|
|
this.setHitGraphEnabled(true);
|
|
return this;
|
|
},
|
|
/**
|
|
* disable hit graph
|
|
* @name disableHitGraph
|
|
* @method
|
|
* @memberof Kinetic.Layer.prototype
|
|
* @returns {Node}
|
|
*/
|
|
disableHitGraph: function() {
|
|
this.setHitGraphEnabled(false);
|
|
return this;
|
|
},
|
|
setSize : function(width, height) {
|
|
Kinetic.BaseLayer.prototype.setSize.call(this, width, height);
|
|
this.hitCanvas.setSize(width, height);
|
|
}
|
|
});
|
|
Kinetic.Util.extend(Kinetic.Layer, Kinetic.BaseLayer);
|
|
|
|
Kinetic.Factory.addGetterSetter(Kinetic.Layer, 'hitGraphEnabled', true);
|
|
/**
|
|
* get/set hitGraphEnabled flag. Disabling the hit graph will greatly increase
|
|
* draw performance because the hit graph will not be redrawn each time the layer is
|
|
* drawn. This, however, also disables mouse/touch event detection
|
|
* @name hitGraphEnabled
|
|
* @method
|
|
* @memberof Kinetic.Layer.prototype
|
|
* @param {Boolean} enabled
|
|
* @returns {Boolean}
|
|
* @example
|
|
* // get hitGraphEnabled flag
|
|
* var hitGraphEnabled = layer.hitGraphEnabled();
|
|
*
|
|
* // disable hit graph
|
|
* layer.hitGraphEnabled(false);
|
|
*
|
|
* // enable hit graph
|
|
* layer.hitGraphEnabled(true);
|
|
*/
|
|
|
|
Kinetic.Collection.mapMethods(Kinetic.Layer);
|
|
})();
|