@pixerate/editor 0.123.0 → 0.124.1

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.
Files changed (116) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.cjs.map +1 -1
  3. package/dist/agent/index.js +3 -3
  4. package/dist/animation/index.d.cts +2 -1
  5. package/dist/animation/index.d.ts +2 -1
  6. package/dist/animation-editor/index.cjs.map +1 -1
  7. package/dist/animation-editor/index.d.cts +11 -733
  8. package/dist/animation-editor/index.d.ts +11 -733
  9. package/dist/animation-editor/index.js.map +1 -1
  10. package/dist/{bezier-C-W9LFyD.d.cts → bezier-CBzKKDjl.d.cts} +2 -66
  11. package/dist/{bezier-DQZE5dxA.d.ts → bezier-CWv6LQal.d.ts} +2 -66
  12. package/dist/{brushstrokeRender-M4S65DHT.cjs → brushstrokeRender-EELTV7U4.cjs} +8 -8
  13. package/dist/{brushstrokeRender-M4S65DHT.cjs.map → brushstrokeRender-EELTV7U4.cjs.map} +1 -1
  14. package/dist/{brushstrokeRender-CDDRSV2W.js → brushstrokeRender-LRV7GXGD.js} +3 -3
  15. package/dist/canvas/index.cjs +6 -6
  16. package/dist/canvas/index.d.cts +10 -6
  17. package/dist/canvas/index.d.ts +10 -6
  18. package/dist/canvas/index.js +1 -1
  19. package/dist/{chunk-SMJGE7HM.cjs → chunk-4QQZV5Y5.cjs} +512 -39
  20. package/dist/chunk-4QQZV5Y5.cjs.map +1 -0
  21. package/dist/{chunk-TVXFHE4T.js → chunk-64PYNEHJ.js} +45 -12
  22. package/dist/chunk-64PYNEHJ.js.map +1 -0
  23. package/dist/{chunk-2IW3BWRT.cjs → chunk-7LQO6OHH.cjs} +2 -2
  24. package/dist/{chunk-2IW3BWRT.cjs.map → chunk-7LQO6OHH.cjs.map} +1 -1
  25. package/dist/chunk-CN4PJHUG.cjs +234 -0
  26. package/dist/chunk-CN4PJHUG.cjs.map +1 -0
  27. package/dist/chunk-FHYDQOPC.cjs +215 -0
  28. package/dist/chunk-FHYDQOPC.cjs.map +1 -0
  29. package/dist/{chunk-KMBC4HNV.js → chunk-J67OLQUQ.js} +2 -2
  30. package/dist/chunk-JRXJTPES.js +215 -0
  31. package/dist/chunk-JRXJTPES.js.map +1 -0
  32. package/dist/{chunk-IPFRWILU.js → chunk-ORHMVUR6.js} +488 -15
  33. package/dist/chunk-ORHMVUR6.js.map +1 -0
  34. package/dist/{chunk-6CJRDJCU.cjs → chunk-PEHYWUVV.cjs} +176 -111
  35. package/dist/chunk-PEHYWUVV.cjs.map +1 -0
  36. package/dist/chunk-QEG3GRN2.js +234 -0
  37. package/dist/chunk-QEG3GRN2.js.map +1 -0
  38. package/dist/{chunk-6ZBR4J62.js → chunk-WATAQ2Z2.js} +79 -14
  39. package/dist/chunk-WATAQ2Z2.js.map +1 -0
  40. package/dist/{chunk-RLRUAPBJ.cjs → chunk-Z6OUCS2K.cjs} +47 -14
  41. package/dist/chunk-Z6OUCS2K.cjs.map +1 -0
  42. package/dist/effects/index.cjs +605 -0
  43. package/dist/effects/index.cjs.map +1 -0
  44. package/dist/effects/index.d.cts +163 -0
  45. package/dist/effects/index.d.ts +163 -0
  46. package/dist/effects/index.js +605 -0
  47. package/dist/effects/index.js.map +1 -0
  48. package/dist/evaluate-2S3vSPLy.d.cts +55 -0
  49. package/dist/evaluate-CfmT6xA3.d.ts +55 -0
  50. package/dist/{export-DfjzHAJI.d.ts → export-BGtXgMJ3.d.ts} +536 -128
  51. package/dist/{export-Dv0-ZVrV.d.cts → export-BdfY_lH0.d.ts} +392 -5
  52. package/dist/{export-Dv0-ZVrV.d.ts → export-CbpjwxaI.d.cts} +392 -5
  53. package/dist/{export-lU5qSpHE.d.cts → export-WmYoWmLM.d.cts} +536 -128
  54. package/dist/flair-CxB3fOcU.d.ts +739 -0
  55. package/dist/flair-D7BxyBwL.d.cts +739 -0
  56. package/dist/gradient-KDfCvhuj.d.cts +66 -0
  57. package/dist/gradient-KDfCvhuj.d.ts +66 -0
  58. package/dist/helpers-777mC6dc.d.cts +253 -0
  59. package/dist/helpers-DX2_7L5t.d.ts +253 -0
  60. package/dist/image-editor/index.cjs +1296 -805
  61. package/dist/image-editor/index.cjs.map +1 -1
  62. package/dist/image-editor/index.d.cts +39 -182
  63. package/dist/image-editor/index.d.ts +39 -182
  64. package/dist/image-editor/index.js +497 -6
  65. package/dist/image-editor/index.js.map +1 -1
  66. package/dist/{opentype-p8Xxf-Wh.d.ts → opentype-14PDI5D2.d.ts} +1 -1
  67. package/dist/{opentype-BV7wWatY.d.cts → opentype-D80AVmMu.d.cts} +1 -1
  68. package/dist/paint/index.cjs +2 -2
  69. package/dist/paint/index.d.cts +32 -10
  70. package/dist/paint/index.d.ts +32 -10
  71. package/dist/paint/index.js +1 -1
  72. package/dist/paint-editor/index.cjs +10 -8
  73. package/dist/paint-editor/index.cjs.map +1 -1
  74. package/dist/paint-editor/index.d.cts +11 -5
  75. package/dist/paint-editor/index.d.ts +11 -5
  76. package/dist/paint-editor/index.js +4 -2
  77. package/dist/paint-editor/index.js.map +1 -1
  78. package/dist/parameters/index.d.cts +4 -184
  79. package/dist/parameters/index.d.ts +4 -184
  80. package/dist/{pathfinder-IxcyhO00.d.cts → pathfinder-C0UoFypx.d.cts} +1 -1
  81. package/dist/{pathfinder-StVChJOT.d.ts → pathfinder-DjISxkUv.d.ts} +1 -1
  82. package/dist/{player-DrcP2xqj.d.cts → player-CBtEoU2x.d.cts} +2 -54
  83. package/dist/{player-DV4Jujdq.d.ts → player-cGLrngL_.d.ts} +2 -54
  84. package/dist/{svg-BtND4TJu.d.cts → svg-BDrjRnL1.d.cts} +3 -2
  85. package/dist/{svg-BfVXG7hL.d.ts → svg-C-vZ3q50.d.ts} +3 -2
  86. package/dist/tools/index.cjs +2 -2
  87. package/dist/tools/index.cjs.map +1 -1
  88. package/dist/tools/index.js +3 -3
  89. package/dist/tools-C7JtLYzo.d.ts +126 -0
  90. package/dist/tools-DHKGHiSK.d.cts +126 -0
  91. package/dist/{types-fuXEme9j.d.ts → types-BcX7sCsn.d.ts} +2 -2
  92. package/dist/{types-DJF3Ghn5.d.cts → types-CznabG7m.d.cts} +2 -2
  93. package/dist/types-D3-J3TFf.d.ts +185 -0
  94. package/dist/types-DMcf_z4J.d.cts +185 -0
  95. package/dist/vector/index.d.cts +10 -10
  96. package/dist/vector/index.d.ts +10 -10
  97. package/dist/video-editor/index.cjs +749 -277
  98. package/dist/video-editor/index.cjs.map +1 -1
  99. package/dist/video-editor/index.d.cts +265 -138
  100. package/dist/video-editor/index.d.ts +265 -138
  101. package/dist/video-editor/index.js +490 -18
  102. package/dist/video-editor/index.js.map +1 -1
  103. package/dist/{warp-BjmAgQwq.d.cts → warp-CH9MM435.d.cts} +1 -1
  104. package/dist/{warp-Ca5fedzS.d.ts → warp-k_PdtXOG.d.ts} +1 -1
  105. package/package.json +6 -1
  106. package/skills/pixerate-image-editor/SKILL.md +7 -0
  107. package/skills/pixerate-image-editor/references/provider-effects.md +35 -0
  108. package/skills/pixerate-video-editor/SKILL.md +39 -1
  109. package/dist/chunk-6CJRDJCU.cjs.map +0 -1
  110. package/dist/chunk-6ZBR4J62.js.map +0 -1
  111. package/dist/chunk-IPFRWILU.js.map +0 -1
  112. package/dist/chunk-RLRUAPBJ.cjs.map +0 -1
  113. package/dist/chunk-SMJGE7HM.cjs.map +0 -1
  114. package/dist/chunk-TVXFHE4T.js.map +0 -1
  115. /package/dist/{brushstrokeRender-CDDRSV2W.js.map → brushstrokeRender-LRV7GXGD.js.map} +0 -0
  116. /package/dist/{chunk-KMBC4HNV.js.map → chunk-J67OLQUQ.js.map} +0 -0
