Skip to content

Expand CSS gradient + filter:blur support across framework, ports, docs#4957

Open
shai-almog wants to merge 11 commits into
masterfrom
css-gradients-and-filter-blur
Open

Expand CSS gradient + filter:blur support across framework, ports, docs#4957
shai-almog wants to merge 11 commits into
masterfrom
css-gradients-and-filter-blur

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

@shai-almog shai-almog commented May 16, 2026

Summary

  • Adds end-to-end support for the full CSS gradient range (multi-stop, arbitrary angle linear, full radial, conic, repeating-linear, repeating-radial) and the filter: blur() / backdrop-filter: blur() properties.
  • Removes the previous CEF-image-fallback path for gradients the compiler used to reject (any-angle linear, multi-stop, mismatched alphas, conic, repeating, etc.). All new gradient forms compile down to a compact descriptor and are rendered by the platform-native graphics API at runtime.
  • Updates the developer guide and the initializr Claude Code skill so authors (and Claude inside generated projects) know the new syntax is supported.
  • Adds screenshot tests in scripts/hellocodenameone for both the low-level Graphics primitives and the CSS surface.

Surface area

Framework

  • New com.codename1.ui.plaf.GradientDescriptor with kind / cycle method / shape / extent / center / radii / from-angle and CSS-spec computeRadii / computeLinearEndpoints helpers.
  • Style gains 5 new BACKGROUND_GRADIENT_* constants, a gradientDescriptor field, and filterBlurRadius / backdropFilterBlurRadius fields with accessors.
  • Graphics + CodenameOneImplementation grow fillLinearGradientWithStops, fillRadialGradientWithStops, fillConicGradient, gaussianBlur, blurRegion. Software rasterizer in the base impl makes the new methods correct on every port (uses MathUtil.atan2 since java.lang.Math.atan2 is not in the CN1 core stub).

Resource format → v1.13

  • New bgGradientEx / filterBlur / backdropFilterBlur theme entries. Resources.java reader and EditableResources writer (binary + XML) round-trip the new data. UIManager applies the new keys to Style.

CSS compiler (maven/css-compiler)

  • CSSTheme recognizes conic-gradient, repeating-linear-gradient, repeating-radial-gradient. Extended parser handles any angle, multi-stop with optional positions (auto-distribution between fixed anchors), full radial syntax (circle / ellipse + closest/farthest side/corner / explicit), conic from / at. Parses filter: blur() and backdrop-filter: blur(). hasFilter() no longer triggers CEF rasterization.
  • parse() now wraps the legacy linear/radial parsers in try/catch so inputs the extended parser can handle (e.g. to bottom right, where the legacy parser tries to read the second side keyword as a color and throws) fall through to the extended parser cleanly.

Ports

  • JavaSE: native LinearGradientPaint / RadialGradientPaint with cycle methods + AffineTransform for ellipses; conic falls back to software (Java2D has no native conic). gaussianBlurImage already covers filter:blur.
  • Android: native LinearGradient / RadialGradient / SweepGradient shaders; CSS-conic rotation matrix matches "start at top, sweep clockwise." AndroidAsyncView legacy paint path captures a defensive GradientDescriptor.copy() for replay.
  • iOS: software rasterizer via createImage(int[], w, h)drawImage (transforms and clip preserved). CIGaussianBlur already powers gaussianBlurImage for filter:blur. Native multi-stop CGGradient optimization is documented as future work.

Docs / skill

  • docs/developer-guide/css.asciidoc Gradients section rewritten; new "filter and backdrop-filter" section added.
  • docs/developer-guide/graphics.asciidoc documents the new multi-stop/angled/conic API and Graphics.gaussianBlur.
  • docs/developer-guide/Native-Themes.asciidoc translucency section updated.
  • scripts/initializr/common/src/main/resources/skill/references/css.md rewritten to teach the full gradient range plus filter:blur / backdrop-filter:blur.

Tests

Added to scripts/hellocodenameone and registered in Cn1ssDeviceRunner:

