@pixerate/editor 0.224.1 → 0.226.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.
Files changed (33) hide show
  1. package/dist/animation-editor/index.cjs +9 -9
  2. package/dist/animation-editor/index.js +1 -1
  3. package/dist/canvas/index.cjs +7 -7
  4. package/dist/canvas/index.js +1 -1
  5. package/dist/{chunk-WA4IS2EY.js → chunk-4MDWEX44.js} +1 -1
  6. package/dist/{chunk-7AAS3JO6.cjs → chunk-4PA6YS6W.cjs} +14 -14
  7. package/dist/{chunk-RDTCG5MS.js → chunk-56RFHF7O.js} +1 -1
  8. package/dist/{chunk-S6KKLFZO.js → chunk-5YLOU7VF.js} +18 -3
  9. package/dist/{chunk-HKPLKISO.js → chunk-7DZ55AIE.js} +10 -5
  10. package/dist/{chunk-NTWR5TQB.cjs → chunk-ABQ2VGUB.cjs} +10 -5
  11. package/dist/{chunk-MDNYYOBZ.cjs → chunk-C6CMQXMI.cjs} +7 -7
  12. package/dist/{chunk-R35YOUP2.cjs → chunk-IWMDW7LB.cjs} +18 -3
  13. package/dist/effects/index.cjs +44 -44
  14. package/dist/effects/index.js +2 -2
  15. package/dist/effects/tools.d.cts +6 -0
  16. package/dist/effects/tools.d.ts +6 -0
  17. package/dist/image-editor/controller.d.cts +12 -4
  18. package/dist/image-editor/controller.d.ts +12 -4
  19. package/dist/image-editor/index.cjs +882 -860
  20. package/dist/image-editor/index.js +46 -24
  21. package/dist/image-editor/layers/model.d.cts +6 -0
  22. package/dist/image-editor/layers/model.d.ts +6 -0
  23. package/dist/paint-editor/index.cjs +13 -13
  24. package/dist/paint-editor/index.js +3 -3
  25. package/dist/vector/index.cjs +19 -6
  26. package/dist/vector/index.js +14 -1
  27. package/dist/video-editor/index.cjs +309 -309
  28. package/dist/video-editor/index.js +3 -3
  29. package/package.json +1 -1
  30. package/skills/pixerate-image-editor/SKILL.md +14 -7
  31. package/skills/pixerate-image-editor/references/provider-effects.md +11 -0
  32. package/skills/pixerate-image-selection-masking/SKILL.md +3 -3
  33. package/skills/pixerate-vector/SKILL.md +1 -0
@@ -405,7 +405,7 @@ import {
405
405
  writePsd,
406
406
  xyToTempTint,
407
407
  xyToUv
408
- } from "../chunk-S6KKLFZO.js";
408
+ } from "../chunk-5YLOU7VF.js";
409
409
  import {
410
410
  parseCssColor
411
411
  } from "../chunk-3CI7FRMK.js";
@@ -743,7 +743,7 @@ import {
743
743
  createEffectToolProvider,
744
744
  linearToSrgb,
745
745
  resolveEffectValues
746
- } from "../chunk-HKPLKISO.js";
746
+ } from "../chunk-7DZ55AIE.js";
747
747
  import "../chunk-V2ZCJRDM.js";
