Add JAX typography, xector primitives, deferred effect chains, and GPU streaming
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m28s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m28s
- Add JAX text rendering with font atlas, styled text placement, and typography primitives - Add xector (element-wise/reduction) operations library and sexp effects - Add deferred effect chain fusion for JIT-compiled effect pipelines - Expand drawing primitives with font management, alignment, shadow, and outline - Add interpreter support for function-style define and require - Add GPU persistence mode and hardware decode support to streaming - Add new sexp effects: cell_pattern, halftone, mosaic, and derived definitions - Add path registry for asset resolution - Add integration, primitives, and xector tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
:params (
|
||||
(char_size :type int :default 8 :range [4 32])
|
||||
(alphabet :type string :default "standard")
|
||||
(color_mode :type string :default "color" :desc ""color", "mono", "invert", or any color name/hex")
|
||||
(color_mode :type string :default "color" :desc "color, mono, invert, or any color name/hex")
|
||||
(background_color :type string :default "black" :desc "background color name/hex")
|
||||
(invert_colors :type int :default 0 :desc "swap foreground and background colors")
|
||||
(contrast :type float :default 1.5 :range [1 3])
|
||||
|
||||
65
sexp_effects/effects/cell_pattern.sexp
Normal file
65
sexp_effects/effects/cell_pattern.sexp
Normal file
@@ -0,0 +1,65 @@
|
||||
;; Cell Pattern effect - custom patterns within cells
|
||||
;;
|
||||
;; Demonstrates building arbitrary per-cell visuals from primitives.
|
||||
;; Uses local coordinates within cells to draw patterns scaled by luminance.
|
||||
|
||||
(require-primitives "xector")
|
||||
|
||||
(define-effect cell_pattern
|
||||
:params (
|
||||
(cell-size :type int :default 16 :range [8 48] :desc "Cell size")
|
||||
(pattern :type string :default "diagonal" :desc "Pattern: diagonal, cross, ring")
|
||||
)
|
||||
(let* (
|
||||
;; Pool to get cell colors
|
||||
(pooled (pool-frame frame cell-size))
|
||||
(cell-r (nth pooled 0))
|
||||
(cell-g (nth pooled 1))
|
||||
(cell-b (nth pooled 2))
|
||||
(cell-lum (α/ (nth pooled 3) 255))
|
||||
|
||||
;; Cell indices for each pixel
|
||||
(cell-idx (cell-indices frame cell-size))
|
||||
|
||||
;; Look up cell values for each pixel
|
||||
(pix-r (gather cell-r cell-idx))
|
||||
(pix-g (gather cell-g cell-idx))
|
||||
(pix-b (gather cell-b cell-idx))
|
||||
(pix-lum (gather cell-lum cell-idx))
|
||||
|
||||
;; Local position within cell [0, 1]
|
||||
(lx (local-x-norm frame cell-size))
|
||||
(ly (local-y-norm frame cell-size))
|
||||
|
||||
;; Pattern mask based on pattern type
|
||||
(mask
|
||||
(cond
|
||||
;; Diagonal lines - thickness based on luminance
|
||||
((= pattern "diagonal")
|
||||
(let* ((diag (αmod (α+ lx ly) 0.25))
|
||||
(thickness (α* pix-lum 0.125)))
|
||||
(α< diag thickness)))
|
||||
|
||||
;; Cross pattern
|
||||
((= pattern "cross")
|
||||
(let* ((cx (αabs (α- lx 0.5)))
|
||||
(cy (αabs (α- ly 0.5)))
|
||||
(thickness (α* pix-lum 0.25)))
|
||||
(αor (α< cx thickness) (α< cy thickness))))
|
||||
|
||||
;; Ring pattern
|
||||
((= pattern "ring")
|
||||
(let* ((dx (α- lx 0.5))
|
||||
(dy (α- ly 0.5))
|
||||
(dist (αsqrt (α+ (α² dx) (α² dy))))
|
||||
(target (α* pix-lum 0.4))
|
||||
(thickness 0.05))
|
||||
(α< (αabs (α- dist target)) thickness)))
|
||||
|
||||
;; Default: solid
|
||||
(else (α> pix-lum 0)))))
|
||||
|
||||
;; Apply mask: show cell color where mask is true, black elsewhere
|
||||
(rgb (where mask pix-r 0)
|
||||
(where mask pix-g 0)
|
||||
(where mask pix-b 0))))
|
||||
@@ -6,10 +6,10 @@
|
||||
(num_echoes :type int :default 4 :range [1 20])
|
||||
(decay :type float :default 0.5 :range [0 1])
|
||||
)
|
||||
(let* ((buffer (state-get 'buffer (list)))
|
||||
(let* ((buffer (state-get "buffer" (list)))
|
||||
(new-buffer (take (cons frame buffer) (+ num_echoes 1))))
|
||||
(begin
|
||||
(state-set 'buffer new-buffer)
|
||||
(state-set "buffer" new-buffer)
|
||||
;; Blend frames with decay
|
||||
(if (< (length new-buffer) 2)
|
||||
frame
|
||||
|
||||
49
sexp_effects/effects/halftone.sexp
Normal file
49
sexp_effects/effects/halftone.sexp
Normal file
@@ -0,0 +1,49 @@
|
||||
;; Halftone/dot effect - built from primitive xector operations
|
||||
;;
|
||||
;; Uses:
|
||||
;; pool-frame - downsample to cell luminances
|
||||
;; cell-indices - which cell each pixel belongs to
|
||||
;; gather - look up cell value for each pixel
|
||||
;; local-x/y-norm - position within cell [0,1]
|
||||
;; where - conditional per-pixel
|
||||
|
||||
(require-primitives "xector")
|
||||
|
||||
(define-effect halftone
|
||||
:params (
|
||||
(cell-size :type int :default 12 :range [4 32] :desc "Size of halftone cells")
|
||||
(dot-scale :type float :default 0.9 :range [0.1 1.0] :desc "Max dot radius")
|
||||
(invert :type bool :default false :desc "Invert (white dots on black)")
|
||||
)
|
||||
(let* (
|
||||
;; Pool frame to get luminance per cell
|
||||
(pooled (pool-frame frame cell-size))
|
||||
(cell-lum (nth pooled 3)) ; luminance is 4th element
|
||||
|
||||
;; For each output pixel, get its cell index
|
||||
(cell-idx (cell-indices frame cell-size))
|
||||
|
||||
;; Get cell luminance for each pixel
|
||||
(pixel-lum (α/ (gather cell-lum cell-idx) 255))
|
||||
|
||||
;; Position within cell, normalized to [-0.5, 0.5]
|
||||
(lx (α- (local-x-norm frame cell-size) 0.5))
|
||||
(ly (α- (local-y-norm frame cell-size) 0.5))
|
||||
|
||||
;; Distance from cell center (0 at center, ~0.7 at corners)
|
||||
(dist (αsqrt (α+ (α² lx) (α² ly))))
|
||||
|
||||
;; Radius based on luminance (brighter = bigger dot)
|
||||
(radius (α* (if invert (α- 1 pixel-lum) pixel-lum)
|
||||
(α* dot-scale 0.5)))
|
||||
|
||||
;; Is this pixel inside the dot?
|
||||
(inside (α< dist radius))
|
||||
|
||||
;; Output color
|
||||
(fg (if invert 255 0))
|
||||
(bg (if invert 0 255))
|
||||
(out (where inside fg bg)))
|
||||
|
||||
;; Grayscale output
|
||||
(rgb out out out)))
|
||||
30
sexp_effects/effects/mosaic.sexp
Normal file
30
sexp_effects/effects/mosaic.sexp
Normal file
@@ -0,0 +1,30 @@
|
||||
;; Mosaic effect - built from primitive xector operations
|
||||
;;
|
||||
;; Uses:
|
||||
;; pool-frame - downsample to cell averages
|
||||
;; cell-indices - which cell each pixel belongs to
|
||||
;; gather - look up cell value for each pixel
|
||||
|
||||
(require-primitives "xector")
|
||||
|
||||
(define-effect mosaic
|
||||
:params (
|
||||
(cell-size :type int :default 16 :range [4 64] :desc "Size of mosaic cells")
|
||||
)
|
||||
(let* (
|
||||
;; Pool frame to get average color per cell (returns r,g,b,lum xectors)
|
||||
(pooled (pool-frame frame cell-size))
|
||||
(cell-r (nth pooled 0))
|
||||
(cell-g (nth pooled 1))
|
||||
(cell-b (nth pooled 2))
|
||||
|
||||
;; For each output pixel, get its cell index
|
||||
(cell-idx (cell-indices frame cell-size))
|
||||
|
||||
;; Gather: look up cell color for each pixel
|
||||
(out-r (gather cell-r cell-idx))
|
||||
(out-g (gather cell-g cell-idx))
|
||||
(out-b (gather cell-b cell-idx)))
|
||||
|
||||
;; Reconstruct frame
|
||||
(rgb out-r out-g out-b)))
|
||||
@@ -5,9 +5,9 @@
|
||||
:params (
|
||||
(thickness :type int :default 2 :range [1 10])
|
||||
(threshold :type int :default 100 :range [20 300])
|
||||
(color :type list :default (list 0 0 0)
|
||||
(color :type list :default (list 0 0 0))
|
||||
(fill_mode :type string :default "original")
|
||||
)
|
||||
(fill_mode "original"))
|
||||
(let* ((edge-img (image:edge-detect frame (/ threshold 2) threshold))
|
||||
(dilated (if (> thickness 1)
|
||||
(dilate edge-img thickness)
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
:params (
|
||||
(frame_rate :type int :default 12 :range [1 60])
|
||||
)
|
||||
(let* ((held (state-get 'held nil))
|
||||
(held-until (state-get 'held-until 0))
|
||||
(let* ((held (state-get "held" nil))
|
||||
(held-until (state-get "held-until" 0))
|
||||
(frame-duration (/ 1 frame_rate)))
|
||||
(if (or (core:is-nil held) (>= t held-until))
|
||||
(begin
|
||||
(state-set 'held (copy frame))
|
||||
(state-set 'held-until (+ t frame-duration))
|
||||
(state-set "held" (copy frame))
|
||||
(state-set "held-until" (+ t frame-duration))
|
||||
frame)
|
||||
held)))
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
:params (
|
||||
(persistence :type float :default 0.8 :range [0 0.99])
|
||||
)
|
||||
(let* ((buffer (state-get 'buffer nil))
|
||||
(let* ((buffer (state-get "buffer" nil))
|
||||
(current frame))
|
||||
(if (= buffer nil)
|
||||
(begin
|
||||
(state-set 'buffer (copy frame))
|
||||
(state-set "buffer" (copy frame))
|
||||
frame)
|
||||
(let* ((faded (blending:blend-images buffer
|
||||
(make-image (image:width frame) (image:height frame) (list 0 0 0))
|
||||
(- 1 persistence)))
|
||||
(result (blending:blend-mode faded current "lighten")))
|
||||
(begin
|
||||
(state-set 'buffer result)
|
||||
(state-set "buffer" result)
|
||||
result)))))
|
||||
|
||||
44
sexp_effects/effects/xector_feathered_blend.sexp
Normal file
44
sexp_effects/effects/xector_feathered_blend.sexp
Normal file
@@ -0,0 +1,44 @@
|
||||
;; Feathered blend - blend two same-size frames with distance-based falloff
|
||||
;; Center shows overlay, edges show background, with smooth transition
|
||||
|
||||
(require-primitives "xector")
|
||||
|
||||
(define-effect xector_feathered_blend
|
||||
:params (
|
||||
(inner-radius :type float :default 0.3 :range [0 1] :desc "Radius where overlay is 100% (fraction of size)")
|
||||
(fade-width :type float :default 0.2 :range [0 0.5] :desc "Width of fade region (fraction of size)")
|
||||
(overlay :type frame :default nil :desc "Frame to blend in center")
|
||||
)
|
||||
(let* (
|
||||
;; Get normalized distance from center (0 at center, ~1 at corners)
|
||||
(dist (dist-from-center frame))
|
||||
(max-dist (βmax dist))
|
||||
(dist-norm (α/ dist max-dist))
|
||||
|
||||
;; Calculate blend factor:
|
||||
;; - 1.0 when dist-norm < inner-radius (fully overlay)
|
||||
;; - 0.0 when dist-norm > inner-radius + fade-width (fully background)
|
||||
;; - linear ramp between
|
||||
(t (α/ (α- dist-norm inner-radius) fade-width))
|
||||
(blend (α- 1 (αclamp t 0 1)))
|
||||
(inv-blend (α- 1 blend))
|
||||
|
||||
;; Background channels
|
||||
(bg-r (red frame))
|
||||
(bg-g (green frame))
|
||||
(bg-b (blue frame)))
|
||||
|
||||
(if (nil? overlay)
|
||||
;; No overlay - visualize the blend mask
|
||||
(let ((vis (α* blend 255)))
|
||||
(rgb vis vis vis))
|
||||
|
||||
;; Blend overlay with background using the mask
|
||||
(let* ((ov-r (red overlay))
|
||||
(ov-g (green overlay))
|
||||
(ov-b (blue overlay))
|
||||
;; lerp: bg * (1-blend) + overlay * blend
|
||||
(r-out (α+ (α* bg-r inv-blend) (α* ov-r blend)))
|
||||
(g-out (α+ (α* bg-g inv-blend) (α* ov-g blend)))
|
||||
(b-out (α+ (α* bg-b inv-blend) (α* ov-b blend))))
|
||||
(rgb r-out g-out b-out)))))
|
||||
34
sexp_effects/effects/xector_grain.sexp
Normal file
34
sexp_effects/effects/xector_grain.sexp
Normal file
@@ -0,0 +1,34 @@
|
||||
;; Film grain effect using xector operations
|
||||
;; Demonstrates random xectors and mixing scalar/xector math
|
||||
|
||||
(require-primitives "xector")
|
||||
|
||||
(define-effect xector_grain
|
||||
:params (
|
||||
(intensity :type float :default 0.2 :range [0 1] :desc "Grain intensity")
|
||||
(colored :type bool :default false :desc "Use colored grain")
|
||||
)
|
||||
(let* (
|
||||
;; Extract channels
|
||||
(r (red frame))
|
||||
(g (green frame))
|
||||
(b (blue frame))
|
||||
|
||||
;; Generate noise xector(s)
|
||||
;; randn-x generates normal distribution noise
|
||||
(grain-amount (* intensity 50)))
|
||||
|
||||
(if colored
|
||||
;; Colored grain: different noise per channel
|
||||
(let* ((nr (randn-x frame 0 grain-amount))
|
||||
(ng (randn-x frame 0 grain-amount))
|
||||
(nb (randn-x frame 0 grain-amount)))
|
||||
(rgb (αclamp (α+ r nr) 0 255)
|
||||
(αclamp (α+ g ng) 0 255)
|
||||
(αclamp (α+ b nb) 0 255)))
|
||||
|
||||
;; Monochrome grain: same noise for all channels
|
||||
(let ((n (randn-x frame 0 grain-amount)))
|
||||
(rgb (αclamp (α+ r n) 0 255)
|
||||
(αclamp (α+ g n) 0 255)
|
||||
(αclamp (α+ b n) 0 255))))))
|
||||
57
sexp_effects/effects/xector_inset_blend.sexp
Normal file
57
sexp_effects/effects/xector_inset_blend.sexp
Normal file
@@ -0,0 +1,57 @@
|
||||
;; Inset blend - fade a smaller frame into a larger background
|
||||
;; Uses distance-based alpha for smooth transition (no hard edges)
|
||||
|
||||
(require-primitives "xector")
|
||||
|
||||
(define-effect xector_inset_blend
|
||||
:params (
|
||||
(x :type int :default 0 :desc "X position of inset")
|
||||
(y :type int :default 0 :desc "Y position of inset")
|
||||
(fade-width :type int :default 50 :desc "Width of fade region in pixels")
|
||||
(overlay :type frame :default nil :desc "The smaller frame to inset")
|
||||
)
|
||||
(let* (
|
||||
;; Get dimensions
|
||||
(bg-h (first (list (nth (list (red frame)) 0)))) ;; TODO: need image:height
|
||||
(bg-w bg-h) ;; placeholder
|
||||
|
||||
;; For now, create a simple centered circular blend
|
||||
;; Distance from center of overlay position
|
||||
(cx (+ x (/ (- bg-w (* 2 x)) 2)))
|
||||
(cy (+ y (/ (- bg-h (* 2 y)) 2)))
|
||||
|
||||
;; Get coordinates as xectors
|
||||
(px (x-coords frame))
|
||||
(py (y-coords frame))
|
||||
|
||||
;; Distance from center
|
||||
(dx (α- px cx))
|
||||
(dy (α- py cy))
|
||||
(dist (αsqrt (α+ (α* dx dx) (α* dy dy))))
|
||||
|
||||
;; Inner radius (fully overlay) and outer radius (fully background)
|
||||
(inner-r (- (/ bg-w 2) x fade-width))
|
||||
(outer-r (- (/ bg-w 2) x))
|
||||
|
||||
;; Blend factor: 1.0 inside inner-r, 0.0 outside outer-r, linear between
|
||||
(t (α/ (α- dist inner-r) fade-width))
|
||||
(blend (α- 1 (αclamp t 0 1)))
|
||||
|
||||
;; Extract channels from both frames
|
||||
(bg-r (red frame))
|
||||
(bg-g (green frame))
|
||||
(bg-b (blue frame)))
|
||||
|
||||
;; If overlay provided, blend it
|
||||
(if overlay
|
||||
(let* ((ov-r (red overlay))
|
||||
(ov-g (green overlay))
|
||||
(ov-b (blue overlay))
|
||||
;; Linear blend: result = bg * (1-blend) + overlay * blend
|
||||
(r-out (α+ (α* bg-r (α- 1 blend)) (α* ov-r blend)))
|
||||
(g-out (α+ (α* bg-g (α- 1 blend)) (α* ov-g blend)))
|
||||
(b-out (α+ (α* bg-b (α- 1 blend)) (α* ov-b blend))))
|
||||
(rgb r-out g-out b-out))
|
||||
;; No overlay - just show the blend mask for debugging
|
||||
(let ((mask-vis (α* blend 255)))
|
||||
(rgb mask-vis mask-vis mask-vis)))))
|
||||
27
sexp_effects/effects/xector_threshold.sexp
Normal file
27
sexp_effects/effects/xector_threshold.sexp
Normal file
@@ -0,0 +1,27 @@
|
||||
;; Threshold effect using xector operations
|
||||
;; Demonstrates where (conditional select) and β (reduction) for normalization
|
||||
|
||||
(require-primitives "xector")
|
||||
|
||||
(define-effect xector_threshold
|
||||
:params (
|
||||
(threshold :type float :default 0.5 :range [0 1] :desc "Brightness threshold (0-1)")
|
||||
(invert :type bool :default false :desc "Invert the threshold")
|
||||
)
|
||||
(let* (
|
||||
;; Get grayscale luminance as xector
|
||||
(luma (gray frame))
|
||||
|
||||
;; Normalize to 0-1 range
|
||||
(luma-norm (α/ luma 255))
|
||||
|
||||
;; Create boolean mask: pixels above threshold
|
||||
(mask (if invert
|
||||
(α< luma-norm threshold)
|
||||
(α>= luma-norm threshold)))
|
||||
|
||||
;; Use where to select: white (255) if above threshold, black (0) if below
|
||||
(out (where mask 255 0)))
|
||||
|
||||
;; Output as grayscale (same value for R, G, B)
|
||||
(rgb out out out)))
|
||||
36
sexp_effects/effects/xector_vignette.sexp
Normal file
36
sexp_effects/effects/xector_vignette.sexp
Normal file
@@ -0,0 +1,36 @@
|
||||
;; Vignette effect using xector operations
|
||||
;; Demonstrates α (element-wise) and β (reduction) patterns
|
||||
|
||||
(require-primitives "xector")
|
||||
|
||||
(define-effect xector_vignette
|
||||
:params (
|
||||
(strength :type float :default 0.5 :range [0 1])
|
||||
(radius :type float :default 1.0 :range [0.5 2])
|
||||
)
|
||||
(let* (
|
||||
;; Get normalized distance from center for each pixel
|
||||
(dist (dist-from-center frame))
|
||||
|
||||
;; Calculate max distance (corner distance)
|
||||
(max-dist (* (βmax dist) radius))
|
||||
|
||||
;; Calculate brightness factor per pixel: 1 - (dist/max-dist * strength)
|
||||
;; Using explicit α operators
|
||||
(factor (α- 1 (α* (α/ dist max-dist) strength)))
|
||||
|
||||
;; Clamp factor to [0, 1]
|
||||
(factor (αclamp factor 0 1))
|
||||
|
||||
;; Extract channels as xectors
|
||||
(r (red frame))
|
||||
(g (green frame))
|
||||
(b (blue frame))
|
||||
|
||||
;; Apply factor to each channel (implicit element-wise via Xector operators)
|
||||
(r-out (* r factor))
|
||||
(g-out (* g factor))
|
||||
(b-out (* b factor)))
|
||||
|
||||
;; Combine back to frame
|
||||
(rgb r-out g-out b-out)))
|
||||
Reference in New Issue
Block a user