@pixerate/flair 0.24.0 → 0.26.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.
- package/CHANGELOG.md +27 -0
- package/README.md +1 -0
- package/dist/catalog/index.js +2 -2
- package/dist/{chunk-WAMA45G3.js → chunk-5USSDEQL.js} +77 -21
- package/dist/chunk-5USSDEQL.js.map +1 -0
- package/dist/{chunk-GTIAOYOK.js → chunk-74ZLCZOG.js} +13 -10
- package/dist/chunk-74ZLCZOG.js.map +1 -0
- package/dist/{chunk-NGKNI3QX.js → chunk-CK6EV37I.js} +19 -495
- package/dist/chunk-CK6EV37I.js.map +1 -0
- package/dist/chunk-DBTLCXKY.js +332 -0
- package/dist/chunk-DBTLCXKY.js.map +1 -0
- package/dist/chunk-G2B5L234.js +414 -0
- package/dist/chunk-G2B5L234.js.map +1 -0
- package/dist/{chunk-VP6IEWIK.js → chunk-H6L67KHI.js} +1 -1
- package/dist/chunk-H6VDFBIO.js +498 -0
- package/dist/chunk-H6VDFBIO.js.map +1 -0
- package/dist/{chunk-OQ4EWZ3B.js → chunk-HVXWNGTW.js} +83 -6
- package/dist/{chunk-OQ4EWZ3B.js.map → chunk-HVXWNGTW.js.map} +1 -1
- package/dist/chunk-JL6TI6II.js +724 -0
- package/dist/chunk-JL6TI6II.js.map +1 -0
- package/dist/{chunk-OHW7UG6D.js → chunk-ORAFCHX3.js} +26 -2
- package/dist/{chunk-OHW7UG6D.js.map → chunk-ORAFCHX3.js.map} +1 -1
- package/dist/{chunk-ARKC7LDW.js → chunk-RDU63CDO.js} +2 -331
- package/dist/chunk-RDU63CDO.js.map +1 -0
- package/dist/{chunk-LVPIIWPP.js → chunk-STKEKXCK.js} +18 -8
- package/dist/chunk-STKEKXCK.js.map +1 -0
- package/dist/chunk-UCZ43ARE.js +902 -0
- package/dist/chunk-UCZ43ARE.js.map +1 -0
- package/dist/chunk-YBGRXVAE.js +407 -0
- package/dist/chunk-YBGRXVAE.js.map +1 -0
- package/dist/cli.js +74 -11
- package/dist/cli.js.map +1 -1
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +14 -12
- package/dist/editor/index.js.map +1 -1
- package/dist/effect-GGH3OSOT.js +47 -0
- package/dist/embed/index.d.ts +215 -0
- package/dist/embed/index.js +21 -0
- package/dist/{export-DH2HMACO.js → export-QVW474KM.js} +3 -3
- package/dist/export-QVW474KM.js.map +1 -0
- package/dist/frames-VEcqa40g.d.ts +161 -0
- package/dist/index.d.ts +47 -164
- package/dist/index.js +102 -80
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +167 -7
- package/dist/node.js +25 -3
- package/dist/react/index.d.ts +33 -2
- package/dist/react/index.js +54 -9
- package/dist/react/index.js.map +1 -1
- package/dist/recorder-CDPPTJMG.js +31 -0
- package/dist/recorder-CDPPTJMG.js.map +1 -0
- package/dist/{renderer-C2P1qGzP.d.ts → renderer-teaa98RE.d.ts} +1 -1
- package/dist/svelte/index.d.ts +33 -4
- package/dist/svelte/index.js +51 -9
- package/dist/svelte/index.js.map +1 -1
- package/dist/vector/index.d.ts +52 -1
- package/dist/vector/index.js +8 -4
- package/package.json +1 -1
- package/dist/chunk-ARKC7LDW.js.map +0 -1
- package/dist/chunk-GTIAOYOK.js.map +0 -1
- package/dist/chunk-LVPIIWPP.js.map +0 -1
- package/dist/chunk-NGKNI3QX.js.map +0 -1
- package/dist/chunk-UP6QQVRS.js +0 -331
- package/dist/chunk-UP6QQVRS.js.map +0 -1
- package/dist/chunk-WAMA45G3.js.map +0 -1
- package/dist/recorder-HKFFHQES.js +0 -10
- /package/dist/{chunk-VP6IEWIK.js.map → chunk-H6L67KHI.js.map} +0 -0
- /package/dist/{export-DH2HMACO.js.map → effect-GGH3OSOT.js.map} +0 -0
- /package/dist/{recorder-HKFFHQES.js.map → embed/index.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shader/thumbnails.ts","../src/shader/gif.ts","../src/shader/standalone.ts","../src/shader/blend.ts","../src/scene/validate.ts","../src/scene/standalone.ts"],"sourcesContent":["import { imageUrls, parseControls, type ControlDef, type ControlValue } from \"./controls.js\";\nimport {\n compileShader,\n createContext,\n drawFrame,\n resetGlCaches,\n type CompiledShader,\n} from \"./program.js\";\nimport { TextureCache } from \"./textures.js\";\n\n/** Controls a registered thumbnail. */\nexport interface ThumbnailHandle {\n /** Shows another source or other values. A new source is recompiled; new values only redraw. */\n update(source: string, values?: Record<string, ControlValue>): void;\n /** With reduced motion, a hovered thumbnail animates and the others hold a still frame. */\n setHovered(hovered: boolean): void;\n /** Stops drawing into the canvas and frees its program. */\n dispose(): void;\n}\n\nexport interface ThumbnailPoolOptions {\n /** Size of the shared render canvas (default 480 × 270). Thumbnails are drawn at most this large, then scaled. */\n renderWidth?: number;\n renderHeight?: number;\n /** Animation rate (default 30). */\n fps?: number;\n /** `iTime` of the still frame shown with reduced motion or hover-only mode (default 2). */\n stillTime?: number;\n /** Hold still frames unless hovered (default: the `prefers-reduced-motion` media query). */\n reducedMotion?: boolean;\n /** Only animate hovered thumbnails, holding `stillTime` on all others (default false). */\n hoverOnly?: boolean;\n /** Animation policy: `\"always\"` (animate all visible), `\"hover\"` (only animate hovered), or `\"reduced-motion\"` (default: follow `reducedMotion` / `prefers-reduced-motion`). */\n animateMode?: \"always\" | \"hover\" | \"reduced-motion\";\n /** Enable a transparent WebGL drawing buffer and clear each thumbnail canvas before copying (default false). */\n alpha?: boolean;\n}\n\ninterface Entry {\n canvas: HTMLCanvasElement;\n ctx: CanvasRenderingContext2D;\n source: string;\n values?: Record<string, ControlValue>;\n controls: ControlDef[];\n shader: CompiledShader | null;\n failed: boolean;\n dirty: boolean;\n visible: boolean;\n hovered: boolean;\n onStatus?: (ok: boolean) => void;\n}\n\n/**\n * Animated thumbnails from a single WebGL context. Browsers cap live WebGL\n * contexts (about 16), so a gallery cannot give every card its own: the pool\n * renders each visible thumbnail in turn on one hidden canvas and copies the\n * frame into the thumbnail's own 2D canvas, cropped to its aspect ratio like\n * `object-fit: cover`. Thumbnails off screen (by `IntersectionObserver`) cost\n * nothing, and programs compile once per source.\n *\n * Nothing is created until the first `register`.\n */\nexport class ThumbnailPool {\n private readonly renderWidth: number;\n private readonly renderHeight: number;\n private readonly interval: number;\n private readonly fps: number;\n private readonly stillTime: number;\n private readonly reducedMotion: boolean;\n private readonly alpha: boolean;\n private readonly entries = new Set<Entry>();\n private readonly byCanvas = new WeakMap<Element, Entry>();\n private observer: IntersectionObserver | null | undefined;\n private glCanvas: HTMLCanvasElement | null = null;\n private gl: WebGL2RenderingContext | null = null;\n private textures: TextureCache | null = null;\n private raf = 0;\n private last = 0;\n private start: number | null = null;\n\n constructor(options: ThumbnailPoolOptions = {}) {\n this.renderWidth = options.renderWidth ?? 480;\n this.renderHeight = options.renderHeight ?? 270;\n this.fps = options.fps ?? 30;\n this.interval = 1000 / this.fps;\n this.stillTime = options.stillTime ?? 2;\n this.alpha = Boolean(options.alpha);\n const mode = options.animateMode ?? (options.hoverOnly ? \"hover\" : \"reduced-motion\");\n this.reducedMotion =\n mode === \"hover\"\n ? true\n : mode === \"always\"\n ? false\n : (options.reducedMotion ??\n (typeof matchMedia !== \"undefined\" &&\n matchMedia(\"(prefers-reduced-motion: reduce)\").matches));\n }\n\n /**\n * Starts drawing `source` into `canvas`, which should be sized with CSS; its\n * drawing buffer follows its CSS size and the device pixel ratio (up to 2).\n * `onStatus` hears whether the source compiled (and `false` without WebGL2).\n */\n register(\n canvas: HTMLCanvasElement,\n source: string,\n values?: Record<string, ControlValue>,\n onStatus?: (ok: boolean) => void,\n ): ThumbnailHandle {\n const observer = this.intersection();\n const entry: Entry = {\n canvas,\n ctx: canvas.getContext(\"2d\")!,\n source,\n values,\n controls: [],\n shader: null,\n failed: false,\n dirty: true,\n visible: observer === null,\n hovered: false,\n onStatus,\n };\n this.entries.add(entry);\n this.byCanvas.set(canvas, entry);\n observer?.observe(canvas);\n if (!this.context()) onStatus?.(false);\n this.schedule();\n return {\n update: (nextSource, nextValues) => {\n if (nextSource !== entry.source) {\n this.release(entry);\n entry.source = nextSource;\n entry.failed = false;\n }\n entry.values = nextValues;\n entry.dirty = true;\n this.retainTextures();\n this.schedule();\n },\n setHovered: (hovered) => {\n entry.hovered = hovered;\n entry.dirty = true;\n this.schedule();\n },\n dispose: () => {\n observer?.unobserve(canvas);\n this.entries.delete(entry);\n this.byCanvas.delete(canvas);\n this.release(entry);\n this.retainTextures();\n },\n };\n }\n\n /** The number of registered thumbnails. */\n get size(): number {\n return this.entries.size;\n }\n\n private intersection(): IntersectionObserver | null {\n if (this.observer !== undefined) return this.observer;\n this.observer =\n typeof IntersectionObserver === \"undefined\"\n ? null\n : new IntersectionObserver(\n (records) => {\n for (const r of records) {\n const entry = this.byCanvas.get(r.target);\n if (!entry) continue;\n entry.visible = r.isIntersecting;\n if (entry.visible) entry.dirty = true;\n }\n this.schedule();\n },\n { rootMargin: \"100px\" },\n );\n return this.observer;\n }\n\n private context(): WebGL2RenderingContext | null {\n if (this.gl && !this.gl.isContextLost()) return this.gl;\n const canvas = document.createElement(\"canvas\");\n canvas.width = this.renderWidth;\n canvas.height = this.renderHeight;\n canvas.addEventListener(\"webglcontextlost\", (e) => {\n e.preventDefault();\n for (const entry of this.entries) entry.shader = null;\n this.textures?.dispose();\n });\n canvas.addEventListener(\"webglcontextrestored\", () => {\n resetGlCaches();\n if (this.gl) this.textures = this.textureCache(this.gl);\n for (const entry of this.entries) entry.dirty = true;\n this.schedule();\n });\n resetGlCaches();\n this.glCanvas = canvas;\n this.gl = createContext(canvas, false, this.alpha);\n this.textures = this.gl && this.textureCache(this.gl);\n return this.gl;\n }\n\n private textureCache(gl: WebGL2RenderingContext): TextureCache {\n return new TextureCache(gl, (url) => {\n // A thumbnail showing the image redraws with it.\n for (const entry of this.entries) {\n if (imageUrls(entry.controls, entry.values).includes(url)) entry.dirty = true;\n }\n this.schedule();\n });\n }\n\n /** Frees the textures no thumbnail shows any more. */\n private retainTextures() {\n this.textures?.retain(\n [...this.entries].flatMap((entry) => imageUrls(entry.controls, entry.values)),\n );\n }\n\n private release(entry: Entry) {\n if (entry.shader) this.gl?.deleteProgram(entry.shader.program);\n entry.shader = null;\n }\n\n private compile(entry: Entry, gl: WebGL2RenderingContext) {\n entry.controls = parseControls(entry.source);\n const result = compileShader(gl, entry.source, entry.controls);\n entry.shader = result.ok ? result.shader : null;\n entry.failed = !result.ok;\n entry.onStatus?.(result.ok);\n }\n\n private renderEntry(entry: Entry, gl: WebGL2RenderingContext, time: number) {\n if (!entry.shader && !entry.failed) this.compile(entry, gl);\n if (!entry.shader) return;\n sizeCanvas(entry.canvas);\n // Fill the thumbnail the way `object-fit: cover` would, at its aspect ratio.\n const aspect = entry.canvas.width / entry.canvas.height;\n const w = Math.min(this.renderWidth, Math.round(this.renderHeight * aspect));\n const h = Math.min(this.renderHeight, Math.round(w / aspect));\n drawFrame(\n gl,\n entry.shader,\n entry.controls,\n entry.values,\n {\n width: w,\n height: h,\n time,\n delta: this.interval / 1000,\n frame: Math.floor(time * this.fps),\n mouse: [0, 0, 0, 0],\n // A hovered thumbnail is pointed at in its middle, for effects that answer hovers.\n pointer: [w / 2, h / 2, entry.hovered ? 1 : 0, 0],\n },\n this.textures ?? undefined,\n );\n // The GL canvas's drawing buffer is valid until this task ends; copy it now.\n // WebGL draws from the bottom left, so the frame sits at the bottom of the canvas.\n if (this.alpha) {\n entry.ctx.clearRect(0, 0, entry.canvas.width, entry.canvas.height);\n }\n entry.ctx.drawImage(\n this.glCanvas!,\n 0,\n this.renderHeight - h,\n w,\n h,\n 0,\n 0,\n entry.canvas.width,\n entry.canvas.height,\n );\n entry.dirty = false;\n }\n\n private schedule() {\n if (!this.raf && this.entries.size) this.raf = requestAnimationFrame(this.tick);\n }\n\n private tick = (now: number) => {\n this.raf = 0;\n const gl = this.context();\n if (!gl) return;\n this.start ??= now;\n const animate = now - this.last >= this.interval;\n const time = (now - this.start) / 1000;\n let keepGoing = false;\n // Compiling is the slow part, so a row of thumbnails scrolling into view compiles one per frame.\n let compiles = 1;\n for (const entry of this.entries) {\n if (!entry.visible) continue;\n if (!entry.shader && !entry.failed) {\n if (!compiles) {\n keepGoing = true;\n continue;\n }\n compiles--;\n }\n const moving = !this.reducedMotion || entry.hovered;\n if (entry.dirty || (moving && animate)) {\n this.renderEntry(entry, gl, moving ? time : this.stillTime);\n }\n if (moving && entry.shader) keepGoing = true;\n }\n if (animate) this.last = now;\n if (keepGoing) this.schedule();\n };\n}\n\nfunction sizeCanvas(canvas: HTMLCanvasElement) {\n // The layout size, not the bounding box: a rotated or scaled canvas keeps its own proportions.\n const ratio = Math.min(window.devicePixelRatio || 1, 2);\n const w = Math.max(1, Math.round(canvas.clientWidth * ratio));\n const h = Math.max(1, Math.round(canvas.clientHeight * ratio));\n if (canvas.width !== w || canvas.height !== h) {\n canvas.width = w;\n canvas.height = h;\n }\n}\n\nlet shared: ThumbnailPool | null = null;\n\n/** The pool `registerThumbnail` uses, created on first use. */\nexport function sharedThumbnailPool(): ThumbnailPool {\n shared ??= new ThumbnailPool();\n return shared;\n}\n\n/** Registers a thumbnail with the shared pool. See {@link ThumbnailPool.register}. */\nexport function registerThumbnail(\n canvas: HTMLCanvasElement,\n source: string,\n values?: Record<string, ControlValue>,\n onStatus?: (ok: boolean) => void,\n): ThumbnailHandle {\n return sharedThumbnailPool().register(canvas, source, values, onStatus);\n}\n","// gifenc ships without types. The reference (not an import) brings them along when a host\n// type-checks these sources through the `workspace` condition.\n// eslint-disable-next-line @typescript-eslint/triple-slash-reference\n/// <reference path=\"./gifenc.d.ts\" />\nimport type { ControlValue } from \"./controls.js\";\nimport { ShaderFrames } from \"./frames.js\";\n\nexport interface GifOptions {\n source: string;\n values?: Record<string, ControlValue>;\n width: number;\n height: number;\n /** Frames per second. GIF delays are whole hundredths of a second, so 10, 20, 25 and 50 play back exactly. */\n fps: number;\n /** Seconds of animation to record. */\n duration: number;\n /** `iTime` of the first frame (default 0). */\n start?: number;\n /** Called after each frame with the frames done and the total. */\n onProgress?: (done: number, total: number) => void;\n /** Aborting rejects with an `AbortError` `DOMException`. */\n signal?: AbortSignal;\n}\n\n/** Lets the page repaint between frames so progress shows and Cancel works. */\nconst yieldToBrowser = () => new Promise<void>((resolve) => setTimeout(resolve, 0));\n\n/**\n * Records a shader as an animated GIF that loops forever. Frames are rendered\n * off screen at exact time steps (`start + i / fps`), so the result is smooth\n * whatever the page's frame rate, and nothing on screen is disturbed. Each\n * frame gets its own 256-colour palette. The encoder,\n * [gifenc](https://github.com/mattdesl/gifenc), is loaded on first use.\n *\n * Image controls are loaded before the first frame. Rejects with a\n * `ShaderCompileError` when the source does not compile, and an\n * `ImageLoadError` when an image cannot be loaded.\n */\nexport async function recordGif(options: GifOptions): Promise<Blob> {\n const { source, values, width, height, fps, duration, start = 0, onProgress, signal } = options;\n const { GIFEncoder, quantize, applyPalette } = await import(\"gifenc\");\n\n const frames = new ShaderFrames({ source, values, width, height });\n try {\n await frames.ready();\n const total = Math.max(1, Math.round(duration * fps));\n const delay = 1000 / fps;\n const rgba = new Uint8Array(frames.width * frames.height * 4);\n const gif = GIFEncoder();\n\n for (let i = 0; i < total; i++) {\n if (signal?.aborted) throw new DOMException(\"Cancelled\", \"AbortError\");\n frames.render(start + i / fps, { frame: i, delta: 1 / fps });\n frames.readPixels(rgba);\n const palette = quantize(rgba, 256);\n gif.writeFrame(applyPalette(rgba, palette), frames.width, frames.height, {\n palette,\n delay,\n ...(i === 0 ? { repeat: 0 } : {}),\n });\n onProgress?.(i + 1, total);\n if (i % 2 === 1) await yieldToBrowser();\n }\n\n gif.finish();\n return new Blob([gif.bytes()], { type: \"image/gif\" });\n } finally {\n frames.dispose();\n }\n}\n","import { controlValue, parseControls, type ControlValue } from \"./controls.js\";\nimport { HOVER_SECONDS, MAX_CLICKS, MAX_TRAIL, TRAIL_SECONDS } from \"./pointer.js\";\nimport { VERTEX_SOURCE, wrapSource } from \"./program.js\";\nimport { SVG_EDGE } from \"./textures.js\";\n\nconst escapeHtml = (s: string) =>\n s.replace(/[&<>\"]/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\" })[c]!);\n\n/** JSON that is safe inside a `<script>`: a `</script>` or `<!--` in a shader comment cannot end it. */\nconst scriptJson = (value: unknown) => JSON.stringify(value).replace(/</g, \"\\\\u003c\");\n\nexport interface StandaloneOptions {\n /** The comment at the top of the script, saying where the page came from. */\n credit?: string;\n}\n\n/**\n * A self-contained HTML page that runs the shader full-window, with the\n * control values baked in. It has no dependencies and opens from disk. Like\n * the live preview, it feeds `iMouse` from pointer drags and caps the pixel\n * ratio at 2.\n *\n * Images are loaded by URL when the page opens: a `data:` URL travels with\n * the page, an `https:` one needs the network and CORS, and a `blob:` one\n * only lives as long as the page that made it. SVGs are sized to\n * {@link SVG_EDGE} pixels before they are decoded, as `loadImage` does.\n */\nexport function standaloneHtml(\n title: string,\n source: string,\n values?: Record<string, ControlValue>,\n options: StandaloneOptions = {},\n): string {\n const controls = parseControls(source).map((c) => ({\n name: c.name,\n type: c.type,\n value: controlValue(c, values),\n ...(c.texture ? { texture: c.texture } : {}),\n }));\n const credit = (options.credit ?? \"Exported with Flair.\").replace(/\\n/g, \" \");\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeHtml(title)}</title>\n<style>html,body{margin:0;height:100%;background:#000}canvas{display:block;width:100%;height:100%}</style>\n</head>\n<body>\n<canvas id=\"c\"></canvas>\n<script>\n// ${credit.replace(/</g, \"\\\\u003c\")} Shadertoy-style fragment shader on WebGL2.\nconst VERTEX = ${scriptJson(VERTEX_SOURCE)};\nconst FRAGMENT = ${scriptJson(wrapSource(source))};\nconst CONTROLS = ${scriptJson(controls)};\nconst canvas = document.getElementById(\"c\");\nconst gl = canvas.getContext(\"webgl2\");\nif (!gl) document.body.textContent = \"WebGL2 is not available.\";\nfunction compile(type, src) {\n const s = gl.createShader(type);\n gl.shaderSource(s, src);\n gl.compileShader(s);\n if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));\n return s;\n}\nconst program = gl.createProgram();\ngl.attachShader(program, compile(gl.VERTEX_SHADER, VERTEX));\ngl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAGMENT));\ngl.linkProgram(program);\ngl.useProgram(program);\nconst u = (n) => gl.getUniformLocation(program, n);\nlet unit = 0;\n// An SVG decodes at its own, often icon, size: give it ${SVG_EDGE} px on its long edge (and a\n// viewBox, so the drawing scales) to keep it sharp.\nasync function sizedSvg(url) {\n const doc = new DOMParser().parseFromString(await (await fetch(url)).text(), \"image/svg+xml\");\n const svg = doc.documentElement;\n const len = (n) => { const m = (svg.getAttribute(n) || \"\").trim().match(/^([\\\\d.]+)(px)?$/); return m && +m[1] > 0 ? +m[1] : 0; };\n const box = (svg.getAttribute(\"viewBox\") || \"\").trim().split(/[\\\\s,]+/).map(Number);\n const hasBox = box.length === 4 && box[2] > 0 && box[3] > 0;\n const w = len(\"width\") || (hasBox ? box[2] : 300), h = len(\"height\") || (hasBox ? box[3] : 150);\n const scale = ${SVG_EDGE} / Math.max(w, h);\n if (!hasBox) svg.setAttribute(\"viewBox\", \"0 0 \" + w + \" \" + h);\n svg.setAttribute(\"width\", Math.max(1, Math.round(w * scale)));\n svg.setAttribute(\"height\", Math.max(1, Math.round(h * scale)));\n return URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(svg)], { type: \"image/svg+xml\" }));\n}\nfunction image(loc, url, texture) {\n const i = unit++, t = gl.createTexture();\n const bind = () => { gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, t); };\n const filter = { mipmap: gl.LINEAR_MIPMAP_LINEAR, linear: gl.LINEAR, nearest: gl.NEAREST }[texture.filter];\n const wrap = { repeat: gl.REPEAT, clamp: gl.CLAMP_TO_EDGE, mirror: gl.MIRRORED_REPEAT }[texture.wrap];\n bind();\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255]));\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.filter === \"nearest\" ? gl.NEAREST : gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);\n gl.uniform1i(loc, i);\n if (!url) return;\n const img = new Image();\n if (!/^(data|blob):/i.test(url)) img.crossOrigin = \"anonymous\";\n img.onload = () => {\n bind();\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);\n gl.generateMipmap(gl.TEXTURE_2D);\n };\n if (/^data:image\\\\/svg\\\\+xml[;,]/i.test(url) || /^(?!data:)[^?#]*\\\\.svg([?#]|$)/i.test(url)) {\n sizedSvg(url).then((src) => { img.src = src; }, () => {});\n } else img.src = url;\n}\nfor (const c of CONTROLS) {\n const loc = u(c.name);\n const v = c.value;\n if (c.type === \"float\") gl.uniform1f(loc, v);\n else if (c.type === \"int\" || c.type === \"bool\") gl.uniform1i(loc, Number(v));\n else if (c.type === \"sampler2D\") image(loc, v, c.texture);\n else gl[\"uniform\" + v.length + \"fv\"](loc, v);\n}\nlet mouse = [0, 0, 0, 0];\nconst px = (e) => { const r = canvas.getBoundingClientRect(); return [(e.clientX - r.left) * canvas.width / r.width, (r.bottom - e.clientY) * canvas.height / r.height]; };\ncanvas.addEventListener(\"pointerdown\", (e) => { const [x, y] = px(e); mouse = [x, y, x, y]; });\ncanvas.addEventListener(\"pointermove\", (e) => { if (e.buttons && mouse[2] > 0) { const [x, y] = px(e); mouse = [x, y, mouse[2], mouse[3]]; } });\naddEventListener(\"pointerup\", () => { if (mouse[2] > 0) mouse = [mouse[0], mouse[1], -mouse[2], -Math.abs(mouse[3])]; });\nlet start = performance.now(), last = start, frame = 0;\n// iPointer (where, how hovered, seconds pressed), iClicks (the latest clicks) and iTrail (where it has just been), newest first.\nconst seconds = () => (performance.now() - start) / 1000;\nlet at = [0, 0], over = false, hoverFrom = 0, hoverAt = -1e9, pressedAt = -1, releasedAt = -1, clicks = [], trail = [];\nconst hovered = (t) => { const k = Math.min(1, Math.max(0, (t - hoverAt) / ${HOVER_SECONDS})); return hoverFrom + ((over ? 1 : 0) - hoverFrom) * k * k * (3 - 2 * k); };\nconst hover = (on) => { const t = seconds(); hoverFrom = hovered(t); hoverAt = t; over = on; };\ncanvas.addEventListener(\"pointerenter\", (e) => { at = px(e); hover(true); toTrail(); });\ncanvas.addEventListener(\"pointerleave\", () => hover(false));\n// A trail point at most every ${TRAIL_SECONDS.toFixed(4)} s; moves in between update the newest.\nconst toTrail = () => { const t = seconds(); if (trail.length && t - trail[0][2] < ${TRAIL_SECONDS}) trail[0] = [at[0], at[1], trail[0][2], 1]; else trail = [[at[0], at[1], t, 1], ...trail].slice(0, ${MAX_TRAIL}); };\ncanvas.addEventListener(\"pointermove\", (e) => { at = px(e); toTrail(); });\ncanvas.addEventListener(\"pointerdown\", (e) => { at = px(e); pressedAt = seconds(); clicks.unshift([at[0], at[1], pressedAt, 1]); clicks = clicks.slice(0, ${MAX_CLICKS}); });\naddEventListener(\"pointerup\", () => { if (pressedAt >= 0) releasedAt = seconds(); pressedAt = -1; });\nfunction draw(now) {\n const dpr = Math.min(devicePixelRatio || 1, 2);\n const w = Math.round(canvas.clientWidth * dpr), h = Math.round(canvas.clientHeight * dpr);\n if (canvas.width !== w || canvas.height !== h) { canvas.width = w; canvas.height = h; }\n gl.viewport(0, 0, w, h);\n gl.uniform3f(u(\"iResolution\"), w, h, 1);\n gl.uniform1f(u(\"iTime\"), (now - start) / 1000);\n gl.uniform1f(u(\"iTimeDelta\"), (now - last) / 1000);\n gl.uniform1i(u(\"iFrame\"), frame++);\n gl.uniform4f(u(\"iMouse\"), mouse[0], mouse[1], mouse[2], mouse[3]);\n const t = (now - start) / 1000;\n gl.uniform4f(u(\"iPointer\"), at[0], at[1], hovered(t), pressedAt >= 0 ? Math.max(0, t - pressedAt) : releasedAt >= 0 ? -Math.max(1e-3, t - releasedAt) : 0);\n const c = new Float32Array(${MAX_CLICKS * 4});\n clicks.forEach((k, i) => c.set(k, i * 4));\n gl.uniform4fv(u(\"iClicks\"), c);\n const p = new Float32Array(${MAX_TRAIL * 4});\n trail.forEach((k, i) => p.set(k, i * 4));\n gl.uniform4fv(u(\"iTrail\"), p);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n last = now;\n requestAnimationFrame(draw);\n}\nrequestAnimationFrame(draw);\n</script>\n</body>\n</html>\n`;\n}\n","/**\n * 38 Layer Blend Modes for Compositing and WebGL Shader Generation.\n * Mathematically matching Adobe After Effects & EffectCraft blending standards.\n */\n\nexport type BlendMode =\n | 'normal'\n | 'dissolve'\n | 'dancingDissolve'\n | 'darken'\n | 'multiply'\n | 'colorBurn'\n | 'classicColorBurn'\n | 'linearBurn'\n | 'darkerColor'\n | 'add'\n | 'lighten'\n | 'screen'\n | 'colorDodge'\n | 'classicColorDodge'\n | 'linearDodge'\n | 'lighterColor'\n | 'overlay'\n | 'softLight'\n | 'hardLight'\n | 'linearLight'\n | 'vividLight'\n | 'pinLight'\n | 'hardMix'\n | 'difference'\n | 'classicDifference'\n | 'exclusion'\n | 'subtract'\n | 'divide'\n | 'hue'\n | 'saturation'\n | 'color'\n | 'luminosity'\n | 'stencilAlpha'\n | 'stencilLuma'\n | 'silhouetteAlpha'\n | 'silhouetteLuma'\n | 'alphaAdd'\n | 'luminescentPremul';\n\nexport const ALL_BLEND_MODES: readonly BlendMode[] = [\n 'normal',\n 'dissolve',\n 'dancingDissolve',\n 'darken',\n 'multiply',\n 'colorBurn',\n 'classicColorBurn',\n 'linearBurn',\n 'darkerColor',\n 'add',\n 'lighten',\n 'screen',\n 'colorDodge',\n 'classicColorDodge',\n 'linearDodge',\n 'lighterColor',\n 'overlay',\n 'softLight',\n 'hardLight',\n 'linearLight',\n 'vividLight',\n 'pinLight',\n 'hardMix',\n 'difference',\n 'classicDifference',\n 'exclusion',\n 'subtract',\n 'divide',\n 'hue',\n 'saturation',\n 'color',\n 'luminosity',\n 'stencilAlpha',\n 'stencilLuma',\n 'silhouetteAlpha',\n 'silhouetteLuma',\n 'alphaAdd',\n 'luminescentPremul',\n];\n\nexport type ColorRgba = [number, number, number, number];\n\nfunction lum(r: number, g: number, b: number): number {\n return 0.3 * r + 0.59 * g + 0.11 * b;\n}\n\nfunction sat(r: number, g: number, b: number): number {\n return Math.max(r, g, b) - Math.min(r, g, b);\n}\n\nfunction clipColor(r: number, g: number, b: number): [number, number, number] {\n const l = lum(r, g, b);\n const n = Math.min(r, g, b);\n const x = Math.max(r, g, b);\n let cr = r;\n let cg = g;\n let cb = b;\n if (n < 0.0) {\n const denom = Math.max(1e-9, l - n);\n cr = l + ((cr - l) * l) / denom;\n cg = l + ((cg - l) * l) / denom;\n cb = l + ((cb - l) * l) / denom;\n }\n if (x > 1.0) {\n const denom = Math.max(1e-9, x - l);\n cr = l + ((cr - l) * (1.0 - l)) / denom;\n cg = l + ((cg - l) * (1.0 - l)) / denom;\n cb = l + ((cb - l) * (1.0 - l)) / denom;\n }\n return [cr, cg, cb];\n}\n\nfunction setLum(r: number, g: number, b: number, l: number): [number, number, number] {\n const d = l - lum(r, g, b);\n return clipColor(r + d, g + d, b + d);\n}\n\nfunction setSat(r: number, g: number, b: number, s: number): [number, number, number] {\n const mx = Math.max(r, g, b);\n const mn = Math.min(r, g, b);\n if (mx - mn <= 1e-9) return [0, 0, 0];\n const f = (v: number) => ((v - mn) * s) / (mx - mn);\n return [f(r), f(g), f(b)];\n}\n\nfunction burn(cb: number, cs: number): number {\n if (cb >= 1.0) return 1.0;\n if (cs <= 0.0) return 0.0;\n return 1.0 - Math.min(1.0, (1.0 - cb) / cs);\n}\n\nfunction dodge(cb: number, cs: number): number {\n if (cb <= 0.0) return 0.0;\n if (cs >= 1.0) return 1.0;\n return Math.min(1.0, cb / (1.0 - cs));\n}\n\nfunction hardLight(cb: number, cs: number): number {\n if (cs <= 0.5) {\n return cb * 2.0 * cs;\n }\n const s = 2.0 * cs - 1.0;\n return cb + s - cb * s;\n}\n\nfunction softLight(cb: number, cs: number): number {\n if (cs <= 0.5) {\n return cb - (1.0 - 2.0 * cs) * cb * (1.0 - cb);\n }\n const d = cb <= 0.25 ? ((16.0 * cb - 12.0) * cb + 4.0) * cb : Math.sqrt(Math.max(0.0, cb));\n return cb + (2.0 * cs - 1.0) * (d - cb);\n}\n\nfunction vividLight(cb: number, cs: number): number {\n return cs <= 0.5 ? burn(cb, 2.0 * cs) : dodge(cb, 2.0 * (cs - 0.5));\n}\n\nfunction separableBlend(mode: BlendMode, cb: number, cs: number): number {\n switch (mode) {\n case 'darken':\n return Math.min(cb, cs);\n case 'multiply':\n return cb * cs;\n case 'colorBurn':\n return burn(cb, cs);\n case 'classicColorBurn':\n return cs <= 0.0 ? 0.0 : 1.0 - (1.0 - cb) / cs;\n case 'linearBurn':\n return Math.max(0.0, cb + cs - 1.0);\n case 'add':\n case 'linearDodge':\n return cb + cs;\n case 'lighten':\n return Math.max(cb, cs);\n case 'screen':\n return cb + cs - cb * cs;\n case 'colorDodge':\n return dodge(cb, cs);\n case 'classicColorDodge':\n return cs >= 1.0 ? 1.0 : Math.min(1.0, cb / (1.0 - cs));\n case 'overlay':\n return hardLight(cs, cb);\n case 'softLight':\n return softLight(cb, cs);\n case 'hardLight':\n return hardLight(cb, cs);\n case 'linearLight':\n return Math.min(1.0, Math.max(0.0, cb + 2.0 * cs - 1.0));\n case 'vividLight':\n return vividLight(cb, cs);\n case 'pinLight':\n return cs <= 0.5 ? Math.min(cb, 2.0 * cs) : Math.max(cb, 2.0 * cs - 1.0);\n case 'hardMix':\n return cb + cs >= 1.0 ? 1.0 : 0.0;\n case 'difference':\n case 'classicDifference':\n return Math.abs(cb - cs);\n case 'exclusion':\n return cb + cs - 2.0 * cb * cs;\n case 'subtract':\n return Math.max(0.0, cb - cs);\n case 'divide':\n return cs <= 0.0 ? (cb > 0.0 ? 1.0 : 0.0) : cb / cs;\n default:\n return cs;\n }\n}\n\n/**\n * Evaluates CPU compositing of premultiplied RGBA source onto premultiplied RGBA destination.\n *\n * @param mode Blend mode to apply.\n * @param dst Destination premultiplied RGBA [r, g, b, a] in [0, 1].\n * @param src Source premultiplied RGBA [r, g, b, a] in [0, 1].\n * @param noise Per-pixel noise parameter in [0, 1] for Dissolve modes (default: 0).\n */\nexport function blendRgba(\n mode: BlendMode,\n dst: ColorRgba,\n src: ColorRgba,\n noise = 0,\n): ColorRgba {\n const sa = src[3];\n const da = dst[3];\n\n switch (mode) {\n case 'normal': {\n const k = 1.0 - sa;\n return [src[0] + dst[0] * k, src[1] + dst[1] * k, src[2] + dst[2] * k, sa + da * k];\n }\n case 'dissolve':\n case 'dancingDissolve': {\n if (sa <= 0.0 || noise >= sa) return [...dst];\n const inv = 1.0 / sa;\n return [src[0] * inv, src[1] * inv, src[2] * inv, 1.0];\n }\n case 'stencilAlpha':\n return [dst[0] * sa, dst[1] * sa, dst[2] * sa, dst[3] * sa];\n case 'silhouetteAlpha': {\n const k = 1.0 - sa;\n return [dst[0] * k, dst[1] * k, dst[2] * k, dst[3] * k];\n }\n case 'stencilLuma':\n case 'silhouetteLuma': {\n const l = lum(src[0], src[1], src[2]);\n const k = mode === 'stencilLuma' ? l : 1.0 - l;\n return [dst[0] * k, dst[1] * k, dst[2] * k, dst[3] * k];\n }\n case 'alphaAdd': {\n const k = 1.0 - sa;\n return [src[0] + dst[0] * k, src[1] + dst[1] * k, src[2] + dst[2] * k, Math.min(1.0, sa + da)];\n }\n case 'luminescentPremul': {\n const k = 1.0 - sa;\n return [src[0] + dst[0] * k, src[1] + dst[1] * k, src[2] + dst[2] * k, sa + da - sa * da];\n }\n }\n\n if (sa <= 0.0) return [...dst];\n if (da <= 0.0) return [...src];\n\n const cs: [number, number, number] = [src[0] / sa, src[1] / sa, src[2] / sa];\n const cb: [number, number, number] = [dst[0] / da, dst[1] / da, dst[2] / da];\n\n let b: [number, number, number];\n switch (mode) {\n case 'hue':\n b = setLum(...setSat(cs[0], cs[1], cs[2], sat(...cb)), lum(...cb));\n break;\n case 'saturation':\n b = setLum(...setSat(cb[0], cb[1], cb[2], sat(...cs)), lum(...cb));\n break;\n case 'color':\n b = setLum(cs[0], cs[1], cs[2], lum(...cb));\n break;\n case 'luminosity':\n b = setLum(cb[0], cb[1], cb[2], lum(...cs));\n break;\n case 'darkerColor':\n b = lum(...cs) < lum(...cb) ? cs : cb;\n break;\n case 'lighterColor':\n b = lum(...cs) > lum(...cb) ? cs : cb;\n break;\n default:\n b = [\n separableBlend(mode, cb[0], cs[0]),\n separableBlend(mode, cb[1], cs[1]),\n separableBlend(mode, cb[2], cs[2]),\n ];\n break;\n }\n\n const ao = sa + da - sa * da;\n return [\n (1.0 - da) * src[0] + (1.0 - sa) * dst[0] + sa * da * b[0],\n (1.0 - da) * src[1] + (1.0 - sa) * dst[1] + sa * da * b[1],\n (1.0 - da) * src[2] + (1.0 - sa) * dst[2] + sa * da * b[2],\n ao,\n ];\n}\n\n/**\n * Returns a self-contained GLSL shader function for blending two vec4 colors.\n * Useful for runtime GPU compositing across multiple shader layers or passes.\n */\nexport function getGlslBlendFunction(mode: BlendMode): string {\n switch (mode) {\n case 'multiply':\n return `vec4 blend(vec4 dst, vec4 src) {\n return vec4(src.rgb * dst.rgb * src.a + dst.rgb * (1.0 - src.a), src.a + dst.a * (1.0 - src.a));\n}`;\n case 'screen':\n return `vec4 blend(vec4 dst, vec4 src) {\n vec3 res = src.rgb + dst.rgb - src.rgb * dst.rgb;\n return vec4(mix(dst.rgb, res, src.a), src.a + dst.a * (1.0 - src.a));\n}`;\n case 'add':\n case 'linearDodge':\n return `vec4 blend(vec4 dst, vec4 src) {\n return vec4(min(vec3(1.0), dst.rgb + src.rgb * src.a), min(1.0, dst.a + src.a));\n}`;\n case 'overlay':\n return `vec4 blend(vec4 dst, vec4 src) {\n vec3 res = mix(2.0 * dst.rgb * src.rgb, 1.0 - 2.0 * (1.0 - dst.rgb) * (1.0 - src.rgb), step(vec3(0.5), dst.rgb));\n return vec4(mix(dst.rgb, res, src.a), src.a + dst.a * (1.0 - src.a));\n}`;\n case 'difference':\n return `vec4 blend(vec4 dst, vec4 src) {\n return vec4(mix(dst.rgb, abs(dst.rgb - src.rgb), src.a), src.a + dst.a * (1.0 - src.a));\n}`;\n case 'normal':\n default:\n return `vec4 blend(vec4 dst, vec4 src) {\n return src + dst * (1.0 - src.a);\n}`;\n }\n}\n","import { CAMERA_TRANSITIONS, MAX_TILT } from './camera.js';\nimport type { Scene, Shot } from './types.js';\nimport { EFFECT_TYPES } from './effects.js';\n\nexport interface SceneIssue {\n /** 1-based shot index (0 indicates whole scene level). */\n shot: number;\n message: string;\n}\n\nconst LONG_CAPTION = 120;\nconst LONG_TITLE = 60;\nconst MAX_EFFECTS_PER_SHOT = 5;\n\n/** Validates a Scene before running or rendering it. */\nexport function validateScene(scene: Scene): SceneIssue[] {\n const issues: SceneIssue[] = [];\n const say = (shot: number, message: string) => issues.push({ shot, message });\n\n if (!scene.name || typeof scene.name !== 'string') {\n say(0, 'Scene needs a string name.');\n } else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(scene.name)) {\n say(0, `Scene name \"${scene.name}\" should be lowercase words and dashes.`);\n }\n\n if (!Array.isArray(scene.shots) || scene.shots.length === 0) {\n say(0, 'Scene needs at least one shot.');\n return issues;\n }\n\n const mediaKeys = new Set(Object.keys(scene.media ?? {}));\n const effectIds = new Set<string>();\n\n // Check media\n if (scene.media) {\n for (const [id, m] of Object.entries(scene.media)) {\n if (!m || typeof m !== 'object') {\n say(0, `Media \"${id}\" must be an object.`);\n } else if (!m.type) {\n say(0, `Media \"${id}\" needs a type.`);\n } else if (m.type === 'shader' && !m.source) {\n say(0, `Shader media \"${id}\" needs a source string.`);\n }\n }\n }\n\n for (const [idx, shot] of scene.shots.entries()) {\n const shotNum = idx + 1;\n\n // Check camera\n if (shot.camera) {\n if ('frame' in shot.camera) {\n if (!Array.isArray(shot.camera.frame) || shot.camera.frame.length === 0) {\n say(shotNum, 'Dynamic camera framing requires a non-empty frame array of element IDs.');\n }\n } else if ('width' in shot.camera && (shot.camera.width <= 0 || shot.camera.height <= 0)) {\n say(shotNum, 'Camera rectangle width and height must be positive.');\n }\n const cam = shot.camera;\n const angle = (name: 'tilt' | 'turn' | 'roll', max: number) => {\n const v = cam[name];\n if (v !== undefined && !(typeof v === 'number' && Math.abs(v) <= max))\n say(shotNum, `Camera ${name} is degrees between -${max} and ${max}.`);\n };\n angle('tilt', MAX_TILT);\n angle('turn', MAX_TILT);\n angle('roll', 180);\n if (cam.perspective !== undefined && !(typeof cam.perspective === 'number' && cam.perspective >= 0.3))\n say(shotNum, 'Camera perspective is the lens distance as a multiple of the stage width, 0.3 or more (1.2 by default).');\n if (cam.motionBlur !== undefined && !(typeof cam.motionBlur === 'number' && cam.motionBlur >= 0 && cam.motionBlur <= 3))\n say(shotNum, 'Camera motionBlur is 0 (none) to 3 (a whip); 1 is natural.');\n if (cam.transition !== undefined && !CAMERA_TRANSITIONS.includes(cam.transition))\n say(shotNum, `Camera transition is one of ${CAMERA_TRANSITIONS.map((t) => `\"${t}\"`).join(', ')}; leave it out to move.`);\n else if (idx === 0 && (cam.transition === 'whip' || cam.transition === 'zoom'))\n say(shotNum, `The first shot has no framing to ${cam.transition} from, so its camera starts in place; move the transition to a later shot or leave it out.`);\n }\n\n // Check hold\n if (shot.hold !== undefined && shot.hold < 0) {\n say(shotNum, 'Shot hold cannot be negative.');\n }\n\n // Check text lengths\n if (shot.caption && shot.caption.length > LONG_CAPTION) {\n say(shotNum, `Caption is long (${shot.caption.length} chars); consider breaking into multiple shots.`);\n }\n if (shot.title && shot.title.length > LONG_TITLE) {\n say(shotNum, `Title is long (${shot.title.length} chars); titles should be concise headings.`);\n }\n\n // Check effects\n const effects = shot.effects ?? [];\n if (effects.length > MAX_EFFECTS_PER_SHOT) {\n say(shotNum, `Shot starts ${effects.length} effects at once; consider keeping effects focused.`);\n }\n\n for (const eff of effects) {\n if (!eff.id || typeof eff.id !== 'string') {\n say(shotNum, 'Every effect needs a unique string id.');\n continue;\n }\n if (effectIds.has(eff.id)) {\n say(shotNum, `Duplicate effect id \"${eff.id}\".`);\n }\n effectIds.add(eff.id);\n\n if (!EFFECT_TYPES.includes(eff.type as any)) {\n say(shotNum, `Unknown effect type \"${(eff as any).type}\".`);\n }\n\n if (eff.type === 'media') {\n if (!eff.media) {\n say(shotNum, `Media effect \"${eff.id}\" needs a media id.`);\n } else if (!mediaKeys.has(eff.media)) {\n say(shotNum, `Media effect \"${eff.id}\" references \"${eff.media}\" which is not defined in scene.media.`);\n }\n }\n\n if (eff.type === 'flow' && !eff.along) {\n say(shotNum, `Flow effect \"${eff.id}\" needs an \"along\" element id.`);\n }\n\n if (['glow', 'ripple', 'spotlight', 'shimmer', 'timer', 'blur', 'burst'].includes(eff.type)) {\n const withIds = eff as { ids?: string[] };\n if (!Array.isArray(withIds.ids) || withIds.ids.length === 0) {\n say(shotNum, `${eff.type} effect \"${eff.id}\" needs a non-empty ids array.`);\n }\n }\n }\n }\n\n return issues;\n}\n","import type { Stage, View } from './camera.js';\nimport { CAMERA_STEP_MS, cameraAt, cameraTrack, sceneStage } from './cameratrack.js';\nimport type { SceneElement } from './geometry.js';\nimport type { Scene } from './types.js';\n\n/** What the standalone player shows for one shot: the camera's move into it and the elements on stage. */\ninterface ShotPlan {\n /** The camera at each step of its move into the shot (one view when it does not move). */\n move: View[];\n /** How long the move takes, in ms (0 when the shot cuts or stays). */\n moveMs: number;\n elements: SceneElement[];\n}\n\n/** Each shot's camera move and elements, worked out the way `playScene` plays them. */\nfunction planShots(scene: Scene, stage: Stage): ShotPlan[] {\n return cameraTrack(scene).map((entry) => {\n // The player's steps: the last one lands on the shot's view, with no blur.\n const steps = Math.round(entry.moveMs / CAMERA_STEP_MS);\n const move = Array.from({ length: Math.max(0, steps - 1) }, (_, i) => cameraAt(entry, (i + 1) * CAMERA_STEP_MS, stage));\n move.push(entry.to);\n return { move, moveMs: entry.moveMs, elements: entry.elements };\n });\n}\n\n/**\n * Generates a self-contained, standalone HTML player for an animated scene.\n * Includes interactive playback controls, camera moves (panning, zooming,\n * 3D angles and motion blur), title and caption rendering, and embedded\n * WebGL fragment shaders for bespoke motion graphics.\n */\nexport function standaloneSceneHtml(scene: Scene): string {\n const stage = sceneStage(scene);\n const serializedScene = JSON.stringify(scene).replace(/</g, '\\\\u003c');\n const serializedPlan = JSON.stringify({ stage, shots: planShots(scene, stage) }).replace(/</g, '\\\\u003c');\n const title = scene.title || scene.name;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(title)} - Flair</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0f1117;\n color: #e2e8f0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 16px;\n user-select: none;\n }\n .player-container {\n position: relative;\n width: 100%;\n max-width: 1200px;\n aspect-ratio: ${stage.width} / ${stage.height};\n background: #000;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.1);\n display: flex;\n flex-direction: column;\n }\n .viewport {\n position: relative;\n flex: 1;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n .board {\n position: absolute;\n inset: 0;\n transform-origin: 50% 50%;\n will-change: transform, filter;\n }\n canvas.layer {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n .overlay-title {\n position: absolute;\n top: 24px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(15, 17, 23, 0.85);\n backdrop-filter: blur(8px);\n border: 1px solid rgba(255, 255, 255, 0.15);\n padding: 8px 24px;\n border-radius: 9999px;\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: #fff;\n pointer-events: none;\n transition: opacity 0.3s ease;\n opacity: 0;\n }\n .overlay-caption {\n position: absolute;\n bottom: 64px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(15, 17, 23, 0.88);\n backdrop-filter: blur(8px);\n border: 1px solid rgba(255, 255, 255, 0.15);\n padding: 10px 20px;\n border-radius: 8px;\n font-size: 1.1rem;\n color: #f1f5f9;\n max-width: 80%;\n text-align: center;\n pointer-events: none;\n transition: opacity 0.3s ease;\n opacity: 0;\n }\n .controls-bar {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 52px;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.9), transparent);\n display: flex;\n align-items: center;\n padding: 0 16px;\n gap: 12px;\n z-index: 10;\n }\n .btn {\n background: rgba(255, 255, 255, 0.15);\n border: none;\n color: #fff;\n padding: 6px 14px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.9rem;\n font-weight: 500;\n transition: background 0.15s;\n }\n .btn:hover { background: rgba(255, 255, 255, 0.25); }\n .timeline {\n flex: 1;\n height: 6px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 3px;\n cursor: pointer;\n position: relative;\n }\n .timeline-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: #3b82f6;\n border-radius: 3px;\n width: 0%;\n }\n .shot-badge {\n font-size: 0.85rem;\n color: #94a3b8;\n font-variant-numeric: tabular-nums;\n }\n </style>\n</head>\n<body>\n <div class=\"player-container\">\n <div class=\"viewport\" id=\"viewport\">\n <canvas id=\"shaderCanvas\" class=\"layer\"></canvas>\n <div id=\"board\" class=\"board\"><canvas id=\"sceneCanvas\" class=\"layer\"></canvas></div>\n <svg width=\"0\" height=\"0\" style=\"position:absolute\" aria-hidden=\"true\"><filter id=\"motionBlur\" x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\"><feGaussianBlur id=\"motionBlurAmount\" stdDeviation=\"0 0\"/></filter></svg>\n <div id=\"titleEl\" class=\"overlay-title\"></div>\n <div id=\"captionEl\" class=\"overlay-caption\"></div>\n </div>\n <div class=\"controls-bar\">\n <button id=\"playBtn\" class=\"btn\">Play</button>\n <button id=\"prevBtn\" class=\"btn\">⏮</button>\n <button id=\"nextBtn\" class=\"btn\">⏭</button>\n <div id=\"timeline\" class=\"timeline\">\n <div id=\"progress\" class=\"timeline-progress\"></div>\n </div>\n <div id=\"shotBadge\" class=\"shot-badge\">Shot 1 / 1</div>\n </div>\n </div>\n\n <script>\n const scene = ${serializedScene};\n // Each shot's camera move (zoom, scroll, 3D angle and blur at each step) and elements, worked out in advance.\n const plan = ${serializedPlan};\n const boardEl = document.getElementById('board');\n const sceneCanvas = document.getElementById('sceneCanvas');\n const shaderCanvas = document.getElementById('shaderCanvas');\n const ctx = sceneCanvas.getContext('2d');\n const titleEl = document.getElementById('titleEl');\n const captionEl = document.getElementById('captionEl');\n const playBtn = document.getElementById('playBtn');\n const prevBtn = document.getElementById('prevBtn');\n const nextBtn = document.getElementById('nextBtn');\n const progressEl = document.getElementById('progress');\n const shotBadge = document.getElementById('shotBadge');\n\n let currentShotIdx = 0;\n let isPlaying = false;\n let shotStartTime = performance.now();\n let moveFrom = performance.now();\n let gl = null;\n let shaderProgram = null;\n\n function resize() {\n const rect = sceneCanvas.parentElement.getBoundingClientRect();\n const dpr = window.devicePixelRatio || 1;\n sceneCanvas.width = rect.width * dpr;\n sceneCanvas.height = rect.height * dpr;\n shaderCanvas.width = rect.width * dpr;\n shaderCanvas.height = rect.height * dpr;\n }\n window.addEventListener('resize', resize);\n resize();\n\n // Setup WebGL if shader media is present\n const firstShaderKey = Object.keys(scene.media || {}).find(k => scene.media[k].type === 'shader');\n if (firstShaderKey && scene.media[firstShaderKey].source) {\n try {\n gl = shaderCanvas.getContext('webgl2');\n if (gl) {\n const vs = gl.createShader(gl.VERTEX_SHADER);\n gl.shaderSource(vs, '#version 300 es\\\\nin vec2 position; void main() { gl_Position = vec4(position, 0.0, 1.0); }');\n gl.compileShader(vs);\n\n const fs = gl.createShader(gl.FRAGMENT_SHADER);\n const fullFrag = '#version 300 es\\\\nprecision highp float;\\\\nout vec4 fragColor;\\\\nuniform vec2 u_resolution;\\\\nuniform float u_time;\\\\n' + scene.media[firstShaderKey].source + '\\\\nvoid main() { mainImage(fragColor, gl_FragCoord.xy); }';\n gl.shaderSource(fs, fullFrag);\n gl.compileShader(fs);\n\n shaderProgram = gl.createProgram();\n gl.attachShader(shaderProgram, vs);\n gl.attachShader(shaderProgram, fs);\n gl.linkProgram(shaderProgram);\n\n const quad = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, quad);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(shaderProgram, 'position');\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n }\n } catch (e) {\n console.warn('Shader setup failed:', e);\n }\n }\n\n function renderShader(timeSeconds) {\n if (!gl || !shaderProgram) return;\n gl.viewport(0, 0, shaderCanvas.width, shaderCanvas.height);\n gl.useProgram(shaderProgram);\n const resLoc = gl.getUniformLocation(shaderProgram, 'u_resolution');\n const timeLoc = gl.getUniformLocation(shaderProgram, 'u_time');\n if (resLoc) gl.uniform2f(resLoc, shaderCanvas.width, shaderCanvas.height);\n if (timeLoc) gl.uniform1f(timeLoc, timeSeconds);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n }\n\n function updateShotUI() {\n const shot = scene.shots[currentShotIdx];\n if (!shot) return;\n\n shotBadge.textContent = 'Shot ' + (currentShotIdx + 1) + ' / ' + scene.shots.length;\n\n if (shot.title) {\n titleEl.textContent = shot.title;\n titleEl.style.opacity = '1';\n } else {\n titleEl.style.opacity = '0';\n }\n\n if (shot.caption) {\n captionEl.textContent = shot.caption;\n captionEl.style.opacity = '1';\n } else {\n captionEl.style.opacity = '0';\n }\n\n const pct = (currentShotIdx / scene.shots.length) * 100;\n progressEl.style.width = pct + '%';\n }\n\n // The camera at time 'now': a step of the current shot's move, or where it settled.\n function cameraNow(now) {\n const { move, moveMs } = plan.shots[currentShotIdx];\n const u = moveMs > 0 ? Math.min(1, (now - moveFrom) / moveMs) : 1;\n return move[Math.min(move.length - 1, Math.floor(u * move.length))];\n }\n\n function applyCamera(view) {\n const w = boardEl.clientWidth || plan.stage.width;\n const angled = view.tilt || view.turn || view.roll;\n boardEl.style.transform = angled\n ? 'perspective(' + (view.perspective || 1.2) * w + 'px) rotateX(' + (view.tilt || 0) + 'deg) rotateY(' + (view.turn || 0) + 'deg) rotateZ(' + (view.roll || 0) + 'deg)'\n : 'none';\n // Motion blur streaks along a pan (taken back through the roll, as the board is blurred before it turns).\n const blur = (view.blur || 0) * w / plan.stage.width;\n if (blur >= 0.05) {\n let sx = blur, sy = blur;\n if (view.blurAngle !== undefined) {\n const a = (view.blurAngle - (view.roll || 0)) * Math.PI / 180;\n sx = Math.max(blur * 0.15, blur * Math.abs(Math.cos(a)));\n sy = Math.max(blur * 0.15, blur * Math.abs(Math.sin(a)));\n }\n document.getElementById('motionBlurAmount').setAttribute('stdDeviation', sx.toFixed(2) + ' ' + sy.toFixed(2));\n boardEl.style.filter = 'url(#motionBlur)';\n } else {\n boardEl.style.filter = 'none';\n }\n }\n\n function draw() {\n const now = performance.now();\n renderShader(now / 1000);\n\n const view = cameraNow(now);\n applyCamera(view);\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, sceneCanvas.width, sceneCanvas.height);\n const k = (sceneCanvas.width / plan.stage.width) * view.zoom;\n ctx.setTransform(k, 0, 0, k, k * view.scrollX, k * view.scrollY);\n\n for (const el of plan.shots[currentShotIdx].elements) {\n ctx.fillStyle = el.backgroundColor || 'rgba(59, 130, 246, 0.4)';\n ctx.strokeStyle = el.strokeColor || '#3b82f6';\n ctx.lineWidth = 3;\n ctx.fillRect(el.x, el.y, el.width, el.height);\n ctx.strokeRect(el.x, el.y, el.width, el.height);\n if (el.text) {\n ctx.fillStyle = '#fff';\n ctx.font = '24px sans-serif';\n ctx.fillText(el.text, el.x + 16, el.y + 40);\n }\n }\n\n // Playing moves on to the next shot once this one's move and hold are over.\n const shot = scene.shots[currentShotIdx];\n if (isPlaying && now - shotStartTime > plan.shots[currentShotIdx].moveMs + (shot.hold ?? 800) && currentShotIdx < scene.shots.length - 1) {\n goTo(currentShotIdx + 1, true);\n }\n if (isPlaying || now - moveFrom < plan.shots[currentShotIdx].moveMs) {\n requestAnimationFrame(draw);\n }\n }\n\n // Shows a shot: forwards it plays the camera's move into it, otherwise it cuts.\n function goTo(idx, animate) {\n currentShotIdx = idx;\n shotStartTime = performance.now();\n moveFrom = animate ? shotStartTime : shotStartTime - plan.shots[idx].moveMs;\n updateShotUI();\n requestAnimationFrame(draw);\n }\n\n playBtn.addEventListener('click', () => {\n isPlaying = !isPlaying;\n playBtn.textContent = isPlaying ? 'Pause' : 'Play';\n // The current shot counts as settled, so its hold runs from now.\n if (isPlaying) {\n shotStartTime = performance.now() - plan.shots[currentShotIdx].moveMs;\n requestAnimationFrame(draw);\n }\n });\n\n prevBtn.addEventListener('click', () => {\n if (currentShotIdx > 0) goTo(currentShotIdx - 1, false);\n });\n\n nextBtn.addEventListener('click', () => {\n if (currentShotIdx < scene.shots.length - 1) goTo(currentShotIdx + 1, true);\n });\n\n window.addEventListener('resize', () => requestAnimationFrame(draw));\n goTo(0, false);\n </script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n"],"mappings":"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","names":["escapeHtml"]}
|
|
@@ -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":[]}
|