cache patterns and gradients

This commit is contained in:
Anton Lavrenov 2019-02-13 22:04:54 -05:00
parent 1b6238cbb2
commit 99e66c380f
6 changed files with 500 additions and 65 deletions

View File

@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
* A bit changed behavior of `removeId` (private method), now it doesn't clear node ref, if object is changed.
* simplified `batchDraw` method (it doesn't use `Konva.Animation`) now.
* `id` and `name` properties defaults are empty strings, not `undefined`
* Performance improvements for shapes will image patterns, linear and radial fills
### Removed
* `Konva.Util.addMethods`

108
konva.js
View File

@ -8,7 +8,7 @@
* Konva JavaScript Framework v3.0.0-0
* http://konvajs.github.io/
* Licensed under the MIT
* Date: Mon Feb 11 2019
* Date: Wed Feb 13 2019
*
* Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS)
* Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva)
@ -1769,29 +1769,22 @@
if (fillPatternOffsetX || fillPatternOffsetY) {
this.translate(-1 * fillPatternOffsetX, -1 * fillPatternOffsetY);
}
// TODO: cache pattern
this.setAttr('fillStyle', this.createPattern(shape.getFillPatternImage(), shape.getFillPatternRepeat() || 'repeat'));
this.setAttr('fillStyle', shape._getFillPattern());
shape._fillFunc(this);
};
SceneContext.prototype._fillLinearGradient = function (shape) {
var start = shape.getFillLinearGradientStartPoint(), end = shape.getFillLinearGradientEndPoint(), colorStops = shape.getFillLinearGradientColorStops(), grd = this.createLinearGradient(start.x, start.y, end.x, end.y);
if (colorStops) {
// build color stops
for (var n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
}
var grd = shape._getLinearGradient();
if (grd) {
this.setAttr('fillStyle', grd);
shape._fillFunc(this);
}
};
SceneContext.prototype._fillRadialGradient = function (shape) {
var start = shape.getFillRadialGradientStartPoint(), end = shape.getFillRadialGradientEndPoint(), startRadius = shape.getFillRadialGradientStartRadius(), endRadius = shape.getFillRadialGradientEndRadius(), colorStops = shape.getFillRadialGradientColorStops(), grd = this.createRadialGradient(start.x, start.y, startRadius, end.x, end.y, endRadius);
// build color stops
for (var n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
var grd = shape._getRadialGradient();
if (grd) {
this.setAttr('fillStyle', grd);
shape._fillFunc(this);
}
this.setAttr('fillStyle', grd);
shape._fillFunc(this);
};
SceneContext.prototype._fill = function (shape) {
var hasColor = shape.fill(), fillPriority = shape.getFillPriority();
@ -6808,7 +6801,17 @@
var HAS_SHADOW = 'hasShadow';
var SHADOW_RGBA = 'shadowRGBA';
// TODO: cache gradient from context
var patternImage = 'patternImage';
var linearGradient = 'linearGradient';
var radialGradient = 'radialGradient';
var dummyContext;
function getDummyContext() {
if (dummyContext) {
return dummyContext;
}
dummyContext = Util.createCanvasElement().getContext('2d');
return dummyContext;
}
// TODO: write a test for adding destroyed shape into the layer
// will it draw?
// will it pass hit test?
@ -6836,6 +6839,15 @@
function _clearGetShadowRGBACache() {
this._clearCache(SHADOW_RGBA);
}
function _clearFillPatternCache() {
this._clearCache(patternImage);
}
function _clearLinearGradientCache() {
this._clearCache(linearGradient);
}
function _clearRadialGradientCache() {
this._clearCache(radialGradient);
}
/**
* Shape constructor. Shapes are primitive objects such as rectangles,
* circles, text, lines, etc.
@ -6947,6 +6959,9 @@
shapes[key] = _this;
_this.on('shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', _clearHasShadowCache);
_this.on('shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', _clearGetShadowRGBACache);
_this.on('fillPriorityChange.konva fillPatternImageChange.konva fillPatternRepeatChange.konva', _clearFillPatternCache);
_this.on('fillPriorityChange.konva fillLinearGradientColorStopsChange.konva fillLinearGradientStartPointXChange.konva fillLinearGradientStartPointYChange.konva fillLinearGradientEndPointXChange.konva fillLinearGradientEndPointYChange.konva', _clearLinearGradientCache);
_this.on('fillPriorityChange.konva fillRadialGradientColorStopsChange.konva fillRadialGradientStartPointXChange.konva fillRadialGradientStartPointYChange.konva fillRadialGradientEndPointXChange.konva fillRadialGradientEndPointYChange.konva fillRadialGradientStartRadiusChange.konva fillRadialGradientEndRadiusChange.konva', _clearRadialGradientCache);
return _this;
}
/**
@ -6987,6 +7002,49 @@
this.shadowOffsetX() ||
this.shadowOffsetY())));
};
Shape.prototype._getFillPattern = function () {
return this._getCache(patternImage, this.__getFillPattern);
};
Shape.prototype.__getFillPattern = function () {
if (this.fillPatternImage()) {
var ctx = getDummyContext();
return ctx.createPattern(this.fillPatternImage(), this.fillPatternRepeat() || 'repeat');
}
};
Shape.prototype._getLinearGradient = function () {
return this._getCache(linearGradient, this.__getLinearGradient);
};
Shape.prototype.__getLinearGradient = function () {
var colorStops = this.fillLinearGradientColorStops();
if (colorStops) {
var ctx = getDummyContext();
var start = this.fillLinearGradientStartPoint();
var end = this.fillLinearGradientEndPoint();
var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
// build color stops
for (var n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
}
return grd;
}
};
Shape.prototype._getRadialGradient = function () {
return this._getCache(radialGradient, this.__getRadialGradient);
};
Shape.prototype.__getRadialGradient = function () {
var colorStops = this.fillRadialGradientColorStops();
if (colorStops) {
var ctx = getDummyContext();
var start = this.fillRadialGradientStartPoint();
var end = this.fillRadialGradientEndPoint();
var grd = ctx.createRadialGradient(start.x, start.y, this.fillRadialGradientStartRadius(), end.x, end.y, this.fillRadialGradientEndRadius());
// build color stops
for (var n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
}
return grd;
}
};
Shape.prototype.getShadowRGBA = function () {
return this._getCache(SHADOW_RGBA, this._getShadowRGBA);
};
@ -12300,13 +12358,13 @@
],
// cached variables
attrChangeListLen$1 = ATTR_CHANGE_LIST$1.length;
var dummyContext;
function getDummyContext() {
if (dummyContext) {
return dummyContext;
var dummyContext$1;
function getDummyContext$1() {
if (dummyContext$1) {
return dummyContext$1;
}
dummyContext = Util.createCanvasElement().getContext(CONTEXT_2D);
return dummyContext;
dummyContext$1 = Util.createCanvasElement().getContext(CONTEXT_2D);
return dummyContext$1;
}
function _fillFunc$1(context) {
context.fillText(this.partialText, 0, 0);
@ -12565,7 +12623,7 @@
};
// TODO: make it public, rename to "measure text"?
Text.prototype._getTextSize = function (text) {
var _context = getDummyContext(), fontSize = this.fontSize(), metrics;
var _context = getDummyContext$1(), fontSize = this.fontSize(), metrics;
_context.save();
_context.font = this._getContextFont();
metrics = _context.measureText(text);
@ -12605,7 +12663,7 @@
Text.prototype._getTextWidth = function (text) {
var letterSpacing = this.letterSpacing();
var length = text.length;
return (getDummyContext().measureText(text).width +
return (getDummyContext$1().measureText(text).width +
(length ? letterSpacing * (length - 1) : 0));
};
Text.prototype._setTextData = function () {
@ -12613,7 +12671,7 @@
// align = this.align(),
shouldWrap = wrap !== NONE$1, wrapAtWord = wrap !== CHAR && shouldWrap, shouldAddEllipsis = this.ellipsis() && !shouldWrap;
this.textArr = [];
getDummyContext().font = this._getContextFont();
getDummyContext$1().font = this._getContextFont();
var additionalWidth = shouldAddEllipsis ? this._getTextWidth(ELLIPSIS) : 0;
for (var i = 0, max = lines.length; i < max; ++i) {
var line = lines[i];

4
konva.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -496,52 +496,23 @@ export class SceneContext extends Context {
this.translate(-1 * fillPatternOffsetX, -1 * fillPatternOffsetY);
}
// TODO: cache pattern
this.setAttr(
'fillStyle',
this.createPattern(
shape.getFillPatternImage(),
shape.getFillPatternRepeat() || 'repeat'
)
);
this.setAttr('fillStyle', shape._getFillPattern());
shape._fillFunc(this);
}
_fillLinearGradient(shape) {
var start = shape.getFillLinearGradientStartPoint(),
end = shape.getFillLinearGradientEndPoint(),
colorStops = shape.getFillLinearGradientColorStops(),
grd = this.createLinearGradient(start.x, start.y, end.x, end.y);
var grd = shape._getLinearGradient();
if (colorStops) {
// build color stops
for (var n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
}
if (grd) {
this.setAttr('fillStyle', grd);
shape._fillFunc(this);
}
}
_fillRadialGradient(shape) {
var start = shape.getFillRadialGradientStartPoint(),
end = shape.getFillRadialGradientEndPoint(),
startRadius = shape.getFillRadialGradientStartRadius(),
endRadius = shape.getFillRadialGradientEndRadius(),
colorStops = shape.getFillRadialGradientColorStops(),
grd = this.createRadialGradient(
start.x,
start.y,
startRadius,
end.x,
end.y,
endRadius
);
// build color stops
for (var n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
var grd = shape._getRadialGradient();
if (grd) {
this.setAttr('fillStyle', grd);
shape._fillFunc(this);
}
this.setAttr('fillStyle', grd);
shape._fillFunc(this);
}
_fill(shape) {
var hasColor = shape.fill(),

View File

@ -8,8 +8,19 @@ import { Context } from './Context';
var HAS_SHADOW = 'hasShadow';
var SHADOW_RGBA = 'shadowRGBA';
var patternImage = 'patternImage';
var linearGradient = 'linearGradient';
var radialGradient = 'radialGradient';
var dummyContext;
function getDummyContext() {
if (dummyContext) {
return dummyContext;
}
dummyContext = Util.createCanvasElement().getContext('2d');
return dummyContext;
}
// TODO: cache gradient from context
// TODO: write a test for adding destroyed shape into the layer
// will it draw?
// will it pass hit test?
@ -42,6 +53,18 @@ function _clearGetShadowRGBACache() {
this._clearCache(SHADOW_RGBA);
}
function _clearFillPatternCache() {
this._clearCache(patternImage);
}
function _clearLinearGradientCache() {
this._clearCache(linearGradient);
}
function _clearRadialGradientCache() {
this._clearCache(radialGradient);
}
/**
* Shape constructor. Shapes are primitive objects such as rectangles,
* circles, text, lines, etc.
@ -101,6 +124,21 @@ export class Shape extends Node {
'shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva',
_clearGetShadowRGBACache
);
this.on(
'fillPriorityChange.konva fillPatternImageChange.konva fillPatternRepeatChange.konva',
_clearFillPatternCache
);
this.on(
'fillPriorityChange.konva fillLinearGradientColorStopsChange.konva fillLinearGradientStartPointXChange.konva fillLinearGradientStartPointYChange.konva fillLinearGradientEndPointXChange.konva fillLinearGradientEndPointYChange.konva',
_clearLinearGradientCache
);
this.on(
'fillPriorityChange.konva fillRadialGradientColorStopsChange.konva fillRadialGradientStartPointXChange.konva fillRadialGradientStartPointYChange.konva fillRadialGradientEndPointXChange.konva fillRadialGradientEndPointYChange.konva fillRadialGradientStartRadiusChange.konva fillRadialGradientEndRadiusChange.konva',
_clearRadialGradientCache
);
}
/**
@ -146,6 +184,64 @@ export class Shape extends Node {
))
);
}
_getFillPattern() {
return this._getCache(patternImage, this.__getFillPattern);
}
__getFillPattern() {
if (this.fillPatternImage()) {
var ctx = getDummyContext();
return ctx.createPattern(
this.fillPatternImage(),
this.fillPatternRepeat() || 'repeat'
);
}
}
_getLinearGradient() {
return this._getCache(linearGradient, this.__getLinearGradient);
}
__getLinearGradient() {
var colorStops = this.fillLinearGradientColorStops();
if (colorStops) {
var ctx = getDummyContext();
var start = this.fillLinearGradientStartPoint();
var end = this.fillLinearGradientEndPoint();
var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
// build color stops
for (var n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
}
return grd;
}
}
_getRadialGradient() {
return this._getCache(radialGradient, this.__getRadialGradient);
}
__getRadialGradient() {
var colorStops = this.fillRadialGradientColorStops();
if (colorStops) {
var ctx = getDummyContext();
var start = this.fillRadialGradientStartPoint();
var end = this.fillRadialGradientEndPoint();
var grd = ctx.createRadialGradient(
start.x,
start.y,
this.fillRadialGradientStartRadius(),
end.x,
end.y,
this.fillRadialGradientEndRadius()
);
// build color stops
for (var n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
}
return grd;
}
}
getShadowRGBA() {
return this._getCache(SHADOW_RGBA, this._getShadowRGBA);
}
@ -550,6 +646,12 @@ export class Shape extends Node {
fillRadialGradientStartRadius: GetSet<number, this>;
fillRadialGradientEndRadius: GetSet<number, this>;
fillRadialGradientColorStops: GetSet<Array<number | string>, this>;
fillRadialGradientStartPoint: GetSet<Vector2d, this>;
fillRadialGradientStartPointX: GetSet<number, this>;
fillRadialGradientStartPointY: GetSet<number, this>;
fillRadialGradientEndPoint: GetSet<Vector2d, this>;
fillRadialGradientEndPointX: GetSet<number, this>;
fillRadialGradientEndPointY: GetSet<number, this>;
fillPatternOffset: GetSet<Vector2d, this>;
fillPatternOffsetX: GetSet<number, this>;
fillPatternOffsetY: GetSet<number, this>;

View File

@ -377,7 +377,7 @@ suite('Shape', function() {
assert.equal(
trace,
'clearRect(0,0,578,200);save();transform(3,0,0,1.5,10,15);beginPath();rect(0,0,100,100);closePath();createLinearGradient(0,0,100,100);fillStyle=[object CanvasGradient];fill();lineWidth=2;createLinearGradient(0,0,100,100);strokeStyle=[object CanvasGradient];stroke();restore();'
'clearRect(0,0,578,200);save();transform(3,0,0,1.5,10,15);beginPath();rect(0,0,100,100);closePath();fillStyle=[object CanvasGradient];fill();lineWidth=2;createLinearGradient(0,0,100,100);strokeStyle=[object CanvasGradient];stroke();restore();'
);
});
@ -1471,4 +1471,307 @@ suite('Shape', function() {
layer.add(rect);
stage.add(layer);
});
// ======================================================
test('cache fill pattern', function(done) {
var imageObj = new Image();
imageObj.onload = function() {
var stage = addStage();
var layer = new Konva.Layer();
var star = new Konva.Star({
x: 200,
y: 100,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fillPatternImage: imageObj,
fillPatternX: -20,
fillPatternY: -30,
fillPatternScale: { x: 0.5, y: 0.5 },
fillPatternOffset: { x: 219, y: 150 },
fillPatternRotation: 90,
fillPatternRepeat: 'no-repeat',
stroke: 'blue',
strokeWidth: 5,
draggable: true
});
layer.add(star);
stage.add(layer);
var ctx = layer.getContext();
var oldCreate = ctx.createPattern;
var callCount = 0;
ctx.createPattern = function() {
callCount += 1;
return oldCreate.apply(this, arguments);
};
layer.draw();
layer.draw();
assert.equal(callCount, 0);
done();
};
imageObj.src = 'assets/darth-vader.jpg';
});
test('recache fill pattern on changes', function(done) {
var imageObj = new Image();
imageObj.onload = function() {
var stage = addStage();
var layer = new Konva.Layer();
var star = new Konva.Star({
x: 200,
y: 100,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fillPatternImage: imageObj,
fillPatternX: -20,
fillPatternY: -30,
fillPatternScale: { x: 0.5, y: 0.5 },
fillPatternOffset: { x: 219, y: 150 },
fillPatternRotation: 90,
fillPatternRepeat: 'no-repeat',
stroke: 'blue',
strokeWidth: 5,
draggable: true
});
layer.add(star);
stage.add(layer);
var pattern1 = star._getFillPattern();
star.fillPatternImage(document.createElement('canvas'));
var pattern2 = star._getFillPattern();
assert.notEqual(pattern1, pattern2);
star.fillPatternRepeat('repeat');
var pattern3 = star._getFillPattern();
assert.notEqual(pattern2, pattern3);
done();
};
imageObj.src = 'assets/darth-vader.jpg';
});
// ======================================================
test('cache linear gradient', function() {
var stage = addStage();
var layer = new Konva.Layer();
var star = new Konva.Star({
x: 200,
y: 100,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 50, y: 50 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow'],
stroke: 'blue',
strokeWidth: 5,
draggable: true
});
layer.add(star);
stage.add(layer);
var ctx = layer.getContext();
var oldCreate = ctx.createLinearGradient;
var callCount = 0;
ctx.createLinearGradient = function() {
callCount += 1;
return oldCreate.apply(this, arguments);
};
layer.draw();
layer.draw();
assert.equal(callCount, 0);
});
test('recache linear gradient on changes', function() {
var stage = addStage();
var layer = new Konva.Layer();
var star = new Konva.Star({
x: 200,
y: 100,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 50, y: 50 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow'],
stroke: 'blue',
strokeWidth: 5,
draggable: true
});
layer.add(star);
stage.add(layer);
var gradient1 = star._getLinearGradient();
star.fillLinearGradientStartPointX(-10);
var gradient2 = star._getLinearGradient();
assert.notEqual(gradient1, gradient2);
star.fillLinearGradientStartPointY(-10);
var gradient3 = star._getLinearGradient();
assert.notEqual(gradient2, gradient3);
star.fillLinearGradientEndPointX(100);
var gradient4 = star._getLinearGradient();
assert.notEqual(gradient3, gradient4);
star.fillLinearGradientEndPointY(100);
var gradient5 = star._getLinearGradient();
assert.notEqual(gradient4, gradient5);
star.fillLinearGradientColorStops([0, 'red', 1, 'green']);
var gradient6 = star._getLinearGradient();
assert.notEqual(gradient5, gradient6);
layer.draw();
});
// ======================================================
test('cache radial gradient', function() {
var stage = addStage();
var layer = new Konva.Layer();
var star = new Konva.Star({
x: 200,
y: 100,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fillRadialGradientStartPoint: { x: 0, y: 0 },
fillRadialGradientStartRadius: 0,
fillRadialGradientEndPoint: { x: 0, y: 0 },
fillRadialGradientEndRadius: 70,
fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'],
stroke: 'blue',
strokeWidth: 5,
draggable: true
});
layer.add(star);
stage.add(layer);
var ctx = layer.getContext();
var oldCreate = ctx.createRadialGradient;
var callCount = 0;
ctx.createRadialGradient = function() {
callCount += 1;
return oldCreate.apply(this, arguments);
};
layer.draw();
layer.draw();
assert.equal(callCount, 0);
});
test('recache linear gradient on changes', function() {
var stage = addStage();
var layer = new Konva.Layer();
var star = new Konva.Star({
x: 200,
y: 100,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fillRadialGradientStartPoint: { x: 0, y: 0 },
fillRadialGradientStartRadius: 0,
fillRadialGradientEndPoint: { x: 0, y: 0 },
fillRadialGradientEndRadius: 70,
fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'],
stroke: 'blue',
strokeWidth: 5,
draggable: true
});
layer.add(star);
stage.add(layer);
var gradient1 = star._getRadialGradient();
star.fillRadialGradientStartPointX(-10);
var gradient2 = star._getRadialGradient();
assert.notEqual(gradient1, gradient2);
star.fillRadialGradientStartPointY(-10);
var gradient3 = star._getRadialGradient();
assert.notEqual(gradient2, gradient3);
star.fillRadialGradientEndPointX(100);
var gradient4 = star._getRadialGradient();
assert.notEqual(gradient3, gradient4);
star.fillRadialGradientEndPointY(100);
var gradient5 = star._getRadialGradient();
assert.notEqual(gradient4, gradient5);
star.fillRadialGradientColorStops([0, 'red', 1, 'green']);
var gradient6 = star._getRadialGradient();
assert.notEqual(gradient5, gradient6);
star.fillRadialGradientStartRadius(10);
var gradient7 = star._getRadialGradient();
assert.notEqual(gradient6, gradient7);
star.fillRadialGradientEndRadius(200);
var gradient8 = star._getRadialGradient();
assert.notEqual(gradient7, gradient8);
layer.draw();
});
});