Skip to content

Ready-made scanners on top of the on-device vision API - #5575

Merged
shai-almog merged 5 commits into
masterfrom
first-class-vision-scanners
Aug 21, 2026
Merged

Ready-made scanners on top of the on-device vision API#5575
shai-almog merged 5 commits into
masterfrom
first-class-vision-scanners

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

The vision analyzers replaced the QRScanner/codescan cn1libs but gave up their one-call ergonomics. Scanning a QR code meant wiring Camera.openCameraSessionOptionsFrameListenerVisionImage.fromCameraFrameVisionPipeline → back to the EDT, and results came back as magic strings ("leftEye", "QR_CODE") and normalized floats with no way to draw them. Camera's own javadoc advertised a BarcodeScanner.scan(...) that has never existed, which is roughly how discoverable the API was.

This adds the two layers that were missing above the analyzers, without touching the analyzer contract.

One call

CodeScanner.scan().ready(code -> {
    if (code != null) {                 // null means the user backed out
        urlField.setText(code.getValue());
    }
}).except(error -> Log.e(error));

A whole scanner screen: camera, decode, restore the previous form. The cn1lib's three ScanResult callbacks map onto one AsyncResourcescanCompleted is ready, scanCanceled is a null value, scanError is except. CodeScannerOptions rewords the screen and restricts the accepted symbologies, so a stray product barcode in the frame does not complete a QR scan.

One component

VisionCameraView is the same wiring as a component, for a preview inside the application's own form, and works with every analyzer rather than just barcodes. It opens the camera when shown and releases it when the user navigates away; close() additionally releases the analyzer.

The analyzer stays caller-constructed on purpose: the builders decide which native dependency to package from the analyzer classes the application references, so naming it in application code is what keeps a face-detection app from also carrying the barcode and pose models.

Reading a result

No more literals or arithmetic. BarcodeFormat, FaceLandmarks and PoseLandmarks hold the names the backends normalize onto; Face.getLandmark / Pose.getLandmark look one up; VisionRect.toBounds(component) and VisionPoint.toPoint(component) turn normalized geometry into the pixels a paint method draws in. SegmentationMask.cutOut(image, threshold) applies a mask to its source image and rescales it — selfie segmentation was unusable without it — and VisionImage.fromFile / fromImage bridge a picked photo, passing an EncodedImage's original bytes through instead of re-encoding.

The part that fails silently

The builders scan the application's classes, not core. An app referencing only CodeScanner never names BarcodeScanner, so the Android adapter would be pruned and the iOS camera natives left out — the build green, the feature inert on the device. CodeScanner now selects the barcode adapter and the vision feature, and CodeScanner and VisionCameraView both select the camera natives. HighLevelVisionDependencyTest walks the vision sources rather than a hand-written list, so a convenience class added later fails the test until it is mapped.

Simulator

createVisionImpl() returned null on JavaSE, so every analyzer reported itself unsupported on the desktop and a scanner screen could not be built without a device. It now serves whatever is scripted under Simulate ▸ Vision, in the shape of the existing Simulate ▸ NFC menu: supported on/off, an outcome switch covering result / nothing-found / UNSUPPORTED / backend error, and the scripted values themselves. Results carry plausible geometry, so overlay and debounce code can be written before the app reaches hardware.

Samples

Every analyzer, every result type that needs one, and package-info carry a markdown javadoc sample. VisionSnippets.java in docs/demos backs twelve tagged regions in Ai-And-Speech.asciidoc — compiled and bytecode-compliance-checked by CI, so they cannot rot. Camera and CameraView lose the BarcodeScanner.scan example that never compiled. The initializr skill reference is updated to match.

Two bugs the new tests found

  • Toolbar.setBackCommand(...) before Form.show() is dropped when the form's toolbar is set explicitly, which left the Android hardware back button doing nothing on the scanner screen. Re-registered after show().
  • The torch button originally polled the EDT waiting for the camera session; CameraInfo.hasFlash() answers the same question synchronously before the form is shown.

Verification

  • 42 vision/camera core-unittests pass (26 new), plus the 676-test plugin suite.
  • Quality gate (generate-quality-report.py over SpotBugs/PMD/Checkstyle) exits 0. It caught one SpotBugs and 20 PMD findings in this code; all fixed rather than suppressed, except three CloseResource false positives that each carry a comment.
  • check-cast-semantics.sh, check-package-info.sh and validate-guide-snippets.py (676 blocks) pass; javadoc builds warning-free for these packages.

