@pixerate/editor 0.174.1 → 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.
Files changed (36) hide show
  1. package/dist/{chunk-OVYQAYGH.cjs → chunk-UHMSMUHK.cjs} +75 -66
  2. package/dist/{chunk-BDIM6NVW.js → chunk-Y46OMERN.js} +10 -1
  3. package/dist/effects/fake.d.cts +1 -1
  4. package/dist/effects/fake.d.ts +1 -1
  5. package/dist/effects/index.cjs +36 -22
  6. package/dist/effects/index.js +19 -5
  7. package/dist/effects/types.d.cts +6 -0
  8. package/dist/effects/types.d.ts +6 -0
  9. package/dist/effects/ui.d.cts +0 -6
  10. package/dist/effects/ui.d.ts +0 -6
  11. package/dist/image-editor/controller.d.cts +28 -5
  12. package/dist/image-editor/controller.d.ts +28 -5
  13. package/dist/image-editor/effect-layers.d.cts +40 -0
  14. package/dist/image-editor/effect-layers.d.ts +40 -0
  15. package/dist/image-editor/index.cjs +931 -812
  16. package/dist/image-editor/index.d.cts +1 -0
  17. package/dist/image-editor/index.d.ts +1 -0
  18. package/dist/image-editor/index.js +152 -33
  19. package/dist/image-editor/layers/model.d.cts +15 -1
  20. package/dist/image-editor/layers/model.d.ts +15 -1
  21. package/dist/image-editor/layers/panel.d.cts +6 -2
  22. package/dist/image-editor/layers/panel.d.ts +6 -2
  23. package/dist/image-editor/provider-effect-tools.d.cts +2 -1
  24. package/dist/image-editor/provider-effect-tools.d.ts +2 -1
  25. package/dist/paint-editor/index.cjs +6 -6
  26. package/dist/paint-editor/index.js +1 -1
  27. package/dist/parameters/index.cjs +33 -1
  28. package/dist/parameters/index.d.cts +2 -0
  29. package/dist/parameters/index.d.ts +2 -0
  30. package/dist/parameters/index.js +32 -0
  31. package/dist/parameters/options.d.cts +7 -0
  32. package/dist/parameters/options.d.ts +7 -0
  33. package/dist/parameters/theme.d.cts +30 -0
  34. package/dist/parameters/theme.d.ts +30 -0
  35. package/package.json +1 -1
  36. 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-BDIM6NVW.js";
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. Returns its id.
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: options.id && !ids.has(options.id) ? options.id : newLayerId(),
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, ...options.time ? { time: options.time } : {} }
4959
+ generator: { effect: d.id, provider: this.effects.require().id, name: d.name, values, ...time ? { time } : {}, ...animate ? { animate } : {} }
4923
4960
  };
4924
- const next = placeLayer(this.state.layers, layer, this.newLayerPlacement(options));
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 with new `values` (merged over its own; `null` resets a control)
4933
- * or `time`, at the document's size and depth, replacing its pixels (painting on it is lost).
4934
- * One undo step (none with `commitToHistory` false). Returns its effect settings.
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 d = await this.describeEffect(g.effect);
4943
- const base = { ...g.values };
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 ?? g.time ?? 0;
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 = { ...g, values, provider: this.effects.require().id, ...time ? { time } : {} };
4955
- if (!time) delete generator.time;
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
- { id: "properties", title: "Layer Properties" },
14644
- ...effects
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
- panels.push({ id: "effects", title: "Layer Effects" });
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
- panels.push({ id: "effects", title: "Layer Effects" });
14728
+ effects();
14729
+ styles("effects", "Layer Effects");
14654
14730
  } else if (layer.type === "shape") {
14655
14731
  panels.push({ id: "shape", title: "Shape Options" });
14656
- panels.push({ id: "effects", title: "Layer Effects" });
14732
+ effects();
14733
+ styles("effects", "Layer Effects");
14657
14734
  } else if (layer.type === "smart") {
14658
- panels.push({ id: "smart-filters", title: "Smart Filters" });
14659
- panels.push({ id: "effects", title: "Layer Effects" });
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
- /** The moment of the effect drawn (ms, default 0). */
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
- /** The moment of the effect drawn (ms, default 0). */
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`): adds the `provider-effects`
114
- * panel, to add effect layers and to apply effects to the layer or change an effect layer's own.
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`): adds the `provider-effects`
114
- * panel, to add effect layers and to apply effects to the layer or change an effect layer's own.
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. (Drop shadows, strokes and the other layer styles are
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. (Drop shadows, strokes and the other layer styles are
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 _chunkOVYQAYGHcjs = require('../chunk-OVYQAYGH.cjs');
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 } = _chunkOVYQAYGHcjs.serializeLayers.call(void 0, state.layers);
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 _chunkOVYQAYGHcjs.serializeLayersExternal.call(void 0, state.layers, store);
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(_chunkOVYQAYGHcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
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 _chunkOVYQAYGHcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
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 = _chunkOVYQAYGHcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
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)));
@@ -7,7 +7,7 @@ import {
7
7
  resolveExternalAssets,
8
8
  serializeLayers,
9
9
  serializeLayersExternal
10
- } from "../chunk-BDIM6NVW.js";
10
+ } from "../chunk-Y46OMERN.js";
11
11
  import "../chunk-3CI7FRMK.js";
