@pixerate/editor 0.225.0 → 0.227.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 (64) hide show
  1. package/dist/animation-editor/index.cjs +66 -66
  2. package/dist/animation-editor/index.js +4 -4
  3. package/dist/{brushstrokeRender-C3UU6GVC.js → brushstrokeRender-MAHOXL7K.js} +2 -2
  4. package/dist/{brushstrokeRender-EAJNE6WP.cjs → brushstrokeRender-WDTKD54B.cjs} +5 -5
  5. package/dist/canvas/index.cjs +15 -15
  6. package/dist/canvas/index.js +2 -2
  7. package/dist/{chunk-WA4IS2EY.js → chunk-4MDWEX44.js} +1 -1
  8. package/dist/{chunk-7AAS3JO6.cjs → chunk-4PA6YS6W.cjs} +14 -14
  9. package/dist/{chunk-RDTCG5MS.js → chunk-56RFHF7O.js} +1 -1
  10. package/dist/{chunk-HKPLKISO.js → chunk-7DZ55AIE.js} +10 -5
  11. package/dist/{chunk-NTWR5TQB.cjs → chunk-ABQ2VGUB.cjs} +10 -5
  12. package/dist/{chunk-LCCMOWR7.cjs → chunk-ATKKG76G.cjs} +23 -23
  13. package/dist/{chunk-QAUIZPYI.cjs → chunk-BQE3Y6ZO.cjs} +2 -2
  14. package/dist/{chunk-MDNYYOBZ.cjs → chunk-C6CMQXMI.cjs} +7 -7
  15. package/dist/{chunk-G4E6RUPC.js → chunk-CDP2AKMA.js} +1 -1
  16. package/dist/{chunk-NDMXB4PR.js → chunk-CU6ANS5L.js} +1 -1
  17. package/dist/{chunk-ROVK6AXX.cjs → chunk-GNZI3KYK.cjs} +95 -23
  18. package/dist/{chunk-I7G3RSUJ.cjs → chunk-GXLZZVR5.cjs} +35 -35
  19. package/dist/{chunk-S6KKLFZO.js → chunk-KN3Y3FRY.js} +19 -4
  20. package/dist/{chunk-R35YOUP2.cjs → chunk-MXJP77K4.cjs} +104 -89
  21. package/dist/{chunk-F7YA3MMC.js → chunk-TQOL6CF7.js} +76 -4
  22. package/dist/{chunk-ZMHPQ6HP.cjs → chunk-V3L7U63N.cjs} +1 -1
  23. package/dist/{chunk-DJBYJOHM.js → chunk-XSVUNSDJ.js} +1 -1
  24. package/dist/{chunk-BGOCAUXH.js → chunk-YMREOA67.js} +1 -1
  25. package/dist/effects/index.cjs +44 -44
  26. package/dist/effects/index.js +2 -2
  27. package/dist/effects/tools.d.cts +6 -0
  28. package/dist/effects/tools.d.ts +6 -0
  29. package/dist/image-editor/controller.d.cts +12 -4
  30. package/dist/image-editor/controller.d.ts +12 -4
  31. package/dist/image-editor/index.cjs +944 -922
  32. package/dist/image-editor/index.js +49 -27
  33. package/dist/image-editor/layers/model.d.cts +6 -0
  34. package/dist/image-editor/layers/model.d.ts +6 -0
  35. package/dist/paint-editor/controller.d.cts +54 -3
  36. package/dist/paint-editor/controller.d.ts +54 -3
  37. package/dist/paint-editor/dab-stroke.d.cts +3 -0
  38. package/dist/paint-editor/dab-stroke.d.ts +3 -0
  39. package/dist/paint-editor/effect-paint.d.cts +47 -0
  40. package/dist/paint-editor/effect-paint.d.ts +47 -0
  41. package/dist/paint-editor/effect-tools.d.cts +4 -2
  42. package/dist/paint-editor/effect-tools.d.ts +4 -2
  43. package/dist/paint-editor/effects.d.cts +3 -0
  44. package/dist/paint-editor/effects.d.ts +3 -0
  45. package/dist/paint-editor/engine.d.cts +4 -0
  46. package/dist/paint-editor/engine.d.ts +4 -0
  47. package/dist/paint-editor/index.cjs +542 -315
  48. package/dist/paint-editor/index.d.cts +1 -0
  49. package/dist/paint-editor/index.d.ts +1 -0
  50. package/dist/paint-editor/index.js +252 -25
  51. package/dist/paint-editor/procedural.d.cts +4 -0
  52. package/dist/paint-editor/procedural.d.ts +4 -0
  53. package/dist/paint-editor/stroke.d.cts +9 -0
  54. package/dist/paint-editor/stroke.d.ts +9 -0
  55. package/dist/paint-editor/strokes.d.cts +6 -2
  56. package/dist/paint-editor/strokes.d.ts +6 -2
  57. package/dist/vector/index.cjs +968 -968
  58. package/dist/vector/index.js +5 -5
  59. package/dist/video-editor/index.cjs +309 -309
  60. package/dist/video-editor/index.js +3 -3
  61. package/package.json +1 -1
  62. package/skills/pixerate-canvas/SKILL.md +2 -1
  63. package/skills/pixerate-paint-editor/SKILL.md +42 -6
  64. package/skills/pixerate-paint-sketch-to-finish/SKILL.md +7 -0
