fix #149 began decoupling scene graph draw and buffer graph draw logic. This will enable more flexibility for developers to define custom buffer draw functions, and it also improves draw performance for both the scene and buffer graphs, because each function can be optimized for its purpose. Also moved text drawing logic to the Text shape

This commit is contained in:
Eric Rowell 2012-11-12 19:59:19 -08:00
parent a30d6730fe
commit 5be1802729
10 changed files with 187 additions and 125 deletions

View File

@ -39,8 +39,9 @@ Kinetic.Layer.prototype = {
this.afterDrawFunc = undefined;
this.canvas = new Kinetic.Canvas();
this.canvas.getElement().style.position = 'absolute';
this.canvas.getContext().type = 'scene';
this.bufferCanvas = new Kinetic.Canvas();
this.bufferCanvas.name = 'buffer';
this.bufferCanvas.getContext().type = 'buffer';
// call super constructor
Kinetic.Container.call(this, config);
@ -266,7 +267,8 @@ Kinetic.Layer.prototype = {
*/
try {
this.getStage().content.removeChild(this.canvas.element);
} catch(e) {
}
catch(e) {
Kinetic.Global.warn('unable to remove layer scene canvas element from the document');
}
}

View File

@ -946,7 +946,7 @@ Kinetic.Node.prototype = {
}
},
_shouldDraw: function(canvas) {
return (this.isVisible() && (!canvas || canvas.name !== 'buffer' || this.getListening()));
return (this.isVisible() && (!canvas || canvas.getContext().type === 'scene' || this.getListening()));
}
};

View File

