@pixerate/editor 0.149.0 → 0.150.1

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 (38) hide show
  1. package/dist/agent/index.cjs +7 -5
  2. package/dist/agent/index.js +5 -3
  3. package/dist/animation/index.cjs +4 -2
  4. package/dist/animation/index.js +3 -1
  5. package/dist/chart/index.cjs +7 -7
  6. package/dist/chart/index.js +1 -1
  7. package/dist/{chunk-3EVMUYF4.js → chunk-6DXDUVSL.js} +1 -1
  8. package/dist/{chunk-ZCFHPORF.cjs → chunk-ERAGQL2N.cjs} +2 -2
  9. package/dist/{chunk-FR7GHH2K.cjs → chunk-GMWNBSBS.cjs} +3 -3
  10. package/dist/{chunk-OOV64UEB.js → chunk-KMU6QJB7.js} +1 -1
  11. package/dist/{chunk-PEEP2YR7.js → chunk-O2ANJFUF.js} +23 -1
  12. package/dist/{chunk-QCEQIRJQ.cjs → chunk-PI45JMP7.cjs} +163 -136
  13. package/dist/{chunk-UJGAA6AK.cjs → chunk-SWTUM32H.cjs} +23 -1
  14. package/dist/{chunk-ZQUANEK4.js → chunk-Y64YPIFT.js} +28 -1
  15. package/dist/commands/index.cjs +3 -3
  16. package/dist/commands/index.js +2 -2
  17. package/dist/diagram/index.cjs +7 -7
  18. package/dist/diagram/index.js +1 -1
  19. package/dist/image-editor/canvas-viewport.d.cts +52 -0
  20. package/dist/image-editor/canvas-viewport.d.ts +52 -0
  21. package/dist/image-editor/index.cjs +687 -596
  22. package/dist/image-editor/index.d.cts +1 -0
  23. package/dist/image-editor/index.d.ts +1 -0
  24. package/dist/image-editor/index.js +120 -29
  25. package/dist/image-editor/layers/ops.d.cts +21 -3
  26. package/dist/image-editor/layers/ops.d.ts +21 -3
  27. package/dist/image-editor/playback.d.cts +20 -1
  28. package/dist/image-editor/playback.d.ts +20 -1
  29. package/dist/image-editor/renderer.d.cts +7 -0
  30. package/dist/image-editor/renderer.d.ts +7 -0
  31. package/dist/paint-editor/index.cjs +12 -12
  32. package/dist/paint-editor/index.js +2 -2
  33. package/dist/tools/index.cjs +6 -4
  34. package/dist/tools/index.js +5 -3
  35. package/dist/tools/presence.d.cts +17 -0
  36. package/dist/tools/presence.d.ts +17 -0
  37. package/package.json +2 -2
  38. package/skills/pixerate-image-editor/SKILL.md +6 -6
@@ -4,6 +4,7 @@ export * from './color.cjs';
4
4
  export * from './renderer.cjs';
5
5
  export * from './controller.cjs';
6
6
  export * from './geometry.cjs';
7
+ export * from './canvas-viewport.cjs';
7
8
  export * from './document.cjs';
8
9
  export * from './blend.cjs';
9
10
  export * from './adjustment-layers.cjs';
@@ -4,6 +4,7 @@ export * from './color.js';
4
4
  export * from './renderer.js';
5
5
  export * from './controller.js';
6
6
  export * from './geometry.js';
7
+ export * from './canvas-viewport.js';
7
8
  export * from './document.js';
8
9
  export * from './blend.js';
9
10
  export * from './adjustment-layers.js';
