@pixerate/editor 0.241.0 → 0.243.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/animation-editor/controller.d.cts +10 -0
- package/dist/animation-editor/controller.d.ts +10 -0
- package/dist/animation-editor/effects.d.cts +1 -1
- package/dist/animation-editor/effects.d.ts +1 -1
- package/dist/animation-editor/index.cjs +195 -146
- package/dist/animation-editor/index.js +63 -14
- package/dist/{brushstrokeRender-MOYGEXOC.js → brushstrokeRender-274HGNZR.js} +3 -3
- package/dist/{brushstrokeRender-UKADOV5D.cjs → brushstrokeRender-C7RJZPMC.cjs} +5 -5
- package/dist/canvas/index.cjs +15 -15
- package/dist/canvas/index.js +2 -2
- package/dist/{chunk-KCMV2534.cjs → chunk-3WOZEBXZ.cjs} +2 -2
- package/dist/{chunk-ZDPLZDTT.js → chunk-4ZHXLFXS.js} +1 -1
- package/dist/{chunk-5BR7LC24.cjs → chunk-5FZS7J7C.cjs} +50 -4
- package/dist/{chunk-ILM6GYAX.js → chunk-6L3WWIDW.js} +1 -1
- package/dist/{chunk-A7PQNIBX.cjs → chunk-7V7QMFTI.cjs} +14 -14
- package/dist/{chunk-JAOSL334.cjs → chunk-GHCSC2WP.cjs} +38 -16
- package/dist/{chunk-UDBDYZET.js → chunk-H4Z72Z4Z.js} +1 -1
- package/dist/{chunk-XBZK47LV.js → chunk-HQZHOPNE.js} +49 -3
- package/dist/{chunk-QP2B3MID.js → chunk-IXI5KATU.js} +1 -1
- package/dist/{chunk-JWCKJNDR.cjs → chunk-K5DS2JTA.cjs} +6 -6
- package/dist/{chunk-HGGJ36HR.cjs → chunk-KK2FWMA2.cjs} +1 -1
- package/dist/{chunk-WDDUPWVG.js → chunk-L3JFOTBQ.js} +1 -1
- package/dist/{chunk-SIK3LT6M.js → chunk-OFH6Q67U.js} +28 -6
- package/dist/{chunk-AFLMXITL.cjs → chunk-OONFIFEU.cjs} +23 -23
- package/dist/{chunk-HW72ABGR.cjs → chunk-R2FVAKFH.cjs} +86 -86
- package/dist/{chunk-G3RUKJ5C.js → chunk-RXZL4NG5.js} +1 -1
- package/dist/{chunk-NE6OOKWJ.js → chunk-SRXWDOUI.js} +1 -1
- package/dist/{chunk-7F4G74UQ.cjs → chunk-UV7OOZJQ.cjs} +35 -35
- package/dist/{chunk-TEFQISBY.js → chunk-X3ZINLIP.js} +1 -1
- package/dist/{chunk-ZQIWBJGH.cjs → chunk-XNZFVYC2.cjs} +7 -7
- package/dist/effects/fake.d.cts +4 -0
- package/dist/effects/fake.d.ts +4 -0
- package/dist/effects/helpers.d.cts +6 -0
- package/dist/effects/helpers.d.ts +6 -0
- package/dist/effects/index.cjs +222 -94
- package/dist/effects/index.js +131 -3
- package/dist/effects/types.d.cts +31 -0
- package/dist/effects/types.d.ts +31 -0
- package/dist/image-editor/index.cjs +788 -788
- package/dist/image-editor/index.js +5 -5
- package/dist/paint-editor/brushes.d.cts +18 -2
- package/dist/paint-editor/brushes.d.ts +18 -2
- package/dist/paint-editor/controller.d.cts +28 -0
- package/dist/paint-editor/controller.d.ts +28 -0
- package/dist/paint-editor/document.d.cts +3 -0
- package/dist/paint-editor/document.d.ts +3 -0
- package/dist/paint-editor/index.cjs +1017 -499
- package/dist/paint-editor/index.js +562 -44
- package/dist/paint-editor/light.d.cts +74 -0
- package/dist/paint-editor/light.d.ts +74 -0
- package/dist/paint-editor/stroke.d.cts +14 -0
- package/dist/paint-editor/stroke.d.ts +14 -0
- package/dist/paint-editor/thick-tools.d.cts +2 -2
- package/dist/paint-editor/thick-tools.d.ts +2 -2
- package/dist/paint-editor/thick.d.cts +5 -1
- package/dist/paint-editor/thick.d.ts +5 -1
- package/dist/paint-editor/ui.d.cts +2 -0
- package/dist/paint-editor/ui.d.ts +2 -0
- package/dist/vector/index.cjs +968 -968
- package/dist/vector/index.js +5 -5
- package/dist/video-editor/controller.d.cts +49 -1
- package/dist/video-editor/controller.d.ts +49 -1
- package/dist/video-editor/effect-path.d.cts +66 -0
- package/dist/video-editor/effect-path.d.ts +66 -0
- package/dist/video-editor/index.cjs +616 -427
- package/dist/video-editor/index.d.cts +1 -0
- package/dist/video-editor/index.d.ts +1 -0
- package/dist/video-editor/index.js +193 -4
- package/package.json +1 -1
- package/skills/pixerate-animation-editor/SKILL.md +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +9 -9
- package/skills/pixerate-paint-editor/references/canvas-hosted.md +8 -0
- package/skills/pixerate-paint-editor/references/light-paint.md +31 -0
- package/skills/pixerate-paint-oil-impasto/SKILL.md +1 -0
- package/skills/pixerate-video-editor/SKILL.md +9 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createEffectAuthoringSession
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-X3ZINLIP.js";
|
|
4
4
|
import {
|
|
5
5
|
VIDEO_ADJUSTMENT_SCOPES,
|
|
6
6
|
VIDEO_BLEND_MODES,
|
|
@@ -149,7 +149,7 @@ import {
|
|
|
149
149
|
videoTransitionEase,
|
|
150
150
|
videoValueAt,
|
|
151
151
|
videoWipeRect
|
|
152
|
-
} from "../chunk-
|
|
152
|
+
} from "../chunk-L3JFOTBQ.js";
|
|
153
153
|
import {
|
|
154
154
|
namedWithSuggestion
|
|
155
155
|
} from "../chunk-EUKY4KFX.js";
|
|
@@ -159,7 +159,7 @@ import {
|
|
|
159
159
|
effectSequenceTiming,
|
|
160
160
|
effectTemplateTools,
|
|
161
161
|
fillEffectTemplate
|
|
162
|
-
} from "../chunk-
|
|
162
|
+
} from "../chunk-RXZL4NG5.js";
|
|
163
163
|
import "../chunk-EHLOCVWB.js";
|
|
164
164
|
import {
|
|
165
165
|
CAMERA_MOVE_MAX_MS,
|
|
@@ -168,9 +168,10 @@ import {
|
|
|
168
168
|
cameraMoveParts,
|
|
169
169
|
createEffectToolProvider,
|
|
170
170
|
isEffectError,
|
|
171
|
+
pathControlsOf,
|
|
171
172
|
resolveCameraMoveSpec,
|
|
172
173
|
sampleEffectCameraMove
|
|
173
|
-
} from "../chunk-
|
|
174
|
+
} from "../chunk-OFH6Q67U.js";
|
|
174
175
|
import "../chunk-V2ZCJRDM.js";
|
|
175
176
|
import {
|
|
176
177
|
defineEditorDocument
|
|
@@ -2205,6 +2206,73 @@ var VideoAudioPeaksCache = class {
|
|
|
2205
2206
|
}
|
|
2206
2207
|
};
|
|
2207
2208
|
|
|
2209
|
+
// src/video-editor/effect-path.ts
|
|
2210
|
+
function videoClipPicture(project, clip) {
|
|
2211
|
+
const asset = clip.type === "media" ? project.assets.find((a) => a.id === clip.asset) : void 0;
|
|
2212
|
+
const size = asset ? { width: asset.width ?? project.width, height: asset.height ?? project.height } : { width: project.width, height: project.height };
|
|
2213
|
+
const fit = clip.fit ?? (clip.type === "media" || clip.type === "sequence" ? "contain" : "fill");
|
|
2214
|
+
const rect = placeVideoRect(project, size, clip.type === "text" ? "fill" : fit, clip.transform, 0);
|
|
2215
|
+
return { size, rect, rotation: videoTransformAt(clip.transform, 0).rotation };
|
|
2216
|
+
}
|
|
2217
|
+
function turn(x, y, cx, cy, deg) {
|
|
2218
|
+
if (!deg) return [x, y];
|
|
2219
|
+
const a = deg * Math.PI / 180;
|
|
2220
|
+
const dx = x - cx;
|
|
2221
|
+
const dy = y - cy;
|
|
2222
|
+
return [cx + dx * Math.cos(a) - dy * Math.sin(a), cy + dx * Math.sin(a) + dy * Math.cos(a)];
|
|
2223
|
+
}
|
|
2224
|
+
function framePointsToPicture(points, picture) {
|
|
2225
|
+
const { rect, size, rotation } = picture;
|
|
2226
|
+
const cx = rect.x + rect.width / 2;
|
|
2227
|
+
const cy = rect.y + rect.height / 2;
|
|
2228
|
+
const out = [];
|
|
2229
|
+
for (let i = 0; i + 1 < points.length; i += 2) {
|
|
2230
|
+
const [x, y] = turn(points[i], points[i + 1], cx, cy, -rotation);
|
|
2231
|
+
out.push(Math.round((x - rect.x) / rect.width * size.width * 100) / 100, Math.round((y - rect.y) / rect.height * size.height * 100) / 100);
|
|
2232
|
+
}
|
|
2233
|
+
return out;
|
|
2234
|
+
}
|
|
2235
|
+
function videoClipMaskOutline(project, clip) {
|
|
2236
|
+
if (!clip.mask) return null;
|
|
2237
|
+
const picture = videoClipPicture(project, clip);
|
|
2238
|
+
const { rect, rotation } = picture;
|
|
2239
|
+
const cx = rect.x + rect.width / 2;
|
|
2240
|
+
const cy = rect.y + rect.height / 2;
|
|
2241
|
+
const polygon = videoMaskPolygon(clip.mask, rect).map(([x, y]) => turn(x, y, cx, cy, rotation));
|
|
2242
|
+
if (polygon.length < 2) return null;
|
|
2243
|
+
return [...polygon, polygon[0]].flatMap(([x, y]) => [Math.round(x * 100) / 100, Math.round(y * 100) / 100]);
|
|
2244
|
+
}
|
|
2245
|
+
var VideoPathGesture = class {
|
|
2246
|
+
constructor(frame, spacing = 3) {
|
|
2247
|
+
this.frame = frame;
|
|
2248
|
+
this.spacing = spacing;
|
|
2249
|
+
}
|
|
2250
|
+
frame;
|
|
2251
|
+
spacing;
|
|
2252
|
+
points = [];
|
|
2253
|
+
drawing = false;
|
|
2254
|
+
at(e, box) {
|
|
2255
|
+
return [Math.round((e.clientX - box.left) / box.width * this.frame.width * 100) / 100, Math.round((e.clientY - box.top) / box.height * this.frame.height * 100) / 100];
|
|
2256
|
+
}
|
|
2257
|
+
down(e, box) {
|
|
2258
|
+
this.drawing = true;
|
|
2259
|
+
this.points = this.at(e, box);
|
|
2260
|
+
}
|
|
2261
|
+
move(e, box) {
|
|
2262
|
+
if (!this.drawing) return false;
|
|
2263
|
+
const [x, y] = this.at(e, box);
|
|
2264
|
+
const n = this.points.length;
|
|
2265
|
+
if (Math.hypot(x - this.points[n - 2], y - this.points[n - 1]) < this.spacing) return false;
|
|
2266
|
+
this.points.push(x, y);
|
|
2267
|
+
return true;
|
|
2268
|
+
}
|
|
2269
|
+
/** Ends the stroke; returns its points, or `null` for a tap (fewer than two points). */
|
|
2270
|
+
up() {
|
|
2271
|
+
this.drawing = false;
|
|
2272
|
+
return this.points.length >= 4 ? [...this.points] : null;
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
|
|
2208
2276
|
// src/video-editor/plan.ts
|
|
2209
2277
|
function clipIndex(project) {
|
|
2210
2278
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -3660,6 +3728,7 @@ var VideoEditorController = class _VideoEditorController {
|
|
|
3660
3728
|
previewScale: this.previewScale,
|
|
3661
3729
|
effectsProvider: this.effects.provider,
|
|
3662
3730
|
plans: this.plans,
|
|
3731
|
+
pathDrawing: this.pathDrawing,
|
|
3663
3732
|
revision: this.revision
|
|
3664
3733
|
};
|
|
3665
3734
|
}
|
|
@@ -4176,6 +4245,81 @@ var VideoEditorController = class _VideoEditorController {
|
|
|
4176
4245
|
return { edit: { op: "set_transition", clip, transition: { ...t, effect: { ...t.effect, values: merged } } }, label: `Change transition ${d.name}`, clip };
|
|
4177
4246
|
}, options.merge);
|
|
4178
4247
|
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Gives an effect that draws along a path (see `pathControlsOf`) its path: on an effect clip
|
|
4250
|
+
* (`instance` "generator") or a clip effect (its id). The path is frame points (`points`
|
|
4251
|
+
* `[x0, y0, …]`, mapped into the pixels of the picture the effect renders: the project frame for
|
|
4252
|
+
* an effect clip, the clip's own picture for a clip effect), SVG path data (`d`, which the effect
|
|
4253
|
+
* fits as its control says; `""` clears it), the outline of a clip's mask (`mask`), or words the
|
|
4254
|
+
* provider writes as a single-stroke path (`text`). `control` picks the path control when there
|
|
4255
|
+
* are several. Checked before anything changes; one undo step. Returns the edit, the control and
|
|
4256
|
+
* the value it took.
|
|
4257
|
+
*/
|
|
4258
|
+
async setEffectPath(clip, instance, path, options = {}) {
|
|
4259
|
+
const fail2 = (error) => ({ ok: false, error });
|
|
4260
|
+
try {
|
|
4261
|
+
const current = this.clipOrThrow(clip);
|
|
4262
|
+
const effect = instance === "generator" ? current.type === "effect" ? current.effect : null : current.effects?.find((e) => e.id === instance)?.effect;
|
|
4263
|
+
if (!effect) {
|
|
4264
|
+
const ids = ["generator"].filter(() => current.type === "effect").concat((current.effects ?? []).map((e) => e.id));
|
|
4265
|
+
return fail2(`Clip "${clip}" has no effect "${instance}"; its effects are ${ids.map((i) => `"${i}"`).join(", ") || "none"} ("generator" is an effect clip's own picture).`);
|
|
4266
|
+
}
|
|
4267
|
+
const d = await this.describeEffect(effect);
|
|
4268
|
+
const controls = pathControlsOf(d);
|
|
4269
|
+
if (!controls.length) return fail2(`"${d.id}" does not draw along a path. Find one that does with search_effects accepts ["path"].`);
|
|
4270
|
+
const control = options.control ?? (controls.length === 1 ? controls[0] : void 0);
|
|
4271
|
+
if (!control) return fail2(`"${d.id}" has several path controls (${controls.join(", ")}); pass control.`);
|
|
4272
|
+
if (!controls.includes(control)) return fail2(`"${control}" is not a path control of "${d.id}"; its path controls are ${controls.join(", ")}.`);
|
|
4273
|
+
const picture = videoClipPicture(this.project, current);
|
|
4274
|
+
let value;
|
|
4275
|
+
if ("points" in path) {
|
|
4276
|
+
const pts = path.points;
|
|
4277
|
+
if (!Array.isArray(pts) || pts.length < 4 || pts.length % 2 || !pts.every(Number.isFinite)) return fail2("points must be at least two points as [x0, y0, x1, y1, \u2026] in frame pixels.");
|
|
4278
|
+
value = framePointsToPicture(pts, picture);
|
|
4279
|
+
} else if ("d" in path) {
|
|
4280
|
+
if (typeof path.d !== "string") return fail2('d must be SVG path data (a string), or "" to clear the path.');
|
|
4281
|
+
value = path.d;
|
|
4282
|
+
} else if ("mask" in path) {
|
|
4283
|
+
const from = findVideoClip(this.project, path.mask.clip)?.clip;
|
|
4284
|
+
if (!from) return fail2(`There is no clip "${path.mask.clip}" to take a mask from.`);
|
|
4285
|
+
const outline = videoClipMaskOutline(this.project, from);
|
|
4286
|
+
if (!outline) return fail2(`Clip "${path.mask.clip}" has no mask; add one with set_video_clip_mask, or pass points or d.`);
|
|
4287
|
+
value = framePointsToPicture(outline, picture);
|
|
4288
|
+
} else if ("text" in path) {
|
|
4289
|
+
const provider = this.effects.require();
|
|
4290
|
+
if (!provider.pathFromText) return fail2(`The effects provider "${provider.id}" cannot write words as a path; pass points or d.`);
|
|
4291
|
+
if (typeof path.text !== "string" || !path.text.trim()) return fail2("text is empty; pass the words to write.");
|
|
4292
|
+
value = await provider.pathFromText(path.text, { ...path.font ? { font: path.font } : {}, ...path.align ? { align: path.align } : {} });
|
|
4293
|
+
} else return fail2("Pass the path as one of points, d, mask or text.");
|
|
4294
|
+
const r = instance === "generator" ? await this.updateEffectValues(clip, "generator", { [control]: value }) : await this.updateClipEffect(clip, instance, { values: { [control]: value } });
|
|
4295
|
+
return r.ok ? { ...r, control, value } : r;
|
|
4296
|
+
} catch (e) {
|
|
4297
|
+
return fail2(e instanceof Error ? e.message : String(e));
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
pathDrawing = null;
|
|
4301
|
+
/**
|
|
4302
|
+
* Starts drawing a path on the preview for an effect that draws along one (the preview then
|
|
4303
|
+
* takes the pointer; `finishPathDrawing` with the frame points applies it). Editor state.
|
|
4304
|
+
*/
|
|
4305
|
+
startPathDrawing(target) {
|
|
4306
|
+
this.pathDrawing = { ...target };
|
|
4307
|
+
this.notify();
|
|
4308
|
+
}
|
|
4309
|
+
/** Stops drawing a path without changing anything. */
|
|
4310
|
+
cancelPathDrawing() {
|
|
4311
|
+
if (!this.pathDrawing) return;
|
|
4312
|
+
this.pathDrawing = null;
|
|
4313
|
+
this.notify();
|
|
4314
|
+
}
|
|
4315
|
+
/** Applies the drawn path (frame points) to the effect being drawn for, and stops drawing. One undo step. */
|
|
4316
|
+
async finishPathDrawing(points) {
|
|
4317
|
+
const target = this.pathDrawing;
|
|
4318
|
+
this.pathDrawing = null;
|
|
4319
|
+
this.notify();
|
|
4320
|
+
if (!target) return { ok: false, error: "No path is being drawn; start one from the effect's Draw path button." };
|
|
4321
|
+
return this.setEffectPath(target.clip, target.instance, { points }, { control: target.control });
|
|
4322
|
+
}
|
|
4179
4323
|
/** Keys for one numeric control of a clip effect (clip-relative ms), or `null` to stop animating it (keeping its value at the clip's start). */
|
|
4180
4324
|
setClipEffectKeyframes(clip, instance, control, keys) {
|
|
4181
4325
|
const current = findVideoClip(this.project, clip)?.clip;
|
|
@@ -6117,6 +6261,47 @@ function createVideoEffectsToolProvider(controller, options = {}) {
|
|
|
6117
6261
|
return { clip: args.clip, transition: clipOf(args.clip).transition, effects: videoClipAppliedEffects(clipOf(args.clip)) };
|
|
6118
6262
|
}
|
|
6119
6263
|
},
|
|
6264
|
+
{
|
|
6265
|
+
name: "set_video_effect_path",
|
|
6266
|
+
title: "Run an effect along a path",
|
|
6267
|
+
description: `Gives an effect that draws along a path (search_effects with accepts ["path"]; describe_effect lists its pathControls) the path to draw along: on an effect clip (instance "generator", added with add_video_effect_clip) or a clip effect (its id, from apply_effect). Pass exactly one of: points (frame pixels from the top left, as on the preview; mapped into the pixels of the picture the effect renders, so a clip effect lines up with its moved or scaled clip), d (SVG path data the effect fits into its picture; "" clears the path), mask (use the outline of a clip's mask, in frame pixels), or text (words the provider writes as a single-stroke path, when it can). Refuses an effect without a path control, naming how to find one. One undo step. Returns the clip, instance, the control set and the value it took (points in picture pixels, or path data).`,
|
|
6268
|
+
inputSchema: {
|
|
6269
|
+
type: "object",
|
|
6270
|
+
properties: {
|
|
6271
|
+
clip: { type: "string", minLength: 1, description: "The clip id from get_video_project." },
|
|
6272
|
+
instance: { type: "string", minLength: 1, description: `"generator" for an effect clip's own picture, or a clip effect id from list_applied_effects.` },
|
|
6273
|
+
control: { type: "string", minLength: 1, description: "Which path control (default: the effect's only one; describe_effect pathControls lists them)." },
|
|
6274
|
+
points: { type: "array", minItems: 4, maxItems: 4096, items: { type: "number", description: "x or y, frame pixels." }, description: "The path as frame points [x0, y0, x1, y1, \u2026] in pixels from the frame's top left." },
|
|
6275
|
+
d: { type: "string", description: `SVG path data, fitted into the effect's picture as its control says; "" clears the path.` },
|
|
6276
|
+
mask: { type: "object", properties: { clip: { type: "string", minLength: 1, description: "The clip whose mask outline to use." } }, required: ["clip"], additionalProperties: false, description: "Use the outline of this clip's mask (set_video_clip_mask), in frame pixels." },
|
|
6277
|
+
text: {
|
|
6278
|
+
type: "object",
|
|
6279
|
+
properties: {
|
|
6280
|
+
text: { type: "string", minLength: 1, description: "The words to write." },
|
|
6281
|
+
font: { type: "string", minLength: 1, description: "A single-stroke font the provider offers (default its first)." },
|
|
6282
|
+
align: { type: "string", enum: ["start", "middle", "end"], description: "Where the line sits (default start)." }
|
|
6283
|
+
},
|
|
6284
|
+
required: ["text"],
|
|
6285
|
+
additionalProperties: false,
|
|
6286
|
+
description: "Words the provider writes as a single-stroke path (a signature, handwriting)."
|
|
6287
|
+
}
|
|
6288
|
+
},
|
|
6289
|
+
required: ["clip", "instance"],
|
|
6290
|
+
oneOf: [{ required: ["points"] }, { required: ["d"] }, { required: ["mask"] }, { required: ["text"] }],
|
|
6291
|
+
additionalProperties: false
|
|
6292
|
+
},
|
|
6293
|
+
annotations: { title: "Run an effect along a path", ...ctx.annotations.write },
|
|
6294
|
+
handler: async (args) => {
|
|
6295
|
+
ctx.requireProvider();
|
|
6296
|
+
const given = ["points", "d", "mask", "text"].filter((k2) => args[k2] !== void 0);
|
|
6297
|
+
if (given.length !== 1) throw new Error(`Pass exactly one of points, d, mask or text${given.length ? ` (got ${given.join(", ")})` : ""}.`);
|
|
6298
|
+
const k = given[0];
|
|
6299
|
+
const path = k === "text" ? { ...args.text } : { [k]: args[k] };
|
|
6300
|
+
const r = await controller.setEffectPath(args.clip, args.instance, path, { control: args.control });
|
|
6301
|
+
if (!r.ok) throw new Error(r.error);
|
|
6302
|
+
return { clip: args.clip, instance: args.instance, control: r.control, value: r.value };
|
|
6303
|
+
}
|
|
6304
|
+
},
|
|
6120
6305
|
{
|
|
6121
6306
|
name: "set_video_effect_keyframes",
|
|
6122
6307
|
title: "Keyframe an effect control",
|
|
@@ -6675,6 +6860,7 @@ export {
|
|
|
6675
6860
|
VideoEffectsEngine,
|
|
6676
6861
|
VideoFrameCache,
|
|
6677
6862
|
VideoLutCache,
|
|
6863
|
+
VideoPathGesture,
|
|
6678
6864
|
VideoPreviewEngine,
|
|
6679
6865
|
VideoPreviewPixels,
|
|
6680
6866
|
allVideoClipIds,
|
|
@@ -6718,6 +6904,7 @@ export {
|
|
|
6718
6904
|
formatVideoSeconds,
|
|
6719
6905
|
formatVideoTimecode,
|
|
6720
6906
|
formatVideoTimecodeFrames,
|
|
6907
|
+
framePointsToPicture,
|
|
6721
6908
|
isVideoDropFrameRate,
|
|
6722
6909
|
isVideoKeyframes,
|
|
6723
6910
|
measureVideoLoudness,
|
|
@@ -6809,7 +6996,9 @@ export {
|
|
|
6809
6996
|
videoClipHeading,
|
|
6810
6997
|
videoClipKeyframes,
|
|
6811
6998
|
videoClipLabel,
|
|
6999
|
+
videoClipMaskOutline,
|
|
6812
7000
|
videoClipOverhang,
|
|
7001
|
+
videoClipPicture,
|
|
6813
7002
|
videoClipShowsWaveform,
|
|
6814
7003
|
videoClipSoundGain,
|
|
6815
7004
|
videoClipSourceRange,
|
package/package.json
CHANGED
|
@@ -161,7 +161,7 @@ refuse.
|
|
|
161
161
|
| `apply_effect` | Animate a node (`target`, a group animates as one) with a motion effect: the changes become ordinary commands, tagged with the effect, in a new shot after the last. Returns the instance id. |
|
|
162
162
|
| `update_effect` | Make an applied effect again with new `values`, in the same place (`effect` is its instance id). |
|
|
163
163
|
| `remove_effect` | Remove an applied effect: its commands, shots it alone filled and a vector effect's group. |
|
|
164
|
-
| `add_vector_effect` | Add a vector effect's own picture as an artwork group (fitted into `box`) with its motion as new shots. Returns the instance and group ids. |
|
|
164
|
+
| `add_vector_effect` | Add a vector effect's own picture as an artwork group (fitted into `box`) with its motion as new shots. For an effect that draws along a path (`search_effects` `accepts: ["path"]`: a signature, a route), `path: { node }` runs it along a path node and lands it on the node, `path: { text }` writes words. Use it when the effect should trace the path itself; use `follow` or a draw-on to move or reveal your own art. Returns the instance and group ids. |
|
|
165
165
|
| `list_camera_moves` | List the provider's camera moves for a `role` (`transition`: how the camera gets into a shot; `hold`: how it moves while the shot holds), with `durationMs` and `cut`. Returns `available: false` without camera moves. Call before `set_shot_camera_move`; `describe_effect` gives a move's controls and presets. |
|
|
166
166
|
| `set_shot_camera_move` | Give a shot a camera move (`shot`, `role`, `move`, optional `ms` for a transition, `preset`, `values`): it is saved on the shot's camera as `in` or `during`. Returns the shot's camera and the `window` (ms) the move plays in. A wrong-role id is refused naming the moves that fit. |
|
|
167
167
|
| `clear_shot_camera_move` | Remove a shot's move for a `role`, keeping its framing. Returns the shot's camera (null when nothing is left). |
|
|
@@ -159,8 +159,9 @@ read [references/wet-media.md](references/wet-media.md) for the engine and the w
|
|
|
159
159
|
|
|
160
160
|
## Oils and thick paint
|
|
161
161
|
|
|
162
|
-
Wet oil
|
|
163
|
-
`
|
|
162
|
+
Wet oil blends where the brush meets it; impasto adds relief lit by `set_lighting`: load
|
|
163
|
+
`pixerate-paint-oil-impasto` and read [references/oils.md](references/oils.md). **Light paint**
|
|
164
|
+
(neon, glowing glass: `light.amount`, the Light category): [references/light-paint.md](references/light-paint.md).
|
|
164
165
|
|
|
165
166
|
## Gradient paint
|
|
166
167
|
|
|
@@ -261,12 +262,8 @@ When to use which:
|
|
|
261
262
|
|
|
262
263
|
## Canvas-hosted layer
|
|
263
264
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
pixel: stroke points, picks, bounds, guides, stencils, stickers and wet areas alike.
|
|
267
|
-
`get_canvas_info` `bounds` says where the painting lies on that canvas; paint inside it. The
|
|
268
|
-
host may leave out tools it owns (`new_canvas`, `set_canvas`, `extend_canvas`, exports, the
|
|
269
|
-
view), so work with the tools you are given.
|
|
265
|
+
When `get_canvas_info` has `bounds`, the painting is a layer of a larger canvas and every position
|
|
266
|
+
is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hosted.md).
|
|
270
267
|
|
|
271
268
|
## Tool reference
|
|
272
269
|
|
|
@@ -375,7 +372,10 @@ view), so work with the tools you are given.
|
|
|
375
372
|
| --- | --- |
|
|
376
373
|
| `set_lighting` | Move the light for thick paint and metallic paper: `azimuth` (degrees clockwise from the top), `elevation` (5..90), `relief` (0..1). Nothing is repainted; not an undo step. Returns the lighting. Use a low elevation to show brushwork. |
|
|
377
374
|
| `get_relief` | Read a layer's relief (`bounds`, `maxHeight` 0..255, null when flat) and the lighting. Changes nothing. |
|
|
378
|
-
| `flatten_relief` | Remove a layer's relief and material, keeping its colour. One undo step. Returns whether anything was flattened. |
|
|
375
|
+
| `flatten_relief` | Remove a layer's relief and material (rim and clarity too), keeping its colour and light. One undo step. Returns whether anything was flattened. |
|
|
376
|
+
| `get_light` | Read a layer's light paint: `hasLight`, `bounds`, `peak` 0..255 and its `spill`. Changes nothing. |
|
|
377
|
+
| `set_light_spill` | How far a layer's light spills (`radius` px, 0 off) and how strongly (`strength` 0..2). Not an undo step; nothing is repainted. |
|
|
378
|
+
| `clear_light` | Take a layer's light away, keeping its colour. One undo step. |
|
|
379
379
|
|
|
380
380
|
### Stickers
|
|
381
381
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Canvas-hosted layer
|
|
2
|
+
|
|
3
|
+
A host can mount the painting as a layer of a larger canvas (`createPaintEditorToolProvider` with
|
|
4
|
+
`origin`). Then every position you pass or read back is a canvas coordinate, not a document
|
|
5
|
+
pixel: stroke points, picks, bounds, guides, stencils, stickers and wet areas alike.
|
|
6
|
+
`get_canvas_info` `bounds` says where the painting lies on that canvas; paint inside it. The
|
|
7
|
+
host may leave out tools it owns (`new_canvas`, `set_canvas`, `extend_canvas`, exports, the
|
|
8
|
+
view), so work with the tools you are given.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Light paint
|
|
2
|
+
|
|
3
|
+
Light paint is paint that gives off light: neon, light painting, glow-in-the-dark, glowing glassy
|
|
4
|
+
tubes. Any dab, particle, bristle, Mixer Brush or impasto brush lays it with `light.amount` above 0
|
|
5
|
+
(`set_brush`); the Light category presets are ready-made (`glow-gel`, `neon-marker`,
|
|
6
|
+
`light-airbrush`, `spark-trail`).
|
|
7
|
+
|
|
8
|
+
- **What it does.** Each layer keeps a light channel next to its colour. The light's colour is the
|
|
9
|
+
paint's colour at that pixel, so recolouring paint recolours its light. It glows (the paint
|
|
10
|
+
itself is brighter, whatever `set_lighting` says) and spills onto everything around and below it
|
|
11
|
+
in each stroke's own colour.
|
|
12
|
+
- **It behaves like paint.** Plain paint over glowing paint dims it; erasers remove it; smudges,
|
|
13
|
+
blenders, the knife, the Mixer Brush and pick-up brushes carry it with the colour they move. A
|
|
14
|
+
smeared neon line glows along the smear. `clear_light` takes it away and keeps the colour.
|
|
15
|
+
- **Spill** (`set_light_spill`, per layer): `radius` px (default 24; 0 turns it off) and
|
|
16
|
+
`strength` 0..2 (default 1). It is not an undo step and repaints nothing; it shows the
|
|
17
|
+
painting as committed, so a stroke's spill appears when the stroke ends. It spills onto
|
|
18
|
+
everything below, paper included, so it reads best on dark paper (`set_paper` colour).
|
|
19
|
+
- **Glassy beads** (impasto): `impasto.rim` makes a bead's light stronger at its edges, where its
|
|
20
|
+
surface turns away (backlit glass); `impasto.clarity` lets the middle of a bead show the layers
|
|
21
|
+
below while its edges stay solid. `glow-gel` uses both: paint loops with a distance-mapped
|
|
22
|
+
gradient (`set_paint`) for glowing glass tubes.
|
|
23
|
+
- **Crossings.** Beads on one layer fuse where they cross (their heights combine by maximum), so
|
|
24
|
+
clarity only shows other layers: paint one loop per layer for glassy crossings.
|
|
25
|
+
- **Spill vs a glow layer style.** Spill takes each stroke's colour and strength. For a one-colour
|
|
26
|
+
halo around everything on a layer, use a blend or an effect layer instead.
|
|
27
|
+
- **Not yet:** wet brushes (glowing watercolour is planned, #494), sticker sprays and stroke layers
|
|
28
|
+
refuse light above 0, with the alternatives.
|
|
29
|
+
- **PSD.** `export_psd` writes each layer's pixels with their glow, and its spill as a
|
|
30
|
+
"‹layer› light" Linear Dodge (Add) layer directly above it. Re-importing gives plain pixel
|
|
31
|
+
layers, not editable light.
|
|
@@ -66,6 +66,7 @@ This skill is the method. `pixerate-paint-editor` has the full tool reference an
|
|
|
66
66
|
| Scraped, smeared texture | `palette-knife` |
|
|
67
67
|
| Blobs of paint straight from the tube | `paint-tube` |
|
|
68
68
|
| A glossy bead or line | `gloop-pen` |
|
|
69
|
+
| A glowing, glassy bead (a neon or glass tube) | `glow-gel` (light paint with rim and clarity, on dark paper; one loop per layer where they cross) |
|
|
69
70
|
| Dry, broken strokes over texture | `dry-brush`, or a lower `impasto.load` |
|
|
70
71
|
| A metallic highlight | `metallic-paint` |
|
|
71
72
|
| Paint that stays exactly as laid | `set_instant_dry` on |
|
|
@@ -82,7 +82,9 @@ the pitfalls, and uses the tools below.
|
|
|
82
82
|
controls, then `apply_effect` (filters and overlays on a clip), `set_video_effect_transition`
|
|
83
83
|
or `add_video_effect_clip` (generators and titles). Tune with `update_effect` and
|
|
84
84
|
`set_video_effect_keyframes`; check with `get_video_frame` (layers list `effects`) and
|
|
85
|
-
`list_applied_effects`.
|
|
85
|
+
`list_applied_effects`. **An effect along a path** (a beam along a border, a trail, words
|
|
86
|
+
written on): `search_effects` with `accepts: ["path"]`, add it as a clip or clip effect, then
|
|
87
|
+
`set_video_effect_path` with frame `points`, path data `d`, a clip's `mask` outline or `text`.
|
|
86
88
|
12. **Camera moves** (a provider with camera moves, such as Flair): a clip is the shot.
|
|
87
89
|
`list_video_camera_moves` with a `role`, then `set_clip_camera_move`: `transition` moves
|
|
88
90
|
from `from` to `framing` from the clip's start (a glide, whip pan, punch zoom); `hold`
|
|
@@ -298,7 +300,7 @@ and every write tool is refused with the reason.
|
|
|
298
300
|
|
|
299
301
|
| Tool | Use it to |
|
|
300
302
|
| --- | --- |
|
|
301
|
-
| `search_effects` | Find effects: words in `query`, filtered by `kind`, `category
|
|
303
|
+
| `search_effects` | Find effects: words in `query`, filtered by `kind`, `category`, `use` (filter, overlay for clips; transition; generator, title) or `accepts: ["path"]` (effects that draw along a path you give). Returns ids, names, uses, whether they are transparent or loop, and their length. Camera moves (`kind` camera) are listed but go on clips with `set_clip_camera_move`, not `apply_effect`. Call `describe_effect` next. |
|
|
302
304
|
| `describe_effect` | Read one effect's `controls` (a strict JSON Schema: names, types, ranges, units), `defaults`, `presets` and `inputs`, and whether it `appliesHere` (as a clip effect). Use the names and ranges for values. |
|
|
303
305
|
| `list_applied_effects` | See the effects in the project per clip, in order: clip effects (`fx1`...), an effect clip's own effect (`generator`) and an effect transition (`transition`), with their saved values and `available` (false when the provider lacks it: drawn as a placeholder). |
|
|
304
306
|
| `apply_effect` | Add a filter or overlay effect to a clip's stack (`target` clip id, `effect`, `values`, `index`, `enabled`, `opacity`). Effects run in order on the clip's picture before its grade and placement. Returns the effect's new id and the clip's effects. |
|
|
@@ -306,6 +308,7 @@ and every write tool is refused with the reason.
|
|
|
306
308
|
| `remove_effect` | Take an effect off a clip (`transition` removes an effect transition; an effect clip is deleted with `delete_video_clips`). Returns what was removed and what is left. |
|
|
307
309
|
| `add_video_effect_clip` | Put a generator, title or overlay effect on a video `track` as a clip filling the frame (`start`, `duration` default the effect's own length, `values`, `mode`). Transparent effects show the tracks beneath. Returns the clip id. |
|
|
308
310
|
| `set_video_effect_transition` | Use a transition effect from the effects provider into a `clip` from the one ending where it starts (`duration` centred on the cut, `values`). Needs handles like a crossfade. For a cross dissolve, dip, wipe, push, slide or iris use the built-in `set_video_transition` instead. |
|
|
311
|
+
| `set_video_effect_path` | Give an effect that draws along a path (`describe_effect` `pathControls`) its path, on an effect clip (`instance` "generator") or a clip effect: frame `points` `[x0, y0, …]` (mapped into the picture the effect renders, so a clip effect follows its moved or scaled clip), path data `d` (`""` clears), a clip's `mask` outline, or `text` the provider writes. One undo step. Returns the control and the value it took. |
|
|
309
312
|
| `set_video_effect_keyframes` | Animate one number `control` of a clip effect (or an effect clip's `generator`) with `keys` in clip ms, or `null` to stop animating it. |
|
|
310
313
|
| `list_video_camera_moves` | List the provider's camera moves for a `role` (`transition` into a clip, `hold` across it) with `durationMs` and `cut`. Returns `available: false` without camera moves. Call before `set_clip_camera_move`. |
|
|
311
314
|
| `set_clip_camera_move` | Play a camera move on a clip's picture (`clip`, `role`, `move`, `ms` for a transition, `preset`, `values`, `from` and `framing` as `{ zoom, x, y }`): baked into its transform keys, one per frame, two at a cut. Returns the camera, key counts, `cuts`, `windows` and `notes` (tilt, turn and blur cannot be baked). Refuses a clip keyed by hand. |
|
|
@@ -328,6 +331,10 @@ The host may add the shared document tools (see `pixerate-agent-tools`). A video
|
|
|
328
331
|
|
|
329
332
|
## Pitfalls
|
|
330
333
|
|
|
334
|
+
- **Path points are frame pixels**, as on the preview: `set_video_effect_path` maps them into the
|
|
335
|
+
picture the effect renders. Do not pass a clip effect points in its source's pixels yourself, and
|
|
336
|
+
give it the path again after moving or scaling the clip a lot (it maps the clip's first frame).
|
|
337
|
+
|
|
331
338
|
- **Camera moves are baked keys.** After trimming or splitting a clip, `check_video_project`
|
|
332
339
|
reports `camera-stale`: set the move again to re-bake it. Editing the clip's x, y, scale or
|
|
333
340
|
rotation keys by hand leaves the moves no longer describing them; clear the moves first.
|