Camera
The perry/ui module provides a live camera preview widget with color
sampling capabilities.
import {
CameraView,
cameraStart, cameraStop,
cameraFreeze, cameraUnfreeze,
cameraSampleColor, cameraSetOnTap,
} from "perry/ui"
Platform support: real capture is implemented on iOS (AVCaptureSession) and Android (Camera2). On macOS, Linux (GTK4), Windows, and the Web target the runtime exports no-op stubs so cross-platform code compiles and links cleanly —
CameraView()returns handle 0 andcameraSampleColorreturns-1. Wiring real capture on those platforms (AVFoundation on macOS, GStreamer/V4L2 on Linux, Media Foundation on Windows,getUserMediaon Web) is tracked as a follow-up.
Quick Example
const colorHex = State("#000000")
const cam = CameraView()
cameraStart(cam)
cameraSetOnTap(cam, (x: number, y: number) => {
const rgb = cameraSampleColor(x, y)
if (rgb >= 0) {
const r = Math.floor(rgb / 65536)
const g = Math.floor((rgb % 65536) / 256)
const b = Math.floor(rgb % 256)
colorHex.set(`#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`)
}
})
App({
title: "Color Picker",
width: 400,
height: 600,
body: VStack(16, [
cam,
Text(`Color: ${colorHex.value}`),
]),
})
API Reference
CameraView()
Create a live camera preview widget.
const preview = CameraView()
Returns a widget handle. The camera does not start automatically — call cameraStart() to begin capture.
cameraStart(handle)
Start the live camera feed.
cameraStart(preview)
On iOS, the camera permission dialog is shown automatically on first use.
cameraStop(handle)
Stop the camera feed and release the capture session.
cameraStop(preview)
cameraFreeze(handle)
Pause the live preview (freeze the current frame).
cameraFreeze(preview)
The camera session remains active but the preview stops updating. Useful for “capture” moments where you want to inspect the frozen frame.
cameraUnfreeze(handle)
Resume the live preview after a freeze.
cameraUnfreeze(preview)
cameraSampleColor(x, y)
Sample the pixel color at normalized coordinates.
const rgb = cameraSampleColor(0.5, 0.5) // center of frame
x,yare normalized coordinates (0.0–1.0)- Returns packed RGB as a number:
r * 65536 + g * 256 + b - Returns
-1if no frame is available
To extract individual channels:
const r = Math.floor(rgb / 65536)
const g = Math.floor((rgb % 65536) / 256)
const b = Math.floor(rgb % 256)
The color is averaged over a 5x5 pixel region around the sample point for noise reduction.
cameraSetOnTap(handle, callback)
Register a tap handler on the camera view.
cameraSetOnTap(preview, (tx: number, ty: number) => {
// tx, ty are normalized coordinates (0.0-1.0)
const tappedRgb = cameraSampleColor(tx, ty)
console.log(`tapped color: ${tappedRgb}`)
})
The callback receives normalized coordinates of the tap location, which can be passed directly to cameraSampleColor().
Implementation
On iOS, the camera uses AVCaptureSession with AVCaptureVideoPreviewLayer for GPU-accelerated live preview, and AVCaptureVideoDataOutput for frame capture. Color sampling reads pixel data from CVPixelBuffer.
On Android, the camera uses Camera2 with a TextureView preview surface. Color sampling reads from the most recent ImageReader frame.
Next Steps
- Widgets — All available widgets
- Audio Capture — Microphone input and sound metering