mirror of
https://github.com/konvajs/konva.git
synced 2026-02-24 20:26:01 +08:00
Fix bounding box calculation for bezier lines.
With a bezier line, the client rect was based directly on the positions of the points as if it were a normal line. This would result in a bounding box that doesn't correspond to the actual rendered curve, which in turn causes a Transformer containing the line to look incorrect. Fix this by calculating the extrema of the bezier curve on each axis and use those to get the bounds of the actual rendered line.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user