12
12
  import {
13
13
  EffectRendererPool
@@ -1359,6 +1359,38 @@ function createParametersEditorDocument(controller) {
1359
1359
  });
1360
1360
  }
1361
1361
 
1362
+ // src/parameters/theme.ts
1363
+ var parameterThemeStyles = {
1364
+ input: {
1365
+ backgroundColor: "var(--pixerate-parameter-input-bg, var(--background, transparent))",
1366
+ color: "var(--pixerate-parameter-input-fg, var(--foreground, currentColor))",
1367
+ borderColor: "var(--pixerate-parameter-border, var(--input, var(--border, currentColor)))"
1368
+ },
1369
+ select: {
1370
+ backgroundColor: "var(--pixerate-parameter-select-bg, var(--pixerate-parameter-input-bg, var(--background, transparent)))",
1371
+ color: "var(--pixerate-parameter-select-fg, var(--pixerate-parameter-input-fg, var(--foreground, currentColor)))",
1372
+ borderColor: "var(--pixerate-parameter-border, var(--input, var(--border, currentColor)))"
1373
+ },
1374
+ button: {
1375
+ color: "var(--pixerate-parameter-button-fg, var(--foreground, currentColor))",
1376
+ borderColor: "var(--pixerate-parameter-border, var(--input, var(--border, currentColor)))"
1377
+ },
1378
+ range: {
1379
+ accentColor: "var(--pixerate-parameter-accent, var(--primary, currentColor))"
1380
+ }
1381
+ };
1382
+ function parameterThemeStyleText(part) {
1383
+ return Object.entries(parameterThemeStyles[part]).map(([key, value]) => `${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}: ${value};`).join(" ");
1384
+ }
1385
+
1386
+ // src/parameters/options.ts
1387
+ function matchParameterOptionValue(options, value) {
1388
+ return _nullishCoalesce(_nullishCoalesce(_optionalChain([options, 'access', _22 => _22.find, 'call', _23 => _23((o) => o.value === value), 'optionalAccess', _24 => _24.value]), () => ( _optionalChain([options, 'access', _25 => _25.find, 'call', _26 => _26((o) => String(o.value) === String(value)), 'optionalAccess', _27 => _27.value]))), () => ( value));
1389
+ }
1390
+
1391
+
1392
+
1393
+
1362
1394
 
1363
1395
 
1364
1396
 
