Skip to content

Commit 6146e5f

Browse files
committed
Optimize save-transform-constructPath-restore
The 4 operations can be replaced with just one in applying the transform to the points coordinates.
1 parent 7c5695f commit 6146e5f

File tree

2 files changed

+139
-1
lines changed

2 files changed

+139
-1
lines changed

src/core/operator_list.js

+72-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@
1313
* limitations under the License.
1414
*/
1515

16-
import { ImageKind, OPS, RenderingIntentFlag, warn } from "../shared/util.js";
16+
import {
17+
DrawOPS,
18+
ImageKind,
19+
OPS,
20+
RenderingIntentFlag,
21+
Util,
22+
warn,
23+
} from "../shared/util.js";
1724

1825
function addState(parentState, pattern, checkFn, iterateFn, processFn) {
1926
let state = parentState;
@@ -470,6 +477,70 @@ addState(
470477
}
471478
);
472479

480+
// This replaces (save, transform, constructPath, restore)
481+
// sequences with |constructPath| operation.
482+
addState(
483+
InitialState,
484+
[OPS.save, OPS.transform, OPS.constructPath, OPS.restore],
485+
context => {
486+
const argsArray = context.argsArray;
487+
const iFirstConstructPath = context.iCurr - 1;
488+
const op = argsArray[iFirstConstructPath][0];
489+
490+
// When stroking the transform has to be applied to the line width too.
491+
// So we can only optimize if the transform is an identity.
492+
if (
493+
op !== OPS.stroke &&
494+
op !== OPS.closeStroke &&
495+
op !== OPS.fillStroke &&
496+
op !== OPS.eoFillStroke &&
497+
op !== OPS.closeFillStroke &&
498+
op !== OPS.closeEOFillStroke
499+
) {
500+
return true;
501+
}
502+
const iFirstTransform = context.iCurr - 2;
503+
const transform = argsArray[iFirstTransform];
504+
return (
505+
transform[0] === 1 &&
506+
transform[1] === 0 &&
507+
transform[2] === 0 &&
508+
transform[3] === 1
509+
);
510+
},
511+
() => false,
512+
(context, i) => {
513+
const { fnArray, argsArray } = context;
514+
const curr = context.iCurr;
515+
const iFirstSave = curr - 3;
516+
const iFirstTransform = curr - 2;
517+
const iFirstConstructPath = curr - 1;
518+
const args = argsArray[iFirstConstructPath];
519+
const transform = argsArray[iFirstTransform];
520+
const [, [buffer], minMax] = args;
521+
522+
Util.scaleMinMax(transform, minMax);
523+
for (let k = 0, kk = buffer.length; k < kk; ) {
524+
switch (buffer[k++]) {
525+
case DrawOPS.moveTo:
526+
case DrawOPS.lineTo:
527+
Util.applyTransformInPlace(buffer.subarray(k), transform);
528+
k += 2;
529+
break;
530+
case DrawOPS.curveTo:
531+
Util.applyTransformToBezierInPlace(buffer.subarray(k), transform);
532+
k += 6;
533+
break;
534+
}
535+
}
536+
// Replace queue items.
537+
fnArray.splice(iFirstSave, 4, OPS.constructPath);
538+
argsArray.splice(iFirstSave, 4, args);
539+
540+
return iFirstSave + 1;
541+
}
542+
);
543+
473544
class NullOptimizer {
474545
constructor(queue) {
475546
this.queue = queue;

src/shared/util.js

+67
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,57 @@ class Util {
676676
return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
677677
}
678678

679+
// Apply a scaling matrix to some min/max values.
680+
// If a scaling factor is negative then min and max must be
681+
// swapped.
682+
static scaleMinMax(transform, minMax) {
683+
let temp;
684+
if (transform[0]) {
685+
if (transform[0] < 0) {
686+
temp = minMax[0];
687+
minMax[0] = minMax[2];
688+
minMax[2] = temp;
689+
}
690+
minMax[0] *= transform[0];
691+
minMax[2] *= transform[0];
692+
693+
if (transform[3] < 0) {
694+
temp = minMax[1];
695+
minMax[1] = minMax[3];
696+
minMax[3] = temp;
697+
}
698+
minMax[1] *= transform[3];
699+
minMax[3] *= transform[3];
700+
} else {
701+
temp = minMax[0];
702+
minMax[0] = minMax[1];
703+
minMax[1] = temp;
704+
temp = minMax[2];
705+
minMax[2] = minMax[3];
706+
minMax[3] = temp;
707+
708+
if (transform[1] < 0) {
709+
temp = minMax[1];
710+
minMax[1] = minMax[3];
711+
minMax[3] = temp;
712+
}
713+
minMax[1] *= transform[1];
714+
minMax[3] *= transform[1];
715+
716+
if (transform[2] < 0) {
717+
temp = minMax[0];
718+
minMax[0] = minMax[2];
719+
minMax[2] = temp;
720+
}
721+
minMax[0] *= transform[2];
722+
minMax[2] *= transform[2];
723+
}
724+
minMax[0] += transform[4];
725+
minMax[1] += transform[5];
726+
minMax[2] += transform[4];
727+
minMax[3] += transform[5];
728+
}
729+
679730
// Concatenates two transformation matrices together and returns the result.
680731
static transform(m1, m2) {
681732
return [
@@ -695,6 +746,22 @@ class Util {
695746
return [xt, yt];
696747
}
697748

749+
static applyTransformInPlace(p, m) {
750+
const [p0, p1] = p;
751+
p[0] = p0 * m[0] + p1 * m[2] + m[4];
752+
p[1] = p0 * m[1] + p1 * m[3] + m[5];
753+
}
754+
755+
// For 2d affine transforms
756+
static applyTransformToBezierInPlace(p, [m0, m1, m2, m3, m4, m5]) {
757+
for (let i = 0; i < 6; i += 2) {
758+
const pI = p[i];
759+
const pI1 = p[i + 1];
760+
p[i] = pI * m0 + pI1 * m2 + m4;
761+
p[i + 1] = pI * m1 + pI1 * m3 + m5;
762+
}
763+
}
764+
698765
static applyInverseTransform(p, m) {
699766
const d = m[0] * m[3] - m[1] * m[2];
700767
const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;

0 commit comments

Comments
 (0)