@pixerate/editor 0.175.0 → 0.176.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/{chunk-OVYQAYGH.cjs → chunk-UHMSMUHK.cjs} +75 -66
- package/dist/{chunk-BDIM6NVW.js → chunk-Y46OMERN.js} +10 -1
- package/dist/effects/fake.d.cts +1 -1
- package/dist/effects/fake.d.ts +1 -1
- package/dist/effects/index.cjs +36 -22
- package/dist/effects/index.js +19 -5
- package/dist/effects/types.d.cts +6 -0
- package/dist/effects/types.d.ts +6 -0
- package/dist/effects/ui.d.cts +0 -6
- package/dist/effects/ui.d.ts +0 -6
- package/dist/image-editor/controller.d.cts +28 -5
- package/dist/image-editor/controller.d.ts +28 -5
- package/dist/image-editor/effect-layers.d.cts +40 -0
- package/dist/image-editor/effect-layers.d.ts +40 -0
- package/dist/image-editor/index.cjs +931 -812
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +152 -33
- package/dist/image-editor/layers/model.d.cts +15 -1
- package/dist/image-editor/layers/model.d.ts +15 -1
- package/dist/image-editor/layers/panel.d.cts +6 -2
- package/dist/image-editor/layers/panel.d.ts +6 -2
- package/dist/image-editor/provider-effect-tools.d.cts +2 -1
- package/dist/image-editor/provider-effect-tools.d.ts +2 -1
- package/dist/paint-editor/index.cjs +6 -6
- package/dist/paint-editor/index.js +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +2 -1
package/dist/effects/fake.d.cts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Effects (ids prefixed `fake.`):
|
|
6
6
|
* - `invert` (filter), `tint` (filter)
|
|
7
|
-
* - `gradient` (generator, loops)
|
|
7
|
+
* - `gradient` (generator, loops; its background opacity is `opacity`)
|
|
8
8
|
* - `bar` (title / overlay: a lower-third bar that slides in, transparent)
|
|
9
9
|
* - `crossfade` (transition)
|
|
10
10
|
* - `fade-in` (motion, vector only)
|
package/dist/effects/fake.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Effects (ids prefixed `fake.`):
|
|
6
6
|
* - `invert` (filter), `tint` (filter)
|
|
7
|
-
* - `gradient` (generator, loops)
|
|
7
|
+
* - `gradient` (generator, loops; its background opacity is `opacity`)
|
|
8
8
|
* - `bar` (title / overlay: a lower-third bar that slides in, transparent)
|
|
9
9
|
* - `crossfade` (transition)
|
|
10
10
|
* - `fade-in` (motion, vector only)
|
package/dist/effects/index.cjs
CHANGED
|
@@ -61,6 +61,7 @@ function checkSummary(e, issues) {
|
|
|
61
61
|
if (typeof e.transparent !== "boolean") at("transparent is not a boolean");
|
|
62
62
|
if (typeof e.loop !== "boolean") at("loop is not a boolean");
|
|
63
63
|
if (e.duration !== void 0 && !(e.duration > 0)) at("duration must be a positive number of ms when present");
|
|
64
|
+
if (e.backgroundOpacity !== void 0 && (typeof e.backgroundOpacity !== "string" || !e.backgroundOpacity)) at("backgroundOpacity must name a control when present");
|
|
64
65
|
}
|
|
65
66
|
function checkDescription(id, d, issues) {
|
|
66
67
|
const at = (message) => issues.push({ effectId: id, message });
|
|
@@ -73,6 +74,11 @@ function checkDescription(id, d, issues) {
|
|
|
73
74
|
if (!(name in (_nullishCoalesce(d.defaults, () => ( {}))))) at(`control "${name}" has no default`);
|
|
74
75
|
}
|
|
75
76
|
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`);
|
|
77
|
+
if (d.backgroundOpacity !== void 0) {
|
|
78
|
+
const control = _optionalChain([c, 'optionalAccess', _4 => _4.properties, 'optionalAccess', _5 => _5[d.backgroundOpacity]]);
|
|
79
|
+
if (!control) at(`backgroundOpacity "${d.backgroundOpacity}" is not a control`);
|
|
80
|
+
else if (control.type !== "number" || control.minimum !== 0 || control.maximum !== 1) at(`backgroundOpacity "${d.backgroundOpacity}" must be a number control from 0 to 1`);
|
|
81
|
+
}
|
|
76
82
|
for (const e of _chunkA3A5TI6Rcjs.effectValueErrors.call(void 0, d, d.defaults)) at(`default ${e.path}: ${e.message}`);
|
|
77
83
|
for (const p of _nullishCoalesce(d.presets, () => ( []))) for (const e of _chunkA3A5TI6Rcjs.effectValueErrors.call(void 0, d, p.values)) at(`preset "${p.name}" ${e.path}: ${e.message}`);
|
|
78
84
|
for (const input2 of _nullishCoalesce(d.inputs, () => ( []))) {
|
|
@@ -118,11 +124,11 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
118
124
|
if (!provider.id) top("provider has no id");
|
|
119
125
|
const caps = provider.capabilities;
|
|
120
126
|
for (const k of ["supportsWorker", "supportsFloat", "supportsMask", "supportsHostImages", "supportsVector"]) {
|
|
121
|
-
if (typeof _optionalChain([caps, 'optionalAccess',
|
|
127
|
+
if (typeof _optionalChain([caps, 'optionalAccess', _6 => _6[k]]) !== "boolean") top(`capabilities.${k} is not a boolean`);
|
|
122
128
|
}
|
|
123
|
-
if (_optionalChain([caps, 'optionalAccess',
|
|
124
|
-
if (_optionalChain([caps, 'optionalAccess',
|
|
125
|
-
if (_optionalChain([caps, 'optionalAccess',
|
|
129
|
+
if (_optionalChain([caps, 'optionalAccess', _7 => _7.supportsAuthoring]) !== void 0 && typeof caps.supportsAuthoring !== "boolean") top("capabilities.supportsAuthoring is not a boolean");
|
|
130
|
+
if (_optionalChain([caps, 'optionalAccess', _8 => _8.supportsVector]) && typeof provider.applyVectorEffect !== "function") top("supportsVector is set but applyVectorEffect is missing");
|
|
131
|
+
if (_optionalChain([caps, 'optionalAccess', _9 => _9.supportsAuthoring]) && typeof provider.openAuthoring !== "function") top("supportsAuthoring is set but openAuthoring is missing");
|
|
126
132
|
const list = await provider.list();
|
|
127
133
|
const ids = /* @__PURE__ */ new Set();
|
|
128
134
|
for (const e of list) {
|
|
@@ -132,7 +138,7 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
132
138
|
}
|
|
133
139
|
const all = await provider.search("");
|
|
134
140
|
if (all.length !== list.length) top(`search("") returned ${all.length} effects, list() ${list.length}`);
|
|
135
|
-
const firstUse = _optionalChain([list, 'access',
|
|
141
|
+
const firstUse = _optionalChain([list, 'access', _10 => _10[0], 'optionalAccess', _11 => _11.uses, 'access', _12 => _12[0]]);
|
|
136
142
|
if (firstUse) {
|
|
137
143
|
const filtered = await provider.search("", { use: firstUse });
|
|
138
144
|
if (filtered.some((e) => !e.uses.includes(firstUse))) top(`search("", { use: "${firstUse}" }) returned effects without that use`);
|
|
@@ -164,7 +170,7 @@ async function checkEffectProvider(provider, options = {}) {
|
|
|
164
170
|
if (bad) issues.push({ effectId: id, message: `applyVectorEffect with an unknown control ${bad}` });
|
|
165
171
|
}
|
|
166
172
|
}
|
|
167
|
-
if (_optionalChain([caps, 'optionalAccess',
|
|
173
|
+
if (_optionalChain([caps, 'optionalAccess', _13 => _13.supportsAuthoring]) && options.authoring !== false && typeof provider.openAuthoring === "function") {
|
|
168
174
|
const targetEffect = (await Promise.all((_nullishCoalesce(options.ids, () => ( [...ids]))).map((id) => provider.describe(id)))).find(
|
|
169
175
|
(d) => d.kind !== "motion" && d.kind !== "vector"
|
|
170
176
|
);
|
|
@@ -278,6 +284,7 @@ var DESCRIPTIONS = [
|
|
|
278
284
|
transparent: false,
|
|
279
285
|
loop: true,
|
|
280
286
|
duration: 4e3,
|
|
287
|
+
backgroundOpacity: "opacity",
|
|
281
288
|
controls: {
|
|
282
289
|
type: "object",
|
|
283
290
|
additionalProperties: false,
|
|
@@ -285,10 +292,11 @@ var DESCRIPTIONS = [
|
|
|
285
292
|
from: colour("Start colour as #rrggbb. Default #1e3a8a."),
|
|
286
293
|
to: colour("End colour as #rrggbb. Default #f472b6."),
|
|
287
294
|
angle: { type: "number", minimum: 0, maximum: 360, description: "Direction in degrees, 0 = left to right, 90 = top to bottom. Default 90." },
|
|
288
|
-
spin: { type: "number", minimum: -360, maximum: 360, description: "Turn per loop (4 s) in degrees. Default 0." }
|
|
295
|
+
spin: { type: "number", minimum: -360, maximum: 360, description: "Turn per loop (4 s) in degrees. Default 0." },
|
|
296
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Background opacity 0\u20131: 0 is fully transparent. Default 1." }
|
|
289
297
|
}
|
|
290
298
|
},
|
|
291
|
-
defaults: { from: "#1e3a8a", to: "#f472b6", angle: 90, spin: 0 },
|
|
299
|
+
defaults: { from: "#1e3a8a", to: "#f472b6", angle: 90, spin: 0, opacity: 1 },
|
|
292
300
|
presets: [{ name: "Sunset", values: { from: "#f97316", to: "#7c3aed" } }],
|
|
293
301
|
inputs: []
|
|
294
302
|
},
|
|
@@ -391,7 +399,7 @@ var hexToRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
|
|
391
399
|
var smooth = (t) => t * t * (3 - 2 * t);
|
|
392
400
|
var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
393
401
|
function input(request, name, id, width, height) {
|
|
394
|
-
const src = _optionalChain([request, 'access',
|
|
402
|
+
const src = _optionalChain([request, 'access', _14 => _14.inputs, 'optionalAccess', _15 => _15[name]]);
|
|
395
403
|
if (!src) throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)(`Effect "${id}" needs an input named "${name}".`, id);
|
|
396
404
|
if (!_chunkA3A5TI6Rcjs.isEffectPixels.call(void 0, src)) throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)(`The fake provider only takes EffectPixels inputs ("${name}").`, id);
|
|
397
405
|
if (src.width !== width || src.height !== height) {
|
|
@@ -434,7 +442,7 @@ function renderPixels(d, v, request, width, height) {
|
|
|
434
442
|
for (let x = 0; x < width; x++, i += 4) {
|
|
435
443
|
const t = clamp01(((x + 0.5 - width / 2) * dx + (y + 0.5 - height / 2) * dy) / (2 * half) + 0.5);
|
|
436
444
|
for (let c = 0; c < 3; c++) o[i + c] = a[c] + (b[c] - a[c]) * t;
|
|
437
|
-
o[i + 3] = 255;
|
|
445
|
+
o[i + 3] = 255 * clamp01(v.opacity);
|
|
438
446
|
}
|
|
439
447
|
}
|
|
440
448
|
break;
|
|
@@ -538,11 +546,11 @@ function createFakeEffectProvider(options = {}) {
|
|
|
538
546
|
throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)(`time must be a finite number of ms \u2265 0, got ${request.time}.`, id);
|
|
539
547
|
}
|
|
540
548
|
const values = _chunkA3A5TI6Rcjs.resolveEffectValues.call(void 0, d, request.values);
|
|
541
|
-
_optionalChain([options, 'access',
|
|
549
|
+
_optionalChain([options, 'access', _16 => _16.onRender, 'optionalCall', _17 => _17(id, request)]);
|
|
542
550
|
const out = renderPixels(d, values, request, width, height);
|
|
543
551
|
if (request.mask) {
|
|
544
552
|
if (!capabilities.supportsMask) throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)("This provider does not take masks.", id);
|
|
545
|
-
const source = d.uses.includes("filter") ? _optionalChain([request, 'access',
|
|
553
|
+
const source = d.uses.includes("filter") ? _optionalChain([request, 'access', _18 => _18.inputs, 'optionalAccess', _19 => _19.source]) : void 0;
|
|
546
554
|
maskResult(out, request.mask, source, id);
|
|
547
555
|
}
|
|
548
556
|
return depth > 8 && capabilities.supportsFloat ? toFloat(out) : out;
|
|
@@ -592,10 +600,16 @@ var typeOf = (s) => Array.isArray(s.type) ? s.type.find((t) => t !== "null") : s
|
|
|
592
600
|
function isEffectFontControl(schema) {
|
|
593
601
|
return typeOf(schema) === "string" && (schema.format === "font" || schema["x-pixerate-control"] === "font");
|
|
594
602
|
}
|
|
603
|
+
function jsonTypeOf(value) {
|
|
604
|
+
if (Array.isArray(value)) return "array";
|
|
605
|
+
if (typeof value === "string" || typeof value === "boolean") return typeof value;
|
|
606
|
+
if (typeof value === "number") return "number";
|
|
607
|
+
return void 0;
|
|
608
|
+
}
|
|
595
609
|
function effectControlField(name, schema, fallback, options = {}) {
|
|
596
610
|
const base = { id: name, ...schema.title ? { label: schema.title } : {}, ...schema.description ? { description: schema.description } : {} };
|
|
597
611
|
const def = _nullishCoalesce(schema.default, () => ( fallback));
|
|
598
|
-
const type = typeOf(schema);
|
|
612
|
+
const type = _nullishCoalesce(typeOf(schema), () => ( typeOf(_nullishCoalesce(_optionalChain([schema, 'access', _20 => _20.anyOf, 'optionalAccess', _21 => _21.find, 'call', _22 => _22((s) => typeOf(s) === jsonTypeOf(def))]), () => ( {})))));
|
|
599
613
|
if (Array.isArray(schema.enum) && schema.enum.length) {
|
|
600
614
|
const options2 = schema.enum.filter((v) => typeof v === "string" || typeof v === "number");
|
|
601
615
|
return { ...base, type: "choice", options: options2, default: typeof def === "string" || typeof def === "number" ? def : options2[0], control: options2.length <= 4 ? "segmented" : "select" };
|
|
@@ -609,7 +623,7 @@ function effectControlField(name, schema, fallback, options = {}) {
|
|
|
609
623
|
if (isEffectFontControl(schema)) {
|
|
610
624
|
const fonts = _nullishCoalesce(options.fonts, () => ( _chunkFKUKJ3XAcjs.SYSTEM_FONT_CATALOG));
|
|
611
625
|
const opts = fonts.map((f) => ({ value: f.family, label: f.family, description: f.category }));
|
|
612
|
-
const current = typeof def === "string" && def ? def : _nullishCoalesce(_optionalChain([opts, 'access',
|
|
626
|
+
const current = typeof def === "string" && def ? def : _nullishCoalesce(_optionalChain([opts, 'access', _23 => _23[0], 'optionalAccess', _24 => _24.value]), () => ( "sans-serif"));
|
|
613
627
|
if (!opts.some((o) => o.value.toLowerCase() === current.toLowerCase())) opts.unshift({ value: current, label: current, description: "current" });
|
|
614
628
|
return { ...base, type: "choice", options: opts, default: opts.find((o) => o.value.toLowerCase() === current.toLowerCase()).value, control: "select" };
|
|
615
629
|
}
|
|
@@ -618,7 +632,7 @@ function effectControlField(name, schema, fallback, options = {}) {
|
|
|
618
632
|
return { ...base, type: "text", default: typeof def === "string" ? def : "", ...schema.maxLength ? { maxLength: schema.maxLength } : {} };
|
|
619
633
|
}
|
|
620
634
|
if (type === "array" && Array.isArray(def) && def.length >= 2 && def.length <= 4 && def.every((n) => typeof n === "number")) {
|
|
621
|
-
const items = _nullishCoalesce(schema.items, () => ( {}));
|
|
635
|
+
const items = _nullishCoalesce(_nullishCoalesce(schema.items, () => ( _optionalChain([schema, 'access', _25 => _25.anyOf, 'optionalAccess', _26 => _26.find, 'call', _27 => _27((s) => typeOf(s) === "array"), 'optionalAccess', _28 => _28.items]))), () => ( {}));
|
|
622
636
|
return { ...base, type: "vector", size: def.length, default: [...def], ...items.minimum !== void 0 ? { min: items.minimum } : {}, ...items.maximum !== void 0 ? { max: items.maximum } : {} };
|
|
623
637
|
}
|
|
624
638
|
return null;
|
|
@@ -728,11 +742,11 @@ var EASING_SCHEMA = {
|
|
|
728
742
|
};
|
|
729
743
|
var NO_SESSION = "No effect authoring session is currently open. Open an effect using EffectProvider.openAuthoring() or in an authoring editor before calling effect authoring tools.";
|
|
730
744
|
function createEffectAuthoringToolProvider(host, options) {
|
|
731
|
-
const id = _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
732
|
-
const namespace = _optionalChain([options, 'optionalAccess',
|
|
733
|
-
const title = _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
734
|
-
const description = _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
735
|
-
const skill = _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
745
|
+
const id = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _29 => _29.id]), () => ( "effect-authoring"));
|
|
746
|
+
const namespace = _optionalChain([options, 'optionalAccess', _30 => _30.namespace]);
|
|
747
|
+
const title = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _31 => _31.title]), () => ( "Effect authoring"));
|
|
748
|
+
const description = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _32 => _32.description]), () => ( "Author shaders and procedural effects, inspect parameters, adjust values, and manage keyframe animations."));
|
|
749
|
+
const skill = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _33 => _33.skill]), () => ( "pixerate-effect-authoring"));
|
|
736
750
|
const requireSession = () => {
|
|
737
751
|
const s = host.session();
|
|
738
752
|
if (!s) throw new Error(NO_SESSION);
|
|
@@ -753,7 +767,7 @@ function createEffectAuthoringToolProvider(host, options) {
|
|
|
753
767
|
sourceLanguage: state.sourceLanguage,
|
|
754
768
|
controls: effectParameterFields(state.description),
|
|
755
769
|
values: state.values,
|
|
756
|
-
presets: _nullishCoalesce(_optionalChain([state, 'access',
|
|
770
|
+
presets: _nullishCoalesce(_optionalChain([state, 'access', _34 => _34.description, 'access', _35 => _35.presets, 'optionalAccess', _36 => _36.map, 'call', _37 => _37((p) => p.name)]), () => ( [])),
|
|
757
771
|
tracks: Object.entries(state.tracks).map(([control, track]) => ({
|
|
758
772
|
control,
|
|
759
773
|
keyframes: track.length
|
|
@@ -1060,7 +1074,7 @@ function createEffectAuthoringToolProvider(host, options) {
|
|
|
1060
1074
|
const tools = [
|
|
1061
1075
|
describeTool,
|
|
1062
1076
|
getSourceTool,
|
|
1063
|
-
..._optionalChain([session, 'optionalAccess',
|
|
1077
|
+
..._optionalChain([session, 'optionalAccess', _38 => _38.setSource]) ? [setSourceTool] : [],
|
|
1064
1078
|
setValuesTool,
|
|
1065
1079
|
resetValuesTool,
|
|
1066
1080
|
applyPresetTool,
|
package/dist/effects/index.js
CHANGED
|
@@ -61,6 +61,7 @@ function checkSummary(e, issues) {
|
|
|
61
61
|
if (typeof e.transparent !== "boolean") at("transparent is not a boolean");
|
|
62
62
|
if (typeof e.loop !== "boolean") at("loop is not a boolean");
|
|
63
63
|
if (e.duration !== void 0 && !(e.duration > 0)) at("duration must be a positive number of ms when present");
|
|
64
|
+
if (e.backgroundOpacity !== void 0 && (typeof e.backgroundOpacity !== "string" || !e.backgroundOpacity)) at("backgroundOpacity must name a control when present");
|
|
64
65
|
}
|
|
65
66
|
function checkDescription(id, d, issues) {
|
|
66
67
|
const at = (message) => issues.push({ effectId: id, message });
|
|
@@ -73,6 +74,11 @@ function checkDescription(id, d, issues) {
|
|
|
73
74
|
if (!(name in (d.defaults ?? {}))) at(`control "${name}" has no default`);
|
|
74
75
|
}
|
|
75
76
|
for (const name of Object.keys(d.defaults ?? {})) if (!(name in (c?.properties ?? {}))) at(`default "${name}" is not a control`);
|
|
77
|
+
if (d.backgroundOpacity !== void 0) {
|
|
78
|
+
const control = c?.properties?.[d.backgroundOpacity];
|
|
79
|
+
if (!control) at(`backgroundOpacity "${d.backgroundOpacity}" is not a control`);
|
|
80
|
+
else if (control.type !== "number" || control.minimum !== 0 || control.maximum !== 1) at(`backgroundOpacity "${d.backgroundOpacity}" must be a number control from 0 to 1`);
|
|
81
|
+
}
|
|
76
82
|
for (const e of effectValueErrors(d, d.defaults)) at(`default ${e.path}: ${e.message}`);
|
|
77
83
|
for (const p of d.presets ?? []) for (const e of effectValueErrors(d, p.values)) at(`preset "${p.name}" ${e.path}: ${e.message}`);
|
|
78
84
|
for (const input2 of d.inputs ?? []) {
|
|
@@ -278,6 +284,7 @@ var DESCRIPTIONS = [
|
|
|
278
284
|
transparent: false,
|
|
279
285
|
loop: true,
|
|
280
286
|
duration: 4e3,
|
|
287
|
+
backgroundOpacity: "opacity",
|
|
281
288
|
controls: {
|
|
282
289
|
type: "object",
|
|
283
290
|
additionalProperties: false,
|
|
@@ -285,10 +292,11 @@ var DESCRIPTIONS = [
|
|
|
285
292
|
from: colour("Start colour as #rrggbb. Default #1e3a8a."),
|
|
286
293
|
to: colour("End colour as #rrggbb. Default #f472b6."),
|
|
287
294
|
angle: { type: "number", minimum: 0, maximum: 360, description: "Direction in degrees, 0 = left to right, 90 = top to bottom. Default 90." },
|
|
288
|
-
spin: { type: "number", minimum: -360, maximum: 360, description: "Turn per loop (4 s) in degrees. Default 0." }
|
|
295
|
+
spin: { type: "number", minimum: -360, maximum: 360, description: "Turn per loop (4 s) in degrees. Default 0." },
|
|
296
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Background opacity 0\u20131: 0 is fully transparent. Default 1." }
|
|
289
297
|
}
|
|
290
298
|
},
|
|
291
|
-
defaults: { from: "#1e3a8a", to: "#f472b6", angle: 90, spin: 0 },
|
|
299
|
+
defaults: { from: "#1e3a8a", to: "#f472b6", angle: 90, spin: 0, opacity: 1 },
|
|
292
300
|
presets: [{ name: "Sunset", values: { from: "#f97316", to: "#7c3aed" } }],
|
|
293
301
|
inputs: []
|
|
294
302
|
},
|
|
@@ -434,7 +442,7 @@ function renderPixels(d, v, request, width, height) {
|
|
|
434
442
|
for (let x = 0; x < width; x++, i += 4) {
|
|
435
443
|
const t = clamp01(((x + 0.5 - width / 2) * dx + (y + 0.5 - height / 2) * dy) / (2 * half) + 0.5);
|
|
436
444
|
for (let c = 0; c < 3; c++) o[i + c] = a[c] + (b[c] - a[c]) * t;
|
|
437
|
-
o[i + 3] = 255;
|
|
445
|
+
o[i + 3] = 255 * clamp01(v.opacity);
|
|
438
446
|
}
|
|
439
447
|
}
|
|
440
448
|
break;
|
|
@@ -592,10 +600,16 @@ var typeOf = (s) => Array.isArray(s.type) ? s.type.find((t) => t !== "null") : s
|
|
|
592
600
|
function isEffectFontControl(schema) {
|
|
593
601
|
return typeOf(schema) === "string" && (schema.format === "font" || schema["x-pixerate-control"] === "font");
|
|
594
602
|
}
|
|
603
|
+
function jsonTypeOf(value) {
|
|
604
|
+
if (Array.isArray(value)) return "array";
|
|
605
|
+
if (typeof value === "string" || typeof value === "boolean") return typeof value;
|
|
606
|
+
if (typeof value === "number") return "number";
|
|
607
|
+
return void 0;
|
|
608
|
+
}
|
|
595
609
|
function effectControlField(name, schema, fallback, options = {}) {
|
|
596
610
|
const base = { id: name, ...schema.title ? { label: schema.title } : {}, ...schema.description ? { description: schema.description } : {} };
|
|
597
611
|
const def = schema.default ?? fallback;
|
|
598
|
-
const type = typeOf(schema);
|
|
612
|
+
const type = typeOf(schema) ?? typeOf(schema.anyOf?.find((s) => typeOf(s) === jsonTypeOf(def)) ?? {});
|
|
599
613
|
if (Array.isArray(schema.enum) && schema.enum.length) {
|
|
600
614
|
const options2 = schema.enum.filter((v) => typeof v === "string" || typeof v === "number");
|
|
601
615
|
return { ...base, type: "choice", options: options2, default: typeof def === "string" || typeof def === "number" ? def : options2[0], control: options2.length <= 4 ? "segmented" : "select" };
|
|
@@ -618,7 +632,7 @@ function effectControlField(name, schema, fallback, options = {}) {
|
|
|
618
632
|
return { ...base, type: "text", default: typeof def === "string" ? def : "", ...schema.maxLength ? { maxLength: schema.maxLength } : {} };
|
|
619
633
|
}
|
|
620
634
|
if (type === "array" && Array.isArray(def) && def.length >= 2 && def.length <= 4 && def.every((n) => typeof n === "number")) {
|
|
621
|
-
const items = schema.items ?? {};
|
|
635
|
+
const items = schema.items ?? schema.anyOf?.find((s) => typeOf(s) === "array")?.items ?? {};
|
|
622
636
|
return { ...base, type: "vector", size: def.length, default: [...def], ...items.minimum !== void 0 ? { min: items.minimum } : {}, ...items.maximum !== void 0 ? { max: items.maximum } : {} };
|
|
623
637
|
}
|
|
624
638
|
return null;
|
package/dist/effects/types.d.cts
CHANGED
|
@@ -42,6 +42,12 @@ export interface EffectSummary {
|
|
|
42
42
|
loop: boolean;
|
|
43
43
|
/** Length of one play (ms). Absent for effects that do not change over time. */
|
|
44
44
|
duration?: number;
|
|
45
|
+
/**
|
|
46
|
+
* The control (a number from 0 to 1) setting how opaque the effect's own background is: at 0
|
|
47
|
+
* only what it draws is left, so it can sit over other content. Absent when the effect has no
|
|
48
|
+
* background of its own to fade (an overlay is already transparent).
|
|
49
|
+
*/
|
|
50
|
+
backgroundOpacity?: string;
|
|
45
51
|
}
|
|
46
52
|
export interface EffectSearchOptions {
|
|
47
53
|
kind?: EffectKind;
|
package/dist/effects/types.d.ts
CHANGED
|
@@ -42,6 +42,12 @@ export interface EffectSummary {
|
|
|
42
42
|
loop: boolean;
|
|
43
43
|
/** Length of one play (ms). Absent for effects that do not change over time. */
|
|
44
44
|
duration?: number;
|
|
45
|
+
/**
|
|
46
|
+
* The control (a number from 0 to 1) setting how opaque the effect's own background is: at 0
|
|
47
|
+
* only what it draws is left, so it can sit over other content. Absent when the effect has no
|
|
48
|
+
* background of its own to fade (an overlay is already transparent).
|
|
49
|
+
*/
|
|
50
|
+
backgroundOpacity?: string;
|
|
45
51
|
}
|
|
46
52
|
export interface EffectSearchOptions {
|
|
47
53
|
kind?: EffectKind;
|
package/dist/effects/ui.d.cts
CHANGED
|
@@ -19,12 +19,6 @@ export interface EffectFieldOptions {
|
|
|
19
19
|
* `"x-pixerate-control": "font"`.
|
|
20
20
|
*/
|
|
21
21
|
export declare function isEffectFontControl(schema: JsonSchema): boolean;
|
|
22
|
-
/**
|
|
23
|
-
* One control as a parameter field: numbers and integers (sliders when bounded), switches,
|
|
24
|
-
* choices (`enum`), font families (`format: "font"`, a choice of `options.fonts`), colours
|
|
25
|
-
* (strings whose default is `#rrggbb`), vectors (2–4 numbers) and text.
|
|
26
|
-
* `null` for a control no field can edit (objects, long lists).
|
|
27
|
-
*/
|
|
28
22
|
export declare function effectControlField(name: string, schema: JsonSchema, fallback: EffectValue | undefined, options?: EffectFieldOptions): ParameterField | null;
|
|
29
23
|
/** An effect's controls as parameter fields, in schema order (controls no field can edit are left out). */
|
|
30
24
|
export declare function effectParameterFields(description: Pick<EffectDescription, 'controls' | 'defaults'>, options?: EffectFieldOptions): ParameterField[];
|
package/dist/effects/ui.d.ts
CHANGED
|
@@ -19,12 +19,6 @@ export interface EffectFieldOptions {
|
|
|
19
19
|
* `"x-pixerate-control": "font"`.
|
|
20
20
|
*/
|
|
21
21
|
export declare function isEffectFontControl(schema: JsonSchema): boolean;
|
|
22
|
-
/**
|
|
23
|
-
* One control as a parameter field: numbers and integers (sliders when bounded), switches,
|
|
24
|
-
* choices (`enum`), font families (`format: "font"`, a choice of `options.fonts`), colours
|
|
25
|
-
* (strings whose default is `#rrggbb`), vectors (2–4 numbers) and text.
|
|
26
|
-
* `null` for a control no field can edit (objects, long lists).
|
|
27
|
-
*/
|
|
28
22
|
export declare function effectControlField(name: string, schema: JsonSchema, fallback: EffectValue | undefined, options?: EffectFieldOptions): ParameterField | null;
|
|
29
23
|
/** An effect's controls as parameter fields, in schema order (controls no field can edit are left out). */
|
|
30
24
|
export declare function effectParameterFields(description: Pick<EffectDescription, 'controls' | 'defaults'>, options?: EffectFieldOptions): ParameterField[];
|
|
@@ -9,7 +9,7 @@ import { type SelectionModifyOp } from './selection/modify.cjs';
|
|
|
9
9
|
import { type RefineEdgeParams } from './selection/refine.cjs';
|
|
10
10
|
import type { LayerMaskProps } from './selection/layer-mask.cjs';
|
|
11
11
|
import type { AdjustmentLayerMask } from './types.cjs';
|
|
12
|
-
import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeFillRule, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TypeLayer } from './layers/model.cjs';
|
|
12
|
+
import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeFillRule, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TypeLayer, type ImageEffectAnimation } from './layers/model.cjs';
|
|
13
13
|
import { type TextLayout, type TextShaper } from './layers/text.cjs';
|
|
14
14
|
import type { VectorSubpath } from '../vector/types.cjs';
|
|
15
15
|
import { EffectsSlot } from '../effects/helpers.cjs';
|
|
@@ -609,25 +609,48 @@ export declare class ImageEditorController {
|
|
|
609
609
|
/**
|
|
610
610
|
* Adds an effect layer: a pixel layer drawn by a provider generator, overlay or title effect at
|
|
611
611
|
* the document's size and bit depth (at `time` ms, default 0), placed like `addPixelLayer`. One
|
|
612
|
-
* undo step. Masks, blend and opacity apply as to any layer.
|
|
612
|
+
* undo step. Masks, blend and opacity apply as to any layer. An effect with a background of its
|
|
613
|
+
* own (`backgroundOpacity`) starts with it transparent when there are layers below it, unless
|
|
614
|
+
* `values` set it. `animate` makes it play when the document is output as an animation. Returns
|
|
615
|
+
* its id.
|
|
613
616
|
*/
|
|
614
617
|
addEffectLayer(effect: string, options?: LayerPlacement & {
|
|
615
618
|
values?: Record<string, EffectValue>;
|
|
616
619
|
name?: string;
|
|
617
620
|
time?: number;
|
|
621
|
+
animate?: ImageEffectAnimation;
|
|
618
622
|
opacity?: number;
|
|
619
623
|
blend?: string;
|
|
620
624
|
id?: string;
|
|
621
625
|
}): Promise<string>;
|
|
622
626
|
/**
|
|
623
|
-
* Draws an effect layer again
|
|
624
|
-
*
|
|
625
|
-
*
|
|
627
|
+
* Draws an effect layer again, replacing its pixels (painting on it is lost): with new `values`
|
|
628
|
+
* (merged over its own; `null` resets a control), at another `time`, or as another `effect`
|
|
629
|
+
* (a generator, overlay or title: its defaults plus `values`, with its background transparent
|
|
630
|
+
* when there are layers below; the layer keeps a name of its own, else takes the new effect's).
|
|
631
|
+
* `animate` sets how it plays in an animation (null: a still frame). One undo step (none with
|
|
632
|
+
* `commitToHistory` false). Returns its effect settings.
|
|
626
633
|
*/
|
|
627
634
|
updateEffectLayer(layerId: string, changes: {
|
|
635
|
+
effect?: string;
|
|
628
636
|
values?: Record<string, EffectValue | null>;
|
|
629
637
|
time?: number;
|
|
638
|
+
animate?: ImageEffectAnimation | null;
|
|
630
639
|
}, commitToHistory?: boolean): Promise<NonNullable<PixelLayer['generator']>>;
|
|
640
|
+
/**
|
|
641
|
+
* The document as an animation: its visible effect layers that animate (`generator.animate`)
|
|
642
|
+
* and how long it runs, the longest of theirs (ms). Null when none animates.
|
|
643
|
+
*/
|
|
644
|
+
getAnimation(): {
|
|
645
|
+
duration: number;
|
|
646
|
+
layerIds: string[];
|
|
647
|
+
} | null;
|
|
648
|
+
/**
|
|
649
|
+
* Renders the document `time` ms into its animation (see `getAnimation`), like `renderExport`:
|
|
650
|
+
* each animating effect layer drawn at its own `time` plus `time` (an effect that loops keeps
|
|
651
|
+
* looping past its duration), every other layer as it is. Nothing in the document changes.
|
|
652
|
+
*/
|
|
653
|
+
renderAnimationFrame(time: number, options?: ExportOptions): Promise<HTMLCanvasElement>;
|
|
631
654
|
/** Turns an effect layer into a plain pixel layer keeping its pixels (one undo step). */
|
|
632
655
|
rasterizeEffectLayer(layerId: string): void;
|
|
633
656
|
private displayFor;
|
|
@@ -9,7 +9,7 @@ import { type SelectionModifyOp } from './selection/modify.js';
|
|
|
9
9
|
import { type RefineEdgeParams } from './selection/refine.js';
|
|
10
10
|
import type { LayerMaskProps } from './selection/layer-mask.js';
|
|
11
11
|
import type { AdjustmentLayerMask } from './types.js';
|
|
12
|
-
import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeFillRule, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TypeLayer } from './layers/model.js';
|
|
12
|
+
import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeFillRule, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TypeLayer, type ImageEffectAnimation } from './layers/model.js';
|
|
13
13
|
import { type TextLayout, type TextShaper } from './layers/text.js';
|
|
14
14
|
import type { VectorSubpath } from '../vector/types.js';
|
|
15
15
|
import { EffectsSlot } from '../effects/helpers.js';
|
|
@@ -609,25 +609,48 @@ export declare class ImageEditorController {
|
|
|
609
609
|
/**
|
|
610
610
|
* Adds an effect layer: a pixel layer drawn by a provider generator, overlay or title effect at
|
|
611
611
|
* the document's size and bit depth (at `time` ms, default 0), placed like `addPixelLayer`. One
|
|
612
|
-
* undo step. Masks, blend and opacity apply as to any layer.
|
|
612
|
+
* undo step. Masks, blend and opacity apply as to any layer. An effect with a background of its
|
|
613
|
+
* own (`backgroundOpacity`) starts with it transparent when there are layers below it, unless
|
|
614
|
+
* `values` set it. `animate` makes it play when the document is output as an animation. Returns
|
|
615
|
+
* its id.
|
|
613
616
|
*/
|
|
614
617
|
addEffectLayer(effect: string, options?: LayerPlacement & {
|
|
615
618
|
values?: Record<string, EffectValue>;
|
|
616
619
|
name?: string;
|
|
617
620
|
time?: number;
|
|
621
|
+
animate?: ImageEffectAnimation;
|
|
618
622
|
opacity?: number;
|
|
619
623
|
blend?: string;
|
|
620
624
|
id?: string;
|
|
621
625
|
}): Promise<string>;
|
|
622
626
|
/**
|
|
623
|
-
* Draws an effect layer again
|
|
624
|
-
*
|
|
625
|
-
*
|
|
627
|
+
* Draws an effect layer again, replacing its pixels (painting on it is lost): with new `values`
|
|
628
|
+
* (merged over its own; `null` resets a control), at another `time`, or as another `effect`
|
|
629
|
+
* (a generator, overlay or title: its defaults plus `values`, with its background transparent
|
|
630
|
+
* when there are layers below; the layer keeps a name of its own, else takes the new effect's).
|
|
631
|
+
* `animate` sets how it plays in an animation (null: a still frame). One undo step (none with
|
|
632
|
+
* `commitToHistory` false). Returns its effect settings.
|
|
626
633
|
*/
|
|
627
634
|
updateEffectLayer(layerId: string, changes: {
|
|
635
|
+
effect?: string;
|
|
628
636
|
values?: Record<string, EffectValue | null>;
|
|
629
637
|
time?: number;
|
|
638
|
+
animate?: ImageEffectAnimation | null;
|
|
630
639
|
}, commitToHistory?: boolean): Promise<NonNullable<PixelLayer['generator']>>;
|
|
640
|
+
/**
|
|
641
|
+
* The document as an animation: its visible effect layers that animate (`generator.animate`)
|
|
642
|
+
* and how long it runs, the longest of theirs (ms). Null when none animates.
|
|
643
|
+
*/
|
|
644
|
+
getAnimation(): {
|
|
645
|
+
duration: number;
|
|
646
|
+
layerIds: string[];
|
|
647
|
+
} | null;
|
|
648
|
+
/**
|
|
649
|
+
* Renders the document `time` ms into its animation (see `getAnimation`), like `renderExport`:
|
|
650
|
+
* each animating effect layer drawn at its own `time` plus `time` (an effect that loops keeps
|
|
651
|
+
* looping past its duration), every other layer as it is. Nothing in the document changes.
|
|
652
|
+
*/
|
|
653
|
+
renderAnimationFrame(time: number, options?: ExportOptions): Promise<HTMLCanvasElement>;
|
|
631
654
|
/** Turns an effect layer into a plain pixel layer keeping its pixels (one undo step). */
|
|
632
655
|
rasterizeEffectLayer(layerId: string): void;
|
|
633
656
|
private displayFor;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect layer rules shared by the controller and the panels: which layers sit below one (so its
|
|
3
|
+
* background starts transparent), the checks on a still frame's time and an animation, and the
|
|
4
|
+
* timing the panels show.
|
|
5
|
+
*/
|
|
6
|
+
import type { EffectDescription, EffectSummary, EffectValue } from '../effects/types.cjs';
|
|
7
|
+
import { type ImageEffectAnimation, type ImageEffectGenerator, type ImageLayer } from './layers/model.cjs';
|
|
8
|
+
/**
|
|
9
|
+
* Whether anything is drawn below layer `id` in the stack: a pixel, shape, type or smart object
|
|
10
|
+
* layer under it (groups and adjustment layers draw nothing of their own). False for the bottom
|
|
11
|
+
* layer, or an id not in the stack.
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasLayersBelow(layers: readonly ImageLayer[], id: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* `values` with the effect's background opacity control (`backgroundOpacity`) set to 0 when the
|
|
16
|
+
* layer has others below it and `values` leave it unset; otherwise `values` unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export declare function withBackgroundOpacity(d: Pick<EffectDescription, 'backgroundOpacity'>, values: Record<string, EffectValue> | undefined, below: boolean): Record<string, EffectValue>;
|
|
19
|
+
/** A still frame's time: a number of ms from 0 (undefined is 0). */
|
|
20
|
+
export declare function checkEffectTime(time: number | undefined): number;
|
|
21
|
+
/** An effect layer's animation: a duration in ms, more than 0 and at most `MAX_EFFECT_ANIMATION_MS`. */
|
|
22
|
+
export declare function checkEffectAnimation(animate: ImageEffectAnimation): ImageEffectAnimation;
|
|
23
|
+
/** What the panels show of an effect layer's timing (see `effectLayerTiming`). */
|
|
24
|
+
export interface EffectLayerTiming {
|
|
25
|
+
/** A still frame at `time`, or playing from `time` in an animation. */
|
|
26
|
+
mode: 'still' | 'animate';
|
|
27
|
+
/** The frame drawn (ms): the still frame, and where the animation starts. */
|
|
28
|
+
time: number;
|
|
29
|
+
/** How far the frame can be moved (ms): one loop of a looping effect, else one play. */
|
|
30
|
+
range: number;
|
|
31
|
+
/** How long it plays in an animation (ms): its own, else the effect's play. */
|
|
32
|
+
duration: number;
|
|
33
|
+
/** The effect repeats every `range` ms. */
|
|
34
|
+
loops: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* An effect layer's timing for the panels, or null when its effect does not change over time
|
|
38
|
+
* (the provider gives it no `duration`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function effectLayerTiming(g: ImageEffectGenerator, d: Pick<EffectSummary, 'duration' | 'loop'> | null | undefined): EffectLayerTiming | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect layer rules shared by the controller and the panels: which layers sit below one (so its
|
|
3
|
+
* background starts transparent), the checks on a still frame's time and an animation, and the
|
|
4
|
+
* timing the panels show.
|
|
5
|
+
*/
|
|
6
|
+
import type { EffectDescription, EffectSummary, EffectValue } from '../effects/types.js';
|
|
7
|
+
import { type ImageEffectAnimation, type ImageEffectGenerator, type ImageLayer } from './layers/model.js';
|
|
8
|
+
/**
|
|
9
|
+
* Whether anything is drawn below layer `id` in the stack: a pixel, shape, type or smart object
|
|
10
|
+
* layer under it (groups and adjustment layers draw nothing of their own). False for the bottom
|
|
11
|
+
* layer, or an id not in the stack.
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasLayersBelow(layers: readonly ImageLayer[], id: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* `values` with the effect's background opacity control (`backgroundOpacity`) set to 0 when the
|
|
16
|
+
* layer has others below it and `values` leave it unset; otherwise `values` unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export declare function withBackgroundOpacity(d: Pick<EffectDescription, 'backgroundOpacity'>, values: Record<string, EffectValue> | undefined, below: boolean): Record<string, EffectValue>;
|
|
19
|
+
/** A still frame's time: a number of ms from 0 (undefined is 0). */
|
|
20
|
+
export declare function checkEffectTime(time: number | undefined): number;
|
|
21
|
+
/** An effect layer's animation: a duration in ms, more than 0 and at most `MAX_EFFECT_ANIMATION_MS`. */
|
|
22
|
+
export declare function checkEffectAnimation(animate: ImageEffectAnimation): ImageEffectAnimation;
|
|
23
|
+
/** What the panels show of an effect layer's timing (see `effectLayerTiming`). */
|
|
24
|
+
export interface EffectLayerTiming {
|
|
25
|
+
/** A still frame at `time`, or playing from `time` in an animation. */
|
|
26
|
+
mode: 'still' | 'animate';
|
|
27
|
+
/** The frame drawn (ms): the still frame, and where the animation starts. */
|
|
28
|
+
time: number;
|
|
29
|
+
/** How far the frame can be moved (ms): one loop of a looping effect, else one play. */
|
|
30
|
+
range: number;
|
|
31
|
+
/** How long it plays in an animation (ms): its own, else the effect's play. */
|
|
32
|
+
duration: number;
|
|
33
|
+
/** The effect repeats every `range` ms. */
|
|
34
|
+
loops: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* An effect layer's timing for the panels, or null when its effect does not change over time
|
|
38
|
+
* (the provider gives it no `duration`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function effectLayerTiming(g: ImageEffectGenerator, d: Pick<EffectSummary, 'duration' | 'loop'> | null | undefined): EffectLayerTiming | null;
|