@@ -1409,4 +1441,4 @@ function createParametersEditorDocument(controller) {
1409
1441
 
1410
1442
 
1411
1443
 
1412
- exports.PARAMETER_KEY_TOLERANCE = PARAMETER_KEY_TOLERANCE; exports.ParameterSchemaError = ParameterSchemaError; exports.ParametersController = ParametersController; exports.checkParameterSchema = checkParameterSchema; exports.choiceOptionLabel = choiceOptionLabel; exports.clampParameterNumber = clampParameterNumber; exports.cloneValue = cloneValue; exports.coerceParameterValue = coerceParameterValue; exports.createParameterCommands = createParameterCommands; exports.createParameterKeyframeStore = createParameterKeyframeStore; exports.createParametersController = createParametersController; exports.createParametersEditorDocument = createParametersEditorDocument; exports.createRangeMapper = createRangeMapper; exports.evaluateParameterCondition = evaluateParameterCondition; exports.findParameterKeyframe = findParameterKeyframe; exports.formatParameterNumber = formatParameterNumber; exports.formatParameterValue = formatParameterValue; exports.getChoiceOptions = getChoiceOptions; exports.getParameterDefaults = getParameterDefaults; exports.getParameterFields = getParameterFields; exports.getParameterGroups = getParameterGroups; exports.getParameterKeyState = getParameterKeyState; exports.getParameterVisibility = getParameterVisibility; exports.hexToRgba01 = hexToRgba01; exports.humanizeParameterId = humanizeParameterId; exports.isParameterGroup = isParameterGroup; exports.isSliderField = isSliderField; exports.mapRange = mapRange; exports.normalizeHexColor = normalizeHexColor; exports.parameterDecimals = parameterDecimals; exports.parameterFieldFromShaderControl = parameterFieldFromShaderControl; exports.parameterFieldToJsonSchema = parameterFieldToJsonSchema; exports.parameterLabel = parameterLabel; exports.parameterSchemaFromShaderControls = parameterSchemaFromShaderControls; exports.parameterSchemaToJsonSchema = parameterSchemaToJsonSchema; exports.parameterStep = parameterStep; exports.parameterValueFromShaderControl = parameterValueFromShaderControl; exports.parameterValuesEqual = parameterValuesEqual; exports.parseParameterNumberInput = parseParameterNumberInput; exports.planParameterSetPlayback = planParameterSetPlayback; exports.rgba01ToHex = rgba01ToHex; exports.seededRandom = seededRandom; exports.shaderControlValue = shaderControlValue; exports.validateParameterValue = validateParameterValue; exports.validateParameterValues = validateParameterValues; exports.vectorComponentField = vectorComponentField; exports.vectorSize = vectorSize; exports.withParameterKeyframe = withParameterKeyframe; exports.withParameterPlayback = withParameterPlayback; exports.withoutParameterKeyframe = withoutParameterKeyframe;
1444
+ exports.PARAMETER_KEY_TOLERANCE = PARAMETER_KEY_TOLERANCE; exports.ParameterSchemaError = ParameterSchemaError; exports.ParametersController = ParametersController; exports.checkParameterSchema = checkParameterSchema; exports.choiceOptionLabel = choiceOptionLabel; exports.clampParameterNumber = clampParameterNumber; exports.cloneValue = cloneValue; exports.coerceParameterValue = coerceParameterValue; exports.createParameterCommands = createParameterCommands; exports.createParameterKeyframeStore = createParameterKeyframeStore; exports.createParametersController = createParametersController; exports.createParametersEditorDocument = createParametersEditorDocument; exports.createRangeMapper = createRangeMapper; exports.evaluateParameterCondition = evaluateParameterCondition; exports.findParameterKeyframe = findParameterKeyframe; exports.formatParameterNumber = formatParameterNumber; exports.formatParameterValue = formatParameterValue; exports.getChoiceOptions = getChoiceOptions; exports.getParameterDefaults = getParameterDefaults; exports.getParameterFields = getParameterFields; exports.getParameterGroups = getParameterGroups; exports.getParameterKeyState = getParameterKeyState; exports.getParameterVisibility = getParameterVisibility; exports.hexToRgba01 = hexToRgba01; exports.humanizeParameterId = humanizeParameterId; exports.isParameterGroup = isParameterGroup; exports.isSliderField = isSliderField; exports.mapRange = mapRange; exports.matchParameterOptionValue = matchParameterOptionValue; exports.normalizeHexColor = normalizeHexColor; exports.parameterDecimals = parameterDecimals; exports.parameterFieldFromShaderControl = parameterFieldFromShaderControl; exports.parameterFieldToJsonSchema = parameterFieldToJsonSchema; exports.parameterLabel = parameterLabel; exports.parameterSchemaFromShaderControls = parameterSchemaFromShaderControls; exports.parameterSchemaToJsonSchema = parameterSchemaToJsonSchema; exports.parameterStep = parameterStep; exports.parameterThemeStyleText = parameterThemeStyleText; exports.parameterThemeStyles = parameterThemeStyles; exports.parameterValueFromShaderControl = parameterValueFromShaderControl; exports.parameterValuesEqual = parameterValuesEqual; exports.parseParameterNumberInput = parseParameterNumberInput; exports.planParameterSetPlayback = planParameterSetPlayback; exports.rgba01ToHex = rgba01ToHex; exports.seededRandom = seededRandom; exports.shaderControlValue = shaderControlValue; exports.validateParameterValue = validateParameterValue; exports.validateParameterValues = validateParameterValues; exports.vectorComponentField = vectorComponentField; exports.vectorSize = vectorSize; exports.withParameterKeyframe = withParameterKeyframe; exports.withParameterPlayback = withParameterPlayback; exports.withoutParameterKeyframe = withoutParameterKeyframe;
@@ -7,3 +7,5 @@ export * from './shader.cjs';
7
7
  export * from './rangeMapper.cjs';
8
8
  export * from './playback.cjs';
9
9
  export * from './editorDocument.cjs';
10
+ export * from './theme.cjs';
11
+ export * from './options.cjs';
@@ -7,3 +7,5 @@ export * from './shader.js';
7
7
  export * from './rangeMapper.js';
8
8
  export * from './playback.js';
9
9
  export * from './editorDocument.js';
10
+ export * from './theme.js';
11
+ export * from './options.js';