@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.
- package/dist/animation-editor/index.cjs +66 -66
- package/dist/animation-editor/index.js +4 -4
- package/dist/{brushstrokeRender-C3UU6GVC.js → brushstrokeRender-MAHOXL7K.js} +2 -2
- package/dist/{brushstrokeRender-EAJNE6WP.cjs → brushstrokeRender-WDTKD54B.cjs} +5 -5
- package/dist/canvas/index.cjs +15 -15
- package/dist/canvas/index.js +2 -2
- package/dist/{chunk-WA4IS2EY.js → chunk-4MDWEX44.js} +1 -1
- package/dist/{chunk-7AAS3JO6.cjs → chunk-4PA6YS6W.cjs} +14 -14
- package/dist/{chunk-RDTCG5MS.js → chunk-56RFHF7O.js} +1 -1
- package/dist/{chunk-HKPLKISO.js → chunk-7DZ55AIE.js} +10 -5
- package/dist/{chunk-NTWR5TQB.cjs → chunk-ABQ2VGUB.cjs} +10 -5
- package/dist/{chunk-LCCMOWR7.cjs → chunk-ATKKG76G.cjs} +23 -23
- package/dist/{chunk-QAUIZPYI.cjs → chunk-BQE3Y6ZO.cjs} +2 -2
- package/dist/{chunk-MDNYYOBZ.cjs → chunk-C6CMQXMI.cjs} +7 -7
- package/dist/{chunk-G4E6RUPC.js → chunk-CDP2AKMA.js} +1 -1
- package/dist/{chunk-NDMXB4PR.js → chunk-CU6ANS5L.js} +1 -1
- package/dist/{chunk-ROVK6AXX.cjs → chunk-GNZI3KYK.cjs} +95 -23
- package/dist/{chunk-I7G3RSUJ.cjs → chunk-GXLZZVR5.cjs} +35 -35
- package/dist/{chunk-S6KKLFZO.js → chunk-KN3Y3FRY.js} +19 -4
- package/dist/{chunk-R35YOUP2.cjs → chunk-MXJP77K4.cjs} +104 -89
- package/dist/{chunk-F7YA3MMC.js → chunk-TQOL6CF7.js} +76 -4
- package/dist/{chunk-ZMHPQ6HP.cjs → chunk-V3L7U63N.cjs} +1 -1
- package/dist/{chunk-DJBYJOHM.js → chunk-XSVUNSDJ.js} +1 -1
- package/dist/{chunk-BGOCAUXH.js → chunk-YMREOA67.js} +1 -1
- package/dist/effects/index.cjs +44 -44
- package/dist/effects/index.js +2 -2
- package/dist/effects/tools.d.cts +6 -0
- package/dist/effects/tools.d.ts +6 -0
- package/dist/image-editor/controller.d.cts +12 -4
- package/dist/image-editor/controller.d.ts +12 -4
- package/dist/image-editor/index.cjs +944 -922
- package/dist/image-editor/index.js +49 -27
- package/dist/image-editor/layers/model.d.cts +6 -0
- package/dist/image-editor/layers/model.d.ts +6 -0
- package/dist/paint-editor/controller.d.cts +54 -3
- package/dist/paint-editor/controller.d.ts +54 -3
- package/dist/paint-editor/dab-stroke.d.cts +3 -0
- package/dist/paint-editor/dab-stroke.d.ts +3 -0
- package/dist/paint-editor/effect-paint.d.cts +47 -0
- package/dist/paint-editor/effect-paint.d.ts +47 -0
- package/dist/paint-editor/effect-tools.d.cts +4 -2
- package/dist/paint-editor/effect-tools.d.ts +4 -2
- package/dist/paint-editor/effects.d.cts +3 -0
- package/dist/paint-editor/effects.d.ts +3 -0
- package/dist/paint-editor/engine.d.cts +4 -0
- package/dist/paint-editor/engine.d.ts +4 -0
- package/dist/paint-editor/index.cjs +542 -315
- package/dist/paint-editor/index.d.cts +1 -0
- package/dist/paint-editor/index.d.ts +1 -0
- package/dist/paint-editor/index.js +252 -25
- package/dist/paint-editor/procedural.d.cts +4 -0
- package/dist/paint-editor/procedural.d.ts +4 -0
- package/dist/paint-editor/stroke.d.cts +9 -0
- package/dist/paint-editor/stroke.d.ts +9 -0
- package/dist/paint-editor/strokes.d.cts +6 -2
- package/dist/paint-editor/strokes.d.ts +6 -2
- package/dist/vector/index.cjs +968 -968
- package/dist/vector/index.js +5 -5
- package/dist/video-editor/index.cjs +309 -309
- package/dist/video-editor/index.js +3 -3
- package/package.json +1 -1
- package/skills/pixerate-canvas/SKILL.md +2 -1
- package/skills/pixerate-paint-editor/SKILL.md +42 -6
- 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-
|
|
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-
|
|
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-
|
|
731
|
-
import "../chunk-
|
|
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-
|
|
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
|
|
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.
|
|
6590
|
-
*
|
|
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
|
-
|
|
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,
|
|
149
|
-
* (`@pixerate/editor/paint` `PaintSource`)
|
|
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,
|
|
149
|
-
* (`@pixerate/editor/paint` `PaintSource`)
|
|
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
|
|
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;
|