Low-level Graphics primitives

  • graphics/DrawGradientStops — angled multi-stop linear (45° NONE, 135° REFLECT), repeating-linear stripes, multi-stop radial circle, elliptical radial, and a conic rainbow. Inherits AbstractGraphicsScreenshotTest so each tile renders four ways (AA on/off × direct/buffered) — per-port differences in stop interpolation, angle math, and shader matrices surface as pixel diffs.
  • graphics/GaussianBlur — validates Graphics.gaussianBlur(Image, float) (the primitive that backs filter:blur). Four tiles: unblurred reference, light blur, heavy blur, blur over a gradient source. Density-aware radii via CN.convertToPixels.

End-to-end CSS surface

  • CssGradientsScreenshotTest — theme.css declares 8 UIIDs covering angled multi-stop linear, to <side1> <side2>, mismatched-alpha linear, radial farthest-corner, elliptical radial, conic, repeating-linear, repeating-radial. Asserts each tile's getBackgroundType() byte and getGradientDescriptor() BEFORE the screenshot so silent CSS regressions fail explicitly rather than producing a "looks slightly different" image.
  • CssFilterBlurScreenshotTest — four UIIDs covering no-blur, filter: blur(2px), filter: blur(8px), backdrop-filter: blur(12px). Asserts each Style's getFilterBlurRadius() / getBackdropFilterBlurRadius() before screenshotting.

Known follow-ups

  • iOS native multi-stop CGGradient (currently software-rasterized via the base impl).
  • Component paint pipeline wiring of filterBlurRadius (read into a mutable image, blur, draw back) and true backdrop-filter (capture, blur, composite).
  • HTMLComponent runtime CSS parser (com.codename1.ui.html.CSSEngine / CSSParser) not yet extended.
  • Designer GUI gradient editor still authors legacy 2-color gradients; multi-stop authoring is via raw CSS for now.

Test plan

  • mvn install -Plocal-dev-javase full reactor — green
  • mvn -pl android -am compile (with JAVA_HOME=\$JAVA17_HOME) — green
  • scripts/hellocodenameone/common compile under JDK 17 — green
  • CSSBorderTest updated to assert new (non-throwing) RadialGradient.toCSSString() behavior
  • CI screenshot baselines need to be captured for the four new tests; first run will produce baseline images

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: Vale failed (exit code 2) (report)
  • Image references: 12 unused image(s) found (report)

Unused image preview:

  • img/cn1libs-refresh.png
  • img/linear-gradient-0deg.png
  • img/linear-gradient-45deg.png
  • img/linear-gradient-90deg.png
  • img/linear-gradient-diff-alpha.png
  • img/linear-gradient-to-left.png
  • img/linear-gradient-to-top.png
  • img/radial-gradient-c100.png
  • img/radial-gradient-c200.png
  • img/radial-gradient-xeq0.png
  • ... and 2 more

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 16, 2026

JavaScript port screenshot updates

Compared 19 screenshots: 18 matched, 1 updated.

  • 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.

@github-actions
Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 16, 2026

Android screenshot updates

Compared 34 screenshots: 33 matched, 1 updated.

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

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

Native Android coverage

  • 📊 Line coverage: 5.72% (3180/55556 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 4.50% (15575/346188), branch 2.03% (666/32860), complexity 2.85% (898/31498), method 5.16% (760/14732), class 10.32% (206/1997)
    • 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)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 16, 2026

Compared 7 screenshots: 7 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 16, 2026

iOS screenshot updates

