@pixerate/flair 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +124 -6
  3. package/dist/catalog/index.d.ts +2 -2
  4. package/dist/catalog/index.js +2 -2
  5. package/dist/chunk-DBTLCXKY.js +332 -0
  6. package/dist/chunk-DBTLCXKY.js.map +1 -0
  7. package/dist/{chunk-VP6IEWIK.js → chunk-H6L67KHI.js} +1 -1
  8. package/dist/chunk-H6VDFBIO.js +498 -0
  9. package/dist/chunk-H6VDFBIO.js.map +1 -0
  10. package/dist/chunk-JL6TI6II.js +724 -0
  11. package/dist/chunk-JL6TI6II.js.map +1 -0
  12. package/dist/{chunk-NGKNI3QX.js → chunk-O44ZJTGG.js} +5 -485
  13. package/dist/chunk-O44ZJTGG.js.map +1 -0
  14. package/dist/chunk-OKYMKSXK.js +414 -0
  15. package/dist/chunk-OKYMKSXK.js.map +1 -0
  16. package/dist/{chunk-OHW7UG6D.js → chunk-ORAFCHX3.js} +26 -2
  17. package/dist/{chunk-OHW7UG6D.js.map → chunk-ORAFCHX3.js.map} +1 -1
  18. package/dist/{chunk-4F4KL4KU.js → chunk-PYDN5G2P.js} +769 -11
  19. package/dist/chunk-PYDN5G2P.js.map +1 -0
  20. package/dist/{chunk-ARKC7LDW.js → chunk-RDU63CDO.js} +2 -331
  21. package/dist/chunk-RDU63CDO.js.map +1 -0
  22. package/dist/chunk-S2RGHHKZ.js +902 -0
  23. package/dist/chunk-S2RGHHKZ.js.map +1 -0
  24. package/dist/{chunk-LVPIIWPP.js → chunk-STKEKXCK.js} +18 -8
  25. package/dist/chunk-STKEKXCK.js.map +1 -0
  26. package/dist/chunk-YBGRXVAE.js +407 -0
  27. package/dist/chunk-YBGRXVAE.js.map +1 -0
  28. package/dist/cli.js +68 -5
  29. package/dist/cli.js.map +1 -1
  30. package/dist/editor/index.d.ts +2 -2
  31. package/dist/editor/index.js +14 -12
  32. package/dist/editor/index.js.map +1 -1
  33. package/dist/effect-26CPF5HS.js +47 -0
  34. package/dist/embed/index.d.ts +215 -0
  35. package/dist/embed/index.js +21 -0
  36. package/dist/embed/index.js.map +1 -0
  37. package/dist/frames-VEcqa40g.d.ts +161 -0
  38. package/dist/index.d.ts +6 -163
  39. package/dist/index.js +86 -78
  40. package/dist/index.js.map +1 -1
  41. package/dist/node.d.ts +167 -7
  42. package/dist/node.js +25 -3
  43. package/dist/react/index.d.ts +33 -2
  44. package/dist/react/index.js +54 -9
  45. package/dist/react/index.js.map +1 -1
  46. package/dist/recorder-CDPPTJMG.js +31 -0
  47. package/dist/recorder-CDPPTJMG.js.map +1 -0
  48. package/dist/{renderer-C2P1qGzP.d.ts → renderer-teaa98RE.d.ts} +1 -1
  49. package/dist/svelte/index.d.ts +33 -4
  50. package/dist/svelte/index.js +51 -9
  51. package/dist/svelte/index.js.map +1 -1
  52. package/dist/{types-DLkbOX8d.d.ts → types-DUOMxZaq.d.ts} +1 -1
  53. package/dist/vector/index.d.ts +52 -1
  54. package/dist/vector/index.js +8 -4
  55. package/package.json +5 -1
  56. package/dist/chunk-4F4KL4KU.js.map +0 -1
  57. package/dist/chunk-ARKC7LDW.js.map +0 -1
  58. package/dist/chunk-LVPIIWPP.js.map +0 -1
  59. package/dist/chunk-NGKNI3QX.js.map +0 -1
  60. package/dist/chunk-UP6QQVRS.js +0 -331
  61. package/dist/chunk-UP6QQVRS.js.map +0 -1
  62. package/dist/recorder-HKFFHQES.js +0 -10
  63. /package/dist/{chunk-VP6IEWIK.js.map → chunk-H6L67KHI.js.map} +0 -0
  64. /package/dist/{recorder-HKFFHQES.js.map → effect-26CPF5HS.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @pixerate/flair
2
2
 
3
+ ## 0.25.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c33dcac: Pair any catalog effect with a picture, video, SVG, words or path, put it on a page, and record or export it.
8
+
9
+ - `@pixerate/flair/embed` (new entry): `mountEffect(host, effect, { subject })` runs a shader or vector effect applied to `{ image }`, `{ video }`, `{ svg }` (markup or URL), `{ text }` or `{ path }`, putting it in the right control (from `effectInputs`) and explaining in a sentence when the effect can't take it; `subjectValues` gives the values without mounting. `recordEffect(effect, { subject, format })` records MP4 (H.264), WebM (VP9, with transparency), GIF or a ZIP of PNG frames in the browser with WebCodecs, frame-exact, with no new dependencies (small MP4, WebM and ZIP writers are included).
10
+ - React `<FlairEffect>` and the Svelte `flairEffect` action run any catalog effect, shader or vector, applied to a subject.
11
+ - `svgAnimatedMarkup(motion)` (in `/vector`) writes a vector effect as one self-contained animated `.svg` file (SMIL), exact for eases, draw-on, motion along paths and view zooms, with written text and counts switched in steps.
12
+
13
+ - c33dcac: Node rendering: `exportEffect` (and `flair render <effect>`) renders a catalog effect on an image, SVG, text, path or video to MP4, WebM (alpha), MOV (ProRes 4444), GIF, PNG frames or, for vector effects, an animated SVG. `flair effects` lists effects with what they take. `exportMotionGraphic` now compiles shaders with the real wrapper (so `iPointer`, `iClicks` and `iTrail` work), loads image controls, sets `int`/`bool` controls correctly, and can keep alpha.
14
+
15
+ ## 0.24.0
16
+
17
+ ### Minor Changes
18
+
19
+ - e909f4e: Catalog: a new **lighting** category, 10 effects that relight a picture or video rather than draw light as a background. The shaders read the picture's brightness as a height map (a Sobel of fine detail plus two broader scales from its mip levels) and shade the normals it gives.
20
+
21
+ - **relight**: a directional light with azimuth, elevation, colour, ambient and a soft specular.
22
+ - **point light**: a torch over a darkened picture, following the pointer while hovered and drifting otherwise.
23
+ - **studio lighting**: three-point lighting on a subject, warm key, cool fill and a rim along its silhouette (alpha, or the corner colour keyed out), on a studio backdrop.
24
+ - **time of day**: dawn, noon, golden hour, blue hour and night on an hour control or animated, with lights glowing from the brightest spots at night.
25
+ - **gobo projection**: window panes, foliage, stars, venetian slats or your own text (a gobo image) projected onto a picture, softly focused and drifting.
26
+ - **neon spill**, **candlelight**, **pool caustics** and **party lights** (beams, sweeping pools, mirror ball and strobe).
27
+ - **lamp switch** (vector, click): a pendant lamp whose pull cord switches a warm light cone on and off.
28
+
3
29
  ## 0.23.0
4
30
 
5
31
  ### Minor Changes
package/README.md CHANGED
@@ -19,7 +19,7 @@ Flair gives products a library of good-looking, configurable visual effects (her
19
19
 
20
20
  ### Core Capabilities
21
21
 
22
- - 🎨 **Effect catalog (`@pixerate/flair/catalog`)**: 287 effects, each with controls and presets. There are 205 WebGL2 shaders (gradients, organic textures and growth, patterns, light, space, retro and pixel art, image treatments, transparent overlays, text fills, image morphs, pointer interactions, pyrotechnics, time warps, melting, electricity, splatters, bubbles, stickers, pointer trails, particles, debris, impacts, comic looks, atmosphere, glows, shadows, distortions, transitions) and 82 animated SVG effects (loaders, status icons, animated titles, shape morphs, UI micro-interactions, pixel sprites, stickers, cartoon effects, glowing and lifting UI, charts, counters, node graphs). [Browse them live](https://pixerate.github.io/flair/).
22
+ - 🎨 **Effect catalog (`@pixerate/flair/catalog`)**: 297 effects, each with controls and presets. There are 214 WebGL2 shaders (gradients, organic textures and growth, patterns, light, lighting that relights pictures, space, retro and pixel art, image treatments, transparent overlays, text fills, image morphs, pointer interactions, pyrotechnics, time warps, melting, electricity, splatters, bubbles, stickers, pointer trails, particles, debris, impacts, comic looks, atmosphere, glows, shadows, distortions, transitions) and 83 animated SVG effects (loaders, status icons, animated titles, shape morphs, UI micro-interactions, pixel sprites, stickers, cartoon effects, glowing and lifting UI, a lamp switch, charts, counters, node graphs). [Browse them live](https://pixerate.github.io/flair/).
23
23
  - ⚡ **Shader engine**:
24
24
  - Shadertoy-style GLSL ES 3.00 fragment shaders, with controls declared in the source (`// @control`), including image inputs.
25
25
  - Compile errors come back with line numbers that match the source.
@@ -43,11 +43,12 @@ Flair gives products a library of good-looking, configurable visual effects (her
43
43
  | :--- | :--- | :--- |
44
44
  | `@pixerate/flair` | Shader engine, vector motion, time and easing, colour, blending, transitions, scenes, interchange exporters | None (browser-safe) |
45
45
  | `@pixerate/flair/catalog` | `EFFECTS`, `getEffect`, `findEffects` (also by `video` use), `effectDefaults`, `renderVectorEffect`, `vectorControls`, GLSL helpers, sample images, `textImage` | None |
46
- | `@pixerate/flair/vector` | `SvgMotion`, `svgFrame`, `svgView`, `SvgPlayer`, `svgFrameMarkup`, `svgViewBox`, `animateCount`, `formatCount`, `morphPaths`, `morphSequence`, `svgMotionIssues` | None |
47
- | `@pixerate/flair/react` | `<VectorEffect>`, `<SvgPlayerView>`: vector effects as React components, server-rendered as a still | `react` ≥ 18 (optional peer) |
48
- | `@pixerate/flair/svelte` | `vectorEffect` action, `vectorEffectStill`: vector effects in Svelte, server-rendered as a still | `svelte` ≥ 5 (optional peer; types only) |
46
+ | `@pixerate/flair/vector` | `SvgMotion`, `svgFrame`, `svgView`, `SvgPlayer`, `svgFrameMarkup`, `svgViewBox`, `animateCount`, `formatCount`, `morphPaths`, `morphSequence`, `svgAnimatedMarkup` (an animated `.svg` file), `svgMotionIssues` | None |
47
+ | `@pixerate/flair/embed` | `mountEffect`, `subjectValues`: any catalog effect on a page, applied to a picture, video, SVG, words or a path; `recordEffect`: MP4, WebM, GIF or PNG frames in the browser | None |
48
+ | `@pixerate/flair/react` | `<FlairEffect>`: any catalog effect applied to a subject; `<VectorEffect>`, `<SvgPlayerView>`: vector effects as React components, server-rendered as a still | `react` ≥ 18 (optional peer) |
49
+ | `@pixerate/flair/svelte` | `flairEffect` action: any catalog effect applied to a subject; `vectorEffect` action, `vectorEffectStill`: vector effects in Svelte, server-rendered as a still | `svelte` ≥ 5 (optional peer; types only) |
49
50
  | `@pixerate/flair/editor` | `EffectEditor`, effect documents, keyframe tracks, renderer bindings, parameter-panel binding, agent tools and instructions | `@pixerate/editor` ≥ 0.29 (optional peer; only its dependency-free `/history`, `/commands`, `/tools`, `/animation` and `/parameters` subpaths) |
50
- | `@pixerate/flair/node` | `exportMotionGraphic`: renders a shader to GIF/MP4 through headless Chrome and ffmpeg | Node.js |
51
+ | `@pixerate/flair/node` | `exportEffect` and `exportMotionGraphic`: render a catalog effect (on a picture, video, SVG, words or a path) or a shader to MP4, WebM, MOV, GIF, PNG frames or animated SVG, through headless Chrome (and ffmpeg for video) | Node.js |
51
52
 
52
53
  The `flair` CLI inspects shaders, writes standalone pages and directs scenes (see [CLI](#-cli)).
53
54
 
@@ -58,7 +59,7 @@ The `flair` CLI inspects shaders, writes standalone pages and directs scenes (se
58
59
  ```mermaid
59
60
  graph TD
60
61
  subgraph Effects["Effects (no dependencies)"]
61
- Catalog["catalog: 287 effects + presets"]
62
+ Catalog["catalog: 297 effects + presets"]
62
63
  Shader["shader engine: controls, compile, render, GIF, HTML"]
63
64
  Vector["vector motion: SvgMotion, SvgPlayer"]
64
65
  Time["time: clocks, easing, keyframes, noise"]
@@ -177,8 +178,63 @@ findEffects({ transparent: true }); // overlays to lay over a page, picture or v
177
178
 
178
179
  `findEffects` filters on `accepts` (all of them), `interaction` (any of them; `[]` for effects that don't answer the pointer) and `transparent`, and the `catalog.search` command takes the same filters and lists each effect's `accepts` and `interactions`. The `interactive` tag is kept in step with `interactions`.
179
180
 
181
+ #### Put an effect on a page
182
+
183
+ `@pixerate/flair/embed` pairs a catalog effect with what it takes and runs it, shader or vector, in one call. `mountEffect(host, effect, { subject })` takes a subject:
184
+ - `{ image }`: a picture's URL.
185
+ - `{ video }`: a playing `<video>`, shown live.
186
+ - `{ svg }`: an SVG, as markup or a URL.
187
+ - `{ text }`: words, typed into a vector effect, or drawn with `textImage` for a shader's text or mask.
188
+ - `{ path }`: path data.
189
+
190
+ It puts the subject in the right control (see `effectInputs`) and throws a sentence if the effect can't take it.
191
+
192
+ ```ts
193
+ import { mountEffect } from "@pixerate/flair/embed";
194
+
195
+ mountEffect(el, "image-duotone", { subject: { image: "/team.jpg" }, preset: "Spotify" });
196
+ mountEffect(el, "crt", { subject: { video: document.querySelector("video")! } });
197
+ mountEffect(el, "neon-outline", { subject: { svg: logoMarkup } });
198
+ mountEffect(el, "typewriter", { subject: { text: "Hello there" } });
199
+ mountEffect(el, "transition-wipe", { subject: [{ image: "/a.jpg" }, { image: "/b.jpg" }] });
200
+ const m = mountEffect(el, "click-ripple"); // overlays get a transparent canvas, interactive effects answer the pointer
201
+ m.setValues({ uSpeed: 2 });
202
+ m.dispose();
203
+ ```
204
+
205
+ `subjectValues(effect, subject)` gives the control values (and live sources) without mounting anything, for `ShaderRenderer`, `ShaderFrames` or `renderVectorEffect`.
206
+
207
+ In React, `<FlairEffect effect="image-duotone" subject={{ image: "/team.jpg" }} style={{ aspectRatio: "16 / 9" }} />` (from `@pixerate/flair/react`) does the same. In Svelte, use `<div use:flairEffect={{ effect, subject }}></div>` (from `@pixerate/flair/svelte`). Either way the effect fills its element, so size it with CSS.
208
+
180
209
  Effects that show a picture (image, transition, morph, time-warp and the like) take transparent pictures too, such as SVG icons (Lucide and similar sets draw black `currentColor` strokes on a clear background). They lay the picture over a backdrop: `uBackdrop` (white by default) shows through its clear parts. Set `uBackdropAlpha` to 0 to keep the picture's alpha instead, and render with `{ alpha: true }` to lay the result over the page. `SAMPLE_IMAGES.icon` is a Lucide-style sample icon. SVGs are rasterised at 2048 px on the long edge, so icons stay sharp.
181
210
 
211
+ #### Recording an effect
212
+
213
+ `recordEffect(effect, options)` (from `@pixerate/flair/embed`) records a catalog effect, applied to a subject, to a file in the browser, with no server and no extra dependencies:
214
+
215
+ ```ts
216
+ import { recordEffect } from "@pixerate/flair/embed";
217
+
218
+ const mp4 = await recordEffect("image-duotone", { subject: { image: "/team.jpg" }, preset: "Spotify" });
219
+ const webm = await recordEffect("crt", { subject: { video }, format: "webm", duration: 5 });
220
+ const gif = await recordEffect("typewriter", { subject: { text: "Hello there" }, format: "gif", width: 640, height: 360, fps: 25 });
221
+ const overlay = await recordEffect("click-ripple", {
222
+ format: "webm",
223
+ alpha: true,
224
+ pointer: { clicks: [{ x: 640, y: 360, time: 0.2 }] }, // or (t) => ({ pointer: [x, y, hover, press] })
225
+ onProgress: (f) => (bar.value = f),
226
+ });
227
+ download(mp4); // a Blob
228
+ ```
229
+
230
+ - **Formats.** `mp4` (H.264) or `webm` (VP9, else VP8) are encoded with WebCodecs and written by small built-in muxers: a faststart MP4 (`moov` before `mdat`) and a WebM with no `Cues`. `gif` uses gifenc. `png` gives a ZIP of numbered frames (`frame-0001.png`, …), so every format is one `Blob`. The default is `mp4` where the browser encodes H.264, else `webm`. When the browser can't make a format, it rejects with a sentence that names the formats that would work.
231
+ - **Exact frames.** Frame `i` is drawn at `start + i / fps` off screen, so the result is the same whatever the page is doing. Shaders render with `ShaderFrames`. A `<video>` subject is paused and sought to each frame's time, then put back. A canvas subject is drawn as it is, so draw it for each frame in `beforeFrame(time)`. Vector effects are drawn with `svgView` and rasterised.
232
+ - **Defaults.** 1280 × 720 at 30 fps. The length is a shader's `loop`, or a vector effect's picture plus its `hold`, else 4 s. The bitrate is `width × height × fps × 0.15`. Video sizes round up to even numbers. `recordingPlan(effect, options)` tells you what a recording will be (format, size, frames, encoder) before any frame is drawn.
233
+ - **Interaction.** Shaders that answer the pointer play their idle demo unless `pointer` scripts it, with the same `pointer`, `clicks` and `trail` that `ShaderFrames.render` takes. Click-triggered vector effects take `steps` (ms).
234
+ - **Transparency.** `alpha` defaults to the effect's `transparent`. It is kept in `webm` (VP9/VP8 with an alpha channel, encoded as a second stream and stored as `BlockAdditions`, as Chrome does), in `png`, and in `gif` (one see-through colour, so edges are hard). An `mp4` has no alpha, so asking for both is refused. When alpha is off, transparent pixels are laid over `background` (black for shaders, white for vector effects).
235
+
236
+ Limits: video needs WebCodecs (Chrome and Edge 94+, Safari 16.4+ for H.264, and Firefox 130+). Safari doesn't play alpha WebM, so use PNG frames there. Everything is held in memory until the end, so record long or 4K clips in parts. Vector effects are rasterised once per frame, which is slower than shaders. A picture from another site needs CORS, as on screen.
237
+
182
238
  #### Effects on video
183
239
 
184
240
  Image controls can show a playing `<video>` (or a canvas) instead of a picture. `setLiveSource(name, video)` puts it in the control named `name`. The video's current frame is uploaded each time it changes, and a paused preview still follows the video. The renderer never plays, seeks or loops the video itself. The control's value keeps its URL, so documents, presets and agents still see a plain string.
@@ -208,6 +264,24 @@ Overlay shaders (`category: "overlay"`) have `transparent: true`: they write cov
208
264
 
209
265
  To morph between any two SVG shapes, `morphPaths(from, to)` (from `@pixerate/flair/vector`) returns two paths with one structure, so `attrs: { d: [a, b] }` blends them point by point: curves and arcs are read, pieces are paired by position and size, and a piece with no partner grows from or shrinks to a point (draw them with `fill-rule="evenodd"`). `morphSequence` does the same for three or more shapes. `feature-morph` morphs two photos from five matching points, and `silhouette-morph` morphs any two silhouettes or logos (or words, through `textImage`).
210
266
 
267
+ #### An animated SVG file
268
+
269
+ `svgAnimatedMarkup(motion, options)` (from `@pixerate/flair/vector`) writes any `SvgMotion` as one self-contained `.svg` that plays and loops on its own, with no script and no player: put it in an `<img src>`, open it as a file, or inline it. The changes are written as SMIL, so it needs no DOM to make:
270
+
271
+ ```ts
272
+ import { effectDefaults, getEffect, renderVectorEffect, type VectorEffect } from "@pixerate/flair/catalog";
273
+ import { svgAnimatedMarkup } from "@pixerate/flair/vector";
274
+
275
+ const effect = getEffect("stat-counter") as VectorEffect;
276
+ const svg = svgAnimatedMarkup(renderVectorEffect(effect, effectDefaults(effect)), { hold: effect.hold })!;
277
+ img.src = `data:image/svg+xml,${encodeURIComponent(svg)}`; // or save it as stat-counter.svg
278
+ ```
279
+
280
+ - **Options**: `hold` (ms on the last frame, default 600), `duration` (the loop, default the picture's length plus `hold`), `loop: false` to play once and stay on the last frame, `steps` (ms) for click-triggered effects, `fps` (default 30) for what is sampled, and `idSuffix`.
281
+ - **How it is written**: each attribute change is one `<animate>` (a `transform` is one `<animateTransform>` per function), and eases are exact `keySplines`. `draw` animates `stroke-dashoffset`, `along` becomes `<animateMotion>` on the path, `view` animates the `viewBox` (keeping `data-fixed` and `data-nonscaling` elements as the player does), and `solo` toggles `display`. Counts and written text are one `<tspan>` per text, shown in turn, sampled at `fps`. Every element starts on the first frame, so it still shows that where SMIL does not run.
282
+ - **Limits**: a count that changes faster than `fps` can be up to half a sample out. A change cut short by the next one, or a `matrix` transform, is sampled. A `view` that frames an element by id (it needs layout) is left out, as in `svgFrameMarkup`. Attributes SMIL cannot blend change in steps. Ids the picture refers to (gradients, filters, paths followed) get a suffix, a hash of the motion by default, so two files inlined in one page don't clash. `#id` selectors in a `<style>` are not renamed. Interaction triggers (hover, press, click) cannot be written as SMIL, so a click-triggered effect plays its `steps` on a timer.
283
+ - **Size**: catalog effects come out at 1 to 105 KB (`drip-text` is the largest).
284
+
211
285
  ### React
212
286
 
213
287
  `@pixerate/flair/react` wraps the player, so a page needs no ref, effect or dispose of its own. `<VectorEffect>` plays a catalog vector effect by id, `<SvgPlayerView>` any `SvgMotion`:
@@ -384,6 +458,7 @@ The exporters carry the camera too:
384
458
  | organic | liquid marble, smoke, lava lamp, plasma, caustics, flames, reaction diffusion, cell division, coral growth, veins, ferrofluid, jellyfish, fur, blob morph |
385
459
  | pattern | dot wave, Truchet flow, hex pulse, living cells, moiré rings, kaleidoscope, stripes, neon circuit |
386
460
  | light / space | bokeh, god rays, sparkles, neon ripple, light ribbons, warp starfield, nebula, event horizon |
461
+ | lighting | relight, point light, studio lighting, time of day, gobo projection, neon spill, candlelight, pool caustics, party lights, lamp switch |
387
462
  | retro | synthwave, digital rain, dithered gradient, TV static, 8-bit, Game Boy screen, pixel fire, pixel landscape, OutRun road, palette cycle, raster bars, tape loading, pixel water, pixel text, coin spin, space invader, heart health bar, chomp loader |
388
463
  | image | ripple, glitch, duotone, halftone, pixel reveal, Ken Burns, ASCII art, shatter, melt, risograph, thermal, edge glow, tilt-shift, slit-scan, mosaic flip, chromatic lens, oil paint, CRT screen, film look |
389
464
  | overlay | rain on glass, snowfall, lens flare, vignette & grain, scanline sweep, spotlight, embers, shine sweep, frost creep, viewfinder |
@@ -421,8 +496,51 @@ npx flair html ./effect.frag --out page.html --open
421
496
  npx flair scene validate ./scene.json # layout, framing and timing checks
422
497
  npx flair scene review ./scene.json # fast headless simulation + review
423
498
  npx flair scene lottie ./scene.json out.json # also: fcp7, edl, motn
499
+
500
+ # Catalog effects to files
501
+ npx flair effects --accepts video # pick an effect: what each takes (--interaction click, --category image, --kind, --search, --json)
502
+ npx flair render image-duotone --image team.jpg --preset Spotify -o team.mp4
503
+ npx flair render crt --video clip.mp4 -o crt.mp4
504
+ npx flair render click-ripple --alpha -o ripple.webm
505
+ npx flair render shader-text --text "FLAIR" --set uSpeed=2 --size 1280x720 -o title.mov
506
+ npx flair render typewriter --text "Hello" -o hello.gif
507
+ npx flair render toggle-switch -o toggle.svg # vector effects: an animated SVG, no browser needed
508
+ npx flair render aurora --duration 4 -o frames/ # PNG frames (or -o out_%04d.png)
509
+
510
+ # A shader file of your own
511
+ npx flair export ./effect.frag -o out.mp4 -w 1280 -h 720 -d 5 --values '{"speed":2}'
424
512
  ```
425
513
 
514
+ #### Rendering in Node
515
+
516
+ `flair render` and `flair export` render in headless Chrome, so a shader draws exactly as `ShaderRenderer` draws it: the same GLSL wrapper and built-ins (`iPointer`, `iClicks`, `iTrail`), image controls (SVGs sized up first), `int` and `bool` controls. The output format comes from the extension:
517
+
518
+ | Output | Notes |
519
+ | --- | --- |
520
+ | `.mp4` | H.264, yuv420p, BT.709. Always opaque (`--alpha` is refused). |
521
+ | `.webm` | VP9; `--alpha` keeps transparency (yuva420p). |
522
+ | `.mov` | ProRes 4444; `--alpha` keeps transparency. |
523
+ | `.gif` | 256 colours a frame; `--alpha` gives one-bit transparency. No ffmpeg needed. |
524
+ | `.png` | Frames: a folder (`-o frames/`) or a pattern (`-o out_%04d.png`). No ffmpeg needed. |
525
+ | `.svg` | Vector effects only: one animated SVG (`svgAnimatedMarkup`), with no browser at all. |
526
+
527
+ Subjects: `--image` (a file or URL), `--svg` (a file, URL or markup), `--text` (with `--font`, `--weight`), `--path` (path data) and `--video` (a file, decoded with ffmpeg to one frame per output frame, looping a short clip; the render lasts as long as the clip, up to 10 s, unless `--duration` says). Repeat a flag for effects that take two pictures, such as transitions. `--set name=value` sets a control (numbers, `true`/`false`, colours like `#ff0080`, or comma-separated vectors), over `--preset`.
528
+
529
+ Without `--alpha`, overlays are drawn over `--background` (black for shaders, white for vector effects). Effects that answer the pointer get a scripted one that hovers, wanders and clicks (`--no-pointer` leaves it still); click-triggered vector effects are clicked through their steps, and hover or press ones play through and back.
530
+
531
+ Chrome is found in the usual places, in Playwright's downloads, or at `CHROME_PATH`; ffmpeg on the `PATH` or at `FFMPEG_PATH`. The same from code:
532
+
533
+ ```ts
534
+ import { exportEffect, exportMotionGraphic } from "@pixerate/flair/node";
535
+
536
+ await exportEffect("image-duotone", { subject: { image: "team.jpg" }, preset: "Spotify", outputPath: "team.mp4" });
537
+ await exportEffect("click-ripple", { alpha: true, outputPath: "ripple.webm" });
538
+ await exportEffect("toggle-switch", { outputPath: "toggle.svg" });
539
+ await exportMotionGraphic({ source, values: { speed: 2 }, outputPath: "out.gif", duration: 2 });
540
+ ```
541
+
542
+ Limits: vector frames are drawn with `svgFrameMarkup`, so a change `along` a path and a `view` that frames an element by id stay where the markup puts them; GIF frame delays are whole hundredths of a second (30 fps plays at 33.3).
543
+
426
544
  ---
427
545
 
428
546
  ## 🧪 Interactive Browser Demo
@@ -1,7 +1,7 @@
1
1
  import { a as ControlDef, C as ControlValue } from '../controls-Ec6P0Vpy.js';
2
2
  import { a as SvgTrigger } from '../svg-BhRboN9q.js';
3
- import { a as VideoUse, E as EffectDefinition, b as EffectCategory, S as ShaderEffect } from '../types-DLkbOX8d.js';
4
- export { C as CURRENT_COLOR, c as EFFECT_CATEGORIES, d as EffectPreset, e as VECTOR_CONTROL_TYPES, f as VIDEO_USES, g as VectorControl, V as VectorEffect, h as colorControl, i as isCurrentColor, n as numberControl, r as renderVectorEffect, v as vectorControlDef, j as vectorControlIssues, k as vectorControls, l as vectorEffectControls } from '../types-DLkbOX8d.js';
3
+ import { a as VideoUse, E as EffectDefinition, b as EffectCategory, S as ShaderEffect } from '../types-DUOMxZaq.js';
4
+ export { C as CURRENT_COLOR, c as EFFECT_CATEGORIES, d as EffectPreset, e as VECTOR_CONTROL_TYPES, f as VIDEO_USES, g as VectorControl, V as VectorEffect, h as colorControl, i as isCurrentColor, n as numberControl, r as renderVectorEffect, v as vectorControlDef, j as vectorControlIssues, k as vectorControls, l as vectorEffectControls } from '../types-DUOMxZaq.js';
5
5
 
6
6
  /**
7
7
  * What an effect takes and how it answers the pointer, worked out from the
@@ -30,10 +30,10 @@ import {
30
30
  vectorControlIssues,
31
31
  vectorControls,
32
32
  vectorEffectControls
33
- } from "../chunk-4F4KL4KU.js";
34
- import "../chunk-OHW7UG6D.js";
33
+ } from "../chunk-PYDN5G2P.js";
35
34
  import "../chunk-EHDJ3CPJ.js";
36
35
  import "../chunk-VYOMXE6I.js";
36
+ import "../chunk-ORAFCHX3.js";
37
37
  export {
38
38
  CURRENT_COLOR,
39
39
  EFFECTS,
@@ -0,0 +1,332 @@
1
+ // src/shader/color.ts
2
+ function srgbToLinear(c) {
3
+ const v = Math.max(0, c);
4
+ return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
5
+ }
6
+ function linearToSrgb(c) {
7
+ const v = Math.max(0, c);
8
+ return v <= 31308e-7 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;
9
+ }
10
+ function srgbToLinearRgb(rgb) {
11
+ return [srgbToLinear(rgb[0]), srgbToLinear(rgb[1]), srgbToLinear(rgb[2])];
12
+ }
13
+ function linearRgbToSrgb(rgb) {
14
+ return [linearToSrgb(rgb[0]), linearToSrgb(rgb[1]), linearToSrgb(rgb[2])];
15
+ }
16
+ function linearSrgbToOklab(rgb) {
17
+ const [r, g, b] = rgb;
18
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
19
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
20
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
21
+ const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
22
+ const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
23
+ const bVal = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
24
+ return [L, a, bVal];
25
+ }
26
+ function oklabToLinearSrgb(lab) {
27
+ const [L, a, b] = lab;
28
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
29
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
30
+ const s_ = L - 0.0894841775 * a - 1.291485548 * b;
31
+ const l = l_ * l_ * l_;
32
+ const m = m_ * m_ * m_;
33
+ const s = s_ * s_ * s_;
34
+ const r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
35
+ const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
36
+ const bVal = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
37
+ return [r, g, bVal];
38
+ }
39
+ function srgbToOklab(rgb) {
40
+ return linearSrgbToOklab(srgbToLinearRgb(rgb));
41
+ }
42
+ function oklabToSrgb(lab) {
43
+ const linear = oklabToLinearSrgb(lab);
44
+ return linearRgbToSrgb(linear);
45
+ }
46
+ function oklabToOklch(lab) {
47
+ const [L, a, b] = lab;
48
+ const C = Math.sqrt(a * a + b * b);
49
+ let h = Math.atan2(b, a);
50
+ if (h < 0) h += 2 * Math.PI;
51
+ return [L, C, h];
52
+ }
53
+ function oklchToOklab(lch) {
54
+ const [L, C, h] = lch;
55
+ return [L, C * Math.cos(h), C * Math.sin(h)];
56
+ }
57
+ function interpolateOklab(c1, c2, t) {
58
+ const lab1 = srgbToOklab(c1);
59
+ const lab2 = srgbToOklab(c2);
60
+ const clampedT = Math.max(0, Math.min(1, t));
61
+ const lab = [
62
+ lab1[0] + (lab2[0] - lab1[0]) * clampedT,
63
+ lab1[1] + (lab2[1] - lab1[1]) * clampedT,
64
+ lab1[2] + (lab2[2] - lab1[2]) * clampedT
65
+ ];
66
+ const res = oklabToSrgb(lab);
67
+ return [
68
+ Math.max(0, Math.min(1, res[0])),
69
+ Math.max(0, Math.min(1, res[1])),
70
+ Math.max(0, Math.min(1, res[2]))
71
+ ];
72
+ }
73
+ function interpolateOklch(c1, c2, t) {
74
+ const lch1 = oklabToOklch(srgbToOklab(c1));
75
+ const lch2 = oklabToOklch(srgbToOklab(c2));
76
+ const clampedT = Math.max(0, Math.min(1, t));
77
+ const L = lch1[0] + (lch2[0] - lch1[0]) * clampedT;
78
+ const C = lch1[1] + (lch2[1] - lch1[1]) * clampedT;
79
+ let dHue = lch2[2] - lch1[2];
80
+ if (dHue > Math.PI) dHue -= 2 * Math.PI;
81
+ if (dHue < -Math.PI) dHue += 2 * Math.PI;
82
+ let h = lch1[2] + dHue * clampedT;
83
+ if (h < 0) h += 2 * Math.PI;
84
+ if (h >= 2 * Math.PI) h -= 2 * Math.PI;
85
+ const res = oklabToSrgb(oklchToOklab([L, C, h]));
86
+ return [
87
+ Math.max(0, Math.min(1, res[0])),
88
+ Math.max(0, Math.min(1, res[1])),
89
+ Math.max(0, Math.min(1, res[2]))
90
+ ];
91
+ }
92
+ function mulMat3(m, v) {
93
+ return [
94
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
95
+ m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
96
+ m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
97
+ ];
98
+ }
99
+ var MAT_LINEAR_SRGB_TO_P3 = [
100
+ 0.822462,
101
+ 0.177538,
102
+ 0,
103
+ 0.033194,
104
+ 0.966806,
105
+ 0,
106
+ 0.017083,
107
+ 0.072397,
108
+ 0.91052
109
+ ];
110
+ var MAT_P3_TO_LINEAR_SRGB = [
111
+ 1.22494,
112
+ -0.22494,
113
+ 0,
114
+ -0.042057,
115
+ 1.042057,
116
+ 0,
117
+ -0.019638,
118
+ -0.078636,
119
+ 1.098274
120
+ ];
121
+ var MAT_LINEAR_SRGB_TO_ACESCG = [
122
+ 0.613097,
123
+ 0.339523,
124
+ 0.047379,
125
+ 0.070194,
126
+ 0.916354,
127
+ 0.013452,
128
+ 0.020616,
129
+ 0.10957,
130
+ 0.869815
131
+ ];
132
+ var MAT_ACESCG_TO_LINEAR_SRGB = [
133
+ 1.704859,
134
+ -0.621714,
135
+ -0.083299,
136
+ -0.130078,
137
+ 1.140735,
138
+ -0.01056,
139
+ -0.023964,
140
+ -0.128975,
141
+ 1.153014
142
+ ];
143
+ function convertColorSpace(color, from, to) {
144
+ if (from === to) return [...color];
145
+ let lin;
146
+ switch (from) {
147
+ case "srgb":
148
+ lin = srgbToLinearRgb(color);
149
+ break;
150
+ case "linear-srgb":
151
+ lin = [...color];
152
+ break;
153
+ case "rec709":
154
+ lin = [
155
+ Math.pow(Math.max(0, color[0]), 2.4),
156
+ Math.pow(Math.max(0, color[1]), 2.4),
157
+ Math.pow(Math.max(0, color[2]), 2.4)
158
+ ];
159
+ break;
160
+ case "display-p3": {
161
+ const p3Lin = srgbToLinearRgb(color);
162
+ lin = mulMat3(MAT_P3_TO_LINEAR_SRGB, p3Lin);
163
+ break;
164
+ }
165
+ case "acescg":
166
+ lin = mulMat3(MAT_ACESCG_TO_LINEAR_SRGB, color);
167
+ break;
168
+ }
169
+ switch (to) {
170
+ case "linear-srgb":
171
+ return lin;
172
+ case "srgb":
173
+ return linearRgbToSrgb(lin);
174
+ case "rec709":
175
+ return [
176
+ Math.pow(Math.max(0, lin[0]), 1 / 2.4),
177
+ Math.pow(Math.max(0, lin[1]), 1 / 2.4),
178
+ Math.pow(Math.max(0, lin[2]), 1 / 2.4)
179
+ ];
180
+ case "display-p3": {
181
+ const p3Lin = mulMat3(MAT_LINEAR_SRGB_TO_P3, lin);
182
+ return linearRgbToSrgb(p3Lin);
183
+ }
184
+ case "acescg":
185
+ return mulMat3(MAT_LINEAR_SRGB_TO_ACESCG, lin);
186
+ }
187
+ }
188
+ function applyAscCdl(color, cdl) {
189
+ const slope = cdl.slope ?? [1, 1, 1];
190
+ const offset = cdl.offset ?? [0, 0, 0];
191
+ const power = cdl.power ?? [1, 1, 1];
192
+ const sat = cdl.saturation ?? 1;
193
+ let r = Math.pow(Math.max(0, color[0] * slope[0] + offset[0]), power[0]);
194
+ let g = Math.pow(Math.max(0, color[1] * slope[1] + offset[1]), power[1]);
195
+ let b = Math.pow(Math.max(0, color[2] * slope[2] + offset[2]), power[2]);
196
+ if (sat !== 1) {
197
+ const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
198
+ r = luma + sat * (r - luma);
199
+ g = luma + sat * (g - luma);
200
+ b = luma + sat * (b - luma);
201
+ }
202
+ return [Math.max(0, r), Math.max(0, g), Math.max(0, b)];
203
+ }
204
+ function applyLiftGammaGain(color, lgg) {
205
+ const lift = lgg.lift ?? [0, 0, 0];
206
+ const gamma = lgg.gamma ?? [1, 1, 1];
207
+ const gain = lgg.gain ?? [1, 1, 1];
208
+ const processChannel = (val, l, gm, gn) => {
209
+ const lifted = val + l * (1 - val);
210
+ const gained = Math.max(0, lifted * gn);
211
+ const safeGamma = Math.max(1e-4, gm);
212
+ return Math.pow(gained, 1 / safeGamma);
213
+ };
214
+ return [
215
+ processChannel(color[0], lift[0], gamma[0], gain[0]),
216
+ processChannel(color[1], lift[1], gamma[1], gain[1]),
217
+ processChannel(color[2], lift[2], gamma[2], gain[2])
218
+ ];
219
+ }
220
+ function applyVibrance(color, amount) {
221
+ if (Math.abs(amount) < 1e-4) return [...color];
222
+ const [r, g, b] = color;
223
+ const maxC = Math.max(r, g, b);
224
+ const minC = Math.min(r, g, b);
225
+ const sat = maxC > 1e-5 ? (maxC - minC) / maxC : 0;
226
+ let skinProtection = 1;
227
+ if (r > g && g > b) {
228
+ const skinFactor = (g - b) / (r - b + 1e-5);
229
+ skinProtection = Math.max(0.2, 1 - skinFactor * 0.5);
230
+ }
231
+ const boost = amount * (1 - sat) * skinProtection;
232
+ const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
233
+ const newR = Math.max(0, Math.min(1, luma + (1 + boost) * (r - luma)));
234
+ const newG = Math.max(0, Math.min(1, luma + (1 + boost) * (g - luma)));
235
+ const newB = Math.max(0, Math.min(1, luma + (1 + boost) * (b - luma)));
236
+ return [newR, newG, newB];
237
+ }
238
+ function getWhiteBalanceGains(kelvin, tint = 0) {
239
+ const k = Math.max(2e3, Math.min(12e3, kelvin)) / 100;
240
+ let r;
241
+ let g;
242
+ let b;
243
+ if (k <= 66) {
244
+ r = 255;
245
+ g = 99.4708025861 * Math.log(k) - 161.1195681661;
246
+ b = k <= 19 ? 0 : 138.5177312231 * Math.log(k - 10) - 305.0447927307;
247
+ } else {
248
+ r = 329.698727446 * Math.pow(k - 60, -0.1332047592);
249
+ g = 288.1221695283 * Math.pow(k - 60, -0.0755148492);
250
+ b = 255;
251
+ }
252
+ const d65R = 255;
253
+ const d65G = 250.7;
254
+ const d65B = 255;
255
+ let gainR = r / d65R;
256
+ let gainG = g / d65G;
257
+ let gainB = b / d65B;
258
+ const tintFactor = tint / 100;
259
+ gainG *= 1 - tintFactor * 0.15;
260
+ gainR *= 1 + tintFactor * 0.05;
261
+ gainB *= 1 + tintFactor * 0.05;
262
+ return [gainR, gainG, gainB];
263
+ }
264
+ function getOklabGLSL() {
265
+ return `
266
+ // Oklab <-> Linear sRGB (Bj\xF6rn Ottosson)
267
+ vec3 flair_linear_to_oklab(vec3 c) {
268
+ float l = pow(0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b, 1.0 / 3.0);
269
+ float m = pow(0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b, 1.0 / 3.0);
270
+ float s = pow(0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b, 1.0 / 3.0);
271
+ return vec3(
272
+ 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
273
+ 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
274
+ 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s
275
+ );
276
+ }
277
+
278
+ vec3 flair_oklab_to_linear(vec3 lab) {
279
+ float l_ = lab.x + 0.3963377774 * lab.y + 0.2158037573 * lab.z;
280
+ float m_ = lab.x - 0.1055613458 * lab.y - 0.0638541728 * lab.z;
281
+ float s_ = lab.x - 0.0894841775 * lab.y - 1.2914855480 * lab.z;
282
+ float l = l_ * l_ * l_;
283
+ float m = m_ * m_ * m_;
284
+ float s = s_ * s_ * s_;
285
+ return vec3(
286
+ +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
287
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
288
+ -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
289
+ );
290
+ }
291
+ `.trim();
292
+ }
293
+ function getColorGradingGLSL() {
294
+ return `
295
+ // ASC CDL Grading
296
+ vec3 flair_cdl(vec3 color, vec3 slope, vec3 offset, vec3 power, float sat) {
297
+ vec3 c = pow(max(color * slope + offset, vec3(0.0)), power);
298
+ float luma = dot(c, vec3(0.2126, 0.7152, 0.0722));
299
+ return mix(vec3(luma), c, sat);
300
+ }
301
+
302
+ // 3-Way Lift / Gamma / Gain Grading
303
+ vec3 flair_lift_gamma_gain(vec3 color, vec3 lift, vec3 gamma, vec3 gain) {
304
+ vec3 lifted = color + lift * (vec3(1.0) - color);
305
+ vec3 gained = max(lifted * gain, vec3(0.0));
306
+ return pow(gained, 1.0 / max(gamma, vec3(0.0001)));
307
+ }
308
+ `.trim();
309
+ }
310
+
311
+ export {
312
+ srgbToLinear,
313
+ linearToSrgb,
314
+ srgbToLinearRgb,
315
+ linearRgbToSrgb,
316
+ linearSrgbToOklab,
317
+ oklabToLinearSrgb,
318
+ srgbToOklab,
319
+ oklabToSrgb,
320
+ oklabToOklch,
321
+ oklchToOklab,
322
+ interpolateOklab,
323
+ interpolateOklch,
324
+ convertColorSpace,
325
+ applyAscCdl,
326
+ applyLiftGammaGain,
327
+ applyVibrance,
328
+ getWhiteBalanceGains,
329
+ getOklabGLSL,
330
+ getColorGradingGLSL
331
+ };
332
+ //# sourceMappingURL=chunk-DBTLCXKY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shader/color.ts"],"sourcesContent":["/**\n * Perceptual Color Science & Film Grading Engine.\n *\n * Implements:\n * - Oklab & Oklch: Perceptually uniform color spaces that eliminate hue shifts and gray dead zones.\n * - Standard Color Spaces: sRGB, Linear sRGB, Rec.709, Display P3, and ACEScg.\n * - Film Color Grading: ASC CDL (Slope, Offset, Power), Lift/Gamma/Gain, Vibrance (skin tone protection),\n * and White Balance (Kelvin color temperature & tint).\n * - GLSL code generators for real-time WebGL fragment shader execution.\n */\n\nexport type ColorRGB = [number, number, number];\nexport type ColorRGBA = [number, number, number, number];\n\n// -----------------------------------------------------------------------------\n// sRGB <-> Linear Transfer Curves\n// -----------------------------------------------------------------------------\n\nexport function srgbToLinear(c: number): number {\n const v = Math.max(0, c);\n return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);\n}\n\nexport function linearToSrgb(c: number): number {\n const v = Math.max(0, c);\n return v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1.0 / 2.4) - 0.055;\n}\n\nexport function srgbToLinearRgb(rgb: ColorRGB): ColorRGB {\n return [srgbToLinear(rgb[0]), srgbToLinear(rgb[1]), srgbToLinear(rgb[2])];\n}\n\nexport function linearRgbToSrgb(rgb: ColorRGB): ColorRGB {\n return [linearToSrgb(rgb[0]), linearToSrgb(rgb[1]), linearToSrgb(rgb[2])];\n}\n\n// -----------------------------------------------------------------------------\n// Oklab & Oklch Perceptual Color Spaces (Björn Ottosson, 2020)\n// -----------------------------------------------------------------------------\n\nexport function linearSrgbToOklab(rgb: ColorRGB): [number, number, number] {\n const [r, g, b] = rgb;\n\n // Linear sRGB to LMS cone response\n const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\n const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\n const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\n\n // LMS to Oklab (L: lightness [0..1], a: green-red, b: blue-yellow)\n const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;\n const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;\n const bVal = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;\n\n return [L, a, bVal];\n}\n\nexport function oklabToLinearSrgb(lab: [number, number, number]): ColorRGB {\n const [L, a, b] = lab;\n\n const l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n const m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n const s_ = L - 0.0894841775 * a - 1.291485548 * b;\n\n const l = l_ * l_ * l_;\n const m = m_ * m_ * m_;\n const s = s_ * s_ * s_;\n\n const r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;\n const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;\n const bVal = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;\n\n return [r, g, bVal];\n}\n\nexport function srgbToOklab(rgb: ColorRGB): [number, number, number] {\n return linearSrgbToOklab(srgbToLinearRgb(rgb));\n}\n\nexport function oklabToSrgb(lab: [number, number, number]): ColorRGB {\n const linear = oklabToLinearSrgb(lab);\n return linearRgbToSrgb(linear);\n}\n\nexport function oklabToOklch(lab: [number, number, number]): [number, number, number] {\n const [L, a, b] = lab;\n const C = Math.sqrt(a * a + b * b);\n let h = Math.atan2(b, a);\n if (h < 0) h += 2 * Math.PI;\n return [L, C, h];\n}\n\nexport function oklchToOklab(lch: [number, number, number]): [number, number, number] {\n const [L, C, h] = lch;\n return [L, C * Math.cos(h), C * Math.sin(h)];\n}\n\n/**\n * Perceptually uniform color interpolation using Oklab space.\n * Prevents intermediate grayish dead spots.\n */\nexport function interpolateOklab(c1: ColorRGB, c2: ColorRGB, t: number): ColorRGB {\n const lab1 = srgbToOklab(c1);\n const lab2 = srgbToOklab(c2);\n const clampedT = Math.max(0, Math.min(1, t));\n\n const lab: [number, number, number] = [\n lab1[0] + (lab2[0] - lab1[0]) * clampedT,\n lab1[1] + (lab2[1] - lab1[1]) * clampedT,\n lab1[2] + (lab2[2] - lab1[2]) * clampedT,\n ];\n\n const res = oklabToSrgb(lab);\n return [\n Math.max(0, Math.min(1, res[0])),\n Math.max(0, Math.min(1, res[1])),\n Math.max(0, Math.min(1, res[2])),\n ];\n}\n\n/**\n * Perceptually uniform color interpolation using Oklch cylindrical space.\n * Interpolates along the shortest hue arc for vibrant gradients.\n */\nexport function interpolateOklch(c1: ColorRGB, c2: ColorRGB, t: number): ColorRGB {\n const lch1 = oklabToOklch(srgbToOklab(c1));\n const lch2 = oklabToOklch(srgbToOklab(c2));\n const clampedT = Math.max(0, Math.min(1, t));\n\n const L = lch1[0] + (lch2[0] - lch1[0]) * clampedT;\n const C = lch1[1] + (lch2[1] - lch1[1]) * clampedT;\n\n // Shortest arc hue interpolation\n let dHue = lch2[2] - lch1[2];\n if (dHue > Math.PI) dHue -= 2 * Math.PI;\n if (dHue < -Math.PI) dHue += 2 * Math.PI;\n let h = lch1[2] + dHue * clampedT;\n if (h < 0) h += 2 * Math.PI;\n if (h >= 2 * Math.PI) h -= 2 * Math.PI;\n\n const res = oklabToSrgb(oklchToOklab([L, C, h]));\n return [\n Math.max(0, Math.min(1, res[0])),\n Math.max(0, Math.min(1, res[1])),\n Math.max(0, Math.min(1, res[2])),\n ];\n}\n\n// -----------------------------------------------------------------------------\n// Color Space Transformations (sRGB, Rec.709, Display P3, ACEScg)\n// -----------------------------------------------------------------------------\n\nexport type ColorSpace = 'srgb' | 'linear-srgb' | 'rec709' | 'display-p3' | 'acescg';\n\ntype Matrix3x3 = [\n number, number, number,\n number, number, number,\n number, number, number\n];\n\nfunction mulMat3(m: Matrix3x3, v: ColorRGB): ColorRGB {\n return [\n m[0] * v[0] + m[1] * v[1] + m[2] * v[2],\n m[3] * v[0] + m[4] * v[1] + m[5] * v[2],\n m[6] * v[0] + m[7] * v[1] + m[8] * v[2],\n ];\n}\n\n// Linear sRGB to Display P3 (linear)\nconst MAT_LINEAR_SRGB_TO_P3: Matrix3x3 = [\n 0.822462, 0.177538, 0.000000,\n 0.033194, 0.966806, 0.000000,\n 0.017083, 0.072397, 0.910520,\n];\n\n// Display P3 (linear) to Linear sRGB\nconst MAT_P3_TO_LINEAR_SRGB: Matrix3x3 = [\n 1.224940, -0.224940, 0.000000,\n -0.042057, 1.042057, 0.000000,\n -0.019638, -0.078636, 1.098274,\n];\n\n// Linear sRGB to ACEScg (AP1 primaries with Bradford adaptation to ACES white)\nconst MAT_LINEAR_SRGB_TO_ACESCG: Matrix3x3 = [\n 0.613097, 0.339523, 0.047379,\n 0.070194, 0.916354, 0.013452,\n 0.020616, 0.109570, 0.869815,\n];\n\n// ACEScg to Linear sRGB\nconst MAT_ACESCG_TO_LINEAR_SRGB: Matrix3x3 = [\n 1.704859, -0.621714, -0.083299,\n -0.130078, 1.140735, -0.010560,\n -0.023964, -0.128975, 1.153014,\n];\n\n/**\n * Converts a color between standard imaging color spaces.\n */\nexport function convertColorSpace(color: ColorRGB, from: ColorSpace, to: ColorSpace): ColorRGB {\n if (from === to) return [...color];\n\n // First convert `from` to Linear sRGB\n let lin: ColorRGB;\n switch (from) {\n case 'srgb':\n lin = srgbToLinearRgb(color);\n break;\n case 'linear-srgb':\n lin = [...color];\n break;\n case 'rec709':\n // BT.709 gamma 2.4 EOTF\n lin = [\n Math.pow(Math.max(0, color[0]), 2.4),\n Math.pow(Math.max(0, color[1]), 2.4),\n Math.pow(Math.max(0, color[2]), 2.4),\n ];\n break;\n case 'display-p3': {\n // P3 sRGB transfer to linear P3, then matrix to linear sRGB\n const p3Lin = srgbToLinearRgb(color);\n lin = mulMat3(MAT_P3_TO_LINEAR_SRGB, p3Lin);\n break;\n }\n case 'acescg':\n lin = mulMat3(MAT_ACESCG_TO_LINEAR_SRGB, color);\n break;\n }\n\n // Then convert from Linear sRGB to `to`\n switch (to) {\n case 'linear-srgb':\n return lin;\n case 'srgb':\n return linearRgbToSrgb(lin);\n case 'rec709':\n // BT.709 gamma 1/2.4\n return [\n Math.pow(Math.max(0, lin[0]), 1 / 2.4),\n Math.pow(Math.max(0, lin[1]), 1 / 2.4),\n Math.pow(Math.max(0, lin[2]), 1 / 2.4),\n ];\n case 'display-p3': {\n const p3Lin = mulMat3(MAT_LINEAR_SRGB_TO_P3, lin);\n return linearRgbToSrgb(p3Lin);\n }\n case 'acescg':\n return mulMat3(MAT_LINEAR_SRGB_TO_ACESCG, lin);\n }\n}\n\n// -----------------------------------------------------------------------------\n// Film Color Grading: ASC CDL, Lift/Gamma/Gain, Vibrance, White Balance\n// -----------------------------------------------------------------------------\n\nexport interface AscCdl {\n /** Multiplicative scaling (default [1, 1, 1]) */\n slope?: ColorRGB;\n /** Additive offset (default [0, 0, 0]) */\n offset?: ColorRGB;\n /** Exponential power (default [1, 1, 1]) */\n power?: ColorRGB;\n /** Master saturation (default 1.0) */\n saturation?: number;\n}\n\n/**\n * Standard ASC CDL (American Society of Cinematographers Color Decision List).\n * Formula: out = (in * slope + offset)^power, followed by Rec.709 luma-weighted saturation.\n */\nexport function applyAscCdl(color: ColorRGB, cdl: AscCdl): ColorRGB {\n const slope = cdl.slope ?? [1, 1, 1];\n const offset = cdl.offset ?? [0, 0, 0];\n const power = cdl.power ?? [1, 1, 1];\n const sat = cdl.saturation ?? 1.0;\n\n let r = Math.pow(Math.max(0, color[0] * slope[0] + offset[0]), power[0]);\n let g = Math.pow(Math.max(0, color[1] * slope[1] + offset[1]), power[1]);\n let b = Math.pow(Math.max(0, color[2] * slope[2] + offset[2]), power[2]);\n\n if (sat !== 1.0) {\n const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;\n r = luma + sat * (r - luma);\n g = luma + sat * (g - luma);\n b = luma + sat * (b - luma);\n }\n\n return [Math.max(0, r), Math.max(0, g), Math.max(0, b)];\n}\n\nexport interface LiftGammaGain {\n /** Shadows adjustment (default [0, 0, 0]) */\n lift?: ColorRGB;\n /** Midtones gamma adjustment (default [1, 1, 1]) */\n gamma?: ColorRGB;\n /** Highlights gain adjustment (default [1, 1, 1]) */\n gain?: ColorRGB;\n}\n\n/**\n * Three-way Lift / Gamma / Gain film color grading.\n * Formula: out = (gain * (in + lift * (1 - in)))^(1 / gamma)\n */\nexport function applyLiftGammaGain(color: ColorRGB, lgg: LiftGammaGain): ColorRGB {\n const lift = lgg.lift ?? [0, 0, 0];\n const gamma = lgg.gamma ?? [1, 1, 1];\n const gain = lgg.gain ?? [1, 1, 1];\n\n const processChannel = (val: number, l: number, gm: number, gn: number): number => {\n const lifted = val + l * (1.0 - val);\n const gained = Math.max(0, lifted * gn);\n const safeGamma = Math.max(1e-4, gm);\n return Math.pow(gained, 1.0 / safeGamma);\n };\n\n return [\n processChannel(color[0], lift[0], gamma[0], gain[0]),\n processChannel(color[1], lift[1], gamma[1], gain[1]),\n processChannel(color[2], lift[2], gamma[2], gain[2]),\n ];\n}\n\n/**\n * Vibrance adjustment with skin tone protection.\n * Selectively boosts muted colors while preventing already saturated colors and skin tones\n * from clipping.\n * @param amount Vibrance delta in [-1..1]\n */\nexport function applyVibrance(color: ColorRGB, amount: number): ColorRGB {\n if (Math.abs(amount) < 1e-4) return [...color];\n\n const [r, g, b] = color;\n const maxC = Math.max(r, g, b);\n const minC = Math.min(r, g, b);\n const sat = maxC > 1e-5 ? (maxC - minC) / maxC : 0;\n\n // Protect warm skin tones (hue around red/orange/yellow: r > g > b)\n let skinProtection = 1.0;\n if (r > g && g > b) {\n const skinFactor = (g - b) / (r - b + 1e-5);\n skinProtection = Math.max(0.2, 1.0 - skinFactor * 0.5);\n }\n\n // Weight inversely proportional to current saturation\n const boost = amount * (1.0 - sat) * skinProtection;\n const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;\n\n const newR = Math.max(0, Math.min(1, luma + (1 + boost) * (r - luma)));\n const newG = Math.max(0, Math.min(1, luma + (1 + boost) * (g - luma)));\n const newB = Math.max(0, Math.min(1, luma + (1 + boost) * (b - luma)));\n\n return [newR, newG, newB];\n}\n\n/**\n * Calculates RGB multiplier from Kelvin blackbody color temperature and green/magenta tint.\n * @param kelvin Temperature in Kelvin [2000..12000], D65 neutral is 6500K.\n * @param tint Green/magenta shift in [-100..100] (0 = neutral).\n */\nexport function getWhiteBalanceGains(kelvin: number, tint = 0): ColorRGB {\n const k = Math.max(2000, Math.min(12000, kelvin)) / 100.0;\n\n let r: number;\n let g: number;\n let b: number;\n\n if (k <= 66) {\n r = 255;\n g = 99.4708025861 * Math.log(k) - 161.1195681661;\n b = k <= 19 ? 0 : 138.5177312231 * Math.log(k - 10) - 305.0447927307;\n } else {\n r = 329.698727446 * Math.pow(k - 60, -0.1332047592);\n g = 288.1221695283 * Math.pow(k - 60, -0.0755148492);\n b = 255;\n }\n\n // Normalize D65 (6500K) to [1, 1, 1]\n const d65R = 255;\n const d65G = 250.7;\n const d65B = 255;\n\n let gainR = r / d65R;\n let gainG = g / d65G;\n let gainB = b / d65B;\n\n // Tint adjustment (green vs magenta)\n const tintFactor = tint / 100.0;\n gainG *= 1.0 - tintFactor * 0.15;\n gainR *= 1.0 + tintFactor * 0.05;\n gainB *= 1.0 + tintFactor * 0.05;\n\n return [gainR, gainG, gainB];\n}\n\n// -----------------------------------------------------------------------------\n// GLSL Shaders\n// -----------------------------------------------------------------------------\n\n/**\n * Self-contained GLSL functions for fast Oklab <-> Linear sRGB transforms.\n */\nexport function getOklabGLSL(): string {\n return `\n// Oklab <-> Linear sRGB (Björn Ottosson)\nvec3 flair_linear_to_oklab(vec3 c) {\n float l = pow(0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b, 1.0 / 3.0);\n float m = pow(0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b, 1.0 / 3.0);\n float s = pow(0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b, 1.0 / 3.0);\n return vec3(\n 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,\n 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,\n 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s\n );\n}\n\nvec3 flair_oklab_to_linear(vec3 lab) {\n float l_ = lab.x + 0.3963377774 * lab.y + 0.2158037573 * lab.z;\n float m_ = lab.x - 0.1055613458 * lab.y - 0.0638541728 * lab.z;\n float s_ = lab.x - 0.0894841775 * lab.y - 1.2914855480 * lab.z;\n float l = l_ * l_ * l_;\n float m = m_ * m_ * m_;\n float s = s_ * s_ * s_;\n return vec3(\n +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s\n );\n}\n`.trim();\n}\n\n/**\n * Self-contained GLSL functions for ASC CDL and Lift/Gamma/Gain grading.\n */\nexport function getColorGradingGLSL(): string {\n return `\n// ASC CDL Grading\nvec3 flair_cdl(vec3 color, vec3 slope, vec3 offset, vec3 power, float sat) {\n vec3 c = pow(max(color * slope + offset, vec3(0.0)), power);\n float luma = dot(c, vec3(0.2126, 0.7152, 0.0722));\n return mix(vec3(luma), c, sat);\n}\n\n// 3-Way Lift / Gamma / Gain Grading\nvec3 flair_lift_gamma_gain(vec3 color, vec3 lift, vec3 gamma, vec3 gain) {\n vec3 lifted = color + lift * (vec3(1.0) - color);\n vec3 gained = max(lifted * gain, vec3(0.0));\n return pow(gained, 1.0 / max(gamma, vec3(0.0001)));\n}\n`.trim();\n}\n"],"mappings":"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","names":[]}
@@ -128,4 +128,4 @@ export {
128
128
  stepVariant,
129
129
  formatUnit
130
130
  };
131
- //# sourceMappingURL=chunk-VP6IEWIK.js.map
131
+ //# sourceMappingURL=chunk-H6L67KHI.js.map