Skip to content

Fix #3037: implement pushClip/popClip on Android port#5016

Merged
shai-almog merged 3 commits into
masterfrom
fix-3037-android-pushpop-clip
May 23, 2026
Merged

Fix #3037: implement pushClip/popClip on Android port#5016
shai-almog merged 3 commits into
masterfrom
fix-3037-android-pushpop-clip

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

  • The Android port inherited the empty default pushClip(Object) / popClip(Object) bodies from CodenameOneImplementation -- iOS, JavaSE, and JavaScript all override them but Android never did. Graphics.pushClip()/popClip() were silent no-ops on Android, so any clip narrowed inside a push/pop block (especially a polygon clip created under a non-identity transform via clipRect-under-rotation) leaked to whatever was drawn after popClip().
  • Visible in the graphics-clip-under-rotation screenshot test: the navy axis-aligned reference outline drawn after popClip() was being clipped by the leftover rotated-inner-rect polygon clip, and the green sentinel dot was missing. Same symptom in graphics-clip. iOS Metal was fixed in 6b553ff; this brings Android to parity.
  • Implementation in AndroidGraphics.pushClip/popClip uses canvas.save() / canvas.restore() with a pushedClipSetStack (Deque<Boolean>) to preserve the existing clipSet-flag pairing. AsyncGraphics overrides save/restore the async-side clip state (clip, clipP, clipGP, clipIsPath) onto a savedClipStack; subsequent AsyncOps capture the restored clip via their constructor so no extra canvas op needs to be queued. AndroidImplementation delegates the impl-level calls to AndroidGraphics.
  • Verified locally on cn1Api34Arm AVD: graphics-clip-under-rotation now matches the iOS Metal / GL reference (navy outline fully visible, red rect 30deg-tilted overhanging diagonal corners, green sentinel dot present).

Test plan

  • CI `scripts-android.yml` runs all three Android matrix variants (default 8, JDK 17, JDK 21)
  • graphics-clip-under-rotation Android golden mismatch (expected on first run); update golden from CI capture artifact in a follow-up commit on this branch
  • graphics-clip Android golden also likely needs refresh (same root cause); update from CI capture if it mismatches
  • All other Android screenshot tests continue to pass (pushClip/popClip was previously a no-op so the only tests affected are those that explicitly rely on pushClip/popClip semantics, i.e. Clip and ClipUnderRotation)
  • Final CI green after golden refresh

🤖 Generated with Claude Code

The Android port inherited the empty default pushClip(Object) /
popClip(Object) bodies from CodenameOneImplementation -- iOS, JavaSE,
and JavaScript all override them but Android never did. That made
Graphics.pushClip() / popClip() silent no-ops on Android: any clip
narrowed inside a pushClip/popClip block (especially a polygon clip
created under a non-identity transform via clipRect-under-rotation)
leaked out to the rest of paint(), so geometry drawn after popClip()
was still clipped to the inner shape.

Visible in the graphics-clip-under-rotation screenshot test: the navy
axis-aligned reference outline drawn after popClip() was getting
clipped by the leftover rotated-inner-rect polygon clip and the green
sentinel dot was missing. Same symptom in graphics-clip. iOS Metal
was fixed in 6b553ff ("Update iOS Metal golden -- stencil clip now
matches GL"); this commit brings Android to parity.

Wiring:

- AndroidGraphics.pushClip() does canvas.save() and stashes the
  current clipSet flag onto a pushedClipSetStack, then resets
  clipSet=false so a setClip/setClipRaw inside the push/pop block
  doesn't restore (and prematurely pop) our outer save.
  popClip() restores the inner setClip save first if clipSet became
  true, then pops the outer save and restores the saved clipSet.

- AsyncGraphics (the form-graphics path) overrides pushClip /
  popClip to push and pop the async-side clip state (clip Rectangle,
  clipP Path, clipGP GeneralPath, clipIsPath flag) onto a
  savedClipStack. Subsequent AsyncOps capture the restored clip in
  their constructors, so no extra canvas.save / canvas.restore op
  needs to be queued for the underlying canvas -- each AsyncOp's
  executeWithClip already manages the underlying clip via
  setClipRaw.

- AndroidImplementation.pushClip(Object) / popClip(Object) now
  forward to AndroidGraphics, matching the iOS / JavaSE / JS
  delegate pattern.

Verified locally on the cn1Api34Arm AVD: graphics-clip-under-rotation
now renders with the navy outline fully visible and the red rect
30deg-tilted overhanging the diagonal corners, matching the
iOS Metal / GL reference. The Android golden for that test (and the
graphics-clip golden, which is broken the same way) will be
regenerated from the CI artifact in a follow-up commit on this
branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 23, 2026

Compared 110 screenshots: 110 matched.

Native Android coverage

  • 📊 Line coverage: 12.00% (6817/56827 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.72% (34202/351812), branch 4.20% (1403/33371), complexity 5.25% (1681/32020), method 9.12% (1366/14980), class 14.81% (304/2052)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 12.00% (6817/56827 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.72% (34202/351812), branch 4.20% (1403/33371), complexity 5.25% (1681/32020), method 9.12% (1366/14980), class 14.81% (304/2052)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 682.000 ms
Base64 CN1 encode 114.000 ms
Base64 encode ratio (CN1/native) 0.167x (83.3% faster)
Base64 native decode 1177.000 ms
Base64 CN1 decode 413.000 ms
Base64 decode ratio (CN1/native) 0.351x (64.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

shai-almog and others added 2 commits May 23, 2026 18:01
Both goldens encoded the pushClip/popClip no-op bug fixed in the
previous commit. The Android-default-8 CI job on PR #5016 captured
the post-fix renders (Default: 8 is configured without
CN1SS_FAIL_ON_MISMATCH, so the actual emulator screenshots are
uploaded to the emulator-screenshot artifact even when they don't
match the golden); promote those exact bytes here so the JDK 17 /
JDK 21 matrix variants -- which do fail on mismatch -- now pass.

graphics-clip-under-rotation: post-fix render shows the navy
axis-aligned reference outline fully visible, the red rect is
30deg-tilted and overhangs the navy outline at the two diagonal
corners, and the green sentinel dot drawn after popClip() is
visible at each panel's top-left. Matches iOS Metal / GL.

graphics-clip: post-fix render shows the green sentinel dot at
each panel's bottom-right -- it was getting clipped away by the
leftover triangular clip from the previous pushClip block on the
old Android render.

Bytes are the CI artifact captured by the Default 8 emulator at
PR #5016 run 26334855513.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SpotBugs RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE flagged the
`(prev != null) && prev.booleanValue()` guard on the Boolean popped
from pushedClipSetStack. ArrayDeque rejects null elements at push
time, so a successful pop() can never return null, and we already
guarded the empty-stack case with `pushedClipSetStack.isEmpty()` at
the top of popClip(). Replace with a direct `.pop().booleanValue()`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog shai-almog linked an issue May 23, 2026 that may be closed by this pull request
@shai-almog shai-almog merged commit 2c3a932 into master May 23, 2026
14 checks passed
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.

incorrent affine transform on IOS

1 participant