Skip to content

Fix bug where using transform with image data results in twice-transformed clip path - #273

Open
canislupaster wants to merge 1 commit into
samizdatco:mainfrom
canislupaster:main
Open

Fix bug where using transform with image data results in twice-transformed clip path#273
canislupaster wants to merge 1 commit into
samizdatco:mainfrom
canislupaster:main

Conversation

@canislupaster

@canislupaster canislupaster commented Oct 25, 2025

Copy link
Copy Markdown

I was wondering why I was getting different results when downscaling my canvas. It took me a while to isolate that the issue required both clipping and image data. Here's a PoC (now a visual test):

tests['clip path after image data'] = function (ctx) {
  ctx.scale(10,10);
  const data = ctx.getImageData(0,0,1,1);
  ctx.putImageData(data,0,0);

  ctx.rect(5,5,10,10);
  ctx.clip();

  ctx.rect(0,0,15,15);
  ctx.fillStyle="black";
  ctx.fill();
}

This was blank with skia-canvas but draws a square with canvas.

It occurs since setting image data restores to save count 1, and then set_matrix during Context2D::pop overwrites the matrix at save count 1. When the clip path is loaded again with set_clip, it has already been transformed and undergoes the same transform again at save count 1.

The Skia docs note that restoreToCount

Does nothing if saveCount is greater than state stack count. Restores state to initial values if saveCount is less than or equal to one.

but the former behavior takes precedence (I think / apparently), so the matrix isn't reset during PageRecorder::restore.

I simplified things so both set_matrix and set_clip call restore (though this could be split up again if there are other concerns like performance). PageRecorder::restore calls save after restoring to save count 1, so it maintains the invariant that save count 1 has no clip path and the identity matrix, which set_matrix previously violated.

@samizdatco samizdatco mentioned this pull request Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant