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.afterDrawFunc = undefined;
this.canvas = new Kinetic.Canvas(); this.canvas = new Kinetic.Canvas();
this.canvas.getElement().style.position = 'absolute'; this.canvas.getElement().style.position = 'absolute';
this.canvas.getContext().type = 'scene';
this.bufferCanvas = new Kinetic.Canvas(); this.bufferCanvas = new Kinetic.Canvas();
this.bufferCanvas.name = 'buffer'; this.bufferCanvas.getContext().type = 'buffer';
// call super constructor // call super constructor
Kinetic.Container.call(this, config); Kinetic.Container.call(this, config);
@ -266,7 +267,8 @@ Kinetic.Layer.prototype = {
*/ */
try { try {
this.getStage().content.removeChild(this.canvas.element); this.getStage().content.removeChild(this.canvas.element);
} catch(e) { }
catch(e) {
Kinetic.Global.warn('unable to remove layer scene canvas element from the document'); 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) { _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(); return this.getLayer().getCanvas();
}, },
/** /**
* helper method to stroke the shape and apply * helper method to stroke the shape
* shadows if needed
* @name stroke * @name stroke
* @methodOf Kinetic.Shape.prototype * @methodOf Kinetic.Shape.prototype
*/ */
stroke: function(context) { stroke: function(context) {
var strokeWidth = this.getStrokeWidth(); if(context.type === 'scene') {
var stroke = this.getStroke(); this._strokeScene(context);
}
else if(context.type === 'buffer') {
this._strokeBuffer(context);
}
},
_strokeScene: function(context) {
var strokeWidth = this.getStrokeWidth(), stroke = this.getStroke();
if(stroke || strokeWidth) { if(stroke || strokeWidth) {
var go = Kinetic.Global;
var appliedShadow = false; var appliedShadow = false;
context.save(); 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) { _getFillType: function(fill) {
if(!fill) { if(!fill) {
return undefined; return undefined;
@ -145,15 +160,20 @@ Kinetic.Shape.prototype = {
} }
}, },
/** /**
* helper method to fill the shape with a color, linear gradient, * helper method to fill the shape
* radial gradient, or pattern, and also apply shadows if needed
* @name fill * @name fill
* @methodOf Kinetic.Shape.prototype * @methodOf Kinetic.Shape.prototype
* */ * */
fill: function(context) { fill: function(context) {
var appliedShadow = false; if(context.type === 'scene') {
var fill = this.getFill(); this._fillScene(context);
var fillType = this._getFillType(fill); }
else if(context.type === 'buffer') {
this._fillBuffer(context);
}
},
_fillScene: function(context) {
var appliedShadow = false, fill = this.getFill(), fillType = this._getFillType(fill);
if(fill) { if(fill) {
context.save(); context.save();
if(this.attrs.shadow && !this.appliedShadow) { if(this.attrs.shadow && !this.appliedShadow) {
@ -216,54 +236,11 @@ Kinetic.Shape.prototype = {
this.fill(context); this.fill(context);
} }
}, },
/** _fillBuffer: function(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(); context.save();
if(this.attrs.shadow && !this.appliedShadow) { context.fillStyle = this.colorKey;
appliedShadow = this._applyShadow(context); context.fill(context);
}
context.fillStyle = this.attrs.textFill;
context.fillText(text, 0, 0);
context.restore(); 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);
}
}, },
/** /**
* helper method to draw an image and apply * 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]); context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
} }
var type = canvas.getContext().type;
if(type === 'scene') {
this.applyOpacity(context); this.applyOpacity(context);
}
this.applyLineJoin(context); this.applyLineJoin(context);
this.applyLineCap(context); this.applyLineCap(context);
// draw the shape // draw the shape
this.appliedShadow = false; this.appliedShadow = false;
var wl = Kinetic.Global.BUFFER_WHITELIST; var drawFunc = (type === 'buffer' && this.attrs.drawBufferFunc) ? this.attrs.drawBufferFunc : this.attrs.drawFunc;
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;
}
drawFunc.call(this, canvas.getContext());
context.restore(); context.restore();
} }
} }

View File

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

View File

@ -19,6 +19,7 @@ Kinetic.Image.prototype = {
_initImage: function(config) { _initImage: function(config) {
this.shapeType = "Image"; this.shapeType = "Image";
config.drawFunc = this.drawFunc; config.drawFunc = this.drawFunc;
config.drawBufferFunc = this.drawBufferFunc;
// call super constructor // call super constructor
Kinetic.Shape.call(this, config); Kinetic.Shape.call(this, config);
@ -30,8 +31,7 @@ Kinetic.Image.prototype = {
this._syncSize(); this._syncSize();
}, },
drawFunc: function(context) { drawFunc: function(context) {
var width = this.getWidth(); var width = this.getWidth(), height = this.getHeight();
var height = this.getHeight();
context.beginPath(); context.beginPath();
context.rect(0, 0, width, height); 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 * apply filter
* @name applyFilter * @name applyFilter
@ -78,7 +87,8 @@ Kinetic.Image.prototype = {
config.callback(); config.callback();
} }
}); });
} catch(e) { }
catch(e) {
Kinetic.Global.warn('Unable to apply filter. ' + e.message); Kinetic.Global.warn('Unable to apply filter. ' + e.message);
} }
}, },
@ -130,7 +140,8 @@ Kinetic.Image.prototype = {
callback(); callback();
} }
}); });
} catch(e) { }
catch(e) {
Kinetic.Global.warn('Unable to create image buffer.'); Kinetic.Global.warn('Unable to create image buffer.');
} }
}, },

View File

@ -19,6 +19,7 @@ Kinetic.Sprite.prototype = {
}); });
this.shapeType = "Sprite"; this.shapeType = "Sprite";
config.drawFunc = this.drawFunc; config.drawFunc = this.drawFunc;
config.drawBufferFunc = this.drawBufferFunc;
// call super constructor // call super constructor
Kinetic.Shape.call(this, config); Kinetic.Shape.call(this, config);
this.anim = new Kinetic.Animation(); 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); 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 * start sprite animation
* @name start * @name start

View File

@ -166,6 +166,91 @@ Kinetic.Text.prototype = {
height: parseInt(this.attrs.fontSize, 10) 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 * set text data. wrap logic and width and height setting occurs
* here * here

View File

@ -306,6 +306,13 @@ Kinetic.Global.extend(Kinetic.TextPath, Kinetic.Shape);
Kinetic.Node.addGettersSetters(Kinetic.TextPath, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth']); Kinetic.Node.addGettersSetters(Kinetic.TextPath, ['fontFamily', 'fontSize', 'fontStyle', 'textFill', 'textStroke', 'textStrokeWidth']);
Kinetic.Node.addGetters(Kinetic.TextPath, ['text']); 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 * set font family
* @name setFontFamily * @name setFontFamily

View File

@ -327,6 +327,8 @@ Test.prototype.tests = {
layer.add(darth); layer.add(darth);
stage.add(layer); stage.add(layer);
//document.body.appendChild(layer.bufferCanvas.element)
}; };
imageObj.src = '../assets/darth-vader.jpg'; imageObj.src = '../assets/darth-vader.jpg';
}, },
@ -713,7 +715,7 @@ Test.prototype.tests = {
layer.add(redCircle); layer.add(redCircle);
stage.add(layer); 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({ var stage = new Kinetic.Stage({
container: containerId, container: containerId,
width: 578, width: 578,

View File

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