@@ -0,0 +1,234 @@
1
+ import {
2
+ validateJsonSchema
3
+ } from "./chunk-4FX5ACVG.js";
4
+
5
+ // src/effects/errors.ts
6
+ var EFFECT_ERROR_CODES = ["unknown_effect", "invalid_values", "unsupported", "disposed", "no_provider"];
7
+ var EffectError = class extends Error {
8
+ code;
9
+ effectId;
10
+ constructor(code, message, effectId) {
11
+ super(message);
12
+ this.name = "EffectError";
13
+ this.code = code;
14
+ this.effectId = effectId;
15
+ }
16
+ };
17
+ var UnknownEffectError = class extends EffectError {
18
+ constructor(effectId, providerName) {
19
+ super(
20
+ "unknown_effect",
21
+ `Unknown effect "${effectId}"${providerName ? ` in ${providerName}` : ""}. Call search_effects or list() for valid ids.`,
22
+ effectId
23
+ );
24
+ this.name = "UnknownEffectError";
25
+ }
26
+ };
27
+ var EffectControlError = class extends EffectError {
28
+ details;
29
+ constructor(effectId, details) {
30
+ const lines = details.map((d) => `- ${d.path || "/"}: ${d.message}`).join("\n");
31
+ super("invalid_values", `Invalid values for effect "${effectId}":
32
+ ${lines}`, effectId);
33
+ this.name = "EffectControlError";
34
+ this.details = details;
35
+ }
36
+ };
37
+ var EffectUnsupportedError = class extends EffectError {
38
+ constructor(message, effectId) {
39
+ super("unsupported", message, effectId);
40
+ this.name = "EffectUnsupportedError";
41
+ }
42
+ };
43
+ var EffectDisposedError = class extends EffectError {
44
+ constructor(effectId) {
45
+ super("disposed", `The renderer for "${effectId}" was disposed. Create a new renderer.`, effectId);
46
+ this.name = "EffectDisposedError";
47
+ }
48
+ };
49
+ var NoEffectsProviderError = class extends EffectError {
50
+ constructor() {
51
+ super("no_provider", "No effects provider is registered with this editor. The host must call setEffectsProvider(provider) first.");
52
+ this.name = "NoEffectsProviderError";
53
+ }
54
+ };
55
+ function isEffectError(error) {
56
+ if (error instanceof EffectError) return true;
57
+ const code = error instanceof Error ? error.code : void 0;
58
+ return typeof code === "string" && EFFECT_ERROR_CODES.includes(code);
59
+ }
60
+
61
+ // src/effects/helpers.ts
62
+ function effectValueErrors(description, values) {
63
+ if (values === void 0) return [];
64
+ return validateJsonSchema(values, { ...description.controls, required: [] });
65
+ }
66
+ function resolveEffectValues(description, values) {
67
+ const errors = effectValueErrors(description, values);
68
+ if (errors.length) throw new EffectControlError(description.id, errors);
69
+ return { ...description.defaults, ...values ?? {} };
70
+ }
71
+ var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use));
72
+ function searchEffectSummaries(catalog, query = "", options = {}) {
73
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
74
+ const scored = [];
75
+ catalog.forEach((e, i) => {
76
+ if (!matchesFilters(e, options)) return;
77
+ const name = e.name.toLowerCase();
78
+ const hay = [e.id, e.name, e.category, ...e.tags ?? [], e.description ?? ""].join(" ").toLowerCase();
79
+ let score = 0;
80
+ for (const w of words) {
81
+ if (!hay.includes(w)) return;
82
+ score += name.startsWith(w) ? 3 : name.includes(w) ? 2 : 1;
83
+ }
84
+ scored.push({ e, score, i });
85
+ });
86
+ scored.sort((a, b) => b.score - a.score || a.i - b.i);
87
+ const out = scored.map((s) => s.e);
88
+ return options.limit !== void 0 ? out.slice(0, Math.max(0, options.limit)) : out;
89
+ }
90
+ function createEffectPixels(width, height, float = false) {
91
+ const n = Math.max(0, width * height * 4);
92
+ return { width, height, data: new Uint8ClampedArray(n), ...float ? { float: new Float32Array(n) } : {} };
93
+ }
94
+ function isEffectPixels(source) {
95
+ return !!source && typeof source === "object" && source.data instanceof Uint8ClampedArray && typeof source.width === "number" && typeof source.height === "number";
96
+ }
97
+ var srgbToLinear = (v) => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
98
+ var linearToSrgb = (v) => v > 0 ? v <= 31308e-7 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055 : 0;
99
+ function encodeEffectFloat(pixels) {
100
+ const f = pixels.float;
101
+ if (!f) return pixels;
102
+ const d = pixels.data;
103
+ for (let i = 0; i < d.length; i += 4) {
104
+ d[i] = Math.round(linearToSrgb(f[i]) * 255);
105
+ d[i + 1] = Math.round(linearToSrgb(f[i + 1]) * 255);
106
+ d[i + 2] = Math.round(linearToSrgb(f[i + 2]) * 255);
107
+ d[i + 3] = Math.round(Math.min(1, Math.max(0, f[i + 3])) * 255);
108
+ }
109
+ return pixels;
110
+ }
111
+ function applyEffectMask(result, mask, source) {
112
+ const { data } = result;
113
+ const m = mask.data;
114
+ for (let i = 0; i < data.length; i += 4) {
115
+ const k = m[i + 3] / 255;
116
+ if (k >= 1) continue;
117
+ if (source) {
118
+ for (let c = 0; c < 4; c++) data[i + c] = source.data[i + c] + (data[i + c] - source.data[i + c]) * k;
119
+ if (result.float && source.float) {
120
+ for (let c = 0; c < 4; c++) result.float[i + c] = source.float[i + c] + (result.float[i + c] - source.float[i + c]) * k;
121
+ }
122
+ } else {
123
+ data[i + 3] = data[i + 3] * k;
124
+ if (result.float) result.float[i + 3] *= k;
125
+ }
126
+ }
127
+ return result;
128
+ }
129
+ var EffectsSlot = class {
130
+ current = null;
131
+ listeners = /* @__PURE__ */ new Set();
132
+ get provider() {
133
+ return this.current;
134
+ }
135
+ set(provider) {
136
+ if (provider === this.current) return;
137
+ this.current = provider;
138
+ for (const l of [...this.listeners]) l(provider);
139
+ }
140
+ /** Sets `provider` and returns a function that clears it again (if it is still the one set). */
141
+ use(provider) {
142
+ this.set(provider);
143
+ return () => {
144
+ if (this.current === provider) this.set(null);
145
+ };
146
+ }
147
+ /** The provider, or a `NoEffectsProviderError`. */
148
+ require() {
149
+ if (!this.current) throw new NoEffectsProviderError();
150
+ return this.current;
151
+ }
152
+ subscribe(listener) {
153
+ this.listeners.add(listener);
154
+ return () => this.listeners.delete(listener);
155
+ }
156
+ };
157
+
158
+ // src/effects/pool.ts
159
+ var EffectRendererPool = class {
160
+ entries = /* @__PURE__ */ new Map();
161
+ maxRenderers;
162
+ constructor(options = {}) {
163
+ this.maxRenderers = Math.max(1, options.maxRenderers ?? 4);
164
+ }
165
+ /** Live renderers now. */
166
+ get size() {
167
+ return this.entries.size;
168
+ }
169
+ /** Renders `request` with the provider's renderer for this effect, size and depth (made on first use). */
170
+ render(provider, id, size, request) {
171
+ const depth = size.depth ?? 8;
172
+ const key = `${provider.id}:${id}@${size.width}x${size.height}x${depth}`;
173
+ let entry = this.entries.get(key);
174
+ if (entry) {
175
+ this.entries.delete(key);
176
+ this.entries.set(key, entry);
177
+ } else {
178
+ const renderer = Promise.resolve().then(() => provider.createRenderer(id, { width: size.width, height: size.height, depth }));
179
+ renderer.catch(() => {
180
+ if (this.entries.get(key)?.renderer === renderer) this.entries.delete(key);
181
+ });
182
+ entry = { renderer, queue: Promise.resolve() };
183
+ this.entries.set(key, entry);
184
+ this.evict();
185
+ }
186
+ const e = entry;
187
+ const next = e.queue.then(() => e.renderer).then((r) => r.render(request));
188
+ e.queue = next.catch(() => void 0);
189
+ return next;
190
+ }
191
+ /** Disposes the least recently used renderers over the limit, after their queued renders. */
192
+ evict() {
193
+ while (this.entries.size > this.maxRenderers) {
194
+ const [key, entry] = this.entries.entries().next().value;
195
+ this.entries.delete(key);
196
+ dispose(entry);
197
+ }
198
+ }
199
+ /** Disposes every renderer (after its queued renders). */
200
+ clear() {
201
+ for (const entry of this.entries.values()) dispose(entry);
202
+ this.entries.clear();
203
+ }
204
+ };
205
+ function dispose(entry) {
206
+ entry.queue.then(() => entry.renderer).then(
207
+ (r) => r.dispose(),
208
+ () => {
209
+ }
210
+ );
211
+ }
212
+
213
+ export {
214
+ EFFECT_ERROR_CODES,
215
+ EffectError,
216
+ UnknownEffectError,
217
+ EffectControlError,
218
+ EffectUnsupportedError,
219
+ EffectDisposedError,
220
+ NoEffectsProviderError,
221
+ isEffectError,
222
+ effectValueErrors,
223
+ resolveEffectValues,
224
+ searchEffectSummaries,
225
+ createEffectPixels,
226
+ isEffectPixels,
227
+ srgbToLinear,
228
+ linearToSrgb,
229
+ encodeEffectFloat,
230
+ applyEffectMask,
231
+ EffectsSlot,
232
+ EffectRendererPool
233
+ };
234
+ //# sourceMappingURL=chunk-QEG3GRN2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/effects/errors.ts","../src/effects/helpers.ts","../src/effects/pool.ts"],"sourcesContent":["import type { SchemaValidationError } from '../tools/schema.js';\n\nexport type EffectErrorCode = 'unknown_effect' | 'invalid_values' | 'unsupported' | 'disposed' | 'no_provider';\n\nexport const EFFECT_ERROR_CODES: readonly EffectErrorCode[] = ['unknown_effect', 'invalid_values', 'unsupported', 'disposed', 'no_provider'];\n\n/**\n * Base of every effects error. Check `code` (or use `isEffectError`) rather than `instanceof`\n * across packages, since a provider may be bundled with its own copy of this class.\n */\nexport class EffectError extends Error {\n\treadonly code: EffectErrorCode;\n\treadonly effectId?: string;\n\n\tconstructor(code: EffectErrorCode, message: string, effectId?: string) {\n\t\tsuper(message);\n\t\tthis.name = 'EffectError';\n\t\tthis.code = code;\n\t\tthis.effectId = effectId;\n\t}\n}\n\n/** No effect has this id. */\nexport class UnknownEffectError extends EffectError {\n\tconstructor(effectId: string, providerName?: string) {\n\t\tsuper(\n\t\t\t'unknown_effect',\n\t\t\t`Unknown effect \"${effectId}\"${providerName ? ` in ${providerName}` : ''}. Call search_effects or list() for valid ids.`,\n\t\t\teffectId\n\t\t);\n\t\tthis.name = 'UnknownEffectError';\n\t}\n}\n\n/** Control values do not match the effect's control schema (unknown control, wrong type, out of range). */\nexport class EffectControlError extends EffectError {\n\treadonly details: readonly SchemaValidationError[];\n\n\tconstructor(effectId: string, details: readonly SchemaValidationError[]) {\n\t\tconst lines = details.map((d) => `- ${d.path || '/'}: ${d.message}`).join('\\n');\n\t\tsuper('invalid_values', `Invalid values for effect \"${effectId}\":\\n${lines}`, effectId);\n\t\tthis.name = 'EffectControlError';\n\t\tthis.details = details;\n\t}\n}\n\n/** The provider cannot do this (a vector effect asked for pixels, a size above `maxSize`, a missing input...). */\nexport class EffectUnsupportedError extends EffectError {\n\tconstructor(message: string, effectId?: string) {\n\t\tsuper('unsupported', message, effectId);\n\t\tthis.name = 'EffectUnsupportedError';\n\t}\n}\n\n/** The renderer was disposed. Create a new one. */\nexport class EffectDisposedError extends EffectError {\n\tconstructor(effectId: string) {\n\t\tsuper('disposed', `The renderer for \"${effectId}\" was disposed. Create a new renderer.`, effectId);\n\t\tthis.name = 'EffectDisposedError';\n\t}\n}\n\n/** No effects provider is registered with this editor. */\nexport class NoEffectsProviderError extends EffectError {\n\tconstructor() {\n\t\tsuper('no_provider', 'No effects provider is registered with this editor. The host must call setEffectsProvider(provider) first.');\n\t\tthis.name = 'NoEffectsProviderError';\n\t}\n}\n\n/** True for any effects error, including one thrown by another copy of this module. */\nexport function isEffectError(error: unknown): error is EffectError {\n\tif (error instanceof EffectError) return true;\n\tconst code = error instanceof Error ? (error as unknown as { code?: unknown }).code : undefined;\n\treturn typeof code === 'string' && EFFECT_ERROR_CODES.includes(code as EffectErrorCode);\n}\n","/**\n * Helpers for providers and editors: value validation, catalog search, pixel buffers, masking,\n * and a provider slot editors hold.\n */\n\nimport { validateJsonSchema, type SchemaValidationError } from '../tools/schema.js';\nimport { EffectControlError, NoEffectsProviderError } from './errors.js';\nimport type {\n\tEffectDescription,\n\tEffectImageSource,\n\tEffectPixels,\n\tEffectProvider,\n\tEffectSearchOptions,\n\tEffectSummary,\n\tEffectValues\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Values\n// ---------------------------------------------------------------------------\n\n/** Problems with `values` against an effect's control schema (unknown controls, types, ranges). Partial values are fine. */\nexport function effectValueErrors(description: Pick<EffectDescription, 'controls'>, values: unknown): SchemaValidationError[] {\n\tif (values === undefined) return [];\n\treturn validateJsonSchema(values, { ...description.controls, required: [] });\n}\n\n/**\n * `values` over the effect's defaults, validated against its control schema. Throws\n * `EffectControlError` naming every bad control. Providers call this in `render`; editors call\n * it before saving values.\n */\nexport function resolveEffectValues(description: Pick<EffectDescription, 'id' | 'controls' | 'defaults'>, values?: EffectValues): Record<string, EffectValues[string]> {\n\tconst errors = effectValueErrors(description, values);\n\tif (errors.length) throw new EffectControlError(description.id, errors);\n\treturn { ...description.defaults, ...(values ?? {}) };\n}\n\n// ---------------------------------------------------------------------------\n// Search\n// ---------------------------------------------------------------------------\n\nconst matchesFilters = (e: EffectSummary, o: EffectSearchOptions) =>\n\t(!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use));\n\n/**\n * Filters a catalog and ranks it against `query` (every word must appear in the id, name,\n * category, tags or description; name matches rank first). An empty query keeps catalog order.\n * Providers can implement `list` and `search` with it.\n */\nexport function searchEffectSummaries<T extends EffectSummary>(catalog: readonly T[], query = '', options: EffectSearchOptions = {}): T[] {\n\tconst words = query.toLowerCase().split(/\\s+/).filter(Boolean);\n\tconst scored: { e: T; score: number; i: number }[] = [];\n\tcatalog.forEach((e, i) => {\n\t\tif (!matchesFilters(e, options)) return;\n\t\tconst name = e.name.toLowerCase();\n\t\tconst hay = [e.id, e.name, e.category, ...(e.tags ?? []), e.description ?? ''].join(' ').toLowerCase();\n\t\tlet score = 0;\n\t\tfor (const w of words) {\n\t\t\tif (!hay.includes(w)) return;\n\t\t\tscore += name.startsWith(w) ? 3 : name.includes(w) ? 2 : 1;\n\t\t}\n\t\tscored.push({ e, score, i });\n\t});\n\tscored.sort((a, b) => b.score - a.score || a.i - b.i);\n\tconst out = scored.map((s) => s.e);\n\treturn options.limit !== undefined ? out.slice(0, Math.max(0, options.limit)) : out;\n}\n\n// ---------------------------------------------------------------------------\n// Pixels\n// ---------------------------------------------------------------------------\n\n/** A transparent picture, with float samples when `float` is set. */\nexport function createEffectPixels(width: number, height: number, float = false): EffectPixels {\n\tconst n = Math.max(0, width * height * 4);\n\treturn { width, height, data: new Uint8ClampedArray(n), ...(float ? { float: new Float32Array(n) } : {}) };\n}\n\n/** True for an `EffectPixels` (rather than a host image object). */\nexport function isEffectPixels(source: EffectImageSource | unknown): source is EffectPixels {\n\treturn (\n\t\t!!source &&\n\t\ttypeof source === 'object' &&\n\t\t(source as EffectPixels).data instanceof Uint8ClampedArray &&\n\t\ttypeof (source as EffectPixels).width === 'number' &&\n\t\ttypeof (source as EffectPixels).height === 'number'\n\t);\n}\n\n/** sRGB-encoded 0–1 to linear light. */\nexport const srgbToLinear = (v: number) => (v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4));\n/** Linear light to sRGB-encoded (clamped below at 0; may exceed 1). */\nexport const linearToSrgb = (v: number) => (v > 0 ? (v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055) : 0);\n\n/** Fills `pixels.data` from `pixels.float` (linear float to clamped 8-bit sRGB). */\nexport function encodeEffectFloat(pixels: EffectPixels): EffectPixels {\n\tconst f = pixels.float;\n\tif (!f) return pixels;\n\tconst d = pixels.data;\n\tfor (let i = 0; i < d.length; i += 4) {\n\t\td[i] = Math.round(linearToSrgb(f[i]) * 255);\n\t\td[i + 1] = Math.round(linearToSrgb(f[i + 1]) * 255);\n\t\td[i + 2] = Math.round(linearToSrgb(f[i + 2]) * 255);\n\t\td[i + 3] = Math.round(Math.min(1, Math.max(0, f[i + 3])) * 255);\n\t}\n\treturn pixels;\n}\n\n/**\n * Blends an effect `result` back over its `source` by `mask` coverage (alpha channel), in place\n * on `result`: where the mask is 0 the source shows, where it is 255 the result. Without a\n * source (generators), the result's alpha is multiplied by coverage. Editors use it when the\n * provider does not `supportsMask`. All three must have the same size.\n */\nexport function applyEffectMask(result: EffectPixels, mask: EffectPixels, source?: EffectPixels): EffectPixels {\n\tconst { data } = result;\n\tconst m = mask.data;\n\tfor (let i = 0; i < data.length; i += 4) {\n\t\tconst k = m[i + 3] / 255;\n\t\tif (k >= 1) continue;\n\t\tif (source) {\n\t\t\tfor (let c = 0; c < 4; c++) data[i + c] = source.data[i + c] + (data[i + c] - source.data[i + c]) * k;\n\t\t\tif (result.float && source.float) {\n\t\t\t\tfor (let c = 0; c < 4; c++) result.float[i + c] = source.float[i + c] + (result.float[i + c] - source.float[i + c]) * k;\n\t\t\t}\n\t\t} else {\n\t\t\tdata[i + 3] = data[i + 3] * k;\n\t\t\tif (result.float) result.float[i + 3] *= k;\n\t\t}\n\t}\n\treturn result;\n}\n\n// ---------------------------------------------------------------------------\n// The slot an editor keeps\n// ---------------------------------------------------------------------------\n\n/**\n * Holds an editor's effects provider. Controllers expose it as `effectsProvider`,\n * `setEffectsProvider(provider | null)` and `useEffects(provider)`; listeners hear every change.\n */\nexport class EffectsSlot {\n\tprivate current: EffectProvider | null = null;\n\tprivate readonly listeners = new Set<(provider: EffectProvider | null) => void>();\n\n\tget provider(): EffectProvider | null {\n\t\treturn this.current;\n\t}\n\n\tset(provider: EffectProvider | null): void {\n\t\tif (provider === this.current) return;\n\t\tthis.current = provider;\n\t\tfor (const l of [...this.listeners]) l(provider);\n\t}\n\n\t/** Sets `provider` and returns a function that clears it again (if it is still the one set). */\n\tuse(provider: EffectProvider): () => void {\n\t\tthis.set(provider);\n\t\treturn () => {\n\t\t\tif (this.current === provider) this.set(null);\n\t\t};\n\t}\n\n\t/** The provider, or a `NoEffectsProviderError`. */\n\trequire(): EffectProvider {\n\t\tif (!this.current) throw new NoEffectsProviderError();\n\t\treturn this.current;\n\t}\n\n\tsubscribe(listener: (provider: EffectProvider | null) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n}\n","/**\n * `EffectRendererPool`: keeps a few live renderers, reused per effect, size and depth.\n *\n * GPU providers hold one graphics context per renderer, and browsers allow only about sixteen\n * WebGL contexts at a time (the oldest is lost after that). So the pool keeps at most\n * `maxRenderers` renderers. It disposes the least recently used one once its renders finish, and it\n * runs one render at a time per renderer, as the contract requires.\n */\n\nimport type { EffectPixels, EffectProvider, EffectRenderer, EffectRenderRequest } from './types.js';\n\ninterface PoolEntry {\n\trenderer: Promise<EffectRenderer>;\n\t/** The last render queued on it. */\n\tqueue: Promise<unknown>;\n}\n\nexport interface EffectRendererPoolOptions {\n\t/** Most live renderers (default 4). */\n\tmaxRenderers?: number;\n}\n\nexport class EffectRendererPool {\n\tprivate readonly entries = new Map<string, PoolEntry>();\n\tprivate readonly maxRenderers: number;\n\n\tconstructor(options: EffectRendererPoolOptions = {}) {\n\t\tthis.maxRenderers = Math.max(1, options.maxRenderers ?? 4);\n\t}\n\n\t/** Live renderers now. */\n\tget size(): number {\n\t\treturn this.entries.size;\n\t}\n\n\t/** Renders `request` with the provider's renderer for this effect, size and depth (made on first use). */\n\trender(provider: EffectProvider, id: string, size: { width: number; height: number; depth?: 8 | 16 | 32 }, request: EffectRenderRequest): Promise<EffectPixels> {\n\t\tconst depth = size.depth ?? 8;\n\t\tconst key = `${provider.id}:${id}@${size.width}x${size.height}x${depth}`;\n\t\tlet entry = this.entries.get(key);\n\t\tif (entry) {\n\t\t\tthis.entries.delete(key);\n\t\t\tthis.entries.set(key, entry);\n\t\t} else {\n\t\t\tconst renderer = Promise.resolve().then(() => provider.createRenderer(id, { width: size.width, height: size.height, depth }));\n\t\t\trenderer.catch(() => {\n\t\t\t\tif (this.entries.get(key)?.renderer === renderer) this.entries.delete(key);\n\t\t\t});\n\t\t\tentry = { renderer, queue: Promise.resolve() };\n\t\t\tthis.entries.set(key, entry);\n\t\t\tthis.evict();\n\t\t}\n\t\tconst e = entry;\n\t\tconst next = e.queue.then(() => e.renderer).then((r) => r.render(request));\n\t\te.queue = next.catch(() => undefined);\n\t\treturn next;\n\t}\n\n\t/** Disposes the least recently used renderers over the limit, after their queued renders. */\n\tprivate evict(): void {\n\t\twhile (this.entries.size > this.maxRenderers) {\n\t\t\tconst [key, entry] = this.entries.entries().next().value!;\n\t\t\tthis.entries.delete(key);\n\t\t\tdispose(entry);\n\t\t}\n\t}\n\n\t/** Disposes every renderer (after its queued renders). */\n\tclear(): void {\n\t\tfor (const entry of this.entries.values()) dispose(entry);\n\t\tthis.entries.clear();\n\t}\n}\n\nfunction dispose(entry: PoolEntry) {\n\tentry.queue\n\t\t.then(() => entry.renderer)\n\t\t.then(\n\t\t\t(r) => r.dispose(),\n\t\t\t() => {}\n\t\t);\n}\n"],"mappings":"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","names":[]}
@@ -4661,6 +4661,15 @@ var fail4 = (message) => {
4661
4661
  throw new LayerOperationError(message);
4662
4662
  };