@@ -4,7 +4,7 @@ import {
4
4
  parseOpenTypeFont,
5
5
  removeAnchorsKeepingShape,
6
6
  traceField
7
- } from "../chunk-DJBYJOHM.js";
7
+ } from "../chunk-XSVUNSDJ.js";
8
8
  import {
9
9
  ColorGmm,
10
10
  GAMMA,
@@ -405,7 +405,7 @@ import {
405
405
  writePsd,
406
406
  xyToTempTint,
407
407
  xyToUv
408
- } from "../chunk-S6KKLFZO.js";
408
+ } from "../chunk-KN3Y3FRY.js";
409
409
  import {
410
410
  parseCssColor
411
411
  } from "../chunk-3CI7FRMK.js";
@@ -727,8 +727,8 @@ import {
727
727
  warpMeshParamAt,
728
728
  warpMeshRows,
729
729
  warpPresetMesh
730
- } from "../chunk-NDMXB4PR.js";
731
- import "../chunk-F7YA3MMC.js";
730
+ } from "../chunk-CU6ANS5L.js";
731
+ import "../chunk-TQOL6CF7.js";
732
732
  import "../chunk-M6DNZNVY.js";
733
733
  import {
734
734
  bezierPathToSvg,
@@ -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) & {
@@ -40,6 +40,7 @@ import { type WetBackendInfo } from '../paint/wet/world.cjs';
40
40
  import { type TimeLapse, type TimeLapseOptions } from './replay.cjs';
41
41
  import { EffectsSlot } from '../effects/helpers.cjs';
42
42
  import type { EffectDescription, EffectProvider, EffectValue } from '../effects/types.cjs';
43
+ import { type PaintEffectPaint } from './effect-paint.cjs';
43
44
  import { type PaintEffectPlace, type PaintEffectSource, type PaintPaperEffect, type PaperEffectBlend } from './effects.cjs';
44
45
  import { type PaintLighting } from './thick.cjs';
45
46
  /** A refusal with a message that says what is wrong and how to fix it. */
@@ -100,6 +101,13 @@ export interface PaintEditorState {
100
101
  * own gradient (`brush.paint`) paints that instead.
101
102
  */
102
103
  paint: PaintSource;
104
+ /**
105
+ * Effect paint (`setEffectPaint`): a provider effect strokes take each pixel's colour from,
106
+ * instead of `paint`; `null` when they paint `paint`. `setColor` and `setPaint` clear it.
107
+ */
108
+ effectPaint: PaintEffectPaint | null;
109
+ /** The effect paint is still rendering: strokes that would paint with it wait (they throw). */
110
+ effectPaintRendering: boolean;
103
111
  /** What the Mixer Brush carries between strokes. */
104
112
  mixerLoad: MixerLoad;
105
113
  view: PaintView;
@@ -145,10 +153,11 @@ export interface PaintStrokeInit {
145
153
  /** Default the editor colour. */
146
154
  color?: string;
147
155
  /**
148
- * What this stroke paints with, instead of the editor's paint: a colour, or gradient paint
149
- * (`@pixerate/editor/paint` `PaintSource`). A colour here is the same as `color`.
156
+ * What this stroke paints with, instead of the editor's paint: a colour, gradient paint
157
+ * (`@pixerate/editor/paint` `PaintSource`), or effect paint (render it first with
158
+ * `prepareEffectPaints`). A colour here is the same as `color`.
150
159
  */
151
- paint?: PaintSource | string;
160
+ paint?: PaintSource | PaintEffectPaint | string;
152
161
  /** Default the active brush. */
153
162
  brushId?: string;
154
163
  /** Changes to the brush for this stroke only. */
@@ -256,6 +265,13 @@ export declare class PaintEditorController {
256
265
  private secondaryColor;
257
266
  /** Gradient paint chosen with `setPaint` (`null`: the paint colour). */
258
267
  private gradient;
268
+ /** Effect paint chosen with `setEffectPaint` (rendered), and one still rendering. */
269
+ private effectPaint;
270
+ private effectPaintPending;
271
+ /** Changes whenever the paint is chosen, so an effect paint that finishes rendering late does not take over. */
272
+ private paintEpoch;
273
+ /** Rendered effect paints by `effectPaintKey` (most recently used last). */
274
+ private readonly effectPaintPixels;
259
275
  private mixerLoad;
260
276
  private view;
261
277
  private hand;
@@ -508,6 +524,41 @@ export declare class PaintEditorController {
508
524
  layerId: string;
509
525
  source: PaintEffectSource;
510
526
  }>;
527
+ /** At most this many rendered effect paints are kept (each is canvas-sized). */
528
+ private static readonly EFFECT_PAINT_CACHE;
529
+ /** The effect paint strokes use (or the one rendering), or `null`. */
530
+ getEffectPaint(): PaintEffectPaint | null;
531
+ /**
532
+ * Paints with a provider generator, overlay or title effect: it is rendered once at the canvas
533
+ * size, and brushes then take each pixel's colour (and alpha) from it, fixed to the canvas, as if
534
+ * painting a hidden picture into view. `setColor` and `setPaint` go back to colour. Editor state,
535
+ * not an undo step. Strokes that would paint with it throw while it renders. Returns the paint.
536
+ */
537
+ setEffectPaint(source: {
538
+ effect: string;
539
+ values?: Record<string, EffectValue | null>;
540
+ time?: number;
541
+ }): Promise<PaintEffectPaint>;
542
+ /** The rendered pixels of an effect paint at the current canvas size, or `null` when it has not been rendered. */
543
+ private effectPaintImage;
544
+ /** Renders an effect paint at the canvas size into the cache (nothing when it is there already). */
545
+ private renderEffectPaint;
546
+ /**
547
+ * Renders effect paints that are not rendered yet (strokes and replays need them rendered
548
+ * first, since strokes are synchronous). Throws naming the effect and provider when one cannot be.
549
+ */
550
+ prepareEffectPaints(paints: Iterable<PaintEffectPaint>): Promise<void>;
551
+ /** Renders the effect paints of recorded strokes (default all of them) before a replay. */
552
+ prepareStrokeEffects(strokeIds?: readonly string[]): Promise<void>;
553
+ /** A small preview of the effect paint (a PNG data URL `size` px wide), or `null` without a rendered one. */
554
+ effectPaintThumbnail(size?: number): string | null;
555
+ /**
556
+ * The effect paint a stroke paints with and its pixels, or `null`: the stroke's own effect
557
+ * `paint`, else (without its own `paint` or `color`, and with no brush gradient) the editor's.
558
+ * Throws while the editor's is still rendering, for an engine that cannot paint with it, or when
559
+ * its pixels are not rendered.
560
+ */
561
+ private strokeEffect;
511
562
  /**
512
563
  * Runs a filter effect (light, glow, colour grade …) on a layer's dried paint and bakes it in, at
513
564
  * `amount` strength (0..1, default 1). Transparent areas stay transparent; thick paint keeps its
@@ -40,6 +40,7 @@ import { type WetBackendInfo } from '../paint/wet/world.js';
40
40
  import { type TimeLapse, type TimeLapseOptions } from './replay.js';
41
41
  import { EffectsSlot } from '../effects/helpers.js';
42
42
  import type { EffectDescription, EffectProvider, EffectValue } from '../effects/types.js';
43
+ import { type PaintEffectPaint } from './effect-paint.js';
43
44
  import { type PaintEffectPlace, type PaintEffectSource, type PaintPaperEffect, type PaperEffectBlend } from './effects.js';
44
45
  import { type PaintLighting } from './thick.js';
45
46
  /** A refusal with a message that says what is wrong and how to fix it. */
@@ -100,6 +101,13 @@ export interface PaintEditorState {
100
101
  * own gradient (`brush.paint`) paints that instead.
101
102
  */
102
103
  paint: PaintSource;
104
+ /**
105
+ * Effect paint (`setEffectPaint`): a provider effect strokes take each pixel's colour from,
106
+ * instead of `paint`; `null` when they paint `paint`. `setColor` and `setPaint` clear it.
107
+ */
108
+ effectPaint: PaintEffectPaint | null;
109
+ /** The effect paint is still rendering: strokes that would paint with it wait (they throw). */
110
+ effectPaintRendering: boolean;
103
111
  /** What the Mixer Brush carries between strokes. */
104
112
  mixerLoad: MixerLoad;
105
113
  view: PaintView;
@@ -145,10 +153,11 @@ export interface PaintStrokeInit {
145
153
  /** Default the editor colour. */
146
154
  color?: string;
147
155
  /**
148
- * What this stroke paints with, instead of the editor's paint: a colour, or gradient paint
149
- * (`@pixerate/editor/paint` `PaintSource`). A colour here is the same as `color`.
156
+ * What this stroke paints with, instead of the editor's paint: a colour, gradient paint
157
+ * (`@pixerate/editor/paint` `PaintSource`), or effect paint (render it first with
158
+ * `prepareEffectPaints`). A colour here is the same as `color`.
150
159
  */
151
- paint?: PaintSource | string;
160
+ paint?: PaintSource | PaintEffectPaint | string;
152
161
  /** Default the active brush. */
153
162
  brushId?: string;
154
163
  /** Changes to the brush for this stroke only. */
@@ -256,6 +265,13 @@ export declare class PaintEditorController {
256
265
  private secondaryColor;
257
266
  /** Gradient paint chosen with `setPaint` (`null`: the paint colour). */
258
267
  private gradient;
268
+ /** Effect paint chosen with `setEffectPaint` (rendered), and one still rendering. */
269
+ private effectPaint;
270
+ private effectPaintPending;
271
+ /** Changes whenever the paint is chosen, so an effect paint that finishes rendering late does not take over. */
272
+ private paintEpoch;
273
+ /** Rendered effect paints by `effectPaintKey` (most recently used last). */
274
+ private readonly effectPaintPixels;
259
275
  private mixerLoad;
260
276
  private view;
261
277
  private hand;
@@ -508,6 +524,41 @@ export declare class PaintEditorController {
508
524
  layerId: string;
509
525
  source: PaintEffectSource;
510
526
  }>;
527
+ /** At most this many rendered effect paints are kept (each is canvas-sized). */
528
+ private static readonly EFFECT_PAINT_CACHE;
529
+ /** The effect paint strokes use (or the one rendering), or `null`. */
530
+ getEffectPaint(): PaintEffectPaint | null;
531
+ /**
532
+ * Paints with a provider generator, overlay or title effect: it is rendered once at the canvas
533
+ * size, and brushes then take each pixel's colour (and alpha) from it, fixed to the canvas, as if
534
+ * painting a hidden picture into view. `setColor` and `setPaint` go back to colour. Editor state,
535
+ * not an undo step. Strokes that would paint with it throw while it renders. Returns the paint.
536
+ */
537
+ setEffectPaint(source: {
538
+ effect: string;
539
+ values?: Record<string, EffectValue | null>;
540
+ time?: number;
541
+ }): Promise<PaintEffectPaint>;
542
+ /** The rendered pixels of an effect paint at the current canvas size, or `null` when it has not been rendered. */
543
+ private effectPaintImage;
544
+ /** Renders an effect paint at the canvas size into the cache (nothing when it is there already). */
545
+ private renderEffectPaint;
546
+ /**
547
+ * Renders effect paints that are not rendered yet (strokes and replays need them rendered
548
+ * first, since strokes are synchronous). Throws naming the effect and provider when one cannot be.
549
+ */
550
+ prepareEffectPaints(paints: Iterable<PaintEffectPaint>): Promise<void>;
551
+ /** Renders the effect paints of recorded strokes (default all of them) before a replay. */
552
+ prepareStrokeEffects(strokeIds?: readonly string[]): Promise<void>;
553
+ /** A small preview of the effect paint (a PNG data URL `size` px wide), or `null` without a rendered one. */
554
+ effectPaintThumbnail(size?: number): string | null;
555
+ /**
556
+ * The effect paint a stroke paints with and its pixels, or `null`: the stroke's own effect
557
+ * `paint`, else (without its own `paint` or `color`, and with no brush gradient) the editor's.
558
+ * Throws while the editor's is still rendering, for an engine that cannot paint with it, or when
559
+ * its pixels are not rendered.
560
+ */
561
+ private strokeEffect;
511
562
  /**
512
563
  * Runs a filter effect (light, glow, colour grade …) on a layer's dried paint and bakes it in, at
513
564
  * `amount` strength (0..1, default 1). Transparent areas stay transparent; thick paint keeps its
@@ -19,6 +19,7 @@ import { type MixerLoad, type MixingModel, type PaintTileTarget, type RgbTriple
19
19
  import type { RasterRect } from '../raster/surface.cjs';
20
20
  import { type PaintGradientSource } from '../paint/gradient-paint.cjs';
21
21
  import type { MultiColorSettings, PaintBrush } from './brushes.cjs';
22
+ import { type EffectPaintPixels } from './effect-paint.cjs';
22
23
  import { type PenPoint, type StrokeCopy } from './engine.cjs';
23
24
  import { type PaperGrain } from './paper.cjs';
24
25
  export interface PaintDabStrokeOptions {
@@ -36,6 +37,8 @@ export interface PaintDabStrokeOptions {
36
37
  copies?: readonly StrokeCopy[];
37
38
  /** Gradient paint (resolved: no `paint` / `secondary` stops): each dab's colour comes from it. */
38
39
  paint?: PaintGradientSource | null;
40
+ /** Effect paint: each dab's colour (and flow, by alpha) is the effect's at the dab's centre. */
41
+ effect?: EffectPaintPixels | null;
39
42
  }
40
43
  /** Colours of a multi-colour brush, resolved against the paint and secondary colours. */
41
44
  export declare function multiColorPalette(mc: MultiColorSettings, color: RgbTriple, secondary: RgbTriple): RgbTriple[];
@@ -19,6 +19,7 @@ import { type MixerLoad, type MixingModel, type PaintTileTarget, type RgbTriple
19
19
  import type { RasterRect } from '../raster/surface.js';
20
20
  import { type PaintGradientSource } from '../paint/gradient-paint.js';
21
21
  import type { MultiColorSettings, PaintBrush } from './brushes.js';
22
+ import { type EffectPaintPixels } from './effect-paint.js';
22
23
  import { type PenPoint, type StrokeCopy } from './engine.js';
23
24
  import { type PaperGrain } from './paper.js';
24
25
  export interface PaintDabStrokeOptions {
@@ -36,6 +37,8 @@ export interface PaintDabStrokeOptions {
36
37
  copies?: readonly StrokeCopy[];
37
38
  /** Gradient paint (resolved: no `paint` / `secondary` stops): each dab's colour comes from it. */
38
39
  paint?: PaintGradientSource | null;
40
+ /** Effect paint: each dab's colour (and flow, by alpha) is the effect's at the dab's centre. */
41
+ effect?: EffectPaintPixels | null;
39
42
  }
40
43
  /** Colours of a multi-colour brush, resolved against the paint and secondary colours. */
41
44
  export declare function multiColorPalette(mc: MultiColorSettings, color: RgbTriple, secondary: RgbTriple): RgbTriple[];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Effect paint: a provider generator, overlay or title effect that brushes paint with. The effect
3
+ * is rendered once at the canvas size when it is chosen; strokes then take each pixel's colour
4
+ * from that image, fixed to the canvas, as if painting a hidden picture into view. The effect's
5
+ * alpha multiplies the brush coverage, so a transparent part of an overlay paints nothing.
6
+ *
7
+ * Strokes are synchronous and effect renders are not, so the pixels are rendered (and cached by
8
+ * source) before a stroke can use them; a stroke records the source, and replays render any
9
+ * missing source before they paint.
10
+ *
11
+ * Kept out of the shared `PaintSource` union (`@pixerate/editor/paint`), which the image editor
12
+ * shares: only the paint editor paints with effects.
13
+ */
14
+ import type { EffectValue } from '../effects/types.cjs';
15
+ import type { RasterRect } from '../raster/surface.cjs';
16
+ /** What strokes paint with when they paint with a provider effect. */
17
+ export interface PaintEffectPaint {
18
+ type: 'effect';
19
+ /** The effect id (search_effects). */
20
+ effect: string;
21
+ /** The provider's id when it was chosen. */
22
+ provider: string;
23
+ /** Every control value. */
24
+ values: Record<string, EffectValue>;
25
+ /** Which moment of an animated effect, in ms. */
26
+ time: number;
27
+ }
28
+ /** Canvas-sized straight-alpha RGBA an effect paint was rendered to. */
29
+ export interface EffectPaintPixels {
30
+ width: number;
31
+ height: number;
32
+ data: ArrayLike<number>;
33
+ }
34
+ /** The cache key of an effect paint (the same source renders the same pixels). */
35
+ export declare function effectPaintKey(p: PaintEffectPaint): string;
36
+ /** Whether a value is an effect paint (`{ type: 'effect', effect, … }`). */
37
+ export declare function isPaintEffectPaint(raw: unknown): raw is PaintEffectPaint;
38
+ /** A saved effect paint, or `null` when it is not one. */
39
+ export declare function readPaintEffectPaint(raw: unknown): PaintEffectPaint | null;
40
+ /**
41
+ * Takes a dab's colours from the effect: fills `field` (sRGB 0..1, 3 per pixel of `rect`, a
42
+ * document rectangle) with the effect's colour at each pixel and multiplies `vals` by its alpha.
43
+ * Pixels off the canvas paint nothing.
44
+ */
45
+ export declare function sampleEffectField(img: EffectPaintPixels, rect: RasterRect, vals: Float32Array, field: Float32Array): void;
46
+ /** The effect's colour (sRGB 0..1 into `out`) at a document point; returns its alpha 0..1 (0 off the canvas). */
47
+ export declare function sampleEffectAt(img: EffectPaintPixels, x: number, y: number, out: [number, number, number]): number;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Effect paint: a provider generator, overlay or title effect that brushes paint with. The effect
3
+ * is rendered once at the canvas size when it is chosen; strokes then take each pixel's colour
4
+ * from that image, fixed to the canvas, as if painting a hidden picture into view. The effect's
5
+ * alpha multiplies the brush coverage, so a transparent part of an overlay paints nothing.
6
+ *
7
+ * Strokes are synchronous and effect renders are not, so the pixels are rendered (and cached by
8
+ * source) before a stroke can use them; a stroke records the source, and replays render any
9
+ * missing source before they paint.
10
+ *
11
+ * Kept out of the shared `PaintSource` union (`@pixerate/editor/paint`), which the image editor
12
+ * shares: only the paint editor paints with effects.
13
+ */
14
+ import type { EffectValue } from '../effects/types.js';
15
+ import type { RasterRect } from '../raster/surface.js';
16
+ /** What strokes paint with when they paint with a provider effect. */
17
+ export interface PaintEffectPaint {
18
+ type: 'effect';
19
+ /** The effect id (search_effects). */
20
+ effect: string;
21
+ /** The provider's id when it was chosen. */
22
+ provider: string;
23
+ /** Every control value. */
24
+ values: Record<string, EffectValue>;
25
+ /** Which moment of an animated effect, in ms. */
26
+ time: number;
27
+ }
28
+ /** Canvas-sized straight-alpha RGBA an effect paint was rendered to. */
29
+ export interface EffectPaintPixels {
30
+ width: number;
31
+ height: number;
32
+ data: ArrayLike<number>;
33
+ }
34
+ /** The cache key of an effect paint (the same source renders the same pixels). */
35
+ export declare function effectPaintKey(p: PaintEffectPaint): string;
36
+ /** Whether a value is an effect paint (`{ type: 'effect', effect, … }`). */
37
+ export declare function isPaintEffectPaint(raw: unknown): raw is PaintEffectPaint;
38
+ /** A saved effect paint, or `null` when it is not one. */
39
+ export declare function readPaintEffectPaint(raw: unknown): PaintEffectPaint | null;
40
+ /**
41
+ * Takes a dab's colours from the effect: fills `field` (sRGB 0..1, 3 per pixel of `rect`, a
42
+ * document rectangle) with the effect's colour at each pixel and multiplies `vals` by its alpha.
43
+ * Pixels off the canvas paint nothing.
44
+ */
45
+ export declare function sampleEffectField(img: EffectPaintPixels, rect: RasterRect, vals: Float32Array, field: Float32Array): void;
46
+ /** The effect's colour (sRGB 0..1 into `out`) at a document point; returns its alpha 0..1 (0 off the canvas). */
47
+ export declare function sampleEffectAt(img: EffectPaintPixels, x: number, y: number, out: [number, number, number]): number;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Agent tools for provider effects in the paint editor: the shared effect tools, where
3
3
  * `apply_effect` runs a filter (light, glow, grade) on a layer's dried paint and bakes it in, and
4
- * the target `paper` is the effect paper; plus `set_paper_effect`, `add_effect_layer` and
5
- * `fill_layer_with_effect`.
4
+ * the target `paper` is the effect paper and the target `paint` the effect paint; plus
5
+ * `set_paper_effect`, `add_effect_layer`, `fill_layer_with_effect` and `set_effect_paint`.
6
6
  */
7
7
  import type { AgentToolProvider } from '../tools/registry.cjs';
8
8
  import type { PaintEditorController } from './controller.cjs';
@@ -21,5 +21,7 @@ export interface PaintEffectsToolProviderOptions {
21
21
  }
22
22
  /** The target id of the effect paper. */
23
23
  export declare const PAPER_TARGET = "paper";
24
+ /** The target id of the effect paint (what brushes paint with). */
25
+ export declare const PAINT_TARGET = "paint";
24
26
  /** The effect tools for a paint editor (see the module comment). */
25
27
  export declare function createPaintEffectsToolProvider(controller: PaintEditorController, options?: PaintEffectsToolProviderOptions): AgentToolProvider;