diff --git a/CLAUDE.md b/CLAUDE.md index e9cce05..5fafb91 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,7 +111,7 @@ Compose: `effect name = sepia >> contrast(1.5) >> vignette;` Types: `crossfade`, `slideLeft`, `slideRight`, `slideUp`, `slideDown`, `wipe`, `fadeBlack`, `zoom` -Easing: `linear`, `easeIn`, `easeOut`, `easeInOut`, `bounce` +Easing: `linear`, `easeIn`, `easeOut`, `easeInOut` Syntax: `--- crossfade 300ms easeInOut ---` between gif entries @@ -194,7 +194,7 @@ Version lives in the `VERSION` file (single source of truth). CMake injects it a - `MAC_OUTPUT_DIR` env var overrides default output directory (`~/mac/output/`) - Output paths are sanitized (directory components stripped) to prevent path traversal - Template resolution validates canonical paths stay under script/binary directories -- Max image dimensions clamped to 4096x4096, max GIF frames capped at 200 +- Max image dimensions clamped to 4096x4096, max GIF frames capped at 500 ## Code Style @@ -222,7 +222,7 @@ Version lives in the `VERSION` file (single source of truth). CMake injects it a | `include/MacEnum.h` | Enum sum type (MacEnumDef, MacEnum) | | `include/MacLambda.h` | Lambda/closure with tail expression support | | `include/MacMeme.h` | Template map, resolveTemplate, path traversal protection | -| `include/MacGif.h` | GIF frame storage, frame limit (200 max) | +| `include/MacGif.h` | GIF frame storage, frame limit (500 max) | | `include/GifLimits.h` | `MAX_GIF_FRAMES` constant | | `include/MacAnalyzer.h` | Semantic analyzer entry point | | `include/AnalyzerWalk.h` | AST walk for analysis (match binding scope fix) | diff --git a/docs/reference/src/meme/effects.md b/docs/reference/src/meme/effects.md index 270e288..beb1ff1 100644 --- a/docs/reference/src/meme/effects.md +++ b/docs/reference/src/meme/effects.md @@ -38,7 +38,7 @@ These take a numeric argument and return a function that transforms a meme. | Effect | Parameter | Description | |--------|-----------|-------------| -| `blur(radius)` | 1-20 | Gaussian blur | +| `blur(radius)` | 1-20 | Box blur | | `pixelate(blockSize)` | 2-50 | Pixelation | | `noise(amount)` | 0.0-1.0 | Random noise overlay | | `saturate(factor)` | 0.0-5.0 | Color saturation (1.0 = normal) | @@ -49,12 +49,18 @@ These take a numeric argument and return a function that transforms a meme. | `posterize(levels)` | 2-32 | Reduce color levels | | `chromatic(offset)` | 1-20 | RGB channel displacement | | `threshold(level)` | 0-255 | Black/white binarization | -| `tint(hexColor)` | hex string | Color tint overlay | | `jpeg(quality)` | 1-100 | JPEG compression artifacts | +> `tint()` is currently not usable from Mac source code. It exists in the +> registry but requires a numeric parameter and hardcodes alpha to 0.5; +> the previously-documented `tint("#RRGGBBAA")` form rejects strings. +> Until this is fixed, prefer `hueShift`, `saturate`, or a `background` +> color on a style for similar effects. See the tracking issue for +> `tint()` before using it. + ``` @blank "Blurry" |> blur(5) => "blurred.png"; -@blank "Warm" |> tint("#FF880044") => "tinted.png"; +@blank "Posterized" |> posterize(4) => "poster.png"; ``` ## Named Effects diff --git a/docs/reference/src/meme/gif.md b/docs/reference/src/meme/gif.md index f0cd914..30d58fd 100644 --- a/docs/reference/src/meme/gif.md +++ b/docs/reference/src/meme/gif.md @@ -87,7 +87,6 @@ gif { | `easeIn` | Accelerate from rest | | `easeOut` | Decelerate to rest | | `easeInOut` | Accelerate then decelerate | -| `bounce` | Bouncing effect at end | ### Loop-Back Transition @@ -138,7 +137,7 @@ Frames with transitions can also have effects: gif { @blank "Day" : 2s --- crossfade 500ms --- - @blank "Night" |> tint("#000044AA") |> vignette : 2s + @blank "Night" |> vignette |> brightness(0.5) : 2s } => "day_night.gif"; ``` @@ -156,7 +155,9 @@ gif { ## Programmatic GIFs -Use the `animate()` function to create GIFs from arrays: +Use the `animate()` function to create GIFs from an array of frames where +every frame has the same duration. The duration argument must be a +`Duration` instance, not a raw number. ``` var frames = [ @@ -164,7 +165,15 @@ var frames = [ @blank "Two", @blank "Three" ]; -animate(frames, 500) => "uniform.gif"; +animate(frames, Duration(500)) => "uniform.gif"; +``` + +For GIFs with per-frame durations, build up a `Gif` with `reduce`: + +``` +var gif = frames + |> reduce((g, m) -> g.frame(m, Duration(500)), Gif()); +gif.save("uniform.gif"); ``` ## Looping @@ -178,6 +187,16 @@ GIFs loop infinitely by default. - Cannot be placed inside grids or other frames - Cannot be nested inside other gifs +## Operational Limits + +- **Frame cap: 500 total frames per GIF.** This includes transition + frames, which are rendered at 15 fps. A 500ms `crossfade` between two + keyframes contributes ~7 interpolated frames on top of the keyframes + themselves. Exceeding 500 frames raises `GIF frame limit exceeded`. +- **Transition frame rate: 15 fps.** A 200ms transition is only 3 frames + and will look stepped; target 400-800ms for smooth transitions. +- **Image dimensions capped at 4096x4096.** Larger values are clamped. + ## See Also - [Saving Output](./save.md) diff --git a/docs/reference/src/meme/grid.md b/docs/reference/src/meme/grid.md index 7fb303b..02139c9 100644 --- a/docs/reference/src/meme/grid.md +++ b/docs/reference/src/meme/grid.md @@ -72,6 +72,39 @@ grid 1x2 { } => "nested.png"; ``` +## Spread from an Array + +When the entries are already in an array, use `...` to spread them into +the grid body, or omit the body entirely and let `grid` derive the shape +from the array's length. + +``` +var memes = data |> map(d -> @two_panel { top: d[0] bottom: d[1] }); + +grid 2x2 { ...memes } => "explicit.png"; // explicit shape +grid memes => "auto.png"; // auto-dimensioned +``` + +If an explicit `COLSxROWS` is given and the array size doesn't match, +extras are truncated and missing cells are filled with blank. + +## Programmatic Composition + +For cases where a literal `grid { }` block is awkward, three native +functions compose memes without syntax sugar: + +| Function | Description | +|----------|-------------| +| `beside(a, b)` | Place two memes side by side | +| `stack(a, b)` | Stack two memes vertically | +| `toGrid(arr, cols, rows)` | Arrange an array of memes into a grid | + +``` +beside(@blank "before", @blank "after") => "compare.png"; +stack(@blank "setup", @blank "punchline") => "bit.png"; +toGrid(memes, 3, 2) => "dashboard.png"; +``` + ## Composition Type `grid` produces a frame type (Meme), which means it can be used inside: diff --git a/docs/reference/src/meme/meme_literal.md b/docs/reference/src/meme/meme_literal.md index de31472..23b2a73 100644 --- a/docs/reference/src/meme/meme_literal.md +++ b/docs/reference/src/meme/meme_literal.md @@ -100,14 +100,41 @@ Pipe memes through effect functions. ## With Positioned Text -Use `x` and `y` for absolute text positioning. +Use `text:` followed by `x:` and `y:` to place text at absolute pixel +coordinates. Multiple positioned entries are allowed and can mix with +the named positions (`top:`, `bottom:`, `center:`). ``` @blank 720x720 { - text: "Anywhere" x: 100 y: 200 + top: "HEADER" + text: "label A" x: 120 y: 200 fontSize: "sm" + text: "label B" x: 520 y: 200 fontSize: 64 + bottom: "footer" } ``` +`fontSize` accepts either a pixel number or one of the size tier strings +`"sm"`, `"md"`, `"lg"`, `"xlg"`. If omitted, the meme's default sizing +applies. + +All positioned text on a single meme shares the meme's outer style +(color, outline, weight, etc.). Per-entry colors are not currently +supported — use `beside`, `stack`, or `grid` to combine memes with +different styles. + +## Meme Assets + +The `@meme.` syntax resolves to a built-in meme asset image. + +``` +@meme.shrek_smirk "When your code compiles" => "shrek.png"; +@meme.girl_side_eye { bottom: "QA finds a bug" } => "qa.png"; +``` + +Available assets: `shrek_smirk`, `shrek_side_eye`, `girl_side_eye`, +`king_bach_stare`, `jordan_crying`, `kid_crying`, `window_despair`, +`guy_crying`, `idk_about_that`. + ## See Also - [Styles](./styles.md) -- text color, outline, font diff --git a/docs/reference/src/meme/styles.md b/docs/reference/src/meme/styles.md index b4226b7..6284c81 100644 --- a/docs/reference/src/meme/styles.md +++ b/docs/reference/src/meme/styles.md @@ -80,21 +80,18 @@ style subtitle { ## Inline Style -Styles can also be applied using keyword modifiers after the template: +Styles can be applied after the template (and after any dimensions): ``` -@blank cinematic { top: "Movie Style" } -``` +// Quick form: dimensions are optional +@blank cinematic "Movie Style" => "movie.png"; -Built-in style keywords: +// Block form: dimensions are required when a style is used +@blank 720x720 cinematic { top: "Movie Style" } => "movie.png"; +``` -| Keyword | Effect | -|---------|--------| -| `impact` | White text, black outline, bold, uppercase | -| `cinematic` | Letter-boxed look | -| `shout` | Large bold uppercase | -| `whisper` | Small, normal weight | -| `panic` | Red text, heavy outline | +A style must be defined with `style name { ... }` before it can be +referenced this way. There are no pre-defined style keywords. ## See Also