@ -97,16 +97,21 @@ Kinetic.Shape.prototype = {
return this.getLayer().getCanvas();
},
/**
* helper method to stroke the shape and apply
* shadows if needed
* helper method to stroke the shape
* @name stroke
* @methodOf Kinetic.Shape.prototype
*/
stroke: function(context) {
var strokeWidth = this.getStrokeWidth();
var stroke = this.getStroke();
if(context.type === 'scene') {
this._strokeScene(context);
}
else if(context.type === 'buffer') {
this._strokeBuffer(context);
}
},
_strokeScene: function(context) {
var strokeWidth = this.getStrokeWidth(), stroke = this.getStroke();
if(stroke || strokeWidth) {
var go = Kinetic.Global;
var appliedShadow = false;
context.save();
@ -124,6 +129,16 @@ Kinetic.Shape.prototype = {
}
}
},
_strokeBuffer: function(context) {
var strokeWidth = this.getStrokeWidth(), stroke = this.colorKey;
if(stroke || strokeWidth) {
context.save();
context.lineWidth = strokeWidth || 2;
context.strokeStyle = stroke || 'black';
context.stroke(context);
context.restore();
}
},
_getFillType: function(fill) {
if(!fill) {
return undefined;
@ -145,15 +160,20 @@ Kinetic.Shape.prototype = {
}
},
/**
* helper method to fill the shape with a color, linear gradient,
* radial gradient, or pattern, and also apply shadows if needed
* helper method to fill the shape
* @name fill
* @methodOf Kinetic.Shape.prototype
* */
fill: function(context) {
var appliedShadow = false;
var fill = this.getFill();
var fillType = this._getFillType(fill);
if(context.type === 'scene') {
this._fillScene(context);
}
else if(context.type === 'buffer') {
this._fillBuffer(context);
}
},
_fillScene: function(context) {
var appliedShadow = false, fill = this.getFill(), fillType = this._getFillType(fill);
if(fill) {
context.save();
if(this.attrs.shadow && !this.appliedShadow) {
@ -216,54 +236,11 @@ Kinetic.Shape.prototype = {
this.fill(context);
}
},
/**
* helper method to fill text and appy shadows if needed
* @param {String} text
* @name fillText
* @methodOf Kinetic.Shape.prototype
*/
fillText: function(context, text) {
var appliedShadow = false;
if(this.attrs.textFill) {
context.save();
if(this.attrs.shadow && !this.appliedShadow) {
appliedShadow = this._applyShadow(context);
}
context.fillStyle = this.attrs.textFill;
context.fillText(text, 0, 0);
context.restore();
}
if(appliedShadow) {
this.fillText(context, text, 0, 0);
}
},
/**
* helper method to stroke text and apply shadows
* if needed
* @name strokeText
* @methodOf Kinetic.Shape.prototype
* @param {String} text
*/
strokeText: function(context, text) {
var appliedShadow = false;
if(this.attrs.textStroke || this.attrs.textStrokeWidth) {
context.save();
if(this.attrs.shadow && !this.appliedShadow) {
appliedShadow = this._applyShadow(context);
}
// defaults
var textStroke = this.attrs.textStroke ? this.attrs.textStroke : 'black';
var textStrokeWidth = this.attrs.textStrokeWidth ? this.attrs.textStrokeWidth : 2;
context.lineWidth = textStrokeWidth;
context.strokeStyle = textStroke;
context.strokeText(text, 0, 0);
context.restore();
}
if(appliedShadow) {
this.strokeText(context, text, 0, 0);
}
_fillBuffer: function(context) {
context.save();
context.fillStyle = this.colorKey;
context.fill(context);
context.restore();
},
/**
* helper method to draw an image and apply
@ -464,61 +441,21 @@ Kinetic.Shape.prototype = {
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
this.applyOpacity(context);
var type = canvas.getContext().type;
if(type === 'scene') {
this.applyOpacity(context);
}
this.applyLineJoin(context);
this.applyLineCap(context);
// draw the shape
this.appliedShadow = false;
var wl = Kinetic.Global.BUFFER_WHITELIST;
var bl = Kinetic.Global.BUFFER_BLACKLIST;
var attrs = {};
if(canvas.name === 'buffer') {
for(var n = 0; n < wl.length; n++) {
var key = wl[n];
attrs[key] = this.attrs[key];
if(this.attrs[key] || (key === 'fill' && !this.attrs.stroke && !('image' in this.attrs))) {
this.attrs[key] = '#' + this.colorKey;
}
}
for(var n = 0; n < bl.length; n++) {
var key = bl[n];
attrs[key] = this.attrs[key];
this.attrs[key] = '';
}
// image is a special case
if('image' in this.attrs) {
attrs.image = this.attrs.image;
if(this.imageBuffer) {
this.attrs.image = this.imageBuffer;
}
else {
this.attrs.image = null;
this.attrs.fill = '#' + this.colorKey;
}
}
context.globalAlpha = 1;
}
this.attrs.drawFunc.call(this, canvas.getContext());
if(canvas.name === 'buffer') {
var bothLists = wl.concat(bl);
for(var n = 0; n < bothLists.length; n++) {
var key = bothLists[n];
this.attrs[key] = attrs[key];
}
// image is a special case
this.attrs.image = attrs.image;
}
var drawFunc = (type === 'buffer' && this.attrs.drawBufferFunc) ? this.attrs.drawBufferFunc : this.attrs.drawFunc;
drawFunc.call(this, canvas.getContext());
context.restore();
}
}

View File

@ -284,6 +284,7 @@ Kinetic.Stage.prototype = {
this.content.style.width = width + 'px';
this.content.style.height = height + 'px';
this.canvas.setSize(width, height);
this.bufferCanvas.setSize(width, height);
// set user defined layer dimensions
var layers = this.children;
@ -561,7 +562,10 @@ Kinetic.Stage.prototype = {
this.content.className = 'kineticjs-content';
this.attrs.container.appendChild(this.content);
this.canvas = new Kinetic.Canvas();
this.canvas.getContext().type = 'scene';
this.bufferCanvas = new Kinetic.Canvas();
this.bufferCanvas.getContext().type = 'buffer';
this._resizeDOM();
},

View File

@ -19,6 +19,7 @@ Kinetic.Image.prototype = {
_initImage: function(config) {
this.shapeType = "Image";
config.drawFunc = this.drawFunc;
config.drawBufferFunc = this.drawBufferFunc;
// call super constructor
Kinetic.Shape.call(this, config);
@ -30,8 +31,7 @@ Kinetic.Image.prototype = {
this._syncSize();
},
drawFunc: function(context) {
var width = this.getWidth();
var height = this.getHeight();
var width = this.getWidth(), height = this.getHeight();
context.beginPath();
context.rect(0, 0, width, height);
@ -54,6 +54,15 @@ Kinetic.Image.prototype = {
}
}
},
drawBufferFunc: function(context) {
var width = this.getWidth(), height = this.getHeight();
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
this.fill(context);
this.stroke(context);
},
/**
* apply filter
* @name applyFilter
@ -78,7 +87,8 @@ Kinetic.Image.prototype = {
config.callback();
}
});
} catch(e) {
}
catch(e) {
Kinetic.Global.warn('Unable to apply filter. ' + e.message);
}
},
@ -93,7 +103,7 @@ Kinetic.Image.prototype = {
* @param {Number} config.height
*/
setCrop: function() {
var config = [].slice.call(arguments);
var config = [].slice.call(arguments);
var pos = Kinetic.Type._getXY(config);
var size = Kinetic.Type._getSize(config);
var both = Kinetic.Type._merge(pos, size);
@ -130,7 +140,8 @@ Kinetic.Image.prototype = {
callback();
}
});
} catch(e) {
}
catch(e) {
Kinetic.Global.warn('Unable to create image buffer.');
}
},

View File

@ -19,6 +19,7 @@ Kinetic.Sprite.prototype = {
});
this.shapeType = "Sprite";
config.drawFunc = this.drawFunc;
config.drawBufferFunc = this.drawBufferFunc;
// call super constructor
Kinetic.Shape.call(this, config);
this.anim = new Kinetic.Animation();
@ -48,6 +49,17 @@ Kinetic.Sprite.prototype = {
this.drawImage(context, this.attrs.image, f.x, f.y, f.width, f.height, 0, 0, f.width, f.height);
}
},
drawBufferFunc: function(context) {
var anim = this.attrs.animation;
var index = this.attrs.index;
var f = this.attrs.animations[anim][index];
context.beginPath();
context.rect(0, 0, f.width, f.height);
context.closePath();
this.fill(context);
this.stroke(context);
},
/**
* start sprite animation
* @name start

View File

@ -166,6 +166,91 @@ Kinetic.Text.prototype = {
height: parseInt(this.attrs.fontSize, 10)
};
},
/**
* helper method to fill text
* @param {String} text
* @name fillText
* @methodOf Kinetic.Text.prototype
*/
fillText: function(context, text) {
if(context.type === 'scene') {
this._fillTextScene(context, text);
}
else if(context.type === 'buffer') {
this._fillTextBuffer(context, text);
}
},
_fillTextScene: function(context, text) {
var appliedShadow = false;
if(this.attrs.textFill) {
context.save();
if(this.attrs.shadow && !this.appliedShadow) {
appliedShadow = this._applyShadow(context);
}
context.fillStyle = this.attrs.textFill;
context.fillText(text, 0, 0);
context.restore();
}
if(appliedShadow) {
this.fillText(context, text, 0, 0);
}
},
_fillTextBuffer: function(context, text) {
if(this.attrs.textFill) {
context.save();
context.fillStyle = this.colorKey;
context.fillText(text, 0, 0);
context.restore();
}
},
/**
* helper method to stroke text
* if needed
* @name strokeText
* @methodOf Kinetic.Shape.prototype
* @param {String} text
*/
strokeText: function(context, text) {
if(context.type === 'scene') {
this._strokeTextScene(context, text);
}
else if(context.type === 'buffer') {
this._strokeTextBuffer(context, text);
}
},
_strokeTextScene: function(context, text) {
var appliedShadow = false;
if(this.attrs.textStroke || this.attrs.textStrokeWidth) {
context.save();
if(this.attrs.shadow && !this.appliedShadow) {
appliedShadow = this._applyShadow(context);
}
// defaults
var textStroke = this.attrs.textStroke ? this.attrs.textStroke : 'black';
var textStrokeWidth = this.attrs.textStrokeWidth ? this.attrs.textStrokeWidth : 2;
context.lineWidth = textStrokeWidth;
context.strokeStyle = textStroke;
context.strokeText(text, 0, 0);
context.restore();
}
if(appliedShadow) {
this.strokeText(context, text, 0, 0);
}
},
_strokeTextBuffer: function(context, text) {
if(this.attrs.textStroke || this.attrs.textStrokeWidth) {
context.save();
// defaults
var textStroke = this.colorKey ? this.colorKey : 'black';
var textStrokeWidth = this.attrs.textStrokeWidth ? this.attrs.textStrokeWidth : 2;
context.lineWidth = textStrokeWidth;
context.strokeStyle = textStroke;
context.strokeText(text, 0, 0);
context.restore();
}
},
/**
* set text data. wrap logic and width and height setting occurs
* here

View File

@ -106,14 +106,14 @@ Kinetic.TextPath.prototype = {
return this.textHeight;
},
/**
* set text
* @name setText
* @methodOf Kinetic.TextPath.prototype
* @param {String} text
*/
setText: function(text) {
Kinetic.Text.prototype.setText.call(this, text);
},
* set text
* @name setText
* @methodOf Kinetic.TextPath.prototype
* @param {String} text
*/
setText: function(text) {
Kinetic.Text.prototype.setText.call(this, text);
},
_getTextSize: function(text) {
var dummyCanvas = this.dummyCanvas;
var context = dummyCanvas.getContext('2d');
@ -306,6 +306,13 @@ Kinetic.Global.extend(Kinetic.TextPath, Kinetic.Shape);
Kinetic.Node.addGettersSetters(Kinetic.TextPath, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth']);
Kinetic.Node.addGetters(Kinetic.TextPath, ['text']);
// create reference to Text methods
Kinetic.TextPath.prototype.fillText = Kinetic.Text.prototype.fillText;
Kinetic.TextPath.prototype._fillTextScene = Kinetic.Text.prototype._fillTextScene;
Kinetic.TextPath.prototype._fillTextBuffer = Kinetic.Text.prototype._fillTextBuffer;
Kinetic.TextPath.prototype.strokeText = Kinetic.Text.prototype.strokeText;
Kinetic.TextPath.prototype._strokeTextScene = Kinetic.Text.prototype._strokeTextScene;
Kinetic.TextPath.prototype._strokeTextBuffer = Kinetic.Text.prototype._strokeTextBuffer;
/**
* set font family
* @name setFontFamily

View File

@ -327,6 +327,8 @@ Test.prototype.tests = {
layer.add(darth);
stage.add(layer);
//document.body.appendChild(layer.bufferCanvas.element)
};
imageObj.src = '../assets/darth-vader.jpg';
},
@ -713,7 +715,7 @@ Test.prototype.tests = {
layer.add(redCircle);
stage.add(layer);
},
'*DRAG AND DROP - drag and drop elastic star with shadow': function(containerId) {
'DRAG AND DROP - drag and drop elastic star with shadow': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,

View File

@ -3067,6 +3067,8 @@ Test.prototype.tests = {
//layer.setListening(false);
layer.drawBuffer();
},
'SHAPE - test size setters and getters': function(containerId) {
var stage = new Kinetic.Stage({