Skip to content

Graphics.useMatrixTranslation: route g.translate through impl matrix (GH-3302)#4960

Open
shai-almog wants to merge 13 commits into
masterfrom
matrix-translate-flag
Open

Graphics.useMatrixTranslation: route g.translate through impl matrix (GH-3302)#4960
shai-almog wants to merge 13 commits into
masterfrom
matrix-translate-flag

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

Adds a static Graphics.useMatrixTranslation opt-in that flips every g.translate(int, int) in the rendering chain through the impl-side affine matrix (via translateMatrix) instead of the legacy per-Graphics integer accumulator that was added to draw coords. Closes the residual Y-axis drift from GH-3302's direct-to-screen mode.

Why

The framework's container/component painting pipeline stacks g.translate(absX, absY) calls before user paint() runs. With the legacy integer accumulator a user g.scale(sx, sy) inside paint() multiplied the chrome offset (status-bar + title-area height), pushing direct-to-screen drawing off the Y axis. Buffered (mutable-image) drawing avoided this because the offscreen Graphics started with xTranslate=0.

With the flag on AND impl.isTranslateMatrixSupported() == true (iOS, Android, JavaSE, HTML5), translate(int, int) composes onto the impl matrix the same way scale/rotate do. xTranslate/yTranslate is still bumped as a shadow accumulator so existing getTranslateX/getTranslateY consumers in Form/Component/Label/Container/FontImage/LinearGradientPaint/TextSelection keep returning legacy values; the drawing primitives no longer add it (the matrix carries it).

Ports that opt out (default isTranslateMatrixSupported() == false) keep using the legacy path regardless of the flag — no port needs lockstep changes.

Changes

  • Graphics.java: flag + matrixMode()/tx/ty helpers; 25 primitive sites switched from xTranslate + x to tx(x); shape pre-shift gated in setClip(Shape)/drawShape/fillShape/fillPolygon/drawPolygon; getClipX/Y skips - xTranslate in matrix mode; rotateRadians pivot compensation is a no-op; setTransform composes T(xt,yt) * M without the legacy terminal T(-xt,-yt).
  • TestCodenameOneImplementation: setTranslateMatrixSupported + translateMatrix tracking so unit tests exercise both paths.
  • GraphicsTest: 4 new tests covering matrix-mode push-through, no-pre-shift, opt-out fallback, shape pre-translate skip. Full unittests suite (2429 tests) green.
  • HelloCodenameOne.init(): flips Graphics.useMatrixTranslation = true so the screenshot suite exercises matrix mode end-to-end.
  • New TranslateThenScale screenshot test under hellocodenameone graphics/ for the direct-to-screen g.translate + g.scale repro from the issue.

Test plan

  • Core module compiles (JDK 8)
  • hellocodenameone-common compiles with the flag flip (JDK 17)
  • Full codenameone-core-unittests suite — 2429 / 2429 pass
  • CI screenshot suite: expected diffs in graphics-inscribed-triangle-grid form-direct panels and any test mixing framework container translates with user g.scale/g.rotate in direct-to-screen mode. Mutable-image panels unaffected. Goldens to be promoted after visual review.
  • iOS / Android / JS port screenshots reviewed for the same delta.

🤖 Generated with Claude Code

Add a static Graphics.useMatrixTranslation opt-in that flips every
g.translate(int, int) in the rendering chain through the impl-side
affine matrix (via translateMatrix) instead of into the per-Graphics
integer accumulator that historically was added to draw coords.

Why this matters: the framework's container/component painting pipeline
stacks g.translate(absX, absY) calls before user paint() runs. With the
legacy integer accumulator, a user g.scale(sx, sy) inside paint()
multiplies the chrome offset (status-bar + title-area height), pushing
direct-to-screen drawing off the Y axis -- exactly the residue from
GH-3302's follow-up. Buffered (mutable-image) drawing avoided this
because the offscreen Graphics started with xTranslate=0.

With the flag on AND impl.isTranslateMatrixSupported() == true (iOS,
Android, JavaSE, HTML5 today), translate(int, int) now composes onto
the impl matrix the same way scale/rotate do, so the chrome offset
flows through the matrix and is not stretched by a subsequent
g.scale. xTranslate/yTranslate is still bumped as a shadow accumulator
so existing getTranslateX/getTranslateY consumers in
Form/Component/Label/Container/FontImage/LinearGradientPaint/
TextSelection keep returning legacy values; the drawing primitives no
longer add it (the impl matrix carries the translate). 25 primitive
sites switched from `xTranslate + x` to a `tx(x)` helper, plus shape
pre-shift conditionals in setClip(Shape)/drawShape/fillShape/
fillPolygon/drawPolygon were gated, getClipX/Y returns the impl's
matrix-local clip directly, rotateRadians' pivot compensation is a
no-op in matrix mode, and setTransform composes T(xt,yt) * M without
the legacy terminal T(-xt,-yt).

