@pixerate/editor 0.175.0 → 0.176.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-OVYQAYGH.cjs → chunk-UHMSMUHK.cjs} +75 -66
- package/dist/{chunk-BDIM6NVW.js → chunk-Y46OMERN.js} +10 -1
- package/dist/effects/fake.d.cts +1 -1
- package/dist/effects/fake.d.ts +1 -1
- package/dist/effects/index.cjs +36 -22
- package/dist/effects/index.js +19 -5
- package/dist/effects/types.d.cts +6 -0
- package/dist/effects/types.d.ts +6 -0
- package/dist/effects/ui.d.cts +0 -6
- package/dist/effects/ui.d.ts +0 -6
- package/dist/image-editor/controller.d.cts +28 -5
- package/dist/image-editor/controller.d.ts +28 -5
- package/dist/image-editor/effect-layers.d.cts +40 -0
- package/dist/image-editor/effect-layers.d.ts +40 -0
- package/dist/image-editor/index.cjs +931 -812
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +152 -33
- package/dist/image-editor/layers/model.d.cts +15 -1
- package/dist/image-editor/layers/model.d.ts +15 -1
- package/dist/image-editor/layers/panel.d.cts +6 -2
- package/dist/image-editor/layers/panel.d.ts +6 -2
- package/dist/image-editor/provider-effect-tools.d.cts +2 -1
- package/dist/image-editor/provider-effect-tools.d.ts +2 -1
- package/dist/paint-editor/index.cjs +6 -6
- package/dist/paint-editor/index.js +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +2 -1
|
@@ -61,6 +61,7 @@ export { transformSurface, warpSurface, reduceSurface, compositeSurfaceOver, tra
|
|
|
61
61
|
export { rectToQuad, homographyFromPoints, applyHomography, invertHomography, homographyFromAffine, boxQuad, identityWarpMesh, evalWarpMesh, warpPresetMesh, warpMeshEditPoints, warpMeshGridCurves, dragWarpMeshPoint, pullWarpMesh, warpMeshParamAt, resizeWarpMesh, WARP_STYLE_LABELS, } from '../geom/index.cjs';
|
|
62
62
|
export * from './layers/transform-ui.cjs';
|
|
63
63
|
export * from './effects.cjs';
|
|
64
|
+
export { checkEffectAnimation, checkEffectTime, effectLayerTiming, hasLayersBelow, withBackgroundOpacity, type EffectLayerTiming } from './effect-layers.cjs';
|
|
64
65
|
export * from './provider-effect-tools.cjs';
|
|
65
66
|
export * from './playback.cjs';
|
|
66
67
|
export * from './editorDocument.cjs';
|
|
@@ -61,6 +61,7 @@ export { transformSurface, warpSurface, reduceSurface, compositeSurfaceOver, tra
|
|
|
61
61
|
export { rectToQuad, homographyFromPoints, applyHomography, invertHomography, homographyFromAffine, boxQuad, identityWarpMesh, evalWarpMesh, warpPresetMesh, warpMeshEditPoints, warpMeshGridCurves, dragWarpMeshPoint, pullWarpMesh, warpMeshParamAt, resizeWarpMesh, WARP_STYLE_LABELS, } from '../geom/index.js';
|
|
62
62
|
export * from './layers/transform-ui.js';
|
|
63
63
|
export * from './effects.js';
|
|
64
|
+
export { checkEffectAnimation, checkEffectTime, effectLayerTiming, hasLayersBelow, withBackgroundOpacity, type EffectLayerTiming } from './effect-layers.js';
|
|
64
65
|
export * from './provider-effect-tools.js';
|
|
65
66
|
export * from './playback.js';
|
|
66
67
|
export * from './editorDocument.js';
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
LUMA_709,
|
|
31
31
|
LayerCompositor,
|
|
32
32
|
LayerOperationError,
|
|
33
|
+
MAX_EFFECT_ANIMATION_MS,
|
|
33
34
|
MAX_KELVIN,
|
|
34
35
|
MIN_KELVIN,
|
|
35
36
|
MIRED_PER_TEMPERATURE_UNIT,
|
|
@@ -369,7 +370,7 @@ import {
|
|
|
369
370
|
writePsd,
|
|
370
371
|
xyToTempTint,
|
|
371
372
|
xyToUv
|
|
372
|
-
} from "../chunk-
|
|
373
|
+
} from "../chunk-Y46OMERN.js";
|
|
373
374
|
import {
|
|
374
375
|
parseCssColor
|
|
375
376
|
} from "../chunk-3CI7FRMK.js";
|
|
@@ -3298,6 +3299,35 @@ function refineSelection(pixels, mask, params = {}) {
|
|
|
3298
3299
|
return createSelectionMask(mask.width, mask.height, out);
|
|
3299
3300
|
}
|
|
3300
3301
|
|
|
3302
|
+
// src/image-editor/effect-layers.ts
|
|
3303
|
+
function hasLayersBelow(layers, id) {
|
|
3304
|
+
const all = flattenLayers(layers);
|
|
3305
|
+
const at = all.findIndex((l) => l.id === id);
|
|
3306
|
+
return at > 0 && all.slice(0, at).some((l) => l.type !== "group" && l.type !== "adjustment" && l.type !== "annotation");
|
|
3307
|
+
}
|
|
3308
|
+
function withBackgroundOpacity(d, values, below) {
|
|
3309
|
+
const out = { ...values };
|
|
3310
|
+
if (below && d.backgroundOpacity && out[d.backgroundOpacity] === void 0) out[d.backgroundOpacity] = 0;
|
|
3311
|
+
return out;
|
|
3312
|
+
}
|
|
3313
|
+
function checkEffectTime(time) {
|
|
3314
|
+
if (time === void 0) return 0;
|
|
3315
|
+
if (!(typeof time === "number" && Number.isFinite(time) && time >= 0)) throw new LayerOperationError("time must be a number of ms from 0.");
|
|
3316
|
+
return time;
|
|
3317
|
+
}
|
|
3318
|
+
function checkEffectAnimation(animate) {
|
|
3319
|
+
const duration = animate?.duration;
|
|
3320
|
+
if (!(typeof duration === "number" && Number.isFinite(duration) && duration > 0 && duration <= MAX_EFFECT_ANIMATION_MS)) {
|
|
3321
|
+
throw new LayerOperationError(`animate.duration must be more than 0 and at most ${MAX_EFFECT_ANIMATION_MS} ms.`);
|
|
3322
|
+
}
|
|
3323
|
+
return { duration: Math.round(duration) };
|
|
3324
|
+
}
|
|
3325
|
+
function effectLayerTiming(g, d) {
|
|
3326
|
+
if (!d?.duration) return null;
|
|
3327
|
+
const time = g.time ?? 0;
|
|
3328
|
+
return { mode: g.animate ? "animate" : "still", time, range: Math.max(d.duration, time), duration: g.animate?.duration ?? d.duration, loops: d.loop };
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3301
3331
|
// src/image-editor/effects.ts
|
|
3302
3332
|
var IMAGE_EFFECT_PLACES = {
|
|
3303
3333
|
/** A smart filter: the effect changes the pixels below it. */
|
|
@@ -4900,28 +4930,35 @@ var ImageEditorController = class {
|
|
|
4900
4930
|
/**
|
|
4901
4931
|
* Adds an effect layer: a pixel layer drawn by a provider generator, overlay or title effect at
|
|
4902
4932
|
* the document's size and bit depth (at `time` ms, default 0), placed like `addPixelLayer`. One
|
|
4903
|
-
* undo step. Masks, blend and opacity apply as to any layer.
|
|
4933
|
+
* undo step. Masks, blend and opacity apply as to any layer. An effect with a background of its
|
|
4934
|
+
* own (`backgroundOpacity`) starts with it transparent when there are layers below it, unless
|
|
4935
|
+
* `values` set it. `animate` makes it play when the document is output as an animation. Returns
|
|
4936
|
+
* its id.
|
|
4904
4937
|
*/
|
|
4905
4938
|
async addEffectLayer(effect, options = {}) {
|
|
4906
4939
|
const d = await this.describeEffect(effect, "layer");
|
|
4907
|
-
const values = resolveEffectValues(d, options.values);
|
|
4908
|
-
const { width, height } = this.naturalSize;
|
|
4909
|
-
const pixels = await this.effectsEngine.renderLayer(d.id, values, { width, height, depth: this.bitDepth, time: options.time });
|
|
4910
4940
|
const blend = parseBlend(options.blend);
|
|
4911
4941
|
if (options.opacity !== void 0 && !(options.opacity >= 0 && options.opacity <= 1)) throw new LayerOperationError("opacity must be from 0 to 1.");
|
|
4942
|
+
const time = checkEffectTime(options.time);
|
|
4943
|
+
const animate = options.animate === void 0 ? void 0 : checkEffectAnimation(options.animate);
|
|
4912
4944
|
const ids = new Set(flattenLayers(this.state.layers).map((l) => l.id));
|
|
4945
|
+
const id = options.id && !ids.has(options.id) ? options.id : newLayerId();
|
|
4946
|
+
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));
|
|
4947
|
+
const values = resolveEffectValues(d, withBackgroundOpacity(d, options.values, hasLayersBelow(placed, id)));
|
|
4948
|
+
const { width, height } = this.naturalSize;
|
|
4949
|
+
const pixels = await this.effectsEngine.renderLayer(d.id, values, { width, height, depth: this.bitDepth, time });
|
|
4913
4950
|
const layer = {
|
|
4914
4951
|
type: "pixel",
|
|
4915
|
-
id
|
|
4952
|
+
id,
|
|
4916
4953
|
name: options.name?.trim() || d.name,
|
|
4917
4954
|
visible: true,
|
|
4918
4955
|
opacity: options.opacity ?? 1,
|
|
4919
4956
|
blend: blend ?? "normal",
|
|
4920
4957
|
surface: effectPixelsToSurface(pixels, this.bitDepth),
|
|
4921
4958
|
offset: { x: 0, y: 0 },
|
|
4922
|
-
generator: { effect: d.id, provider: this.effects.require().id, name: d.name, values, ...
|
|
4959
|
+
generator: { effect: d.id, provider: this.effects.require().id, name: d.name, values, ...time ? { time } : {}, ...animate ? { animate } : {} }
|
|
4923
4960
|
};
|
|
4924
|
-
const next =
|
|
4961
|
+
const next = mapLayer(placed, id, () => layer);
|
|
4925
4962
|
this.commitLayers(next, () => {
|
|
4926
4963
|
this.state.activeLayerId = layer.id;
|
|
4927
4964
|
this.state.activeTarget = "pixels";
|
|
@@ -4929,9 +4966,12 @@ var ImageEditorController = class {
|
|
|
4929
4966
|
return layer.id;
|
|
4930
4967
|
}
|
|
4931
4968
|
/**
|
|
4932
|
-
* Draws an effect layer again
|
|
4933
|
-
*
|
|
4934
|
-
*
|
|
4969
|
+
* Draws an effect layer again, replacing its pixels (painting on it is lost): with new `values`
|
|
4970
|
+
* (merged over its own; `null` resets a control), at another `time`, or as another `effect`
|
|
4971
|
+
* (a generator, overlay or title: its defaults plus `values`, with its background transparent
|
|
4972
|
+
* when there are layers below; the layer keeps a name of its own, else takes the new effect's).
|
|
4973
|
+
* `animate` sets how it plays in an animation (null: a still frame). One undo step (none with
|
|
4974
|
+
* `commitToHistory` false). Returns its effect settings.
|
|
4935
4975
|
*/
|
|
4936
4976
|
async updateEffectLayer(layerId, changes, commitToHistory = true) {
|
|
4937
4977
|
const found = findLayer(this.state.layers, layerId);
|
|
@@ -4939,8 +4979,9 @@ var ImageEditorController = class {
|
|
|
4939
4979
|
if (!layer || layer.type !== "pixel" || !layer.generator) throw new LayerOperationError(`"${layerId}" is not an effect layer; add one with add_effect_layer.`);
|
|
4940
4980
|
requireUnlocked(layer, "pixels", "draw its effect again");
|
|
4941
4981
|
const g = layer.generator;
|
|
4942
|
-
const
|
|
4943
|
-
const
|
|
4982
|
+
const swap = changes.effect !== void 0 && changes.effect !== g.effect;
|
|
4983
|
+
const d = swap ? await this.describeEffect(changes.effect, "layer") : await this.describeEffect(g.effect);
|
|
4984
|
+
const base = swap ? { ...d.defaults, ...withBackgroundOpacity(d, {}, hasLayersBelow(this.state.layers, layerId)) } : { ...g.values };
|
|
4944
4985
|
const given = {};
|
|
4945
4986
|
for (const [k, v] of Object.entries(changes.values ?? {})) {
|
|
4946
4987
|
if (v !== null) given[k] = v;
|
|
@@ -4948,15 +4989,47 @@ var ImageEditorController = class {
|
|
|
4948
4989
|
else throw new LayerOperationError(`"${k}" is not a control of "${d.id}"; its controls are ${Object.keys(d.controls.properties).join(", ")}.`);
|
|
4949
4990
|
}
|
|
4950
4991
|
const values = { ...base, ...resolveEffectValues({ ...d, defaults: base }, given) };
|
|
4951
|
-
const time = changes.time
|
|
4992
|
+
const time = changes.time === void 0 ? g.time ?? 0 : checkEffectTime(changes.time);
|
|
4993
|
+
const animate = changes.animate === void 0 ? g.animate : changes.animate === null ? void 0 : checkEffectAnimation(changes.animate);
|
|
4952
4994
|
const { width, height } = this.naturalSize;
|
|
4953
4995
|
const pixels = await this.effectsEngine.renderLayer(d.id, values, { width, height, depth: this.bitDepth, time });
|
|
4954
|
-
const generator = {
|
|
4955
|
-
|
|
4956
|
-
const next = mapLayer(this.state.layers, layerId, (l) => ({ ...l, surface: effectPixelsToSurface(pixels, this.bitDepth), offset: { x: 0, y: 0 }, generator }));
|
|
4996
|
+
const generator = { effect: d.id, provider: this.effects.require().id, name: d.name, values, ...time ? { time } : {}, ...animate ? { animate } : {} };
|
|
4997
|
+
const renamed = swap && (!layer.name || layer.name === (g.name ?? g.effect));
|
|
4998
|
+
const next = mapLayer(this.state.layers, layerId, (l) => ({ ...l, ...renamed ? { name: d.name } : {}, surface: effectPixelsToSurface(pixels, this.bitDepth), offset: { x: 0, y: 0 }, generator }));
|
|
4957
4999
|
this.commitLayers(next, void 0, commitToHistory);
|
|
4958
5000
|
return generator;
|
|
4959
5001
|
}
|
|
5002
|
+
/**
|
|
5003
|
+
* The document as an animation: its visible effect layers that animate (`generator.animate`)
|
|
5004
|
+
* and how long it runs, the longest of theirs (ms). Null when none animates.
|
|
5005
|
+
*/
|
|
5006
|
+
getAnimation() {
|
|
5007
|
+
const layers = flattenLayers(this.state.layers).filter((l) => l.type === "pixel" && l.visible && !!l.generator?.animate);
|
|
5008
|
+
if (!layers.length) return null;
|
|
5009
|
+
return { duration: Math.max(...layers.map((l) => l.generator.animate.duration)), layerIds: layers.map((l) => l.id) };
|
|
5010
|
+
}
|
|
5011
|
+
/**
|
|
5012
|
+
* Renders the document `time` ms into its animation (see `getAnimation`), like `renderExport`:
|
|
5013
|
+
* each animating effect layer drawn at its own `time` plus `time` (an effect that loops keeps
|
|
5014
|
+
* looping past its duration), every other layer as it is. Nothing in the document changes.
|
|
5015
|
+
*/
|
|
5016
|
+
async renderAnimationFrame(time, options) {
|
|
5017
|
+
if (!(Number.isFinite(time) && time >= 0)) throw new LayerOperationError("time must be a number of ms from 0.");
|
|
5018
|
+
const source = this.drawSource();
|
|
5019
|
+
if (!source) return this.renderer.createCanvas(1, 1);
|
|
5020
|
+
this.syncViews();
|
|
5021
|
+
const { width, height } = this.naturalSize;
|
|
5022
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
5023
|
+
for (const l of flattenLayers(this.state.layers)) {
|
|
5024
|
+
if (l.type !== "pixel" || !l.visible || !l.generator?.animate) continue;
|
|
5025
|
+
const g = l.generator;
|
|
5026
|
+
const pixels = await this.effectsEngine.renderLayer(g.effect, g.values, { width, height, depth: this.bitDepth, time: (g.time ?? 0) + time });
|
|
5027
|
+
surfaces.set(l.id, effectPixelsToSurface(pixels, this.bitDepth));
|
|
5028
|
+
}
|
|
5029
|
+
let layers = this.state.layers;
|
|
5030
|
+
for (const [id, surface] of surfaces) layers = mapLayer(layers, id, (l) => ({ ...l, surface, offset: { x: 0, y: 0 } }));
|
|
5031
|
+
return this.renderer.renderExport(source, { ...this.state, layers }, options);
|
|
5032
|
+
}
|
|
4960
5033
|
/** Turns an effect layer into a plain pixel layer keeping its pixels (one undo step). */
|
|
4961
5034
|
rasterizeEffectLayer(layerId) {
|
|
4962
5035
|
const found = findLayer(this.state.layers, layerId);
|
|
@@ -14631,32 +14704,39 @@ async function addLayerOfKind(controller, id, init) {
|
|
|
14631
14704
|
throw new Error(`Unknown layer kind "${id}"`);
|
|
14632
14705
|
}
|
|
14633
14706
|
function layerPanels(layer, options = {}) {
|
|
14634
|
-
const effects = options.effects ? [{ id: "provider-effects", title: "Effects" }] : [];
|
|
14635
14707
|
if (!layer) {
|
|
14636
14708
|
return [
|
|
14637
14709
|
{ id: "canvas-size", title: "Composition Size" },
|
|
14638
|
-
{ id: "selection", title: "Selection" }
|
|
14639
|
-
...effects
|
|
14710
|
+
{ id: "selection", title: "Selection" }
|
|
14640
14711
|
];
|
|
14641
14712
|
}
|
|
14642
|
-
const panels = [
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14713
|
+
const panels = [{ id: "properties", title: "Layer Properties" }];
|
|
14714
|
+
const styles = (id, title) => {
|
|
14715
|
+
if (!options.effects) panels.push({ id, title });
|
|
14716
|
+
};
|
|
14717
|
+
const effects = () => {
|
|
14718
|
+
if (options.effects) panels.push({ id: "provider-effects", title: "Effects" });
|
|
14719
|
+
};
|
|
14646
14720
|
if (layer.type === "pixel") {
|
|
14721
|
+
effects();
|
|
14647
14722
|
panels.push({ id: "paint", title: "Brush Options" });
|
|
14648
|
-
|
|
14723
|
+
styles("effects", "Layer Effects");
|
|
14649
14724
|
} else if (layer.type === "adjustment") {
|
|
14650
14725
|
panels.push({ id: "adjustment", title: adjustmentKindLabel(layer.kind) });
|
|
14651
14726
|
} else if (layer.type === "type") {
|
|
14652
14727
|
panels.push({ id: "type", title: "Typography" });
|
|
14653
|
-
|
|
14728
|
+
effects();
|
|
14729
|
+
styles("effects", "Layer Effects");
|
|
14654
14730
|
} else if (layer.type === "shape") {
|
|
14655
14731
|
panels.push({ id: "shape", title: "Shape Options" });
|
|
14656
|
-
|
|
14732
|
+
effects();
|
|
14733
|
+
styles("effects", "Layer Effects");
|
|
14657
14734
|
} else if (layer.type === "smart") {
|
|
14658
|
-
|
|
14659
|
-
|
|
14735
|
+
effects();
|
|
14736
|
+
styles("smart-filters", "Smart Filters");
|
|
14737
|
+
styles("effects", "Layer Effects");
|
|
14738
|
+
} else if (layer.type === "group") {
|
|
14739
|
+
effects();
|
|
14660
14740
|
}
|
|
14661
14741
|
if (layer.mask) {
|
|
14662
14742
|
panels.push({ id: "mask", title: "Mask Options" });
|
|
@@ -15518,10 +15598,17 @@ var FreeTransformTool = class {
|
|
|
15518
15598
|
};
|
|
15519
15599
|
|
|
15520
15600
|
// src/image-editor/provider-effect-tools.ts
|
|
15601
|
+
var ANIMATE_SCHEMA = {
|
|
15602
|
+
type: "object",
|
|
15603
|
+
properties: { duration: { type: "number", exclusiveMinimum: 0, maximum: MAX_EFFECT_ANIMATION_MS, description: `How long it plays in ms (at most ${MAX_EFFECT_ANIMATION_MS}).` } },
|
|
15604
|
+
required: ["duration"],
|
|
15605
|
+
additionalProperties: false,
|
|
15606
|
+
description: "Play when the document is output as an animation, for duration ms from time (the longest animating layer sets the animation's length)."
|
|
15607
|
+
};
|
|
15521
15608
|
function imageLayerAppliedEffects(layer) {
|
|
15522
15609
|
const out = [];
|
|
15523
15610
|
const ref = (r) => ({ effect: r.effect, ...r.name ? { name: r.name } : {}, ...r.provider ? { provider: r.provider } : {} });
|
|
15524
|
-
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 } : {} });
|
|
15611
|
+
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 } : {} });
|
|
15525
15612
|
if (layer.type === "smart")
|
|
15526
15613
|
(layer.smartFilters?.filters ?? []).forEach((f, index) => {
|
|
15527
15614
|
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 });
|
|
@@ -15590,13 +15677,14 @@ function createImageEffectsToolProvider(controller, options = {}) {
|
|
|
15590
15677
|
{
|
|
15591
15678
|
name: "add_effect_layer",
|
|
15592
15679
|
title: "Add an effect layer",
|
|
15593
|
-
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. 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.`,
|
|
15680
|
+
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.`,
|
|
15594
15681
|
inputSchema: {
|
|
15595
15682
|
type: "object",
|
|
15596
15683
|
properties: {
|
|
15597
15684
|
effect: ctx.effectIdSchema(["generator", "overlay", "title"]),
|
|
15598
15685
|
values: ctx.valuesSchema("Control values over the effect's defaults"),
|
|
15599
|
-
time: { type: "number", minimum: 0, description: "Which moment of an animated effect to draw, in ms (default 0)." },
|
|
15686
|
+
time: { type: "number", minimum: 0, description: "Which moment of an animated effect to draw, in ms (default 0); where its animation starts." },
|
|
15687
|
+
animate: ANIMATE_SCHEMA,
|
|
15600
15688
|
name: { type: "string", minLength: 1, description: "The layer name (default: the effect's name)." },
|
|
15601
15689
|
aboveId: { type: "string", minLength: 1, description: "Put it directly above this layer (default: above the active layer, else on top)." },
|
|
15602
15690
|
belowId: { type: "string", minLength: 1, description: "Put it directly below this layer." },
|
|
@@ -15609,9 +15697,34 @@ function createImageEffectsToolProvider(controller, options = {}) {
|
|
|
15609
15697
|
annotations: { title: "Add an effect layer", ...ctx.annotations.write },
|
|
15610
15698
|
handler: async (args) => {
|
|
15611
15699
|
ctx.requireProvider();
|
|
15612
|
-
const id = await controller.addEffectLayer(args.effect, { values: plainValues(args.values), time: args.time, name: args.name, aboveId: args.aboveId, belowId: args.belowId, opacity: args.opacity, blend: args.blend });
|
|
15700
|
+
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 });
|
|
15613
15701
|
return { layerId: id, effects: imageLayerAppliedEffects(layerOf(id)) };
|
|
15614
15702
|
}
|
|
15703
|
+
},
|
|
15704
|
+
{
|
|
15705
|
+
name: "set_effect_layer",
|
|
15706
|
+
title: "Change an effect layer",
|
|
15707
|
+
description: 'Changes an effect layer (list_applied_effects shows its effect as "generator"): effect swaps in another generator, overlay or title effect (its defaults plus values, the background transparent when there are layers below); time picks the still frame of an animated effect; animate { duration } makes it play when the document is output as an animation, null makes it a still frame again. Redraws its pixels (painting on it is lost). One undo step. Returns its effects.',
|
|
15708
|
+
inputSchema: {
|
|
15709
|
+
type: "object",
|
|
15710
|
+
properties: {
|
|
15711
|
+
layerId: { type: "string", minLength: 1, description: "The effect layer's id (list_layers)." },
|
|
15712
|
+
effect: ctx.effectIdSchema(["generator", "overlay", "title"]),
|
|
15713
|
+
values: ctx.valuesSchema("Control values: over the new effect's defaults when effect is given, else over the layer's own (null resets one)"),
|
|
15714
|
+
time: { type: "number", minimum: 0, description: "The moment drawn as the still frame, in ms; where the animation starts." },
|
|
15715
|
+
animate: { anyOf: [ANIMATE_SCHEMA, { type: "null" }], description: "Play for this long in an animation, or null for a still frame." }
|
|
15716
|
+
},
|
|
15717
|
+
required: ["layerId"],
|
|
15718
|
+
additionalProperties: false
|
|
15719
|
+
},
|
|
15720
|
+
annotations: { title: "Change an effect layer", ...ctx.annotations.write },
|
|
15721
|
+
handler: async (args) => {
|
|
15722
|
+
if (args.effect !== void 0) ctx.requireProvider();
|
|
15723
|
+
const layer = layerOf(args.layerId);
|
|
15724
|
+
if (layer.type !== "pixel" || !layer.generator) throw new Error(`"${args.layerId}" is not an effect layer; its effects are in list_applied_effects, and add_effect_layer adds one.`);
|
|
15725
|
+
await controller.updateEffectLayer(args.layerId, { effect: args.effect, values: args.values, time: args.time, animate: args.animate });
|
|
15726
|
+
return { layerId: args.layerId, effects: imageLayerAppliedEffects(layerOf(args.layerId)) };
|
|
15727
|
+
}
|
|
15615
15728
|
}
|
|
15616
15729
|
]
|
|
15617
15730
|
}), options.playbackSurface);
|
|
@@ -15752,6 +15865,7 @@ export {
|
|
|
15752
15865
|
MAX_BATCH,
|
|
15753
15866
|
MAX_BRUSH_SIZE,
|
|
15754
15867
|
MAX_CANVAS_BACKING_SIDE,
|
|
15868
|
+
MAX_EFFECT_ANIMATION_MS,
|
|
15755
15869
|
MAX_EFFECT_REACH,
|
|
15756
15870
|
MAX_KELVIN,
|
|
15757
15871
|
MAX_TILT_ACCEL,
|
|
@@ -15972,6 +16086,8 @@ export {
|
|
|
15972
16086
|
canvasToImagePoint,
|
|
15973
16087
|
caretAt,
|
|
15974
16088
|
changedTiles,
|
|
16089
|
+
checkEffectAnimation,
|
|
16090
|
+
checkEffectTime,
|
|
15975
16091
|
checkImageEffectPlace,
|
|
15976
16092
|
clampControlValue,
|
|
15977
16093
|
clampCropToImage,
|
|
@@ -16099,6 +16215,7 @@ export {
|
|
|
16099
16215
|
edgeWidth,
|
|
16100
16216
|
effectColorHex,
|
|
16101
16217
|
effectControlChange,
|
|
16218
|
+
effectLayerTiming,
|
|
16102
16219
|
effectPixelsToSurface,
|
|
16103
16220
|
effectReach,
|
|
16104
16221
|
effectiveProof,
|
|
@@ -16158,6 +16275,7 @@ export {
|
|
|
16158
16275
|
guidedFilterColor,
|
|
16159
16276
|
handlePaintShortcut,
|
|
16160
16277
|
hasActiveEffects,
|
|
16278
|
+
hasLayersBelow,
|
|
16161
16279
|
hasResampledLegacyMask,
|
|
16162
16280
|
hasSurface,
|
|
16163
16281
|
hasWarp,
|
|
@@ -16606,6 +16724,7 @@ export {
|
|
|
16606
16724
|
wetSupport,
|
|
16607
16725
|
whiteBalanceFromSample,
|
|
16608
16726
|
whiteBalanceMatrix,
|
|
16727
|
+
withBackgroundOpacity,
|
|
16609
16728
|
withFilterMaskSurface,
|
|
16610
16729
|
withImagePlayback,
|
|
16611
16730
|
withNewIds,
|
|
@@ -68,8 +68,22 @@ export interface ImageEffectGenerator {
|
|
|
68
68
|
name?: string;
|
|
69
69
|
/** Control values (over the effect's defaults). */
|
|
70
70
|
values: Record<string, EffectValue>;
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* The moment of the effect drawn (ms, default 0): the still frame, and where an animation
|
|
73
|
+
* starts.
|
|
74
|
+
*/
|
|
72
75
|
time?: number;
|
|
76
|
+
/**
|
|
77
|
+
* The layer plays when the document is output as an animation (`renderAnimationFrame`),
|
|
78
|
+
* for `duration` ms from `time`. Absent: the layer is the still frame at `time`.
|
|
79
|
+
*/
|
|
80
|
+
animate?: ImageEffectAnimation;
|
|
81
|
+
}
|
|
82
|
+
/** The longest an effect layer animates (ms): a minute. */
|
|
83
|
+
export declare const MAX_EFFECT_ANIMATION_MS = 60000;
|
|
84
|
+
/** How an animated effect layer plays: for `duration` ms (more than 0, at most `MAX_EFFECT_ANIMATION_MS`). */
|
|
85
|
+
export interface ImageEffectAnimation {
|
|
86
|
+
duration: number;
|
|
73
87
|
}
|
|
74
88
|
export interface PixelLayer extends LayerBase, Omit<RasterPixelLayer, keyof RasterLayerBase> {
|
|
75
89
|
/**
|
|
@@ -68,8 +68,22 @@ export interface ImageEffectGenerator {
|
|
|
68
68
|
name?: string;
|
|
69
69
|
/** Control values (over the effect's defaults). */
|
|
70
70
|
values: Record<string, EffectValue>;
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* The moment of the effect drawn (ms, default 0): the still frame, and where an animation
|
|
73
|
+
* starts.
|
|
74
|
+
*/
|
|
72
75
|
time?: number;
|
|
76
|
+
/**
|
|
77
|
+
* The layer plays when the document is output as an animation (`renderAnimationFrame`),
|
|
78
|
+
* for `duration` ms from `time`. Absent: the layer is the still frame at `time`.
|
|
79
|
+
*/
|
|
80
|
+
animate?: ImageEffectAnimation;
|
|
81
|
+
}
|
|
82
|
+
/** The longest an effect layer animates (ms): a minute. */
|
|
83
|
+
export declare const MAX_EFFECT_ANIMATION_MS = 60000;
|
|
84
|
+
/** How an animated effect layer plays: for `duration` ms (more than 0, at most `MAX_EFFECT_ANIMATION_MS`). */
|
|
85
|
+
export interface ImageEffectAnimation {
|
|
86
|
+
duration: number;
|
|
73
87
|
}
|
|
74
88
|
export interface PixelLayer extends LayerBase, Omit<RasterPixelLayer, keyof RasterLayerBase> {
|
|
75
89
|
/**
|
|
@@ -110,8 +110,12 @@ export interface LayerPanelDescriptor {
|
|
|
110
110
|
}
|
|
111
111
|
export interface LayerPanelsOptions {
|
|
112
112
|
/**
|
|
113
|
-
* The editor has an effects provider (`controller.effectsProvider`):
|
|
114
|
-
* panel
|
|
113
|
+
* The editor has an effects provider (`controller.effectsProvider`): one `provider-effects`
|
|
114
|
+
* panel ("Effects") holds everything that changes how a layer looks beyond its own settings:
|
|
115
|
+
* an effect layer's effect (change it, its frame or animation, its controls), and the layer's
|
|
116
|
+
* styles (drop shadow, stroke…) and filters. It takes the place of the `effects` (Layer
|
|
117
|
+
* Effects) and `smart-filters` panels. New effect layers are added from the layers panel
|
|
118
|
+
* (`newLayerOptions`).
|
|
115
119
|
*/
|
|
116
120
|
effects?: boolean;
|
|
117
121
|
}
|
|
@@ -110,8 +110,12 @@ export interface LayerPanelDescriptor {
|
|
|
110
110
|
}
|
|
111
111
|
export interface LayerPanelsOptions {
|
|
112
112
|
/**
|
|
113
|
-
* The editor has an effects provider (`controller.effectsProvider`):
|
|
114
|
-
* panel
|
|
113
|
+
* The editor has an effects provider (`controller.effectsProvider`): one `provider-effects`
|
|
114
|
+
* panel ("Effects") holds everything that changes how a layer looks beyond its own settings:
|
|
115
|
+
* an effect layer's effect (change it, its frame or animation, its controls), and the layer's
|
|
116
|
+
* styles (drop shadow, stroke…) and filters. It takes the place of the `effects` (Layer
|
|
117
|
+
* Effects) and `smart-filters` panels. New effect layers are added from the layers panel
|
|
118
|
+
* (`newLayerOptions`).
|
|
115
119
|
*/
|
|
116
120
|
effects?: boolean;
|
|
117
121
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Agent tools for provider effects in the image editor: the shared effect tools
|
|
3
3
|
* (`search_effects`, `describe_effect`, `list_applied_effects`, `apply_effect`, `update_effect`,
|
|
4
4
|
* `remove_effect`) on layers, where filter effects become smart filters, plus `add_effect_layer`
|
|
5
|
-
* for generator, overlay and title effects
|
|
5
|
+
* for generator, overlay and title effects and `set_effect_layer` to change one (its effect, still
|
|
6
|
+
* frame or animation). (Drop shadows, strokes and the other layer styles are
|
|
6
7
|
* `set_layer_effect`, a different thing.)
|
|
7
8
|
*/
|
|
8
9
|
import { type AppliedEffectInfo } from '../effects/tools.cjs';
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Agent tools for provider effects in the image editor: the shared effect tools
|
|
3
3
|
* (`search_effects`, `describe_effect`, `list_applied_effects`, `apply_effect`, `update_effect`,
|
|
4
4
|
* `remove_effect`) on layers, where filter effects become smart filters, plus `add_effect_layer`
|
|
5
|
-
* for generator, overlay and title effects
|
|
5
|
+
* for generator, overlay and title effects and `set_effect_layer` to change one (its effect, still
|
|
6
|
+
* frame or animation). (Drop shadows, strokes and the other layer styles are
|
|
6
7
|
* `set_layer_effect`, a different thing.)
|
|
7
8
|
*/
|
|
8
9
|
import { type AppliedEffectInfo } from '../effects/tools.js';
|
|
@@ -7,7 +7,7 @@ var _chunkZ64NRDEHcjs = require('../chunk-Z64NRDEH.cjs');
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _chunkUHMSMUHKcjs = require('../chunk-UHMSMUHK.cjs');
|
|
11
11
|
require('../chunk-A2IS7EHE.cjs');
|
|
12
12
|
|
|
13
13
|
|
|
@@ -1653,11 +1653,11 @@ function header(state, options) {
|
|
|
1653
1653
|
return out;
|
|
1654
1654
|
}
|
|
1655
1655
|
function serializePaintDocument(state, options = {}) {
|
|
1656
|
-
const { layers, assets } =
|
|
1656
|
+
const { layers, assets } = _chunkUHMSMUHKcjs.serializeLayers.call(void 0, state.layers);
|
|
1657
1657
|
return { ...header(state, options), layers, assets };
|
|
1658
1658
|
}
|
|
1659
1659
|
async function serializePaintDocumentExternal(state, store, options = {}) {
|
|
1660
|
-
const { layers, assets } = await
|
|
1660
|
+
const { layers, assets } = await _chunkUHMSMUHKcjs.serializeLayersExternal.call(void 0, state.layers, store);
|
|
1661
1661
|
return { ...header(state, options), layers, assets };
|
|
1662
1662
|
}
|
|
1663
1663
|
function paintLayersOnly(layers, problems) {
|
|
@@ -1694,7 +1694,7 @@ function deserializePaintDocument(raw) {
|
|
|
1694
1694
|
const canvas = normalizeCanvas({ width: r.width, height: r.height, dpi: _nullishCoalesce(r.dpi, () => ( void 0)) });
|
|
1695
1695
|
const paper = _chunkYQ5C5BVKcjs.normalizePaper.call(void 0, _nullishCoalesce(r.paper, () => ( {})));
|
|
1696
1696
|
const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
|
|
1697
|
-
const layers = paintLayersOnly(
|
|
1697
|
+
const layers = paintLayersOnly(_chunkUHMSMUHKcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
|
|
1698
1698
|
const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
|
|
1699
1699
|
const activeLayerId = wanted && _chunkHGTDMKNEcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkHGTDMKNEcjs.flattenLayers.call(void 0, layers), 'access', _70 => _70.filter, 'call', _71 => _71((l) => l.type === "pixel"), 'access', _72 => _72.pop, 'call', _73 => _73(), 'optionalAccess', _74 => _74.id]), () => ( null));
|
|
1700
1700
|
const wetEvents = Array.isArray(r.wetEvents) ? r.wetEvents.filter((e) => e && typeof e === "object" && Number.isInteger(e.clock) && typeof e.kind === "string") : [];
|
|
@@ -1710,7 +1710,7 @@ async function deserializePaintDocumentAsync(raw, store) {
|
|
|
1710
1710
|
if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
|
|
1711
1711
|
const r = raw;
|
|
1712
1712
|
const problems = [];
|
|
1713
|
-
const assets = await
|
|
1713
|
+
const assets = await _chunkUHMSMUHKcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
|
|
1714
1714
|
const loaded = deserializePaintDocument({ ...r, assets });
|
|
1715
1715
|
return { ...loaded, problems: [...problems, ...loaded.problems] };
|
|
1716
1716
|
}
|
|
@@ -5409,7 +5409,7 @@ var PaintEditorController = (_class10 = class _PaintEditorController {
|
|
|
5409
5409
|
return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
|
|
5410
5410
|
});
|
|
5411
5411
|
const layers = toStandard(dried);
|
|
5412
|
-
const result =
|
|
5412
|
+
const result = _chunkUHMSMUHKcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
|
|
5413
5413
|
for (const [mode, names] of changed) {
|
|
5414
5414
|
const label = _nullishCoalesce(_optionalChain([_chunkCWM3CUUXcjs.BLEND_MODES, 'access', _271 => _271.find, 'call', _272 => _272((m) => m.id === mode), 'optionalAccess', _273 => _273.label]), () => ( mode));
|
|
5415
5415
|
const std = _nullishCoalesce(_optionalChain([_chunkCWM3CUUXcjs.BLEND_MODES, 'access', _274 => _274.find, 'call', _275 => _275((m) => m.id === _chunkCWM3CUUXcjs.standardBlendMode.call(void 0, mode)), 'optionalAccess', _276 => _276.label]), () => ( _chunkCWM3CUUXcjs.standardBlendMode.call(void 0, mode)));
|
package/package.json
CHANGED
|
@@ -390,7 +390,8 @@ in the source frame at the document's bit depth, colour mode and profile. Read
|
|
|
390
390
|
| `apply_effect` | Run a provider filter effect on a layer as a smart filter (`target`, `effect`, `values`, `index`, `enabled`, `opacity`); a pixel, shape or type layer becomes a smart object in the same undo step (`converted`). Returns the filter id. |
|
|
391
391
|
| `update_effect` | Change an effect smart filter (`values` merged, `null` resets; `enabled`, `opacity`, `index`) or redraw an effect layer (`effect: "generator"`, values only). |
|
|
392
392
|
| `remove_effect` | Remove an effect smart filter, or (`generator`) keep an effect layer's pixels as a plain layer. |
|
|
393
|
-
| `add_effect_layer` | Add a layer drawn by a generator, overlay or title effect at the document's size and bit depth (`values`, `time`, `name`, `aboveId` / `belowId`, `opacity`, `blend`). Returns the layer id. |
|
|
393
|
+
| `add_effect_layer` | Add a layer drawn by a generator, overlay or title effect at the document's size and bit depth (`values`, `time`, `animate`, `name`, `aboveId` / `belowId`, `opacity`, `blend`). An effect with its own background (`backgroundOpacity` in `describe_effect`) starts with it transparent when there are layers below. Returns the layer id. |
|
|
394
|
+
| `set_effect_layer` | Change an effect layer: swap its `effect` (its defaults plus `values`), pick the still frame (`time`, ms), or make it play in an animation (`animate: { duration }`; `null` for a still frame). The longest animating layer sets the animation's length. |
|
|
394
395
|
| `apply_selection_as_mask` | Mask a layer with the current selection: pass `layerId` to mask an existing layer, or `kind` (plus optional `params`) to create a new masked layer in one step. `density` 0..1 and `feather` px soften the mask non-destructively. Returns `layerId`, kind, mask info and the selection summary. |
|
|
395
396
|
|
|
396
397
|
### Save and open
|