Photon Lab
An image-processing playground built on Photon, a Rust image library compiled to WebAssembly. Seven modes - each its own slice of what WASM uniquely enables in the browser. Everything runs on your device; nothing leaves the page.
Preview vs export. Loaded images are downsampled to 800px on the longest edge for interactive use, so dragging sliders stays snappy regardless of source size. Save re-runs the active mode against the original full-resolution source and exports the result at native dimensions. Choose lossless PNG or a smaller, high-quality WebP from the export-format menu; browsers without WebP canvas encoding fall back to PNG. Most effects look identical at both resolutions; pixel-sized effects (Stylize’s pixelize/halftone, Caption’s text size) scale with image pixels and so look proportionally smaller in the export than in the preview.
CarveContent-aware resize
What it does. Seam carving finds one-pixel-wide paths of low-importance pixels (vertical or horizontal) and removes them. A 1000×800 portrait against a busy background can shrink to 500×800 - the subject keeps its proportions; the background gets squeezed.
- Width / Height sliders
- Target dimensions as percent of the original. Each drag re-runs the carver from the cached source, so quality stays constant across the whole range.
- Energy overlay
- Shows the “energy map” the carver sees, computed via Sobel (a gradient operator). Bright = high contrast / structure, kept. Dark = smooth, first to go.
- Save
- Downloads the carved canvas in the selected PNG or WebP format, named with its target dimensions.
What WASM unlocks. Seam carving is O(W·H) per removed seam; pure-JS implementations stutter on megapixel images. The Rust core stays under 16ms per drag, so the slider feels live.
EdgesConvolution sandbox
What it does. Each operator is a 3×3 (or 5×5) matrix applied to every pixel; the matrix shape determines which structure surfaces. Same source, very different outcomes.
- Sobel
- Gradient magnitude. Highlights any edge - intensity scales with how sharp the transition is.
- Laplace
- Second-derivative zero-crossings. Picks out fine edges with less directional bias than Sobel.
- Prewitt
- Simpler gradient (uniform weights). Faster, more noise-sensitive than Sobel.
- Emboss
- Directional kernel that turns edges into a relief / sculpted look.
- Sharpen
- High-pass filter that exaggerates local contrast - the opposite of blur.
- Box blur / Gaussian
- Low-pass filters. Box averages neighbors uniformly; Gaussian weights by distance and produces a softer, more natural blur.
- Line detection (4 angles)
- Hough-style detectors for horizontal, vertical, 45°, 135° lines. Each one wipes most of the image and highlights only its matched lines; toggles composite additively with screen blending so multiple angles can show at once.
What WASM unlocks. Each operator visits every pixel - convolution is O(W·H·k²) in kernel size. Switching operators stays instant because the source image is cached on load and re-opened per pass; only the filter step runs through WASM.
ColorPerceptual color theory
What it does. Same image, four quadrants: HSL / HSLuv / HSV / LCH. Drag the hue slider and the four panels rotate by the same number of degrees - but each interprets “degrees” in its own colorspace.
- HSL
- The colorspace every photo editor uses. Cylindrical, computed from sRGB. Rotation distance is mathematically uniform but perceptually NOT - skin tones shift differently than skies.
- HSLuv
- Perceptually uniform variant. A 60° rotation looks like a 60° rotation to the human eye regardless of which colors you started with. Built on the CIELUV colorspace.
- HSV
- Similar to HSL but with brightness instead of lightness. Common in pickers (Photoshop's color wheel). Same non-uniform rotation issue as HSL.
- LCH
- Polar form of CIELAB. Strong perceptual uniformity, especially across hue. The closest of the four to “what your eyes do.”
- Channel scale (R / G / B)
- Adds or subtracts a constant from every pixel's R, G, or B channel. Applied before hue rotation so the rotated result reflects the channel shift.
- RGB offset (chromatic aberration)
- Shifts one channel a few pixels in the X direction. Mimics the lens artifact where different colors focus at slightly different positions. Crank it and you get the classic glitch / VHS look.
- Save
- Downloads the HSLuv quadrant (the perceptually-correct one) in the selected format, named with the current hue rotation.
What WASM unlocks. Computing rotations in HSLuv and LCH requires converting RGB ↔ CIE colorspaces per pixel; doing four colorspaces in parallel on a megapixel image is heavy for pure JS but stays under ~100ms in Rust. The four panels diverge live as you drag.
StylizeGraphic-design transforms
What it does. Seven effects that pull an image out of photographic space and into print press / pixel-art / poster aesthetics. The kind of treatments commodity web editors don't surface as primary tools.
- Dither
- Reduces colour depth using ordered dithering. Lower depth = chunkier pattern. Classic newspaper / vintage-game look. The Depth slider controls the bit depth (1-4).
- Halftone
- Print-press dot pattern. Each region becomes a dot whose size encodes the local brightness. The look of newspaper images magnified 6x.
- Threshold
- Pure black-and-white cutoff. Every pixel above the slider value becomes white; everything else black. Crank to extremes for high-contrast posters.
- Pixelize
- Replaces each NxN block with the average colour of that block. Small block sizes give chunky pixel-art; large blocks abstract the image into colour fields.
- Solarize
- Inverts pixels above a fixed brightness threshold. Mimics the darkroom “Sabattier effect” - partial film re-exposure producing psychedelic colour shifts.
- Primary
- Quantises every pixel down to a handful of primary colours. Closest Photon comes to a posterize effect; produces bold flat-colour blocks.
- Frosted glass
- Randomised per-pixel displacement. Looks like the image is behind textured glass; preserves overall composition while obliterating detail.
- Tint
- Replaces the image's colour information with a single hue while preserving luminance; the colour picker controls the hue. Maps every pixel onto your chosen colour at its original brightness. Different from Duotone's Tint - that's a two-colour gradient mapping; this is monochrome with one chosen colour.
What WASM unlocks. Effects like dither and halftone require per-pixel decisions plus 2D pattern lookups; doing them on megapixel images at slider rates is heavy for pure JS. Photon handles each in a single Rust pass.
DuotoneEditorial design in one function call
What it does. Maps the image's luminance values onto a 2-color gradient: dark pixels take on the shadow colour, bright pixels take on the highlight. The result is the instantly-editorial look that Spotify card art, Apple Music covers, and newspaper photo features rely on. One Photon call; no curves, no levels, no fuss.
- Horizon
- Warm-cool gradient mapping. Cool indigo shadows into warm orange highlights - the classic sunset-over-water duotone.
- Lilac
- Deep violet shadows into soft pink highlights. Quiet, contemplative.
- Ochre
- Earth-tone gradient. Reads like a vintage book illustration or sepia-toned print.
- Violette
- Saturated purple-magenta gradient. Loud, declarative; great for poster work.
- Tint
- Map luminance against your chosen colour and a fixed gray. One picker. Closer to monochrome than full duotone, but lets you sample any tint without thinking about a second colour.
- Custom
- Two colour pickers - shadow and highlight - feed Photon’s parametric
duotone(img, Rgb_a, Rgb_b)directly. Default is a navy/gold pair; pick anything.
What WASM unlocks. Each pixel: convert to luminance, look up the position on the colour gradient, write the result. Trivial per pixel but heavy in aggregate; Photon does the whole pass in a few ms.
GeneratePhoton as a synthesizer
What it does. Makes images from scratch - no input required. Pick a base (Photon's built-in gradient or a solid colour you choose), optionally layer one of the parameter-free Stylize treatments on top, set the canvas size, and Photon does the rest. The compositional angle: Photon builds the picture, then the same Photon library that drives Stylize transforms it. Useful as procedural backgrounds, test cards, or the “huh, didn't expect that” reminder that an image library can build pictures, not just modify them.
- Gradient base
create_gradient(w, h)- Photon's built-in multi-hue colour ramp at the chosen canvas size. The recipe is fixed (Photon picks the colours); the size is yours.- Solid base
- Fills the canvas with the colour from the picker. The treatments react very differently to a flat field than they do to a gradient - useful for understanding what each transform does in isolation.
- Treatments
- Apply one Stylize effect to the synthesized base: Dither (ordered-pattern colour reduction), Halftone (newspaper dot pattern), Solarize (darkroom inversion), Frosted (per-pixel random displacement). Each shares its implementation with the Stylize mode but acquires new visual character on procedural inputs - a dithered gradient looks nothing like a dithered photograph.
- Size
- Width and height of the output canvas in pixels. Independent of any image you may have loaded; Generate is purely synthesized.
What WASM unlocks. Generating a megapixel gradient and pushing it through a transform pass is heavy in pure JS - per-pixel writes plus structured per-block decisions don't vectorise well. Photon does the whole stack in a few ms regardless of canvas size, so dragging the size sliders feels live.
CaptionText overlay as a creator surface
What it does. Bakes text into the image via Photon's bundled Roboto font. Type a caption, pick a size, choose plain text or text-with-border, and click anywhere on the canvas to set the position. Saves with the text fused into the selected PNG or WebP file - the result is one image, ready to share.
- Text
- The caption to bake in. 80-character cap; longer captions will simply extend off the canvas edge.
- Size
- Font size in pixels (16-160). The position you clicked anchors the top-left of the first letter, so the text extends right and down from that point.
- Mode
- Plain draws the text in Photon's default colour. With border wraps each character in a contrasting outline so the caption stays legible against busy backgrounds (calls
draw_text_with_border). The text colour itself is hardcoded by Photon - no picker - and Border adds the outline, not a different fill. Watermark swaps in a file picker; pick a small PNG/JPG (auto-scaled to 256px max) and Photon overlays it at the click point viaphoton.watermark(img, wm, x, y). - Click-to-position + ghost preview
- Click anywhere on the captioned canvas to anchor the text or watermark. The click point becomes the top-left. In text modes, hovering shows a translucent ghost-text preview at the cursor so you can pick the spot before committing (Canvas 2D fillText, not Photon - the ghost vanishes on mouseleave). Default position is centred.
What WASM unlocks. Rasterising a TrueType font directly into ImageData at any size, in the browser, with no DOM intermediary and no canvas fillText fallback - the glyph outlines are decoded by Rust and the pixels are written straight to the image buffer. The same call works on a Photon-synthesized gradient, a seam-carved photo, or a duotone treatment without changing API.
LiveWebcam as a source
What it does. The Live button in the util tray flips the source from a static image to your webcam. Photon then processes every frame — drag the Edges sub-pill, the Stylize amount slider, or the Duotone preset while looking at yourself and the WASM result updates in real time. Works across Carve, Edges, Stylize, Duotone, Caption. Color (four panels) and Generate (no source) auto-stop the stream when selected.
- Permission
- The first click prompts the browser for camera access. Decline and the stream simply doesn't start; click again to retry. Nothing is recorded or transmitted — the frame goes straight from
getUserMediaintoelBuf.getImageDataand out to Photon. - FPS reporting
- The perf badge swaps to
Edges live · 17 fps · 58 mswhile the stream is on. Each mode reports its own per-frame cost, so you can see Photon's true budget: light passes (Duotone, Caption) hit display rate; convolution-heavy passes (Edges, Carve) settle at whatever WASM can sustain. - Save / Reload
- Save captures the current rendered frame in the selected PNG or WebP format. Reload (or dropping a new file or clicking a sample) stops the stream and restores the previously-loaded image.
What WASM unlocks. A 640×480 frame is 300k pixels; running Sobel + line detection on every pixel at 30fps means ~10M kernel evaluations per second from a single tab. Pure JS stutters; Rust/WASM holds the budget. The same code paths that run on a loaded photo run on the live feed — no special case in any run* function.