Merge pull request #2001 from ircubic/fix-bezier-curve-bounds
Some checks failed
Test Browser / build (20.x) (push) Has been cancelled
Test NodeJS / build (23.x) (push) Has been cancelled

Fix bounding box calculation for bezier lines.
This commit is contained in:
Anton Lavrenov
2025-11-28 08:48:35 -05:00
committed by GitHub
2 changed files with 107 additions and 0 deletions

View File

@@ -56,6 +56,43 @@ function expandPoints(p, tension) {
return allPoints;
}
function getBezierExtremaPoints(points) {
const axisPoints = [
[points[0], points[2], points[4], points[6]],
[points[1], points[3], points[5], points[7]],
];
const extremaTs: number[] = [];
for (const axis of axisPoints) {
const a = -3 * axis[0] + 9 * axis[1] - 9 * axis[2] + 3 * axis[3];
if (a !== 0) {
const b = 6 * axis[0] - 12 * axis[1] + 6 * axis[2];
const c = -3 * axis[0] + 3 * axis[1];
const discriminant = b * b - 4 * a * c;
if (discriminant >= 0) {
const d = Math.sqrt(discriminant);
extremaTs.push((-b + d) / (2 * a));
extremaTs.push((-b - d) / (2 * a));
}
}
}
return extremaTs
.filter((t) => t > 0 && t < 1)
.flatMap((t) =>
axisPoints.map((axis) => {
const mt = 1 - t;
return (
mt * mt * mt * axis[0] +
3 * mt * mt * t * axis[1] +
3 * mt * t * t * axis[2] +
t * t * t * axis[3]
);
})
);
}
export interface LineConfig extends ShapeConfig {
points?:
| number[]
@@ -259,6 +296,14 @@ export class Line<
points[points.length - 2],
points[points.length - 1],
];
} else if (this.bezier()) {
points = [
points[0],
points[1],
...getBezierExtremaPoints(this.points()),
points[points.length - 2],
points[points.length - 1],
];
} else {
points = this.points();
}

View File

@@ -456,6 +456,68 @@ describe('Line', function () {
assert.equal(client.height, 2, 'check height');
});
it('getClientRect with bezier', function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var line = new Konva.Line({
x: 0,
y: 0,
points: [25, 5, -47.7791, 20, 107.7837, 35, 25, 50],
bezier: true,
stroke: '#0f0',
});
layer.add(line);
layer.draw();
var client = line.getClientRect();
assert.equal(Math.round(client.x), 4, 'check x');
assert.equal(Math.round(client.y), 4, 'check y');
assert.equal(Math.round(client.width), 47, 'check width');
assert.equal(Math.round(client.height), 47, 'check height');
line.points([5, 25, 20, -47.7791, 35, 107.7837, 50, 25]);
client = line.getClientRect();
assert.equal(Math.round(client.x), 4, 'check x');
assert.equal(Math.round(client.y), 4, 'check y');
assert.equal(Math.round(client.width), 47, 'check width');
assert.equal(Math.round(client.height), 47, 'check height');
});
it('getClientRect with linear bezier', function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var line = new Konva.Line({
x: 0,
y: 0,
points: [5, 5, 20, 5, 35, 5, 50, 5],
bezier: true,
stroke: '#0f0',
});
layer.add(line);
layer.draw();
var client = line.getClientRect();
assert.equal(Math.round(client.x), 4, 'check x');
assert.equal(Math.round(client.y), 4, 'check y');
assert.equal(Math.round(client.width), 47, 'check width');
assert.equal(Math.round(client.height), 2, 'check height');
line.points([5, 5, 5, 20, 5, 35, 5, 50]);
client = line.getClientRect();
assert.equal(Math.round(client.x), 4, 'check x');
assert.equal(Math.round(client.y), 4, 'check y');
assert.equal(Math.round(client.width), 2, 'check width');
assert.equal(Math.round(client.height), 47, 'check height');
});
it('line caching', function () {
var stage = addStage();
var layer = new Konva.Layer();