Skip to content

Commit e273a74

Browse files
shai-almogclaude
andcommitted
js-port: reset canvas transform at drain start (#137)
drainPendingDisplayFrame starts a frame by capturing a save() and issuing a ``context.rect(cropX, cropY, cropW, cropH); context.clip();`` to bound the frame to the dirty region — then optionally clearRect inside that clip. Both the rect and the clearRect are evaluated under whatever transform was active on the canvas when save() ran. ClipShape ops leave the canvas at the shape's clipTransform (a rotation when a clipRect was issued under rotateRadians, for example) and that transform survives across drains via the outer save()/restore() pair: each new drain inherits the previous drain's tail-end transform. Visible symptom in the master JS golden for ``graphics-clip-under-rotation``: the entire form — title bar, cells, even the green sentinel dots — appears rotated ~30deg, identical to the test's own pivot rotation, even though the test's ``rotateRadians(+angle) + rotateRadians(-angle)`` pair cancels mathematically. Cause: the test cell's rotated polygon clip leaves the canvas at rotate(+30). The form's NEXT drain captures that rotated transform with save(), clips to a rotated rectangle, draws into it. The drawn content stays where SetTransform ops place it, but pixels OUTSIDE the rotated clip mask are leftover from the previous (also-rotated) drain — so visually the whole frame looks 30deg-rotated. iOS GL and Android don't hit this because their per-frame draw setup explicitly normalises the device transform before applying the clip; the JS port did not. Fix: emit ``context.setTransform(1, 0, 0, 1, 0, 0)`` after save() and before the clip-rect / clearRect, so the drain's bounding clip is always evaluated in device-coordinate space regardless of what the previous drain ended with. The subsequent SetTransform ops in the frame queue restore the per-paint transform as they always did. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 12545db commit e273a74

1 file changed

Lines changed: 16 additions & 0 deletions

File tree

Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,6 +2296,22 @@ public void setGraphicsLocked(boolean locked) {
22962296
}
22972297
CanvasRenderingContext2D context = (CanvasRenderingContext2D)outputCanvas.getContext("2d");
22982298
context.save();
2299+
// The crop rect and the optional clearRect below are interpreted
2300+
// in whatever transform was active on the canvas when ``save()``
2301+
// ran. ClipShape ops leave the canvas at the shape's transform
2302+
// (e.g. a rotation, when a clipRect runs under a rotateRadians)
2303+
// and a subsequent drain inherits that transform via the
2304+
// outer-most save/restore pair -- so without resetting here, the
2305+
// drain's clip becomes a rotated rectangle and clearRect wipes
2306+
// only a rotated wedge, leaving the form to be painted INSIDE
2307+
// that rotated mask while pixels OUTSIDE the mask are leftover
2308+
// content from the previous drain. Visible symptom in the
2309+
// ``graphics-clip-under-rotation`` screenshot test: the entire
2310+
// form (title bar + cells) appears rotated ~30deg even though
2311+
// the test's own ``rotateRadians(+angle) + rotateRadians(-angle)``
2312+
// pair cancels mathematically. Force the drain's initial clip
2313+
// and clear to run in device-coordinate space.
2314+
context.setTransform(1, 0, 0, 1, 0, 0);
22992315
context.beginPath();
23002316
context.rect(frame.getCropX(), frame.getCropY(), frame.getCropW(), frame.getCropH());
23012317
context.clip();

0 commit comments

Comments
 (0)