748
748
  import {
749
749
  BrushstrokeCache,
@@ -6545,7 +6545,7 @@ var ImageEditorController = class {
6545
6545
  id = res2.layer.id;
6546
6546
  converted = true;
6547
6547
  }
6548
- const filter = normalizeSmartFilter({ kind: "effect", effect: d.id, provider: this.effects.require().id, name: d.name, values, enabled: options.enabled !== false, blend: options.blend, opacity: options.opacity });
6548
+ const filter = normalizeSmartFilter({ kind: "effect", effect: d.id, provider: this.effects.require().id, name: d.name, values, enabled: options.enabled !== false, blend: options.blend, opacity: options.opacity, preserveTransparency: options.preserveTransparency });
6549
6549
  const res = addSmartFilter(layers, id, filter, this.canvasSize, options.index);
6550
6550
  const masked = this.asOneStep(() => {
6551
6551
  this.commitLayers(res.layers);
@@ -6555,7 +6555,8 @@ var ImageEditorController = class {
6555
6555
  }
6556
6556
  /**
6557
6557
  * Changes an effect smart filter: `values` merged over its own (`null` resets a control to the
6558
- * effect's default), `enabled`, `blend`, `opacity` or its place in the stack (`index`). Values
6558
+ * effect's default), `enabled`, `blend`, `opacity`, `preserveTransparency` or its place in the
6559
+ * stack (`index`). Values
6559
6560
  * are checked against the provider's controls. One undo step (none with `commitToHistory`
6560
6561
  * false, during a drag).
6561
6562
  */
@@ -6575,7 +6576,7 @@ var ImageEditorController = class {
6575
6576
  }
6576
6577
  update.values = { ...base, ...resolveEffectValues({ ...d, defaults: base }, given) };
6577
6578
  }
6578
- for (const k of ["enabled", "blend", "opacity"]) if (changes[k] !== void 0) update[k] = changes[k];
6579
+ for (const k of ["enabled", "blend", "opacity", "preserveTransparency"]) if (changes[k] !== void 0) update[k] = changes[k];
6579
6580
  let res = updateSmartFilter(this.state.layers, layerId, filterId, update, this.canvasSize);
6580
6581
  if (changes.index !== void 0) res = { ...res, layers: moveSmartFilter(res.layers, layerId, filterId, changes.index, this.canvasSize).layers };
6581
6582
  this.commitLayers(res.layers, void 0, commitToHistory);
@@ -6586,8 +6587,9 @@ var ImageEditorController = class {
6586
6587
  * the document's size and bit depth (at `time` ms, default 0), placed like `addPixelLayer`. One
6587
6588
  * undo step. Masks, blend and opacity apply as to any layer. An effect with a background of its
6588
6589
  * own (`backgroundOpacity`) starts with it transparent when there are layers below it, unless
6589
- * `values` set it. `animate` makes it play when the document is output as an animation. Returns
6590
- * its id.
6590
+ * `values` set it. `animate` makes it play when the document is output as an animation.
6591
+ * `clipped` clips it to the layer below (a clipping mask) in the same step, so it only shows
6592
+ * over that layer's pixels. Returns its id.
6591
6593
  */
6592
6594
  async addEffectLayer(effect, options = {}) {
6593
6595
  const d = await this.describeEffect(effect, "layer");
@@ -6598,6 +6600,7 @@ var ImageEditorController = class {
6598
6600
  const ids = new Set(flattenLayers(this.state.layers).map((l) => l.id));
6599
6601
  const id = options.id && !ids.has(options.id) ? options.id : newLayerId();
6600
6602
  const placed = placeLayer(this.state.layers, { type: "pixel", id, name: "", visible: true, opacity: 1, blend: "normal", surface: emptySurface(4), offset: { x: 0, y: 0 } }, this.newLayerPlacement(options));
6603
+ if (options.clipped) requireClipBase(placed, id);
6601
6604
  const values = resolveEffectValues(d, withBackgroundOpacity(d, options.values, hasLayersBelow(placed, id)));
6602
6605
  const { width, height } = this.naturalSize;
6603
6606
  const pixels = await this.effectsEngine.renderLayer(d.id, values, { width, height, depth: this.bitDepth, time });
@@ -6610,7 +6613,8 @@ var ImageEditorController = class {
6610
6613
  blend: blend ?? "normal",
6611
6614
  surface: effectPixelsToSurface(pixels, this.bitDepth),
6612
6615
  offset: { x: 0, y: 0 },
6613
- generator: { effect: d.id, provider: this.effects.require().id, name: d.name, values, ...time ? { time } : {}, ...animate ? { animate } : {} }
6616
+ generator: { effect: d.id, provider: this.effects.require().id, name: d.name, values, ...time ? { time } : {}, ...animate ? { animate } : {} },
6617
+ ...options.clipped ? { clipped: true } : {}
6614
6618
  };
6615
6619
  const next = mapLayer(placed, id, () => layer);
6616
6620
  this.commitLayers(next, () => {
@@ -9763,6 +9767,7 @@ var ImageEditorController = class {
9763
9767
  * above the other adjustment layers (or, without any, above the topmost non-annotation layer,
9764
9768
  * so annotations stay on top); `index` counts adjustment layers (0 = below the others);
9765
9769
  * `aboveId` / `parentId` place it anywhere in the stack. `kind` alone gives neutral parameters.
9770
+ * `clipped` clips it to the layer below in the same step, so it only adjusts that layer.
9766
9771
  */
9767
9772
  addAdjustmentLayer(init, index) {
9768
9773
  const spec = typeof init === "string" ? { kind: init } : init;
@@ -9778,14 +9783,17 @@ var ImageEditorController = class {
9778
9783
  });
9779
9784
  while (ids.has(layer.id)) layer.id = newLayerId("adj");
9780
9785
  const { width, height } = this.naturalSize;
9781
- this.recordHistory();
9782
9786
  const node = adjustmentNodeFromLegacy(layer, width, height);
9787
+ if (spec.clipped) node.clipped = true;
9783
9788
  const positioned = Boolean(spec.aboveId || spec.parentId);
9784
- this.state.layers = this.placeLayer(this.state.layers, node, {
9789
+ const placed = this.placeLayer(this.state.layers, node, {
9785
9790
  aboveId: spec.aboveId,
9786
9791
  parentId: spec.parentId,
9787
9792
  adjustmentIndex: index !== void 0 ? index : positioned ? void 0 : Infinity
9788
9793
  });
9794
+ if (spec.clipped) requireClipBase(placed, node.id);
9795
+ this.recordHistory();
9796
+ this.state.layers = placed;
9789
9797
  this.notify();
9790
9798
  return layer.id;
9791
9799
  }
@@ -12652,6 +12660,9 @@ function serializeAdjustments(adjustments) {
12652
12660
  }
12653
12661
  return out;
12654
12662
  }
12663
+ function requireClipBase(layers, id) {
12664
+ if (findLayer(layers, id)?.index === 0) throw new LayerOperationError("There is no layer below the new layer to clip it to: it would be the bottom layer of its group or the stack. Put it directly above the layer to clip to (aboveId), or leave clipped false.");
12665
+ }
12655
12666
  function withDevelop(layer, partial) {
12656
12667
  const filters = [...layer.filters ?? []];
12657
12668
  let i = filters.findIndex((f) => f.kind === "develop");
@@ -19713,7 +19724,7 @@ function createImageEditorToolProvider(controller, options = {}) {
19713
19724
  const addLayer = {
19714
19725
  name: "add_adjustment_layer",
19715
19726
  title: "Add an adjustment layer",
19716
- description: "Adds a non-destructive adjustment layer on top of the stack (one undoable step) and returns it; with a selection it is masked by it (useSelection, default true; masked in the result says so). Kinds: " + ADJUSTMENT_KINDS.map((k) => k.kind).join(", ") + ". Omitted params are neutral.",
19727
+ description: "Adds a non-destructive adjustment layer on top of the stack (one undoable step) and returns it; with a selection it is masked by it (useSelection, default true; masked in the result says so). clipped clips it to the layer directly below in the same step, so it adjusts only that layer (clipped in the result). Kinds: " + ADJUSTMENT_KINDS.map((k) => k.kind).join(", ") + ". Omitted params are neutral.",
19717
19728
  inputSchema: {
19718
19729
  type: "object",
19719
19730
  properties: {
@@ -19724,7 +19735,8 @@ function createImageEditorToolProvider(controller, options = {}) {
19724
19735
  name: { type: "string", description: "Layer name shown in the layers panel." },
19725
19736
  aboveId: { type: "string", description: "Put the layer directly above this layer (from list_layers). Default: above the other adjustment layers, below annotations." },
19726
19737
  parentId: { type: "string", description: "Put the layer at the top of this group (from list_layers)." },
19727
- useSelection: { type: "boolean", description: "With a selection, mask the new layer with it so the adjustment only affects the selected area (default true; false for a global layer)." }
19738
+ useSelection: { type: "boolean", description: "With a selection, mask the new layer with it so the adjustment only affects the selected area (default true; false for a global layer)." },
19739
+ clipped: { type: "boolean", description: "Clip it to the layer directly below (a clipping mask) so it adjusts only that layer (default false). Use with aboveId; refused when nothing is below it in its group or the stack." }
19728
19740
  },
19729
19741
  required: ["kind"],
19730
19742
  additionalProperties: false
@@ -19736,7 +19748,7 @@ function createImageEditorToolProvider(controller, options = {}) {
19736
19748
  idempotentHint: false,
19737
19749
  openWorldHint: false
19738
19750
  },
19739
- handler: ({ kind, params, opacity, blend, name, aboveId, parentId, useSelection }) => {
19751
+ handler: ({ kind, params, opacity, blend, name, aboveId, parentId, useSelection, clipped }) => {
19740
19752
  if (!isAdjustmentKind(kind)) throw new Error(`Unknown adjustment kind "${kind}".`);
19741
19753
  if (blend !== void 0 && !isBlendMode(blend)) throw new Error(`Unknown blend mode "${blend}".`);
19742
19754
  if (aboveId !== void 0 && parentId !== void 0) throw new Error("Give aboveId or parentId, not both.");
@@ -19744,9 +19756,9 @@ function createImageEditorToolProvider(controller, options = {}) {
19744
19756
  if (ref !== void 0 && !controller.getLayer(ref)) throw new Error(`Unknown layer "${ref}". Call list_layers for ids.`);
19745
19757
  }
19746
19758
  if (parentId !== void 0 && controller.getLayer(parentId)?.type !== "group") throw new Error(`Layer "${parentId}" is not a group.`);
19747
- const id = controller.addAdjustmentLayer({ kind, params, opacity, blend, name, aboveId, parentId, useSelection: useSelection !== false });
19759
+ const id = controller.addAdjustmentLayer({ kind, params, opacity, blend, name, aboveId, parentId, useSelection: useSelection !== false, clipped: clipped === true });
19748
19760
  const layer = controller.getAdjustmentLayer(id);
19749
- return { layer: JSON.parse(JSON.stringify(layer)), index: controller.getAdjustmentLayers().findIndex((l) => l.id === id), masked: Boolean(layer.mask) };
19761
+ return { layer: JSON.parse(JSON.stringify(layer)), index: controller.getAdjustmentLayers().findIndex((l) => l.id === id), masked: Boolean(layer.mask), clipped: Boolean(controller.getLayer(id)?.clipped) };
19750
19762
  }
19751
19763
  };
19752
19764
  const renderPreview = {
@@ -21776,6 +21788,7 @@ var FreeTransformTool = class {
21776
21788
  };
21777
21789
 
21778
21790
  // src/image-editor/provider-effect-tools.ts
21791
+ var FILTER_BLEND_SCHEMA = { type: "string", enum: BLEND_MODES.map((m) => m.id).filter((b) => b !== "pigment"), description: "How the effect's result blends with the layer below it in the stack (default normal)." };
21779
21792
  var ANIMATE_SCHEMA = {
21780
21793
  type: "object",
21781
21794
  properties: { duration: { type: "number", exclusiveMinimum: 0, maximum: MAX_EFFECT_ANIMATION_MS, description: `How long it plays in ms (at most ${MAX_EFFECT_ANIMATION_MS}).` } },
@@ -21789,7 +21802,7 @@ function imageLayerAppliedEffects(layer) {
21789
21802
  if (layer.type === "pixel" && layer.generator) out.push({ id: "generator", ...ref(layer.generator), role: "effect layer", enabled: true, values: layer.generator.values, ...layer.generator.time ? { time: layer.generator.time } : {}, ...layer.generator.animate ? { animate: layer.generator.animate } : {} });
21790
21803
  if (layer.type === "smart")
21791
21804
  (layer.smartFilters?.filters ?? []).forEach((f, index) => {
21792
- if (f.kind === "effect") out.push({ id: f.id, ...ref(f), role: "smart filter", enabled: f.enabled && layer.smartFilters.enabled, opacity: f.opacity, blend: f.blend, values: f.values, index });
21805
+ if (f.kind === "effect") out.push({ id: f.id, ...ref(f), role: "smart filter", enabled: f.enabled && layer.smartFilters.enabled, opacity: f.opacity, blend: f.blend, preserveTransparency: Boolean(f.preserveTransparency), values: f.values, index });
21793
21806
  });
21794
21807
  return out;
21795
21808
  }
@@ -21819,27 +21832,35 @@ function createImageEffectsToolProvider(controller, options = {}) {
21819
21832
  skill: "pixerate-image-editor",
21820
21833
  target: { noun: "layer", id: "A layer id from list_layers." },
21821
21834
  applyUses: ["filter"],
21822
- applyDetails: "The effect becomes a smart filter: it runs on the layer's pixels after the smart filters below it, with its own blend and opacity, under the smart filter mask, and can be changed or removed later. A pixel, shape or type layer (or a group) is converted to a smart object first, in the same undo step (one layer keeps its id; the result says converted). With a selection, the filter mask is set from it in the same step, so the filter only changes the selected area (the result says masked); deselect first to filter the whole layer. Generators and overlays go on their own layer through add_effect_layer.",
21835
+ applyDetails: "The effect becomes a smart filter: it runs on the layer's pixels after the smart filters below it, with its own blend and opacity, under the smart filter mask, and can be changed or removed later. It keeps the layer's transparency by default (preserveTransparency: the result only changes the layer's own pixels, never fills transparent ones); preserveTransparency false lets it draw over the whole canvas. A pixel, shape or type layer (or a group) is converted to a smart object first, in the same undo step (one layer keeps its id; the result says converted). With a selection, the filter mask is set from it in the same step, so the filter only changes the selected area (the result says masked); deselect first to filter the whole layer. Generators and overlays go on their own layer through add_effect_layer.",
21823
21836
  provider: () => controller.effectsProvider,
21824
21837
  readOnlyReason,
21838
+ applyOptions: {
21839
+ preserveTransparency: { type: "boolean", description: "Keep the layer's transparency: the result is drawn over the layer and takes its alpha, so transparent pixels stay transparent and only the layer's content changes (default true). false lets the effect's own alpha replace the layer's, so a shader can fill the empty area." },
21840
+ blend: FILTER_BLEND_SCHEMA
21841
+ },
21842
+ updateOptions: {
21843
+ preserveTransparency: { type: "boolean", description: "Keep the layer's transparency (true) or let the effect fill transparent pixels (false). Smart filters only." },
21844
+ blend: FILTER_BLEND_SCHEMA
21845
+ },
21825
21846
  list: (target) => {
21826
21847
  const layers = target ? [layerOf(target)] : flattenLayers(controller.getState().layers);
21827
21848
  return layers.map((l) => ({ target: l.id, ...l.name ? { name: l.name } : {}, effects: imageLayerAppliedEffects(l) })).filter((t) => target || t.effects.length);
21828
21849
  },
21829
21850
  apply: async (target, effect, args) => {
21830
- const r = await controller.applyEffectFilter(target, effect, { values: plainValues(args.values), index: args.index, enabled: args.enabled, opacity: args.opacity, useSelection: true });
21851
+ const r = await controller.applyEffectFilter(target, effect, { values: plainValues(args.values), index: args.index, enabled: args.enabled, opacity: args.opacity, blend: args.blend, preserveTransparency: args.preserveTransparency !== false, useSelection: true });
21831
21852
  return result(r.layerId, r.filter.id, { converted: r.converted, masked: r.masked });
21832
21853
  },
21833
21854
  update: async (target, instance, changes) => {
21834
21855
  const layer = layerOf(target);
21835
21856
  if (instance === "generator") {
21836
- const extra = ["enabled", "opacity", "index"].filter((k) => changes[k] !== void 0);
21857
+ const extra = ["enabled", "opacity", "index", "blend", "preserveTransparency"].filter((k) => changes[k] !== void 0);
21837
21858
  if (extra.length) throw new Error(`An effect layer's own effect only takes values; change the layer's ${extra.join(", ")} with update_layer.`);
21838
21859
  if (layer.type !== "pixel" || !layer.generator) throw new Error(`"${target}" is not an effect layer.`);
21839
21860
  await controller.updateEffectLayer(target, { values: changes.values });
21840
21861
  return result(target, "generator");
21841
21862
  }
21842
- await controller.updateEffectFilter(target, instance, { values: changes.values, enabled: changes.enabled, opacity: changes.opacity, index: changes.index });
21863
+ await controller.updateEffectFilter(target, instance, { values: changes.values, enabled: changes.enabled, opacity: changes.opacity, index: changes.index, blend: changes.blend, preserveTransparency: changes.preserveTransparency });
21843
21864
  return result(target, instance);
21844
21865
  },
21845
21866
  remove: async (target, instance) => {
@@ -21855,7 +21876,7 @@ function createImageEffectsToolProvider(controller, options = {}) {
21855
21876
  {
21856
21877
  name: "add_effect_layer",
21857
21878
  title: "Add an effect layer",
21858
- description: `Adds a layer drawn by a provider generator, overlay or title effect (search_effects with use generator, overlay or title) at the document's size and bit depth, above the active layer unless placed. Masks, blend mode, opacity and layer styles apply as to any layer; transparent effects show the layers beneath, and an effect with its own background (describe_effect backgroundOpacity) starts with it transparent when there are layers below, unless values set it. animate makes it play when the document is output as an animation. set_effect_layer changes its effect, frame or animation; update_effect with effect "generator" draws it again with new values; remove_effect "generator" keeps its pixels as a plain layer. One undo step. Returns the new layer id and its effect.`,
21879
+ description: `Adds a layer drawn by a provider generator, overlay or title effect (search_effects with use generator, overlay or title) at the document's size and bit depth, above the active layer unless placed. Masks, blend mode, opacity and layer styles apply as to any layer; transparent effects show the layers beneath, and an effect with its own background (describe_effect backgroundOpacity) starts with it transparent when there are layers below, unless values set it. animate makes it play when the document is output as an animation. clipped clips it to the layer directly below (a clipping mask) in the same step, so it only shows over that layer's pixels. set_effect_layer changes its effect, frame or animation; update_effect with effect "generator" draws it again with new values; remove_effect "generator" keeps its pixels as a plain layer. One undo step. Returns the new layer id, whether it is clipped, and its effect.`,
21859
21880
  inputSchema: {
21860
21881
  type: "object",
21861
21882
  properties: {
@@ -21867,7 +21888,8 @@ function createImageEffectsToolProvider(controller, options = {}) {
21867
21888
  aboveId: { type: "string", minLength: 1, description: "Put it directly above this layer (default: above the active layer, else on top)." },
21868
21889
  belowId: { type: "string", minLength: 1, description: "Put it directly below this layer." },
21869
21890
  opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0\u20131 (default 1)." },
21870
- blend: { type: "string", enum: BLEND_MODES.map((m) => m.id).filter((b) => b !== "pigment"), description: "Layer blend mode (default normal); screen or add for light effects, multiply for textures." }
21891
+ blend: { type: "string", enum: BLEND_MODES.map((m) => m.id).filter((b) => b !== "pigment"), description: "Layer blend mode (default normal); screen or add for light effects, multiply for textures." },
21892
+ clipped: { type: "boolean", description: "Clip it to the layer directly below (a clipping mask), so it only shows over that layer's pixels (default false). Refused when nothing is below it in its group or the stack." }
21871
21893
  },
21872
21894
  required: ["effect"],
21873
21895
  additionalProperties: false
@@ -21875,8 +21897,8 @@ function createImageEffectsToolProvider(controller, options = {}) {
21875
21897
  annotations: { title: "Add an effect layer", ...ctx.annotations.write },
21876
21898
  handler: async (args) => {
21877
21899
  ctx.requireProvider();
21878
- const id = await controller.addEffectLayer(args.effect, { values: plainValues(args.values), time: args.time, animate: args.animate, name: args.name, aboveId: args.aboveId, belowId: args.belowId, opacity: args.opacity, blend: args.blend });
21879
- return { layerId: id, effects: imageLayerAppliedEffects(layerOf(id)) };
21900
+ const id = await controller.addEffectLayer(args.effect, { values: plainValues(args.values), time: args.time, animate: args.animate, name: args.name, aboveId: args.aboveId, belowId: args.belowId, opacity: args.opacity, blend: args.blend, clipped: args.clipped });
21901
+ return { layerId: id, clipped: Boolean(layerOf(id).clipped), effects: imageLayerAppliedEffects(layerOf(id)) };
21880
21902
  }
21881
21903
  },
21882
21904
  {
@@ -465,6 +465,12 @@ export interface EffectSmartFilter {
465
465
  name?: string;
466
466
  /** Control values (over the effect's defaults). */
467
467
  values: Record<string, EffectValue>;
468
+ /**
469
+ * Keep the layer's transparency: the effect's result is drawn over its input and takes the
470
+ * input's alpha, so it never fills transparent pixels (before the filter's blend and opacity).
471
+ * Missing means false: the result's alpha replaces the input's.
472
+ */
473
+ preserveTransparency?: boolean;
468
474
  }
469
475
  /** One smart filter: a layer filter, a provider effect or a kept PSD filter, with its own blending over the result below it. */
470
476
  export type SmartFilter = (LayerFilter | PsdSmartFilter | EffectSmartFilter) & {
@@ -465,6 +465,12 @@ export interface EffectSmartFilter {
465
465
  name?: string;
466
466
  /** Control values (over the effect's defaults). */
467
467
  values: Record<string, EffectValue>;
468
+ /**
469
+ * Keep the layer's transparency: the effect's result is drawn over its input and takes the
470
+ * input's alpha, so it never fills transparent pixels (before the filter's blend and opacity).
471
+ * Missing means false: the result's alpha replaces the input's.
472
+ */
473
+ preserveTransparency?: boolean;
468
474
  }
469
475
  /** One smart filter: a layer filter, a provider effect or a kept PSD filter, with its own blending over the result below it. */
470
476
  export type SmartFilter = (LayerFilter | PsdSmartFilter | EffectSmartFilter) & {
@@ -1,13 +1,13 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _class10; var _class11; var _class12;
2
2
 
3
- var _chunkMDNYYOBZcjs = require('../chunk-MDNYYOBZ.cjs');
3
+ var _chunkC6CMQXMIcjs = require('../chunk-C6CMQXMI.cjs');
4
4
 
5
5
 
6
6
 
7
7
 
8
8
 
9
9
 
10
- var _chunkR35YOUP2cjs = require('../chunk-R35YOUP2.cjs');
10
+ var _chunkIWMDW7LBcjs = require('../chunk-IWMDW7LB.cjs');
11
11
  require('../chunk-A2IS7EHE.cjs');
12
12
  require('../chunk-SS53NNZO.cjs');
13
13
 
@@ -132,7 +132,7 @@ require('../chunk-NM3G4VQT.cjs');
132
132
 
133
133
 
134
134
 
135
- var _chunkNTWR5TQBcjs = require('../chunk-NTWR5TQB.cjs');
135
+ var _chunkABQ2VGUBcjs = require('../chunk-ABQ2VGUB.cjs');
136
136
  require('../chunk-WOFXZ3XU.cjs');
137
137
 
138
138
 
@@ -1659,11 +1659,11 @@ function header(state, options) {
1659
1659
  return out;
1660
1660
  }
1661
1661
  function serializePaintDocument(state, options = {}) {
1662
- const { layers, assets } = _chunkR35YOUP2cjs.serializeLayers.call(void 0, state.layers);
1662
+ const { layers, assets } = _chunkIWMDW7LBcjs.serializeLayers.call(void 0, state.layers);
1663
1663
  return { ...header(state, options), layers, assets };
1664
1664
  }
1665
1665
  async function serializePaintDocumentExternal(state, store, options = {}) {
1666
- const { layers, assets } = await _chunkR35YOUP2cjs.serializeLayersExternal.call(void 0, state.layers, store);
1666
+ const { layers, assets } = await _chunkIWMDW7LBcjs.serializeLayersExternal.call(void 0, state.layers, store);
1667
1667
  return { ...header(state, options), layers, assets };
1668
1668
  }
1669
1669
  function paintLayersOnly(layers, problems) {
@@ -1700,7 +1700,7 @@ function deserializePaintDocument(raw) {
1700
1700
  const canvas = normalizeCanvas({ width: r.width, height: r.height, dpi: _nullishCoalesce(r.dpi, () => ( void 0)) });
1701
1701
  const paper = _chunkROVK6AXXcjs.normalizePaper.call(void 0, _nullishCoalesce(r.paper, () => ( {})));
1702
1702
  const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
1703
- const layers = paintLayersOnly(_chunkR35YOUP2cjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1703
+ const layers = paintLayersOnly(_chunkIWMDW7LBcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1704
1704
  const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
1705
1705
  const activeLayerId = wanted && _chunkB46UQUDCcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkB46UQUDCcjs.flattenLayers.call(void 0, layers), 'access', _70 => _70.filter, 'call', _71 => _71((l) => l.type === "pixel"), 'access', _72 => _72.pop, 'call', _73 => _73(), 'optionalAccess', _74 => _74.id]), () => ( null));
1706
1706
  const wetEvents = Array.isArray(r.wetEvents) ? r.wetEvents.filter((e) => e && typeof e === "object" && Number.isInteger(e.clock) && typeof e.kind === "string") : [];
@@ -1716,7 +1716,7 @@ async function deserializePaintDocumentAsync(raw, store) {
1716
1716
  if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
1717
1717
  const r = raw;
1718
1718
  const problems = [];
1719
- const assets = await _chunkR35YOUP2cjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1719
+ const assets = await _chunkIWMDW7LBcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1720
1720
  const loaded = deserializePaintDocument({ ...r, assets });
1721
1721
  return { ...loaded, problems: [...problems, ...loaded.problems] };
1722
1722
  }
@@ -3198,7 +3198,7 @@ function timeLapseVideo(frameCount, width, height, fps, holdMs, name = "Time-lap
3198
3198
  }
3199
3199
  const video = { version: 1, name, width: w, height: h, fps, background: "#000000", assets, tracks: [{ id: "v1", kind: "video", clips }] };
3200
3200
  const durationMs = frameCount ? clips[clips.length - 1].start + clips[clips.length - 1].duration : 0;
3201
- return { video, ffmpeg: _chunkMDNYYOBZcjs.toVideoFfmpeg.call(void 0, video, { output: "timelapse.mp4" }), durationMs };
3201
+ return { video, ffmpeg: _chunkC6CMQXMIcjs.toVideoFfmpeg.call(void 0, video, { output: "timelapse.mp4" }), durationMs };
3202
3202
  }
3203
3203
  async function buildTimeLapse(source, target, options = {}) {
3204
3204
  const maxSide = Math.max(32, Math.min(4096, Math.round(_nullishCoalesce(options.maxSide, () => ( 720)))));
@@ -3277,7 +3277,7 @@ function checkPaintEffectPlace(d, place) {
3277
3277
  if (d.uses.includes("filter") && place !== "light") instead = "; run it on a layer's dried paint with apply_effect instead";
3278
3278
  else if (place === "light" && d.uses.some((u) => PAINT_EFFECT_PLACES.layer.includes(u))) instead = "; add it as an effect layer (add_effect_layer) or fill a layer with it (fill_layer_with_effect) instead";
3279
3279
  else if (place === "paper" && d.uses.some((u) => u === "overlay" || u === "title")) instead = "; add it as an effect layer with add_effect_layer instead";
3280
- throw new (0, _chunkNTWR5TQBcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.uses.join(" / ") || d.kind} effect, and ${PLACE_WORDS[place]}${instead}.`, d.id);
3280
+ throw new (0, _chunkABQ2VGUBcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.uses.join(" / ") || d.kind} effect, and ${PLACE_WORDS[place]}${instead}.`, d.id);
3281
3281
  }
3282
3282
  var PAPER_EFFECT_BLENDS = ["multiply", "normal", "overlay", "soft-light", "screen", "color"];
3283
3283
  var paperVersion = 1;
@@ -3453,7 +3453,7 @@ var PaintEditorController = (_class10 = class _PaintEditorController {
3453
3453
  /** Set while an operation that changes wet paint runs: the simulation before it (for undo). */
3454
3454
  __init102() {this.wetOp = null}
3455
3455
  /** The effects provider slot (see `setEffectsProvider`). */
3456
- __init103() {this.effects = new (0, _chunkNTWR5TQBcjs.EffectsSlot)()}
3456
+ __init103() {this.effects = new (0, _chunkABQ2VGUBcjs.EffectsSlot)()}
3457
3457
  __init104() {this.effectsRenderer = new PaintEffectsRenderer()}
3458
3458
  __init105() {this.paperEffectNodeCache = null}
3459
3459
  constructor(options = {}) {;_class10.prototype.__init72.call(this);_class10.prototype.__init73.call(this);_class10.prototype.__init74.call(this);_class10.prototype.__init75.call(this);_class10.prototype.__init76.call(this);_class10.prototype.__init77.call(this);_class10.prototype.__init78.call(this);_class10.prototype.__init79.call(this);_class10.prototype.__init80.call(this);_class10.prototype.__init81.call(this);_class10.prototype.__init82.call(this);_class10.prototype.__init83.call(this);_class10.prototype.__init84.call(this);_class10.prototype.__init85.call(this);_class10.prototype.__init86.call(this);_class10.prototype.__init87.call(this);_class10.prototype.__init88.call(this);_class10.prototype.__init89.call(this);_class10.prototype.__init90.call(this);_class10.prototype.__init91.call(this);_class10.prototype.__init92.call(this);_class10.prototype.__init93.call(this);_class10.prototype.__init94.call(this);_class10.prototype.__init95.call(this);_class10.prototype.__init96.call(this);_class10.prototype.__init97.call(this);_class10.prototype.__init98.call(this);_class10.prototype.__init99.call(this);_class10.prototype.__init100.call(this);_class10.prototype.__init101.call(this);_class10.prototype.__init102.call(this);_class10.prototype.__init103.call(this);_class10.prototype.__init104.call(this);_class10.prototype.__init105.call(this);_class10.prototype.__init106.call(this);_class10.prototype.__init107.call(this);_class10.prototype.__init108.call(this);_class10.prototype.__init109.call(this);
@@ -4126,7 +4126,7 @@ var PaintEditorController = (_class10 = class _PaintEditorController {
4126
4126
  if (v === null) merged[k] = d.defaults[k];
4127
4127
  else given[k] = v;
4128
4128
  }
4129
- return { ...merged, ..._chunkNTWR5TQBcjs.resolveEffectValues.call(void 0, { ...d, defaults: merged }, given) };
4129
+ return { ...merged, ..._chunkABQ2VGUBcjs.resolveEffectValues.call(void 0, { ...d, defaults: merged }, given) };
4130
4130
  }
4131
4131
  /** Renders a generator, overlay or title effect at the canvas size. */
4132
4132
  renderEffectCanvas(d, values, time = 0) {
@@ -5468,7 +5468,7 @@ var PaintEditorController = (_class10 = class _PaintEditorController {
5468
5468
  return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
5469
5469
  });
5470
5470
  const layers = toStandard(dried);
5471
- const result = _chunkR35YOUP2cjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
5471
+ const result = _chunkIWMDW7LBcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
5472
5472
  for (const [mode, names] of changed) {
5473
5473
  const label = _nullishCoalesce(_optionalChain([_chunkOLNIPTB3cjs.BLEND_MODES, 'access', _279 => _279.find, 'call', _280 => _280((m) => m.id === mode), 'optionalAccess', _281 => _281.label]), () => ( mode));
5474
5474
  const std = _nullishCoalesce(_optionalChain([_chunkOLNIPTB3cjs.BLEND_MODES, 'access', _282 => _282.find, 'call', _283 => _283((m) => m.id === _chunkOLNIPTB3cjs.standardBlendMode.call(void 0, mode)), 'optionalAccess', _284 => _284.label]), () => ( _chunkOLNIPTB3cjs.standardBlendMode.call(void 0, mode)));
@@ -8730,7 +8730,7 @@ function createPaintEffectsToolProvider(controller, options = {}) {
8730
8730
  if (changes.enabled !== void 0) throw new Error("enabled does not apply here: hide an effect layer with update_layer visible false, or remove the effect paper.");
8731
8731
  };
8732
8732
  const values = (v) => v;
8733
- return withPaintPlayback(_chunkNTWR5TQBcjs.createEffectToolProvider.call(void 0, {
8733
+ return withPaintPlayback(_chunkABQ2VGUBcjs.createEffectToolProvider.call(void 0, {
8734
8734
  id: _nullishCoalesce(options.id, () => ( "paint-effects")),
8735
8735
  namespace: options.namespace,
8736
8736
  title: "Paint effects",
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  toVideoFfmpeg
3
- } from "../chunk-RDTCG5MS.js";
3
+ } from "../chunk-56RFHF7O.js";
4
4
  import {
5
5
  deserializeLayers,
6
6
  exportPsd,
7
7
  resolveExternalAssets,
8
8
  serializeLayers,
9
9
  serializeLayersExternal
10
- } from "../chunk-S6KKLFZO.js";
10
+ } from "../chunk-5YLOU7VF.js";
11
11
  import "../chunk-3CI7FRMK.js";
12
12
  import "../chunk-ZNA3IQZF.js";
13
13
  import {
@@ -132,7 +132,7 @@ import {
132
132
  EffectsSlot,
133
133
  createEffectToolProvider,
134
134
  resolveEffectValues
135
- } from "../chunk-HKPLKISO.js";
135
+ } from "../chunk-7DZ55AIE.js";
136
136
  import "../chunk-V2ZCJRDM.js";
137
137
  import {
138
138
  CoverageMap,
@@ -558,7 +558,7 @@ var _chunkNM3G4VQTcjs = require('../chunk-NM3G4VQT.cjs');
558
558
 
559
559
 
560
560
 
561
- var _chunkNTWR5TQBcjs = require('../chunk-NTWR5TQB.cjs');
561
+ var _chunkABQ2VGUBcjs = require('../chunk-ABQ2VGUB.cjs');
562
562
  require('../chunk-WOFXZ3XU.cjs');
563
563
 
564
564
 
@@ -7328,7 +7328,7 @@ var VectorEditorController = (_class7 = class _VectorEditorController {
7328
7328
  }
7329
7329
  // --- Provider effects ---
7330
7330
  /** The effects provider slot (see `setEffectsProvider`). */
7331
- __init59() {this.effects = new (0, _chunkNTWR5TQBcjs.EffectsSlot)()}
7331
+ __init59() {this.effects = new (0, _chunkABQ2VGUBcjs.EffectsSlot)()}
7332
7332
  /** The registered effects provider, or `null`. */
7333
7333
  get effectsProvider() {
7334
7334
  return this.effects.provider;
@@ -7352,10 +7352,10 @@ var VectorEditorController = (_class7 = class _VectorEditorController {
7352
7352
  /** The effect's picture as group children, scaled into `box` (an affine fit). */
7353
7353
  async vectorEffectPicture(effect, values) {
7354
7354
  const provider = this.effects.require();
7355
- if (!provider.applyVectorEffect) throw new (0, _chunkNTWR5TQBcjs.EffectUnsupportedError)(`${provider.name} has no vector effects.`);
7355
+ if (!provider.applyVectorEffect) throw new (0, _chunkABQ2VGUBcjs.EffectUnsupportedError)(`${provider.name} has no vector effects.`);
7356
7356
  const d = await provider.describe(effect);
7357
- if (d.kind !== "vector") throw new (0, _chunkNTWR5TQBcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.kind} effect; the vector editor adds vector effects, which bring their own picture${d.kind === "motion" ? " (apply motion in the animation editor)" : ""}.`, d.id);
7358
- const resolved = _chunkNTWR5TQBcjs.resolveEffectValues.call(void 0, d, values);
7357
+ if (d.kind !== "vector") throw new (0, _chunkABQ2VGUBcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.kind} effect; the vector editor adds vector effects, which bring their own picture${d.kind === "motion" ? " (apply motion in the animation editor)" : ""}.`, d.id);
7358
+ const resolved = _chunkABQ2VGUBcjs.resolveEffectValues.call(void 0, d, values);
7359
7359
  const motion = await provider.applyVectorEffect(d.id, this.toSvg(), resolved);
7360
7360
  const parsed = _chunkQAUIZPYIcjs.parseVectorSvg.call(void 0, motion.svg);
7361
7361
  if (!parsed.nodes.length) throw new Error(`"${d.name}" drew no shapes the editor can read.`);
@@ -17233,6 +17233,19 @@ function createVectorCommands(controller, options = {}) {
17233
17233
  return { ok: true };
17234
17234
  }
17235
17235
  },
17236
+ {
17237
+ ...base,
17238
+ id: `${prefix}rename`,
17239
+ label: "Rename Layer\u2026",
17240
+ keywords: ["rename", "name", "layer", "label"],
17241
+ description: "Renames node `id` (a path, group, text or any layer) to `name`, as the Layers panel shows it. One undo step. Returns { id, name }.",
17242
+ params: { type: "object", properties: { id: { type: "string", description: "The node (from vector.list)." }, name: { type: "string", minLength: 1, description: "Its new name." } }, required: ["id", "name"], additionalProperties: false },
17243
+ undoable: true,
17244
+ run: (_ctx, params) => {
17245
+ check2(controller.rename(params.id, params.name));
17246
+ return { id: params.id, name: params.name };
17247
+ }
17248
+ },
17236
17249
  {
17237
17250
  ...base,
17238
17251
  id: `${prefix}boolean`,
@@ -18159,7 +18172,7 @@ function createVectorEffectsToolProvider(controller, options = {}) {
18159
18172
  const effects = _optionalChain([g, 'optionalAccess', _765 => _765.providerEffect]) ? [info(g)] : [];
18160
18173
  return { target: id, effect: _nullishCoalesce(removed, () => ( effects[0])), effects };
18161
18174
  };
18162
- return _chunkNTWR5TQBcjs.createEffectToolProvider.call(void 0, {
18175
+ return _chunkABQ2VGUBcjs.createEffectToolProvider.call(void 0, {
18163
18176
  id: _nullishCoalesce(options.id, () => ( "vector-effects")),
18164
18177
  namespace: options.namespace,
18165
18178
  title: "Vector effects",
@@ -558,7 +558,7 @@ import {
558
558
  EffectsSlot,
559
559
  createEffectToolProvider,
560
560
  resolveEffectValues
561
- } from "../chunk-HKPLKISO.js";
561
+ } from "../chunk-7DZ55AIE.js";
562
562
  import "../chunk-V2ZCJRDM.js";
563
563
  import {
564
564
  GRAIN_PATTERNS
@@ -17233,6 +17233,19 @@ function createVectorCommands(controller, options = {}) {
17233
17233
  return { ok: true };
17234
17234
  }
17235
17235
  },
17236
+ {
17237
+ ...base,
17238
+ id: `${prefix}rename`,
17239
+ label: "Rename Layer\u2026",
17240
+ keywords: ["rename", "name", "layer", "label"],
17241
+ description: "Renames node `id` (a path, group, text or any layer) to `name`, as the Layers panel shows it. One undo step. Returns { id, name }.",
17242
+ params: { type: "object", properties: { id: { type: "string", description: "The node (from vector.list)." }, name: { type: "string", minLength: 1, description: "Its new name." } }, required: ["id", "name"], additionalProperties: false },
17243
+ undoable: true,
17244
+ run: (_ctx, params) => {
17245
+ check2(controller.rename(params.id, params.name));
17246
+ return { id: params.id, name: params.name };
17247
+ }
17248
+ },
17236
17249
  {
17237
17250
  ...base,
17238
17251
  id: `${prefix}boolean`,