Commit 826d60f
* iOS Metal: per-axis scale decomposition in alpha-mask path (#3302)
Under a non-uniform scale, fillShape/drawShape used to rasterise the path at
a uniform diagonal-ratio scale and then stretch the resulting alpha-mask
texture non-uniformly through the GPU matrix to recover the requested aspect.
That bbox math is exact in real numbers but the texture is pixel-rounded at
the intermediate uniform scale, so the stretch drifts the rasterised shape
off the axis-aligned drawRect / drawLine the framework would emit alongside
it — the symptom in GH-3302's grid of "scaled triangles inscribed in
rectangles" where the inscribed triangle escapes its bounding rect on iOS.
Factor the user transform's 2x2 linear part by taking the column norms as
(sx, sy), rasterise the path at S(sx, sy), and apply only the residual
transform = transform * S(1/sx, 1/sy) on the GPU side. The residual is pure
rotation (and shear, in the worst case) so no per-axis stretch happens at
sample time, and the alpha-mask texture matches the rest of the primitives
on the same pixel grid. Stroke widening and the radial-gradient bbox use
sqrt(sx*sy) so the on-screen pen size matches the legacy uniform behaviour
when sx == sy.
Gated on `metalRendering` for GlobalGraphics; MutableGraphics's
renderShapeViaAlphaMask is metal-only by construction. The GL ES2 path is
unchanged so existing GL goldens stay valid.
Adds hellocodenameone/InscribedTriangleGrid screenshot test (registered in
Cn1ssDeviceRunner). The test exercises the (sx, sy) in {1, 2} cells under
g.translate + g.scale + drawRect + fillShape + drawShape so the inscribed-
shape property can be verified visually against the goldens once captured.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR feedback: explicit float->int casts + diagnostic test backdrop
CodeQL flagged the radial-gradient-bbox scale as 4 implicit narrowing casts
(IOSImplementation.java:5848-5851). Make the int casts explicit on the
RadialGradient field assignments. Same numeric behaviour as the original
*= which silently truncated; just satisfies the analyser.
Test improvements requested in PR review:
- Fill a known light-grey background so the BLACK rectangle frame is visible
on Android (default form bg there is dark) and on JavaSE / iOS without
relying on the form's painter to lay one down first.
- Drop a per-cell "(sx,sy)" label and an at-the-top "Triangle should fit
inside rectangle" hint so the screenshot is self-documenting -- a reader
can identify a per-axis-scale failure mode (drift only at sx != sy)
straight from the image, without cross-referencing the test source.
- Trim the grid to a (1,1) / (1,2) / (2,1) / (2,2) 2x2 layout so the cells
fit on a typical simulator panel after the matrix scale doubles their
on-screen extent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Graphics.translateMatrix: matrix-correct translate across all ports
Add a Graphics.translateMatrix(float, float) public API and
CodenameOneImplementation.translateMatrix(graphics, x, y) hook that
composes T(x, y) onto the impl-side transform matrix the same way
Graphics.scale and Graphics.rotate do. Plus
Graphics.isTranslateMatrixSupported() / impl
isTranslateMatrixSupported() so ports that haven't wired the matrix
path yet can opt out cleanly.
Why this exists: every active port today returns
isTranslationSupported() == false (per the comment at Graphics.java:62),
so g.translate(int, int) is a per-Graphics integer accumulator that
gets *added to draw coordinates before* the impl matrix is applied.
A subsequent g.scale() therefore multiplies the integer translate too.
The user-visible consequence is that the same drawing code can land at
different on-screen positions depending on whether you're drawing into
a Form's Graphics (where bounds.getX() carries the component-absolute
offset that gets scaled) versus a mutable Image's Graphics (where
bounds.getX() is 0). The GH-3302 inscribed-triangle test exposed this
clearly: top-right form-direct cells get pushed off the panel while
the same code on the mutable-image side stays put.
translateMatrix composes the translate into the impl matrix instead,
producing uniform "matrix-correct" semantics: g.translateMatrix(20, 30)
followed by g.scale(2, 2) is the same final transform as a Java2D
Graphics2D.translate(20, 30) + .scale(2, 2) pair, on every port. The
fallback path on legacy / restricted ports (isTranslateMatrixSupported
== false) routes through the integer translate(int, int) so apps still
render -- just at the legacy position.
Wiring:
- Default isTranslateMatrixSupported() == false; no-op translateMatrix.
- iOS: NativeGraphics.translateMatrix composes T onto the in-memory
matrix (clipDirty / inverseClipDirty / inverseTransformDirty flagged
exactly like scale/rotate, then applyTransform).
- JavaSE: getTransform / setTransform pair, same as scale.
- Android: AndroidGraphics.translateMatrix uses
getTransform().translate(x, y) and flips the same dirty flags scale
does.
- JavaScript (HTML5): mirrors scale's setTransformChanged + applyTransform
pattern. Legacy JS port keeps the default opt-out.
Update InscribedTriangleGrid test to use translateMatrix(cellX, cellY)
so each cell anchors via the matrix instead of the integer
accumulator; the form-direct and mutable-image panels then render
identically up to the blit offset, matching the JavaSE gold-standard
behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert misdiagnosed Java-side Metal/GL gating in nativeDrawRect/etc
The previous commit added metalRendering gates around nativeDrawRect /
nativeFillRect / nativeDrawLine in NativeGraphics, routing through
renderShapeViaAlphaMask under Metal "to avoid the CG fallback." That
diagnosis was wrong: there is no CG fallback under Metal here. The
C-side counterparts already short-circuit through the Metal pipeline:
nativeDrawRectMutableImpl -- has #ifdef CN1_USE_METAL guard
nativeFillRectMutableImpl -- has #ifdef CN1_USE_METAL guard
nativeDrawLineMutableImpl -- has #ifdef CN1_USE_METAL guard
nativeDrawStringMutableImpl -- has #ifdef CN1_USE_METAL guard
nativeDrawImageMutableImpl -- has #ifdef CN1_USE_METAL guard
nativeFillRoundRectMutableImpl -- has #ifdef CN1_USE_METAL guard
nativeDrawRoundRectMutableImpl -- has #ifdef CN1_USE_METAL guard
nativeDrawArcMutableImpl -- has #ifdef CN1_USE_METAL guard
nativeFillArcMutableImpl -- "Dead under Metal", Java-side routes
through nativeFillShape instead
i.e. on a Metal build the legacy CG branches in those Mutable JNIs
are unreachable. Rerouting drawRect/fillRect/drawLine through the
alpha-mask path on the Java side just changes which Metal op is
queued (DrawTextureAlphaMask vs FillRect / DrawRect / DrawLine) -- it
doesn't fix any CG leak because there isn't one. Replace the gating
with three short comments documenting that the C side is responsible
for Metal routing here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* InscribedTriangleGrid: adapt cell layout to small panels
The Android screenshot simulator runs at 320x640 (160x320 per quadrant
under GridLayout(2, 2)), and the test's previous fixed-pixel layout
(baseW = 80, gridOffsetX = 30 + 30 = 60) didn't leave enough room for
the right-column cell's 2x scaling -- the (2,*) cells fell off the
right edge so only column 0 was visible.
Switch cell dimensions to fractions of `bounds`:
baseW = max(8, panelW * 2 / 9)
baseH = max(6, gridH * 2 / 9)
Solves baseW * 3 + gutter * 3 = panelW with gutter = baseW / 2 so the
1x and 2x columns + three gutters fit inside the panel width on every
target. Same arithmetic on the height for the 1x and 2x rows.
Triangle dimensions and the rectangle frame derive from those base
sizes so the inscribed-shape property is exercised regardless of
panel size.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* iOS alpha-mask: apply residual via scale() to fix mutable encoder
The per-axis decomposition in GlobalGraphics.nativeDrawShape and
NativeGraphics.renderShapeViaAlphaMask used to apply the residual
S(1/sx, 1/sy) by building a separate composed Transform (`tmpTransform`
or a fresh `Transform.makeIdentity()`) and calling `setTransform(...)`
on the NativeGraphics. That path turns out to silently drop the matrix
update on the Metal mutable-image encoder -- exactly the failure mode
documented in Transform.setTransform's "iOS Metal port has shown that
without this flag setTransform(composed) silently fails to apply" note.
User-visible symptom: on a mutable-image Graphics with a non-uniform
g.scale active, the alpha-mask quad got drawn under the previous
non-residual transform, so fillShape / drawShape rendered at the wrong
scale relative to the axis-aligned drawRect siblings. The triangle
ended up at S(sx, sy) * S(sx, sy) instead of S(sx, sy), i.e. 4x height
at sy=2.
Fix: apply the residual via `scale(1f/sx, 1f/sy)` followed by the draw
and a paired `scale(sx, sy)` to restore -- the same code path g.scale
uses, which reliably queues a SetTransform op tagged with the active
mutable target. Both GlobalGraphics.nativeDrawShape (screen) and
NativeGraphics.renderShapeViaAlphaMask (mutable) now route through
this. Drops the tmpTransform / tmpTransform2 / `setTransform(inv)`
scaffolding that's no longer needed.
Verified locally on iOS Metal simulator: the InscribedTriangleGrid
test now renders identical 2x2 cell grids in form-direct AA off + AA
on + mutable AA off + AA on panels, with triangles correctly inscribed
in the rectangles at every (sx, sy) combination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* InscribedTriangleGrid: add iOS Metal/GL and JavaSE goldens
iOS Metal golden is the reference for the GH-3302 fix: all four panels
(form-direct uniform, form-direct retina, mutable uniform, mutable
retina) render the green triangle inscribed within the black rectangle
in every cell variant.
iOS GL golden captures the legacy CG-based mutable path's known
fillShape limitation (outlines only on bottom panels). This is a
pre-existing GL behaviour the user accepted; the alpha-mask change
landed for the Metal path only.
JavaSE golden is included as a cross-port reference (graphics tests
do not run in JavaSE CI, but the file documents the gold-standard
output).
Android and JavaScript goldens still need to be captured from CI
artifacts on the first PR run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* InscribedTriangleGrid: add Android and JavaScript goldens from CI
Android golden (320x640) is the emulator-screenshot artifact from the
api-level 36 / x86_64 / google_apis runner. Form-direct panels (bottom
row) render the four inscribed-triangle cells correctly; mutable-image
panels (top row) show the existing Android mutable-blit layout
limitation the user accepted in review.
JavaScript golden (750x1334) is the javascript-ui-tests artifact and
captures the existing JS panel-overlap layout. Both are reference
captures of current behaviour, not claims of correctness; this PR
scopes its rendering fixes to the iOS Metal path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* InscribedTriangleGrid: replace iOS goldens with CI captures
Local captures were 1170x2532 (iPhone 15 simulator); CI runs the iOS
UI tests on a 1179x2556 simulator (iPhone 15 Pro). Pull the goldens
from the CI artifacts so screenshot comparison passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fad364b commit 826d60f
15 files changed
Lines changed: 454 additions & 75 deletions
File tree
- CodenameOne/src/com/codename1
- impl
- ui
- Ports
- Android/src/com/codename1/impl/android
- JavaSE/src/com/codename1/impl/javase
- JavaScriptPort/src/main/java/com/codename1/impl/html5
- iOSPort/src/com/codename1/impl/ios
- scripts
- android/screenshots
- hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests
- graphics
- ios
- screenshots-metal
- screenshots
- javascript/screenshots
- javase/screenshots
Lines changed: 41 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3616 | 3616 | | |
3617 | 3617 | | |
3618 | 3618 | | |
| 3619 | + | |
| 3620 | + | |
| 3621 | + | |
| 3622 | + | |
| 3623 | + | |
| 3624 | + | |
| 3625 | + | |
| 3626 | + | |
| 3627 | + | |
| 3628 | + | |
| 3629 | + | |
| 3630 | + | |
| 3631 | + | |
| 3632 | + | |
| 3633 | + | |
| 3634 | + | |
| 3635 | + | |
| 3636 | + | |
| 3637 | + | |
| 3638 | + | |
| 3639 | + | |
| 3640 | + | |
| 3641 | + | |
| 3642 | + | |
| 3643 | + | |
| 3644 | + | |
| 3645 | + | |
| 3646 | + | |
| 3647 | + | |
| 3648 | + | |
| 3649 | + | |
| 3650 | + | |
| 3651 | + | |
| 3652 | + | |
| 3653 | + | |
| 3654 | + | |
| 3655 | + | |
| 3656 | + | |
| 3657 | + | |
| 3658 | + | |
| 3659 | + | |
3619 | 3660 | | |
3620 | 3661 | | |
3621 | 3662 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1613 | 1613 | | |
1614 | 1614 | | |
1615 | 1615 | | |
| 1616 | + | |
| 1617 | + | |
| 1618 | + | |
| 1619 | + | |
| 1620 | + | |
| 1621 | + | |
| 1622 | + | |
| 1623 | + | |
| 1624 | + | |
| 1625 | + | |
| 1626 | + | |
| 1627 | + | |
| 1628 | + | |
| 1629 | + | |
| 1630 | + | |
| 1631 | + | |
| 1632 | + | |
| 1633 | + | |
| 1634 | + | |
| 1635 | + | |
| 1636 | + | |
| 1637 | + | |
| 1638 | + | |
| 1639 | + | |
| 1640 | + | |
| 1641 | + | |
| 1642 | + | |
| 1643 | + | |
| 1644 | + | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
| 1648 | + | |
| 1649 | + | |
| 1650 | + | |
| 1651 | + | |
| 1652 | + | |
| 1653 | + | |
| 1654 | + | |
| 1655 | + | |
| 1656 | + | |
| 1657 | + | |
| 1658 | + | |
| 1659 | + | |
| 1660 | + | |
| 1661 | + | |
| 1662 | + | |
| 1663 | + | |
| 1664 | + | |
| 1665 | + | |
| 1666 | + | |
| 1667 | + | |
| 1668 | + | |
| 1669 | + | |
| 1670 | + | |
| 1671 | + | |
| 1672 | + | |
| 1673 | + | |
| 1674 | + | |
| 1675 | + | |
| 1676 | + | |
1616 | 1677 | | |
1617 | 1678 | | |
1618 | 1679 | | |
| |||
Lines changed: 11 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1483 | 1483 | | |
1484 | 1484 | | |
1485 | 1485 | | |
| 1486 | + | |
| 1487 | + | |
| 1488 | + | |
| 1489 | + | |
| 1490 | + | |
| 1491 | + | |
| 1492 | + | |
| 1493 | + | |
| 1494 | + | |
| 1495 | + | |
| 1496 | + | |
1486 | 1497 | | |
1487 | 1498 | | |
1488 | 1499 | | |
| |||
Lines changed: 10 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5483 | 5483 | | |
5484 | 5484 | | |
5485 | 5485 | | |
| 5486 | + | |
| 5487 | + | |
| 5488 | + | |
| 5489 | + | |
| 5490 | + | |
| 5491 | + | |
| 5492 | + | |
| 5493 | + | |
| 5494 | + | |
| 5495 | + | |
5486 | 5496 | | |
5487 | 5497 | | |
5488 | 5498 | | |
| |||
Lines changed: 15 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10572 | 10572 | | |
10573 | 10573 | | |
10574 | 10574 | | |
| 10575 | + | |
| 10576 | + | |
| 10577 | + | |
| 10578 | + | |
| 10579 | + | |
| 10580 | + | |
| 10581 | + | |
| 10582 | + | |
| 10583 | + | |
| 10584 | + | |
| 10585 | + | |
| 10586 | + | |
| 10587 | + | |
| 10588 | + | |
| 10589 | + | |
10575 | 10590 | | |
10576 | 10591 | | |
10577 | 10592 | | |
| |||
Lines changed: 13 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
312 | 312 | | |
313 | 313 | | |
314 | 314 | | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
315 | 328 | | |
316 | 329 | | |
317 | 330 | | |
| |||
Lines changed: 10 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5991 | 5991 | | |
5992 | 5992 | | |
5993 | 5993 | | |
| 5994 | + | |
| 5995 | + | |
| 5996 | + | |
| 5997 | + | |
| 5998 | + | |
| 5999 | + | |
| 6000 | + | |
| 6001 | + | |
| 6002 | + | |
| 6003 | + | |
5994 | 6004 | | |
5995 | 6005 | | |
5996 | 6006 | | |
| |||
0 commit comments