Memoize transform and use transform for rendering (#4418)

* done

* done removed dist

* added some test
This commit is contained in:
Andrea Bogazzi 2017-10-27 09:01:32 +02:00 committed by GitHub
parent 3244f6c0ae
commit 40f16a9b4e
6 changed files with 105 additions and 32 deletions

View file

@ -482,14 +482,14 @@
*/
isTargetTransparent: function (target, x, y) {
var ctx = this.contextCache,
originalColor = target.selectionBackgroundColor;
originalColor = target.selectionBackgroundColor, v = this.viewportTransform;
target.selectionBackgroundColor = '';
this.clearContext(ctx);
ctx.save();
ctx.transform.apply(ctx, this.viewportTransform);
ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
target.render(ctx);
ctx.restore();

View file

@ -42,6 +42,16 @@
*/
aCoords: null,
/**
* storage for object transform matrix
*/
ownMatrixCache: null,
/**
* storage for object full transform matrix
*/
matrixCache: null,
/**
* return correct set of coordinates for intersection
*/
@ -437,6 +447,15 @@
return fabric.iMatrix.concat();
},
transformMatrixKey: function(skipGroup) {
var sep = '_', prefix = '';
if (!skipGroup && this.group) {
prefix = this.group.transformMatrixKey(skipGroup) + sep;
};
return prefix + this.top + sep + this.left + sep + this.scaleX + sep + this.scaleY +
sep + this.skewX + sep + this.skewY + sep + this.angle + sep + this.flipX + sep + this.flipY;
},
/**
* calculate trasform Matrix that represent current transformation from
* object properties.
@ -444,22 +463,38 @@
* @return {Array} matrix Transform Matrix for the object
*/
calcTransformMatrix: function(skipGroup) {
if (skipGroup) {
return this.calcOwnMatrix();
}
var key = this.transformMatrixKey(), cache = this.matrixCache || (this.matrixCache = {});
if (cache.key === key) {
return cache.value;
}
var matrix = this.calcOwnMatrix();
if (this.group) {
matrix = multiplyMatrices(this.group.calcTransformMatrix(), matrix);
}
cache.key = key;
cache.value = matrix;
return matrix;
},
calcOwnMatrix: function() {
var key = this.transformMatrixKey(true), cache = this.ownMatrixCache || (this.ownMatrixCache = {});
if (cache.key === key) {
return cache.value;
}
var center = this.getCenterPoint(),
translateMatrix = [1, 0, 0, 1, center.x, center.y],
matrix = [1, 0, 0, 1, center.x, center.y],
rotateMatrix,
dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true),
matrix;
if (this.group && !skipGroup) {
matrix = multiplyMatrices(this.group.calcTransformMatrix(), translateMatrix);
}
else {
matrix = translateMatrix;
}
dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true);
if (this.angle) {
rotateMatrix = this._calcRotateMatrix();
matrix = multiplyMatrices(matrix, rotateMatrix);
}
matrix = multiplyMatrices(matrix, dimensionMatrix);
cache.key = key;
cache.value = matrix;
return matrix;
},

View file

@ -266,9 +266,9 @@
return;
}
if (this.canvas && this.canvas.contextTop) {
var ctx = this.canvas.contextTop;
var ctx = this.canvas.contextTop, v = this.canvas.viewportTransform;
ctx.save();
ctx.transform.apply(ctx, this.canvas.viewportTransform);
ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
this.transform(ctx);
this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix);
this._clearTextArea(ctx);

View file

