Skip to content

fix(🐛): scale SkMatrix uniformly when y is omitted - #4016

Merged
wcandillon merged 5 commits into
Shopify:mainfrom
dennytosp:fix/matrix-scale-uniform-default
Aug 23, 2026
Merged

fix(🐛): scale SkMatrix uniformly when y is omitted#4016
wcandillon merged 5 commits into
Shopify:mainfrom
dennytosp:fix/matrix-scale-uniform-default

Conversation

@dennytosp

Copy link
Copy Markdown
Contributor

SkMatrix.scale() and postScale() declare y as optional:

scale: (x: number, y?: number) => SkMatrix;
postScale: (x: number, y?: number) => SkMatrix;

The two backends disagree on what omitting it means.

CanvasKit (JsiSkMatrix.ts) falls back to x — a uniform scale:

scale(x: number, y?: number) {
  this.preMultiply(this.CanvasKit.Matrix.scaled(x, y ?? x));
}

JSI (JsiSkMatrix.h) falls back to 1 — the y axis is left untouched:

void scale(double x, JsiOptional<double> y) {
  getObject()->preScale(x, y.has_value() ? *y : 1);
}

So matrix.scale(0.5) halves both axes on Web and only the x axis on iOS and Android.

Which one is right

The renderer's own test pins the uniform result:

// packages/skia/src/renderer/__tests__/Transform.spec.tsx
matrix.translate(origin.x, origin.y).scale(0.5).translate(-origin.x, -origin.y);
expect(matrix.get()).toStrictEqual([0.5, 0, 192, 0, 0.5, 192, 0, 0, 1]);
//                                            ^^^ scaleY is 0.5, not 1

That spec is not under e2e/, so it only ever runs against CanvasKit — which is why the native path was never covered. It also renders into snapshots/transform/scale-origin.png, the same baseline the transform={[{ scale: 0.5 }]} test uses, so the intent is unambiguous.

Fix

Default y to x in the JSI bindings. Not a recent regression — the 1 default predates the typed-bindings refactor in 6c95fc6.

Tests

packages/skia/src/skia/__tests__/Matrix.spec.ts covers scale, postScale, and the explicit two-argument form.

To be upfront: this spec exercises the CanvasKit backend, so it passes on main too — it pins the contract the JSI side now matches rather than reproducing the failure. I have no Skia build here to exercise JsiSkMatrix.h directly, so the C++ side rests on SkMatrix::preScale/postScale semantics and the expectation above. Worth a native run on your side before merging.

Full suite green: 749 passed, tsc --noEmit and eslint --max-warnings 0 clean, clang-format -n -Werror clean on the changed header.

SkMatrix.scale() and postScale() take an optional y. The CanvasKit
backend falls back to x for a uniform scale, but the JSI bindings fell
back to 1, so matrix.scale(0.5) halved only the x axis on iOS and
Android while halving both on Web.

The renderer's own "Scale with origin using a matrix" test pins the
uniform result ([0.5, 0, 192, 0, 0.5, 192, 0, 0, 1]), but it runs on
CanvasKit only, so the native path was never covered.
@wcandillon
wcandillon self-requested a review August 22, 2026 09:05

@wcandillon wcandillon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dennytosp Thank You for catching this. Can you update the test to use:

const result = await surface.eval((Skia) => {

See https://github.com/Shopify/react-native-skia/blob/main/packages/skia/CONTRIBUTING.md#writing-end-to-end-tests

that way it will run both against native and canvaskit. In its current form it only runs against canvaskit.

Move it to renderer/__tests__/e2e and go through surface.eval, so the
JSI bindings this fixes are actually exercised. As a plain unit test it
only ever ran against CanvasKit, which was already correct.
@dennytosp

Copy link
Copy Markdown
Contributor Author

@wcandillon good catch, thank you — I had missed that the unit spec never touched the JSI side at all. It goes through surface.eval((Skia) => ...) now, in e2e/Matrix.spec.ts, so the native bindings are genuinely covered. Mind taking another pass?

@dennytosp
dennytosp requested a review from wcandillon August 22, 2026 12:30
@wcandillon
wcandillon merged commit 73b5857 into Shopify:main Aug 23, 2026
10 of 11 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.11.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants