added matrix transform caching, and optimized the clear() method. This has improved rendering performance by about 17%

This commit is contained in:
Eric Rowell
2013-04-21 22:42:25 -07:00
parent 90a2820e5b
commit 8a78b62cad
5 changed files with 89 additions and 37 deletions

View File

@@ -29,8 +29,6 @@
contextType = config.contextType || '2d'; contextType = config.contextType || '2d';
this.pixelRatio = pixelRatio; this.pixelRatio = pixelRatio;
this.width = width;
this.height = height;
this.element = document.createElement('canvas'); this.element = document.createElement('canvas');
this.element.style.padding = 0; this.element.style.padding = 0;
this.element.style.margin = 0; this.element.style.margin = 0;
@@ -62,9 +60,8 @@
* @param {Number} width * @param {Number} width
*/ */
setWidth: function(width) { setWidth: function(width) {
this.width = width;
// take into account pixel ratio // take into account pixel ratio
this.element.width = width * this.pixelRatio; this.width = this.element.width = width * this.pixelRatio;
this.element.style.width = width + 'px'; this.element.style.width = width + 'px';
}, },
/** /**
@@ -74,9 +71,8 @@
* @param {Number} height * @param {Number} height
*/ */
setHeight: function(height) { setHeight: function(height) {
this.height = height;
// take into account pixel ratio // take into account pixel ratio
this.element.height = height * this.pixelRatio; this.height = this.element.height = height * this.pixelRatio;
this.element.style.height = height + 'px'; this.element.style.height = height + 'px';
}, },
/** /**
@@ -127,7 +123,7 @@
clear: function() { clear: function() {
var context = this.getContext(); var context = this.getContext();
var el = this.getElement(); var el = this.getElement();
context.clearRect(0, 0, el.width, el.height); context.clearRect(0, 0, this.getWidth(), this.getHeight());
}, },
/** /**
* to data url * to data url
@@ -226,9 +222,12 @@
} }
}, },
_applyAncestorTransforms: function(node) { _applyAncestorTransforms: function(node) {
var context = this.context; var context = this.context,
t, m;
node._eachAncestorReverse(function(no) { node._eachAncestorReverse(function(no) {
var t = no.getTransform(), m = t.getMatrix(); t = no.getTransform(true);
m = t.getMatrix();
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]);
}, true); }, true);
}, },

View File

@@ -199,8 +199,7 @@
drawScene: function(canvas) { drawScene: function(canvas) {
var layer = this.getLayer(), var layer = this.getLayer(),
clip = !!this.getClipFunc(), clip = !!this.getClipFunc(),
stage = this.getStage(), children, n, len;
children, len;
if (!canvas && layer) { if (!canvas && layer) {
canvas = layer.getCanvas(); canvas = layer.getCanvas();
@@ -214,7 +213,7 @@
children = this.children; children = this.children;
len = children.length; len = children.length;
for(var n = 0; n < len; n++) { for(n = 0; n < len; n++) {
children[n].drawScene(canvas); children[n].drawScene(canvas);
} }

View File

@@ -645,12 +645,7 @@
}, true); }, true);
return am; return am;
}, },
/** _getAndCacheTransform: function() {
* get transform of the node
* @name getTransform
* @methodOf Kinetic.Node.prototype
*/
getTransform: function() {
var m = new Kinetic.Transform(), var m = new Kinetic.Transform(),
x = this.getX(), x = this.getX(),
y = this.getY(), y = this.getY(),
@@ -675,8 +670,24 @@
m.translate(-1 * offsetX, -1 * offsetY); m.translate(-1 * offsetX, -1 * offsetY);
} }
// cache result
this.cachedTransform = m;
return m; return m;
}, },
/**
* get transform of the node
* @name getTransform
* @methodOf Kinetic.Node.prototype
*/
getTransform: function(useCache) {
var cachedTransform = this.cachedTransform;
if (useCache && cachedTransform) {
return cachedTransform;
}
else {
return this._getAndCacheTransform();
}
},
/** /**
* clone node. Returns a new Node instance with identical attributes * clone node. Returns a new Node instance with identical attributes
* @name clone * @name clone
@@ -996,28 +1007,30 @@
}; };
// add getter and setter methods // add getter and setter methods
Kinetic.Node.addGetterSetter = function(constructor, arr, def, isTransform) {
Kinetic.Node.addGetterSetter = function(constructor, arr, def) {
this.addGetter(constructor, arr, def); this.addGetter(constructor, arr, def);
this.addSetter(constructor, arr); this.addSetter(constructor, arr, isTransform);
}; };
Kinetic.Node.addPointGetterSetter = function(constructor, arr, def) { Kinetic.Node.addPointGetterSetter = function(constructor, arr, def, isTransform) {
this.addGetter(constructor, arr, def); this.addGetter(constructor, arr, def);
this.addPointSetter(constructor, arr); this.addPointSetter(constructor, arr, isTransform);
}; };
Kinetic.Node.addRotationGetterSetter = function(constructor, arr, def) { Kinetic.Node.addRotationGetterSetter = function(constructor, arr, def, isTransform) {
this.addRotationGetter(constructor, arr, def); this.addRotationGetter(constructor, arr, def);
this.addRotationSetter(constructor, arr); this.addRotationSetter(constructor, arr, isTransform);
}; };
Kinetic.Node.addSetter = function(constructor, attr) { Kinetic.Node.addSetter = function(constructor, attr, isTransform) {
var that = this, var that = this,
method = SET + Kinetic.Type._capitalize(attr); method = SET + Kinetic.Type._capitalize(attr);
constructor.prototype[method] = function(val) { constructor.prototype[method] = function(val) {
this.setAttr(attr, val); this.setAttr(attr, val);
if (isTransform) {
this.cachedTransform = null;
}
}; };
}; };
Kinetic.Node.addPointSetter = function(constructor, attr) { Kinetic.Node.addPointSetter = function(constructor, attr, isTransform) {
var that = this, var that = this,
method = SET + Kinetic.Type._capitalize(attr); method = SET + Kinetic.Type._capitalize(attr);
@@ -1036,19 +1049,29 @@
pos.y = this.attrs[attr].y; pos.y = this.attrs[attr].y;
} }
this.setAttr(attr, pos); this.setAttr(attr, pos);
if (isTransform) {
this.cachedTransform = null;
}
}; };
}; };
Kinetic.Node.addRotationSetter = function(constructor, attr) { Kinetic.Node.addRotationSetter = function(constructor, attr, isTransform) {
var that = this, var that = this,
method = SET + Kinetic.Type._capitalize(attr); method = SET + Kinetic.Type._capitalize(attr);
// radians // radians
constructor.prototype[method] = function(val) { constructor.prototype[method] = function(val) {
this.setAttr(attr, val); this.setAttr(attr, val);
if (isTransform) {
this.cachedTransform = null;
}
}; };
// degrees // degrees
constructor.prototype[method + DEG] = function(deg) { constructor.prototype[method + DEG] = function(deg) {
this.setAttr(attr, Kinetic.Type._degToRad(deg)); this.setAttr(attr, Kinetic.Type._degToRad(deg));
if (isTransform) {
this.cachedTransform = null;
}
}; };
}; };
Kinetic.Node.addGetter = function(constructor, attr, def) { Kinetic.Node.addGetter = function(constructor, attr, def) {
@@ -1134,8 +1157,8 @@
return no; return no;
}; };
// add getters setters // add getters setters
Kinetic.Node.addGetterSetter(Kinetic.Node, 'x', 0); Kinetic.Node.addGetterSetter(Kinetic.Node, 'x', 0, true);
Kinetic.Node.addGetterSetter(Kinetic.Node, 'y', 0); Kinetic.Node.addGetterSetter(Kinetic.Node, 'y', 0, true);
Kinetic.Node.addGetterSetter(Kinetic.Node, 'opacity', 1); Kinetic.Node.addGetterSetter(Kinetic.Node, 'opacity', 1);
/** /**
@@ -1194,7 +1217,7 @@
* @methodOf Kinetic.Node.prototype * @methodOf Kinetic.Node.prototype
*/ */
Kinetic.Node.addRotationGetterSetter(Kinetic.Node, 'rotation', 0); Kinetic.Node.addRotationGetterSetter(Kinetic.Node, 'rotation', 0, true);
/** /**
* set rotation in radians * set rotation in radians
@@ -1222,8 +1245,8 @@
* @methodOf Kinetic.Node.prototype * @methodOf Kinetic.Node.prototype
*/ */
Kinetic.Node.addPointGetterSetter(Kinetic.Node, 'scale', {x:1,y:1}); Kinetic.Node.addPointGetterSetter(Kinetic.Node, 'scale', {x:1,y:1}, true);
Kinetic.Node.addPointGetterSetter(Kinetic.Node, 'offset', {x:0,y:0}); Kinetic.Node.addPointGetterSetter(Kinetic.Node, 'offset', {x:0,y:0}, true);
/** /**
* set scale * set scale

View File

@@ -178,8 +178,7 @@
delete Kinetic.Global.shapes[this.colorKey]; delete Kinetic.Global.shapes[this.colorKey];
}, },
drawScene: function(canvas) { drawScene: function(canvas) {
var attrs = this.getAttrs(), var drawFunc = this.getDrawFunc(),
drawFunc = attrs.drawFunc,
canvas = canvas || this.getLayer().getCanvas(), canvas = canvas || this.getLayer().getCanvas(),
context = canvas.getContext(); context = canvas.getContext();

View File

@@ -23,6 +23,38 @@ Test.Modules.NODE = {
test(circle.getAbsoluteOpacity() === 0.25, 'abs opacity should be 0.25'); test(circle.getAbsoluteOpacity() === 0.25, 'abs opacity should be 0.25');
test(layer.getAbsoluteOpacity() === 0.5, 'abs opacity should be 0.5'); test(layer.getAbsoluteOpacity() === 0.5, 'abs opacity should be 0.5');
}, },
'transformation matrix caching': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var circle = new Kinetic.Circle({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: 70,
fill: 'green',
stroke: 'black',
strokeWidth: 4
});
test(!circle.cachedTransform, 'circle transform cache should be empty');
layer.add(circle);
stage.add(layer);
test(circle.cachedTransform, 'circle transform cache should be present');
circle.setX(100);
test(!circle.cachedTransform, 'circle transform cache should be empty');
layer.draw();
test(circle.cachedTransform, 'circle transform cache should be present');
},
'test pixel ratio toDataURL': function(containerId) { 'test pixel ratio toDataURL': function(containerId) {
var stage = new Kinetic.Stage({ var stage = new Kinetic.Stage({
container: containerId, container: containerId,