4663
4663
  var SMART_RENDER_MARGIN = MAX_EFFECT_REACH;
4664
+ var incompleteSurfaces = /* @__PURE__ */ new WeakSet();
4665
+ var isSmartSurfaceIncomplete = (surface) => incompleteSurfaces.has(surface);
4666
+ var surfaceIds = /* @__PURE__ */ new WeakMap();
4667
+ var nextSurfaceId = 0;
4668
+ var surfaceId = (s) => {
4669
+ let id = surfaceIds.get(s);
4670
+ if (id === void 0) surfaceIds.set(s, id = ++nextSurfaceId);
4671
+ return id;
4672
+ };
4664
4673
  function newContentsId() {
4665
4674
  return newLayerId("so");
4666
4675
  }
@@ -4802,7 +4811,15 @@ function placeContents(layer, canvas, options = {}) {
4802
4811
  }
4803
4812
  function activeSmartFilters(stack) {
4804
4813
  if (!stack?.enabled) return [];
4805
- return stack.filters.filter((f) => f.kind !== "psd" && isActiveFilter(f) && f.opacity > 0);
4814
+ return stack.filters.filter((f) => f.kind !== "psd" && f.kind !== "effect" && isActiveFilter(f) && f.opacity > 0);
4815
+ }
4816
+ function activeEffectSmartFilters(stack) {
4817
+ if (!stack?.enabled) return [];
4818
+ return stack.filters.filter((f) => f.kind === "effect" && f.enabled && f.opacity > 0);
4819
+ }
4820
+ function runnableSmartFilters(stack) {
4821
+ if (!stack?.enabled) return [];
4822
+ return stack.filters.filter((f) => f.opacity > 0 && (f.kind === "effect" ? f.enabled : f.kind !== "psd" && isActiveFilter(f)));
4806
4823
  }