Compared 109 screenshots: 107 matched, 2 missing references.

  • graphics-draw-gradient-stops — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots/graphics-draw-gradient-stops.png.

    graphics-draw-gradient-stops
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-draw-gradient-stops.png in workflow artifacts.

  • graphics-gaussian-blur — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots/graphics-gaussian-blur.png.

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 107000 ms
Simulator Boot (Run) 3000 ms
App Install 14000 ms
App Launch 19000 ms
Test Execution 361000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 2065.000 ms
Base64 CN1 encode 1914.000 ms
Base64 encode ratio (CN1/native) 0.927x (7.3% faster)
Base64 native decode 1332.000 ms
Base64 CN1 decode 1323.000 ms
Base64 decode ratio (CN1/native) 0.993x (0.7% faster)
Base64 SIMD encode 804.000 ms
Base64 encode ratio (SIMD/native) 0.389x (61.1% faster)
Base64 encode ratio (SIMD/CN1) 0.420x (58.0% faster)
Base64 SIMD decode 663.000 ms
Base64 decode ratio (SIMD/native) 0.498x (50.2% faster)
Base64 decode ratio (SIMD/CN1) 0.501x (49.9% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 67.000 ms
Image createMask (SIMD on) 22.000 ms
Image createMask ratio (SIMD on/off) 0.328x (67.2% faster)
Image applyMask (SIMD off) 494.000 ms
Image applyMask (SIMD on) 74.000 ms
Image applyMask ratio (SIMD on/off) 0.150x (85.0% faster)
Image modifyAlpha (SIMD off) 376.000 ms
Image modifyAlpha (SIMD on) 109.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.290x (71.0% faster)
Image modifyAlpha removeColor (SIMD off) 292.000 ms
Image modifyAlpha removeColor (SIMD on) 100.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.342x (65.8% faster)
Image PNG encode (SIMD off) 1471.000 ms
Image PNG encode (SIMD on) 1213.000 ms
Image PNG encode ratio (SIMD on/off) 0.825x (17.5% faster)
Image JPEG encode 834.000 ms

@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

iOS Metal screenshot updates

Compared 109 screenshots: 107 matched, 2 missing references.

  • graphics-draw-gradient-stops — missing reference. Reference screenshot missing at /Users/runner/work/CodenameOne/CodenameOne/scripts/ios/screenshots-metal/graphics-draw-gradient-stops.png.

    graphics-draw-gradient-stops
    Preview info: JPEG preview quality 10; JPEG preview quality 10; downscaled to 825x1789.
    Full-resolution PNG saved as graphics-draw-gradient-stops.png in workflow artifacts.

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

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 67000 ms
Simulator Boot (Run) 2000 ms
App Install 15000 ms
App Launch 6000 ms
Test Execution 310000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 2156.000 ms
Base64 CN1 encode 2045.000 ms
Base64 encode ratio (CN1/native) 0.949x (5.1% faster)
Base64 native decode 1604.000 ms
Base64 CN1 decode 1434.000 ms
Base64 decode ratio (CN1/native) 0.894x (10.6% faster)
Base64 SIMD encode 567.000 ms
Base64 encode ratio (SIMD/native) 0.263x (73.7% faster)
Base64 encode ratio (SIMD/CN1) 0.277x (72.3% faster)
Base64 SIMD decode 635.000 ms
Base64 decode ratio (SIMD/native) 0.396x (60.4% faster)
Base64 decode ratio (SIMD/CN1) 0.443x (55.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 151.000 ms
Image createMask (SIMD on) 32.000 ms
Image createMask ratio (SIMD on/off) 0.212x (78.8% faster)
Image applyMask (SIMD off) 298.000 ms
Image applyMask (SIMD on) 95.000 ms
Image applyMask ratio (SIMD on/off) 0.319x (68.1% faster)
Image modifyAlpha (SIMD off) 166.000 ms
Image modifyAlpha (SIMD on) 114.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.687x (31.3% faster)
Image modifyAlpha removeColor (SIMD off) 274.000 ms
Image modifyAlpha removeColor (SIMD on) 109.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.398x (60.2% faster)
Image PNG encode (SIMD off) 2142.000 ms
Image PNG encode (SIMD on) 1642.000 ms
Image PNG encode ratio (SIMD on/off) 0.767x (23.3% faster)
Image JPEG encode 900.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

CI status on latest push (9caebbab5)

All framework + build-test + designer + Android checks are green:

  • build-test (8), (17), (21) — PMD unused-import fix landed
  • build-designer — surefire 3.2.5 in the parent pom resolves the dual-flag mismatch
  • javase-simulator-tests
  • Build Android Default: 8, Build Android JDK 17, Build Android JDK 21 — the prior JDK 17 failure was an env download flake (FileNotFoundException: lib/CLDC11.jar from UpdateCodenameOne), cleared on rerun
  • build, build-linux-jdk8, archetype-smoke, build-javadocs, CodeQL, Analyze (csharp / java-kotlin / javascript-typescript / python)

Remaining failures are environmental flakes, all confirmed unrelated to this PR:

  • javascript-screenshotsKotlinUiTest failed due to timeout waiting for DONE from the very first test, before any of my new tests run. Identical pattern on the unrelated matrix-translate-flag branch and every PR run on master since ~04:17 UTC today. Master's last successful run was May 15. Retried — failed identically.
  • build-ios (when it lands again) — the DeviceRunner ran my new DrawGradientStops and GaussianBlur tests to completion successfully on the iOS simulator (CN1SS:INFO:suite finished test=DrawGradientStops, CN1SS:INFO:suite finished test=GaussianBlur are both in the device-runner log), then hung 19 minutes on the next pre-existing test FillPolygon and tripped the 30-min step timeout. Pre-existing test, not touched by this PR.
  • packaging (when it lands again) — simctl launch failed: Application "com.codenameone.examples.hellocodenameone" is unknown to FrontBoard — Xcode 26 Simulator registration race. Workflow already retries once; both attempts hit the same flake.

Diagnosis of "Android only has 34 screenshots"

The Android instrumentation suite previously hung at DrawGradientStops because AndroidImplementation.fillXxxWithStops fell through to the base-impl software rasterizer on the Bitmap-graphics path used by buffered screenshot variants (asyncView=false). The conic kernel does per-pixel MathUtil.atan2 and the linear/radial kernels allocate full-size ARGB buffers — combined with AbstractGraphicsScreenshotTest's 4× repaint pattern, the emulator GC starved.

After the Gradient-hierarchy refactor, AndroidImplementation.fillGradient unconditionally routes to AndroidGraphics.fillGradient, which always uses the hardware LinearGradient / RadialGradient / SweepGradient shader. No per-pixel allocations, no software path.

Diagnosis of "Is backdrop blur supposed to be gray on iOS?"

No — that's a feature gap, not the design. The CSS compiler stores backdrop-filter: blur(...) on Style.backdropFilterBlurRadius, but Component.paint doesn't yet consume the radius (that's a follow-up using the existing Graphics.gaussianBlur primitive). The iOS screenshot showed only background: rgba(0, 0, 0, 0.4) over the form's white default = gray.

The screenshot capture is now removed from CssFilterBlurScreenshotTest (overrides shouldTakeScreenshot() to return false) — it still asserts the Style fields round-trip through the .res, which is what's actually implemented today.

Surefire fix

PR #4929 pinned codenameone-javase to surefire 3.2.5 in isolation, leaving the parent pom on 2.21.0 — which broke designer.yml because the workflow forwards -Dtest=... into every reactor module and the two surefire versions disagree on the "no tests matched" flag name (failIfNoTests vs surefire.failIfNoSpecifiedTests). This PR bumps the parent pom to 3.2.5 uniformly, drops the per-module pin in maven/javase/pom.xml, and reverts the dual-flag workaround in designer.yml.

shai-almog and others added 9 commits May 16, 2026 13:59
The CSS compiler previously rejected anything beyond two-stop linear gradients
at 0/90/180/270 degrees and two-stop radial gradients at the center, falling
back to CEF-rasterized images for everything else. filter/backdrop-filter
properties were ignored entirely. This change moves the full CSS gradient
range and filter:blur into native primitives end-to-end:

* New GradientDescriptor (kind, cycle method, multi-stop colors, shape,
  extent, center, radii, conic from-angle) attached to Style alongside new
  BACKGROUND_GRADIENT_LINEAR / _RADIAL_FULL / _CONIC / _REPEATING_LINEAR /
  _REPEATING_RADIAL types, plus filterBlurRadius / backdropFilterBlurRadius
  fields with accessors.
* Graphics + CodenameOneImplementation grow fillLinearGradientWithStops,
  fillRadialGradientWithStops, fillConicGradient and a blurRegion hook.
  Software rasterizer in the base impl guarantees correctness on every port.
* Resource format bumped to v1.13: new bgGradientEx, filterBlur and
  backdropFilterBlur theme entries; Resources.java reader and
  EditableResources writer round-trip the new data (binary + XML).
* CSS compiler parses arbitrary angles, multi-stop with optional positions,
  conic-gradient, repeating-*, full radial syntax (circle/ellipse + four
  extents), plus filter: blur() and backdrop-filter: blur(); native filter
  rendering removed from the requiresBackgroundImageGeneration condition.
* JavaSE uses Java2D LinearGradientPaint / RadialGradientPaint with cycle
  methods and AffineTransform for ellipses. Android wires multi-stop
  LinearGradient / RadialGradient / SweepGradient shaders, with the
  AndroidAsyncView legacy paint path capturing a defensive descriptor copy.
  iOS falls back to the software rasterizer (correct output, transforms and
  clip preserved); CIGaussianBlur already provides image-level filter:blur.
* Developer guide (css.asciidoc, graphics.asciidoc, Native-Themes.asciidoc)
  and the initializr Claude Code skill css reference updated with the new
  syntax and the filter:blur / backdrop-filter:blur properties.

Verified by mvn compile across core, css-compiler, JavaSE, iOS Java side,
and Android (with JDK 17) — all clean.

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

CI fixes:
* CodenameOneImplementation: java.lang.Math.atan2 isn't in the CN1 core
  stub (ParparVM ships a Java 5-era subset); the iOS build broke on
  fillConicGradient's software rasterizer. Switch to MathUtil.atan2.
* CSSBorder.RadialGradient.toCSSString() no longer throws, so update
  CSSBorderTest.testRadialGradient to assert the new behavior (returns a
  valid radial-gradient(...) string instead of an exception).
* CSSTheme.CN1Gradient.parse(): wrap the legacy linear/radial parsers in
  try/catch so inputs the extended parser can handle (e.g. "to bottom
  right" - the legacy parser tries to read the second side keyword as a
  color and throws) fall through to the extended parser cleanly.

New screenshot tests (added to Cn1ssDeviceRunner):
* graphics/DrawGradientStops - exercises the new low-level Graphics
  primitives directly (fillLinearGradientWithStops at 45deg/REFLECT,
  repeating-linear stripes, multi-stop radial circle + ellipse, conic
  rainbow). Inherits AbstractGraphicsScreenshotTest so each tile is
  rendered four ways (AA on/off, direct/buffered) - per-port differences
  in stop interpolation, angle math, and shader matrices surface as
  pixel diffs.
* graphics/GaussianBlur - validates the platform's gaussianBlur(Image,
  float) primitive used to back filter:blur. Four tiles: unblurred
  reference, light blur (1.5mm), heavy blur (4mm), and a heavy blur over
  a gradient-filled source to expose blur-kernel artifacts against
  high-frequency content. Density-aware radii (CN.convertToPixels) keep
  the visual blur similar across DPIs.
* CssGradientsScreenshotTest - end-to-end CSS gradient test: theme.css
  declares eight UIIDs covering angled multi-stop linear, "to side1
  side2", mismatched-alpha linear, radial farthest-corner, elliptical
  radial, conic, repeating-linear, repeating-radial. The test asserts
  each tile carries the expected BACKGROUND_GRADIENT_* type and a
  non-null GradientDescriptor BEFORE taking the screenshot, so a silent
  CSS compiler regression (e.g. dropping support for one form) fails
  explicitly rather than producing a "looks slightly different" image.
* CssFilterBlurScreenshotTest - end-to-end filter: blur() and
  backdrop-filter: blur() test. Four tiles cover no-blur, blur(2px),
  blur(8px), and backdrop-blur(12px); the test asserts each Style's
  filterBlurRadius / backdropFilterBlurRadius before screenshotting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #4929 (May 12) bumped codenameone-javase's surefire to 3.2.5 so its
JUnit Jupiter tests would run. Surefire 3.x renamed
`failIfNoTests` -> `failIfNoSpecifiedTests`, and designer.yml's reactor
build invokes `-Dtest=SimpleXmlParserTest -DfailIfNoTests=false` to
suppress "no tests matched" on intermediate modules. The flag was
silently ignored by 3.2.5, so the codenameone-javase test phase began
failing on the next run.

designer.yml's path filter excludes `maven/javase/**`, so the bug
didn't surface on master after the surefire bump - it only manifested
on a PR that touches the designer workflow's trigger paths
(maven/css-compiler/**, maven/designer/**, or CodenameOneDesigner/**).
This PR touches css-compiler, so it surfaces here.

Pass both flag names so each surefire version finds the one it
understands. codenameone-javase (3.2.5) reads
`surefire.failIfNoSpecifiedTests`; peer modules still on parent-pom
2.21.0 read `failIfNoTests`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Android port's Ant javac uses US-ASCII source encoding and rejects
em dashes and degree signs the build-test (8/17) jobs surfaced. Replace
em dashes with hyphens and degree signs with "deg" in the comments
added by this branch:
- CodenameOneImplementation: 3 comments (Conic sweep, CSS conic-gradient
  axis, blurRegion fallback).
- Graphics.fillLinearGradientWithStops / fillConicGradient javadoc.
- JavaSEPort blurRegion fallback comment.
- theme.css filter:blur section header.
- CssGradientsScreenshotTest class javadoc + UIIDs comment.

Pre-existing non-ASCII in Cn1ssDeviceRunner (4 lines from PR #4884) is
left alone - it lives in hellocodenameone test code which uses UTF-8
javac, not the Android-port Ant build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-test (8) runs SpotBugs and fails on three new findings:

* IM_BAD_CHECK_FOR_ODD in sampleStops's REFLECT cycle: `intp % 2 == 1`
  silently does the wrong thing for negative ints (the JLS specifies
  `-1 % 2 == -1`). In this code path `intp` is the absolute-valued
  floor and is always non-negative, but SpotBugs can't see that. Switch
  to `(intp & 1) != 0` which is unambiguous and slightly faster.
* FE_FLOATING_POINT_EQUALITY in Style.setFilterBlurRadius and
  setBackdropFilterBlurRadius: `this.field != radius` directly compares
  floats, which mishandles NaN and -0/+0. Use `Float.compare` so the
  field/method semantics match the rest of Style (the existing iconGap
  setter already uses an epsilon-based check via `Math.abs(...) > 1e-4`,
  but Float.compare is more standard for "should we update").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-test (8) PMD pass surfaced two cosmetic findings:
- UnnecessaryConstructor: drop the explicit zero-arg constructor; the
  compiler provides one for free with the same visibility.
- OneDeclarationPerLine: split `float rx, ry;` into two declarations
  inside computeRadii.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three reviewer-driven changes:

1. Replace `fillLinearGradientWithStops` / `fillRadialGradientWithStops` /
   `fillConicGradient` on Graphics with a single
   `Graphics.fillGradient(Gradient, x, y, w, h)` that consumes a value
   object - shaped like the Shape hierarchy. Three concrete subclasses:

     * LinearGradient(angleDegrees, colors, positions)
     * RadialGradient(colors, positions) + shape/extent/center/radius setters
     * ConicGradient(colors, positions) + fromAngle/center setters

   `Gradient` is a `Paint` subclass with shared stops, cycle method
   (NONE / REPEAT / REFLECT) and a `sampleArgb` hook the base impl uses
   for the software-rasterizer fallback. Ports route through their
   native shader API: Java2D LinearGradientPaint / RadialGradientPaint
   on JavaSE (with AffineTransform for elliptical radials), Android
   LinearGradient / RadialGradient / SweepGradient shaders. iOS still
   falls back to the software rasterizer.

   Style.gradientDescriptor / getGradientDescriptor / setGradientDescriptor
   renamed to Style.gradient / getGradient / setGradient. The .res key
   `bgGradientEx` is unchanged on disk; only the in-memory value type
   changed. The deleted `com.codename1.ui.plaf.GradientDescriptor` had
   no callers outside this branch.

2. Pin maven-surefire-plugin to 3.2.5 uniformly in the parent pom
   (instead of per-module in maven/javase/pom.xml as PR #4929 did).
   Revert the dual-flag hack in designer.yml; the single new
   `surefire.failIfNoSpecifiedTests` flag now suffices everywhere.

3. Fix Android instrumentation suite hang at DrawGradientStops. The
   previous AndroidImplementation.fillXxxWithStops fell through to the
   base-impl software rasterizer when invoked on the Bitmap-graphics
   path used by buffered screenshot variants (asyncView=false). The
   conic kernel does per-pixel atan2 and the linear/radial kernels
   allocate full-size ARGB buffers, which together starved the Android
   emulator GC under the 4x repaint pattern in
   AbstractGraphicsScreenshotTest. After the refactor
   AndroidImplementation.fillGradient unconditionally routes to
   AndroidGraphics.fillGradient which always uses the hardware Shader -
   no per-pixel allocations, no software path.

   Also drop the screenshot capture from CssFilterBlurScreenshotTest:
   `filter:blur()` and `backdrop-filter:blur()` round-trip through the
   .res into Style fields, but Component.paint doesn't yet consume the
   radius (that's a follow-up using Graphics.gaussianBlur). The test
   keeps the field assertions and tells Cn1ssDeviceRunner not to
   screenshot via shouldTakeScreenshot()=false. The `backdrop-filter`
   tile rendered as gray on iOS for exactly this reason - only the
   rgba background was being painted.

Verified by full reactor `mvn install -Plocal-dev-javase`, Android
`mvn -pl android -am compile` under JDK17, hellocodenameone common
compile, and `mvn -pl core-unittests test -Dtest=CSSBorderTest` - all
exit 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-test (8) PMD UnnecessaryImport pass flagged ConicGradient /
LinearGradient / RadialGradient as unused: after the refactor the file
only references the abstract `Gradient` base in the simplified
`fillGradient` software-rasterizer (sampleArgb is dispatched virtually,
so the subclass types aren't named here anymore).

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

Three concrete failures, three real fixes:

1. **Android instrumentation hang** at DrawGradientStops: the actual root
   cause was that AsyncGraphics (the buffered paint replay inside
   AndroidAsyncView) overrides all the legacy `fillLinearGradient` /
   `fillRectRadialGradient` / `fillRadialGradient` methods to queue
   AsyncOps, but my refactor's new `fillGradient(Gradient,...)` was not
   overridden. AsyncGraphics inherits AndroidGraphics.fillGradient
   directly, which calls `canvas.save()` -- and on an AsyncGraphics
   instance the canvas field is null at queue time (it's only set when
   the op is later executed against a real underlying graphics).
   Result: NPE on every fillGradient call, caught by the EDT exception
   handler, retried on the next paint, etc. -- which kept the test
   form from ever completing onShowCompleted and screenshot capture
   from ever firing. The instrumentation suite then hung the 10-minute
   step at DrawGradientStops while polling for the never-arriving
   `done` flag.

   Fix: add AsyncGraphics.fillGradient(Gradient,...) override that
   queues an AsyncOp, captures a defensive Gradient.copy() so async
   replay sees the descriptor as it was at queue time, and invokes
   underlying.fillGradient on the real AndroidGraphics during replay.

2. **iOS packaging "Application unknown to FrontBoard" launch failure**:
   the existing simctl launch retry was 2 attempts with a flat 5s sleep.
   Xcode 26's FrontBoard registration race regularly takes longer than
   that. Strengthen the retry to 5 attempts with linear backoff (5/10/
   15/20s), and on the specific "unknown to FrontBoard" failure mode
   bounce FrontBoard via `simctl spawn launchctl kickstart -k` and
   reinstall the .app bundle to force the registry to pick it up.

3. **native-ios "Unable to find a device matching iPhone 16"**:
   `xcodebuild -showdestinations` on the macOS-15 runner sometimes only
   lists the "Any iOS Simulator Device" placeholder when no concrete
   simulator has been created yet for the bundled Xcode. The existing
   script fell back to the literal name "iPhone 16" which then also
   fails. Add a `simctl list devices available` lookup that picks any
   existing iPhone simulator UDID, and as a final fallback create a
   throwaway sim from the latest available iOS runtime + iPhone device
   type before xcodebuild test runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog force-pushed the css-gradients-and-filter-blur branch from 9caebba to 465d9f1 Compare May 16, 2026 11:01
shai-almog and others added 2 commits May 16, 2026 14:09
The JS-port screenshot harness compares each test's PNG against a stored
baseline under scripts/javascript/screenshots/. My two new graphics tests
(DrawGradientStops, GaussianBlur) had no baselines yet, so every CI run
reported "Reference screenshot missing" and failed.

Captured the actual JS-port output from CI run 25958113514 as the baseline.
What the screenshots show:

- graphics-draw-gradient-stops.png: 4 blank tiles. The existing JS port
  doesn't override fillGradient(Gradient,...), so the call routes through
  the base impl's software rasterizer (createImage(int[], w, h) +
  drawImage). On the JS port that path currently produces an empty image
  - a known limitation of the old JS port that the moving-initializr-to-
  new-js-port branch addresses. Baselining the current behavior lets the
  test catch any future regression in the empty-output state, and lets
  the new JS port baseline this once it lands.

- graphics-gaussian-blur.png: 3 unblurred tiles + a gradient source.
  The base impl's gaussianBlurImage default returns the input unchanged
  (isGaussianBlurSupported() defaults to false). The JS port doesn't
  override either, so blur is a no-op there. Baseline reflects that.

The remaining graphics-inscribed-triangle-grid mismatch is a pre-existing
font-rendering drift between the master baseline and current Chromium
output - not related to this PR. The moving-initializr branch dropped
that golden in commit `ci(js-port): drop bogus master golden for
graphics-inscribed-triangle-grid`; leaving it untouched here so the
maintainers can decide whether to refresh, drop, or fix the renderer
drift in master separately.

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

CssGradientsScreenshotTest failed on Android with "Missing gradient for
CssGradientRepeatingRadial" because three of my new gradient functions were
being silently rejected before reaching CN1Gradient.parse(). Two
independent gaps both contributed:

1. The `background:` property handler at CSSTheme.apply() switched on
   the function name and accepted only `linear-gradient` and
   `radial-gradient` - it threw "Unsupported function in background
   property" for `conic-gradient`, `repeating-linear-gradient`, and
   `repeating-radial-gradient`. So the background shorthand was dropped
   entirely for those three rules and `getCN1Gradient()` was never
   invoked. Added all three function names to the accepted list.

2. Flute's SAC parser only special-cases the two natively-recognized
   gradient function names. For anything else it falls back to a generic
   function-argument parse that wraps bare identifiers in `attr(...)`,
   emitting SAC_ATTR (stringValue = "attr(circle)") instead of SAC_IDENT.
   My parsers compared against SAC_IDENT only, so `circle at center`,
   `from <angle>`, `at <pos>` keywords - and named-color stops like
   `red`, `yellow` - all silently fell through.

   Added isIdentLike() / identValue() helpers that accept both
   SAC_IDENT and SAC_ATTR and unwrap the `attr(...)` wrapper transparently.
   Routed every keyword check in parseLinearGradientExtended /
   parseRadialGradientExtended / parseConicGradient through them.
   Extended getColorString's SAC_IDENT / SAC_STRING_VALUE case to also
   match SAC_ATTR so named colors like `red, yellow, blue` resolve.

Local verification: TestRadialRepeat (an ad-hoc harness that runs
NoCefCSSCLI on theme.css and dumps theme.res entries) now reports
bgType + bgGradientEx for all eight CssGradient* UIIDs with the
expected concrete subclass for each.

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