- Add primitive_libs/ with modular primitive loading (core, math, image, color, color_ops, filters, geometry, drawing, blending, arrays, ascii) - Effects now explicitly declare dependencies via (require-primitives "...") - Convert ascii-fx-zone from hardcoded special form to loadable primitive - Add _is_symbol/_is_keyword helpers for duck typing to support both sexp_effects.parser.Symbol and artdag.sexp.parser.Symbol classes - Auto-inject _interp and _env for primitives that need them - Remove silent error swallowing in cell_effect evaluation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
103 lines
4.3 KiB
Common Lisp
103 lines
4.3 KiB
Common Lisp
;; Composable ASCII Art with Per-Zone Expression-Driven Effects
|
|
;; Requires ascii primitive library for the ascii-fx-zone primitive
|
|
|
|
(require-primitives "ascii")
|
|
|
|
;; Two modes of operation:
|
|
;;
|
|
;; 1. EXPRESSION MODE: Use zone-* variables in expression parameters
|
|
;; Zone variables available:
|
|
;; zone-row, zone-col: Grid position (integers)
|
|
;; zone-row-norm, zone-col-norm: Normalized position (0-1)
|
|
;; zone-lum: Cell luminance (0-1)
|
|
;; zone-sat: Cell saturation (0-1)
|
|
;; zone-hue: Cell hue (0-360)
|
|
;; zone-r, zone-g, zone-b: RGB components (0-1)
|
|
;;
|
|
;; Example:
|
|
;; (ascii-fx-zone frame
|
|
;; :cols 80
|
|
;; :char_hue (* zone-lum 180)
|
|
;; :char_rotation (* zone-col-norm 30))
|
|
;;
|
|
;; 2. CELL EFFECT MODE: Pass a lambda to apply arbitrary effects per-cell
|
|
;; The lambda receives (cell-image zone-dict) and returns modified cell.
|
|
;; Zone dict contains: row, col, row-norm, col-norm, lum, sat, hue, r, g, b,
|
|
;; char, color, cell_size, plus any bound analysis values.
|
|
;;
|
|
;; Any loaded sexp effect can be called on cells - each cell is just a small frame:
|
|
;; (blur cell radius) - Gaussian blur
|
|
;; (rotate cell angle) - Rotate by angle degrees
|
|
;; (brightness cell factor) - Adjust brightness
|
|
;; (contrast cell factor) - Adjust contrast
|
|
;; (saturation cell factor) - Adjust saturation
|
|
;; (hue_shift cell degrees) - Shift hue
|
|
;; (rgb_split cell offset_x offset_y) - RGB channel split
|
|
;; (invert cell) - Invert colors
|
|
;; (pixelate cell block_size) - Pixelate
|
|
;; (wave cell amplitude freq) - Wave distortion
|
|
;; ... and any other loaded effect
|
|
;;
|
|
;; Example:
|
|
;; (ascii-fx-zone frame
|
|
;; :cols 60
|
|
;; :cell_effect (lambda [cell zone]
|
|
;; (blur (rotate cell (* (get zone "energy") 45))
|
|
;; (if (> (get zone "lum") 0.5) 3 0))))
|
|
|
|
(define-effect ascii_fx_zone
|
|
:params (
|
|
(cols :type int :default 80 :range [20 200]
|
|
:desc "Number of character columns")
|
|
(char_size :type int :default nil :range [4 32]
|
|
:desc "Character cell size in pixels (overrides cols if set)")
|
|
(alphabet :type string :default "standard"
|
|
:desc "Character set: standard, blocks, simple, digits, or custom string")
|
|
(color_mode :type string :default "color"
|
|
:desc "Color mode: color, mono, invert, or any color name/hex")
|
|
(background :type string :default "black"
|
|
:desc "Background color name or hex value")
|
|
(contrast :type float :default 1.5 :range [0.5 3.0]
|
|
:desc "Contrast for character selection")
|
|
(char_hue :type any :default nil
|
|
:desc "Hue shift expression (evaluated per-zone with zone-* vars)")
|
|
(char_saturation :type any :default nil
|
|
:desc "Saturation multiplier expression (1.0 = unchanged)")
|
|
(char_brightness :type any :default nil
|
|
:desc "Brightness multiplier expression (1.0 = unchanged)")
|
|
(char_scale :type any :default nil
|
|
:desc "Character scale expression (1.0 = normal size)")
|
|
(char_rotation :type any :default nil
|
|
:desc "Character rotation expression (degrees)")
|
|
(char_jitter :type any :default nil
|
|
:desc "Position jitter expression (pixels)")
|
|
(cell_effect :type any :default nil
|
|
:desc "Lambda (cell zone) -> cell for arbitrary per-cell effects")
|
|
;; Convenience params for staged recipes (avoids compile-time expression issues)
|
|
(energy :type float :default nil
|
|
:desc "Energy multiplier (0-1) from audio analysis bind")
|
|
(rotation_scale :type float :default 0
|
|
:desc "Max rotation at top-right when energy=1 (degrees)")
|
|
)
|
|
;; The ascii-fx-zone special form handles expression params
|
|
;; If energy + rotation_scale provided, it builds: energy * scale * position_factor
|
|
;; where position_factor = 0 at bottom-left, 3 at top-right
|
|
;; If cell_effect provided, each character is rendered to a cell image,
|
|
;; passed to the lambda, and the result composited back
|
|
(ascii-fx-zone frame
|
|
:cols cols
|
|
:char_size char_size
|
|
:alphabet alphabet
|
|
:color_mode color_mode
|
|
:background background
|
|
:contrast contrast
|
|
:char_hue char_hue
|
|
:char_saturation char_saturation
|
|
:char_brightness char_brightness
|
|
:char_scale char_scale
|
|
:char_rotation char_rotation
|
|
:char_jitter char_jitter
|
|
:cell_effect cell_effect
|
|
:energy energy
|
|
:rotation_scale rotation_scale))
|