Ports that opt out (default isTranslateMatrixSupported() == false)
keep using the legacy integer accumulator regardless of the flag, so
no port has to be touched in lockstep.

Wiring:
- TestCodenameOneImplementation grows setTranslateMatrixSupported +
  translateMatrix tracking so unit tests can exercise both paths.
- GraphicsTest covers: matrix-mode pushes through impl.translateMatrix
  while keeping the shadow accumulator; draw coords are NOT pre-shifted;
  legacy fallback when impl opts out; shape pre-translate is skipped.
- HelloCodenameOne.init() flips the flag on so the existing screenshot
  suite exercises matrix mode end-to-end. New TranslateThenScale test
  added under hellocodenameone graphics/ to exercise the direct-to-
  screen g.translate + g.scale path that was the original repro.

Expected: form-direct panels in graphics-inscribed-triangle-grid and
any other screenshot that mixes the framework's container translates
with user g.scale/g.rotate in direct-to-screen mode will shift, since
those goldens captured the legacy bug. Mutable-image panels are
unaffected.

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

shai-almog commented May 16, 2026

JavaScript port screenshot updates

Compared 18 screenshots: 0 matched, 17 updated, 1 missing reference.

  • chart-bar — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-bar
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-bar.png in workflow artifacts.

  • chart-bar-stacked — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-bar-stacked
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-bar-stacked.png in workflow artifacts.

  • chart-bubble — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-bubble
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-bubble.png in workflow artifacts.

  • chart-combined-xy — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-combined-xy
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-combined-xy.png in workflow artifacts.

  • chart-cubic-line — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-cubic-line
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-cubic-line.png in workflow artifacts.

  • chart-doughnut — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-doughnut
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-doughnut.png in workflow artifacts.

  • chart-line — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-line
    Preview info: JPEG preview quality 40; JPEG preview quality 40.
    Full-resolution PNG saved as chart-line.png in workflow artifacts.

  • chart-pie — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-pie
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-pie.png in workflow artifacts.

  • chart-radar — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-radar
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-radar.png in workflow artifacts.

  • chart-range-bar — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-range-bar
    Preview info: JPEG preview quality 50; JPEG preview quality 50.
    Full-resolution PNG saved as chart-range-bar.png in workflow artifacts.

  • chart-rotated-pie — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-rotated-pie
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-rotated-pie.png in workflow artifacts.

  • chart-scatter — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-scatter
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-scatter.png in workflow artifacts.

  • chart-time — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-time
    Preview info: JPEG preview quality 30; JPEG preview quality 30.
    Full-resolution PNG saved as chart-time.png in workflow artifacts.

  • chart-transform — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    chart-transform
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-transform.png in workflow artifacts.

  • graphics-clip-under-rotation — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    graphics-clip-under-rotation
    Preview info: JPEG preview quality 60; JPEG preview quality 60.
    Full-resolution PNG saved as graphics-clip-under-rotation.png in workflow artifacts.

  • graphics-inscribed-triangle-grid — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    graphics-inscribed-triangle-grid
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-inscribed-triangle-grid.png in workflow artifacts.

  • graphics-large-stroke-dirty-clip — updated screenshot. Screenshot differs (750x1334 px, bit depth 8).

    graphics-large-stroke-dirty-clip
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-large-stroke-dirty-clip.png in workflow artifacts.

  • graphics-translate-then-scale — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/javascript/screenshots/graphics-translate-then-scale.png.

    graphics-translate-then-scale
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-translate-then-scale.png in workflow artifacts.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

✅ 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
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 16, 2026

Android screenshot updates

Compared 108 screenshots: 93 matched, 14 updated, 1 missing reference.

  • BrowserComponent — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    BrowserComponent
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as BrowserComponent.png in workflow artifacts.

  • LightweightPickerButtons — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    LightweightPickerButtons
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as LightweightPickerButtons.png in workflow artifacts.

  • LightweightPickerButtons_above_center — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    LightweightPickerButtons_above_center
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as LightweightPickerButtons_above_center.png in workflow artifacts.

  • LightweightPickerButtons_below_right — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    LightweightPickerButtons_below_right
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as LightweightPickerButtons_below_right.png in workflow artifacts.

  • LightweightPickerButtons_between_mixed — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    LightweightPickerButtons_between_mixed
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as LightweightPickerButtons_between_mixed.png in workflow artifacts.

  • ValidatorLightweightPicker — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    ValidatorLightweightPicker
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as ValidatorLightweightPicker.png in workflow artifacts.

  • chart-rotated-pie — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    chart-rotated-pie
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-rotated-pie.png in workflow artifacts.

  • chart-transform — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    chart-transform
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as chart-transform.png in workflow artifacts.

  • graphics-affine-scale — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    graphics-affine-scale
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-affine-scale.png in workflow artifacts.

  • graphics-inscribed-triangle-grid — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    graphics-inscribed-triangle-grid
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-inscribed-triangle-grid.png in workflow artifacts.

  • graphics-rotate — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    graphics-rotate
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-rotate.png in workflow artifacts.

  • graphics-scale — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    graphics-scale
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-scale.png in workflow artifacts.

  • graphics-transform-rotation — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    graphics-transform-rotation
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-transform-rotation.png in workflow artifacts.

  • graphics-transform-translation — updated screenshot. Screenshot differs (320x640 px, bit depth 8).

    graphics-transform-translation
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-transform-translation.png in workflow artifacts.

  • graphics-translate-then-scale — missing reference. Reference screenshot missing at /home/runner/work/CodenameOne/CodenameOne/scripts/android/screenshots/graphics-translate-then-scale.png.

    graphics-translate-then-scale
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as graphics-translate-then-scale.png in workflow artifacts.

