@pixerate/editor 0.123.0 → 0.124.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 +2 -2
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +3 -3
- package/dist/animation/index.d.cts +2 -1
- package/dist/animation/index.d.ts +2 -1
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +11 -733
- package/dist/animation-editor/index.d.ts +11 -733
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/{bezier-C-W9LFyD.d.cts → bezier-CBzKKDjl.d.cts} +2 -66
- package/dist/{bezier-DQZE5dxA.d.ts → bezier-CWv6LQal.d.ts} +2 -66
- package/dist/{brushstrokeRender-M4S65DHT.cjs → brushstrokeRender-EELTV7U4.cjs} +8 -8
- package/dist/{brushstrokeRender-M4S65DHT.cjs.map → brushstrokeRender-EELTV7U4.cjs.map} +1 -1
- package/dist/{brushstrokeRender-CDDRSV2W.js → brushstrokeRender-LRV7GXGD.js} +3 -3
- package/dist/canvas/index.cjs +6 -6
- package/dist/canvas/index.d.cts +10 -6
- package/dist/canvas/index.d.ts +10 -6
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-TVXFHE4T.js → chunk-64PYNEHJ.js} +45 -12
- package/dist/chunk-64PYNEHJ.js.map +1 -0
- package/dist/{chunk-2IW3BWRT.cjs → chunk-7LQO6OHH.cjs} +2 -2
- package/dist/{chunk-2IW3BWRT.cjs.map → chunk-7LQO6OHH.cjs.map} +1 -1
- package/dist/chunk-IOXKXQNW.cjs +178 -0
- package/dist/chunk-IOXKXQNW.cjs.map +1 -0
- package/dist/{chunk-SMJGE7HM.cjs → chunk-IWSMDQE5.cjs} +523 -39
- package/dist/chunk-IWSMDQE5.cjs.map +1 -0
- package/dist/{chunk-KMBC4HNV.js → chunk-J67OLQUQ.js} +2 -2
- package/dist/{chunk-6CJRDJCU.cjs → chunk-PEHYWUVV.cjs} +176 -111
- package/dist/chunk-PEHYWUVV.cjs.map +1 -0
- package/dist/chunk-TGKT5QAB.js +178 -0
- package/dist/chunk-TGKT5QAB.js.map +1 -0
- package/dist/chunk-TK53AB4Q.cjs +215 -0
- package/dist/chunk-TK53AB4Q.cjs.map +1 -0
- package/dist/chunk-VFAKN46Z.js +215 -0
- package/dist/chunk-VFAKN46Z.js.map +1 -0
- package/dist/{chunk-6ZBR4J62.js → chunk-WATAQ2Z2.js} +79 -14
- package/dist/chunk-WATAQ2Z2.js.map +1 -0
- package/dist/{chunk-IPFRWILU.js → chunk-YVVSTD3B.js} +499 -15
- package/dist/chunk-YVVSTD3B.js.map +1 -0
- package/dist/{chunk-RLRUAPBJ.cjs → chunk-Z6OUCS2K.cjs} +47 -14
- package/dist/chunk-Z6OUCS2K.cjs.map +1 -0
- package/dist/effects/index.cjs +588 -0
- package/dist/effects/index.cjs.map +1 -0
- package/dist/effects/index.d.cts +131 -0
- package/dist/effects/index.d.ts +131 -0
- package/dist/effects/index.js +588 -0
- package/dist/effects/index.js.map +1 -0
- package/dist/evaluate-2S3vSPLy.d.cts +55 -0
- package/dist/evaluate-CfmT6xA3.d.ts +55 -0
- package/dist/{export-DfjzHAJI.d.ts → export-8RnlngAv.d.ts} +534 -128
- package/dist/{export-Dv0-ZVrV.d.ts → export-BzSRXyH6.d.cts} +390 -5
- package/dist/{export-Dv0-ZVrV.d.cts → export-DM3h7QEI.d.ts} +390 -5
- package/dist/{export-lU5qSpHE.d.cts → export-TqXMOlDt.d.cts} +534 -128
- package/dist/flair-CxB3fOcU.d.ts +739 -0
- package/dist/flair-D7BxyBwL.d.cts +739 -0
- package/dist/gradient-KDfCvhuj.d.cts +66 -0
- package/dist/gradient-KDfCvhuj.d.ts +66 -0
- package/dist/helpers-777mC6dc.d.cts +253 -0
- package/dist/helpers-DX2_7L5t.d.ts +253 -0
- package/dist/image-editor/index.cjs +1307 -805
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +39 -182
- package/dist/image-editor/index.d.ts +39 -182
- package/dist/image-editor/index.js +508 -6
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{opentype-p8Xxf-Wh.d.ts → opentype-14PDI5D2.d.ts} +1 -1
- package/dist/{opentype-BV7wWatY.d.cts → opentype-D80AVmMu.d.cts} +1 -1
- package/dist/paint/index.cjs +2 -2
- package/dist/paint/index.d.cts +32 -10
- package/dist/paint/index.d.ts +32 -10
- package/dist/paint/index.js +1 -1
- package/dist/paint-editor/index.cjs +10 -8
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +11 -5
- package/dist/paint-editor/index.d.ts +11 -5
- package/dist/paint-editor/index.js +4 -2
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/parameters/index.d.cts +4 -184
- package/dist/parameters/index.d.ts +4 -184
- package/dist/{pathfinder-IxcyhO00.d.cts → pathfinder-C0UoFypx.d.cts} +1 -1
- package/dist/{pathfinder-StVChJOT.d.ts → pathfinder-DjISxkUv.d.ts} +1 -1
- package/dist/{player-DrcP2xqj.d.cts → player-CBtEoU2x.d.cts} +2 -54
- package/dist/{player-DV4Jujdq.d.ts → player-cGLrngL_.d.ts} +2 -54
- package/dist/{svg-BtND4TJu.d.cts → svg-BDrjRnL1.d.cts} +3 -2
- package/dist/{svg-BfVXG7hL.d.ts → svg-C-vZ3q50.d.ts} +3 -2
- 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 +126 -0
- package/dist/tools-DHKGHiSK.d.cts +126 -0
- package/dist/{types-fuXEme9j.d.ts → types-BcX7sCsn.d.ts} +2 -2
- package/dist/{types-DJF3Ghn5.d.cts → types-CznabG7m.d.cts} +2 -2
- package/dist/types-D3-J3TFf.d.ts +185 -0
- package/dist/types-DMcf_z4J.d.cts +185 -0
- package/dist/vector/index.d.cts +10 -10
- package/dist/vector/index.d.ts +10 -10
- package/dist/video-editor/index.cjs +749 -277
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +265 -138
- package/dist/video-editor/index.d.ts +265 -138
- package/dist/video-editor/index.js +490 -18
- package/dist/video-editor/index.js.map +1 -1
- package/dist/{warp-BjmAgQwq.d.cts → warp-CH9MM435.d.cts} +1 -1
- package/dist/{warp-Ca5fedzS.d.ts → warp-k_PdtXOG.d.ts} +1 -1
- package/package.json +6 -1
- package/skills/pixerate-image-editor/SKILL.md +7 -0
- package/skills/pixerate-image-editor/references/provider-effects.md +35 -0
- package/skills/pixerate-video-editor/SKILL.md +39 -1
- package/dist/chunk-6CJRDJCU.cjs.map +0 -1
- package/dist/chunk-6ZBR4J62.js.map +0 -1
- package/dist/chunk-IPFRWILU.js.map +0 -1
- package/dist/chunk-RLRUAPBJ.cjs.map +0 -1
- package/dist/chunk-SMJGE7HM.cjs.map +0 -1
- package/dist/chunk-TVXFHE4T.js.map +0 -1
- /package/dist/{brushstrokeRender-CDDRSV2W.js.map → brushstrokeRender-LRV7GXGD.js.map} +0 -0
- /package/dist/{chunk-KMBC4HNV.js.map → chunk-J67OLQUQ.js.map} +0 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EFFECT_KINDS,
|
|
3
|
+
EFFECT_USES,
|
|
4
|
+
createEffectToolProvider
|
|
5
|
+
} from "../chunk-VFAKN46Z.js";
|
|
6
|
+
import {
|
|
7
|
+
EFFECT_ERROR_CODES,
|
|
8
|
+
EffectControlError,
|
|
9
|
+
EffectDisposedError,
|
|
10
|
+
EffectError,
|
|
11
|
+
EffectUnsupportedError,
|
|
12
|
+
EffectsSlot,
|
|
13
|
+
NoEffectsProviderError,
|
|
14
|
+
UnknownEffectError,
|
|
15
|
+
applyEffectMask,
|
|
16
|
+
createEffectPixels,
|
|
17
|
+
effectValueErrors,
|
|
18
|
+
encodeEffectFloat,
|
|
19
|
+
isEffectError,
|
|
20
|
+
isEffectPixels,
|
|
21
|
+
linearToSrgb,
|
|
22
|
+
resolveEffectValues,
|
|
23
|
+
searchEffectSummaries,
|
|
24
|
+
srgbToLinear
|
|
25
|
+
} from "../chunk-TGKT5QAB.js";
|
|
26
|
+
import "../chunk-4FX5ACVG.js";
|
|
27
|
+
|
|
28
|
+
// src/effects/conformance.ts
|
|
29
|
+
var codeOf = (e) => isEffectError(e) ? e.code : e instanceof Error ? `untyped ${e.name}: ${e.message}` : String(e);
|
|
30
|
+
async function rejectsWith(code, run) {
|
|
31
|
+
try {
|
|
32
|
+
await run();
|
|
33
|
+
return `did not fail (expected ${code})`;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
const got = codeOf(e);
|
|
36
|
+
return got === code ? null : `failed with ${got} (expected ${code})`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function checkSummary(e, issues) {
|
|
40
|
+
const at = (message) => issues.push({ effectId: e.id, message });
|
|
41
|
+
if (!e.id || typeof e.id !== "string") at("summary has no id");
|
|
42
|
+
if (!e.name) at("summary has no name");
|
|
43
|
+
if (!EFFECT_KINDS.includes(e.kind)) at(`kind "${e.kind}" is not one of ${EFFECT_KINDS.join(", ")}`);
|
|
44
|
+
if (!e.category) at("summary has no category");
|
|
45
|
+
if (!Array.isArray(e.uses) || !e.uses.length) at("summary has no uses");
|
|
46
|
+
for (const u of e.uses ?? []) if (!EFFECT_USES.includes(u)) at(`use "${u}" is not one of ${EFFECT_USES.join(", ")}`);
|
|
47
|
+
if (typeof e.transparent !== "boolean") at("transparent is not a boolean");
|
|
48
|
+
if (typeof e.loop !== "boolean") at("loop is not a boolean");
|
|
49
|
+
if (e.duration !== void 0 && !(e.duration > 0)) at("duration must be a positive number of ms when present");
|
|
50
|
+
}
|
|
51
|
+
function checkDescription(id, d, issues) {
|
|
52
|
+
const at = (message) => issues.push({ effectId: id, message });
|
|
53
|
+
if (d.id !== id) at(`describe("${id}") returned id "${d.id}"`);
|
|
54
|
+
const c = d.controls;
|
|
55
|
+
if (!c || c.type !== "object") at("controls is not an object schema");
|
|
56
|
+
if (c?.additionalProperties !== false) at("controls must set additionalProperties: false");
|
|
57
|
+
for (const [name, schema] of Object.entries(c?.properties ?? {})) {
|
|
58
|
+
if (!schema.description || !String(schema.description).trim()) at(`control "${name}" has no description`);
|
|
59
|
+
if (!(name in (d.defaults ?? {}))) at(`control "${name}" has no default`);
|
|
60
|
+
}
|
|
61
|
+
for (const name of Object.keys(d.defaults ?? {})) if (!(name in (c?.properties ?? {}))) at(`default "${name}" is not a control`);
|
|
62
|
+
for (const e of effectValueErrors(d, d.defaults)) at(`default ${e.path}: ${e.message}`);
|
|
63
|
+
for (const p of d.presets ?? []) for (const e of effectValueErrors(d, p.values)) at(`preset "${p.name}" ${e.path}: ${e.message}`);
|
|
64
|
+
for (const input2 of d.inputs ?? []) {
|
|
65
|
+
if (!input2.name || !input2.description) at("every input needs a name and a description");
|
|
66
|
+
if (input2.kind !== "image" && input2.kind !== "audio") at(`input "${input2.name}" has kind "${input2.kind}"`);
|
|
67
|
+
}
|
|
68
|
+
if (d.uses.includes("filter") && !d.inputs.some((i) => i.name === "source")) at('a filter must read an input named "source"');
|
|
69
|
+
if (d.uses.includes("transition") && !(d.inputs.some((i) => i.name === "from") && d.inputs.some((i) => i.name === "to"))) {
|
|
70
|
+
at('a transition must read inputs named "from" and "to"');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function grey(size) {
|
|
74
|
+
const p = createEffectPixels(size, size);
|
|
75
|
+
p.data.fill(128);
|
|
76
|
+
return p;
|
|
77
|
+
}
|
|
78
|
+
async function checkRender(provider, d, size, issues) {
|
|
79
|
+
const at = (message) => issues.push({ effectId: d.id, message });
|
|
80
|
+
const inputs = Object.fromEntries(d.inputs.filter((i) => i.kind === "image").map((i) => [i.name, grey(size)]));
|
|
81
|
+
let renderer;
|
|
82
|
+
try {
|
|
83
|
+
renderer = await provider.createRenderer(d.id, { width: size, height: size });
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (!(d.kind === "vector" && codeOf(e) === "unsupported")) at(`createRenderer failed: ${codeOf(e)}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const out = await renderer.render({ time: 0, inputs });
|
|
90
|
+
if (!isEffectPixels(out)) at("render did not return EffectPixels");
|
|
91
|
+
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}`);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
at(`render failed: ${codeOf(e)}`);
|
|
94
|
+
}
|
|
95
|
+
const bad = await rejectsWith("invalid_values", () => renderer.render({ time: 0, inputs, values: { __not_a_control__: 1 } }));
|
|
96
|
+
if (bad) at(`render with an unknown control ${bad}`);
|
|
97
|
+
renderer.dispose();
|
|
98
|
+
const after = await rejectsWith("disposed", () => renderer.render({ time: 0, inputs }));
|
|
99
|
+
if (after) at(`render after dispose ${after}`);
|
|
100
|
+
}
|
|
101
|
+
async function checkEffectProvider(provider, options = {}) {
|
|
102
|
+
const issues = [];
|
|
103
|
+
const top = (message) => issues.push({ message });
|
|
104
|
+
if (!provider.id) top("provider has no id");
|
|
105
|
+
const caps = provider.capabilities;
|
|
106
|
+
for (const k of ["supportsWorker", "supportsFloat", "supportsMask", "supportsHostImages", "supportsVector"]) {
|
|
107
|
+
if (typeof caps?.[k] !== "boolean") top(`capabilities.${k} is not a boolean`);
|
|
108
|
+
}
|
|
109
|
+
if (caps?.supportsVector && typeof provider.applyVectorEffect !== "function") top("supportsVector is set but applyVectorEffect is missing");
|
|
110
|
+
const list = await provider.list();
|
|
111
|
+
const ids = /* @__PURE__ */ new Set();
|
|
112
|
+
for (const e of list) {
|
|
113
|
+
if (ids.has(e.id)) top(`duplicate id "${e.id}"`);
|
|
114
|
+
ids.add(e.id);
|
|
115
|
+
checkSummary(e, issues);
|
|
116
|
+
}
|
|
117
|
+
const all = await provider.search("");
|
|
118
|
+
if (all.length !== list.length) top(`search("") returned ${all.length} effects, list() ${list.length}`);
|
|
119
|
+
const firstUse = list[0]?.uses[0];
|
|
120
|
+
if (firstUse) {
|
|
121
|
+
const filtered = await provider.search("", { use: firstUse });
|
|
122
|
+
if (filtered.some((e) => !e.uses.includes(firstUse))) top(`search("", { use: "${firstUse}" }) returned effects without that use`);
|
|
123
|
+
}
|
|
124
|
+
const unknown = await rejectsWith("unknown_effect", () => provider.describe("__no_such_effect__"));
|
|
125
|
+
if (unknown) top(`describe of an unknown id ${unknown}`);
|
|
126
|
+
const unknownRenderer = await rejectsWith("unknown_effect", () => provider.createRenderer("__no_such_effect__", { width: 4, height: 4 }));
|
|
127
|
+
if (unknownRenderer) top(`createRenderer of an unknown id ${unknownRenderer}`);
|
|
128
|
+
const size = options.size ?? 8;
|
|
129
|
+
for (const id of options.ids ?? [...ids]) {
|
|
130
|
+
let d;
|
|
131
|
+
try {
|
|
132
|
+
d = await provider.describe(id);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
issues.push({ effectId: id, message: `describe failed: ${codeOf(e)}` });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
checkDescription(id, d, issues);
|
|
138
|
+
if (options.render !== false && d.kind !== "motion") await checkRender(provider, d, size, issues);
|
|
139
|
+
if ((d.kind === "vector" || d.kind === "motion") && caps.supportsVector && provider.applyVectorEffect) {
|
|
140
|
+
try {
|
|
141
|
+
const 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>');
|
|
142
|
+
if (typeof m.svg !== "string" || !m.svg.includes("<svg")) issues.push({ effectId: id, message: "applyVectorEffect returned no SVG" });
|
|
143
|
+
if (!(m.duration >= 0)) issues.push({ effectId: id, message: "applyVectorEffect returned no duration" });
|
|
144
|
+
} catch (e) {
|
|
145
|
+
if (codeOf(e) !== "unsupported") issues.push({ effectId: id, message: `applyVectorEffect failed: ${codeOf(e)}` });
|
|
146
|
+
}
|
|
147
|
+
const bad = await rejectsWith("invalid_values", () => provider.applyVectorEffect(id, '<svg xmlns="http://www.w3.org/2000/svg"/>', { __not_a_control__: 1 }));
|
|
148
|
+
if (bad) issues.push({ effectId: id, message: `applyVectorEffect with an unknown control ${bad}` });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return issues;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/effects/fake.ts
|
|
155
|
+
var HEX = "^#[0-9a-fA-F]{6}$";
|
|
156
|
+
var colour = (description) => ({ type: "string", pattern: HEX, description });
|
|
157
|
+
var amount = (description) => ({ type: "number", minimum: 0, maximum: 1, description });
|
|
158
|
+
var DESCRIPTIONS = [
|
|
159
|
+
{
|
|
160
|
+
id: "fake.invert",
|
|
161
|
+
name: "Invert",
|
|
162
|
+
kind: "shader",
|
|
163
|
+
category: "image",
|
|
164
|
+
uses: ["filter"],
|
|
165
|
+
description: "Inverts the colours of the source.",
|
|
166
|
+
tags: ["negative", "colour"],
|
|
167
|
+
transparent: false,
|
|
168
|
+
loop: false,
|
|
169
|
+
controls: {
|
|
170
|
+
type: "object",
|
|
171
|
+
additionalProperties: false,
|
|
172
|
+
properties: { amount: amount("How far to invert, 0 (unchanged) to 1 (fully inverted). Default 1.") }
|
|
173
|
+
},
|
|
174
|
+
defaults: { amount: 1 },
|
|
175
|
+
presets: [{ name: "Half", values: { amount: 0.5 } }],
|
|
176
|
+
inputs: [{ name: "source", kind: "image", description: "The picture to invert.", required: true }]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "fake.tint",
|
|
180
|
+
name: "Tint",
|
|
181
|
+
kind: "shader",
|
|
182
|
+
category: "image",
|
|
183
|
+
uses: ["filter"],
|
|
184
|
+
description: "Mixes the source towards a colour.",
|
|
185
|
+
tags: ["colour", "wash"],
|
|
186
|
+
transparent: false,
|
|
187
|
+
loop: false,
|
|
188
|
+
controls: {
|
|
189
|
+
type: "object",
|
|
190
|
+
additionalProperties: false,
|
|
191
|
+
properties: {
|
|
192
|
+
color: colour("Tint colour as #rrggbb. Default #ff8800."),
|
|
193
|
+
amount: amount("How far to mix towards the colour, 0\u20131. Default 0.5.")
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
defaults: { color: "#ff8800", amount: 0.5 },
|
|
197
|
+
presets: [
|
|
198
|
+
{ name: "Sepia", values: { color: "#704214", amount: 0.4 } },
|
|
199
|
+
{ name: "Cool", values: { color: "#2060ff", amount: 0.25 } }
|
|
200
|
+
],
|
|
201
|
+
inputs: [{ name: "source", kind: "image", description: "The picture to tint.", required: true }]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "fake.gradient",
|
|
205
|
+
name: "Gradient",
|
|
206
|
+
kind: "shader",
|
|
207
|
+
category: "gradient",
|
|
208
|
+
uses: ["generator"],
|
|
209
|
+
description: "A linear gradient between two colours that can turn over time.",
|
|
210
|
+
tags: ["background"],
|
|
211
|
+
transparent: false,
|
|
212
|
+
loop: true,
|
|
213
|
+
duration: 4e3,
|
|
214
|
+
controls: {
|
|
215
|
+
type: "object",
|
|
216
|
+
additionalProperties: false,
|
|
217
|
+
properties: {
|
|
218
|
+
from: colour("Start colour as #rrggbb. Default #1e3a8a."),
|
|
219
|
+
to: colour("End colour as #rrggbb. Default #f472b6."),
|
|
220
|
+
angle: { type: "number", minimum: 0, maximum: 360, description: "Direction in degrees, 0 = left to right, 90 = top to bottom. Default 90." },
|
|
221
|
+
spin: { type: "number", minimum: -360, maximum: 360, description: "Turn per loop (4 s) in degrees. Default 0." }
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
defaults: { from: "#1e3a8a", to: "#f472b6", angle: 90, spin: 0 },
|
|
225
|
+
presets: [{ name: "Sunset", values: { from: "#f97316", to: "#7c3aed" } }],
|
|
226
|
+
inputs: []
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "fake.bar",
|
|
230
|
+
name: "Lower-third bar",
|
|
231
|
+
kind: "shader",
|
|
232
|
+
category: "broadcast",
|
|
233
|
+
uses: ["title", "overlay"],
|
|
234
|
+
description: "A coloured bar that slides in from the left in the lower third. Transparent elsewhere.",
|
|
235
|
+
tags: ["lower third", "title"],
|
|
236
|
+
transparent: true,
|
|
237
|
+
loop: false,
|
|
238
|
+
duration: 2e3,
|
|
239
|
+
controls: {
|
|
240
|
+
type: "object",
|
|
241
|
+
additionalProperties: false,
|
|
242
|
+
properties: {
|
|
243
|
+
color: colour("Bar colour as #rrggbb. Default #0ea5e9."),
|
|
244
|
+
height: { type: "number", minimum: 0.02, maximum: 0.5, description: "Bar height as a fraction of the picture height. Default 0.12." },
|
|
245
|
+
slide: { type: "number", minimum: 0, maximum: 5e3, description: "Slide-in time (ms). Default 500." }
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
defaults: { color: "#0ea5e9", height: 0.12, slide: 500 },
|
|
249
|
+
presets: [],
|
|
250
|
+
inputs: []
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "fake.crossfade",
|
|
254
|
+
name: "Crossfade",
|
|
255
|
+
kind: "shader",
|
|
256
|
+
category: "transition",
|
|
257
|
+
uses: ["transition"],
|
|
258
|
+
description: "Dissolves from one picture to the next.",
|
|
259
|
+
tags: ["dissolve", "mix"],
|
|
260
|
+
transparent: false,
|
|
261
|
+
loop: false,
|
|
262
|
+
duration: 1e3,
|
|
263
|
+
controls: {
|
|
264
|
+
type: "object",
|
|
265
|
+
additionalProperties: false,
|
|
266
|
+
properties: { curve: { type: "string", enum: ["linear", "smooth"], description: "Mix curve: linear, or smooth (ease in and out). Default smooth." } }
|
|
267
|
+
},
|
|
268
|
+
defaults: { curve: "smooth" },
|
|
269
|
+
presets: [],
|
|
270
|
+
inputs: [
|
|
271
|
+
{ name: "from", kind: "image", description: "The outgoing picture.", required: true },
|
|
272
|
+
{ name: "to", kind: "image", description: "The incoming picture.", required: true }
|
|
273
|
+
]
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: "fake.fade-in",
|
|
277
|
+
name: "Fade in",
|
|
278
|
+
kind: "motion",
|
|
279
|
+
category: "morph",
|
|
280
|
+
uses: ["motion"],
|
|
281
|
+
description: "Fades vector elements in from transparent, one after another.",
|
|
282
|
+
tags: ["appear", "entrance"],
|
|
283
|
+
transparent: true,
|
|
284
|
+
loop: false,
|
|
285
|
+
duration: 600,
|
|
286
|
+
controls: {
|
|
287
|
+
type: "object",
|
|
288
|
+
additionalProperties: false,
|
|
289
|
+
properties: {
|
|
290
|
+
ms: { type: "number", minimum: 0, maximum: 1e4, description: "Fade time per element (ms). Default 600." },
|
|
291
|
+
stagger: { type: "number", minimum: 0, maximum: 5e3, description: "Delay between elements (ms). Default 0." }
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
defaults: { ms: 600, stagger: 0 },
|
|
295
|
+
presets: [{ name: "Cascade", values: { ms: 400, stagger: 120 } }],
|
|
296
|
+
inputs: []
|
|
297
|
+
}
|
|
298
|
+
];
|
|
299
|
+
var summaryOf = ({ controls: _c, defaults: _d, presets: _p, inputs: _i, ...summary }) => summary;
|
|
300
|
+
var hexToRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
301
|
+
var smooth = (t) => t * t * (3 - 2 * t);
|
|
302
|
+
var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
303
|
+
function input(request, name, id, width, height) {
|
|
304
|
+
const src = request.inputs?.[name];
|
|
305
|
+
if (!src) throw new EffectUnsupportedError(`Effect "${id}" needs an input named "${name}".`, id);
|
|
306
|
+
if (!isEffectPixels(src)) throw new EffectUnsupportedError(`The fake provider only takes EffectPixels inputs ("${name}").`, id);
|
|
307
|
+
if (src.width !== width || src.height !== height) {
|
|
308
|
+
throw new EffectUnsupportedError(`Input "${name}" is ${src.width}\xD7${src.height}; the renderer is ${width}\xD7${height}.`, id);
|
|
309
|
+
}
|
|
310
|
+
return src;
|
|
311
|
+
}
|
|
312
|
+
function renderPixels(d, v, request, width, height) {
|
|
313
|
+
const out = createEffectPixels(width, height);
|
|
314
|
+
const o = out.data;
|
|
315
|
+
switch (d.id) {
|
|
316
|
+
case "fake.invert": {
|
|
317
|
+
const s = input(request, "source", d.id, width, height).data;
|
|
318
|
+
const k = v.amount;
|
|
319
|
+
for (let i = 0; i < o.length; i += 4) {
|
|
320
|
+
for (let c = 0; c < 3; c++) o[i + c] = s[i + c] + (255 - 2 * s[i + c]) * k;
|
|
321
|
+
o[i + 3] = s[i + 3];
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case "fake.tint": {
|
|
326
|
+
const s = input(request, "source", d.id, width, height).data;
|
|
327
|
+
const rgb = hexToRgb(v.color);
|
|
328
|
+
const k = v.amount;
|
|
329
|
+
for (let i = 0; i < o.length; i += 4) {
|
|
330
|
+
for (let c = 0; c < 3; c++) o[i + c] = s[i + c] + (rgb[c] - s[i + c]) * k;
|
|
331
|
+
o[i + 3] = s[i + 3];
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case "fake.gradient": {
|
|
336
|
+
const a = hexToRgb(v.from);
|
|
337
|
+
const b = hexToRgb(v.to);
|
|
338
|
+
const turn = request.time % (d.duration ?? 4e3) / (d.duration ?? 4e3) * v.spin;
|
|
339
|
+
const rad = (v.angle + turn) * Math.PI / 180;
|
|
340
|
+
const dx = Math.cos(rad);
|
|
341
|
+
const dy = Math.sin(rad);
|
|
342
|
+
const half = (Math.abs(dx) * width + Math.abs(dy) * height) / 2 || 1;
|
|
343
|
+
for (let y = 0, i = 0; y < height; y++) {
|
|
344
|
+
for (let x = 0; x < width; x++, i += 4) {
|
|
345
|
+
const t = clamp01(((x + 0.5 - width / 2) * dx + (y + 0.5 - height / 2) * dy) / (2 * half) + 0.5);
|
|
346
|
+
for (let c = 0; c < 3; c++) o[i + c] = a[c] + (b[c] - a[c]) * t;
|
|
347
|
+
o[i + 3] = 255;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
case "fake.bar": {
|
|
353
|
+
const rgb = hexToRgb(v.color);
|
|
354
|
+
const slide = v.slide;
|
|
355
|
+
const shown = slide > 0 ? smooth(clamp01(request.time / slide)) : 1;
|
|
356
|
+
const top = Math.round(height * (0.75 - v.height / 2));
|
|
357
|
+
const bottom = Math.round(height * (0.75 + v.height / 2));
|
|
358
|
+
const right = Math.round(width * 0.7 * shown);
|
|
359
|
+
for (let y = Math.max(0, top); y < Math.min(height, bottom); y++) {
|
|
360
|
+
for (let x = 0; x < right; x++) {
|
|
361
|
+
const i = (y * width + x) * 4;
|
|
362
|
+
o[i] = rgb[0];
|
|
363
|
+
o[i + 1] = rgb[1];
|
|
364
|
+
o[i + 2] = rgb[2];
|
|
365
|
+
o[i + 3] = 255;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
case "fake.crossfade": {
|
|
371
|
+
const from = input(request, "from", d.id, width, height).data;
|
|
372
|
+
const to = input(request, "to", d.id, width, height).data;
|
|
373
|
+
const p = clamp01(request.progress ?? request.time / (d.duration ?? 1e3));
|
|
374
|
+
const k = v.curve === "smooth" ? smooth(p) : p;
|
|
375
|
+
for (let i = 0; i < o.length; i++) o[i] = from[i] + (to[i] - from[i]) * k;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
default:
|
|
379
|
+
throw new EffectUnsupportedError(`"${d.id}" is a ${d.kind} effect; use applyVectorEffect.`, d.id);
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
function toFloat(p) {
|
|
384
|
+
const f = new Float32Array(p.data.length);
|
|
385
|
+
for (let i = 0; i < f.length; i += 4) {
|
|
386
|
+
f[i] = srgbToLinear(p.data[i] / 255);
|
|
387
|
+
f[i + 1] = srgbToLinear(p.data[i + 1] / 255);
|
|
388
|
+
f[i + 2] = srgbToLinear(p.data[i + 2] / 255);
|
|
389
|
+
f[i + 3] = p.data[i + 3] / 255;
|
|
390
|
+
}
|
|
391
|
+
return encodeEffectFloat({ ...p, float: f });
|
|
392
|
+
}
|
|
393
|
+
function maskResult(out, mask, source, id) {
|
|
394
|
+
if (!isEffectPixels(mask) || mask.width !== out.width || mask.height !== out.height) {
|
|
395
|
+
throw new EffectUnsupportedError("The mask must be EffectPixels the size of the renderer.", id);
|
|
396
|
+
}
|
|
397
|
+
const o = out.data;
|
|
398
|
+
for (let i = 0; i < o.length; i += 4) {
|
|
399
|
+
const k = mask.data[i + 3] / 255;
|
|
400
|
+
if (source) for (let c = 0; c < 4; c++) o[i + c] = source.data[i + c] + (o[i + c] - source.data[i + c]) * k;
|
|
401
|
+
else o[i + 3] *= k;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function createFakeEffectProvider(options = {}) {
|
|
405
|
+
const capabilities = {
|
|
406
|
+
supportsWorker: false,
|
|
407
|
+
supportsFloat: true,
|
|
408
|
+
supportsMask: true,
|
|
409
|
+
supportsHostImages: false,
|
|
410
|
+
supportsVector: true,
|
|
411
|
+
maxSize: 8192,
|
|
412
|
+
...options.capabilities
|
|
413
|
+
};
|
|
414
|
+
const byId = new Map(DESCRIPTIONS.map((d) => [d.id, d]));
|
|
415
|
+
const get = (id) => {
|
|
416
|
+
const d = byId.get(id);
|
|
417
|
+
if (!d) throw new UnknownEffectError(id, "the fake provider");
|
|
418
|
+
return d;
|
|
419
|
+
};
|
|
420
|
+
const summaries = DESCRIPTIONS.map(summaryOf);
|
|
421
|
+
const provider = {
|
|
422
|
+
id: "fake",
|
|
423
|
+
name: "Fake effects",
|
|
424
|
+
version: "1",
|
|
425
|
+
capabilities,
|
|
426
|
+
list: (o) => searchEffectSummaries(summaries, "", o),
|
|
427
|
+
search: (q, o) => searchEffectSummaries(summaries, q, o),
|
|
428
|
+
describe: (id) => structuredClone(get(id)),
|
|
429
|
+
createRenderer(id, ro) {
|
|
430
|
+
const d = get(id);
|
|
431
|
+
if (d.kind === "motion") throw new EffectUnsupportedError(`"${id}" is a motion effect; use applyVectorEffect.`, id);
|
|
432
|
+
const { width, height } = ro;
|
|
433
|
+
const depth = ro.depth ?? 8;
|
|
434
|
+
if (!(width > 0 && height > 0) || width > capabilities.maxSize || height > capabilities.maxSize) {
|
|
435
|
+
throw new EffectUnsupportedError(`Renderer size ${width}\xD7${height} is outside 1\u2013${capabilities.maxSize}.`, id);
|
|
436
|
+
}
|
|
437
|
+
let disposed = false;
|
|
438
|
+
return {
|
|
439
|
+
effectId: id,
|
|
440
|
+
width,
|
|
441
|
+
height,
|
|
442
|
+
depth,
|
|
443
|
+
async render(request) {
|
|
444
|
+
if (disposed) throw new EffectDisposedError(id);
|
|
445
|
+
if (!Number.isFinite(request.time) || request.time < 0) {
|
|
446
|
+
throw new EffectUnsupportedError(`time must be a finite number of ms \u2265 0, got ${request.time}.`, id);
|
|
447
|
+
}
|
|
448
|
+
const values = resolveEffectValues(d, request.values);
|
|
449
|
+
options.onRender?.(id, request);
|
|
450
|
+
const out = renderPixels(d, values, request, width, height);
|
|
451
|
+
if (request.mask) {
|
|
452
|
+
if (!capabilities.supportsMask) throw new EffectUnsupportedError("This provider does not take masks.", id);
|
|
453
|
+
const source = d.uses.includes("filter") ? request.inputs?.source : void 0;
|
|
454
|
+
maskResult(out, request.mask, source, id);
|
|
455
|
+
}
|
|
456
|
+
return depth > 8 && capabilities.supportsFloat ? toFloat(out) : out;
|
|
457
|
+
},
|
|
458
|
+
dispose() {
|
|
459
|
+
disposed = true;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
},
|
|
463
|
+
applyVectorEffect(id, target, values) {
|
|
464
|
+
const d = get(id);
|
|
465
|
+
if (d.kind !== "motion" && d.kind !== "vector") throw new EffectUnsupportedError(`"${id}" is a ${d.kind} effect, not a vector one.`, id);
|
|
466
|
+
const v = resolveEffectValues(d, values);
|
|
467
|
+
const svg = typeof target === "string" ? target : target.svg;
|
|
468
|
+
const ids = typeof target === "string" || !target.nodeIds ? [...svg.matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]) : [...target.nodeIds];
|
|
469
|
+
const ms = v.ms;
|
|
470
|
+
const stagger = v.stagger;
|
|
471
|
+
return {
|
|
472
|
+
svg,
|
|
473
|
+
animate: ids.map((elementId, i) => ({ id: elementId, delay: i * stagger, ms, attrs: { opacity: [0, 1] } })),
|
|
474
|
+
duration: ms + Math.max(0, ids.length - 1) * stagger,
|
|
475
|
+
loop: false
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
return provider;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/effects/ui.ts
|
|
483
|
+
var EFFECT_USE_LABELS = {
|
|
484
|
+
filter: "Filter",
|
|
485
|
+
transition: "Transition",
|
|
486
|
+
generator: "Generator",
|
|
487
|
+
overlay: "Overlay",
|
|
488
|
+
title: "Title",
|
|
489
|
+
motion: "Motion",
|
|
490
|
+
interactive: "Interactive"
|
|
491
|
+
};
|
|
492
|
+
var HEX2 = /^#[0-9a-f]{6}([0-9a-f]{2})?$/i;
|
|
493
|
+
var typeOf = (s) => Array.isArray(s.type) ? s.type.find((t) => t !== "null") : s.type;
|
|
494
|
+
function effectControlField(name, schema, fallback) {
|
|
495
|
+
const base = { id: name, ...schema.title ? { label: schema.title } : {}, ...schema.description ? { description: schema.description } : {} };
|
|
496
|
+
const def = schema.default ?? fallback;
|
|
497
|
+
const type = typeOf(schema);
|
|
498
|
+
if (Array.isArray(schema.enum) && schema.enum.length) {
|
|
499
|
+
const options = schema.enum.filter((v) => typeof v === "string" || typeof v === "number");
|
|
500
|
+
return { ...base, type: "choice", options, default: typeof def === "string" || typeof def === "number" ? def : options[0], control: options.length <= 4 ? "segmented" : "select" };
|
|
501
|
+
}
|
|
502
|
+
if (type === "number" || type === "integer") {
|
|
503
|
+
const min = schema.minimum ?? schema.exclusiveMinimum;
|
|
504
|
+
const max = schema.maximum ?? schema.exclusiveMaximum;
|
|
505
|
+
return { ...base, type: type === "integer" ? "int" : "number", default: typeof def === "number" ? def : min ?? 0, ...min !== void 0 ? { min } : {}, ...max !== void 0 ? { max } : {}, ...schema.multipleOf ? { step: schema.multipleOf } : {} };
|
|
506
|
+
}
|
|
507
|
+
if (type === "boolean") return { ...base, type: "boolean", default: def === true };
|
|
508
|
+
if (type === "string") {
|
|
509
|
+
if (typeof def === "string" && HEX2.test(def)) return { ...base, type: "color", alpha: def.length === 9, default: def };
|
|
510
|
+
return { ...base, type: "text", default: typeof def === "string" ? def : "", ...schema.maxLength ? { maxLength: schema.maxLength } : {} };
|
|
511
|
+
}
|
|
512
|
+
if (type === "array" && Array.isArray(def) && def.length >= 2 && def.length <= 4 && def.every((n) => typeof n === "number")) {
|
|
513
|
+
const items = schema.items ?? {};
|
|
514
|
+
return { ...base, type: "vector", size: def.length, default: [...def], ...items.minimum !== void 0 ? { min: items.minimum } : {}, ...items.maximum !== void 0 ? { max: items.maximum } : {} };
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
function effectParameterFields(description) {
|
|
519
|
+
return Object.entries(description.controls.properties).map(([name, schema]) => effectControlField(name, schema, description.defaults[name])).filter((f) => !!f);
|
|
520
|
+
}
|
|
521
|
+
var effectFieldValue = (field, value) => value === void 0 || value === null || typeof value === "object" && !Array.isArray(value) ? field.default : value;
|
|
522
|
+
function effectSamplePicture(width, height, variant = 0) {
|
|
523
|
+
const p = createEffectPixels(width, height);
|
|
524
|
+
for (let y = 0, i = 0; y < height; y++) {
|
|
525
|
+
for (let x = 0; x < width; x++, i += 4) {
|
|
526
|
+
const u = x / Math.max(1, width - 1);
|
|
527
|
+
const v = y / Math.max(1, height - 1);
|
|
528
|
+
const check = (Math.floor(u * 4) + Math.floor(v * 4)) % 2;
|
|
529
|
+
if (variant === 0) p.data.set([Math.round(40 + 200 * u), Math.round(80 + 120 * v), Math.round(200 - 120 * u) + check * 30, 255], i);
|
|
530
|
+
else p.data.set([Math.round(230 - 60 * v), Math.round(190 - 100 * u), 60 + check * 40, 255], i);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return p;
|
|
534
|
+
}
|
|
535
|
+
async function renderEffectThumbnail(provider, effect, size = { width: 96, height: 54 }) {
|
|
536
|
+
const d = "inputs" in effect ? effect : await provider.describe(effect.id);
|
|
537
|
+
const renderer = await provider.createRenderer(d.id, size);
|
|
538
|
+
try {
|
|
539
|
+
const time = d.duration ? d.duration / 2 : 1e3;
|
|
540
|
+
const inputs = {};
|
|
541
|
+
for (const input2 of d.inputs) if (input2.kind === "image") inputs[input2.name] = effectSamplePicture(size.width, size.height, input2.name === "to" ? 1 : 0);
|
|
542
|
+
return await renderer.render({ time, inputs, ...d.uses.includes("transition") ? { progress: 0.5 } : {} });
|
|
543
|
+
} finally {
|
|
544
|
+
renderer.dispose();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function groupEffectsByCategory(effects) {
|
|
548
|
+
const groups = /* @__PURE__ */ new Map();
|
|
549
|
+
for (const e of effects) {
|
|
550
|
+
const g = groups.get(e.category);
|
|
551
|
+
if (g) g.push(e);
|
|
552
|
+
else groups.set(e.category, [e]);
|
|
553
|
+
}
|
|
554
|
+
return [...groups].map(([category, list]) => ({ category, effects: list }));
|
|
555
|
+
}
|
|
556
|
+
export {
|
|
557
|
+
EFFECT_ERROR_CODES,
|
|
558
|
+
EFFECT_KINDS,
|
|
559
|
+
EFFECT_USES,
|
|
560
|
+
EFFECT_USE_LABELS,
|
|
561
|
+
EffectControlError,
|
|
562
|
+
EffectDisposedError,
|
|
563
|
+
EffectError,
|
|
564
|
+
EffectUnsupportedError,
|
|
565
|
+
EffectsSlot,
|
|
566
|
+
NoEffectsProviderError,
|
|
567
|
+
UnknownEffectError,
|
|
568
|
+
applyEffectMask,
|
|
569
|
+
checkEffectProvider,
|
|
570
|
+
createEffectPixels,
|
|
571
|
+
createEffectToolProvider,
|
|
572
|
+
createFakeEffectProvider,
|
|
573
|
+
effectControlField,
|
|
574
|
+
effectFieldValue,
|
|
575
|
+
effectParameterFields,
|
|
576
|
+
effectSamplePicture,
|
|
577
|
+
effectValueErrors,
|
|
578
|
+
encodeEffectFloat,
|
|
579
|
+
groupEffectsByCategory,
|
|
580
|
+
isEffectError,
|
|
581
|
+
isEffectPixels,
|
|
582
|
+
linearToSrgb,
|
|
583
|
+
renderEffectThumbnail,
|
|
584
|
+
resolveEffectValues,
|
|
585
|
+
searchEffectSummaries,
|
|
586
|
+
srgbToLinear
|
|
587
|
+
};
|
|
588
|
+
//# sourceMappingURL=index.js.map
|