
HilbertCam
Light-adaptive Hilbert curves.
0 ratings
Free
About
HilbertCam turns live camera frames into a continuously changing drawing based on the Hilbert space-filling curve. A Hilbert curve is a recursive mapping between a one-dimensional index and a two-dimensional grid. At order n, the grid has side length 2^n, contains 4^n sample locations, and joins every location with one continuous path while preserving strong spatial locality. HilbertCam uses order 9: a 512 x 512 lattice containing 262,144 ordered points and 262,143 connected segments.
The base geometry is generated once on the CPU. For each integer distance d, an iterative Hilbert d-to-(x,y) transform extracts two orientation bits at every scale. Conditional swaps, reflections, and quadrant offsets rotate each subcurve so its entry and exit points connect correctly. Grid coordinates are normalized to UV cell centers and stored in an immutable source-point buffer. Per-frame GPU work therefore changes adaptive positions without rebuilding topology.
Each AVFoundation camera frame arrives as a Core Video pixel buffer exposed to Metal through a zero-copy texture cache. Before sampling, curve coordinates are transformed for device rotation, front-camera mirroring, aspect ratio, and center-crop framing. For each candidate recursion cell, a compute kernel filters a 3 x 3 RGB neighborhood and calculates perceptual luminance: L = 0.2126R + 0.7152G + 0.0722B. Smoothing changes the sampling spread to reduce unstable detail and flicker.
Image intensity controls the recursive depth. In Mode 1, darkness is D = 1 - L; Mode 2 reverses that relationship and exchanges the foreground and background colors. The optional inversion switch reverses it again. Contrast and response are applied as D' = clamp(C(D - 0.5) + 0.5, 0, 1)^gamma, followed by depth = dMin + D'(dMax - dMin). Each GPU thread walks from the minimum depth toward order 9. When the current level reaches the target depth, every fine point inside that region is collapsed to the corresponding cell center. Adjacent duplicate points yield no segment, so uniform regions become broad, sparse turns while selected tonal regions retain progressively finer Hilbert structure.
Rendering is split into compute and raster stages. The compute pass processes all 262,144 points in parallel into an adaptive buffer. The vertex stage treats each neighboring pair as an instanced line segment, expands it in pixel space to a four-vertex triangle strip, and scales line width relative to the smaller viewport dimension. Degenerate segments are moved outside the clip region. The fragment stage emits black for Mode 1 or white for Mode 2 over the opposite clear color. One Metal command buffer orders adaptation, display rendering, screenshots, and movie frames, allowing updates at the display's 60 Hz cadence.
SwiftUI builds the interface; the coordinator owns AVFoundation sessions, Core Video resources, Metal pipelines, camera orientation, and shared state. Controls use a uniform structure shared by Swift and Metal. Each mode stores an independent Codable profile in UserDefaults: depth ranges, line width, contrast, response, smoothing, and inversion. Camera and UI state use observable objects, and locks protect the latest frame, transforms, settings snapshots, screenshot requests, and recorder lifecycle across capture, render, audio, and writer queues.
Captures are rendered offscreen from the adaptive point buffer, so controls never enter the output. Screenshots convert a full-resolution Metal-backed pixel buffer into an image and save it through Photos. Video uses AVAssetWriter with H.264 video, AAC microphone audio, host-clock presentation times, and a target of 60 frames per second. Metal frames are appended only after GPU completion, and stopping waits for pending frames before finalizing the movie. Filenames use local timestamps. The result is a real-time mathematical camera in which recursive topology, perceptual sampling, parallel GPU computation, and synchronized media encoding operate as one pipeline.
Show more
What's New in HilbertCam
1.0
August 3, 2026


