@pixerate/editor 0.124.1 → 0.125.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/dist/agent/index.cjs +3 -3
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +3 -3
- package/dist/animation-editor/index.cjs +424 -85
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +174 -7
- package/dist/animation-editor/index.d.ts +174 -7
- package/dist/animation-editor/index.js +348 -9
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/chunk-FJ3324TZ.js +59 -0
- package/dist/chunk-FJ3324TZ.js.map +1 -0
- package/dist/{chunk-5EO2IXQE.js → chunk-FPRIUR4T.js} +1 -1
- package/dist/chunk-FPRIUR4T.js.map +1 -0
- package/dist/{chunk-ORHMVUR6.js → chunk-G6WXWS4Y.js} +5 -3
- package/dist/{chunk-ORHMVUR6.js.map → chunk-G6WXWS4Y.js.map} +1 -1
- package/dist/{chunk-CN4PJHUG.cjs → chunk-IOXKXQNW.cjs} +3 -59
- package/dist/chunk-IOXKXQNW.cjs.map +1 -0
- package/dist/{chunk-NGYD3AMN.js → chunk-MPQAO33O.js} +2 -2
- package/dist/{chunk-PEHYWUVV.cjs → chunk-O5RVCDEC.cjs} +82 -82
- package/dist/{chunk-PEHYWUVV.cjs.map → chunk-O5RVCDEC.cjs.map} +1 -1
- package/dist/{chunk-FHYDQOPC.cjs → chunk-QDJKUBWE.cjs} +5 -5
- package/dist/{chunk-FHYDQOPC.cjs.map → chunk-QDJKUBWE.cjs.map} +1 -1
- package/dist/{chunk-5XXMLBH4.cjs → chunk-QQKTOA4U.cjs} +1 -1
- package/dist/chunk-QQKTOA4U.cjs.map +1 -0
- package/dist/{chunk-QEG3GRN2.js → chunk-TGKT5QAB.js} +2 -58
- package/dist/chunk-TGKT5QAB.js.map +1 -0
- package/dist/{chunk-4QQZV5Y5.cjs → chunk-UXFI3CDZ.cjs} +11 -9
- package/dist/chunk-UXFI3CDZ.cjs.map +1 -0
- package/dist/{chunk-WE25VLCO.cjs → chunk-V5YGPRCZ.cjs} +36 -36
- package/dist/{chunk-WE25VLCO.cjs.map → chunk-V5YGPRCZ.cjs.map} +1 -1
- package/dist/chunk-W6QTLHZ3.cjs +59 -0
- package/dist/chunk-W6QTLHZ3.cjs.map +1 -0
- package/dist/{chunk-WATAQ2Z2.js → chunk-WUDPFWAN.js} +2 -2
- package/dist/{chunk-JRXJTPES.js → chunk-XZWOLXWA.js} +4 -4
- package/dist/{chunk-JRXJTPES.js.map → chunk-XZWOLXWA.js.map} +1 -1
- package/dist/effects/index.cjs +61 -33
- package/dist/effects/index.cjs.map +1 -1
- package/dist/effects/index.d.cts +4 -5
- package/dist/effects/index.d.ts +4 -5
- package/dist/effects/index.js +32 -4
- package/dist/effects/index.js.map +1 -1
- package/dist/{export-WmYoWmLM.d.cts → export-Bp7ji2r4.d.cts} +1 -2
- package/dist/{export-BdfY_lH0.d.ts → export-C1s4eolZ.d.ts} +1 -1
- package/dist/{export-BGtXgMJ3.d.ts → export-DAyPY94f.d.ts} +1 -2
- package/dist/{export-CbpjwxaI.d.cts → export-DVl4fWy5.d.cts} +1 -1
- package/dist/generation/index.cjs +3 -3
- package/dist/generation/index.cjs.map +1 -1
- package/dist/generation/index.js +3 -3
- package/dist/helpers-BBLlekuM.d.ts +2009 -0
- package/dist/helpers-i95Ob6Vi.d.cts +2009 -0
- package/dist/image-editor/index.cjs +629 -627
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +5 -7
- package/dist/image-editor/index.d.ts +5 -7
- package/dist/image-editor/index.js +15 -13
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint-editor/index.cjs +12 -11
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +3 -5
- package/dist/paint-editor/index.d.ts +3 -5
- package/dist/paint-editor/index.js +6 -5
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/{svg-BDrjRnL1.d.cts → svg-BkRL-fZJ.d.cts} +1 -1
- package/dist/{svg-C-vZ3q50.d.ts → svg-DJKriJ4A.d.ts} +1 -1
- package/dist/tools/index.cjs +2 -2
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/{tools-C7JtLYzo.d.ts → tools-CM8Oe8F3.d.ts} +1 -1
- package/dist/{tools-DHKGHiSK.d.cts → tools-DZCv2kX1.d.cts} +1 -1
- package/dist/vector/index.cjs +1032 -883
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +61 -6
- package/dist/vector/index.d.ts +61 -6
- package/dist/vector/index.js +155 -6
- package/dist/vector/index.js.map +1 -1
- package/dist/video-editor/index.cjs +273 -272
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +4 -6
- package/dist/video-editor/index.d.ts +4 -6
- package/dist/video-editor/index.js +7 -6
- package/dist/video-editor/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/pixerate-animation-editor/SKILL.md +24 -1
- package/skills/pixerate-vector/SKILL.md +15 -0
- package/dist/chunk-4QQZV5Y5.cjs.map +0 -1
- package/dist/chunk-5EO2IXQE.js.map +0 -1
- package/dist/chunk-5XXMLBH4.cjs.map +0 -1
- package/dist/chunk-CN4PJHUG.cjs.map +0 -1
- package/dist/chunk-QEG3GRN2.js.map +0 -1
- package/dist/flair-CxB3fOcU.d.ts +0 -739
- package/dist/flair-D7BxyBwL.d.cts +0 -739
- package/dist/helpers-777mC6dc.d.cts +0 -253
- package/dist/helpers-DX2_7L5t.d.ts +0 -253
- package/dist/types-BcX7sCsn.d.ts +0 -993
- package/dist/types-CznabG7m.d.cts +0 -993
- /package/dist/{chunk-NGYD3AMN.js.map → chunk-MPQAO33O.js.map} +0 -0
- /package/dist/{chunk-WATAQ2Z2.js.map → chunk-WUDPFWAN.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/effects/types.ts","../src/effects/tools.ts"],"sourcesContent":["/**\n * The effects contract: how an editor finds, describes and renders effects that live in another\n * package. This library ships no effects of its own here, only the shapes a provider fills in;\n * a host passes a provider to an editor (`setEffectsProvider`) and the editor degrades to its\n * old behaviour without one.\n *\n * Every time is in milliseconds. Pixels are straight-alpha RGBA rows, top to bottom.\n */\n\nimport type { JsonSchema } from '../tools/schema.js';\nimport type { FlairSvgAnimation } from '../animation-editor/flair.js';\n\n/** How an effect is drawn: a pixel shader, an animated vector picture, or motion applied to someone else's vector art. */\nexport type EffectKind = 'shader' | 'vector' | 'motion';\n\n/**\n * What an effect is for. An effect may have several uses.\n *\n * - `filter`: changes an input picture (`inputs.source`).\n * - `transition`: blends `inputs.from` into `inputs.to`; `progress` runs 0 → 1.\n * - `generator`: draws a picture from nothing (a background, a texture).\n * - `overlay`: draws something meant to sit over other content, usually with transparency.\n * - `title`: draws type or a graphic meant as a title.\n * - `motion`: animates vector art (`applyVectorEffect`).\n * - `interactive`: reacts to pointer, scroll or audio input at run time.\n */\nexport type EffectUse = 'filter' | 'transition' | 'generator' | 'overlay' | 'title' | 'motion' | 'interactive';\n\nexport const EFFECT_KINDS: readonly EffectKind[] = ['shader', 'vector', 'motion'];\nexport const EFFECT_USES: readonly EffectUse[] = ['filter', 'transition', 'generator', 'overlay', 'title', 'motion', 'interactive'];\n\n/** One entry of a provider's catalog. */\nexport interface EffectSummary {\n\t/** Stable id, saved in documents. Never reused for a different effect. */\n\tid: string;\n\tname: string;\n\tkind: EffectKind;\n\t/** The provider's own grouping (e.g. `gradient`, `light`, `transition`). */\n\tcategory: string;\n\tuses: readonly EffectUse[];\n\tdescription?: string;\n\ttags?: readonly string[];\n\t/** True when the effect's output has transparent pixels (it can sit over other content). */\n\ttransparent: boolean;\n\t/** True when the effect loops; its `duration` is then one cycle. */\n\tloop: boolean;\n\t/** Length of one play (ms). Absent for effects that do not change over time. */\n\tduration?: number;\n}\n\nexport interface EffectSearchOptions {\n\tkind?: EffectKind;\n\tcategory?: string;\n\tuse?: EffectUse;\n\t/** Most results to return. */\n\tlimit?: number;\n}\n\n/** One control value: a number, a `#rrggbb` colour or other string, a switch, or a vector of numbers. */\nexport type EffectValue = number | string | boolean | readonly number[];\nexport type EffectValues = Readonly<Record<string, EffectValue>>;\n\n/**\n * The controls of an effect as a strict JSON Schema: `type: 'object'`, `additionalProperties:\n * false`, and a `description` on every property (with units and range). `checkEffectProvider`\n * checks this.\n */\nexport type EffectControlSchema = JsonSchema & {\n\ttype: 'object';\n\tproperties: { readonly [control: string]: JsonSchema };\n\tadditionalProperties: false;\n};\n\nexport interface EffectPreset {\n\tname: string;\n\t/** Values to apply over the defaults. */\n\tvalues: EffectValues;\n}\n\n/**\n * A named input the effect reads. Filters read `source`, transitions `from` and `to`; a mask is\n * passed separately (`EffectRenderRequest.mask`), not as an input.\n */\nexport interface EffectInputSpec {\n\tname: string;\n\tkind: 'image' | 'audio';\n\tdescription: string;\n\trequired: boolean;\n}\n\n/** Everything an editor needs to show an effect's controls and render it. */\nexport interface EffectDescription extends EffectSummary {\n\tcontrols: EffectControlSchema;\n\t/** A value for every control. */\n\tdefaults: EffectValues;\n\tpresets: readonly EffectPreset[];\n\tinputs: readonly EffectInputSpec[];\n}\n\n/**\n * Pixels in and out of a renderer, shaped like `ImageData`.\n *\n * `data` is 8-bit sRGB with straight alpha and is always present. `float`, when present, is the\n * same picture as 32-bit floats in **linear light** (sRGB primaries), straight alpha, where colour\n * may exceed 1. Renderers created with `depth` 16 or 32 return `float` when the provider\n * `supportsFloat`; `data` is then its clamped sRGB encoding.\n */\nexport interface EffectPixels {\n\twidth: number;\n\theight: number;\n\tdata: Uint8ClampedArray;\n\tfloat?: Float32Array;\n}\n\n/**\n * An input picture. Always accepted as `EffectPixels`; a provider whose capabilities include\n * `supportsHostImages` also takes the host's own image objects, which saves a read back.\n */\nexport type EffectImageSource = EffectPixels | ImageBitmap | OffscreenCanvas | VideoFrame;\n\nexport interface EffectRendererOptions {\n\t/** Output size in pixels. */\n\twidth: number;\n\theight: number;\n\t/** Bits per channel of the target (default 8). 16 and 32 ask for `float` output. */\n\tdepth?: 8 | 16 | 32;\n}\n\nexport interface EffectRenderRequest {\n\t/** Effect time (ms), from the start of the effect. */\n\ttime: number;\n\t/** Control values; missing controls take their defaults. Invalid values reject with `EffectControlError`. */\n\tvalues?: EffectValues;\n\t/** Pictures for the effect's named inputs (see `EffectDescription.inputs`). */\n\tinputs?: Readonly<Record<string, EffectImageSource>>;\n\t/** Coverage (alpha channel, 0 = untouched) limiting where the effect applies. Only with `supportsMask`. */\n\tmask?: EffectImageSource;\n\t/** Transitions: how far from `from` to `to`, 0–1. Defaults to `time / duration`. */\n\tprogress?: number;\n}\n\n/** A live renderer for one effect at one size. Not safe for concurrent `render` calls. */\nexport interface EffectRenderer {\n\treadonly effectId: string;\n\treadonly width: number;\n\treadonly height: number;\n\treadonly depth: 8 | 16 | 32;\n\trender(request: EffectRenderRequest): Promise<EffectPixels>;\n\t/** Frees GPU or worker resources. Rendering afterwards rejects. */\n\tdispose(): void;\n}\n\n/** One change of an animated SVG (the shape of Flair's `SvgAnimation`). */\nexport type EffectSvgAnimation = FlairSvgAnimation;\n\n/** An animated SVG: markup plus changes to its elements (the shape of Flair's `SvgMotion`). */\nexport interface EffectVectorMotion {\n\tsvg: string;\n\tanimate?: readonly EffectSvgAnimation[];\n\t/** Length of one play (ms). */\n\tduration: number;\n\tloop: boolean;\n\t/** Ms held on the last frame before looping. */\n\thold?: number;\n}\n\n/** What a vector or motion effect is applied to: SVG markup, optionally with the ids of the elements to animate (default: all). */\nexport type EffectVectorTarget = string | { svg: string; nodeIds?: readonly string[] };\n\n/** What a provider can do, so editors can degrade cleanly. */\nexport interface EffectProviderCapabilities {\n\t/** Renders off the main thread. */\n\tsupportsWorker: boolean;\n\t/** Returns `float` pixels for depth 16 and 32 renderers. */\n\tsupportsFloat: boolean;\n\t/** Honours `EffectRenderRequest.mask`; otherwise the editor masks the result itself. */\n\tsupportsMask: boolean;\n\t/** Takes `ImageBitmap`, `OffscreenCanvas` or `VideoFrame` inputs, not only `EffectPixels`. */\n\tsupportsHostImages: boolean;\n\t/** Implements `applyVectorEffect`. */\n\tsupportsVector: boolean;\n\t/** Largest width or height a renderer accepts (px). */\n\tmaxSize?: number;\n}\n\ntype MaybePromise<T> = T | Promise<T>;\n\n/**\n * A source of effects. Implementations live outside this library (for example Flair's\n * `flairEffects()`); `createFakeEffectProvider` is a small reference implementation.\n *\n * Unknown ids reject or throw `UnknownEffectError`, invalid values `EffectControlError`, and an\n * operation the provider cannot do `EffectUnsupportedError`.\n */\nexport interface EffectProvider {\n\t/** Stable id of the provider, saved next to effect ids (e.g. `flair`). */\n\treadonly id: string;\n\treadonly name: string;\n\treadonly version?: string;\n\treadonly capabilities: EffectProviderCapabilities;\n\tlist(options?: EffectSearchOptions): MaybePromise<readonly EffectSummary[]>;\n\t/** Effects matching every word of `query` (in name, id, category, tags or description), best first. */\n\tsearch(query: string, options?: EffectSearchOptions): MaybePromise<readonly EffectSummary[]>;\n\tdescribe(id: string): MaybePromise<EffectDescription>;\n\t/** A pixel renderer. A `vector` effect may refuse with `EffectUnsupportedError` where it cannot lay out SVG (a Worker). */\n\tcreateRenderer(id: string, options: EffectRendererOptions): MaybePromise<EffectRenderer>;\n\t/**\n\t * Vector and motion effects as an animated SVG. Present when `capabilities.supportsVector`. A\n\t * `motion` effect animates the target's elements (`nodeIds`, else each top-level element with an\n\t * id); a `vector` effect brings its own picture and ignores the target. Refuse a target the\n\t * effect cannot use with `EffectUnsupportedError`.\n\t */\n\tapplyVectorEffect?(id: string, target: EffectVectorTarget, values?: EffectValues): MaybePromise<EffectVectorMotion>;\n}\n","/**\n * The agent tools for provider effects, shared by every editor that takes an `EffectProvider`:\n * `search_effects`, `describe_effect`, `list_applied_effects`, `apply_effect`, `update_effect` and\n * `remove_effect`. An editor supplies an `EffectToolHost` (what its targets are and how to change\n * them as one undo step each) and may add tools of its own (`extraTools`).\n *\n * Without a provider the read tools say so (and `list_applied_effects` still lists what a\n * document holds), and the write tools refuse with the reason. On a read-only surface the write\n * tools are hidden.\n */\n\nimport type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport type { JsonSchema } from '../tools/schema.js';\nimport { isEffectError } from './errors.js';\nimport { EFFECT_KINDS, EFFECT_USES, type EffectDescription, type EffectProvider, type EffectSummary, type EffectUse } from './types.js';\n\n/** One effect applied to a target, as the tools report it. */\nexport interface AppliedEffectInfo {\n\t/** Its id on the target: what `update_effect` and `remove_effect` take. */\n\tid: string;\n\teffect: string;\n\tname?: string;\n\tprovider?: string;\n\t/** What it is on the target (e.g. `clip effect`, `effect clip`, `transition`, `smart filter`, `effect layer`). */\n\trole: string;\n\tenabled: boolean;\n\t/** 0–1, or keyframes, where the role has an opacity. */\n\topacity?: unknown;\n\t/** Its saved control values (numbers may be keyframes where the editor allows them). */\n\tvalues: Record<string, unknown>;\n\t/** Its place in the target's stack (0 = applied first), for stacked roles. */\n\tindex?: number;\n}\n\nexport interface EffectTargetEffects {\n\ttarget: string;\n\t/** A label for the target (a clip or layer name). */\n\tname?: string;\n\teffects: AppliedEffectInfo[];\n}\n\nexport interface EffectChangeResult {\n\ttarget: string;\n\t/** What was added, changed or removed. */\n\teffect: AppliedEffectInfo;\n\t/** The target's effects afterwards. */\n\teffects: AppliedEffectInfo[];\n\t/** Other things the change made (a new clip or layer id, ...). */\n\t[extra: string]: unknown;\n}\n\n/** What an editor gives the shared effect tools. */\nexport interface EffectToolHost {\n\t/** Provider id (unique per registry). */\n\tid: string;\n\t/** Always prefix tool names with `<namespace>__`. */\n\tnamespace?: string;\n\t/** Words for the editor's targets, e.g. `{ noun: 'clip', id: 'A clip id from get_video_project (e.g. \"c1\").' }`. */\n\ttarget: { noun: string; id: string };\n\t/** The uses `apply_effect` takes on a target (others go through `extraTools`). */\n\tapplyUses: readonly EffectUse[];\n\t/** Text added to `apply_effect`'s description: where the effect goes and how it is drawn. */\n\tapplyDetails: string;\n\t/** The current provider, or `null`. */\n\tprovider(): EffectProvider | null;\n\t/** Why edits are refused now (read-only), or `null`. */\n\treadOnlyReason(): string | null;\n\t/** Every target with effects, or one target (throws for an unknown id, naming valid ones). */\n\tlist(target?: string): EffectTargetEffects[];\n\tapply(target: string, effect: string, args: { values?: Record<string, unknown>; index?: number; enabled?: boolean; opacity?: number }): Promise<EffectChangeResult>;\n\tupdate(target: string, instance: string, changes: { values?: Record<string, unknown>; index?: number; enabled?: boolean; opacity?: number }): Promise<EffectChangeResult>;\n\tremove(target: string, instance: string): Promise<EffectChangeResult>;\n\t/** Editor-specific effect tools (transitions, effect clips, effect layers, keyframes). */\n\textraTools?: (context: EffectToolContext) => AgentTool[];\n\t/** Lets the tool list follow the provider and read-only changes. */\n\tsubscribe?: (notify: () => void) => () => void;\n}\n\n/** Shared pieces for `extraTools`. */\nexport interface EffectToolContext {\n\t/** The provider, or a thrown error saying none is registered. */\n\trequireProvider(): EffectProvider;\n\t/** The schema of a `values` argument. */\n\tvaluesSchema(what: string, keyframes?: boolean): JsonSchema;\n\t/** The schema of an `effect` argument for effects with one of `uses`. */\n\teffectIdSchema(uses: readonly EffectUse[]): JsonSchema;\n\treadonly annotations: typeof ANNOTATIONS;\n}\n\nconst ANNOTATIONS = {\n\tread: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n\twrite: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },\n\twriteIdempotent: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n\tdestructive: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }\n} as const;\n\nconst NO_PROVIDER = 'No effects provider is registered with this editor, so there are no effects to search, describe or apply. The host app must register one (setEffectsProvider); effects already in the document are kept and shown as placeholders.';\n\nconst object = (properties: Record<string, JsonSchema>, required: string[] = []): JsonSchema & { type: 'object' } => ({\n\ttype: 'object',\n\tproperties,\n\t...(required.length ? { required } : {}),\n\tadditionalProperties: false\n});\n\n/** The catalog fetched once per provider, so tool schemas can list effect ids. */\ninterface Catalog {\n\tprovider: EffectProvider;\n\teffects: readonly EffectSummary[] | null;\n}\n\n/** Most effect ids listed in an `effect` argument's enum; larger catalogs are searched instead. */\nconst ENUM_LIMIT = 60;\n\nconst summaryOut = (e: EffectSummary) => ({\n\tid: e.id,\n\tname: e.name,\n\tkind: e.kind,\n\tcategory: e.category,\n\tuses: e.uses,\n\t...(e.description ? { description: e.description } : {}),\n\ttransparent: e.transparent,\n\tloop: e.loop,\n\t...(e.duration !== undefined ? { duration: e.duration } : {})\n});\n\n/** The agent tools for provider effects in one editor (see the module comment). */\nexport function createEffectToolProvider(host: EffectToolHost): AgentToolProvider {\n\tlet catalog: Catalog | null = null;\n\tconst listeners = new Set<() => void>();\n\tconst notify = () => listeners.forEach((l) => l());\n\n\t/** Fetches the catalog of a new provider in the background; the tool list changes when it lands. */\n\tconst catalogFor = (provider: EffectProvider | null): readonly EffectSummary[] | null => {\n\t\tif (!provider) return null;\n\t\tif (catalog?.provider !== provider) {\n\t\t\tconst entry: Catalog = { provider, effects: null };\n\t\t\tcatalog = entry;\n\t\t\tPromise.resolve()\n\t\t\t\t.then(() => provider.list())\n\t\t\t\t.then((effects) => {\n\t\t\t\t\tif (catalog === entry) {\n\t\t\t\t\t\tentry.effects = effects;\n\t\t\t\t\t\tnotify();\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\t\t}\n\t\treturn catalog.effects;\n\t};\n\n\tconst requireProvider = (): EffectProvider => {\n\t\tconst p = host.provider();\n\t\tif (!p) throw new Error(NO_PROVIDER);\n\t\treturn p;\n\t};\n\tconst writable = () => {\n\t\tconst reason = host.readOnlyReason();\n\t\tif (reason) throw new Error(reason);\n\t\treturn requireProvider();\n\t};\n\t/** Turns provider errors into messages a model can act on. */\n\tconst explain = async <T>(run: () => Promise<T>): Promise<T> => {\n\t\ttry {\n\t\t\treturn await run();\n\t\t} catch (error) {\n\t\t\tif (isEffectError(error)) throw new Error(error.message);\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tconst valuesSchema = (what: string, keyframes = false): JsonSchema => ({\n\t\ttype: 'object',\n\t\tdescription: `${what}: control name → value, as listed by describe_effect (its \"controls\" schema: types, ranges, units and defaults). Only the given controls change; colours are \"#rrggbb\".${keyframes ? ' A number control may instead be keyframes { keys: [{ t: ms from the start, v, ease }] }.' : ''} Unknown controls and out-of-range values are refused with the valid ones.`\n\t});\n\tconst effectIdSchema = (uses: readonly EffectUse[]): JsonSchema => {\n\t\tconst effects = catalogFor(host.provider())?.filter((e) => e.kind !== 'motion' && e.uses.some((u) => uses.includes(u)));\n\t\tconst base = `An effect id from search_effects (use ${uses.map((u) => `\"${u}\"`).join(' or ')}).`;\n\t\treturn effects && effects.length && effects.length <= ENUM_LIMIT\n\t\t\t? { type: 'string', enum: effects.map((e) => e.id), description: `${base} One of: ${effects.map((e) => `${e.id} (${e.name})`).join(', ')}.` }\n\t\t\t: { type: 'string', minLength: 1, description: base };\n\t};\n\tconst context: EffectToolContext = { requireProvider: writable, valuesSchema, effectIdSchema, annotations: ANNOTATIONS };\n\n\tconst TARGET = { type: 'string', minLength: 1, description: host.target.id } as const;\n\tconst INSTANCE = { type: 'string', minLength: 1, description: `The effect's id on the ${host.target.noun}, from list_applied_effects or apply_effect.` } as const;\n\tconst INDEX = { type: 'integer', minimum: 0, description: `Place in the ${host.target.noun}'s effect stack: 0 is applied first (default: the end, applied last).` } as const;\n\tconst OPACITY = { type: 'number', minimum: 0, maximum: 1, description: 'How much of the effect shows over its input, 0–1 (default 1).' } as const;\n\tconst ENABLED = { type: 'boolean', description: 'false turns the effect off but keeps it (default true).' } as const;\n\n\tconst readTools = (): AgentTool[] => [\n\t\t{\n\t\t\tname: 'search_effects',\n\t\t\ttitle: 'Search effects',\n\t\t\tdescription: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion), category or use (${EFFECT_USES.join(', ')}). For this editor, apply_effect takes uses ${host.applyUses.join(' or ')}. Returns { provider, effects: [{ id, name, kind, category, uses, description, transparent, loop, duration (ms) }], total }. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,\n\t\t\tinputSchema: object({\n\t\t\t\tquery: { type: 'string', description: 'Words to match (default: every effect).' },\n\t\t\t\tkind: { type: 'string', enum: [...EFFECT_KINDS], description: 'shader (pixels), vector (animated SVG) or motion (animates vector art).' },\n\t\t\t\tcategory: { type: 'string', minLength: 1, description: 'The provider\\'s category, e.g. \"gradient\", \"light\", \"transition\".' },\n\t\t\t\tuse: { type: 'string', enum: [...EFFECT_USES], description: 'What the effect is for: filter changes a picture, transition blends two, generator draws a background, overlay draws over content, title draws a title, motion animates vector art, interactive reacts to input.' },\n\t\t\t\tlimit: { type: 'integer', minimum: 1, maximum: 200, description: 'Most effects to return (default 20).' }\n\t\t\t}),\n\t\t\tannotations: { title: 'Search effects', ...ANNOTATIONS.read },\n\t\t\thandler: async (args) => {\n\t\t\t\tconst p = host.provider();\n\t\t\t\tif (!p) return { available: false, reason: NO_PROVIDER, effects: [], total: 0 };\n\t\t\t\tconst found = await explain(async () => p.search((args.query as string | undefined) ?? '', { kind: args.kind, category: args.category, use: args.use }));\n\t\t\t\tconst limit = (args.limit as number | undefined) ?? 20;\n\t\t\t\treturn { available: true, provider: { id: p.id, name: p.name, capabilities: p.capabilities }, effects: found.slice(0, limit).map(summaryOut), total: found.length };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: 'describe_effect',\n\t\t\ttitle: 'Describe an effect',\n\t\t\tdescription: 'Returns one effect\\'s controls as a strict JSON Schema (each control described, with its type, range and units), its defaults, presets (named sets of values) and inputs, plus its summary. Use the control names and ranges for apply_effect and update_effect values. With no provider registered it returns available: false and the reason.',\n\t\t\tinputSchema: object({ effect: { type: 'string', minLength: 1, description: 'An effect id from search_effects or list_applied_effects.' } }, ['effect']),\n\t\t\tannotations: { title: 'Describe an effect', ...ANNOTATIONS.read },\n\t\t\thandler: async (args) => {\n\t\t\t\tconst p = host.provider();\n\t\t\t\tif (!p) return { available: false, reason: NO_PROVIDER };\n\t\t\t\tconst d: EffectDescription = await explain(async () => p.describe(args.effect as string));\n\t\t\t\treturn { available: true, ...summaryOut(d), controls: d.controls, defaults: d.defaults, presets: d.presets, inputs: d.inputs, appliesHere: d.kind !== 'motion' && d.uses.some((u) => host.applyUses.includes(u)) };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: 'list_applied_effects',\n\t\t\ttitle: 'List applied effects',\n\t\t\tdescription: `Lists the effects in the document: for each ${host.target.noun} that has any (or the one given), its effects in order with id, effect id, name, role, enabled, opacity and saved values. available is false for an effect the current provider does not have (or when none is registered): it is kept and drawn as a placeholder, and can still be removed. Works without a provider.`,\n\t\t\tinputSchema: object({ target: { ...TARGET, description: `${host.target.id} Leave out to list every ${host.target.noun} with effects.` } }),\n\t\t\tannotations: { title: 'List applied effects', ...ANNOTATIONS.read },\n\t\t\thandler: async (args) => {\n\t\t\t\tconst p = host.provider();\n\t\t\t\tconst known = new Set((p ? await explain(async () => p.list()) : []).map((e) => e.id));\n\t\t\t\tconst targets = host.list(args.target as string | undefined).map((t) => ({ ...t, effects: t.effects.map((e) => ({ ...e, available: known.has(e.effect) })) }));\n\t\t\t\treturn { provider: p ? { id: p.id, name: p.name } : null, ...(p ? {} : { reason: NO_PROVIDER }), targets };\n\t\t\t}\n\t\t}\n\t];\n\n\tconst writeTools = (): AgentTool[] => [\n\t\t{\n\t\t\tname: 'apply_effect',\n\t\t\ttitle: 'Apply an effect',\n\t\t\tdescription: `Adds a provider effect to a ${host.target.noun}. ${host.applyDetails} values are checked against describe_effect's controls first; nothing changes if any is wrong. One undo step. Returns the effect with its new id (for update_effect and remove_effect) and the ${host.target.noun}'s effects. Refused when no provider is registered.`,\n\t\t\tinputSchema: object(\n\t\t\t\t{\n\t\t\t\t\ttarget: TARGET,\n\t\t\t\t\teffect: effectIdSchema(host.applyUses),\n\t\t\t\t\tvalues: valuesSchema('Control values over the effect\\'s defaults', true),\n\t\t\t\t\tindex: INDEX,\n\t\t\t\t\tenabled: ENABLED,\n\t\t\t\t\topacity: OPACITY\n\t\t\t\t},\n\t\t\t\t['target', 'effect']\n\t\t\t),\n\t\t\tannotations: { title: 'Apply an effect', ...ANNOTATIONS.write },\n\t\t\thandler: async (args) => {\n\t\t\t\twritable();\n\t\t\t\treturn explain(() => host.apply(args.target as string, args.effect as string, { values: args.values, index: args.index, enabled: args.enabled, opacity: args.opacity }));\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: 'update_effect',\n\t\t\ttitle: 'Change an applied effect',\n\t\t\tdescription: `Changes one applied effect on a ${host.target.noun}: its values (merged over the current ones; null resets a control to its default), enabled, opacity or place in the stack. Values are checked against describe_effect's controls first. One undo step. Returns the effect as it is now and the ${host.target.noun}'s effects. Refused when no provider is registered.`,\n\t\t\tinputSchema: object(\n\t\t\t\t{\n\t\t\t\t\ttarget: TARGET,\n\t\t\t\t\teffect: INSTANCE,\n\t\t\t\t\tvalues: { ...valuesSchema('Control values to change', true), description: `${valuesSchema('Control values to change', true).description} null resets a control to its default.` },\n\t\t\t\t\tenabled: ENABLED,\n\t\t\t\t\topacity: OPACITY,\n\t\t\t\t\tindex: { ...INDEX, description: `Move it to this place in the ${host.target.noun}'s stack (0 = applied first).` }\n\t\t\t\t},\n\t\t\t\t['target', 'effect']\n\t\t\t),\n\t\t\tannotations: { title: 'Change an applied effect', ...ANNOTATIONS.writeIdempotent },\n\t\t\thandler: async (args) => {\n\t\t\t\twritable();\n\t\t\t\tif (args.values === undefined && args.enabled === undefined && args.opacity === undefined && args.index === undefined) throw new Error('Give at least one of values, enabled, opacity or index to change.');\n\t\t\t\treturn explain(() => host.update(args.target as string, args.effect as string, { values: args.values, enabled: args.enabled, opacity: args.opacity, index: args.index }));\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: 'remove_effect',\n\t\t\ttitle: 'Remove an applied effect',\n\t\t\tdescription: `Removes one applied effect from a ${host.target.noun} (an effect the provider no longer has included). One undo step. Returns what was removed and the ${host.target.noun}'s remaining effects. Refused when no provider is registered.`,\n\t\t\tinputSchema: object({ target: TARGET, effect: INSTANCE }, ['target', 'effect']),\n\t\t\tannotations: { title: 'Remove an applied effect', ...ANNOTATIONS.destructive },\n\t\t\thandler: async (args) => {\n\t\t\t\twritable();\n\t\t\t\treturn explain(() => host.remove(args.target as string, args.effect as string));\n\t\t\t}\n\t\t}\n\t];\n\n\treturn {\n\t\tid: host.id,\n\t\t...(host.namespace ? { namespace: host.namespace } : {}),\n\t\ttools: () => {\n\t\t\tconst extra = host.extraTools?.(context) ?? [];\n\t\t\tif (host.readOnlyReason()) return [...readTools(), ...extra.filter((t) => t.annotations?.readOnlyHint)];\n\t\t\treturn [...readTools(), ...writeTools(), ...extra];\n\t\t},\n\t\tsubscribe: (l) => {\n\t\t\tlisteners.add(l);\n\t\t\tconst off = host.subscribe?.(l);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(l);\n\t\t\t\toff?.();\n\t\t\t};\n\t\t}\n\t};\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/effects/types.ts","../src/effects/tools.ts"],"sourcesContent":["/**\n * The effects contract: how an editor finds, describes and renders effects that live in another\n * package. This library ships no effects of its own here, only the shapes a provider fills in;\n * a host passes a provider to an editor (`setEffectsProvider`) and the editor degrades to its\n * old behaviour without one.\n *\n * Every time is in milliseconds. Pixels are straight-alpha RGBA rows, top to bottom.\n */\n\nimport type { JsonSchema } from '../tools/schema.js';\nimport type { FlairSvgAnimation } from '../animation-editor/flair.js';\n\n/** How an effect is drawn: a pixel shader, an animated vector picture, or motion applied to someone else's vector art. */\nexport type EffectKind = 'shader' | 'vector' | 'motion';\n\n/**\n * What an effect is for. An effect may have several uses.\n *\n * - `filter`: changes an input picture (`inputs.source`).\n * - `transition`: blends `inputs.from` into `inputs.to`; `progress` runs 0 → 1.\n * - `generator`: draws a picture from nothing (a background, a texture).\n * - `overlay`: draws something meant to sit over other content, usually with transparency.\n * - `title`: draws type or a graphic meant as a title.\n * - `motion`: animates vector art (`applyVectorEffect`).\n * - `interactive`: reacts to pointer, scroll or audio input at run time.\n */\nexport type EffectUse = 'filter' | 'transition' | 'generator' | 'overlay' | 'title' | 'motion' | 'interactive';\n\nexport const EFFECT_KINDS: readonly EffectKind[] = ['shader', 'vector', 'motion'];\nexport const EFFECT_USES: readonly EffectUse[] = ['filter', 'transition', 'generator', 'overlay', 'title', 'motion', 'interactive'];\n\n/** One entry of a provider's catalog. */\nexport interface EffectSummary {\n\t/** Stable id, saved in documents. Never reused for a different effect. */\n\tid: string;\n\tname: string;\n\tkind: EffectKind;\n\t/** The provider's own grouping (e.g. `gradient`, `light`, `transition`). */\n\tcategory: string;\n\tuses: readonly EffectUse[];\n\tdescription?: string;\n\ttags?: readonly string[];\n\t/** True when the effect's output has transparent pixels (it can sit over other content). */\n\ttransparent: boolean;\n\t/** True when the effect loops; its `duration` is then one cycle. */\n\tloop: boolean;\n\t/** Length of one play (ms). Absent for effects that do not change over time. */\n\tduration?: number;\n}\n\nexport interface EffectSearchOptions {\n\tkind?: EffectKind;\n\tcategory?: string;\n\tuse?: EffectUse;\n\t/** Most results to return. */\n\tlimit?: number;\n}\n\n/** One control value: a number, a `#rrggbb` colour or other string, a switch, or a vector of numbers. */\nexport type EffectValue = number | string | boolean | readonly number[];\nexport type EffectValues = Readonly<Record<string, EffectValue>>;\n\n/**\n * The controls of an effect as a strict JSON Schema: `type: 'object'`, `additionalProperties:\n * false`, and a `description` on every property (with units and range). `checkEffectProvider`\n * checks this.\n */\nexport type EffectControlSchema = JsonSchema & {\n\ttype: 'object';\n\tproperties: { readonly [control: string]: JsonSchema };\n\tadditionalProperties: false;\n};\n\nexport interface EffectPreset {\n\tname: string;\n\t/** Values to apply over the defaults. */\n\tvalues: EffectValues;\n}\n\n/**\n * A named input the effect reads. Filters read `source`, transitions `from` and `to`; a mask is\n * passed separately (`EffectRenderRequest.mask`), not as an input.\n */\nexport interface EffectInputSpec {\n\tname: string;\n\tkind: 'image' | 'audio';\n\tdescription: string;\n\trequired: boolean;\n}\n\n/** Everything an editor needs to show an effect's controls and render it. */\nexport interface EffectDescription extends EffectSummary {\n\tcontrols: EffectControlSchema;\n\t/** A value for every control. */\n\tdefaults: EffectValues;\n\tpresets: readonly EffectPreset[];\n\tinputs: readonly EffectInputSpec[];\n}\n\n/**\n * Pixels in and out of a renderer, shaped like `ImageData`.\n *\n * `data` is 8-bit sRGB with straight alpha and is always present. `float`, when present, is the\n * same picture as 32-bit floats in **linear light** (sRGB primaries), straight alpha, where colour\n * may exceed 1. Renderers created with `depth` 16 or 32 return `float` when the provider\n * `supportsFloat`; `data` is then its clamped sRGB encoding.\n */\nexport interface EffectPixels {\n\twidth: number;\n\theight: number;\n\tdata: Uint8ClampedArray;\n\tfloat?: Float32Array;\n}\n\n/**\n * An input picture. Always accepted as `EffectPixels`; a provider whose capabilities include\n * `supportsHostImages` also takes the host's own image objects, which saves a read back.\n */\nexport type EffectImageSource = EffectPixels | ImageBitmap | OffscreenCanvas | VideoFrame;\n\nexport interface EffectRendererOptions {\n\t/** Output size in pixels. */\n\twidth: number;\n\theight: number;\n\t/** Bits per channel of the target (default 8). 16 and 32 ask for `float` output. */\n\tdepth?: 8 | 16 | 32;\n}\n\nexport interface EffectRenderRequest {\n\t/** Effect time (ms), from the start of the effect. */\n\ttime: number;\n\t/** Control values; missing controls take their defaults. Invalid values reject with `EffectControlError`. */\n\tvalues?: EffectValues;\n\t/** Pictures for the effect's named inputs (see `EffectDescription.inputs`). */\n\tinputs?: Readonly<Record<string, EffectImageSource>>;\n\t/** Coverage (alpha channel, 0 = untouched) limiting where the effect applies. Only with `supportsMask`. */\n\tmask?: EffectImageSource;\n\t/** Transitions: how far from `from` to `to`, 0–1. Defaults to `time / duration`. */\n\tprogress?: number;\n}\n\n/** A live renderer for one effect at one size. Not safe for concurrent `render` calls. */\nexport interface EffectRenderer {\n\treadonly effectId: string;\n\treadonly width: number;\n\treadonly height: number;\n\treadonly depth: 8 | 16 | 32;\n\trender(request: EffectRenderRequest): Promise<EffectPixels>;\n\t/** Frees GPU or worker resources. Rendering afterwards rejects. */\n\tdispose(): void;\n}\n\n/** One change of an animated SVG (the shape of Flair's `SvgAnimation`). */\nexport type EffectSvgAnimation = FlairSvgAnimation;\n\n/** An animated SVG: markup plus changes to its elements (the shape of Flair's `SvgMotion`). */\nexport interface EffectVectorMotion {\n\tsvg: string;\n\tanimate?: readonly EffectSvgAnimation[];\n\t/** Length of one play (ms). */\n\tduration: number;\n\tloop: boolean;\n\t/** Ms held on the last frame before looping. */\n\thold?: number;\n}\n\n/** What a vector or motion effect is applied to: SVG markup, optionally with the ids of the elements to animate (default: all). */\nexport type EffectVectorTarget = string | { svg: string; nodeIds?: readonly string[] };\n\n/** What a provider can do, so editors can degrade cleanly. */\nexport interface EffectProviderCapabilities {\n\t/** Renders off the main thread. */\n\tsupportsWorker: boolean;\n\t/** Returns `float` pixels for depth 16 and 32 renderers. */\n\tsupportsFloat: boolean;\n\t/** Honours `EffectRenderRequest.mask`; otherwise the editor masks the result itself. */\n\tsupportsMask: boolean;\n\t/** Takes `ImageBitmap`, `OffscreenCanvas` or `VideoFrame` inputs, not only `EffectPixels`. */\n\tsupportsHostImages: boolean;\n\t/** Implements `applyVectorEffect`. */\n\tsupportsVector: boolean;\n\t/** Largest width or height a renderer accepts (px). */\n\tmaxSize?: number;\n}\n\ntype MaybePromise<T> = T | Promise<T>;\n\n/**\n * A source of effects. Implementations live outside this library (for example Flair's\n * `flairEffects()`); `createFakeEffectProvider` is a small reference implementation.\n *\n * Unknown ids reject or throw `UnknownEffectError`, invalid values `EffectControlError`, and an\n * operation the provider cannot do `EffectUnsupportedError`.\n */\nexport interface EffectProvider {\n\t/** Stable id of the provider, saved next to effect ids (e.g. `flair`). */\n\treadonly id: string;\n\treadonly name: string;\n\treadonly version?: string;\n\treadonly capabilities: EffectProviderCapabilities;\n\tlist(options?: EffectSearchOptions): MaybePromise<readonly EffectSummary[]>;\n\t/** Effects matching every word of `query` (in name, id, category, tags or description), best first. */\n\tsearch(query: string, options?: EffectSearchOptions): MaybePromise<readonly EffectSummary[]>;\n\tdescribe(id: string): MaybePromise<EffectDescription>;\n\t/** A pixel renderer. A `vector` effect may refuse with `EffectUnsupportedError` where it cannot lay out SVG (a Worker). */\n\tcreateRenderer(id: string, options: EffectRendererOptions): MaybePromise<EffectRenderer>;\n\t/**\n\t * Vector and motion effects as an animated SVG. Present when `capabilities.supportsVector`. A\n\t * `motion` effect animates the target's elements (`nodeIds`, else each top-level element with an\n\t * id); a `vector` effect brings its own picture and ignores the target. Refuse a target the\n\t * effect cannot use with `EffectUnsupportedError`.\n\t */\n\tapplyVectorEffect?(id: string, target: EffectVectorTarget, values?: EffectValues): MaybePromise<EffectVectorMotion>;\n}\n","/**\n * The agent tools for provider effects, shared by every editor that takes an `EffectProvider`:\n * `search_effects`, `describe_effect`, `list_applied_effects`, `apply_effect`, `update_effect` and\n * `remove_effect`. An editor supplies an `EffectToolHost` (what its targets are and how to change\n * them as one undo step each) and may add tools of its own (`extraTools`).\n *\n * Without a provider the read tools say so (and `list_applied_effects` still lists what a\n * document holds), and the write tools refuse with the reason. On a read-only surface the write\n * tools are hidden.\n */\n\nimport type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport type { JsonSchema } from '../tools/schema.js';\nimport { isEffectError } from './errors.js';\nimport { EFFECT_KINDS, EFFECT_USES, type EffectDescription, type EffectProvider, type EffectSummary, type EffectUse } from './types.js';\n\n/** One effect applied to a target, as the tools report it. */\nexport interface AppliedEffectInfo {\n\t/** Its id on the target: what `update_effect` and `remove_effect` take. */\n\tid: string;\n\teffect: string;\n\tname?: string;\n\tprovider?: string;\n\t/** What it is on the target (e.g. `clip effect`, `effect clip`, `transition`, `smart filter`, `effect layer`). */\n\trole: string;\n\tenabled: boolean;\n\t/** 0–1, or keyframes, where the role has an opacity. */\n\topacity?: unknown;\n\t/** Its saved control values (numbers may be keyframes where the editor allows them). */\n\tvalues: Record<string, unknown>;\n\t/** Its place in the target's stack (0 = applied first), for stacked roles. */\n\tindex?: number;\n}\n\nexport interface EffectTargetEffects {\n\ttarget: string;\n\t/** A label for the target (a clip or layer name). */\n\tname?: string;\n\teffects: AppliedEffectInfo[];\n}\n\nexport interface EffectChangeResult {\n\ttarget: string;\n\t/** What was added, changed or removed. */\n\teffect: AppliedEffectInfo;\n\t/** The target's effects afterwards. */\n\teffects: AppliedEffectInfo[];\n\t/** Other things the change made (a new clip or layer id, ...). */\n\t[extra: string]: unknown;\n}\n\n/** What an editor gives the shared effect tools. */\nexport interface EffectToolHost {\n\t/** Provider id (unique per registry). */\n\tid: string;\n\t/** Always prefix tool names with `<namespace>__`. */\n\tnamespace?: string;\n\t/** Words for the editor's targets, e.g. `{ noun: 'clip', id: 'A clip id from get_video_project (e.g. \"c1\").' }`. */\n\ttarget: { noun: string; id: string };\n\t/** The uses `apply_effect` takes on a target (others go through `extraTools`). */\n\tapplyUses: readonly EffectUse[];\n\t/** Text added to `apply_effect`'s description: where the effect goes and how it is drawn. */\n\tapplyDetails: string;\n\t/** The current provider, or `null`. */\n\tprovider(): EffectProvider | null;\n\t/** Why edits are refused now (read-only), or `null`. */\n\treadOnlyReason(): string | null;\n\t/** Every target with effects, or one target (throws for an unknown id, naming valid ones). */\n\tlist(target?: string): EffectTargetEffects[];\n\tapply(target: string, effect: string, args: { values?: Record<string, unknown>; index?: number; enabled?: boolean; opacity?: number }): Promise<EffectChangeResult>;\n\tupdate(target: string, instance: string, changes: { values?: Record<string, unknown>; index?: number; enabled?: boolean; opacity?: number }): Promise<EffectChangeResult>;\n\tremove(target: string, instance: string): Promise<EffectChangeResult>;\n\t/** Editor-specific effect tools (transitions, effect clips, effect layers, keyframes). */\n\textraTools?: (context: EffectToolContext) => AgentTool[];\n\t/** Lets the tool list follow the provider and read-only changes. */\n\tsubscribe?: (notify: () => void) => () => void;\n}\n\n/** Shared pieces for `extraTools`. */\nexport interface EffectToolContext {\n\t/** The provider, or a thrown error saying none is registered. */\n\trequireProvider(): EffectProvider;\n\t/** The schema of a `values` argument. */\n\tvaluesSchema(what: string, keyframes?: boolean): JsonSchema;\n\t/** The schema of an `effect` argument for effects with one of `uses`. */\n\teffectIdSchema(uses: readonly EffectUse[]): JsonSchema;\n\treadonly annotations: typeof ANNOTATIONS;\n}\n\nconst ANNOTATIONS = {\n\tread: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n\twrite: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },\n\twriteIdempotent: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n\tdestructive: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }\n} as const;\n\nconst NO_PROVIDER = 'No effects provider is registered with this editor, so there are no effects to search, describe or apply. The host app must register one (setEffectsProvider); effects already in the document are kept and shown as placeholders.';\n\nconst object = (properties: Record<string, JsonSchema>, required: string[] = []): JsonSchema & { type: 'object' } => ({\n\ttype: 'object',\n\tproperties,\n\t...(required.length ? { required } : {}),\n\tadditionalProperties: false\n});\n\n/** The catalog fetched once per provider, so tool schemas can list effect ids. */\ninterface Catalog {\n\tprovider: EffectProvider;\n\teffects: readonly EffectSummary[] | null;\n}\n\n/** Most effect ids listed in an `effect` argument's enum; larger catalogs are searched instead. */\nconst ENUM_LIMIT = 60;\n\nconst summaryOut = (e: EffectSummary) => ({\n\tid: e.id,\n\tname: e.name,\n\tkind: e.kind,\n\tcategory: e.category,\n\tuses: e.uses,\n\t...(e.description ? { description: e.description } : {}),\n\ttransparent: e.transparent,\n\tloop: e.loop,\n\t...(e.duration !== undefined ? { duration: e.duration } : {})\n});\n\n/** The agent tools for provider effects in one editor (see the module comment). */\nexport function createEffectToolProvider(host: EffectToolHost): AgentToolProvider {\n\tlet catalog: Catalog | null = null;\n\tconst listeners = new Set<() => void>();\n\tconst notify = () => listeners.forEach((l) => l());\n\n\t/** Fetches the catalog of a new provider in the background; the tool list changes when it lands. */\n\tconst catalogFor = (provider: EffectProvider | null): readonly EffectSummary[] | null => {\n\t\tif (!provider) return null;\n\t\tif (catalog?.provider !== provider) {\n\t\t\tconst entry: Catalog = { provider, effects: null };\n\t\t\tcatalog = entry;\n\t\t\tPromise.resolve()\n\t\t\t\t.then(() => provider.list())\n\t\t\t\t.then((effects) => {\n\t\t\t\t\tif (catalog === entry) {\n\t\t\t\t\t\tentry.effects = effects;\n\t\t\t\t\t\tnotify();\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch(() => {});\n\t\t}\n\t\treturn catalog.effects;\n\t};\n\n\tconst requireProvider = (): EffectProvider => {\n\t\tconst p = host.provider();\n\t\tif (!p) throw new Error(NO_PROVIDER);\n\t\treturn p;\n\t};\n\tconst writable = () => {\n\t\tconst reason = host.readOnlyReason();\n\t\tif (reason) throw new Error(reason);\n\t\treturn requireProvider();\n\t};\n\t/** Turns provider errors into messages a model can act on. */\n\tconst explain = async <T>(run: () => Promise<T>): Promise<T> => {\n\t\ttry {\n\t\t\treturn await run();\n\t\t} catch (error) {\n\t\t\tif (isEffectError(error)) throw new Error(error.message);\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tconst valuesSchema = (what: string, keyframes = false): JsonSchema => ({\n\t\ttype: 'object',\n\t\tdescription: `${what}: control name → value, as listed by describe_effect (its \"controls\" schema: types, ranges, units and defaults). Only the given controls change; colours are \"#rrggbb\".${keyframes ? ' A number control may instead be keyframes { keys: [{ t: ms from the start, v, ease }] }.' : ''} Unknown controls and out-of-range values are refused with the valid ones.`\n\t});\n\tconst effectIdSchema = (uses: readonly EffectUse[]): JsonSchema => {\n\t\tconst effects = catalogFor(host.provider())?.filter((e) => (e.kind !== 'motion' || uses.includes('motion')) && e.uses.some((u) => uses.includes(u)));\n\t\tconst base = `An effect id from search_effects (use ${uses.map((u) => `\"${u}\"`).join(' or ')}).`;\n\t\treturn effects && effects.length && effects.length <= ENUM_LIMIT\n\t\t\t? { type: 'string', enum: effects.map((e) => e.id), description: `${base} One of: ${effects.map((e) => `${e.id} (${e.name})`).join(', ')}.` }\n\t\t\t: { type: 'string', minLength: 1, description: base };\n\t};\n\tconst context: EffectToolContext = { requireProvider: writable, valuesSchema, effectIdSchema, annotations: ANNOTATIONS };\n\n\tconst TARGET = { type: 'string', minLength: 1, description: host.target.id } as const;\n\tconst INSTANCE = { type: 'string', minLength: 1, description: `The effect's id on the ${host.target.noun}, from list_applied_effects or apply_effect.` } as const;\n\tconst INDEX = { type: 'integer', minimum: 0, description: `Place in the ${host.target.noun}'s effect stack: 0 is applied first (default: the end, applied last).` } as const;\n\tconst OPACITY = { type: 'number', minimum: 0, maximum: 1, description: 'How much of the effect shows over its input, 0–1 (default 1).' } as const;\n\tconst ENABLED = { type: 'boolean', description: 'false turns the effect off but keeps it (default true).' } as const;\n\n\tconst readTools = (): AgentTool[] => [\n\t\t{\n\t\t\tname: 'search_effects',\n\t\t\ttitle: 'Search effects',\n\t\t\tdescription: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion), category or use (${EFFECT_USES.join(', ')}). For this editor, apply_effect takes uses ${host.applyUses.join(' or ')}. Returns { provider, effects: [{ id, name, kind, category, uses, description, transparent, loop, duration (ms) }], total }. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,\n\t\t\tinputSchema: object({\n\t\t\t\tquery: { type: 'string', description: 'Words to match (default: every effect).' },\n\t\t\t\tkind: { type: 'string', enum: [...EFFECT_KINDS], description: 'shader (pixels), vector (animated SVG) or motion (animates vector art).' },\n\t\t\t\tcategory: { type: 'string', minLength: 1, description: 'The provider\\'s category, e.g. \"gradient\", \"light\", \"transition\".' },\n\t\t\t\tuse: { type: 'string', enum: [...EFFECT_USES], description: 'What the effect is for: filter changes a picture, transition blends two, generator draws a background, overlay draws over content, title draws a title, motion animates vector art, interactive reacts to input.' },\n\t\t\t\tlimit: { type: 'integer', minimum: 1, maximum: 200, description: 'Most effects to return (default 20).' }\n\t\t\t}),\n\t\t\tannotations: { title: 'Search effects', ...ANNOTATIONS.read },\n\t\t\thandler: async (args) => {\n\t\t\t\tconst p = host.provider();\n\t\t\t\tif (!p) return { available: false, reason: NO_PROVIDER, effects: [], total: 0 };\n\t\t\t\tconst found = await explain(async () => p.search((args.query as string | undefined) ?? '', { kind: args.kind, category: args.category, use: args.use }));\n\t\t\t\tconst limit = (args.limit as number | undefined) ?? 20;\n\t\t\t\treturn { available: true, provider: { id: p.id, name: p.name, capabilities: p.capabilities }, effects: found.slice(0, limit).map(summaryOut), total: found.length };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: 'describe_effect',\n\t\t\ttitle: 'Describe an effect',\n\t\t\tdescription: 'Returns one effect\\'s controls as a strict JSON Schema (each control described, with its type, range and units), its defaults, presets (named sets of values) and inputs, plus its summary. Use the control names and ranges for apply_effect and update_effect values. With no provider registered it returns available: false and the reason.',\n\t\t\tinputSchema: object({ effect: { type: 'string', minLength: 1, description: 'An effect id from search_effects or list_applied_effects.' } }, ['effect']),\n\t\t\tannotations: { title: 'Describe an effect', ...ANNOTATIONS.read },\n\t\t\thandler: async (args) => {\n\t\t\t\tconst p = host.provider();\n\t\t\t\tif (!p) return { available: false, reason: NO_PROVIDER };\n\t\t\t\tconst d: EffectDescription = await explain(async () => p.describe(args.effect as string));\n\t\t\t\treturn { available: true, ...summaryOut(d), controls: d.controls, defaults: d.defaults, presets: d.presets, inputs: d.inputs, appliesHere: (d.kind !== 'motion' || host.applyUses.includes('motion')) && d.uses.some((u) => host.applyUses.includes(u)) };\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: 'list_applied_effects',\n\t\t\ttitle: 'List applied effects',\n\t\t\tdescription: `Lists the effects in the document: for each ${host.target.noun} that has any (or the one given), its effects in order with id, effect id, name, role, enabled, opacity and saved values. available is false for an effect the current provider does not have (or when none is registered): it is kept and drawn as a placeholder, and can still be removed. Works without a provider.`,\n\t\t\tinputSchema: object({ target: { ...TARGET, description: `${host.target.id} Leave out to list every ${host.target.noun} with effects.` } }),\n\t\t\tannotations: { title: 'List applied effects', ...ANNOTATIONS.read },\n\t\t\thandler: async (args) => {\n\t\t\t\tconst p = host.provider();\n\t\t\t\tconst known = new Set((p ? await explain(async () => p.list()) : []).map((e) => e.id));\n\t\t\t\tconst targets = host.list(args.target as string | undefined).map((t) => ({ ...t, effects: t.effects.map((e) => ({ ...e, available: known.has(e.effect) })) }));\n\t\t\t\treturn { provider: p ? { id: p.id, name: p.name } : null, ...(p ? {} : { reason: NO_PROVIDER }), targets };\n\t\t\t}\n\t\t}\n\t];\n\n\tconst writeTools = (): AgentTool[] => [\n\t\t{\n\t\t\tname: 'apply_effect',\n\t\t\ttitle: 'Apply an effect',\n\t\t\tdescription: `Adds a provider effect to a ${host.target.noun}. ${host.applyDetails} values are checked against describe_effect's controls first; nothing changes if any is wrong. One undo step. Returns the effect with its new id (for update_effect and remove_effect) and the ${host.target.noun}'s effects. Refused when no provider is registered.`,\n\t\t\tinputSchema: object(\n\t\t\t\t{\n\t\t\t\t\ttarget: TARGET,\n\t\t\t\t\teffect: effectIdSchema(host.applyUses),\n\t\t\t\t\tvalues: valuesSchema('Control values over the effect\\'s defaults', true),\n\t\t\t\t\tindex: INDEX,\n\t\t\t\t\tenabled: ENABLED,\n\t\t\t\t\topacity: OPACITY\n\t\t\t\t},\n\t\t\t\t['target', 'effect']\n\t\t\t),\n\t\t\tannotations: { title: 'Apply an effect', ...ANNOTATIONS.write },\n\t\t\thandler: async (args) => {\n\t\t\t\twritable();\n\t\t\t\treturn explain(() => host.apply(args.target as string, args.effect as string, { values: args.values, index: args.index, enabled: args.enabled, opacity: args.opacity }));\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: 'update_effect',\n\t\t\ttitle: 'Change an applied effect',\n\t\t\tdescription: `Changes one applied effect on a ${host.target.noun}: its values (merged over the current ones; null resets a control to its default), enabled, opacity or place in the stack. Values are checked against describe_effect's controls first. One undo step. Returns the effect as it is now and the ${host.target.noun}'s effects. Refused when no provider is registered.`,\n\t\t\tinputSchema: object(\n\t\t\t\t{\n\t\t\t\t\ttarget: TARGET,\n\t\t\t\t\teffect: INSTANCE,\n\t\t\t\t\tvalues: { ...valuesSchema('Control values to change', true), description: `${valuesSchema('Control values to change', true).description} null resets a control to its default.` },\n\t\t\t\t\tenabled: ENABLED,\n\t\t\t\t\topacity: OPACITY,\n\t\t\t\t\tindex: { ...INDEX, description: `Move it to this place in the ${host.target.noun}'s stack (0 = applied first).` }\n\t\t\t\t},\n\t\t\t\t['target', 'effect']\n\t\t\t),\n\t\t\tannotations: { title: 'Change an applied effect', ...ANNOTATIONS.writeIdempotent },\n\t\t\thandler: async (args) => {\n\t\t\t\twritable();\n\t\t\t\tif (args.values === undefined && args.enabled === undefined && args.opacity === undefined && args.index === undefined) throw new Error('Give at least one of values, enabled, opacity or index to change.');\n\t\t\t\treturn explain(() => host.update(args.target as string, args.effect as string, { values: args.values, enabled: args.enabled, opacity: args.opacity, index: args.index }));\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: 'remove_effect',\n\t\t\ttitle: 'Remove an applied effect',\n\t\t\tdescription: `Removes one applied effect from a ${host.target.noun} (an effect the provider no longer has included). One undo step. Returns what was removed and the ${host.target.noun}'s remaining effects. Refused when no provider is registered.`,\n\t\t\tinputSchema: object({ target: TARGET, effect: INSTANCE }, ['target', 'effect']),\n\t\t\tannotations: { title: 'Remove an applied effect', ...ANNOTATIONS.destructive },\n\t\t\thandler: async (args) => {\n\t\t\t\twritable();\n\t\t\t\treturn explain(() => host.remove(args.target as string, args.effect as string));\n\t\t\t}\n\t\t}\n\t];\n\n\treturn {\n\t\tid: host.id,\n\t\t...(host.namespace ? { namespace: host.namespace } : {}),\n\t\ttools: () => {\n\t\t\tconst extra = host.extraTools?.(context) ?? [];\n\t\t\tif (host.readOnlyReason()) return [...readTools(), ...extra.filter((t) => t.annotations?.readOnlyHint)];\n\t\t\treturn [...readTools(), ...writeTools(), ...extra];\n\t\t},\n\t\tsubscribe: (l) => {\n\t\t\tlisteners.add(l);\n\t\t\tconst off = host.subscribe?.(l);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(l);\n\t\t\t\toff?.();\n\t\t\t};\n\t\t}\n\t};\n}\n"],"mappings":"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","names":[]}
|
package/dist/effects/index.cjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
|
+
var _chunkW6QTLHZ3cjs = require('../chunk-W6QTLHZ3.cjs');
|
|
3
4
|
|
|
4
5
|
|
|
5
|
-
var _chunkFHYDQOPCcjs = require('../chunk-FHYDQOPC.cjs');
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
var _chunkQDJKUBWEcjs = require('../chunk-QDJKUBWE.cjs');
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
|
|
@@ -23,11 +24,12 @@ var _chunkFHYDQOPCcjs = require('../chunk-FHYDQOPC.cjs');
|
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
var _chunkIOXKXQNWcjs = require('../chunk-IOXKXQNW.cjs');
|
|
27
29
|
require('../chunk-RIWPYSKU.cjs');
|
|
28
30
|
|
|
29
31
|
// src/effects/conformance.ts
|
|
30
|
-
var codeOf = (e) =>
|
|
32
|
+
var codeOf = (e) => _chunkIOXKXQNWcjs.isEffectError.call(void 0, e) ? e.code : e instanceof Error ? `untyped ${e.name}: ${e.message}` : String(e);
|
|
31
33
|
async function rejectsWith(code, run) {
|
|
32
34
|
try {
|
|
33
35
|
await run();
|
|
@@ -41,10 +43,10 @@ function checkSummary(e, issues) {
|
|
|
41
43
|
const at = (message) => issues.push({ effectId: e.id, message });
|
|
42
44
|
if (!e.id || typeof e.id !== "string") at("summary has no id");
|
|
43
45
|
if (!e.name) at("summary has no name");
|
|
44
|
-
if (!
|
|
46
|
+
if (!_chunkQDJKUBWEcjs.EFFECT_KINDS.includes(e.kind)) at(`kind "${e.kind}" is not one of ${_chunkQDJKUBWEcjs.EFFECT_KINDS.join(", ")}`);
|
|
45
47
|
if (!e.category) at("summary has no category");
|
|
46
48
|
if (!Array.isArray(e.uses) || !e.uses.length) at("summary has no uses");
|
|
47
|
-
for (const u of _nullishCoalesce(e.uses, () => ( []))) if (!
|
|
49
|
+
for (const u of _nullishCoalesce(e.uses, () => ( []))) if (!_chunkQDJKUBWEcjs.EFFECT_USES.includes(u)) at(`use "${u}" is not one of ${_chunkQDJKUBWEcjs.EFFECT_USES.join(", ")}`);
|
|
48
50
|
if (typeof e.transparent !== "boolean") at("transparent is not a boolean");
|
|
49
51
|
if (typeof e.loop !== "boolean") at("loop is not a boolean");
|
|
50
52
|
if (e.duration !== void 0 && !(e.duration > 0)) at("duration must be a positive number of ms when present");
|
|
@@ -60,8 +62,8 @@ function checkDescription(id, d, issues) {
|
|
|
60
62
|
if (!(name in (_nullishCoalesce(d.defaults, () => ( {}))))) at(`control "${name}" has no default`);
|
|
61
63
|
}
|
|
62
64
|
for (const name of Object.keys(_nullishCoalesce(d.defaults, () => ( {})))) if (!(name in (_nullishCoalesce(_optionalChain([c, 'optionalAccess', _3 => _3.properties]), () => ( {}))))) at(`default "${name}" is not a control`);
|
|
63
|
-
for (const e of
|
|
64
|
-
for (const p of _nullishCoalesce(d.presets, () => ( []))) for (const e of
|
|
65
|
+
for (const e of _chunkIOXKXQNWcjs.effectValueErrors.call(void 0, d, d.defaults)) at(`default ${e.path}: ${e.message}`);
|
|
66
|
+
for (const p of _nullishCoalesce(d.presets, () => ( []))) for (const e of _chunkIOXKXQNWcjs.effectValueErrors.call(void 0, d, p.values)) at(`preset "${p.name}" ${e.path}: ${e.message}`);
|
|
65
67
|
for (const input2 of _nullishCoalesce(d.inputs, () => ( []))) {
|
|
66
68
|
if (!input2.name || !input2.description) at("every input needs a name and a description");
|
|
67
69
|
if (input2.kind !== "image" && input2.kind !== "audio") at(`input "${input2.name}" has kind "${input2.kind}"`);
|
|
@@ -72,7 +74,7 @@ function checkDescription(id, d, issues) {
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
function grey(size) {
|
|
75
|
-
const p =
|
|
77
|
+
const p = _chunkIOXKXQNWcjs.createEffectPixels.call(void 0, size, size);
|
|
76
78
|
p.data.fill(128);
|
|
77
79
|
return p;
|
|
78
80
|
}
|
|
@@ -88,7 +90,7 @@ async function checkRender(provider, d, size, issues) {
|
|
|
88
90
|
}
|
|
89
91
|
try {
|
|
90
92
|
const out = await renderer.render({ time: 0, inputs });
|
|
91
|
-
if (!
|
|
93
|
+
if (!_chunkIOXKXQNWcjs.isEffectPixels.call(void 0, out)) at("render did not return EffectPixels");
|
|
92
94
|
else if (out.width !== size || out.height !== size || out.data.length !== size * size * 4) at(`render returned ${out.width}\xD7${out.height}, not ${size}\xD7${size}`);
|
|
93
95
|
} catch (e) {
|
|
94
96
|
at(`render failed: ${codeOf(e)}`);
|
|
@@ -295,6 +297,29 @@ var DESCRIPTIONS = [
|
|
|
295
297
|
defaults: { ms: 600, stagger: 0 },
|
|
296
298
|
presets: [{ name: "Cascade", values: { ms: 400, stagger: 120 } }],
|
|
297
299
|
inputs: []
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "fake.badge",
|
|
303
|
+
name: "Badge",
|
|
304
|
+
kind: "vector",
|
|
305
|
+
category: "sticker",
|
|
306
|
+
uses: ["title"],
|
|
307
|
+
description: "A coloured disc that fades in: a vector effect that brings its own picture.",
|
|
308
|
+
tags: ["sticker", "disc"],
|
|
309
|
+
transparent: true,
|
|
310
|
+
loop: false,
|
|
311
|
+
duration: 500,
|
|
312
|
+
controls: {
|
|
313
|
+
type: "object",
|
|
314
|
+
additionalProperties: false,
|
|
315
|
+
properties: {
|
|
316
|
+
color: colour("Disc colour as #rrggbb. Default #f43f5e."),
|
|
317
|
+
ms: { type: "number", minimum: 0, maximum: 1e4, description: "Fade-in time (ms). Default 500." }
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
defaults: { color: "#f43f5e", ms: 500 },
|
|
321
|
+
presets: [],
|
|
322
|
+
inputs: []
|
|
298
323
|
}
|
|
299
324
|
];
|
|
300
325
|
var summaryOf = ({ controls: _c, defaults: _d, presets: _p, inputs: _i, ...summary }) => summary;
|
|
@@ -303,15 +328,15 @@ var smooth = (t) => t * t * (3 - 2 * t);
|
|
|
303
328
|
var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
304
329
|
function input(request, name, id, width, height) {
|
|
305
330
|
const src = _optionalChain([request, 'access', _9 => _9.inputs, 'optionalAccess', _10 => _10[name]]);
|
|
306
|
-
if (!src) throw new (0,
|
|
307
|
-
if (!
|
|
331
|
+
if (!src) throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(`Effect "${id}" needs an input named "${name}".`, id);
|
|
332
|
+
if (!_chunkIOXKXQNWcjs.isEffectPixels.call(void 0, src)) throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(`The fake provider only takes EffectPixels inputs ("${name}").`, id);
|
|
308
333
|
if (src.width !== width || src.height !== height) {
|
|
309
|
-
throw new (0,
|
|
334
|
+
throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(`Input "${name}" is ${src.width}\xD7${src.height}; the renderer is ${width}\xD7${height}.`, id);
|
|
310
335
|
}
|
|
311
336
|
return src;
|
|
312
337
|
}
|
|
313
338
|
function renderPixels(d, v, request, width, height) {
|
|
314
|
-
const out =
|
|
339
|
+
const out = _chunkIOXKXQNWcjs.createEffectPixels.call(void 0, width, height);
|
|
315
340
|
const o = out.data;
|
|
316
341
|
switch (d.id) {
|
|
317
342
|
case "fake.invert": {
|
|
@@ -377,23 +402,23 @@ function renderPixels(d, v, request, width, height) {
|
|
|
377
402
|
break;
|
|
378
403
|
}
|
|
379
404
|
default:
|
|
380
|
-
throw new (0,
|
|
405
|
+
throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(`"${d.id}" is a ${d.kind} effect; use applyVectorEffect.`, d.id);
|
|
381
406
|
}
|
|
382
407
|
return out;
|
|
383
408
|
}
|
|
384
409
|
function toFloat(p) {
|
|
385
410
|
const f = new Float32Array(p.data.length);
|
|
386
411
|
for (let i = 0; i < f.length; i += 4) {
|
|
387
|
-
f[i] =
|
|
388
|
-
f[i + 1] =
|
|
389
|
-
f[i + 2] =
|
|
412
|
+
f[i] = _chunkIOXKXQNWcjs.srgbToLinear.call(void 0, p.data[i] / 255);
|
|
413
|
+
f[i + 1] = _chunkIOXKXQNWcjs.srgbToLinear.call(void 0, p.data[i + 1] / 255);
|
|
414
|
+
f[i + 2] = _chunkIOXKXQNWcjs.srgbToLinear.call(void 0, p.data[i + 2] / 255);
|
|
390
415
|
f[i + 3] = p.data[i + 3] / 255;
|
|
391
416
|
}
|
|
392
|
-
return
|
|
417
|
+
return _chunkIOXKXQNWcjs.encodeEffectFloat.call(void 0, { ...p, float: f });
|
|
393
418
|
}
|
|
394
419
|
function maskResult(out, mask, source, id) {
|
|
395
|
-
if (!
|
|
396
|
-
throw new (0,
|
|
420
|
+
if (!_chunkIOXKXQNWcjs.isEffectPixels.call(void 0, mask) || mask.width !== out.width || mask.height !== out.height) {
|
|
421
|
+
throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)("The mask must be EffectPixels the size of the renderer.", id);
|
|
397
422
|
}
|
|
398
423
|
const o = out.data;
|
|
399
424
|
for (let i = 0; i < o.length; i += 4) {
|
|
@@ -415,7 +440,7 @@ function createFakeEffectProvider(options = {}) {
|
|
|
415
440
|
const byId = new Map(DESCRIPTIONS.map((d) => [d.id, d]));
|
|
416
441
|
const get = (id) => {
|
|
417
442
|
const d = byId.get(id);
|
|
418
|
-
if (!d) throw new (0,
|
|
443
|
+
if (!d) throw new (0, _chunkIOXKXQNWcjs.UnknownEffectError)(id, "the fake provider");
|
|
419
444
|
return d;
|
|
420
445
|
};
|
|
421
446
|
const summaries = DESCRIPTIONS.map(summaryOf);
|
|
@@ -424,16 +449,16 @@ function createFakeEffectProvider(options = {}) {
|
|
|
424
449
|
name: "Fake effects",
|
|
425
450
|
version: "1",
|
|
426
451
|
capabilities,
|
|
427
|
-
list: (o) =>
|
|
428
|
-
search: (q, o) =>
|
|
452
|
+
list: (o) => _chunkIOXKXQNWcjs.searchEffectSummaries.call(void 0, summaries, "", o),
|
|
453
|
+
search: (q, o) => _chunkIOXKXQNWcjs.searchEffectSummaries.call(void 0, summaries, q, o),
|
|
429
454
|
describe: (id) => structuredClone(get(id)),
|
|
430
455
|
createRenderer(id, ro) {
|
|
431
456
|
const d = get(id);
|
|
432
|
-
if (d.kind
|
|
457
|
+
if (d.kind !== "shader") throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(`"${id}" is a ${d.kind} effect; use applyVectorEffect.`, id);
|
|
433
458
|
const { width, height } = ro;
|
|
434
459
|
const depth = _nullishCoalesce(ro.depth, () => ( 8));
|
|
435
460
|
if (!(width > 0 && height > 0) || width > capabilities.maxSize || height > capabilities.maxSize) {
|
|
436
|
-
throw new (0,
|
|
461
|
+
throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(`Renderer size ${width}\xD7${height} is outside 1\u2013${capabilities.maxSize}.`, id);
|
|
437
462
|
}
|
|
438
463
|
let disposed = false;
|
|
439
464
|
return {
|
|
@@ -442,15 +467,15 @@ function createFakeEffectProvider(options = {}) {
|
|
|
442
467
|
height,
|
|
443
468
|
depth,
|
|
444
469
|
async render(request) {
|
|
445
|
-
if (disposed) throw new (0,
|
|
470
|
+
if (disposed) throw new (0, _chunkIOXKXQNWcjs.EffectDisposedError)(id);
|
|
446
471
|
if (!Number.isFinite(request.time) || request.time < 0) {
|
|
447
|
-
throw new (0,
|
|
472
|
+
throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(`time must be a finite number of ms \u2265 0, got ${request.time}.`, id);
|
|
448
473
|
}
|
|
449
|
-
const values =
|
|
474
|
+
const values = _chunkIOXKXQNWcjs.resolveEffectValues.call(void 0, d, request.values);
|
|
450
475
|
_optionalChain([options, 'access', _11 => _11.onRender, 'optionalCall', _12 => _12(id, request)]);
|
|
451
476
|
const out = renderPixels(d, values, request, width, height);
|
|
452
477
|
if (request.mask) {
|
|
453
|
-
if (!capabilities.supportsMask) throw new (0,
|
|
478
|
+
if (!capabilities.supportsMask) throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)("This provider does not take masks.", id);
|
|
454
479
|
const source = d.uses.includes("filter") ? _optionalChain([request, 'access', _13 => _13.inputs, 'optionalAccess', _14 => _14.source]) : void 0;
|
|
455
480
|
maskResult(out, request.mask, source, id);
|
|
456
481
|
}
|
|
@@ -463,8 +488,11 @@ function createFakeEffectProvider(options = {}) {
|
|
|
463
488
|
},
|
|
464
489
|
applyVectorEffect(id, target, values) {
|
|
465
490
|
const d = get(id);
|
|
466
|
-
if (d.kind !== "motion" && d.kind !== "vector") throw new (0,
|
|
467
|
-
const v =
|
|
491
|
+
if (d.kind !== "motion" && d.kind !== "vector") throw new (0, _chunkIOXKXQNWcjs.EffectUnsupportedError)(`"${id}" is a ${d.kind} effect, not a vector one.`, id);
|
|
492
|
+
const v = _chunkIOXKXQNWcjs.resolveEffectValues.call(void 0, d, values);
|
|
493
|
+
if (d.kind === "vector") {
|
|
494
|
+
return { svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"><circle id="badge" cx="50" cy="50" r="40" fill="${v.color}"/></svg>`, animate: [{ id: "badge", ms: v.ms, attrs: { opacity: [0, 1] } }], duration: v.ms, loop: false };
|
|
495
|
+
}
|
|
468
496
|
const svg = typeof target === "string" ? target : target.svg;
|
|
469
497
|
const ids = typeof target === "string" || !target.nodeIds ? [...svg.matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]) : [...target.nodeIds];
|
|
470
498
|
const ms = v.ms;
|
|
@@ -521,7 +549,7 @@ function effectParameterFields(description) {
|
|
|
521
549
|
}
|
|
522
550
|
var effectFieldValue = (field, value) => value === void 0 || value === null || typeof value === "object" && !Array.isArray(value) ? field.default : value;
|
|
523
551
|
function effectSamplePicture(width, height, variant = 0) {
|
|
524
|
-
const p =
|
|
552
|
+
const p = _chunkIOXKXQNWcjs.createEffectPixels.call(void 0, width, height);
|
|
525
553
|
for (let y = 0, i = 0; y < height; y++) {
|
|
526
554
|
for (let x = 0; x < width; x++, i += 4) {
|
|
527
555
|
const u = x / Math.max(1, width - 1);
|
|
@@ -601,5 +629,5 @@ function groupEffectsByCategory(effects) {
|
|
|
601
629
|
|
|
602
630
|
|
|
603
631
|
|
|
604
|
-
exports.EFFECT_ERROR_CODES =
|
|
632
|
+
exports.EFFECT_ERROR_CODES = _chunkIOXKXQNWcjs.EFFECT_ERROR_CODES; exports.EFFECT_KINDS = _chunkQDJKUBWEcjs.EFFECT_KINDS; exports.EFFECT_USES = _chunkQDJKUBWEcjs.EFFECT_USES; exports.EFFECT_USE_LABELS = EFFECT_USE_LABELS; exports.EffectControlError = _chunkIOXKXQNWcjs.EffectControlError; exports.EffectDisposedError = _chunkIOXKXQNWcjs.EffectDisposedError; exports.EffectError = _chunkIOXKXQNWcjs.EffectError; exports.EffectRendererPool = _chunkW6QTLHZ3cjs.EffectRendererPool; exports.EffectUnsupportedError = _chunkIOXKXQNWcjs.EffectUnsupportedError; exports.EffectsSlot = _chunkIOXKXQNWcjs.EffectsSlot; exports.NoEffectsProviderError = _chunkIOXKXQNWcjs.NoEffectsProviderError; exports.UnknownEffectError = _chunkIOXKXQNWcjs.UnknownEffectError; exports.applyEffectMask = _chunkIOXKXQNWcjs.applyEffectMask; exports.checkEffectProvider = checkEffectProvider; exports.createEffectPixels = _chunkIOXKXQNWcjs.createEffectPixels; exports.createEffectToolProvider = _chunkQDJKUBWEcjs.createEffectToolProvider; exports.createFakeEffectProvider = createFakeEffectProvider; exports.effectControlField = effectControlField; exports.effectFieldValue = effectFieldValue; exports.effectParameterFields = effectParameterFields; exports.effectSamplePicture = effectSamplePicture; exports.effectValueErrors = _chunkIOXKXQNWcjs.effectValueErrors; exports.encodeEffectFloat = _chunkIOXKXQNWcjs.encodeEffectFloat; exports.groupEffectsByCategory = groupEffectsByCategory; exports.isEffectError = _chunkIOXKXQNWcjs.isEffectError; exports.isEffectPixels = _chunkIOXKXQNWcjs.isEffectPixels; exports.linearToSrgb = _chunkIOXKXQNWcjs.linearToSrgb; exports.renderEffectThumbnail = renderEffectThumbnail; exports.resolveEffectValues = _chunkIOXKXQNWcjs.resolveEffectValues; exports.searchEffectSummaries = _chunkIOXKXQNWcjs.searchEffectSummaries; exports.srgbToLinear = _chunkIOXKXQNWcjs.srgbToLinear;
|
|
605
633
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/effects/index.cjs","../../src/effects/conformance.ts","../../src/effects/fake.ts","../../src/effects/ui.ts"],"names":["input"],"mappings":"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,MAAA;AAClF,MAAA;AACK,MAAA;AACX,MAAA;AACN,QAAA;AACsF,QAAA;AACzC,QAAA;AACvC,QAAA;AACP,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AF6GqG;AACA;AGtd/B;AAC7D,EAAA;AACI,EAAA;AACD,EAAA;AACF,EAAA;AACF,EAAA;AACC,EAAA;AACK,EAAA;AACd;AAEY;AACoF;AAO+B;AACxB,EAAA;AACvE,EAAA;AACL,EAAA;AAC4B,EAAA;AACyD,IAAA;AACf,IAAA;AAChG,EAAA;AAC6C,EAAA;AACP,IAAA;AACA,IAAA;AACkD,IAAA;AACxF,EAAA;AACiF,EAAA;AAC1D,EAAA;AACoE,IAAA;AACF,IAAA;AACzF,EAAA;AAC+F,EAAA;AAC/D,IAAA;AAC2E,IAAA;AAC3G,EAAA;AACO,EAAA;AACR;AAGuH;AAEzE,EAAA;AAE9C;AAI4D;AAMkC;AACnD,EAAA;AACF,EAAA;AACC,IAAA;AACJ,MAAA;AACC,MAAA;AACoB,MAAA;AACsC,MAAA;AACA,MAAA;AAC/F,IAAA;AACD,EAAA;AACO,EAAA;AACR;AASuM;AACnK,EAAA;AACqB,EAAA;AACH,EAAA;AAC9B,EAAA;AACb,EAAA;AAE8D,IAAA;AAC7B,IAAA;AACR,IAAA;AAChB,IAAA;AACnB,EAAA;AACO,EAAA;AACR;AAEmF;AAC5B;AAE6G;AAC1F,EAAA;AAChB,EAAA;AACrD,EAAA;AACwC,IAAA;AACG,IAAA;AACiC,IAAA;AACkB,IAAA;AAChG,EAAA;AACgB,IAAA;AAClB,EAAA;AACD;AAG6H;AACxF,EAAA;AACX,EAAA;AACO,IAAA;AAChB,IAAA;AACgB,IAAA;AAChC,EAAA;AAC0E,EAAA;AAC3E;AHwbqG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/effects/index.cjs","sourcesContent":[null,"/**\n * `checkEffectProvider`: a conformance check any provider can run in its own tests, so editors\n * can rely on the contract (strict control schemas, valid defaults, typed errors, renderers that\n * return the size they were asked for).\n */\n\nimport { isEffectError } from './errors.js';\nimport { createEffectPixels, effectValueErrors, isEffectPixels } from './helpers.js';\nimport { EFFECT_KINDS, EFFECT_USES, type EffectDescription, type EffectPixels, type EffectProvider, type EffectSummary } from './types.js';\n\nexport interface EffectConformanceIssue {\n\teffectId?: string;\n\tmessage: string;\n}\n\nexport interface EffectConformanceOptions {\n\t/** Effects to check in depth (default: every listed effect). */\n\tids?: readonly string[];\n\t/** Also create renderers and render one small frame of each pixel effect (default true). */\n\trender?: boolean;\n\t/** Renderer size for the render check (default 8). */\n\tsize?: number;\n}\n\nconst codeOf = (e: unknown) => (isEffectError(e) ? e.code : e instanceof Error ? `untyped ${e.name}: ${e.message}` : String(e));\n\nasync function rejectsWith(code: string, run: () => unknown): Promise<string | null> {\n\ttry {\n\t\tawait run();\n\t\treturn `did not fail (expected ${code})`;\n\t} catch (e) {\n\t\tconst got = codeOf(e);\n\t\treturn got === code ? null : `failed with ${got} (expected ${code})`;\n\t}\n}\n\nfunction checkSummary(e: EffectSummary, issues: EffectConformanceIssue[]) {\n\tconst at = (message: string) => issues.push({ effectId: e.id, message });\n\tif (!e.id || typeof e.id !== 'string') at('summary has no id');\n\tif (!e.name) at('summary has no name');\n\tif (!EFFECT_KINDS.includes(e.kind)) at(`kind \"${e.kind}\" is not one of ${EFFECT_KINDS.join(', ')}`);\n\tif (!e.category) at('summary has no category');\n\tif (!Array.isArray(e.uses) || !e.uses.length) at('summary has no uses');\n\tfor (const u of e.uses ?? []) if (!EFFECT_USES.includes(u)) at(`use \"${u}\" is not one of ${EFFECT_USES.join(', ')}`);\n\tif (typeof e.transparent !== 'boolean') at('transparent is not a boolean');\n\tif (typeof e.loop !== 'boolean') at('loop is not a boolean');\n\tif (e.duration !== undefined && !(e.duration > 0)) at('duration must be a positive number of ms when present');\n}\n\nfunction checkDescription(id: string, d: EffectDescription, issues: EffectConformanceIssue[]) {\n\tconst at = (message: string) => issues.push({ effectId: id, message });\n\tif (d.id !== id) at(`describe(\"${id}\") returned id \"${d.id}\"`);\n\tconst c = d.controls;\n\tif (!c || c.type !== 'object') at('controls is not an object schema');\n\tif (c?.additionalProperties !== false) at('controls must set additionalProperties: false');\n\tfor (const [name, schema] of Object.entries(c?.properties ?? {})) {\n\t\tif (!schema.description || !String(schema.description).trim()) at(`control \"${name}\" has no description`);\n\t\tif (!(name in (d.defaults ?? {}))) at(`control \"${name}\" has no default`);\n\t}\n\tfor (const name of Object.keys(d.defaults ?? {})) if (!(name in (c?.properties ?? {}))) at(`default \"${name}\" is not a control`);\n\tfor (const e of effectValueErrors(d, d.defaults)) at(`default ${e.path}: ${e.message}`);\n\tfor (const p of d.presets ?? []) for (const e of effectValueErrors(d, p.values)) at(`preset \"${p.name}\" ${e.path}: ${e.message}`);\n\tfor (const input of d.inputs ?? []) {\n\t\tif (!input.name || !input.description) at('every input needs a name and a description');\n\t\tif (input.kind !== 'image' && input.kind !== 'audio') at(`input \"${input.name}\" has kind \"${input.kind}\"`);\n\t}\n\tif (d.uses.includes('filter') && !d.inputs.some((i) => i.name === 'source')) at('a filter must read an input named \"source\"');\n\tif (d.uses.includes('transition') && !(d.inputs.some((i) => i.name === 'from') && d.inputs.some((i) => i.name === 'to'))) {\n\t\tat('a transition must read inputs named \"from\" and \"to\"');\n\t}\n}\n\nfunction grey(size: number): EffectPixels {\n\tconst p = createEffectPixels(size, size);\n\tp.data.fill(128);\n\treturn p;\n}\n\nasync function checkRender(provider: EffectProvider, d: EffectDescription, size: number, issues: EffectConformanceIssue[]) {\n\tconst at = (message: string) => issues.push({ effectId: d.id, message });\n\tconst inputs = Object.fromEntries(d.inputs.filter((i) => i.kind === 'image').map((i) => [i.name, grey(size)]));\n\tlet renderer;\n\ttry {\n\t\trenderer = await provider.createRenderer(d.id, { width: size, height: size });\n\t} catch (e) {\n\t\t// A vector effect may need the host's help to become pixels (a Worker has no DOM to lay out SVG).\n\t\tif (!(d.kind === 'vector' && codeOf(e) === 'unsupported')) at(`createRenderer failed: ${codeOf(e)}`);\n\t\treturn;\n\t}\n\ttry {\n\t\tconst out = await renderer.render({ time: 0, inputs });\n\t\tif (!isEffectPixels(out)) at('render did not return EffectPixels');\n\t\telse if (out.width !== size || out.height !== size || out.data.length !== size * size * 4) at(`render returned ${out.width}×${out.height}, not ${size}×${size}`);\n\t} catch (e) {\n\t\tat(`render failed: ${codeOf(e)}`);\n\t}\n\tconst bad = await rejectsWith('invalid_values', () => renderer.render({ time: 0, inputs, values: { __not_a_control__: 1 } }));\n\tif (bad) at(`render with an unknown control ${bad}`);\n\trenderer.dispose();\n\tconst after = await rejectsWith('disposed', () => renderer.render({ time: 0, inputs }));\n\tif (after) at(`render after dispose ${after}`);\n}\n\n/** Runs the contract checks against `provider` and returns every issue found (empty when it conforms). */\nexport async function checkEffectProvider(provider: EffectProvider, options: EffectConformanceOptions = {}): Promise<EffectConformanceIssue[]> {\n\tconst issues: EffectConformanceIssue[] = [];\n\tconst top = (message: string) => issues.push({ message });\n\tif (!provider.id) top('provider has no id');\n\tconst caps = provider.capabilities;\n\tfor (const k of ['supportsWorker', 'supportsFloat', 'supportsMask', 'supportsHostImages', 'supportsVector'] as const) {\n\t\tif (typeof caps?.[k] !== 'boolean') top(`capabilities.${k} is not a boolean`);\n\t}\n\tif (caps?.supportsVector && typeof provider.applyVectorEffect !== 'function') top('supportsVector is set but applyVectorEffect is missing');\n\n\tconst list = await provider.list();\n\tconst ids = new Set<string>();\n\tfor (const e of list) {\n\t\tif (ids.has(e.id)) top(`duplicate id \"${e.id}\"`);\n\t\tids.add(e.id);\n\t\tcheckSummary(e, issues);\n\t}\n\tconst all = await provider.search('');\n\tif (all.length !== list.length) top(`search(\"\") returned ${all.length} effects, list() ${list.length}`);\n\tconst firstUse = list[0]?.uses[0];\n\tif (firstUse) {\n\t\tconst filtered = await provider.search('', { use: firstUse });\n\t\tif (filtered.some((e) => !e.uses.includes(firstUse))) top(`search(\"\", { use: \"${firstUse}\" }) returned effects without that use`);\n\t}\n\tconst unknown = await rejectsWith('unknown_effect', () => provider.describe('__no_such_effect__'));\n\tif (unknown) top(`describe of an unknown id ${unknown}`);\n\tconst unknownRenderer = await rejectsWith('unknown_effect', () => provider.createRenderer('__no_such_effect__', { width: 4, height: 4 }));\n\tif (unknownRenderer) top(`createRenderer of an unknown id ${unknownRenderer}`);\n\n\tconst size = options.size ?? 8;\n\tfor (const id of options.ids ?? [...ids]) {\n\t\tlet d: EffectDescription;\n\t\ttry {\n\t\t\td = await provider.describe(id);\n\t\t} catch (e) {\n\t\t\tissues.push({ effectId: id, message: `describe failed: ${codeOf(e)}` });\n\t\t\tcontinue;\n\t\t}\n\t\tcheckDescription(id, d, issues);\n\t\tif (options.render !== false && d.kind !== 'motion') await checkRender(provider, d, size, issues);\n\t\tif ((d.kind === 'vector' || d.kind === 'motion') && caps.supportsVector && provider.applyVectorEffect) {\n\t\t\ttry {\n\t\t\t\tconst m = await provider.applyVectorEffect(id, '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 10 10\"><rect id=\"r\" width=\"10\" height=\"10\"/></svg>');\n\t\t\t\tif (typeof m.svg !== 'string' || !m.svg.includes('<svg')) issues.push({ effectId: id, message: 'applyVectorEffect returned no SVG' });\n\t\t\t\tif (!(m.duration >= 0)) issues.push({ effectId: id, message: 'applyVectorEffect returned no duration' });\n\t\t\t} catch (e) {\n\t\t\t\t// The probe target is a bare <rect>; an effect that needs more (a path to follow, a fill colour) may refuse it.\n\t\t\t\tif (codeOf(e) !== 'unsupported') issues.push({ effectId: id, message: `applyVectorEffect failed: ${codeOf(e)}` });\n\t\t\t}\n\t\t\tconst bad = await rejectsWith('invalid_values', () => provider.applyVectorEffect!(id, '<svg xmlns=\"http://www.w3.org/2000/svg\"/>', { __not_a_control__: 1 }));\n\t\t\tif (bad) issues.push({ effectId: id, message: `applyVectorEffect with an unknown control ${bad}` });\n\t\t}\n\t}\n\treturn issues;\n}\n","/**\n * `createFakeEffectProvider`: a small, deterministic, CPU-only provider implementing the whole\n * contract. Tests and the demo use it; it is also a reference for real providers.\n *\n * Effects (ids prefixed `fake.`):\n * - `invert` (filter), `tint` (filter)\n * - `gradient` (generator, loops)\n * - `bar` (title / overlay: a lower-third bar that slides in, transparent)\n * - `crossfade` (transition)\n * - `fade-in` (motion, vector only)\n */\n\nimport { EffectDisposedError, EffectUnsupportedError, UnknownEffectError } from './errors.js';\nimport { createEffectPixels, encodeEffectFloat, isEffectPixels, resolveEffectValues, searchEffectSummaries, srgbToLinear } from './helpers.js';\nimport type {\n\tEffectDescription,\n\tEffectImageSource,\n\tEffectPixels,\n\tEffectProvider,\n\tEffectProviderCapabilities,\n\tEffectRenderer,\n\tEffectRendererOptions,\n\tEffectRenderRequest,\n\tEffectSummary,\n\tEffectValues,\n\tEffectVectorMotion,\n\tEffectVectorTarget\n} from './types.js';\n\nconst HEX = '^#[0-9a-fA-F]{6}$';\nconst colour = (description: string) => ({ type: 'string' as const, pattern: HEX, description });\nconst amount = (description: string) => ({ type: 'number' as const, minimum: 0, maximum: 1, description });\n\nconst DESCRIPTIONS: readonly EffectDescription[] = [\n\t{\n\t\tid: 'fake.invert',\n\t\tname: 'Invert',\n\t\tkind: 'shader',\n\t\tcategory: 'image',\n\t\tuses: ['filter'],\n\t\tdescription: 'Inverts the colours of the source.',\n\t\ttags: ['negative', 'colour'],\n\t\ttransparent: false,\n\t\tloop: false,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: { amount: amount('How far to invert, 0 (unchanged) to 1 (fully inverted). Default 1.') }\n\t\t},\n\t\tdefaults: { amount: 1 },\n\t\tpresets: [{ name: 'Half', values: { amount: 0.5 } }],\n\t\tinputs: [{ name: 'source', kind: 'image', description: 'The picture to invert.', required: true }]\n\t},\n\t{\n\t\tid: 'fake.tint',\n\t\tname: 'Tint',\n\t\tkind: 'shader',\n\t\tcategory: 'image',\n\t\tuses: ['filter'],\n\t\tdescription: 'Mixes the source towards a colour.',\n\t\ttags: ['colour', 'wash'],\n\t\ttransparent: false,\n\t\tloop: false,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tcolor: colour('Tint colour as #rrggbb. Default #ff8800.'),\n\t\t\t\tamount: amount('How far to mix towards the colour, 0–1. Default 0.5.')\n\t\t\t}\n\t\t},\n\t\tdefaults: { color: '#ff8800', amount: 0.5 },\n\t\tpresets: [\n\t\t\t{ name: 'Sepia', values: { color: '#704214', amount: 0.4 } },\n\t\t\t{ name: 'Cool', values: { color: '#2060ff', amount: 0.25 } }\n\t\t],\n\t\tinputs: [{ name: 'source', kind: 'image', description: 'The picture to tint.', required: true }]\n\t},\n\t{\n\t\tid: 'fake.gradient',\n\t\tname: 'Gradient',\n\t\tkind: 'shader',\n\t\tcategory: 'gradient',\n\t\tuses: ['generator'],\n\t\tdescription: 'A linear gradient between two colours that can turn over time.',\n\t\ttags: ['background'],\n\t\ttransparent: false,\n\t\tloop: true,\n\t\tduration: 4000,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tfrom: colour('Start colour as #rrggbb. Default #1e3a8a.'),\n\t\t\t\tto: colour('End colour as #rrggbb. Default #f472b6.'),\n\t\t\t\tangle: { type: 'number', minimum: 0, maximum: 360, description: 'Direction in degrees, 0 = left to right, 90 = top to bottom. Default 90.' },\n\t\t\t\tspin: { type: 'number', minimum: -360, maximum: 360, description: 'Turn per loop (4 s) in degrees. Default 0.' }\n\t\t\t}\n\t\t},\n\t\tdefaults: { from: '#1e3a8a', to: '#f472b6', angle: 90, spin: 0 },\n\t\tpresets: [{ name: 'Sunset', values: { from: '#f97316', to: '#7c3aed' } }],\n\t\tinputs: []\n\t},\n\t{\n\t\tid: 'fake.bar',\n\t\tname: 'Lower-third bar',\n\t\tkind: 'shader',\n\t\tcategory: 'broadcast',\n\t\tuses: ['title', 'overlay'],\n\t\tdescription: 'A coloured bar that slides in from the left in the lower third. Transparent elsewhere.',\n\t\ttags: ['lower third', 'title'],\n\t\ttransparent: true,\n\t\tloop: false,\n\t\tduration: 2000,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tcolor: colour('Bar colour as #rrggbb. Default #0ea5e9.'),\n\t\t\t\theight: { type: 'number', minimum: 0.02, maximum: 0.5, description: 'Bar height as a fraction of the picture height. Default 0.12.' },\n\t\t\t\tslide: { type: 'number', minimum: 0, maximum: 5000, description: 'Slide-in time (ms). Default 500.' }\n\t\t\t}\n\t\t},\n\t\tdefaults: { color: '#0ea5e9', height: 0.12, slide: 500 },\n\t\tpresets: [],\n\t\tinputs: []\n\t},\n\t{\n\t\tid: 'fake.crossfade',\n\t\tname: 'Crossfade',\n\t\tkind: 'shader',\n\t\tcategory: 'transition',\n\t\tuses: ['transition'],\n\t\tdescription: 'Dissolves from one picture to the next.',\n\t\ttags: ['dissolve', 'mix'],\n\t\ttransparent: false,\n\t\tloop: false,\n\t\tduration: 1000,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: { curve: { type: 'string', enum: ['linear', 'smooth'], description: 'Mix curve: linear, or smooth (ease in and out). Default smooth.' } }\n\t\t},\n\t\tdefaults: { curve: 'smooth' },\n\t\tpresets: [],\n\t\tinputs: [\n\t\t\t{ name: 'from', kind: 'image', description: 'The outgoing picture.', required: true },\n\t\t\t{ name: 'to', kind: 'image', description: 'The incoming picture.', required: true }\n\t\t]\n\t},\n\t{\n\t\tid: 'fake.fade-in',\n\t\tname: 'Fade in',\n\t\tkind: 'motion',\n\t\tcategory: 'morph',\n\t\tuses: ['motion'],\n\t\tdescription: 'Fades vector elements in from transparent, one after another.',\n\t\ttags: ['appear', 'entrance'],\n\t\ttransparent: true,\n\t\tloop: false,\n\t\tduration: 600,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tms: { type: 'number', minimum: 0, maximum: 10000, description: 'Fade time per element (ms). Default 600.' },\n\t\t\t\tstagger: { type: 'number', minimum: 0, maximum: 5000, description: 'Delay between elements (ms). Default 0.' }\n\t\t\t}\n\t\t},\n\t\tdefaults: { ms: 600, stagger: 0 },\n\t\tpresets: [{ name: 'Cascade', values: { ms: 400, stagger: 120 } }],\n\t\tinputs: []\n\t}\n];\n\nconst summaryOf = ({ controls: _c, defaults: _d, presets: _p, inputs: _i, ...summary }: EffectDescription): EffectSummary => summary;\n\nconst hexToRgb = (hex: string): [number, number, number] => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)) as [number, number, number];\nconst smooth = (t: number) => t * t * (3 - 2 * t);\nconst clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);\n\nfunction input(request: EffectRenderRequest, name: string, id: string, width: number, height: number): EffectPixels {\n\tconst src: EffectImageSource | undefined = request.inputs?.[name];\n\tif (!src) throw new EffectUnsupportedError(`Effect \"${id}\" needs an input named \"${name}\".`, id);\n\tif (!isEffectPixels(src)) throw new EffectUnsupportedError(`The fake provider only takes EffectPixels inputs (\"${name}\").`, id);\n\tif (src.width !== width || src.height !== height) {\n\t\tthrow new EffectUnsupportedError(`Input \"${name}\" is ${src.width}×${src.height}; the renderer is ${width}×${height}.`, id);\n\t}\n\treturn src;\n}\n\n/** Per-pixel colour work in 8-bit sRGB; float output (when asked) is derived from it. */\nfunction renderPixels(d: EffectDescription, v: Record<string, EffectValues[string]>, request: EffectRenderRequest, width: number, height: number): EffectPixels {\n\tconst out = createEffectPixels(width, height);\n\tconst o = out.data;\n\tswitch (d.id) {\n\t\tcase 'fake.invert': {\n\t\t\tconst s = input(request, 'source', d.id, width, height).data;\n\t\t\tconst k = v.amount as number;\n\t\t\tfor (let i = 0; i < o.length; i += 4) {\n\t\t\t\tfor (let c = 0; c < 3; c++) o[i + c] = s[i + c] + (255 - 2 * s[i + c]) * k;\n\t\t\t\to[i + 3] = s[i + 3];\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'fake.tint': {\n\t\t\tconst s = input(request, 'source', d.id, width, height).data;\n\t\t\tconst rgb = hexToRgb(v.color as string);\n\t\t\tconst k = v.amount as number;\n\t\t\tfor (let i = 0; i < o.length; i += 4) {\n\t\t\t\tfor (let c = 0; c < 3; c++) o[i + c] = s[i + c] + (rgb[c] - s[i + c]) * k;\n\t\t\t\to[i + 3] = s[i + 3];\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'fake.gradient': {\n\t\t\tconst a = hexToRgb(v.from as string);\n\t\t\tconst b = hexToRgb(v.to as string);\n\t\t\tconst turn = ((request.time % (d.duration ?? 4000)) / (d.duration ?? 4000)) * (v.spin as number);\n\t\t\tconst rad = (((v.angle as number) + turn) * Math.PI) / 180;\n\t\t\tconst dx = Math.cos(rad);\n\t\t\tconst dy = Math.sin(rad);\n\t\t\tconst half = (Math.abs(dx) * width + Math.abs(dy) * height) / 2 || 1;\n\t\t\tfor (let y = 0, i = 0; y < height; y++) {\n\t\t\t\tfor (let x = 0; x < width; x++, i += 4) {\n\t\t\t\t\tconst t = clamp01(((x + 0.5 - width / 2) * dx + (y + 0.5 - height / 2) * dy) / (2 * half) + 0.5);\n\t\t\t\t\tfor (let c = 0; c < 3; c++) o[i + c] = a[c] + (b[c] - a[c]) * t;\n\t\t\t\t\to[i + 3] = 255;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'fake.bar': {\n\t\t\tconst rgb = hexToRgb(v.color as string);\n\t\t\tconst slide = v.slide as number;\n\t\t\tconst shown = slide > 0 ? smooth(clamp01(request.time / slide)) : 1;\n\t\t\tconst top = Math.round(height * (0.75 - (v.height as number) / 2));\n\t\t\tconst bottom = Math.round(height * (0.75 + (v.height as number) / 2));\n\t\t\tconst right = Math.round(width * 0.7 * shown);\n\t\t\tfor (let y = Math.max(0, top); y < Math.min(height, bottom); y++) {\n\t\t\t\tfor (let x = 0; x < right; x++) {\n\t\t\t\t\tconst i = (y * width + x) * 4;\n\t\t\t\t\to[i] = rgb[0];\n\t\t\t\t\to[i + 1] = rgb[1];\n\t\t\t\t\to[i + 2] = rgb[2];\n\t\t\t\t\to[i + 3] = 255;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'fake.crossfade': {\n\t\t\tconst from = input(request, 'from', d.id, width, height).data;\n\t\t\tconst to = input(request, 'to', d.id, width, height).data;\n\t\t\tconst p = clamp01(request.progress ?? request.time / (d.duration ?? 1000));\n\t\t\tconst k = v.curve === 'smooth' ? smooth(p) : p;\n\t\t\tfor (let i = 0; i < o.length; i++) o[i] = from[i] + (to[i] - from[i]) * k;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tthrow new EffectUnsupportedError(`\"${d.id}\" is a ${d.kind} effect; use applyVectorEffect.`, d.id);\n\t}\n\treturn out;\n}\n\nfunction toFloat(p: EffectPixels): EffectPixels {\n\tconst f = new Float32Array(p.data.length);\n\tfor (let i = 0; i < f.length; i += 4) {\n\t\tf[i] = srgbToLinear(p.data[i] / 255);\n\t\tf[i + 1] = srgbToLinear(p.data[i + 1] / 255);\n\t\tf[i + 2] = srgbToLinear(p.data[i + 2] / 255);\n\t\tf[i + 3] = p.data[i + 3] / 255;\n\t}\n\treturn encodeEffectFloat({ ...p, float: f });\n}\n\nfunction maskResult(out: EffectPixels, mask: EffectImageSource, source: EffectPixels | undefined, id: string): void {\n\tif (!isEffectPixels(mask) || mask.width !== out.width || mask.height !== out.height) {\n\t\tthrow new EffectUnsupportedError('The mask must be EffectPixels the size of the renderer.', id);\n\t}\n\tconst o = out.data;\n\tfor (let i = 0; i < o.length; i += 4) {\n\t\tconst k = mask.data[i + 3] / 255;\n\t\tif (source) for (let c = 0; c < 4; c++) o[i + c] = source.data[i + c] + (o[i + c] - source.data[i + c]) * k;\n\t\telse o[i + 3] *= k;\n\t}\n}\n\nexport interface FakeEffectProviderOptions {\n\t/** Overrides of the capability flags, to test how editors degrade. */\n\tcapabilities?: Partial<EffectProviderCapabilities>;\n\t/** Called on every render (tests count frames with it). */\n\tonRender?: (effectId: string, request: EffectRenderRequest) => void;\n}\n\n/** A deterministic CPU effects provider with six effects (see the module comment). */\nexport function createFakeEffectProvider(options: FakeEffectProviderOptions = {}): EffectProvider {\n\tconst capabilities: EffectProviderCapabilities = {\n\t\tsupportsWorker: false,\n\t\tsupportsFloat: true,\n\t\tsupportsMask: true,\n\t\tsupportsHostImages: false,\n\t\tsupportsVector: true,\n\t\tmaxSize: 8192,\n\t\t...options.capabilities\n\t};\n\tconst byId = new Map(DESCRIPTIONS.map((d) => [d.id, d]));\n\tconst get = (id: string) => {\n\t\tconst d = byId.get(id);\n\t\tif (!d) throw new UnknownEffectError(id, 'the fake provider');\n\t\treturn d;\n\t};\n\tconst summaries = DESCRIPTIONS.map(summaryOf);\n\n\tconst provider: EffectProvider = {\n\t\tid: 'fake',\n\t\tname: 'Fake effects',\n\t\tversion: '1',\n\t\tcapabilities,\n\t\tlist: (o) => searchEffectSummaries(summaries, '', o),\n\t\tsearch: (q, o) => searchEffectSummaries(summaries, q, o),\n\t\tdescribe: (id) => structuredClone(get(id)),\n\t\tcreateRenderer(id: string, ro: EffectRendererOptions): EffectRenderer {\n\t\t\tconst d = get(id);\n\t\t\tif (d.kind === 'motion') throw new EffectUnsupportedError(`\"${id}\" is a motion effect; use applyVectorEffect.`, id);\n\t\t\tconst { width, height } = ro;\n\t\t\tconst depth = ro.depth ?? 8;\n\t\t\tif (!(width > 0 && height > 0) || width > capabilities.maxSize! || height > capabilities.maxSize!) {\n\t\t\t\tthrow new EffectUnsupportedError(`Renderer size ${width}×${height} is outside 1–${capabilities.maxSize}.`, id);\n\t\t\t}\n\t\t\tlet disposed = false;\n\t\t\treturn {\n\t\t\t\teffectId: id,\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tdepth,\n\t\t\t\tasync render(request) {\n\t\t\t\t\tif (disposed) throw new EffectDisposedError(id);\n\t\t\t\t\tif (!Number.isFinite(request.time) || request.time < 0) {\n\t\t\t\t\t\tthrow new EffectUnsupportedError(`time must be a finite number of ms ≥ 0, got ${request.time}.`, id);\n\t\t\t\t\t}\n\t\t\t\t\tconst values = resolveEffectValues(d, request.values);\n\t\t\t\t\toptions.onRender?.(id, request);\n\t\t\t\t\tconst out = renderPixels(d, values, request, width, height);\n\t\t\t\t\tif (request.mask) {\n\t\t\t\t\t\tif (!capabilities.supportsMask) throw new EffectUnsupportedError('This provider does not take masks.', id);\n\t\t\t\t\t\tconst source = d.uses.includes('filter') ? (request.inputs?.source as EffectPixels) : undefined;\n\t\t\t\t\t\tmaskResult(out, request.mask, source, id);\n\t\t\t\t\t}\n\t\t\t\t\treturn depth > 8 && capabilities.supportsFloat ? toFloat(out) : out;\n\t\t\t\t},\n\t\t\t\tdispose() {\n\t\t\t\t\tdisposed = true;\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t\tapplyVectorEffect(id: string, target: EffectVectorTarget, values?: EffectValues): EffectVectorMotion {\n\t\t\tconst d = get(id);\n\t\t\tif (d.kind !== 'motion' && d.kind !== 'vector') throw new EffectUnsupportedError(`\"${id}\" is a ${d.kind} effect, not a vector one.`, id);\n\t\t\tconst v = resolveEffectValues(d, values);\n\t\t\tconst svg = typeof target === 'string' ? target : target.svg;\n\t\t\tconst ids = typeof target === 'string' || !target.nodeIds ? [...svg.matchAll(/\\sid=\"([^\"]+)\"/g)].map((m) => m[1]) : [...target.nodeIds];\n\t\t\tconst ms = v.ms as number;\n\t\t\tconst stagger = v.stagger as number;\n\t\t\treturn {\n\t\t\t\tsvg,\n\t\t\t\tanimate: ids.map((elementId, i) => ({ id: elementId, delay: i * stagger, ms, attrs: { opacity: [0, 1] as const } })),\n\t\t\t\tduration: ms + Math.max(0, ids.length - 1) * stagger,\n\t\t\t\tloop: false\n\t\t\t};\n\t\t}\n\t};\n\treturn provider;\n}\n","/**\n * Framework-free helpers for effects UIs (the React and Svelte effects browser and parameter\n * panel): control schemas as parameter fields, preview thumbnails from a provider's renderer,\n * and labels.\n */\n\nimport type { JsonSchema } from '../tools/schema.js';\nimport type { ParameterField, ParameterValue } from '../parameters/types.js';\nimport { createEffectPixels } from './helpers.js';\nimport type { EffectDescription, EffectPixels, EffectProvider, EffectSummary, EffectUse, EffectValue } from './types.js';\n\n/** Names for the uses, for filters and badges. */\nexport const EFFECT_USE_LABELS: Readonly<Record<EffectUse, string>> = {\n\tfilter: 'Filter',\n\ttransition: 'Transition',\n\tgenerator: 'Generator',\n\toverlay: 'Overlay',\n\ttitle: 'Title',\n\tmotion: 'Motion',\n\tinteractive: 'Interactive'\n};\n\nconst HEX = /^#[0-9a-f]{6}([0-9a-f]{2})?$/i;\nconst typeOf = (s: JsonSchema) => (Array.isArray(s.type) ? s.type.find((t) => t !== 'null') : s.type);\n\n/**\n * One control as a parameter field: numbers and integers (sliders when bounded), switches,\n * choices (`enum`), colours (strings whose default is `#rrggbb`), vectors (2–4 numbers) and text.\n * `null` for a control no field can edit (objects, long lists).\n */\nexport function effectControlField(name: string, schema: JsonSchema, fallback: EffectValue | undefined): ParameterField | null {\n\tconst base = { id: name, ...(schema.title ? { label: schema.title } : {}), ...(schema.description ? { description: schema.description } : {}) };\n\tconst def = (schema.default ?? fallback) as EffectValue | undefined;\n\tconst type = typeOf(schema);\n\tif (Array.isArray(schema.enum) && schema.enum.length) {\n\t\tconst options = schema.enum.filter((v): v is string | number => typeof v === 'string' || typeof v === 'number');\n\t\treturn { ...base, type: 'choice', options, default: (typeof def === 'string' || typeof def === 'number' ? def : options[0]) as string | number, control: options.length <= 4 ? 'segmented' : 'select' };\n\t}\n\tif (type === 'number' || type === 'integer') {\n\t\tconst min = schema.minimum ?? schema.exclusiveMinimum;\n\t\tconst max = schema.maximum ?? schema.exclusiveMaximum;\n\t\treturn { ...base, type: type === 'integer' ? 'int' : 'number', default: typeof def === 'number' ? def : (min ?? 0), ...(min !== undefined ? { min } : {}), ...(max !== undefined ? { max } : {}), ...(schema.multipleOf ? { step: schema.multipleOf } : {}) };\n\t}\n\tif (type === 'boolean') return { ...base, type: 'boolean', default: def === true };\n\tif (type === 'string') {\n\t\tif (typeof def === 'string' && HEX.test(def)) return { ...base, type: 'color', alpha: def.length === 9, default: def };\n\t\treturn { ...base, type: 'text', default: typeof def === 'string' ? def : '', ...(schema.maxLength ? { maxLength: schema.maxLength } : {}) };\n\t}\n\tif (type === 'array' && Array.isArray(def) && def.length >= 2 && def.length <= 4 && def.every((n) => typeof n === 'number')) {\n\t\tconst items = schema.items ?? {};\n\t\treturn { ...base, type: 'vector', size: def.length as 2 | 3 | 4, default: [...def], ...(items.minimum !== undefined ? { min: items.minimum } : {}), ...(items.maximum !== undefined ? { max: items.maximum } : {}) };\n\t}\n\treturn null;\n}\n\n/** An effect's controls as parameter fields, in schema order (controls no field can edit are left out). */\nexport function effectParameterFields(description: Pick<EffectDescription, 'controls' | 'defaults'>): ParameterField[] {\n\treturn Object.entries(description.controls.properties)\n\t\t.map(([name, schema]) => effectControlField(name, schema, description.defaults[name]))\n\t\t.filter((f): f is ParameterField => !!f);\n}\n\n/** A saved value as a field shows it (`ParameterValue`), or the field's default. */\nexport const effectFieldValue = (field: ParameterField, value: unknown): ParameterValue =>\n\tvalue === undefined || value === null || (typeof value === 'object' && !Array.isArray(value)) ? field.default : (value as ParameterValue);\n\n/**\n * A colourful, deterministic picture for previews of filters and transitions (`variant` 0 and 1\n * differ, for a transition's two sides).\n */\nexport function effectSamplePicture(width: number, height: number, variant = 0): EffectPixels {\n\tconst p = createEffectPixels(width, height);\n\tfor (let y = 0, i = 0; y < height; y++) {\n\t\tfor (let x = 0; x < width; x++, i += 4) {\n\t\t\tconst u = x / Math.max(1, width - 1);\n\t\t\tconst v = y / Math.max(1, height - 1);\n\t\t\tconst check = (Math.floor(u * 4) + Math.floor(v * 4)) % 2;\n\t\t\tif (variant === 0) p.data.set([Math.round(40 + 200 * u), Math.round(80 + 120 * v), Math.round(200 - 120 * u) + check * 30, 255], i);\n\t\t\telse p.data.set([Math.round(230 - 60 * v), Math.round(190 - 100 * u), 60 + check * 40, 255], i);\n\t\t}\n\t}\n\treturn p;\n}\n\n/**\n * A small preview of an effect from the provider's own renderer: filters run on a sample picture,\n * transitions go half way between two, generators and overlays draw at `time` (default half\n * their length, or 1 s). Thumbnails render one at a time (each renderer is disposed before the\n * next is made) and are kept per provider. Rejects like the provider does (motion effects have\n * no pixels).\n */\nexport function renderEffectThumbnail(provider: EffectProvider, effect: EffectSummary | EffectDescription, size: { width: number; height: number } = { width: 96, height: 54 }): Promise<EffectPixels> {\n\tlet cache = thumbnails.get(provider);\n\tif (!cache) thumbnails.set(provider, (cache = new Map()));\n\tconst key = `${effect.id}@${size.width}x${size.height}`;\n\tlet out = cache.get(key);\n\tif (!out) {\n\t\t// One at a time: a browser full of previews would otherwise open a graphics context each.\n\t\tout = thumbnailQueue.then(() => renderThumbnail(provider, effect, size));\n\t\tthumbnailQueue = out.catch(() => undefined);\n\t\tout.catch(() => cache!.delete(key));\n\t\tcache.set(key, out);\n\t}\n\treturn out;\n}\n\nconst thumbnails = new WeakMap<EffectProvider, Map<string, Promise<EffectPixels>>>();\nlet thumbnailQueue: Promise<unknown> = Promise.resolve();\n\nasync function renderThumbnail(provider: EffectProvider, effect: EffectSummary | EffectDescription, size: { width: number; height: number }): Promise<EffectPixels> {\n\tconst d = 'inputs' in effect ? effect : await provider.describe(effect.id);\n\tconst renderer = await provider.createRenderer(d.id, size);\n\ttry {\n\t\tconst time = d.duration ? d.duration / 2 : 1000;\n\t\tconst inputs: Record<string, EffectPixels> = {};\n\t\tfor (const input of d.inputs) if (input.kind === 'image') inputs[input.name] = effectSamplePicture(size.width, size.height, input.name === 'to' ? 1 : 0);\n\t\treturn await renderer.render({ time, inputs, ...(d.uses.includes('transition') ? { progress: 0.5 } : {}) });\n\t} finally {\n\t\trenderer.dispose();\n\t}\n}\n\n/** Effects grouped by category, categories in first-seen order. */\nexport function groupEffectsByCategory<T extends EffectSummary>(effects: readonly T[]): { category: string; effects: T[] }[] {\n\tconst groups = new Map<string, T[]>();\n\tfor (const e of effects) {\n\t\tconst g = groups.get(e.category);\n\t\tif (g) g.push(e);\n\t\telse groups.set(e.category, [e]);\n\t}\n\treturn [...groups].map(([category, list]) => ({ category, effects: list }));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/effects/index.cjs","../../src/effects/conformance.ts","../../src/effects/fake.ts","../../src/effects/ui.ts"],"names":["input"],"mappings":"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","file":"/home/runner/work/editor/editor/packages/core/dist/effects/index.cjs","sourcesContent":[null,"/**\n * `checkEffectProvider`: a conformance check any provider can run in its own tests, so editors\n * can rely on the contract (strict control schemas, valid defaults, typed errors, renderers that\n * return the size they were asked for).\n */\n\nimport { isEffectError } from './errors.js';\nimport { createEffectPixels, effectValueErrors, isEffectPixels } from './helpers.js';\nimport { EFFECT_KINDS, EFFECT_USES, type EffectDescription, type EffectPixels, type EffectProvider, type EffectSummary } from './types.js';\n\nexport interface EffectConformanceIssue {\n\teffectId?: string;\n\tmessage: string;\n}\n\nexport interface EffectConformanceOptions {\n\t/** Effects to check in depth (default: every listed effect). */\n\tids?: readonly string[];\n\t/** Also create renderers and render one small frame of each pixel effect (default true). */\n\trender?: boolean;\n\t/** Renderer size for the render check (default 8). */\n\tsize?: number;\n}\n\nconst codeOf = (e: unknown) => (isEffectError(e) ? e.code : e instanceof Error ? `untyped ${e.name}: ${e.message}` : String(e));\n\nasync function rejectsWith(code: string, run: () => unknown): Promise<string | null> {\n\ttry {\n\t\tawait run();\n\t\treturn `did not fail (expected ${code})`;\n\t} catch (e) {\n\t\tconst got = codeOf(e);\n\t\treturn got === code ? null : `failed with ${got} (expected ${code})`;\n\t}\n}\n\nfunction checkSummary(e: EffectSummary, issues: EffectConformanceIssue[]) {\n\tconst at = (message: string) => issues.push({ effectId: e.id, message });\n\tif (!e.id || typeof e.id !== 'string') at('summary has no id');\n\tif (!e.name) at('summary has no name');\n\tif (!EFFECT_KINDS.includes(e.kind)) at(`kind \"${e.kind}\" is not one of ${EFFECT_KINDS.join(', ')}`);\n\tif (!e.category) at('summary has no category');\n\tif (!Array.isArray(e.uses) || !e.uses.length) at('summary has no uses');\n\tfor (const u of e.uses ?? []) if (!EFFECT_USES.includes(u)) at(`use \"${u}\" is not one of ${EFFECT_USES.join(', ')}`);\n\tif (typeof e.transparent !== 'boolean') at('transparent is not a boolean');\n\tif (typeof e.loop !== 'boolean') at('loop is not a boolean');\n\tif (e.duration !== undefined && !(e.duration > 0)) at('duration must be a positive number of ms when present');\n}\n\nfunction checkDescription(id: string, d: EffectDescription, issues: EffectConformanceIssue[]) {\n\tconst at = (message: string) => issues.push({ effectId: id, message });\n\tif (d.id !== id) at(`describe(\"${id}\") returned id \"${d.id}\"`);\n\tconst c = d.controls;\n\tif (!c || c.type !== 'object') at('controls is not an object schema');\n\tif (c?.additionalProperties !== false) at('controls must set additionalProperties: false');\n\tfor (const [name, schema] of Object.entries(c?.properties ?? {})) {\n\t\tif (!schema.description || !String(schema.description).trim()) at(`control \"${name}\" has no description`);\n\t\tif (!(name in (d.defaults ?? {}))) at(`control \"${name}\" has no default`);\n\t}\n\tfor (const name of Object.keys(d.defaults ?? {})) if (!(name in (c?.properties ?? {}))) at(`default \"${name}\" is not a control`);\n\tfor (const e of effectValueErrors(d, d.defaults)) at(`default ${e.path}: ${e.message}`);\n\tfor (const p of d.presets ?? []) for (const e of effectValueErrors(d, p.values)) at(`preset \"${p.name}\" ${e.path}: ${e.message}`);\n\tfor (const input of d.inputs ?? []) {\n\t\tif (!input.name || !input.description) at('every input needs a name and a description');\n\t\tif (input.kind !== 'image' && input.kind !== 'audio') at(`input \"${input.name}\" has kind \"${input.kind}\"`);\n\t}\n\tif (d.uses.includes('filter') && !d.inputs.some((i) => i.name === 'source')) at('a filter must read an input named \"source\"');\n\tif (d.uses.includes('transition') && !(d.inputs.some((i) => i.name === 'from') && d.inputs.some((i) => i.name === 'to'))) {\n\t\tat('a transition must read inputs named \"from\" and \"to\"');\n\t}\n}\n\nfunction grey(size: number): EffectPixels {\n\tconst p = createEffectPixels(size, size);\n\tp.data.fill(128);\n\treturn p;\n}\n\nasync function checkRender(provider: EffectProvider, d: EffectDescription, size: number, issues: EffectConformanceIssue[]) {\n\tconst at = (message: string) => issues.push({ effectId: d.id, message });\n\tconst inputs = Object.fromEntries(d.inputs.filter((i) => i.kind === 'image').map((i) => [i.name, grey(size)]));\n\tlet renderer;\n\ttry {\n\t\trenderer = await provider.createRenderer(d.id, { width: size, height: size });\n\t} catch (e) {\n\t\t// A vector effect may need the host's help to become pixels (a Worker has no DOM to lay out SVG).\n\t\tif (!(d.kind === 'vector' && codeOf(e) === 'unsupported')) at(`createRenderer failed: ${codeOf(e)}`);\n\t\treturn;\n\t}\n\ttry {\n\t\tconst out = await renderer.render({ time: 0, inputs });\n\t\tif (!isEffectPixels(out)) at('render did not return EffectPixels');\n\t\telse if (out.width !== size || out.height !== size || out.data.length !== size * size * 4) at(`render returned ${out.width}×${out.height}, not ${size}×${size}`);\n\t} catch (e) {\n\t\tat(`render failed: ${codeOf(e)}`);\n\t}\n\tconst bad = await rejectsWith('invalid_values', () => renderer.render({ time: 0, inputs, values: { __not_a_control__: 1 } }));\n\tif (bad) at(`render with an unknown control ${bad}`);\n\trenderer.dispose();\n\tconst after = await rejectsWith('disposed', () => renderer.render({ time: 0, inputs }));\n\tif (after) at(`render after dispose ${after}`);\n}\n\n/** Runs the contract checks against `provider` and returns every issue found (empty when it conforms). */\nexport async function checkEffectProvider(provider: EffectProvider, options: EffectConformanceOptions = {}): Promise<EffectConformanceIssue[]> {\n\tconst issues: EffectConformanceIssue[] = [];\n\tconst top = (message: string) => issues.push({ message });\n\tif (!provider.id) top('provider has no id');\n\tconst caps = provider.capabilities;\n\tfor (const k of ['supportsWorker', 'supportsFloat', 'supportsMask', 'supportsHostImages', 'supportsVector'] as const) {\n\t\tif (typeof caps?.[k] !== 'boolean') top(`capabilities.${k} is not a boolean`);\n\t}\n\tif (caps?.supportsVector && typeof provider.applyVectorEffect !== 'function') top('supportsVector is set but applyVectorEffect is missing');\n\n\tconst list = await provider.list();\n\tconst ids = new Set<string>();\n\tfor (const e of list) {\n\t\tif (ids.has(e.id)) top(`duplicate id \"${e.id}\"`);\n\t\tids.add(e.id);\n\t\tcheckSummary(e, issues);\n\t}\n\tconst all = await provider.search('');\n\tif (all.length !== list.length) top(`search(\"\") returned ${all.length} effects, list() ${list.length}`);\n\tconst firstUse = list[0]?.uses[0];\n\tif (firstUse) {\n\t\tconst filtered = await provider.search('', { use: firstUse });\n\t\tif (filtered.some((e) => !e.uses.includes(firstUse))) top(`search(\"\", { use: \"${firstUse}\" }) returned effects without that use`);\n\t}\n\tconst unknown = await rejectsWith('unknown_effect', () => provider.describe('__no_such_effect__'));\n\tif (unknown) top(`describe of an unknown id ${unknown}`);\n\tconst unknownRenderer = await rejectsWith('unknown_effect', () => provider.createRenderer('__no_such_effect__', { width: 4, height: 4 }));\n\tif (unknownRenderer) top(`createRenderer of an unknown id ${unknownRenderer}`);\n\n\tconst size = options.size ?? 8;\n\tfor (const id of options.ids ?? [...ids]) {\n\t\tlet d: EffectDescription;\n\t\ttry {\n\t\t\td = await provider.describe(id);\n\t\t} catch (e) {\n\t\t\tissues.push({ effectId: id, message: `describe failed: ${codeOf(e)}` });\n\t\t\tcontinue;\n\t\t}\n\t\tcheckDescription(id, d, issues);\n\t\tif (options.render !== false && d.kind !== 'motion') await checkRender(provider, d, size, issues);\n\t\tif ((d.kind === 'vector' || d.kind === 'motion') && caps.supportsVector && provider.applyVectorEffect) {\n\t\t\ttry {\n\t\t\t\tconst m = await provider.applyVectorEffect(id, '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 10 10\"><rect id=\"r\" width=\"10\" height=\"10\"/></svg>');\n\t\t\t\tif (typeof m.svg !== 'string' || !m.svg.includes('<svg')) issues.push({ effectId: id, message: 'applyVectorEffect returned no SVG' });\n\t\t\t\tif (!(m.duration >= 0)) issues.push({ effectId: id, message: 'applyVectorEffect returned no duration' });\n\t\t\t} catch (e) {\n\t\t\t\t// The probe target is a bare <rect>; an effect that needs more (a path to follow, a fill colour) may refuse it.\n\t\t\t\tif (codeOf(e) !== 'unsupported') issues.push({ effectId: id, message: `applyVectorEffect failed: ${codeOf(e)}` });\n\t\t\t}\n\t\t\tconst bad = await rejectsWith('invalid_values', () => provider.applyVectorEffect!(id, '<svg xmlns=\"http://www.w3.org/2000/svg\"/>', { __not_a_control__: 1 }));\n\t\t\tif (bad) issues.push({ effectId: id, message: `applyVectorEffect with an unknown control ${bad}` });\n\t\t}\n\t}\n\treturn issues;\n}\n","/**\n * `createFakeEffectProvider`: a small, deterministic, CPU-only provider implementing the whole\n * contract. Tests and the demo use it; it is also a reference for real providers.\n *\n * Effects (ids prefixed `fake.`):\n * - `invert` (filter), `tint` (filter)\n * - `gradient` (generator, loops)\n * - `bar` (title / overlay: a lower-third bar that slides in, transparent)\n * - `crossfade` (transition)\n * - `fade-in` (motion, vector only)\n * - `badge` (a vector effect with its own picture: a disc that fades in; vector only)\n */\n\nimport { EffectDisposedError, EffectUnsupportedError, UnknownEffectError } from './errors.js';\nimport { createEffectPixels, encodeEffectFloat, isEffectPixels, resolveEffectValues, searchEffectSummaries, srgbToLinear } from './helpers.js';\nimport type {\n\tEffectDescription,\n\tEffectImageSource,\n\tEffectPixels,\n\tEffectProvider,\n\tEffectProviderCapabilities,\n\tEffectRenderer,\n\tEffectRendererOptions,\n\tEffectRenderRequest,\n\tEffectSummary,\n\tEffectValues,\n\tEffectVectorMotion,\n\tEffectVectorTarget\n} from './types.js';\n\nconst HEX = '^#[0-9a-fA-F]{6}$';\nconst colour = (description: string) => ({ type: 'string' as const, pattern: HEX, description });\nconst amount = (description: string) => ({ type: 'number' as const, minimum: 0, maximum: 1, description });\n\nconst DESCRIPTIONS: readonly EffectDescription[] = [\n\t{\n\t\tid: 'fake.invert',\n\t\tname: 'Invert',\n\t\tkind: 'shader',\n\t\tcategory: 'image',\n\t\tuses: ['filter'],\n\t\tdescription: 'Inverts the colours of the source.',\n\t\ttags: ['negative', 'colour'],\n\t\ttransparent: false,\n\t\tloop: false,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: { amount: amount('How far to invert, 0 (unchanged) to 1 (fully inverted). Default 1.') }\n\t\t},\n\t\tdefaults: { amount: 1 },\n\t\tpresets: [{ name: 'Half', values: { amount: 0.5 } }],\n\t\tinputs: [{ name: 'source', kind: 'image', description: 'The picture to invert.', required: true }]\n\t},\n\t{\n\t\tid: 'fake.tint',\n\t\tname: 'Tint',\n\t\tkind: 'shader',\n\t\tcategory: 'image',\n\t\tuses: ['filter'],\n\t\tdescription: 'Mixes the source towards a colour.',\n\t\ttags: ['colour', 'wash'],\n\t\ttransparent: false,\n\t\tloop: false,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tcolor: colour('Tint colour as #rrggbb. Default #ff8800.'),\n\t\t\t\tamount: amount('How far to mix towards the colour, 0–1. Default 0.5.')\n\t\t\t}\n\t\t},\n\t\tdefaults: { color: '#ff8800', amount: 0.5 },\n\t\tpresets: [\n\t\t\t{ name: 'Sepia', values: { color: '#704214', amount: 0.4 } },\n\t\t\t{ name: 'Cool', values: { color: '#2060ff', amount: 0.25 } }\n\t\t],\n\t\tinputs: [{ name: 'source', kind: 'image', description: 'The picture to tint.', required: true }]\n\t},\n\t{\n\t\tid: 'fake.gradient',\n\t\tname: 'Gradient',\n\t\tkind: 'shader',\n\t\tcategory: 'gradient',\n\t\tuses: ['generator'],\n\t\tdescription: 'A linear gradient between two colours that can turn over time.',\n\t\ttags: ['background'],\n\t\ttransparent: false,\n\t\tloop: true,\n\t\tduration: 4000,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tfrom: colour('Start colour as #rrggbb. Default #1e3a8a.'),\n\t\t\t\tto: colour('End colour as #rrggbb. Default #f472b6.'),\n\t\t\t\tangle: { type: 'number', minimum: 0, maximum: 360, description: 'Direction in degrees, 0 = left to right, 90 = top to bottom. Default 90.' },\n\t\t\t\tspin: { type: 'number', minimum: -360, maximum: 360, description: 'Turn per loop (4 s) in degrees. Default 0.' }\n\t\t\t}\n\t\t},\n\t\tdefaults: { from: '#1e3a8a', to: '#f472b6', angle: 90, spin: 0 },\n\t\tpresets: [{ name: 'Sunset', values: { from: '#f97316', to: '#7c3aed' } }],\n\t\tinputs: []\n\t},\n\t{\n\t\tid: 'fake.bar',\n\t\tname: 'Lower-third bar',\n\t\tkind: 'shader',\n\t\tcategory: 'broadcast',\n\t\tuses: ['title', 'overlay'],\n\t\tdescription: 'A coloured bar that slides in from the left in the lower third. Transparent elsewhere.',\n\t\ttags: ['lower third', 'title'],\n\t\ttransparent: true,\n\t\tloop: false,\n\t\tduration: 2000,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tcolor: colour('Bar colour as #rrggbb. Default #0ea5e9.'),\n\t\t\t\theight: { type: 'number', minimum: 0.02, maximum: 0.5, description: 'Bar height as a fraction of the picture height. Default 0.12.' },\n\t\t\t\tslide: { type: 'number', minimum: 0, maximum: 5000, description: 'Slide-in time (ms). Default 500.' }\n\t\t\t}\n\t\t},\n\t\tdefaults: { color: '#0ea5e9', height: 0.12, slide: 500 },\n\t\tpresets: [],\n\t\tinputs: []\n\t},\n\t{\n\t\tid: 'fake.crossfade',\n\t\tname: 'Crossfade',\n\t\tkind: 'shader',\n\t\tcategory: 'transition',\n\t\tuses: ['transition'],\n\t\tdescription: 'Dissolves from one picture to the next.',\n\t\ttags: ['dissolve', 'mix'],\n\t\ttransparent: false,\n\t\tloop: false,\n\t\tduration: 1000,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: { curve: { type: 'string', enum: ['linear', 'smooth'], description: 'Mix curve: linear, or smooth (ease in and out). Default smooth.' } }\n\t\t},\n\t\tdefaults: { curve: 'smooth' },\n\t\tpresets: [],\n\t\tinputs: [\n\t\t\t{ name: 'from', kind: 'image', description: 'The outgoing picture.', required: true },\n\t\t\t{ name: 'to', kind: 'image', description: 'The incoming picture.', required: true }\n\t\t]\n\t},\n\t{\n\t\tid: 'fake.fade-in',\n\t\tname: 'Fade in',\n\t\tkind: 'motion',\n\t\tcategory: 'morph',\n\t\tuses: ['motion'],\n\t\tdescription: 'Fades vector elements in from transparent, one after another.',\n\t\ttags: ['appear', 'entrance'],\n\t\ttransparent: true,\n\t\tloop: false,\n\t\tduration: 600,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tms: { type: 'number', minimum: 0, maximum: 10000, description: 'Fade time per element (ms). Default 600.' },\n\t\t\t\tstagger: { type: 'number', minimum: 0, maximum: 5000, description: 'Delay between elements (ms). Default 0.' }\n\t\t\t}\n\t\t},\n\t\tdefaults: { ms: 600, stagger: 0 },\n\t\tpresets: [{ name: 'Cascade', values: { ms: 400, stagger: 120 } }],\n\t\tinputs: []\n\t},\n\t{\n\t\tid: 'fake.badge',\n\t\tname: 'Badge',\n\t\tkind: 'vector',\n\t\tcategory: 'sticker',\n\t\tuses: ['title'],\n\t\tdescription: 'A coloured disc that fades in: a vector effect that brings its own picture.',\n\t\ttags: ['sticker', 'disc'],\n\t\ttransparent: true,\n\t\tloop: false,\n\t\tduration: 500,\n\t\tcontrols: {\n\t\t\ttype: 'object',\n\t\t\tadditionalProperties: false,\n\t\t\tproperties: {\n\t\t\t\tcolor: colour('Disc colour as #rrggbb. Default #f43f5e.'),\n\t\t\t\tms: { type: 'number', minimum: 0, maximum: 10000, description: 'Fade-in time (ms). Default 500.' }\n\t\t\t}\n\t\t},\n\t\tdefaults: { color: '#f43f5e', ms: 500 },\n\t\tpresets: [],\n\t\tinputs: []\n\t}\n];\n\nconst summaryOf = ({ controls: _c, defaults: _d, presets: _p, inputs: _i, ...summary }: EffectDescription): EffectSummary => summary;\n\nconst hexToRgb = (hex: string): [number, number, number] => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)) as [number, number, number];\nconst smooth = (t: number) => t * t * (3 - 2 * t);\nconst clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);\n\nfunction input(request: EffectRenderRequest, name: string, id: string, width: number, height: number): EffectPixels {\n\tconst src: EffectImageSource | undefined = request.inputs?.[name];\n\tif (!src) throw new EffectUnsupportedError(`Effect \"${id}\" needs an input named \"${name}\".`, id);\n\tif (!isEffectPixels(src)) throw new EffectUnsupportedError(`The fake provider only takes EffectPixels inputs (\"${name}\").`, id);\n\tif (src.width !== width || src.height !== height) {\n\t\tthrow new EffectUnsupportedError(`Input \"${name}\" is ${src.width}×${src.height}; the renderer is ${width}×${height}.`, id);\n\t}\n\treturn src;\n}\n\n/** Per-pixel colour work in 8-bit sRGB; float output (when asked) is derived from it. */\nfunction renderPixels(d: EffectDescription, v: Record<string, EffectValues[string]>, request: EffectRenderRequest, width: number, height: number): EffectPixels {\n\tconst out = createEffectPixels(width, height);\n\tconst o = out.data;\n\tswitch (d.id) {\n\t\tcase 'fake.invert': {\n\t\t\tconst s = input(request, 'source', d.id, width, height).data;\n\t\t\tconst k = v.amount as number;\n\t\t\tfor (let i = 0; i < o.length; i += 4) {\n\t\t\t\tfor (let c = 0; c < 3; c++) o[i + c] = s[i + c] + (255 - 2 * s[i + c]) * k;\n\t\t\t\to[i + 3] = s[i + 3];\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'fake.tint': {\n\t\t\tconst s = input(request, 'source', d.id, width, height).data;\n\t\t\tconst rgb = hexToRgb(v.color as string);\n\t\t\tconst k = v.amount as number;\n\t\t\tfor (let i = 0; i < o.length; i += 4) {\n\t\t\t\tfor (let c = 0; c < 3; c++) o[i + c] = s[i + c] + (rgb[c] - s[i + c]) * k;\n\t\t\t\to[i + 3] = s[i + 3];\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'fake.gradient': {\n\t\t\tconst a = hexToRgb(v.from as string);\n\t\t\tconst b = hexToRgb(v.to as string);\n\t\t\tconst turn = ((request.time % (d.duration ?? 4000)) / (d.duration ?? 4000)) * (v.spin as number);\n\t\t\tconst rad = (((v.angle as number) + turn) * Math.PI) / 180;\n\t\t\tconst dx = Math.cos(rad);\n\t\t\tconst dy = Math.sin(rad);\n\t\t\tconst half = (Math.abs(dx) * width + Math.abs(dy) * height) / 2 || 1;\n\t\t\tfor (let y = 0, i = 0; y < height; y++) {\n\t\t\t\tfor (let x = 0; x < width; x++, i += 4) {\n\t\t\t\t\tconst t = clamp01(((x + 0.5 - width / 2) * dx + (y + 0.5 - height / 2) * dy) / (2 * half) + 0.5);\n\t\t\t\t\tfor (let c = 0; c < 3; c++) o[i + c] = a[c] + (b[c] - a[c]) * t;\n\t\t\t\t\to[i + 3] = 255;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'fake.bar': {\n\t\t\tconst rgb = hexToRgb(v.color as string);\n\t\t\tconst slide = v.slide as number;\n\t\t\tconst shown = slide > 0 ? smooth(clamp01(request.time / slide)) : 1;\n\t\t\tconst top = Math.round(height * (0.75 - (v.height as number) / 2));\n\t\t\tconst bottom = Math.round(height * (0.75 + (v.height as number) / 2));\n\t\t\tconst right = Math.round(width * 0.7 * shown);\n\t\t\tfor (let y = Math.max(0, top); y < Math.min(height, bottom); y++) {\n\t\t\t\tfor (let x = 0; x < right; x++) {\n\t\t\t\t\tconst i = (y * width + x) * 4;\n\t\t\t\t\to[i] = rgb[0];\n\t\t\t\t\to[i + 1] = rgb[1];\n\t\t\t\t\to[i + 2] = rgb[2];\n\t\t\t\t\to[i + 3] = 255;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'fake.crossfade': {\n\t\t\tconst from = input(request, 'from', d.id, width, height).data;\n\t\t\tconst to = input(request, 'to', d.id, width, height).data;\n\t\t\tconst p = clamp01(request.progress ?? request.time / (d.duration ?? 1000));\n\t\t\tconst k = v.curve === 'smooth' ? smooth(p) : p;\n\t\t\tfor (let i = 0; i < o.length; i++) o[i] = from[i] + (to[i] - from[i]) * k;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tthrow new EffectUnsupportedError(`\"${d.id}\" is a ${d.kind} effect; use applyVectorEffect.`, d.id);\n\t}\n\treturn out;\n}\n\nfunction toFloat(p: EffectPixels): EffectPixels {\n\tconst f = new Float32Array(p.data.length);\n\tfor (let i = 0; i < f.length; i += 4) {\n\t\tf[i] = srgbToLinear(p.data[i] / 255);\n\t\tf[i + 1] = srgbToLinear(p.data[i + 1] / 255);\n\t\tf[i + 2] = srgbToLinear(p.data[i + 2] / 255);\n\t\tf[i + 3] = p.data[i + 3] / 255;\n\t}\n\treturn encodeEffectFloat({ ...p, float: f });\n}\n\nfunction maskResult(out: EffectPixels, mask: EffectImageSource, source: EffectPixels | undefined, id: string): void {\n\tif (!isEffectPixels(mask) || mask.width !== out.width || mask.height !== out.height) {\n\t\tthrow new EffectUnsupportedError('The mask must be EffectPixels the size of the renderer.', id);\n\t}\n\tconst o = out.data;\n\tfor (let i = 0; i < o.length; i += 4) {\n\t\tconst k = mask.data[i + 3] / 255;\n\t\tif (source) for (let c = 0; c < 4; c++) o[i + c] = source.data[i + c] + (o[i + c] - source.data[i + c]) * k;\n\t\telse o[i + 3] *= k;\n\t}\n}\n\nexport interface FakeEffectProviderOptions {\n\t/** Overrides of the capability flags, to test how editors degrade. */\n\tcapabilities?: Partial<EffectProviderCapabilities>;\n\t/** Called on every render (tests count frames with it). */\n\tonRender?: (effectId: string, request: EffectRenderRequest) => void;\n}\n\n/** A deterministic CPU effects provider with six effects (see the module comment). */\nexport function createFakeEffectProvider(options: FakeEffectProviderOptions = {}): EffectProvider {\n\tconst capabilities: EffectProviderCapabilities = {\n\t\tsupportsWorker: false,\n\t\tsupportsFloat: true,\n\t\tsupportsMask: true,\n\t\tsupportsHostImages: false,\n\t\tsupportsVector: true,\n\t\tmaxSize: 8192,\n\t\t...options.capabilities\n\t};\n\tconst byId = new Map(DESCRIPTIONS.map((d) => [d.id, d]));\n\tconst get = (id: string) => {\n\t\tconst d = byId.get(id);\n\t\tif (!d) throw new UnknownEffectError(id, 'the fake provider');\n\t\treturn d;\n\t};\n\tconst summaries = DESCRIPTIONS.map(summaryOf);\n\n\tconst provider: EffectProvider = {\n\t\tid: 'fake',\n\t\tname: 'Fake effects',\n\t\tversion: '1',\n\t\tcapabilities,\n\t\tlist: (o) => searchEffectSummaries(summaries, '', o),\n\t\tsearch: (q, o) => searchEffectSummaries(summaries, q, o),\n\t\tdescribe: (id) => structuredClone(get(id)),\n\t\tcreateRenderer(id: string, ro: EffectRendererOptions): EffectRenderer {\n\t\t\tconst d = get(id);\n\t\t\tif (d.kind !== 'shader') throw new EffectUnsupportedError(`\"${id}\" is a ${d.kind} effect; use applyVectorEffect.`, id);\n\t\t\tconst { width, height } = ro;\n\t\t\tconst depth = ro.depth ?? 8;\n\t\t\tif (!(width > 0 && height > 0) || width > capabilities.maxSize! || height > capabilities.maxSize!) {\n\t\t\t\tthrow new EffectUnsupportedError(`Renderer size ${width}×${height} is outside 1–${capabilities.maxSize}.`, id);\n\t\t\t}\n\t\t\tlet disposed = false;\n\t\t\treturn {\n\t\t\t\teffectId: id,\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tdepth,\n\t\t\t\tasync render(request) {\n\t\t\t\t\tif (disposed) throw new EffectDisposedError(id);\n\t\t\t\t\tif (!Number.isFinite(request.time) || request.time < 0) {\n\t\t\t\t\t\tthrow new EffectUnsupportedError(`time must be a finite number of ms ≥ 0, got ${request.time}.`, id);\n\t\t\t\t\t}\n\t\t\t\t\tconst values = resolveEffectValues(d, request.values);\n\t\t\t\t\toptions.onRender?.(id, request);\n\t\t\t\t\tconst out = renderPixels(d, values, request, width, height);\n\t\t\t\t\tif (request.mask) {\n\t\t\t\t\t\tif (!capabilities.supportsMask) throw new EffectUnsupportedError('This provider does not take masks.', id);\n\t\t\t\t\t\tconst source = d.uses.includes('filter') ? (request.inputs?.source as EffectPixels) : undefined;\n\t\t\t\t\t\tmaskResult(out, request.mask, source, id);\n\t\t\t\t\t}\n\t\t\t\t\treturn depth > 8 && capabilities.supportsFloat ? toFloat(out) : out;\n\t\t\t\t},\n\t\t\t\tdispose() {\n\t\t\t\t\tdisposed = true;\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t\tapplyVectorEffect(id: string, target: EffectVectorTarget, values?: EffectValues): EffectVectorMotion {\n\t\t\tconst d = get(id);\n\t\t\tif (d.kind !== 'motion' && d.kind !== 'vector') throw new EffectUnsupportedError(`\"${id}\" is a ${d.kind} effect, not a vector one.`, id);\n\t\t\tconst v = resolveEffectValues(d, values);\n\t\t\tif (d.kind === 'vector') {\n\t\t\t\t// Its own picture: the target is not used.\n\t\t\t\treturn { svg: `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" width=\"100\" height=\"100\"><circle id=\"badge\" cx=\"50\" cy=\"50\" r=\"40\" fill=\"${v.color}\"/></svg>`, animate: [{ id: 'badge', ms: v.ms as number, attrs: { opacity: [0, 1] as const } }], duration: v.ms as number, loop: false };\n\t\t\t}\n\t\t\tconst svg = typeof target === 'string' ? target : target.svg;\n\t\t\tconst ids = typeof target === 'string' || !target.nodeIds ? [...svg.matchAll(/\\sid=\"([^\"]+)\"/g)].map((m) => m[1]) : [...target.nodeIds];\n\t\t\tconst ms = v.ms as number;\n\t\t\tconst stagger = v.stagger as number;\n\t\t\treturn {\n\t\t\t\tsvg,\n\t\t\t\tanimate: ids.map((elementId, i) => ({ id: elementId, delay: i * stagger, ms, attrs: { opacity: [0, 1] as const } })),\n\t\t\t\tduration: ms + Math.max(0, ids.length - 1) * stagger,\n\t\t\t\tloop: false\n\t\t\t};\n\t\t}\n\t};\n\treturn provider;\n}\n","/**\n * Framework-free helpers for effects UIs (the React and Svelte effects browser and parameter\n * panel): control schemas as parameter fields, preview thumbnails from a provider's renderer,\n * and labels.\n */\n\nimport type { JsonSchema } from '../tools/schema.js';\nimport type { ParameterField, ParameterValue } from '../parameters/types.js';\nimport { createEffectPixels } from './helpers.js';\nimport type { EffectDescription, EffectPixels, EffectProvider, EffectSummary, EffectUse, EffectValue } from './types.js';\n\n/** Names for the uses, for filters and badges. */\nexport const EFFECT_USE_LABELS: Readonly<Record<EffectUse, string>> = {\n\tfilter: 'Filter',\n\ttransition: 'Transition',\n\tgenerator: 'Generator',\n\toverlay: 'Overlay',\n\ttitle: 'Title',\n\tmotion: 'Motion',\n\tinteractive: 'Interactive'\n};\n\nconst HEX = /^#[0-9a-f]{6}([0-9a-f]{2})?$/i;\nconst typeOf = (s: JsonSchema) => (Array.isArray(s.type) ? s.type.find((t) => t !== 'null') : s.type);\n\n/**\n * One control as a parameter field: numbers and integers (sliders when bounded), switches,\n * choices (`enum`), colours (strings whose default is `#rrggbb`), vectors (2–4 numbers) and text.\n * `null` for a control no field can edit (objects, long lists).\n */\nexport function effectControlField(name: string, schema: JsonSchema, fallback: EffectValue | undefined): ParameterField | null {\n\tconst base = { id: name, ...(schema.title ? { label: schema.title } : {}), ...(schema.description ? { description: schema.description } : {}) };\n\tconst def = (schema.default ?? fallback) as EffectValue | undefined;\n\tconst type = typeOf(schema);\n\tif (Array.isArray(schema.enum) && schema.enum.length) {\n\t\tconst options = schema.enum.filter((v): v is string | number => typeof v === 'string' || typeof v === 'number');\n\t\treturn { ...base, type: 'choice', options, default: (typeof def === 'string' || typeof def === 'number' ? def : options[0]) as string | number, control: options.length <= 4 ? 'segmented' : 'select' };\n\t}\n\tif (type === 'number' || type === 'integer') {\n\t\tconst min = schema.minimum ?? schema.exclusiveMinimum;\n\t\tconst max = schema.maximum ?? schema.exclusiveMaximum;\n\t\treturn { ...base, type: type === 'integer' ? 'int' : 'number', default: typeof def === 'number' ? def : (min ?? 0), ...(min !== undefined ? { min } : {}), ...(max !== undefined ? { max } : {}), ...(schema.multipleOf ? { step: schema.multipleOf } : {}) };\n\t}\n\tif (type === 'boolean') return { ...base, type: 'boolean', default: def === true };\n\tif (type === 'string') {\n\t\tif (typeof def === 'string' && HEX.test(def)) return { ...base, type: 'color', alpha: def.length === 9, default: def };\n\t\treturn { ...base, type: 'text', default: typeof def === 'string' ? def : '', ...(schema.maxLength ? { maxLength: schema.maxLength } : {}) };\n\t}\n\tif (type === 'array' && Array.isArray(def) && def.length >= 2 && def.length <= 4 && def.every((n) => typeof n === 'number')) {\n\t\tconst items = schema.items ?? {};\n\t\treturn { ...base, type: 'vector', size: def.length as 2 | 3 | 4, default: [...def], ...(items.minimum !== undefined ? { min: items.minimum } : {}), ...(items.maximum !== undefined ? { max: items.maximum } : {}) };\n\t}\n\treturn null;\n}\n\n/** An effect's controls as parameter fields, in schema order (controls no field can edit are left out). */\nexport function effectParameterFields(description: Pick<EffectDescription, 'controls' | 'defaults'>): ParameterField[] {\n\treturn Object.entries(description.controls.properties)\n\t\t.map(([name, schema]) => effectControlField(name, schema, description.defaults[name]))\n\t\t.filter((f): f is ParameterField => !!f);\n}\n\n/** A saved value as a field shows it (`ParameterValue`), or the field's default. */\nexport const effectFieldValue = (field: ParameterField, value: unknown): ParameterValue =>\n\tvalue === undefined || value === null || (typeof value === 'object' && !Array.isArray(value)) ? field.default : (value as ParameterValue);\n\n/**\n * A colourful, deterministic picture for previews of filters and transitions (`variant` 0 and 1\n * differ, for a transition's two sides).\n */\nexport function effectSamplePicture(width: number, height: number, variant = 0): EffectPixels {\n\tconst p = createEffectPixels(width, height);\n\tfor (let y = 0, i = 0; y < height; y++) {\n\t\tfor (let x = 0; x < width; x++, i += 4) {\n\t\t\tconst u = x / Math.max(1, width - 1);\n\t\t\tconst v = y / Math.max(1, height - 1);\n\t\t\tconst check = (Math.floor(u * 4) + Math.floor(v * 4)) % 2;\n\t\t\tif (variant === 0) p.data.set([Math.round(40 + 200 * u), Math.round(80 + 120 * v), Math.round(200 - 120 * u) + check * 30, 255], i);\n\t\t\telse p.data.set([Math.round(230 - 60 * v), Math.round(190 - 100 * u), 60 + check * 40, 255], i);\n\t\t}\n\t}\n\treturn p;\n}\n\n/**\n * A small preview of an effect from the provider's own renderer: filters run on a sample picture,\n * transitions go half way between two, generators and overlays draw at `time` (default half\n * their length, or 1 s). Thumbnails render one at a time (each renderer is disposed before the\n * next is made) and are kept per provider. Rejects like the provider does (motion effects have\n * no pixels).\n */\nexport function renderEffectThumbnail(provider: EffectProvider, effect: EffectSummary | EffectDescription, size: { width: number; height: number } = { width: 96, height: 54 }): Promise<EffectPixels> {\n\tlet cache = thumbnails.get(provider);\n\tif (!cache) thumbnails.set(provider, (cache = new Map()));\n\tconst key = `${effect.id}@${size.width}x${size.height}`;\n\tlet out = cache.get(key);\n\tif (!out) {\n\t\t// One at a time: a browser full of previews would otherwise open a graphics context each.\n\t\tout = thumbnailQueue.then(() => renderThumbnail(provider, effect, size));\n\t\tthumbnailQueue = out.catch(() => undefined);\n\t\tout.catch(() => cache!.delete(key));\n\t\tcache.set(key, out);\n\t}\n\treturn out;\n}\n\nconst thumbnails = new WeakMap<EffectProvider, Map<string, Promise<EffectPixels>>>();\nlet thumbnailQueue: Promise<unknown> = Promise.resolve();\n\nasync function renderThumbnail(provider: EffectProvider, effect: EffectSummary | EffectDescription, size: { width: number; height: number }): Promise<EffectPixels> {\n\tconst d = 'inputs' in effect ? effect : await provider.describe(effect.id);\n\tconst renderer = await provider.createRenderer(d.id, size);\n\ttry {\n\t\tconst time = d.duration ? d.duration / 2 : 1000;\n\t\tconst inputs: Record<string, EffectPixels> = {};\n\t\tfor (const input of d.inputs) if (input.kind === 'image') inputs[input.name] = effectSamplePicture(size.width, size.height, input.name === 'to' ? 1 : 0);\n\t\treturn await renderer.render({ time, inputs, ...(d.uses.includes('transition') ? { progress: 0.5 } : {}) });\n\t} finally {\n\t\trenderer.dispose();\n\t}\n}\n\n/** Effects grouped by category, categories in first-seen order. */\nexport function groupEffectsByCategory<T extends EffectSummary>(effects: readonly T[]): { category: string; effects: T[] }[] {\n\tconst groups = new Map<string, T[]>();\n\tfor (const e of effects) {\n\t\tconst g = groups.get(e.category);\n\t\tif (g) g.push(e);\n\t\telse groups.set(e.category, [e]);\n\t}\n\treturn [...groups].map(([category, list]) => ({ category, effects: list }));\n}\n"]}
|