Merge pull request #19721 from calixteman/simplif_singular_decomposition

Simplify singularValueDecompose2dScale in order to make it using less memory
This commit is contained in:
calixteman 2025-03-31 12:53:17 +02:00 committed by GitHub
commit a4950c0b71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 41 deletions

View File

@ -63,6 +63,9 @@ const FULL_CHUNK_HEIGHT = 16;
// creating a new DOMMatrix object each time we need it. // creating a new DOMMatrix object each time we need it.
const SCALE_MATRIX = new DOMMatrix(); const SCALE_MATRIX = new DOMMatrix();
// Used to get some coordinates.
const XY = new Float32Array(2);
/** /**
* Overrides certain methods on a 2d ctx so that when they are called they * Overrides certain methods on a 2d ctx so that when they are called they
* will also call the same method on the destCtx. The methods that are * will also call the same method on the destCtx. The methods that are
@ -522,9 +525,9 @@ class CanvasExtraState {
} }
// Stroked paths can be outside of the path bounding box by 1/2 the line // Stroked paths can be outside of the path bounding box by 1/2 the line
// width. // width.
const scale = Util.singularValueDecompose2dScale(transform); Util.singularValueDecompose2dScale(transform, XY);
const xStrokePad = (scale[0] * this.lineWidth) / 2; const xStrokePad = (XY[0] * this.lineWidth) / 2;
const yStrokePad = (scale[1] * this.lineWidth) / 2; const yStrokePad = (XY[1] * this.lineWidth) / 2;
box[0] -= xStrokePad; box[0] -= xStrokePad;
box[1] -= yStrokePad; box[1] -= yStrokePad;
box[2] += xStrokePad; box[2] += xStrokePad;
@ -777,15 +780,14 @@ function getImageSmoothingEnabled(transform, interpolate) {
return true; return true;
} }
const scale = Util.singularValueDecompose2dScale(transform); Util.singularValueDecompose2dScale(transform, XY);
// Round to a 32bit float so that `<=` check below will pass for numbers that // Round to a 32bit float so that `<=` check below will pass for numbers that
// are very close, but not exactly the same 64bit floats. // are very close, but not exactly the same 64bit floats.
scale[0] = Math.fround(scale[0]);
scale[1] = Math.fround(scale[1]);
const actualScale = Math.fround( const actualScale = Math.fround(
OutputScale.pixelRatio * PixelsPerInch.PDF_TO_CSS_UNITS OutputScale.pixelRatio * PixelsPerInch.PDF_TO_CSS_UNITS
); );
return scale[0] <= actualScale && scale[1] <= actualScale; // `XY` is a Float32Array.
return XY[0] <= actualScale && XY[1] <= actualScale;
} }
const LINE_CAP_STYLES = ["butt", "round", "square"]; const LINE_CAP_STYLES = ["butt", "round", "square"];
@ -1958,12 +1960,12 @@ class CanvasGraphics {
[a, b, c, d, 0, 0], [a, b, c, d, 0, 0],
invPatternTransform invPatternTransform
); );
const [sx, sy] = Util.singularValueDecompose2dScale(transf); Util.singularValueDecompose2dScale(transf, XY);
// Cancel the pattern scaling of the line width. // Cancel the pattern scaling of the line width.
// If sx and sy are different, unfortunately we can't do anything and // If sx and sy are different, unfortunately we can't do anything and
// we'll have a rendering bug. // we'll have a rendering bug.
ctx.lineWidth *= Math.max(sx, sy) / fontSize; ctx.lineWidth *= Math.max(XY[0], XY[1]) / fontSize;
ctx.stroke( ctx.stroke(
this.#getScaledPath(path, currentTransform, patternStrokeTransform) this.#getScaledPath(path, currentTransform, patternStrokeTransform)
); );
@ -2639,9 +2641,7 @@ class CanvasGraphics {
rect[2] = width; rect[2] = width;
rect[3] = height; rect[3] = height;
const [scaleX, scaleY] = Util.singularValueDecompose2dScale( Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx), XY);
getCurrentTransform(this.ctx)
);
const { viewportScale } = this; const { viewportScale } = this;
const canvasWidth = Math.ceil( const canvasWidth = Math.ceil(
width * this.outputScaleX * viewportScale width * this.outputScaleX * viewportScale
@ -2659,7 +2659,7 @@ class CanvasGraphics {
this.annotationCanvas.savedCtx = this.ctx; this.annotationCanvas.savedCtx = this.ctx;
this.ctx = context; this.ctx = context;
this.ctx.save(); this.ctx.save();
this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY); this.ctx.setTransform(XY[0], 0, 0, -XY[1], 0, height * XY[1]);
resetCtxToDefault(this.ctx); resetCtxToDefault(this.ctx);
} else { } else {

View File

@ -398,16 +398,18 @@ class MeshShadingPattern extends BaseShadingPattern {
getPattern(ctx, owner, inverse, pathType) { getPattern(ctx, owner, inverse, pathType) {
applyBoundingBox(ctx, this._bbox); applyBoundingBox(ctx, this._bbox);
let scale; const scale = new Float32Array(2);
if (pathType === PathType.SHADING) { if (pathType === PathType.SHADING) {
scale = Util.singularValueDecompose2dScale(getCurrentTransform(ctx)); Util.singularValueDecompose2dScale(getCurrentTransform(ctx), scale);
} else { } else if (this.matrix) {
// Obtain scale from matrix and current transformation matrix. // Obtain scale from matrix and current transformation matrix.
scale = Util.singularValueDecompose2dScale(owner.baseTransform); Util.singularValueDecompose2dScale(this.matrix, scale);
if (this.matrix) { const [matrixScaleX, matrixScaleY] = scale;
const matrixScale = Util.singularValueDecompose2dScale(this.matrix); Util.singularValueDecompose2dScale(owner.baseTransform, scale);
scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]]; scale[0] *= matrixScaleX;
} scale[1] *= matrixScaleY;
} else {
Util.singularValueDecompose2dScale(owner.baseTransform, scale);
} }
// Rasterizing on the main thread since sending/queue large canvases // Rasterizing on the main thread since sending/queue large canvases
@ -517,12 +519,12 @@ class TilingPattern {
const height = y1 - y0; const height = y1 - y0;
// Obtain scale from matrix and current transformation matrix. // Obtain scale from matrix and current transformation matrix.
const matrixScale = Util.singularValueDecompose2dScale(this.matrix); const scale = new Float32Array(2);
const curMatrixScale = Util.singularValueDecompose2dScale( Util.singularValueDecompose2dScale(this.matrix, scale);
this.baseTransform const [matrixScaleX, matrixScaleY] = scale;
); Util.singularValueDecompose2dScale(this.baseTransform, scale);
const combinedScaleX = matrixScale[0] * curMatrixScale[0]; const combinedScaleX = matrixScaleX * scale[0];
const combinedScaleY = matrixScale[1] * curMatrixScale[1]; const combinedScaleY = matrixScaleY * scale[1];
let canvasWidth = width, let canvasWidth = width,
canvasHeight = height, canvasHeight = height,

View File

@ -732,23 +732,17 @@ class Util {
// This calculation uses Singular Value Decomposition. // This calculation uses Singular Value Decomposition.
// The SVD can be represented with formula A = USV. We are interested in the // The SVD can be represented with formula A = USV. We are interested in the
// matrix S here because it represents the scale values. // matrix S here because it represents the scale values.
static singularValueDecompose2dScale(m) { static singularValueDecompose2dScale([m0, m1, m2, m3], output) {
const transpose = [m[0], m[2], m[1], m[3]];
// Multiply matrix m with its transpose. // Multiply matrix m with its transpose.
const a = m[0] * transpose[0] + m[1] * transpose[2]; const a = m0 ** 2 + m1 ** 2;
const b = m[0] * transpose[1] + m[1] * transpose[3]; const b = m0 * m2 + m1 * m3;
const c = m[2] * transpose[0] + m[3] * transpose[2]; const c = m2 ** 2 + m3 ** 2;
const d = m[2] * transpose[1] + m[3] * transpose[3];
// Solve the second degree polynomial to get roots. // Solve the second degree polynomial to get roots.
const first = (a + d) / 2; const first = (a + c) / 2;
const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2; const second = Math.sqrt(first ** 2 - (a * c - b ** 2));
const sx = first + second || 1; output[0] = Math.sqrt(first + second || 1);
const sy = first - second || 1; output[1] = Math.sqrt(first - second || 1);
// Scale values are the square roots of the eigenvalues.
return [Math.sqrt(sx), Math.sqrt(sy)];
} }
// Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2) // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)