Skip to content

Android: scan dense codes at 1440x1080 instead of 640x480 - #6210

Merged
shai-almog merged 1 commit into
masterfrom
android-scanner-analysis-resolution
Nov 11, 2026
Merged

shai-almog merged 1 commit into
masterfrom
android-scanner-analysis-resolution

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

A camera session opened with no preview size, which is every Vision scanner, analysed frames at 640x480 on Android. That is too few pixels per module for a dense code such as a PDF417: it read on a phone with a sharp lens and failed on others. The iOS port had the same default until #6159; that change did not touch Android.

What changes

  • Analysis size. An NV21 session with no preview size now asks CameraX for 1440x1080, turned to the display's orientation (the display's, not the window's, so split screen does not flip it). It is 4:3 on purpose: the Preview use case is 4:3 by default, so the analysed field stays exactly the one on screen and detector rectangles still map onto the preview. Every other session (JPEG, and RGBA8888, which this port answers with JPEG) keeps 640x480, because each of its frames is compressed on the analysis thread.
  • Half a request. A preview size with one dimension zero used to be paired with one default dimension; it now gets the whole default.
  • Per-frame cost. The NV21 array is handed back to the converter for the next frame, so the port allocates none per frame at the larger size, and the luma plane is copied a row at a time. CameraFrame already documents its arrays as valid only during onFrame.

A size the application asks for is passed through unchanged.

Testing

  • AndroidCameraFrameConverterTest: the size selection (default, portrait, JPEG, pass-through, half request) and the buffer reuse.
  • AndroidCameraThreadingTest still passes; its harness stubs the display read.
  • Android port compiles and its SpotBugs report is at zero.
  • Not run: the on-device scripts/camera-regression suite and a real scan on hardware. The size is a request to CameraX, which picks the nearest size the device supports.

🤖 Generated with Claude Code

A camera session opened with no preview size -- every Vision scanner --
analysed frames at 640x480 on Android. That is too few pixels per module
for a dense code such as a PDF417: it read on a phone with a sharp lens
and failed on others. The iOS port had the same default until #6159.

- An NV21 session with no preview size now asks CameraX for 1440x1080,
  turned to the display's orientation. 4:3 on purpose: the Preview use
  case is 4:3, so the analysed field stays the one on screen. Other
  sessions keep 640x480, because each of their frames is compressed to
  JPEG on the analysis thread.
- Half a request (one dimension zero) used to be paired with one default
  dimension; it now gets the whole default.
- The NV21 array is handed back to the converter for the next frame, so
  the port allocates none per frame at the larger size, and the luma
  plane is copied a row at a time.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@shai-almog

shai-almog commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 195 screenshots: 195 matched.

Native Android coverage

  • 📊 Line coverage: 19.17% (56157/292885 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 18.13% (270510/1492353), branch 12.38% (19671/158943), complexity 10.35% (15844/153017), method 15.73% (11410/72518), class 27.93% (2247/8045)
    • Lowest covered classes
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2396 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • androidx.compose.runtime.androidx.compose.runtime.LinkComposer – 0.00% (0/1430 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlinx.coroutines.channels.kotlinx.coroutines.channels.BufferedChannel – 0.00% (0/863 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/744 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/630 lines covered)
      • com.codename1.androidcompat.rt.com.codename1.androidcompat.rt.TextXmlPullParser – 0.00% (0/623 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 19.17% (56157/292885 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 18.13% (270510/1492353), branch 12.38% (19671/158943), complexity 10.35% (15844/153017), method 15.73% (11410/72518), class 27.93% (2247/8045)
    • Lowest covered classes
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2396 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • androidx.compose.runtime.androidx.compose.runtime.LinkComposer – 0.00% (0/1430 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlinx.coroutines.channels.kotlinx.coroutines.channels.BufferedChannel – 0.00% (0/863 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/744 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/630 lines covered)
      • com.codename1.androidcompat.rt.com.codename1.androidcompat.rt.TextXmlPullParser – 0.00% (0/623 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 126ms / native 138ms = 0.9x speedup
SIMD float-mul (64K x300) java 106ms / native 73ms = 1.4x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 64.000 ms
Base64 CN1 decode 66.000 ms
Base64 native encode 325.000 ms
Base64 encode ratio (CN1/native) 0.197x (80.3% faster)
Base64 native decode 255.000 ms
Base64 decode ratio (CN1/native) 0.259x (74.1% faster)
Image encode benchmark status skipped (SIMD unsupported)

@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)
    • ✅ android-compat: 0 findings (no issues)
    • ✅ android-res-compiler: 0 findings (no issues)
    • ✅ backend: 0 findings (no issues)
    • ✅ backend-test: 0 findings (no issues)
    • ✅ build-engine: 0 findings (no issues)
    • ✅ build-hint-catalog: 0 findings (no issues)
    • ✅ build-hint-tools: 0 findings (no issues)
    • ✅ codenameone-gradle-plugin: 0 findings (no issues)
    • ✅ codenameone-maven-plugin: 0 findings (no issues)
    • ✅ compose-compat: 0 findings (no issues)
    • ✅ core-unittests: 0 findings (no issues)
    • ✅ desktop-compat-runtime: 0 findings (no issues)
    • ✅ fxml-compiler: 0 findings (no issues)
    • ✅ ios: 0 findings (no issues)
    • ✅ javac: 0 findings (no issues)
    • ✅ javafx-compat: 0 findings (no issues)
    • ✅ project-model: 0 findings (no issues)
    • ✅ python-runtime: 0 findings (no issues)
    • ✅ python-transpiler: 0 findings (no issues)
    • ✅ swing-compat: 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 merged commit 48766a0 into master Nov 11, 2026
27 of 28 checks passed
@shai-almog
shai-almog deleted the android-scanner-analysis-resolution branch November 11, 2026 10:20
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