Skip to content

Commit 761b97a

Browse files
committed
Reduce memory use and improve perfs when computing the bounding box of a bezier curve (bug 1875547)
It isn't really a fix for the mentioned bug but it slightly improve things. In reducing the memory use, the time spent in the GC is reduced either. The algorithm to compute the bounding box is the same as before but it has just been rewritten to be more efficient.
1 parent a5d4660 commit 761b97a

File tree

5 files changed

+118
-82
lines changed

5 files changed

+118
-82
lines changed

src/core/evaluator.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1386,17 +1386,17 @@ class PartialEvaluator {
13861386
const y = args[1] + args[3];
13871387
minMax = [
13881388
Math.min(args[0], x),
1389-
Math.max(args[0], x),
13901389
Math.min(args[1], y),
1390+
Math.max(args[0], x),
13911391
Math.max(args[1], y),
13921392
];
13931393
break;
13941394
case OPS.moveTo:
13951395
case OPS.lineTo:
1396-
minMax = [args[0], args[0], args[1], args[1]];
1396+
minMax = [args[0], args[1], args[0], args[1]];
13971397
break;
13981398
default:
1399-
minMax = [Infinity, -Infinity, Infinity, -Infinity];
1399+
minMax = [Infinity, Infinity, -Infinity, -Infinity];
14001400
break;
14011401
}
14021402
operatorList.addOp(OPS.constructPath, [[fn], args, minMax]);
@@ -1420,15 +1420,15 @@ class PartialEvaluator {
14201420
const x = args[0] + args[2];
14211421
const y = args[1] + args[3];
14221422
minMax[0] = Math.min(minMax[0], args[0], x);
1423-
minMax[1] = Math.max(minMax[1], args[0], x);
1424-
minMax[2] = Math.min(minMax[2], args[1], y);
1423+
minMax[1] = Math.min(minMax[1], args[1], y);
1424+
minMax[2] = Math.max(minMax[2], args[0], x);
14251425
minMax[3] = Math.max(minMax[3], args[1], y);
14261426
break;
14271427
case OPS.moveTo:
14281428
case OPS.lineTo:
14291429
minMax[0] = Math.min(minMax[0], args[0]);
1430-
minMax[1] = Math.max(minMax[1], args[0]);
1431-
minMax[2] = Math.min(minMax[2], args[1]);
1430+
minMax[1] = Math.min(minMax[1], args[1]);
1431+
minMax[2] = Math.max(minMax[2], args[0]);
14321432
minMax[3] = Math.max(minMax[3], args[1]);
14331433
break;
14341434
}

src/display/canvas.js

+3-7
Original file line numberDiff line numberDiff line change
@@ -529,18 +529,14 @@ class CanvasExtraState {
529529
updateScalingPathMinMax(transform, minMax) {
530530
Util.scaleMinMax(transform, minMax);
531531
this.minX = Math.min(this.minX, minMax[0]);
532-
this.maxX = Math.max(this.maxX, minMax[1]);
533-
this.minY = Math.min(this.minY, minMax[2]);
532+
this.minY = Math.max(this.minY, minMax[1]);
533+
this.maxX = Math.min(this.maxX, minMax[2]);
534534
this.maxY = Math.max(this.maxY, minMax[3]);
535535
}
536536

537537
updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
538-
const box = Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3);
538+
const box = Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax);
539539
if (minMax) {
540-
minMax[0] = Math.min(minMax[0], box[0], box[2]);
541-
minMax[1] = Math.max(minMax[1], box[0], box[2]);
542-
minMax[2] = Math.min(minMax[2], box[1], box[3]);
543-
minMax[3] = Math.max(minMax[3], box[1], box[3]);
544540
return;
545541
}
546542
this.updateRectMinMax(transform, box);

src/shared/util.js