Not included: the scripts/cn1playground generated access registry (the playground's own Maven build regenerates it, and regenerating it here would pick up ~3,500 lines of unrelated pre-existing drift) and the legacy Samples/samples runner.

🤖 Generated with Claude Code

The vision analyzers replaced the QRScanner/codescan cn1libs but gave up
their one-call ergonomics. Scanning a QR code meant wiring Camera.open ->
CameraSessionOptions -> FrameListener -> VisionImage.fromCameraFrame ->
VisionPipeline -> back to the EDT, and the results came back as magic
strings ("leftEye", "QR_CODE") and normalized floats with no way to draw
them. Camera's own javadoc advertised a BarcodeScanner.scan(...) that has
never existed, which is roughly how discoverable the API was.

This adds the two layers that were missing above the analyzers, without
touching the analyzer contract:

  CodeScanner.scan().ready(code -> { ... });

is a whole scanner screen -- camera, decode, restore the previous form.
It completes with null when the user backs out, so the cn1lib's three
ScanResult callbacks map onto one AsyncResource: scanCompleted is ready,
scanCanceled is a null value, scanError is except. CodeScannerOptions
rewords the screen and restricts the accepted symbologies, so a stray
product barcode in the frame does not complete a QR scan.

VisionCameraView is the same wiring as a component, for a preview inside
the application's own form, and works with every analyzer rather than
just barcodes. It opens the camera when shown and releases it when the
user navigates away; close() additionally releases the analyzer. The
analyzer stays caller-constructed on purpose -- the builders decide which
native dependency to package from the analyzer classes the *application*
references, so naming it in application code is what keeps a face app
from also carrying the barcode and pose models.

Reading a result no longer needs literals or arithmetic: BarcodeFormat,
FaceLandmarks and PoseLandmarks hold the names the backends normalize
onto, Face.getLandmark/Pose.getLandmark look one up, and
VisionRect.toBounds(component)/VisionPoint.toPoint(component) turn
normalized geometry into the pixels a paint method draws in.
SegmentationMask.cutOut(image, threshold) applies a mask to its source
image and rescales it -- selfie segmentation was unusable without that --
and VisionImage.fromFile/fromImage bridge a picked photo, passing an
EncodedImage's original bytes through instead of re-encoding.

The builders had to learn about the two new entry points, and this is the
part that fails silently. They scan the application's classes, not core,
so an app referencing only CodeScanner never names BarcodeScanner: the
Android adapter would be pruned and the iOS camera natives left out, and
the build would be green while the feature shipped inert on the device.
CodeScanner now selects the barcode adapter and the vision feature, and
CodeScanner and VisionCameraView both select the camera natives.
HighLevelVisionDependencyTest walks the vision sources rather than a
hand-written list, so a convenience class added later fails until it is
mapped.

The simulator returned null from createVisionImpl(), so every analyzer
reported itself unsupported on the desktop and a scanner screen could not
be built without a device. It now serves whatever is scripted under
Simulate > Vision, in the shape of the existing Simulate > NFC menu:
supported on/off, an outcome switch covering result, nothing-found,
UNSUPPORTED and backend error, and the scripted values themselves. The
results carry plausible geometry so overlay and debounce code can be
written before the app reaches hardware.

Samples are javadoc-level and guide-level. Every analyzer, every result
type that needs one, and package-info carry a markdown sample, and
VisionSnippets.java in docs/demos backs twelve tagged regions in
Ai-And-Speech.asciidoc -- compiled and bytecode-compliance-checked by CI,
so they cannot rot. Camera and CameraView lose the BarcodeScanner.scan
example that never compiled.

Two bugs the new tests found. Toolbar.setBackCommand(...) called before
Form.show() is dropped when the form's toolbar is set explicitly, which
left the Android hardware back button doing nothing on the scanner
screen; it is re-registered after show(). And the torch button originally
polled the EDT waiting for the camera session, where CameraInfo.hasFlash()
answers the same question synchronously before the form is shown.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a835ae029

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/ai/vision/CodeScanner.java Outdated
Comment thread CodenameOne/src/com/codename1/ai/vision/VisionCameraView.java Outdated
@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

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

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

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

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

isSupported() on both CodeScanner and VisionCameraView asked
Camera.isSupported(), which only reports that the port has a camera
backend. A device with the backend and no capture hardware enumerates no
cameras, so both promised a scan that start() would then fail with "No
camera is available on this device". They now require an enumerated
camera as well.

VisionCameraView mirrored the preview from the *requested* facing.
Camera.getDefault falls back to the first available camera when the
requested one does not exist, so asking for FRONT on a device without a
front camera opened the rear camera and showed the world reversed. The
mirror now follows CameraInfo.getFacing() of the camera that actually
opened.

Both are covered by regression tests that fail against the previous code.

The developer guide also failed its prose gates: Vale wants contractions
in nine places, and LanguageTool does not know "backpressure" and reads
the "0..1" range as two consecutive dots. Reworded rather than
accept-listed, since neither term appeared in the guide before this
branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

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

Generated automatically by the PR CI workflow.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c046ddb43f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

Retaining an adapter source and setting usesCn1Camera is only half of the
build wiring. PlatformFeatureCatalog is what actually adds the Gradle
artifacts, CocoaPods, frameworks, permissions and privacy strings, and it
keyed barcode scanning on BarcodeScanner and the camera on the
com/codename1/camera/ prefix -- neither of which an application using the
new high-level classes ever references.

The result was worse than a missing feature. An app referencing only
CodeScanner kept AndroidBarcodeScanningAdapter.java, which imports ML Kit,
with no com.google.mlkit:barcode-scanning on the classpath: the generated
Android project fails to compile. And an app referencing only CodeScanner
or VisionCameraView got no CameraX, no CAMERA permission and no
AVFoundation or NSCameraUsageDescription, so the preview opened on
hardware the build never provisioned.

CodeScanner now carries the barcode entry and its ML Kit iOS backend
variant, and both camera-driving classes carry the camera entry. The
camera entries deliberately omit RECORD_AUDIO and the microphone privacy
string: both classes open their session with captureAudio(false), and a
barcode scanner asking for the microphone is a privacy smell and an
app-review question. They keep the full CameraX artifact list rather than
trimming to preview-only, because AndroidCameraImpl reflects over the
whole CameraX surface from one class.

HighLevelVisionDependencyTest now checks the catalog and not just the
adapter mapping, so the next convenience class fails there instead of
shipping a project that does not compile. The ML Kit dependency lock count
moves 26 -> 27 for the added barcode entry, whose Android floor is the
same 21 every other ML Kit entry declares.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5497e9924

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/ai/vision/VisionCameraView.java
Comment thread CodenameOne/src/com/codename1/ai/vision/VisionCameraView.java
CameraView.setMirrored and setScaleType stored their values and nothing
read them: a repo-wide search finds no port consuming isMirrored() or
getScaleType(). The simulator always stretched the frame, iOS is pinned to
AVLayerVideoGravityResizeAspectFill, and Android constructs a bare
PreviewView, so a front-facing preview rendered unmirrored while
isMirrored() returned true. That predates this branch, but the new
VisionCameraView surfaces both settings, and its just-corrected mirroring
was invisible.

CameraImpl gains setPreviewMirrored and setPreviewScaleType, defaulting to
no-ops so no port breaks and a port that cannot honor them keeps today's
behavior. CameraView forwards to them instead of only recording.

The JavaSE preview now implements both: fitPreview computes the letterbox
for FIT, the overflow for CROP and the stretch for FILL, and mirroring
swaps the destination x coordinates. That is the target this branch can
actually verify, and it is where a developer building a scanner on the
desktop would otherwise see the wrong thing.

iOS and Android are left alone on purpose. Both need native preview
changes I cannot exercise from here, and blind native edits do not belong
in a vision-API branch. The javadoc and the guide now say plainly which
targets honor these settings and which currently ignore them, instead of
implying they work everywhere.

Covered by CameraSessionTest.previewSettingsReachThePortRatherThanOnlyThe
Getter and by the VisionCameraView tests, which now assert the port saw
the value rather than only that the getter agrees with itself.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ec6a4c05a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/ai/vision/VisionCameraView.java Outdated
A port returns no preview peer when the native preview cannot be created:
IOSCameraImpl returns null when the session or the view allocation fails,
and AndroidCameraImpl returns null when CameraX cannot be resolved or the
PreviewView reflection throws. CameraSession.createView() wraps that null
without complaint, so start() installed an empty CameraView, reported
isRunning() true, and began analyzing frames nobody could aim. Through
CodeScanner that is a permanently blank scanner screen with no error ever
delivered -- the failure mode a user cannot tell from a hung app.

start() now treats a missing peer as a startup failure: it closes the
session it just opened and reports through the listener, which CodeScanner
turns into a failed AsyncResource and a return to the previous form.

Only the peer is checked. createView() itself always returns a view, and
SpotBugs correctly rejects a null check on it.

The camera test double returned a null peer for every call, which is what
let this path go unnoticed; it now returns a stand-in peer and reproduces
the failure behind a flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 8.90% (8757/98388 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.68% (45129/520128), branch 3.35% (1650/49187), complexity 3.36% (1769/52578), method 5.16% (1429/27716), class 10.34% (382/3695)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 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/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 8.90% (8757/98388 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.68% (45129/520128), branch 3.35% (1650/49187), complexity 3.36% (1769/52578), method 5.16% (1429/27716), class 10.34% (382/3695)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 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/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 117ms / native 52ms = 2.2x speedup
SIMD float-mul (64K x300) java 237ms / native 106ms = 2.2x 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 75.000 ms
Base64 CN1 decode 60.000 ms
Base64 native encode 339.000 ms
Base64 encode ratio (CN1/native) 0.221x (77.9% faster)
Base64 native decode 285.000 ms
Base64 decode ratio (CN1/native) 0.211x (78.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 73ms / native 3ms = 24.3x speedup
SIMD float-mul (64K x300) java 72ms / native 4ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 200.000 ms
Base64 CN1 decode 100.000 ms
Base64 native encode 598.000 ms
Base64 encode ratio (CN1/native) 0.334x (66.6% faster)
Base64 native decode 232.000 ms
Base64 decode ratio (CN1/native) 0.431x (56.9% faster)
Base64 SIMD encode 52.000 ms
Base64 encode ratio (SIMD/CN1) 0.260x (74.0% faster)
Base64 SIMD decode 51.000 ms
Base64 decode ratio (SIMD/CN1) 0.510x (49.0% faster)
Base64 encode ratio (SIMD/native) 0.087x (91.3% faster)
Base64 decode ratio (SIMD/native) 0.220x (78.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 8.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.250x (75.0% faster)
Image applyMask (SIMD off) 52.000 ms
Image applyMask (SIMD on) 59.000 ms
Image applyMask ratio (SIMD on/off) 1.135x (13.5% slower)
Image modifyAlpha (SIMD off) 49.000 ms
Image modifyAlpha (SIMD on) 40.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.816x (18.4% faster)
Image modifyAlpha removeColor (SIMD off) 46.000 ms
Image modifyAlpha removeColor (SIMD on) 47.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.022x (2.2% slower)

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 94000 ms
Simulator Boot (Run) 1000 ms
App Install 24000 ms
App Launch 1000 ms
Test Execution 421000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 5ms = 10.8x speedup
SIMD float-mul (64K x300) java 73ms / native 3ms = 24.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 564.000 ms
Base64 CN1 decode 283.000 ms
Base64 native encode 462.000 ms
Base64 encode ratio (CN1/native) 1.221x (22.1% slower)
Base64 native decode 550.000 ms
Base64 decode ratio (CN1/native) 0.515x (48.5% faster)
Base64 SIMD encode 125.000 ms
Base64 encode ratio (SIMD/CN1) 0.222x (77.8% faster)
Base64 SIMD decode 93.000 ms
Base64 decode ratio (SIMD/CN1) 0.329x (67.1% faster)
Base64 encode ratio (SIMD/native) 0.271x (72.9% faster)
Base64 decode ratio (SIMD/native) 0.169x (83.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 8.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.250x (75.0% faster)
Image applyMask (SIMD off) 42.000 ms
Image applyMask (SIMD on) 34.000 ms
Image applyMask ratio (SIMD on/off) 0.810x (19.0% faster)
Image modifyAlpha (SIMD off) 35.000 ms
Image modifyAlpha (SIMD on) 69.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.971x (97.1% slower)
Image modifyAlpha removeColor (SIMD off) 515.000 ms
Image modifyAlpha removeColor (SIMD on) 100.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.194x (80.6% faster)

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 88000 ms
Simulator Boot (Run) 1000 ms
App Install 22000 ms
App Launch 1000 ms
Test Execution 536000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 70ms / native 3ms = 23.3x speedup
SIMD float-mul (64K x300) java 68ms / native 3ms = 22.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 351.000 ms
Base64 CN1 decode 233.000 ms
Base64 native encode 973.000 ms
Base64 encode ratio (CN1/native) 0.361x (63.9% faster)
Base64 native decode 636.000 ms
Base64 decode ratio (CN1/native) 0.366x (63.4% faster)
Base64 SIMD encode 101.000 ms
Base64 encode ratio (SIMD/CN1) 0.288x (71.2% faster)
Base64 SIMD decode 59.000 ms
Base64 decode ratio (SIMD/CN1) 0.253x (74.7% faster)
Base64 encode ratio (SIMD/native) 0.104x (89.6% faster)
Base64 decode ratio (SIMD/native) 0.093x (90.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 17.000 ms
Image createMask (SIMD on) 206.000 ms
Image createMask ratio (SIMD on/off) 12.118x (1111.8% slower)
Image applyMask (SIMD off) 437.000 ms
Image applyMask (SIMD on) 272.000 ms
Image applyMask ratio (SIMD on/off) 0.622x (37.8% faster)
Image modifyAlpha (SIMD off) 312.000 ms
Image modifyAlpha (SIMD on) 199.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.638x (36.2% faster)
Image modifyAlpha removeColor (SIMD off) 137.000 ms
Image modifyAlpha removeColor (SIMD on) 202.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.474x (47.4% slower)

@shai-almog
shai-almog merged commit a4a6848 into master Aug 21, 2026
50 of 51 checks passed
@shai-almog
shai-almog deleted the first-class-vision-scanners branch August 21, 2026 02:54
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