fix caching when buffer is used. close #1886

fix svg length calculation. close #1869
This commit is contained in:
Anton Lavrevov 2025-04-03 13:02:58 -05:00
parent 8211db3233
commit 2e08f7319f
7 changed files with 120 additions and 13 deletions

View File

@ -1,8 +1,6 @@
import { Util } from './Util';
import { SceneContext, HitContext, Context } from './Context';
import { Konva } from './Global';
import { Factory } from './Factory';
import { getNumberValidator } from './Validators';
// calculate pixel ratio
let _pixelRatio;

View File

@ -248,8 +248,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
*/
clearCache() {
if (this._cache.has(CANVAS)) {
const { scene, filter, hit } = this._cache.get(CANVAS);
Util.releaseCanvas(scene, filter, hit);
const { scene, filter, hit, buffer } = this._cache.get(CANVAS);
Util.releaseCanvas(scene, filter, hit, buffer);
this._cache.delete(CANVAS);
}
@ -385,6 +385,18 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
sceneContext = cachedSceneCanvas.getContext(),
hitContext = cachedHitCanvas.getContext();
const bufferCanvas = new SceneCanvas({
// width and height already multiplied by pixelRatio
// so we need to revert that
// also increase size by x nd y offset to make sure content fits canvas
width:
cachedSceneCanvas.width / cachedSceneCanvas.pixelRatio + Math.abs(x),
height:
cachedSceneCanvas.height / cachedSceneCanvas.pixelRatio + Math.abs(y),
pixelRatio: cachedSceneCanvas.pixelRatio,
}),
bufferContext = bufferCanvas.getContext();
cachedHitCanvas.isCache = true;
cachedSceneCanvas.isCache = true;
@ -398,16 +410,23 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
sceneContext.save();
hitContext.save();
bufferContext.save();
sceneContext.translate(-x, -y);
hitContext.translate(-x, -y);
bufferContext.translate(-x, -y);
// hard-code offset to make sure content fits canvas
// @ts-ignore
bufferCanvas.x = x;
// @ts-ignore
bufferCanvas.y = y;
// extra flag to skip on getAbsolute opacity calc
this._isUnderCache = true;
this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY);
this._clearSelfAndDescendantCache(ABSOLUTE_SCALE);
this.drawScene(cachedSceneCanvas, this);
this.drawScene(cachedSceneCanvas, this, bufferCanvas);
this.drawHit(cachedHitCanvas, this);
this._isUnderCache = false;
@ -431,6 +450,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
scene: cachedSceneCanvas,
filter: cachedFilterCanvas,
hit: cachedHitCanvas,
buffer: bufferCanvas,
x: x,
y: y,
});

View File

@ -602,7 +602,7 @@ export class Shape<
stage,
bufferContext;
const skipBuffer = canvas.isCache;
const skipBuffer = false;
const cachingSelf = top === this;
if (!this.isVisible() && !cachingSelf) {
@ -646,7 +646,13 @@ export class Shape<
}
context._applyOpacity(this);
context._applyGlobalCompositeOperation(this);
context.drawImage(bc._canvas, 0, 0, bc.width / ratio, bc.height / ratio);
context.drawImage(
bc._canvas,
bc.x || 0,
bc.y || 0,
bc.width / ratio,
bc.height / ratio
);
} else {
context._applyLineJoin(this);

View File

@ -258,11 +258,19 @@ export class Path extends Shape<PathConfig> {
}
if (length < 0.01) {
points = dataArray[i].points.slice(0, 2);
return {
x: points[0],
y: points[1],
};
const cmd = dataArray[i].command;
if (cmd === 'M') {
points = dataArray[i].points.slice(0, 2);
return {
x: points[0],
y: points[1],
};
} else {
return {
x: dataArray[i].start.x,
y: dataArray[i].start.y,
};
}
}
const cp = dataArray[i];

View File

@ -1083,6 +1083,7 @@ describe('Path', function () {
it('get point at path', function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
const data =
'M 300,10 L 250,100 A 100 40 30 1 0 150 150 C 160,100, 290,100, 300,150';
var path = new Konva.Path({
@ -1102,7 +1103,7 @@ describe('Path', function () {
var circle = new Konva.Circle({
x: p.x,
y: p.y,
radius: 2,
radius: 0.1,
fill: 'black',
stroke: 'black',
});
@ -1164,13 +1165,49 @@ describe('Path', function () {
{ x: 299.87435436448743, y: 149.4028482225714 },
]);
}
});
it('get point at vertical path', function () {
var stage = addStage();
var layer = new Konva.Layer();
const data = 'M 614.96002,7.5147864 611.20262,429.59529';
var path = new Konva.Path({
stroke: 'red',
strokeWidth: 3,
data,
x: -600,
});
layer.add(path);
if (isBrowser) {
const SVGPath = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
) as SVGPathElement;
SVGPath.setAttribute('d', data);
for (var i = 0.001; i < path.getLength(); i += 1) {
var p = path.getPointAtLength(i);
console.log(p);
var circle = new Konva.Circle({
x: p.x + path.x(),
y: p.y + path.y(),
radius: 2,
fill: 'black',
stroke: 'black',
});
layer.add(circle);
const position = SVGPath.getPointAtLength(i);
console.log(position);
assert(Math.abs(p.x - position.x) <= 1);
assert(Math.abs(p.y - position.y) <= 1);
}
}
stage.add(layer);
});
it('get point at path with float attrs', function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
const data =
'M419.0000314094981 342.88624187900973 L419.00003140949804 577.0038889378335 L465.014001082264 577.0038889378336 Z';

View File

@ -1597,6 +1597,36 @@ describe('Shape', function () {
compareCanvases(canvas1, canvas2, 240, 110);
});
it('check stroke rendering on buffer canvas', async function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
const rect = new Konva.Rect({
x: 150,
y: 50,
width: 50,
height: 50,
fill: '#039BE5',
stroke: 'yellow',
strokeWidth: 5,
shadowColor: 'black',
shadowOffset: { x: 10, y: 10 },
shadowOpacity: 0.5,
});
layer.add(rect);
const canvas1 = layer.toCanvas();
rect.cache();
const canvas2 = layer.toCanvas();
// throw new Error('stop');
compareCanvases(canvas1, canvas2);
});
// ======================================================
it('optional disable shadow for stroke', function () {
var stage = addStage();

View File

@ -131,6 +131,14 @@ export function compareCanvases(canvas1, canvas2, tol?, secondTol?) {
b.appendChild(canvas2);
c.appendChild(diffCanvas);
div.appendChild(b);
if (!canvas1.parentNode) {
const d = get('div', '<div>Original:</div>');
canvas1.style.position = '';
canvas1.style.display = '';
d.style.float = 'left';
d.appendChild(canvas1);
div.appendChild(d);
}
div.appendChild(c);
getContainer().appendChild(div);
}