Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 and cameraSampleColor returns -1. Wiring real capture on those platforms (AVFoundation on macOS, GStreamer/V4L2 on Linux, Media Foundation on Windows, getUserMedia on 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, y are normalized coordinates (0.0–1.0)
  • Returns packed RGB as a number: r * 65536 + g * 256 + b
  • Returns -1 if 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