@@ -52,6 +52,7 @@ import {
52
52
  XYZ_D50_TO_SRGB,
53
53
  activeAdjustmentLayers,
54
54
  activeEffectSmartFilters,
55
+ activeLayerAfterRemoval,
55
56
  activeSmartFilters,
56
57
  addLayerEffect,
57
58
  addLayerFilter,
@@ -117,6 +118,7 @@ import {
117
118
  decodePlanes,
118
119
  decodePngDataUrl,
119
120
  decodeSelectionRle,
121
+ defaultActiveLayerId,
120
122
  defaultAdjustmentParams,
121
123
  defaultShapeName,
122
124
  depthMaskAlpha,
@@ -178,6 +180,7 @@ import {
178
180
  isDefaultDepthMask,
179
181
  isIdentityTransform,
180
182
  isLegacyShape,
183
+ isPaintableLayer,
181
184
  isQuarterTurnSwapped,
182
185
  isRenderedLayer,
183
186
  isSelectionEmpty,
@@ -348,6 +351,7 @@ import {
348
351
  updateShapeLayer,
349
352
  updateSmartFilter,
350
353
  updateTypeLayer,
354
+ userErrorMessage,
351
355
  uvToXy,
352
356
  versionedDescriptorBytes,
353
357
  vibranceLinear,
@@ -363,7 +367,7 @@ import {
363
367
  writePsd,
364
368
  xyToTempTint,
365
369
  xyToUv
366
- } from "../chunk-ZQUANEK4.js";
370
+ } from "../chunk-Y64YPIFT.js";
367
371
  import {
368
372
  parseCssColor
369
373
  } from "../chunk-3CI7FRMK.js";
@@ -1245,8 +1249,10 @@ var ImageEditorRenderer = class {
1245
1249
  const { crop, transform, adjustments, depthMask, annotations, selectedAnnotationId, zoom, pan, isOriginalCompared } = state;
1246
1250
  const vpWidth = options.viewportWidth || targetCanvas.width;
1247
1251
  const vpHeight = options.viewportHeight || targetCanvas.height;
1248
- targetCanvas.width = vpWidth;
1249
- targetCanvas.height = vpHeight;
1252
+ const ratio = options.pixelRatio && Number.isFinite(options.pixelRatio) && options.pixelRatio > 0 ? options.pixelRatio : 1;
1253
+ targetCanvas.width = Math.max(1, Math.round(vpWidth * ratio));
1254
+ targetCanvas.height = Math.max(1, Math.round(vpHeight * ratio));
1255
+ if (ratio !== 1 && typeof ctx.setTransform === "function") ctx.setTransform(targetCanvas.width / vpWidth, 0, 0, targetCanvas.height / vpHeight, 0, 0);
1250
1256
  ctx.clearRect(0, 0, vpWidth, vpHeight);
1251
1257
  if (isOriginalCompared) {
1252
1258
  this.drawFittedImage(ctx, img, naturalWidth, naturalHeight, vpWidth, vpHeight, zoom, pan, fit);
@@ -4901,8 +4907,8 @@ var ImageEditorController = class {
4901
4907
  this.state.layers = next;
4902
4908
  extra?.();
4903
4909
  if (depth !== 8 && this.bitDepth === depth) this.state.layers = keepDeepLayers(prev, this.state.layers);
4904
- if (this.state.activeLayerId && !findLayer(next, this.state.activeLayerId)) {
4905
- this.state.activeLayerId = null;
4910
+ if (this.state.activeLayerId && !findLayer(this.state.layers, this.state.activeLayerId)) {
4911
+ this.state.activeLayerId = activeLayerAfterRemoval(prev, this.state.layers, this.state.activeLayerId);
4906
4912
  this.state.activeTarget = "pixels";
4907
4913
  }
4908
4914
  this.notify();
@@ -6126,27 +6132,28 @@ var ImageEditorController = class {
6126
6132
  /** Resolves and validates what a stroke, fill or clear paints into. */
6127
6133
  paintTarget(layerId, channel, action) {
6128
6134
  const id = layerId ?? this.state.activeLayerId;
6129
- if (!id) throw new LayerOperationError(`No layer given and none is active; pass layerId (see list_layers) to ${action}.`);
6135
+ if (!id) throw new LayerOperationError(`No layer given and none is active; pass layerId (see list_layers) to ${action}.`, `Pick a layer to ${action} on in the Layers panel.`);
6130
6136
  const layer = requireLayer(this.state.layers, id).layer;
6137
+ const name = layer.name || id;
6131
6138
  const ch = channel ?? (id === this.state.activeLayerId ? this.state.activeTarget : "pixels");
6132
6139
  if (ch === "mask") {
6133
- if (!layer.mask) throw new LayerOperationError(`Layer "${layer.name || id}" has no mask to ${action}; add one with add_layer_mask, or target pixels.`);
6134
- if (layer.mask.source) throw new LayerOperationError(`Layer "${layer.name || id}" has a depth mask, which is generated, not painted.`);
6140
+ if (!layer.mask) throw new LayerOperationError(`Layer "${name}" has no mask to ${action}; add one with add_layer_mask, or target pixels.`, `Layer "${name}" has no mask; add a mask or ${action} on its pixels.`);
6141
+ if (layer.mask.source) throw new LayerOperationError(`Layer "${name}" has a depth mask, which is generated, not painted.`);
6135
6142
  requireUnlocked(layer, "all", action);
6136
6143
  } else if (ch === "filter-mask") {
6137
6144
  if (layer.type !== "smart" || !layer.smartFilters?.mask) {
6138
- throw new LayerOperationError(`Layer "${layer.name || id}" has no smart filter mask to ${action}; add one with set_smart_filter_mask, or target pixels or mask.`);
6145
+ throw new LayerOperationError(`Layer "${name}" has no smart filter mask to ${action}; add one with set_smart_filter_mask, or target pixels or mask.`, `Layer "${name}" has no smart filter mask to ${action}.`);
6139
6146
  }
6140
- if (!isContentsDecoded(layer.contents)) throw new LayerOperationError(`The contents of "${layer.name || id}" are not decoded here, so its filter mask cannot be painted; run prepareSmartObjects() first.`);
6147
+ if (!isContentsDecoded(layer.contents)) throw new LayerOperationError(`The contents of "${name}" are not decoded here, so its filter mask cannot be painted; run prepareSmartObjects() first.`, `The smart object "${name}" is still loading; try again in a moment.`);
6141
6148
  requireUnlocked(layer, "all", action);
6142
6149
  } else {
6143
- if (layer.type === "smart") throw new LayerOperationError(`Layer "${layer.name || id}" is a smart object; its pixels come from its contents. Rasterize it (rasterize_smart_object), edit its contents (edit_smart_object_contents), or ${action} on its mask or filter mask.`);
6144
- if (layer.type === "shape") throw new LayerOperationError(`Layer "${layer.name || id}" is a shape layer; its pixels are drawn from its paths. Rasterize it first (rasterize_layer), or ${action} on its mask (channel mask).`);
6145
- if (layer.type === "type") throw new LayerOperationError(`Layer "${layer.name || id}" is a type layer; its pixels are drawn from its text. Rasterize it first (rasterize_layer), or ${action} on its mask (channel mask).`);
6146
- if (layer.type !== "pixel") throw new LayerOperationError(`Layer "${layer.name || id}" is a ${layer.type} layer; ${action} on a pixel layer, or on this layer's mask (channel mask).`);
6150
+ if (layer.type === "smart") throw new LayerOperationError(`Layer "${name}" is a smart object; its pixels come from its contents. Rasterize it (rasterize_smart_object), edit its contents (edit_smart_object_contents), or ${action} on its mask or filter mask.`, `"${name}" is a smart object; rasterize it or edit its contents to ${action} on it.`);
6151
+ if (layer.type === "shape") throw new LayerOperationError(`Layer "${name}" is a shape layer; its pixels are drawn from its paths. Rasterize it first (rasterize_layer), or ${action} on its mask (channel mask).`, `"${name}" is a shape layer; rasterize it to ${action} on it.`);
6152
+ if (layer.type === "type") throw new LayerOperationError(`Layer "${name}" is a type layer; its pixels are drawn from its text. Rasterize it first (rasterize_layer), or ${action} on its mask (channel mask).`, `"${name}" is a type layer; rasterize it to ${action} on it.`);
6153
+ if (layer.type !== "pixel") throw new LayerOperationError(`Layer "${name}" is a ${layer.type} layer; ${action} on a pixel layer, or on this layer's mask (channel mask).`, `"${name}" is not a pixel layer; pick a pixel layer to ${action} on.`);
6147
6154
  requireUnlocked(layer, "pixels", action);
6148
6155
  }
6149
- if (!layer.visible) throw new LayerOperationError(`Layer "${layer.name || id}" is hidden; show it before you ${action}.`);
6156
+ if (!layer.visible) throw new LayerOperationError(`Layer "${name}" is hidden; show it before you ${action}.`);
6150
6157
  const offset = layer.type === "pixel" ? layer.offset : { x: 0, y: 0 };
6151
6158
  const surface = ch === "mask" ? layer.mask.surface : ch === "filter-mask" ? layer.smartFilters.mask.surface : this.surfaceOf(layer);
6152
6159
  const lockTransparency = ch === "pixels" && Boolean(layer.locks?.transparency);
@@ -6171,7 +6178,7 @@ var ImageEditorController = class {
6171
6178
  if (!STROKE_TOOLS.includes(tool)) throw new LayerOperationError(`Unknown paint tool "${String(tool)}"; use one of ${STROKE_TOOLS.join(", ")}.`);
6172
6179
  const target = this.paintTarget(init.layerId, init.channel, tool === "eraser" ? "erase" : isRetouchTool(tool) ? "retouch" : "paint");
6173
6180
  if (tool === "mixer" && target.channel !== "pixels") {
6174
- throw new LayerOperationError("The Mixer Brush paints colour on pixels, not on masks; target pixels (channel pixels), or paint the mask with the brush.");
6181
+ throw new LayerOperationError("The Mixer Brush paints colour on pixels, not on masks; target pixels (channel pixels), or paint the mask with the brush.", "The Mixer Brush paints on pixels, not masks; target the layer's pixels, or paint the mask with the Brush.");
6175
6182
  }
6176
6183
  const brush = normalizeBrush({ ...this.state.paint.brush, ...init.brush ?? {} });
6177
6184
  let strokePaint = null;
@@ -6650,8 +6657,12 @@ var ImageEditorController = class {
6650
6657
  const loc = findLayer(this.state.layers, id);
6651
6658
  if (!loc || loc.layer.type !== "adjustment") return false;
6652
6659
  this.recordHistory();
6653
- this.state.layers = mapLayer(this.state.layers, id, () => null);
6654
- if (this.state.activeLayerId === id) this.state.activeLayerId = null;
6660
+ const prev = this.state.layers;
6661
+ this.state.layers = mapLayer(prev, id, () => null);
6662
+ if (this.state.activeLayerId === id) {
6663
+ this.state.activeLayerId = activeLayerAfterRemoval(prev, this.state.layers, id);
6664
+ this.state.activeTarget = "pixels";
6665
+ }
6655
6666
  this.notify();
6656
6667
  return true;
6657
6668
  }
@@ -6689,10 +6700,15 @@ var ImageEditorController = class {
6689
6700
  const nodes = this.adjustmentNodes();
6690
6701
  if (!nodes.length) return;
6691
6702
  this.recordHistory();
6692
- let layers = this.state.layers;
6703
+ const prev = this.state.layers;
6704
+ let layers = prev;
6693
6705
  for (const n of nodes) layers = mapLayer(layers, n.id, () => null);
6694
6706
  this.state.layers = layers;
6695
- if (nodes.some((n) => n.id === this.state.activeLayerId)) this.state.activeLayerId = null;
6707
+ const active = this.state.activeLayerId;
6708
+ if (active && nodes.some((n) => n.id === active)) {
6709
+ this.state.activeLayerId = activeLayerAfterRemoval(prev, layers, active);
6710
+ this.state.activeTarget = "pixels";
6711
+ }
6696
6712
  this.notify();
6697
6713
  }
6698
6714
  // --- Selection ---
@@ -8009,8 +8025,7 @@ var ImageEditorController = class {
8009
8025
  delete s.legacyOutputBlur;
8010
8026
  s.selection = null;
8011
8027
  s.selectedAnnotationId = null;
8012
- const pixels = flattenLayers(result.layers).filter((l) => l.type === "pixel");
8013
- s.activeLayerId = pixels.length ? pixels[pixels.length - 1].id : null;
8028
+ s.activeLayerId = defaultActiveLayerId(s.layers);
8014
8029
  s.activeTarget = "pixels";
8015
8030
  this.psdResolution = result.resolution;
8016
8031
  if (result.patterns.length) s.patterns = result.patterns;
@@ -8338,7 +8353,7 @@ var ImageEditorController = class {
8338
8353
  s.outputOpacity = data.opacity;
8339
8354
  if (typeof data.legacyOutputBlur === "number" && data.legacyOutputBlur >= 0) s.legacyOutputBlur = data.legacyOutputBlur;
8340
8355
  else delete s.legacyOutputBlur;
8341
- s.activeLayerId = data.activeLayerId && findLayer(s.layers, data.activeLayerId) ? data.activeLayerId : null;
8356
+ s.activeLayerId = data.activeLayerId && findLayer(s.layers, data.activeLayerId) ? data.activeLayerId : defaultActiveLayerId(s.layers);
8342
8357
  const light = data.globalLight;
8343
8358
  if (light && Number.isFinite(light.angle) && Number.isFinite(light.altitude)) s.globalLight = { angle: light.angle, altitude: Math.min(90, Math.max(0, light.altitude)) };
8344
8359
  else delete s.globalLight;
@@ -8365,7 +8380,7 @@ var ImageEditorController = class {
8365
8380
  else delete s.legacyOutputBlur;
8366
8381
  s.crop = data.crop ? { ...data.crop } : null;
8367
8382
  s.transform = { ...data.transform };
8368
- s.activeLayerId = null;
8383
+ s.activeLayerId = defaultActiveLayerId(s.layers);
8369
8384
  delete s.globalLight;
8370
8385
  delete s.patterns;
8371
8386
  delete s.bitDepth;
@@ -8677,6 +8692,49 @@ function scaleAnnotation(ann, sx, sy) {
8677
8692
  }
8678
8693
  }
8679
8694
 
8695
+ // src/image-editor/canvas-viewport.ts
8696
+ var DEFAULT_CANVAS_VIEWPORT = { width: 800, height: 500 };
8697
+ var MAX_CANVAS_BACKING_SIDE = 8192;
8698
+ var currentDevicePixelRatio = () => typeof window !== "undefined" && window.devicePixelRatio > 0 ? window.devicePixelRatio : 1;
8699
+ function measureCanvasViewport(el, devicePixelRatio = currentDevicePixelRatio(), fallback = DEFAULT_CANVAS_VIEWPORT) {
8700
+ const width = el.clientWidth || fallback.width;
8701
+ const height = el.clientHeight || fallback.height;
8702
+ const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
8703
+ const box = el.getBoundingClientRect();
8704
+ const layoutW = el.offsetWidth || el.clientWidth;
8705
+ const layoutH = el.offsetHeight || el.clientHeight;
8706
+ const sx = layoutW > 0 && box.width > 0 ? box.width / layoutW : 1;
8707
+ const sy = layoutH > 0 && box.height > 0 ? box.height / layoutH : 1;
8708
+ let pixelRatio = dpr * Math.max(sx, sy);
8709
+ pixelRatio = Math.min(pixelRatio, MAX_CANVAS_BACKING_SIDE / Math.max(width, height));
8710
+ return { width, height, pixelRatio: Number.isFinite(pixelRatio) && pixelRatio > 0 ? pixelRatio : 1 };
8711
+ }
8712
+ function sameCanvasViewport(a, b) {
8713
+ return a.width === b.width && a.height === b.height && Math.abs(a.pixelRatio - b.pixelRatio) < 1e-3;
8714
+ }
8715
+ function watchCanvasViewport(el, onChange) {
8716
+ let last = measureCanvasViewport(el);
8717
+ const check = () => {
8718
+ const next = measureCanvasViewport(el);
8719
+ if (sameCanvasViewport(next, last)) return;
8720
+ last = next;
8721
+ onChange(next);
8722
+ };
8723
+ const stops = [];
8724
+ if (typeof ResizeObserver !== "undefined" && typeof Element !== "undefined" && el instanceof Element) {
8725
+ const observer = new ResizeObserver(check);
8726
+ observer.observe(el);
8727
+ stops.push(() => observer.disconnect());
8728
+ }
8729
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
8730
+ for (const type of ["resize", "transitionend", "animationend"]) {
8731
+ window.addEventListener(type, check, true);
8732
+ stops.push(() => window.removeEventListener(type, check, true));
8733
+ }
8734
+ }
8735
+ return () => stops.forEach((stop) => stop());
8736
+ }
8737
+
8680
8738
  // src/image-editor/document.ts
8681
8739
  var IMAGE_EDITOR_STATE_VERSION = 3;
8682
8740
  var imageEditorStateMigrations = {};
@@ -9309,6 +9367,29 @@ function withImagePlayback(provider, surface) {
9309
9367
  tools: typeof tools === "function" ? () => tools().map(plan) : tools.map(plan)
9310
9368
  };
9311
9369
  }
9370
+ function agentDragDraft(mode, box, image) {
9371
+ if (mode === "select") {
9372
+ return {
9373
+ selectionPath: [
9374
+ { x: box.x, y: box.y },
9375
+ { x: box.x + box.width, y: box.y },
9376
+ { x: box.x + box.width, y: box.y + box.height },
9377
+ { x: box.x, y: box.y + box.height }
9378
+ ]
9379
+ };
9380
+ }
9381
+ if (mode !== "crop" || !image.width || !image.height) return null;
9382
+ const x = Math.max(0, Math.min(image.width, box.x));
9383
+ const y = Math.max(0, Math.min(image.height, box.y));
9384
+ return {
9385
+ crop: {
9386
+ x,
9387
+ y,
9388
+ width: Math.max(1, Math.min(image.width - x, box.width)),
9389
+ height: Math.max(1, Math.min(image.height - y, box.height))
9390
+ }
9391
+ };
9392
+ }
9312
9393
 
9313
9394
  // src/image-editor/selection/tool-options.ts
9314
9395
  var SELECTION_TOOLS = [
@@ -10117,13 +10198,13 @@ function createImageLayerTools(controller) {
10117
10198
  const remove = {
10118
10199
  name: "remove_layer",
10119
10200
  title: "Delete a layer",
10120
- description: "Deletes a layer (a group with its children). Refused for a layer locked with all. Prefer hiding it (set_layer_properties visible false) when it may be wanted back. One undo step. Returns the removed id and the remaining top-level ids.",
10201
+ description: "Deletes a layer (a group with its children). Refused for a layer locked with all. Prefer hiding it (set_layer_properties visible false) when it may be wanted back. Deleting the active layer makes the nearest paintable pixel layer below it active (else the topmost one). One undo step. Returns the removed id, the remaining top-level ids and the active layer id afterwards.",
10121
10202
  inputSchema: { type: "object", properties: { layerId: LAYER_ID2 }, required: ["layerId"], additionalProperties: false },
10122
10203
  annotations: { title: "Delete a layer", ...DESTRUCTIVE },
10123
10204
  handler: ({ layerId }) => {
10124
10205
  const id = exists(layerId);
10125
10206
  controller.removeLayer(id);
10126
- return { removed: id, layers: controller.getLayers().map((l) => l.id) };
10207
+ return { removed: id, layers: controller.getLayers().map((l) => l.id), activeLayerId: controller.getState().activeLayerId };
10127
10208
  }
10128
10209
  };
10129
10210
  const move = {
@@ -11024,7 +11105,7 @@ var PaintGesture = class {
11024
11105
  return null;
11025
11106
  }
11026
11107
  } catch (err) {
11027
- this.error = err instanceof Error ? err.message : String(err);
11108
+ this.error = userErrorMessage(err);
11028
11109
  this.stroke = null;
11029
11110
  this.move = null;
11030
11111
  }
@@ -11046,7 +11127,7 @@ var PaintGesture = class {
11046
11127
  this.controller.offsetLayer(m.id, { x, y }, !m.moved);
11047
11128
  m.moved = true;
11048
11129
  } catch (err) {
11049
- this.error = err instanceof Error ? err.message : String(err);
11130
+ this.error = userErrorMessage(err);
11050
11131
  this.move = null;
11051
11132
  }
11052
11133
  }
@@ -11056,7 +11137,7 @@ var PaintGesture = class {
11056
11137
  try {
11057
11138
  this.stroke.end();
11058
11139
  } catch (err) {
11059
- this.error = err instanceof Error ? err.message : String(err);
11140
+ this.error = userErrorMessage(err);
11060
11141
  }
11061
11142
  }
11062
11143
  this.stroke = null;
@@ -15202,6 +15283,7 @@ export {
15202
15283
  DEFAULT_AUTO_LEADING,
15203
15284
  DEFAULT_BLENDER,
15204
15285
  DEFAULT_BRUSH,
15286
+ DEFAULT_CANVAS_VIEWPORT,
15205
15287
  DEFAULT_DEPTH_MASK,
15206
15288
  DEFAULT_FLOW,
15207
15289
  DEFAULT_GLOBAL_LIGHT,
@@ -15276,6 +15358,7 @@ export {
15276
15358
  LayerOperationError,
15277
15359
  MAX_BATCH,
15278
15360
  MAX_BRUSH_SIZE,
15361
+ MAX_CANVAS_BACKING_SIDE,
15279
15362
  MAX_EFFECT_REACH,
15280
15363
  MAX_KELVIN,
15281
15364
  MAX_TILT_ACCEL,
@@ -15412,6 +15495,7 @@ export {
15412
15495
  ZlibError,
15413
15496
  activeAdjustmentLayers,
15414
15497
  activeEffectSmartFilters,
15498
+ activeLayerAfterRemoval,
15415
15499
  activeSmartFilters,
15416
15500
  addLayerEffect,
15417
15501
  addLayerFilter,
@@ -15423,6 +15507,7 @@ export {
15423
15507
  adjustmentNodeAsLayer,
15424
15508
  adjustmentNodeFromLegacy,
15425
15509
  adler32,
15510
+ agentDragDraft,
15426
15511
  anchorPoint,
15427
15512
  annotationBlend,
15428
15513
  annotationLayerFor,
@@ -15573,6 +15658,7 @@ export {
15573
15658
  decodeSelectionRle,
15574
15659
  deepBytes,
15575
15660
  deepTile,
15661
+ defaultActiveLayerId,
15576
15662
  defaultAdjustmentParams,
15577
15663
  defaultEffectGradient,
15578
15664
  defaultLayerEffect,
@@ -15727,6 +15813,7 @@ export {
15727
15813
  isLayerLocked,
15728
15814
  isLegacyShape,
15729
15815
  isPaintTool,
15816
+ isPaintableLayer,
15730
15817
  isPainterlyBlendMode,
15731
15818
  isPng,
15732
15819
  isQuarterTurn,
@@ -15791,6 +15878,7 @@ export {
15791
15878
  maskSummary,
15792
15879
  matrixFromCorners,
15793
15880
  matrixFromParams,
15881
+ measureCanvasViewport,
15794
15882
  membraneFill,
15795
15883
  mergeLayerEffect,
15796
15884
  mergeShapeComponents,
@@ -15969,6 +16057,7 @@ export {
15969
16057
  runLayerFilters,
15970
16058
  runLayerFiltersFloat,
15971
16059
  runLayerMenuAction,
16060
+ sameCanvasViewport,
15972
16061
  sampleLut,
15973
16062
  sampleToneLut,
15974
16063
  saturationOf,
@@ -16095,6 +16184,7 @@ export {
16095
16184
  updateShapeLayer,
16096
16185
  updateSmartFilter,
16097
16186
  updateTypeLayer,
16187
+ userErrorMessage,
16098
16188
  uvToXy,
16099
16189
  validateEffectGradient,
16100
16190
  validateLayerEffect,
@@ -16108,6 +16198,7 @@ export {
16108
16198
  warpMeshParamAt,
16109
16199
  warpPresetMesh,
16110
16200
  warpSurface,
16201
+ watchCanvasViewport,
16111
16202
  wbMatrix,
16112
16203
  wetGpuSupport,
16113
16204
  wetSimdSupported,
@@ -6,11 +6,29 @@
6
6
  import type { BlendMode } from '../blend.cjs';
7
7
  import type { CropBox } from '../types.cjs';
8
8
  import { type LayerEffect, type LayerEffectKind, type LayerEffects } from '../../raster/effects/model.cjs';
9
- import { type ImageLayer, type LayerBlendMode, type LayerFilter, type LayerLocks, type RasterMask } from './model.cjs';
10
- /** A refused layer operation; the message says why and how to fix it. */
9
+ import { type ImageLayer, type LayerBlendMode, type LayerFilter, type LayerLocks, type PixelLayer, type RasterMask } from './model.cjs';
10
+ /**
11
+ * A refused layer operation; the message says why and how to fix it (written for agents: it names
12
+ * tools and arguments). `userMessage`, when given, says the same for a person at the canvas.
13
+ */
11
14
  export declare class LayerOperationError extends Error {
12
- constructor(message: string);
15
+ readonly userMessage?: string;
16
+ constructor(message: string, userMessage?: string);
13
17
  }
18
+ /** The message of a refusal to show a person: a `LayerOperationError`'s `userMessage`, else the message. */
19
+ export declare function userErrorMessage(err: unknown): string;
20
+ /** Whether brush strokes can go on a layer's pixels: a visible pixel layer without its pixels locked. */
21
+ export declare function isPaintableLayer(layer: ImageLayer): layer is PixelLayer;
22
+ /**
23
+ * The layer a document with no active layer works on: the topmost paintable pixel layer (the
24
+ * Background of a newly loaded image), else the topmost pixel layer, else `null`.
25
+ */
26
+ export declare function defaultActiveLayerId(layers: readonly ImageLayer[]): string | null;
27
+ /**
28
+ * The layer to make active when the active layer `removedId` left the stack (`prev` → `next`):
29
+ * the nearest paintable layer that was below it, else `defaultActiveLayerId(next)`.
30
+ */
31
+ export declare function activeLayerAfterRemoval(prev: readonly ImageLayer[], next: readonly ImageLayer[], removedId: string): string | null;
14
32
  /** Where a layer goes: above or below a layer (in its group), or at a position in a group. */
15
33
  export interface LayerPlacement {
16
34
  /** Directly above this layer, in the same group. */
@@ -6,11 +6,29 @@
6
6
  import type { BlendMode } from '../blend.js';
7
7
  import type { CropBox } from '../types.js';
8
8
  import { type LayerEffect, type LayerEffectKind, type LayerEffects } from '../../raster/effects/model.js';
9
- import { type ImageLayer, type LayerBlendMode, type LayerFilter, type LayerLocks, type RasterMask } from './model.js';
10
- /** A refused layer operation; the message says why and how to fix it. */
9
+ import { type ImageLayer, type LayerBlendMode, type LayerFilter, type LayerLocks, type PixelLayer, type RasterMask } from './model.js';
10
+ /**
11
+ * A refused layer operation; the message says why and how to fix it (written for agents: it names
12
+ * tools and arguments). `userMessage`, when given, says the same for a person at the canvas.
13
+ */
11
14
  export declare class LayerOperationError extends Error {
12
- constructor(message: string);
15
+ readonly userMessage?: string;
16
+ constructor(message: string, userMessage?: string);
13
17
  }
18
+ /** The message of a refusal to show a person: a `LayerOperationError`'s `userMessage`, else the message. */
19
+ export declare function userErrorMessage(err: unknown): string;
20
+ /** Whether brush strokes can go on a layer's pixels: a visible pixel layer without its pixels locked. */
21
+ export declare function isPaintableLayer(layer: ImageLayer): layer is PixelLayer;
22
+ /**
23
+ * The layer a document with no active layer works on: the topmost paintable pixel layer (the
24
+ * Background of a newly loaded image), else the topmost pixel layer, else `null`.
25
+ */
26
+ export declare function defaultActiveLayerId(layers: readonly ImageLayer[]): string | null;
27
+ /**
28
+ * The layer to make active when the active layer `removedId` left the stack (`prev` → `next`):
29
+ * the nearest paintable layer that was below it, else `defaultActiveLayerId(next)`.
30
+ */
31
+ export declare function activeLayerAfterRemoval(prev: readonly ImageLayer[], next: readonly ImageLayer[], removedId: string): string | null;
14
32
  /** Where a layer goes: above or below a layer (in its group), or at a position in a group. */
15
33
  export interface LayerPlacement {
16
34
  /** Directly above this layer, in the same group. */
@@ -1,5 +1,6 @@
1
1
  import type { AgentToolProvider } from '../tools/registry.cjs';
2
- import type { PlaybackRect, PlaybackRef, PlaybackStep } from '../tools/playback.cjs';
2
+ import type { PlaybackPoint, PlaybackRect, PlaybackRef, PlaybackStep } from '../tools/playback.cjs';
3
+ import type { PresenceDragBox } from '../tools/presence.cjs';
3
4
  import type { ImageEditorController } from './controller.cjs';
4
5
  /** The playback kind of an image layer (`{ kind: 'layer', id }`, as in `data-layer-id`). */
5
6
  export declare const IMAGE_PLAYBACK_KIND = "layer";
@@ -20,3 +21,21 @@ export declare function planImageToolPlayback(tool: string, args: Record<string,
20
21
  * provider's own `playbackSurface`; with neither, it is `image` (where `ImageEditor` attaches itself).
21
22
  */
22
23
  export declare function withImagePlayback(provider: AgentToolProvider, surface?: string): AgentToolProvider;
24
+ /**
25
+ * What a canvas draws while an agent drags on it (see `trackPresenceDrag`): in crop mode, the crop
26
+ * box being pulled out (clamped to the image); in select mode, the marquee as a selection path.
27
+ * The tool call that follows sets the real crop or selection.
28
+ */
29
+ export declare function agentDragDraft(mode: string, box: PresenceDragBox, image: {
30
+ width: number;
31
+ height: number;
32
+ }): {
33
+ crop: {
34
+ x: number;
35
+ y: number;
36
+ width: number;
37
+ height: number;
38
+ };
39
+ } | {
40
+ selectionPath: PlaybackPoint[];
41
+ } | null;
@@ -1,5 +1,6 @@
1
1
  import type { AgentToolProvider } from '../tools/registry.js';
2
- import type { PlaybackRect, PlaybackRef, PlaybackStep } from '../tools/playback.js';
2
+ import type { PlaybackPoint, PlaybackRect, PlaybackRef, PlaybackStep } from '../tools/playback.js';
3
+ import type { PresenceDragBox } from '../tools/presence.js';
3
4
  import type { ImageEditorController } from './controller.js';
4
5
  /** The playback kind of an image layer (`{ kind: 'layer', id }`, as in `data-layer-id`). */
5
6
  export declare const IMAGE_PLAYBACK_KIND = "layer";
@@ -20,3 +21,21 @@ export declare function planImageToolPlayback(tool: string, args: Record<string,
20
21
  * provider's own `playbackSurface`; with neither, it is `image` (where `ImageEditor` attaches itself).
21
22
  */
22
23
  export declare function withImagePlayback(provider: AgentToolProvider, surface?: string): AgentToolProvider;
24
+ /**
25
+ * What a canvas draws while an agent drags on it (see `trackPresenceDrag`): in crop mode, the crop
26
+ * box being pulled out (clamped to the image); in select mode, the marquee as a selection path.
27
+ * The tool call that follows sets the real crop or selection.
28
+ */
29
+ export declare function agentDragDraft(mode: string, box: PresenceDragBox, image: {
30
+ width: number;
31
+ height: number;
32
+ }): {
33
+ crop: {
34
+ x: number;
35
+ y: number;
36
+ width: number;
37
+ height: number;
38
+ };
39
+ } | {
40
+ selectionPath: PlaybackPoint[];
41
+ } | null;
@@ -7,8 +7,15 @@ import { type PixelSurface } from '../raster/surface.cjs';
7
7
  import type { GlobalLight } from '../raster/effects/model.cjs';
8
8
  import type { EffectPattern } from '../raster/effects/patterns.cjs';
9
9
  export interface RenderOptions {
10
+ /** The viewport's size in CSS pixels: layout, the projection and overlays use these units. */
10
11
  viewportWidth?: number;
11
12
  viewportHeight?: number;
13
+ /**
14
+ * Backing-store pixels per CSS pixel (default 1): the canvas is sized `viewport × pixelRatio` and
15
+ * drawn through that scale, so it stays sharp on high-DPI screens and under CSS transforms. See
16
+ * `measureCanvasViewport`.
17
+ */
18
+ pixelRatio?: number;
12
19
  showCropOverlay?: boolean;
13
20
  showSelectionOverlay?: boolean;
14
21
  interactive?: boolean;
@@ -7,8 +7,15 @@ import { type PixelSurface } from '../raster/surface.js';
7
7
  import type { GlobalLight } from '../raster/effects/model.js';
8
8
  import type { EffectPattern } from '../raster/effects/patterns.js';
9
9
  export interface RenderOptions {
10
+ /** The viewport's size in CSS pixels: layout, the projection and overlays use these units. */
10
11
  viewportWidth?: number;
11
12
  viewportHeight?: number;
13
+ /**
14
+ * Backing-store pixels per CSS pixel (default 1): the canvas is sized `viewport × pixelRatio` and
15
+ * drawn through that scale, so it stays sharp on high-DPI screens and under CSS transforms. See
16
+ * `measureCanvasViewport`.
17
+ */
18
+ pixelRatio?: number;
12
19
  showCropOverlay?: boolean;
13
20
  showSelectionOverlay?: boolean;
14
21
  interactive?: boolean;
@@ -7,7 +7,7 @@ var _chunkVKF3PWKRcjs = require('../chunk-VKF3PWKR.cjs');
7
7
 
8
8
 
9
9
 
10
- var _chunkQCEQIRJQcjs = require('../chunk-QCEQIRJQ.cjs');
10
+ var _chunkPI45JMP7cjs = require('../chunk-PI45JMP7.cjs');
11
11
  require('../chunk-A2IS7EHE.cjs');
12
12
 
13
13
 
@@ -53,7 +53,7 @@ var _chunkJLWLX52Lcjs = require('../chunk-JLWLX52L.cjs');
53
53
 
54
54
 
55
55
 
56
- var _chunkUJGAA6AKcjs = require('../chunk-UJGAA6AK.cjs');
56
+ var _chunkSWTUM32Hcjs = require('../chunk-SWTUM32H.cjs');
57
57
  require('../chunk-RHXW4TA5.cjs');
58
58
 
59
59
 
@@ -1634,11 +1634,11 @@ function header(state, options) {
1634
1634
  return out;
1635
1635
  }
1636
1636
  function serializePaintDocument(state, options = {}) {
1637
- const { layers, assets } = _chunkQCEQIRJQcjs.serializeLayers.call(void 0, state.layers);
1637
+ const { layers, assets } = _chunkPI45JMP7cjs.serializeLayers.call(void 0, state.layers);
1638
1638
  return { ...header(state, options), layers, assets };
1639
1639
  }
1640
1640
  async function serializePaintDocumentExternal(state, store, options = {}) {
1641
- const { layers, assets } = await _chunkQCEQIRJQcjs.serializeLayersExternal.call(void 0, state.layers, store);
1641
+ const { layers, assets } = await _chunkPI45JMP7cjs.serializeLayersExternal.call(void 0, state.layers, store);
1642
1642
  return { ...header(state, options), layers, assets };
1643
1643
  }
1644
1644
  function paintLayersOnly(layers, problems) {
@@ -1675,7 +1675,7 @@ function deserializePaintDocument(raw) {
1675
1675
  const canvas = normalizeCanvas({ width: r.width, height: r.height, dpi: _nullishCoalesce(r.dpi, () => ( void 0)) });
1676
1676
  const paper = _chunkBXMIBMVGcjs.normalizePaper.call(void 0, _nullishCoalesce(r.paper, () => ( {})));
1677
1677
  const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
1678
- const layers = paintLayersOnly(_chunkQCEQIRJQcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1678
+ const layers = paintLayersOnly(_chunkPI45JMP7cjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1679
1679
  const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
1680
1680
  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));
1681
1681
  const wetEvents = Array.isArray(r.wetEvents) ? r.wetEvents.filter((e) => e && typeof e === "object" && Number.isInteger(e.clock) && typeof e.kind === "string") : [];
@@ -1689,7 +1689,7 @@ async function deserializePaintDocumentAsync(raw, store) {
1689
1689
  if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
1690
1690
  const r = raw;
1691
1691
  const problems = [];
1692
- const assets = await _chunkQCEQIRJQcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1692
+ const assets = await _chunkPI45JMP7cjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1693
1693
  const loaded = deserializePaintDocument({ ...r, assets });
1694
1694
  return { ...loaded, problems: [...problems, ...loaded.problems] };
1695
1695
  }
@@ -5301,7 +5301,7 @@ var PaintEditorController = (_class10 = class _PaintEditorController {
5301
5301
  return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
5302
5302
  });
5303
5303
  const layers = toStandard(dried);
5304
- const result = _chunkQCEQIRJQcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
5304
+ const result = _chunkPI45JMP7cjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
5305
5305
  for (const [mode, names] of changed) {
5306
5306
  const label = _nullishCoalesce(_optionalChain([_chunkCWM3CUUXcjs.BLEND_MODES, 'access', _268 => _268.find, 'call', _269 => _269((m) => m.id === mode), 'optionalAccess', _270 => _270.label]), () => ( mode));
5307
5307
  const std = _nullishCoalesce(_optionalChain([_chunkCWM3CUUXcjs.BLEND_MODES, 'access', _271 => _271.find, 'call', _272 => _272((m) => m.id === _chunkCWM3CUUXcjs.standardBlendMode.call(void 0, mode)), 'optionalAccess', _273 => _273.label]), () => ( _chunkCWM3CUUXcjs.standardBlendMode.call(void 0, mode)));
@@ -5844,7 +5844,7 @@ function resampleInto(src, rect, width, height) {
5844
5844
  }
5845
5845
 
5846
5846
  // src/paint-editor/presence.ts
5847
- var PaintPresence = class extends _chunkUJGAA6AKcjs.PresenceStore {
5847
+ var PaintPresence = class extends _chunkSWTUM32Hcjs.PresenceStore {
5848
5848
  };
5849
5849
  var FRAME = 16;
5850
5850
  function strokeSchedule(points) {
@@ -5888,8 +5888,8 @@ var PaintDirector = class {
5888
5888
  this.controller = controller;
5889
5889
  this.presence = presence;
5890
5890
  this.options = options;
5891
- this.clock = _nullishCoalesce(options.clock, () => ( _chunkUJGAA6AKcjs.realPresenceClock));
5892
- this.queue = new (0, _chunkUJGAA6AKcjs.PerformanceQueue)(presence, { clock: this.clock, linger: options.linger });
5891
+ this.clock = _nullishCoalesce(options.clock, () => ( _chunkSWTUM32Hcjs.realPresenceClock));
5892
+ this.queue = new (0, _chunkSWTUM32Hcjs.PerformanceQueue)(presence, { clock: this.clock, linger: options.linger });
5893
5893
  }
5894
5894
 
5895
5895
 
@@ -5924,7 +5924,7 @@ var PaintDirector = class {
5924
5924
  this.presence.set({ id, x: to.x, y: to.y });
5925
5925
  return;
5926
5926
  }
5927
- await _chunkUJGAA6AKcjs.tweenPresenceCursor.call(void 0, this.presence, this.clock, id, this.where(id), to, this.ms(ms));
5927
+ await _chunkSWTUM32Hcjs.tweenPresenceCursor.call(void 0, this.presence, this.clock, id, this.where(id), to, this.ms(ms));
5928
5928
  }
5929
5929
  async say(id, text, ms) {
5930
5930
  if (!text) return;
@@ -5954,7 +5954,7 @@ var PaintDirector = class {
5954
5954
  await this.glide(id, path[0]);
5955
5955
  this.presence.set({ id, pressed: true });
5956
5956
  try {
5957
- for (let i = 1; i < path.length; i++) await _chunkUJGAA6AKcjs.tweenPresenceCursor.call(void 0, this.presence, this.clock, id, path[i - 1], path[i], this.ms(i % 2 ? 260 : 90));
5957
+ for (let i = 1; i < path.length; i++) await _chunkSWTUM32Hcjs.tweenPresenceCursor.call(void 0, this.presence, this.clock, id, path[i - 1], path[i], this.ms(i % 2 ? 260 : 90));
5958
5958
  } finally {
5959
5959
  this.presence.set({ id, pressed: false });
5960
5960
  }