Native Android coverage

  • 📊 Line coverage: 11.57% (6410/55425 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.26% (31959/345270), branch 3.96% (1297/32780), complexity 5.08% (1598/31442), method 8.85% (1303/14717), class 14.89% (297/1995)
    • 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 1639.000 ms
Base64 CN1 encode 129.000 ms
Base64 encode ratio (CN1/native) 0.079x (92.1% faster)
Base64 native decode 959.000 ms
Base64 CN1 decode 199.000 ms
Base64 decode ratio (CN1/native) 0.208x (79.2% faster)
Image encode benchmark status skipped (SIMD unsupported)

…ll sites

The previous revision kept xTranslate/yTranslate accumulating alongside
the impl-matrix translate so getTranslateX/getTranslateY could keep
returning legacy values for framework callers. That broke ~103 of 108
screenshot tests because the framework's bypass-Graphics fast paths
(Label.drawLabelComponent, BGPainter.paintComponentBackground) add
getTranslateX() onto local coords and hand the absolute result straight
to Display.impl, which then applies the matrix that ALREADY encodes the
same translate -- so every label/border drawn through those paths
double-counted the chrome offset and basic layout drifted.

This commit:

- Drops the shadow accumulator in matrix mode. Graphics.translate(int,
  int) now ONLY calls impl.translateMatrix when the flag is on.
- Reads getTranslateX/getTranslateY off the impl matrix
  (impl.getTransform(ng).getTranslateX()) in matrix mode. Reliable
  while the matrix is pure-translate, which is the only state in which
  the framework callers (Form.paintGlassImpl, Container.paintComponent,
  Component.paintIntersectingComponentsAbove, paintIntersecting/$FLAT,
  FontImage absolute-pivot rotation, LinearGradientPaint,
  TextSelection) snapshot translate -- they all run before any user
  scale/rotate composes onto the matrix.
- Fixes the two bypass-Graphics call sites:
  - Label.draw at Label.java:913 now uses 0 instead of g.getTranslateX
    in matrix mode (the impl matrix carries the translate that
    drawLabelComponent's internal draws will apply).
  - BGPainter.paint at Component.java:8659 does the same for the
    impl.paintComponentBackground bypass.
- TestCodenameOneImplementation grows a per-Graphics matrixTranslateX/Y
  accumulator that translateMatrix mutates, plus an override of
  getTransform(Object) that returns Transform.makeTranslation of those
  accumulators -- so unit tests can verify matrix-mode getTranslateX/Y
  flows end-to-end through the same call path the production Graphics
  uses.

All 2429 core-unittests still pass (including the 4 matrix-mode tests
in GraphicsTest, now updated to assert matrix-derived getTranslateX/Y
instead of the dropped shadow accumulator).

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

shai-almog commented May 16, 2026

iOS Metal screenshot updates

Compared 108 screenshots: 88 matched, 19 updated, 1 missing reference.

  • chart-rotated-pie — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    chart-rotated-pie
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as chart-rotated-pie.png in workflow artifacts.

  • chart-transform — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    chart-transform
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as chart-transform.png in workflow artifacts.

  • FloatingActionButtonTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    FloatingActionButtonTheme_dark
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as FloatingActionButtonTheme_dark.png in workflow artifacts.

  • FloatingActionButtonTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    FloatingActionButtonTheme_light
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as FloatingActionButtonTheme_light.png in workflow artifacts.

  • graphics-affine-scale — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-affine-scale
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-affine-scale.png in workflow artifacts.

  • graphics-inscribed-triangle-grid — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-inscribed-triangle-grid
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 590x1278.
    Full-resolution PNG saved as graphics-inscribed-triangle-grid.png in workflow artifacts.

  • graphics-rotate — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-rotate
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-rotate.png in workflow artifacts.

  • graphics-scale — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-scale
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-scale.png in workflow artifacts.

  • graphics-tile-image — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-tile-image
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 413x895.
    Full-resolution PNG saved as graphics-tile-image.png in workflow artifacts.

  • graphics-transform-rotation — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-transform-rotation
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-transform-rotation.png in workflow artifacts.

  • graphics-transform-translation — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-transform-translation
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-transform-translation.png in workflow artifacts.

  • graphics-translate-then-scale — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-metal/graphics-translate-then-scale.png.

    graphics-translate-then-scale
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 590x1278.
    Full-resolution PNG saved as graphics-translate-then-scale.png in workflow artifacts.

  • kotlin — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    kotlin
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as kotlin.png in workflow artifacts.

  • LightweightPickerButtons — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    LightweightPickerButtons
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as LightweightPickerButtons.png in workflow artifacts.

  • LightweightPickerButtons_above_center — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    LightweightPickerButtons_above_center
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as LightweightPickerButtons_above_center.png in workflow artifacts.

  • LightweightPickerButtons_below_right — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    LightweightPickerButtons_below_right
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as LightweightPickerButtons_below_right.png in workflow artifacts.

  • LightweightPickerButtons_between_mixed — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    LightweightPickerButtons_between_mixed
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as LightweightPickerButtons_between_mixed.png in workflow artifacts.

  • SwitchTheme_dark — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SwitchTheme_dark
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as SwitchTheme_dark.png in workflow artifacts.

  • SwitchTheme_light — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SwitchTheme_light
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as SwitchTheme_light.png in workflow artifacts.

  • ValidatorLightweightPicker — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ValidatorLightweightPicker
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as ValidatorLightweightPicker.png in workflow artifacts.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 157 seconds

Build and Run Timing

Metric Duration
Simulator Boot 76000 ms
Simulator Boot (Run) 1000 ms
App Install 14000 ms
App Launch 10000 ms
Test Execution 315000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1436.000 ms
Base64 CN1 encode 1624.000 ms
Base64 encode ratio (CN1/native) 1.131x (13.1% slower)
Base64 native decode 1214.000 ms
Base64 CN1 decode 2198.000 ms
Base64 decode ratio (CN1/native) 1.811x (81.1% slower)
Base64 SIMD encode 1029.000 ms
Base64 encode ratio (SIMD/native) 0.717x (28.3% faster)
Base64 encode ratio (SIMD/CN1) 0.634x (36.6% faster)
Base64 SIMD decode 888.000 ms
Base64 decode ratio (SIMD/native) 0.731x (26.9% faster)
Base64 decode ratio (SIMD/CN1) 0.404x (59.6% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 135.000 ms
Image createMask (SIMD on) 34.000 ms
Image createMask ratio (SIMD on/off) 0.252x (74.8% faster)
Image applyMask (SIMD off) 279.000 ms
Image applyMask (SIMD on) 112.000 ms
Image applyMask ratio (SIMD on/off) 0.401x (59.9% faster)
Image modifyAlpha (SIMD off) 222.000 ms
Image modifyAlpha (SIMD on) 87.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.392x (60.8% faster)
Image modifyAlpha removeColor (SIMD off) 282.000 ms
Image modifyAlpha removeColor (SIMD on) 150.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.532x (46.8% faster)
Image PNG encode (SIMD off) 1998.000 ms
Image PNG encode (SIMD on) 1433.000 ms
Image PNG encode ratio (SIMD on/off) 0.717x (28.3% faster)
Image JPEG encode 1027.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 16, 2026

iOS screenshot updates

Compared 108 screenshots: 92 matched, 15 updated, 1 missing reference.

  • chart-rotated-pie — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    chart-rotated-pie
    Preview info: JPEG preview quality 70; JPEG preview quality 70; downscaled to 825x1789.
    Full-resolution PNG saved as chart-rotated-pie.png in workflow artifacts.

  • chart-transform — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    chart-transform
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as chart-transform.png in workflow artifacts.

  • graphics-affine-scale — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-affine-scale
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-affine-scale.png in workflow artifacts.

  • graphics-clip-under-rotation — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-clip-under-rotation
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-clip-under-rotation.png in workflow artifacts.

  • graphics-inscribed-triangle-grid — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-inscribed-triangle-grid
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 590x1278.
    Full-resolution PNG saved as graphics-inscribed-triangle-grid.png in workflow artifacts.

  • graphics-rotate — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-rotate
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-rotate.png in workflow artifacts.

  • graphics-scale — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-scale
    Preview info: JPEG preview quality 30; JPEG preview quality 30; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-scale.png in workflow artifacts.

  • graphics-transform-rotation — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-transform-rotation
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-transform-rotation.png in workflow artifacts.

  • graphics-transform-translation — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    graphics-transform-translation
    Preview info: JPEG preview quality 60; JPEG preview quality 60; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-transform-translation.png in workflow artifacts.

  • graphics-translate-then-scale — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots/graphics-translate-then-scale.png.

    graphics-translate-then-scale
    Preview info: JPEG preview quality 20; JPEG preview quality 20; downscaled to 590x1278.
    Full-resolution PNG saved as graphics-translate-then-scale.png in workflow artifacts.

  • LightweightPickerButtons — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    LightweightPickerButtons
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as LightweightPickerButtons.png in workflow artifacts.

  • LightweightPickerButtons_above_center — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    LightweightPickerButtons_above_center
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as LightweightPickerButtons_above_center.png in workflow artifacts.

  • LightweightPickerButtons_below_right — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    LightweightPickerButtons_below_right
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as LightweightPickerButtons_below_right.png in workflow artifacts.

  • LightweightPickerButtons_between_mixed — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    LightweightPickerButtons_between_mixed
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as LightweightPickerButtons_between_mixed.png in workflow artifacts.

  • SheetSlideUpAnimationScreenshotTest — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    SheetSlideUpAnimationScreenshotTest
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as SheetSlideUpAnimationScreenshotTest.png in workflow artifacts.

  • ValidatorLightweightPicker — updated screenshot. Screenshot differs (1179x2556 px, bit depth 8).

    ValidatorLightweightPicker
    Preview info: Preview provided by instrumentation.
    Full-resolution PNG saved as ValidatorLightweightPicker.png in workflow artifacts.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 342 seconds

Build and Run Timing

Metric Duration
Simulator Boot 112000 ms
Simulator Boot (Run) 10000 ms
App Install 21000 ms
App Launch 15000 ms
Test Execution 358000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 2834.000 ms
Base64 CN1 encode 2782.000 ms
Base64 encode ratio (CN1/native) 0.982x (1.8% faster)
Base64 native decode 1665.000 ms
Base64 CN1 decode 1616.000 ms
Base64 decode ratio (CN1/native) 0.971x (2.9% faster)
Base64 SIMD encode 657.000 ms
Base64 encode ratio (SIMD/native) 0.232x (76.8% faster)
Base64 encode ratio (SIMD/CN1) 0.236x (76.4% faster)
Base64 SIMD decode 691.000 ms
Base64 decode ratio (SIMD/native) 0.415x (58.5% faster)
Base64 decode ratio (SIMD/CN1) 0.428x (57.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 86.000 ms
Image createMask (SIMD on) 56.000 ms
Image createMask ratio (SIMD on/off) 0.651x (34.9% faster)
Image applyMask (SIMD off) 282.000 ms
Image applyMask (SIMD on) 159.000 ms
Image applyMask ratio (SIMD on/off) 0.564x (43.6% faster)
Image modifyAlpha (SIMD off) 231.000 ms
Image modifyAlpha (SIMD on) 100.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.433x (56.7% faster)
Image modifyAlpha removeColor (SIMD off) 222.000 ms
Image modifyAlpha removeColor (SIMD on) 166.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.748x (25.2% faster)
Image PNG encode (SIMD off) 1587.000 ms
Image PNG encode (SIMD on) 4174.000 ms
Image PNG encode ratio (SIMD on/off) 2.630x (163.0% slower)
Image JPEG encode 1667.000 ms

shai-almog and others added 11 commits May 16, 2026 13:26
…cross resetAffine / setTransform

The previous attempt dropped the shadow xTranslate/yTranslate accumulator
entirely, deriving getTranslateX/Y from the impl matrix instead. That
worked for snapshot-reset patterns but didn't address the bigger reason
the framework was rendering wrong: many user-paint snippets in the
codebase call resetAffine() or setTransform(null) -- MapComponent, Scene,
CommonTransitions, FontImage, beginNativeGraphicsAccess, and a handful
of transition classes -- and impl.resetAffine / impl.setTransform wipe
the impl matrix to identity. In legacy mode that's fine because the
matrix only carries scale/rotate; the framework's chain of
g.translate(absX, absY) lives in the integer xTranslate accumulator.
In matrix mode the matrix carries everything, so resetAffine destroyed
the chrome offset and the next draw landed at screen origin.

This commit:

- Restores the shadow xTranslate/yTranslate accumulator in matrix mode.
  Graphics.translate(int, int) bumps both the shadow AND calls
  impl.translateMatrix. Drawing primitives still use tx(x)/ty(y) which
  skip the shadow in matrix mode (no double shift); the shadow is the
  single source of truth for "what screen offset is currently in
  effect," consulted by setTransform's conjugation, resetAffine's
  re-translate, and getTranslateX/Y.
- getTranslateX/Y returns the shadow directly. Reading the impl matrix
  was unreliable: Transform.getTranslateX caches translateX only while
  type == TYPE_TRANSLATION, and freezes the moment any scale/rotate is
  composed, so the value lags as soon as user code mixes transforms.
- resetAffine in matrix mode: after impl.resetAffine, replay
  xTranslate/yTranslate via impl.translateMatrix so the matrix returns
  to T(framework_translate) rather than identity. This restores the
  contract MapComponent/Scene/CommonTransitions depend on (resetAffine
  rolls back user transforms but leaves the framework baseline alone).
- setTransform(null) / setTransform(identity) in matrix mode: same
  recipe -- reset impl, replay translate. The non-identity branch
  already composed T(xt) * M, so that path was correct already.
- Graphics.translate's matrix-mode branch also re-composes
  T(xt + d) * userTransform if a setTransform call had set one, the
  same way the legacy branch does.

GraphicsTest grows two tests pinning the resetAffine and
setTransform(null) preserve-translate contracts so a future revert
can't break them silently. Full core-unittests still green at 2431/2431.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… updated

Previous revisions tried to maintain both the legacy xTranslate/yTranslate
integer accumulator and the impl-side matrix translate in parallel. That
created two contradictory bookkeeping systems whose interaction surfaced
as duplicated form titles and ~86 of 108 screenshots drifting across
JS/Android/iOS.

This revision commits fully to the matrix path: in matrix mode the impl
matrix is the only source of truth for the framework painting-chain
translates, and the framework callsites that historically used the
integer accumulator are each updated with a matrix-mode branch.

Graphics.java:
- translate(int, int) in matrix mode calls ONLY impl.translateMatrix.
  xTranslate/yTranslate stay at zero -- they are never bumped.
- getTranslateX/Y returns the (now permanently zero) shadow. This is
  semantically "what to add to local coords before drawing" -- zero in
  matrix mode because drawing primitives no longer add it (the impl
  matrix carries the translate). Callers needing the actual screen
  offset for a save / reset / restore pattern must use getTransform().
- resetAffine and setTransform delegate straight to impl in matrix mode
  -- no more shadow-replay hack. Callers that need the framework chain
  preserved across these calls handle save/restore themselves.
- beginNativeGraphicsAccess / endNativeGraphicsAccess: matrix-mode
  branch snapshots the impl Transform, sets identity for the native
  access, restores the Transform on end.

Callsites updated with explicit matrix-mode branches (save / identity /
restore the impl Transform in place of the legacy snapshot-reset
translate, or save / scale+rotate / restore in place of resetAffine):
- Form.paintGlassImpl (glass pane painted at screen origin)
- Container.paintComponent tail (glass + tensile painted at screen origin)
- Component.paintIntersectingComponentsAbove (parent-chain walk)
- Component.drawPainters $FLAT cache path
- TextSelection.paint (span fills under selectionRoot's absolute origin)
- FontImage rotation (drawImage and drawString variants)
- MapComponent.paint zoom path
- Scene.paint
- CommonTransitions TYPE_PULSATE_DIALOG
- CodenameOneImplementation animation paintQueue wrapper reset

The Label.draw and BGPainter.paint bypass-Graphics fast paths now work
naturally without the prior `useMatrixTranslation ? 0 : g.getTranslateX()`
guard, because getTranslateX returns 0 in matrix mode -- the guard is
revertedand the original arithmetic stays unchanged.

GraphicsTest updated:
- testMatrixModeTranslatePushesToImplMatrix asserts getTranslateX==0
  (not 5) in matrix mode -- matches the new contract.
- testMatrixModeResetAffinePreservesFrameworkTranslate +
  testMatrixModeSetTransformIdentityPreservesFrameworkTranslate (which
  tested the now-dropped preserve-translate hack) replaced with
  testMatrixModeResetAffineWipesImplMatrix, pinning that no hidden
  state survives across resetAffine.

All 2430 core-unittests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PMD's UnnecessaryFullyQualifiedName rule failed CI because earlier
commits referenced Graphics.useMatrixTranslation and Transform via
their fully-qualified names instead of imports. Add the Transform
import where missing (MapComponent, Scene, CommonTransitions) and
unqualify both names in: CodenameOneImplementation, MapComponent,
Scene, CommonTransitions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of Android rendering coming back blank ("text missing from
many"): Container.paintComponent / Form.paintGlassImpl /
Component.paintIntersectingComponentsAbove / Component.drawPainters
($FLAT branch) / TextSelection.paint /
Graphics.beginNativeGraphicsAccess all call g.setTransform(null) in
their matrix-mode branches. Graphics.setTransform was forwarding the
null straight to impl.setTransform, and AndroidAsyncView.setTransform
NPEs unconditionally on its argument (getTransform().setTransform(t)
reads t.type / t.scaleX / etc with no null guard), tanking the entire
paint frame.

Route setTransform(null)/setTransform(identity) through impl.resetAffine
in matrix mode -- every port already implements that as
"matrix -> identity" without dereferencing a Transform.

Also add a spotbugs-exclude entry for Graphics.useMatrixTranslation
covering MS_SHOULD_BE_FINAL and UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD:
the field must stay mutable (app init code flips it once at startup)
and public (sole opt-in knob), but SpotBugs flags it because the only
writes happen in *other* modules (e.g. HelloCodenameOne.kt in the
hellocodenameone-common module) that the core module's analysis
doesn't see.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…leaks

In matrix mode setTransform was setting userTransform = transform.copy()
for non-identity transforms. That field is part of the legacy
T(xt) * M * T(-xt) conjugation machinery; matrix mode does not conjugate,
so the field served no purpose -- and it leaked.

Concrete leak: Container.paint matrix-mode tail does

    Transform saved = g.getTransform();
    g.setTransform(null);                 // routes to impl.resetAffine
    paintGlass(g); paintTensile(g);
    g.setTransform(saved);                // here userTransform = saved
    ... outer caller does g.translate(-getX(), -getY()) ...

The trailing translate composes T(-getX, -getY) onto the IMPL matrix but
does not touch userTransform, so userTransform stays at the pre-translate
value forever. Any subsequent g.getTransform() in matrix mode hit the
"if (userTransform != null) return userTransform.copy()" early-return
and returned the stale snapshot instead of the actual impl matrix --
which is exactly what framework save/restore patterns rely on.

Fix: in matrix mode setTransform, always leave userTransform null. The
impl matrix is the only source of truth, and getTransform reads from
it directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tate

Root cause of the Android-specific "missing text" / blank-Theme-test
breakage in matrix mode: AsyncGraphics overrides scale, rotate,
setTransform, and resetAffine so each one (1) updates the AsyncGraphics
local transform field used for clip / bounds checks and (2) pushes an
AsyncOp into pendingRenderingOperations so the underlying canvas gets
the same transform mutation at playback time.

But it did NOT override translateMatrix -- so calls inherited
AndroidGraphics.translateMatrix, which only updates the local Transform
and does not queue an op. The result: with
Graphics.useMatrixTranslation = true, every framework
g.translate(absX, absY) (status bar height, title-area height, every
container in the chain) routes through impl.translateMatrix; the
async-view's local transform sees them, but the queued draw ops play
back against an underlying canvas matrix that never received them.
Form chrome and Theme components paint at screen origin with no
offset; nested containers render outside the visible clip.

Override translateMatrix in AsyncGraphics to mirror the scale path:
update getTransform(), flip the same dirty flags, and queue an
AsyncOp whose execute() calls underlying.translateMatrix(x, y).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…chor

Previous matrix-mode setTransform replaced the impl matrix wholesale,
wiping the framework painting-chain translates. The TransformRotation
test (Transform.makeIdentity().translate(cx, cy).rotate(45deg)
.translate(-cx, -cy) then g.setTransform(t) then g.fillRect(cx-25,
cy-25, 50, 50)) computes (cx, cy) as PARENT-LOCAL coords (bounds.getX()
+ 100). In legacy mode setTransform conjugated as T(xt) * M * T(-xt)
where xt was the framework painting-chain translate, so the rotation
ended up centered at screen (xt + cx, yt + cy). In the old matrix-mode
path the impl matrix became just t, drawing the rotated rect at screen
(cx, cy) -- inside the form-origin coordinate system, not the panel.
Result: the four panel grids drew their rotated diamonds at the wrong
screen position; the top two went off-screen and the bottom two
appeared in the same spot.

Same root cause behind graphics-affine-scale, graphics-scale,
graphics-tile-image, graphics-transform-translation, FloatingActionButton
(BorderLayer painter calls setTransform).

Fix: maintain an INTERNAL matrixFrameworkX/Y shadow accumulator that
mirrors the framework painting-chain translates -- bumped by
g.translate(int, int) in matrix mode. It is NOT exposed via
getTranslateX/Y (so bypass-Graphics paths like
Label.drawLabelComponent and BGPainter.paintComponentBackground keep
passing local coords and don't double-shift). It IS used by
setTransform / resetAffine to rebuild the impl matrix as
T(matrixFrameworkX) * userTransform (no terminal T(-matrixFrameworkX)
because vertex coords aren't pre-shifted in matrix mode).

- translate(int, int) in matrix mode: bumps matrixFrameworkX/Y AND
  calls impl.translateMatrix. If userTransform is set, recomposes the
  impl matrix so subsequent draws use T(new framework) * userTransform.
- setTransform(M) in matrix mode: impl matrix = T(matrixFrameworkX) *
  M. Non-identity M sets userTransform; null/identity restores impl
  matrix to T(matrixFrameworkX).
- resetAffine in matrix mode: impl matrix = T(matrixFrameworkX) (same
  recipe as setTransform(null)).

Note: matrixFrameworkX/Y bumping happens on every g.translate inside
the framework painting chain. It also bumps on user g.translate calls
inside paint(). That matches legacy's xTranslate behavior, which also
included user-issued g.translate in the conjugation anchor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oords

drawPeerComponent's punch-hole clearRect bypasses the tx()/ty() shift
and passes peer.getAbsoluteX/Y straight to impl.clearRect. iOS
NativeGraphics.clearRect calls applyTransform before clearing, so in
matrix mode the impl matrix that already encodes the framework
painting-chain translates would apply on top -- double-translating the
cleared rect to (framework + absX). The native peer would appear as a
solid background-color rect over the canvas because the punch hole
landed off-screen.

In matrix mode subtract matrixFrameworkX/Y so impl sees local coords;
the matrix T(framework) then puts the cleared rect back at the peer's
true screen-absolute position. Legacy unchanged (matrix is identity,
so passing absolute coords directly is correct).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…et callsites to translate-based pattern

Previous matrix-mode Container.paint / Form.paintGlassImpl /
Component.paintIntersectingComponentsAbove / drawPainters / TextSelection
used Transform.getTransform + setTransform(null) + setTransform(saved)
to snapshot/reset/restore the impl matrix during glass and overlay
painting. That ran into a self-defeating problem with my recently-added
setTransform conjugation: setTransform(saved) where `saved` came from
g.getTransform() ALREADY contained T(matrixFrameworkX) baked in, and
the matrix-mode setTransform re-conjugated by T(matrixFrameworkX) --
double-translating the restore and tanking Android paint to 1/108 in
the previous CI run.

Fix: expose matrixFrameworkTranslateX/Y -- a public framework-internal
getter that returns xTranslate in legacy mode and the matrixFrameworkX
shadow in matrix mode. Revert every snapshot-reset callsite to the
legacy translate-based recipe:

    int tx = g.matrixFrameworkTranslateX();
    int ty = g.matrixFrameworkTranslateY();
    g.translate(-tx, -ty);    // matrix -> identity
    paintAtScreenAbsolute(g); // glass / tensile / parent walk / FLAT
    g.translate(tx, ty);      // restore

g.translate in matrix mode composes onto the impl matrix AND bumps
matrixFrameworkX, so this pattern brings the impl matrix back to
identity (matrixFrameworkX -> 0) for the inner paint, then restores
both. No setTransform involved -> no double conjugation.

Callsites updated:
- Form.paintGlassImpl
- Container.paintComponent tail
- Component.paintIntersectingComponentsAbove
- Component.drawPainters $FLAT branch
- TextSelection.paint
- FontImage rotation (both drawString and drawImage variants)
- Graphics.beginNativeGraphicsAccess
- CodenameOneImplementation paint-queue wrapper reset
- MapComponent.paint zoom path
- Scene.paint
- CommonTransitions TYPE_PULSATE_DIALOG
- Graphics.drawPeerComponent (peer punch-hole clearRect; matrix mode
  still has to subtract matrixFrameworkX since clearRectImpl bypasses
  tx() shifting -- the impl matrix would otherwise translate the
  punch-hole to the wrong screen position)

Matrix-mode setTransform / resetAffine keep their conjugation: user
code like TransformRotation.drawContent that builds Transform.makeIdentity
.translate(cx, cy).rotate(...).translate(-cx, -cy) and calls
g.setTransform(t) continues to render the rotation centered on the
local pivot at screen (framework + cx, framework + cy).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…shot-reset callsites to translate-based pattern"

This reverts commit 9a5ae15.
Three commits between 7af8707 (AsyncGraphics translateMatrix fix that
brought Android 21->93 matched) and HEAD introduced regressions that
dropped iOS/Android/iOS-Metal to ~1-2 / 108 matched:

  501fc54 conjugate user setTransform around framework anchor
  c237464 subtract framework anchor from peer clearRect coords
  9a5ae15 framework anchor accessor + revert snapshot-reset callsites
  bc0da53 revert "framework anchor accessor + ..."

The bc0da53 revert undid 9a5ae15 but the conjugation in
501fc54 (matrixFrameworkX shadow + setTransform composition) was
still active and is itself the root cause of the iOS/iOS-Metal/Android
regressions. Rather than chase more commits that interact, restore
Graphics.java to the 7af8707 revision -- the last known-good state
that scored Android 93/108. Other files were unchanged across those
intermediate commits, so a single-file revert is sufficient.

From here we can address the specific remaining issues at 93/108
(Picker / FAB / graphics-affine-scale / graphics-rotate /
graphics-scale / graphics-transform-rotation / graphics-transform-
translation / graphics-tile-image / BrowserComponent) more carefully
without taking on the broad setTransform-conjugation cost that broke
the working tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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