4807
4824
  function filterMaskCoverage(mask, canvas) {
4808
4825
  if (!mask || mask.enabled === false || mask.source) return null;
@@ -4823,27 +4840,41 @@ function filterMaskCoverage(mask, canvas) {
4823
4840
  return v;
4824
4841
  }
4825
4842
  function applySmartFilters(placed, stack, canvas) {
4826
- const active = activeSmartFilters(stack);
4843
+ const active = runnableSmartFilters(stack);
4827
4844
  if (!active.length || !(canvas.width > 0 && canvas.height > 0)) return placed;
4828
4845
  const W = canvas.width;
4829
4846
  const H = canvas.height;
4830
- const key = JSON.stringify([W, H, active.map((f) => ({ ...f, id: void 0 }))]);
4847
+ const token = active.some((f) => f.kind === "effect") ? canvas.effects?.token ?? -1 : 0;
4848
+ const key = JSON.stringify([W, H, token, active.map((f) => ({ ...f, id: void 0 }))]);
4831
4849
  let entry = filteredCache.get(placed);
4850
+ let incomplete = false;
4832
4851
  if (!entry || entry.key !== key) {
4833
4852
  const base = readRegion(placed, { x: 0, y: 0, width: W, height: H });
4834
4853
  let acc = new Uint8ClampedArray(base);
4835
- for (const f of active) {
4836
- const out = new Uint8ClampedArray(acc);
4837
- runLayerFilters(out, W, H, [f]);
4854
+ for (const [i, f] of active.entries()) {
4855
+ let out2;
4856
+ if (f.kind === "effect") {
4857
+ if (incomplete) continue;
4858
+ const inputKey = JSON.stringify([surfaceId(placed), W, H, active.slice(0, i + 1).map((x) => ({ ...x, id: void 0, blend: x === f ? void 0 : x.blend, opacity: x === f ? void 0 : x.opacity }))]);
4859
+ const r = canvas.effects?.resolve(f, acc, W, H, inputKey);
4860
+ if (!r?.result) {
4861
+ if (r?.pending) incomplete = true;
4862
+ continue;
4863
+ }
4864
+ out2 = new Uint8ClampedArray(r.result);
4865
+ } else {
4866
+ out2 = new Uint8ClampedArray(acc);
4867
+ runLayerFilters(out2, W, H, [f]);
4868
+ }
4838
4869
  if (f.blend === "normal") {
4839
- if (f.opacity >= 1) acc = out;
4840
- else lerpInto(acc, out, f.opacity, null);
4870
+ if (f.opacity >= 1) acc = out2;
4871
+ else lerpInto(acc, out2, f.opacity, null);
4841
4872
  } else {
4842
- blendTile(acc, out, f.blend, f.opacity, null);
4873
+ blendTile(acc, out2, f.blend, f.opacity, null);
4843
4874
  }
4844
4875
  }
4845
4876
  entry = { key, base, filtered: acc };
4846
- filteredCache.set(placed, entry);
4877
+ if (!incomplete) filteredCache.set(placed, entry);
4847
4878
  }
4848
4879
  const cov = filterMaskCoverage(stack.mask, canvas);
4849
4880
  let result = entry.filtered;
@@ -4851,7 +4882,9 @@ function applySmartFilters(placed, stack, canvas) {
4851
4882
  result = new Uint8ClampedArray(entry.base);
4852
4883
  lerpInto(result, entry.filtered, 1, cov);
4853
4884
  }
4854
- return surfaceFromPixels({ width: W, height: H, data: result }, 4, 0, 0);
4885
+ const out = surfaceFromPixels({ width: W, height: H, data: result }, 4, 0, 0);
4886
+ if (incomplete) incompleteSurfaces.add(out);
4887
+ return out;
4855
4888
  }
4856
4889
  function lerpInto(dst, src, k, coverage) {
4857
4890
  for (let i = 0, p = 0; i < dst.length; i += 4, p++) {
@@ -4940,7 +4973,7 @@ function offsetSmartObject(layers, id, dx, dy, canvas) {
4940
4973
  const moveMask = (m2) => m2 && m2.linked !== false && !m2.source ? { ...m2, surface: translateSurface(m2.surface, mx, my) } : m2;
4941
4974
  if (layer.mask) next.mask = moveMask(layer.mask);
4942
4975
  if (layer.smartFilters?.mask) next.smartFilters = { ...layer.smartFilters, mask: moveMask(layer.smartFilters.mask) };
4943
- const spatial = activeSmartFilters(layer.smartFilters).some((f) => f.kind === "blur");
4976
+ const spatial = activeSmartFilters(layer.smartFilters).some((f) => f.kind === "blur") || activeEffectSmartFilters(layer.smartFilters).length > 0;
4944
4977
  const b = contentBounds(layer.surface);
4945
4978
  const m = SMART_RENDER_MARGIN;
4946
4979
  const cut = b && (b.x <= -m || b.y <= -m || b.x + b.width >= canvas.width + m || b.y + b.height >= canvas.height + m);
@@ -5185,13 +5218,27 @@ function refitPlacement(layer, contents) {
5185
5218
  }
5186
5219
  function normalizeSmartFilter(raw, id = newLayerId("flt")) {
5187
5220
  if (raw.kind === "psd") fail4("Photoshop filters can only come from a PSD; add develop or blur.");
5188
- const base = normalizeFilter(raw, id);
5221
+ const base = raw.kind === "effect" ? normalizeEffectSmartFilter(raw, id) : normalizeFilter(raw, id);
5189
5222
  const blend = raw.blend ?? "normal";
5190
5223
  if (!isBlendMode(blend) || blend === "pigment") fail4(`Unknown blend mode "${String(blend)}" for a smart filter.`);
5191
5224
  const opacity = raw.opacity ?? 1;
5192
5225
  if (!(typeof opacity === "number" && opacity >= 0 && opacity <= 1)) fail4("opacity must be a number from 0 to 1.");
5193
5226
  return { ...base, blend, opacity };
5194
5227
  }
5228
+ function normalizeEffectSmartFilter(raw, id) {
5229
+ if (typeof raw.effect !== "string" || !raw.effect) fail4('An effect smart filter needs an "effect" id from search_effects.');
5230
+ const values = raw.values ?? {};
5231
+ if (typeof values !== "object" || values === null || Array.isArray(values)) fail4('"values" must be an object of control values.');
5232
+ return {
5233
+ id,
5234
+ kind: "effect",
5235
+ enabled: raw.enabled !== false,
5236
+ effect: raw.effect,
5237
+ ...typeof raw.provider === "string" ? { provider: raw.provider } : {},
5238
+ ...typeof raw.name === "string" ? { name: raw.name } : {},
5239
+ values: { ...values }
5240
+ };
5241
+ }
5195
5242
  function withStack(layer, stack, canvas) {
5196
5243
  const next = { ...layer };
5197
5244
  if (stack && (stack.filters.length || stack.mask || !stack.enabled)) next.smartFilters = stack;
@@ -5422,6 +5469,7 @@ function serializeLayer(layer, ctx) {
5422
5469
  }
5423
5470
  if (layer.fillOpacity !== void 0 && layer.fillOpacity !== 1) out.fillOpacity = layer.fillOpacity;
5424
5471
  if (layer.filters?.length) out.filters = JSON.parse(JSON.stringify(layer.filters));
5472
+ if (layer.generator) out.generator = JSON.parse(JSON.stringify(layer.generator));
5425
5473
  if (layer.source !== "document") {
5426
5474
  const deep = encodeDeepSurface(layer.surface);
5427
5475
  if (deep) out.deep = deep;
@@ -5812,6 +5860,8 @@ function deserializeLayer(raw, ctx) {
5812
5860
  if (typeof r.fillOpacity === "number" && r.fillOpacity !== 1) layer.fillOpacity = num2(r.fillOpacity, 0, 1, 1);
5813
5861
  const filters = readFilters(r.filters);
5814
5862
  if (filters) layer.filters = filters;
5863
+ const generator = readEffectRef(r.generator);
5864
+ if (generator) layer.generator = { ...generator, ...typeof r.generator.time === "number" && r.generator.time > 0 ? { time: r.generator.time } : {} };
5815
5865
  return layer;
5816
5866
  }
5817
5867
  case "adjustment": {
@@ -5898,6 +5948,15 @@ function readContents(id, ctx) {
5898
5948
  memo.set(id, c);
5899
5949
  return c;
5900
5950
  }
5951
+ var isEffectValue = (x) => typeof x === "string" || typeof x === "boolean" || typeof x === "number" && Number.isFinite(x) || Array.isArray(x) && x.every((n) => typeof n === "number" && Number.isFinite(n));
5952
+ function readEffectRef(raw) {
5953
+ if (!raw || typeof raw !== "object" || typeof raw.effect !== "string" || !raw.effect) return void 0;
5954
+ const values = {};
5955
+ if (raw.values && typeof raw.values === "object") {
5956
+ for (const [k, v] of Object.entries(raw.values)) if (isEffectValue(v)) values[k] = Array.isArray(v) ? [...v] : v;
5957
+ }
5958
+ return { effect: raw.effect, ...typeof raw.provider === "string" ? { provider: raw.provider } : {}, ...typeof raw.name === "string" ? { name: raw.name } : {}, values };
5959
+ }
5901
5960
  function readSmartFilters(raw, ctx, what) {
5902
5961
  if (!raw || typeof raw !== "object") return void 0;
5903
5962
  const r = raw;
@@ -5918,6 +5977,10 @@ function readSmartFilters(raw, ctx, what) {
5918
5977
  } else if (f.kind === "develop" || f.kind === "blur") {
5919
5978
  const base = plain[k++];
5920
5979
  if (base) filters.push({ ...base, blend, opacity });
5980
+ } else if (f.kind === "effect") {
5981
+ const ref = readEffectRef(f);
5982
+ if (ref) filters.push({ id: typeof f.id === "string" ? f.id : newLayerId("flt"), kind: "effect", enabled: f.enabled !== false, ...ref, blend, opacity });
5983
+ else ctx.problems.push(`${what}: an effect smart filter without an effect id was left out`);
5921
5984
  }
5922
5985
  }
5923
5986
  const stack = { enabled: r.enabled !== false, filters };
@@ -11594,6 +11657,7 @@ export {
11594
11657
  isValidWarp,
11595
11658
  geometryHomography,
11596
11659
  SMART_RENDER_MARGIN,
11660
+ isSmartSurfaceIncomplete,
11597
11661
  newContentsId,
11598
11662
  contentsBox,
11599
11663
  placementGeometry,
@@ -11605,6 +11669,7 @@ export {
11605
11669
  documentContents,
11606
11670
  placeContents,
11607
11671
  activeSmartFilters,
11672
+ activeEffectSmartFilters,
11608
11673
  filterMaskCoverage,
11609
11674
  applySmartFilters,
11610
11675
  renderSmartObject,
@@ -11671,4 +11736,4 @@ export {
11671
11736
  smartContentsFile,
11672
11737
  exportPsd
11673
11738
  };
11674
- //# sourceMappingURL=chunk-6ZBR4J62.js.map
11739
+ //# sourceMappingURL=chunk-WATAQ2Z2.js.map