+106-66
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ class Util {
651651

652652
// Apply a scaling matrix to some min/max values.
653653
// If a scaling factor is negative then min and max must be
654-
// swaped.
654+
// swapped.
655655
static scaleMinMax(transform, minMax) {
656656
let temp;
657657
if (transform[0]) {
@@ -822,76 +822,116 @@ class Util {
822822
return [xLow, yLow, xHigh, yHigh];
823823
}
824824

825-
// From https://github.com/adobe-webplatform/Snap.svg/blob/b365287722a72526000ac4bfcf0ce4cac2faa015/src/path.js#L852
826-
static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3) {
827-
const tvalues = [],
828-
bounds = [[], []];
829-
let a, b, c, t, t1, t2, b2ac, sqrtb2ac;
830-
for (let i = 0; i < 2; ++i) {
831-
if (i === 0) {
832-
b = 6 * x0 - 12 * x1 + 6 * x2;
833-
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
834-
c = 3 * x1 - 3 * x0;
835-
} else {
836-
b = 6 * y0 - 12 * y1 + 6 * y2;
837-
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
838-
c = 3 * y1 - 3 * y0;
839-
}
840-
if (Math.abs(a) < 1e-12) {
841-
if (Math.abs(b) < 1e-12) {
842-
continue;
843-
}
844-
t = -c / b;
845-
if (0 < t && t < 1) {
846-
tvalues.push(t);
847-
}
848-
continue;
849-
}
850-
b2ac = b * b - 4 * c * a;
851-
sqrtb2ac = Math.sqrt(b2ac);
852-
if (b2ac < 0) {
853-
continue;
854-
}
855-
t1 = (-b + sqrtb2ac) / (2 * a);
856-
if (0 < t1 && t1 < 1) {
857-
tvalues.push(t1);
858-
}
859-
t2 = (-b - sqrtb2ac) / (2 * a);
860-
if (0 < t2 && t2 < 1) {
861-
tvalues.push(t2);
862-
}
825+
static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) {
826+
if (t <= 0 || t >= 1) {
827+
return;
863828
}
829+
const mt = 1 - t;
830+
const tt = t * t;
831+
const ttt = tt * t;
832+
const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3;
833+
const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3;
834+
minMax[0] = Math.min(minMax[0], x);
835+
minMax[1] = Math.min(minMax[1], y);
836+
minMax[2] = Math.max(minMax[2], x);
837+
minMax[3] = Math.max(minMax[3], y);
838+
}
864839

865-
let j = tvalues.length,
866-
mt;
867-
const jlen = j;
868-
while (j--) {
869-
t = tvalues[j];
870-
mt = 1 - t;
871-
bounds[0][j] =
872-
mt * mt * mt * x0 +
873-
3 * mt * mt * t * x1 +
874-
3 * mt * t * t * x2 +
875-
t * t * t * x3;
876-
bounds[1][j] =
877-
mt * mt * mt * y0 +
878-
3 * mt * mt * t * y1 +
879-
3 * mt * t * t * y2 +
880-
t * t * t * y3;
840+
static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) {
841+
if (Math.abs(a) < 1e-12) {
842+
if (Math.abs(b) >= 1e-12) {
843+
this.#getExtremumOnCurve(
844+
x0,
845+
x1,
846+
x2,
847+
x3,
848+
y0,
849+
y1,
850+
y2,
851+
y3,
852+
-c / b,
853+
minMax
854+
);
855+
}
856+
return;
881857
}
882858

883-
bounds[0][jlen] = x0;
884-
bounds[1][jlen] = y0;
885-
bounds[0][jlen + 1] = x3;
886-
bounds[1][jlen + 1] = y3;
887-
bounds[0].length = bounds[1].length = jlen + 2;
859+
const delta = b ** 2 - 4 * c * a;
860+
if (delta < 0) {
861+
return;
862+
}
863+
const sqrtDelta = Math.sqrt(delta);
864+
const a2 = 2 * a;
865+
this.#getExtremumOnCurve(
866+
x0,
867+
x1,
868+
x2,
869+
x3,
870+
y0,
871+
y1,
872+
y2,
873+
y3,
874+
(-b + sqrtDelta) / a2,
875+
minMax
876+
);
877+
this.#getExtremumOnCurve(
878+
x0,
879+
x1,
880+
x2,
881+
x3,
882+
y0,
883+
y1,
884+
y2,
885+
y3,
886+
(-b - sqrtDelta) / a2,
887+
minMax
888+
);
889+
}
888890

889-
return [
890-
Math.min(...bounds[0]),
891-
Math.min(...bounds[1]),
892-
Math.max(...bounds[0]),
893-
Math.max(...bounds[1]),
894-
];
891+
// From https://github.com/adobe-webplatform/Snap.svg/blob/b365287722a72526000ac4bfcf0ce4cac2faa015/src/path.js#L852
892+
static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
893+
if (minMax) {
894+
minMax[0] = Math.min(minMax[0], x0, x3);
895+
minMax[1] = Math.min(minMax[1], y0, y3);
896+
minMax[2] = Math.max(minMax[2], x0, x3);
897+
minMax[3] = Math.max(minMax[3], y0, y3);
898+
} else {
899+
minMax = [
900+
Math.min(x0, x3),
901+
Math.min(y0, y3),
902+
Math.max(x0, x3),
903+
Math.max(y0, y3),
904+
];
905+
}
906+
this.#getExtremum(
907+
x0,
908+
x1,
909+
x2,
910+
x3,
911+
y0,
912+
y1,
913+
y2,
914+
y3,
915+
3 * (-x0 + 3 * (x1 - x2) + x3),
916+
6 * (x0 - 2 * x1 + x2),
917+
3 * (x1 - x0),
918+
minMax
919+
);
920+
this.#getExtremum(
921+
x0,
922+
x1,
923+
x2,
924+
x3,
925+
y0,
926+
y1,
927+
y2,
928+
y3,
929+
3 * (-y0 + 3 * (y1 - y2) + y3),
930+
6 * (y0 - 2 * y1 + y2),
931+
3 * (y1 - y0),
932+
minMax
933+
);
934+
return minMax;
895935
}
896936
}
897937

test/unit/annotation_spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4548,7 +4548,7 @@ describe("annotation", function () {
45484548
expect(opList.argsArray[5][0]).toEqual([OPS.moveTo, OPS.curveTo]);
45494549
expect(opList.argsArray[5][1]).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
45504550
// Min-max.
4551-
expect(opList.argsArray[5][2]).toEqual([1, 1, 2, 2]);
4551+
expect(opList.argsArray[5][2]).toEqual([1, 2, 1, 2]);
45524552
});
45534553
});
45544554

test/unit/api_spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,7 @@ describe("api", function () {
771771
[
772772
[OPS.moveTo, OPS.lineTo],
773773
[0, 9.75, 0.5, 9.75],
774-
[0, 0.5, 9.75, 9.75],
774+
[0, 9.75, 0.5, 9.75],
775775
],
776776
null,
777777
]);

0 commit comments

Comments
 (0)