@ -743,21 +743,16 @@
/**
* Transforms context when rendering an object
* @param {CanvasRenderingContext2D} ctx Context
* @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node
*/
transform: function(ctx, fromLeft) {
transform: function(ctx) {
var m;
if (this.group && !this.group._transformDone) {
this.group.transform(ctx);
m = this.calcTransformMatrix();
}
var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint();
ctx.translate(center.x, center.y);
this.angle && ctx.rotate(degreesToRadians(this.angle));
ctx.scale(
this.scaleX * (this.flipX ? -1 : 1),
this.scaleY * (this.flipY ? -1 : 1)
);
this.skewX && ctx.transform(1, 0, Math.tan(degreesToRadians(this.skewX)), 1, 0, 0);
this.skewY && ctx.transform(1, Math.tan(degreesToRadians(this.skewY)), 0, 1, 0, 0);
else {
m = this.calcOwnMatrix();
}
ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
},
/**
@ -1239,12 +1234,12 @@
if (!filler || !filler.toLive) {
return { offsetX: 0, offsetY: 0 };
}
var transform = filler.gradientTransform || filler.patternTransform;
var t = filler.gradientTransform || filler.patternTransform;
var offsetX = -this.width / 2 + filler.offsetX || 0,
offsetY = -this.height / 2 + filler.offsetY || 0;
ctx.translate(offsetX, offsetY);
if (transform) {
ctx.transform.apply(ctx, transform);
if (t) {
ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]);
}
return { offsetX: offsetX, offsetY: offsetY };
},

View file

@ -879,6 +879,7 @@
* @chainable
*/
renderCanvas: function(ctx, objects) {
var v = this.viewportTransform;
if (this.isRendering) {
fabric.util.cancelAnimFrame(this.isRendering);
this.isRendering = 0;
@ -893,7 +894,7 @@
ctx.save();
//apply viewport transform once for all rendering process
ctx.transform.apply(ctx, this.viewportTransform);
ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
this._renderObjects(ctx, objects);
ctx.restore();
if (!this.controlsAboveOverlay && this.interactive) {
@ -927,7 +928,7 @@
* @param {string} property 'background' or 'overlay'
*/
_renderBackgroundOrOverlay: function(ctx, property) {
var object = this[property + 'Color'];
var object = this[property + 'Color'], v;
if (object) {
ctx.fillStyle = object.toLive
? object.toLive(ctx, this)
@ -942,8 +943,9 @@
object = this[property + 'Image'];
if (object) {
if (this[property + 'Vpt']) {
v = this.viewportTransform;
ctx.save();
ctx.transform.apply(ctx, this.viewportTransform);
ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
}
object.render(ctx);
this[property + 'Vpt'] && ctx.restore();

View file

@ -356,9 +356,50 @@
assert.ok(!cObj.isOnScreen(), 'object is completely out of viewport');
});
QUnit.test('calcTransformMatrix', function(assert) {
QUnit.test('calcTransformMatrix with no group', function(assert) {
var cObj = new fabric.Object({ width: 10, height: 15, strokeWidth: 0 });
assert.ok(typeof cObj.calcTransformMatrix === 'function', 'calcTransformMatrix should exist');
cObj.top = 0;
cObj.left = 0;
cObj.scaleX = 2;
cObj.scaleY = 3;
assert.deepEqual(cObj.calcTransformMatrix(), cObj.calcOwnMatrix(), 'without group matrix is same');
});
QUnit.test('calcOwnMatrix', function(assert) {
var cObj = new fabric.Object({ width: 10, height: 15, strokeWidth: 0 });
assert.ok(typeof cObj.calcOwnMatrix === 'function', 'calcTransformMatrix should exist');
cObj.top = 0;
cObj.left = 0;
assert.deepEqual(cObj.calcOwnMatrix(), [1, 0, 0, 1, 5, 7.5], 'only translate matrix');
cObj.scaleX = 2;
cObj.scaleY = 3;
assert.deepEqual(cObj.calcOwnMatrix(), [2, 0, 0, 3, 10, 22.5], 'only translate matrix and scale');
cObj.skewX = 45;
assert.deepEqual(cObj.calcOwnMatrix(), [2, 0, 1.9999999999999998, 3, 25, 22.5], 'translate matrix scale skewX');
cObj.skewY = 30;
assert.deepEqual(cObj.calcOwnMatrix(), [3.1547005383792515, 1.7320508075688772, 1.9999999999999998, 3, 30.773502691896255, 31.160254037844386], 'translate matrix scale skewX skewY');
cObj.angle = 38;
assert.deepEqual(cObj.calcOwnMatrix(), [1.4195809931249126,
3.3071022498267006,
-0.2709629187635314,
3.595355211471482,
5.065683074898075,
43.50067533516962], 'translate matrix scale skewX skewY angle');
cObj.flipX = true;
assert.deepEqual(cObj.calcOwnMatrix(), [-3.552294904178618,
-0.5773529255117364,
-3.4230059331904186,
1.1327093101688495,
5.065683074898075,
43.50067533516962], 'translate matrix scale skewX skewY angle flipX');
cObj.flipY = true;
assert.deepEqual(cObj.calcOwnMatrix(), [-1.4195809931249126,
-3.3071022498267006,
0.2709629187635314,
-3.595355211471482,
5.065683074898075,
43.50067533516962], 'translate matrix scale skewX skewY angle flipX flipY');
});
QUnit.test('_calcDimensionsTransformMatrix', function(assert) {