@pixerate/editor 0.228.2 → 0.229.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.
@@ -72,6 +72,7 @@ import {
72
72
  LUMA_709,
73
73
  LayerCompositor,
74
74
  LayerOperationError,
75
+ MARGIN_FIT_MIN_GUTTER,
75
76
  MAX_EFFECT_ANIMATION_MS,
76
77
  MAX_KELVIN,
77
78
  MIN_KELVIN,
@@ -405,7 +406,7 @@ import {
405
406
  writePsd,
406
407
  xyToTempTint,
407
408
  xyToUv
408
- } from "../chunk-SWKXPAJC.js";
409
+ } from "../chunk-JVNGCS26.js";
409
410
  import {
410
411
  parseCssColor
411
412
  } from "../chunk-3CI7FRMK.js";
@@ -7156,6 +7157,35 @@ var ImageEditorController = class {
7156
7157
  const data = this.compositor.composite(layers, r, this.compositorSources());
7157
7158
  return { width: r.width, height: r.height, data };
7158
7159
  }
7160
+ /**
7161
+ * The Eyedropper: the colour (#rrggbb) of the composite at a document point, or the average of
7162
+ * the `size` × `size` square round it (1, 3 or 5…), or null outside the canvas or where every
7163
+ * layer is transparent. With `setForeground` it also becomes the paint foreground.
7164
+ */
7165
+ sampleColor(point2, options = {}) {
7166
+ const { width, height } = this.naturalSize;
7167
+ const size = Math.max(1, Math.floor(options.size ?? 1));
7168
+ const cx = Math.floor(point2.x);
7169
+ const cy = Math.floor(point2.y);
7170
+ if (cx < 0 || cy < 0 || cx >= width || cy >= height) return null;
7171
+ const half = Math.floor(size / 2);
7172
+ const x = Math.max(0, cx - half);
7173
+ const y = Math.max(0, cy - half);
7174
+ const rect3 = { x, y, width: Math.min(width, cx + half + 1) - x, height: Math.min(height, cy + half + 1) - y };
7175
+ const { data } = this.compositePixels(rect3);
7176
+ let r = 0, g = 0, b = 0, a = 0;
7177
+ for (let i = 0; i < data.length; i += 4) {
7178
+ const w = data[i + 3];
7179
+ r += data[i] * w;
7180
+ g += data[i + 1] * w;
7181
+ b += data[i + 2] * w;
7182
+ a += w;
7183
+ }
7184
+ if (a === 0) return null;
7185
+ const hex = toHex([r / a, g / a, b / a]);
7186
+ if (options.setForeground) this.setPaintOptions({ foreground: hex });
7187
+ return hex;
7188
+ }
7159
7189
  compositorSources() {
7160
7190
  const { width, height } = this.naturalSize;
7161
7191
  return {
@@ -13523,7 +13553,7 @@ var DEPTH_MASK_CONTROLS = [
13523
13553
 
13524
13554
  // src/image-editor/playback.ts
13525
13555
  var IMAGE_PLAYBACK_KIND = "layer";
13526
- var PAINT = /^(paint_stroke|clone_stamp|heal_brush|spot_heal|dodge|burn|sponge|smudge|blur_stroke|sharpen_stroke|history_brush|set_clone_source|set_clone_transform|fill_pixels|clear_pixels|set_paint|set_retouch_options|load_mixer_brush|clean_mixer_brush)$/;
13556
+ var PAINT = /^(paint_stroke|clone_stamp|heal_brush|spot_heal|dodge|burn|sponge|smudge|blur_stroke|sharpen_stroke|history_brush|set_clone_source|set_clone_transform|fill_pixels|clear_pixels|set_paint|sample_color|set_retouch_options|load_mixer_brush|clean_mixer_brush)$/;
13527
13557
  var SELECT = /^(make_work_path|add_path|set_path_geometry|rename_path|duplicate_path|delete_path|fill_path|stroke_path|select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|quick_select|select_object|select_focus_area|select_sky|select_line|magic_wand|grow_selection|select_similar|reselect|select_layer_transparency|select_color_range|load_selection_from_image|load_selection_from_mask|set_selection_display|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy|stroke_selection|content_aware_fill|copy_pixels|cut_pixels|paste_pixels|save_selection|load_selection_from_channel|set_channel_properties|duplicate_channel|delete_channel|load_channel_from_image|set_quick_mask|path_to_selection|select_text_shape|compare_selections|edit_selection_step|insert_selection_step|remove_selection_step|freeze_selection|separate_subject)$/;
13528
13558
  var ANNOTATE = /^(add_shape_layer|update_shape|add_shape_component|set_shape_component|remove_shape_component|merge_shape_components|add_text_layer|set_text|set_text_style|set_paragraph_style|set_text_frame|set_text_orientation|convert_text_to_shape)$/;
13529
13559
  var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
@@ -17441,7 +17471,29 @@ function createImagePaintTools(controller) {
17441
17471
  annotations: { title: "Get the paint", ...READ4 },
17442
17472
  handler: () => paintView()
17443
17473
  };
17444
- return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool, setPaint, getPaint];
17474
+ const sampleColor2 = {
17475
+ name: "sample_color",
17476
+ title: "Sample a colour (Eyedropper)",
17477
+ description: "The Eyedropper: reads the composite colour at a point in document pixels, averaged over a size \xD7 size square (default 1), and by default makes it the paint foreground. Fails outside the canvas or where every layer is transparent. View state, not an undo step. Returns the colour (#rrggbb) and the foreground.",
17478
+ inputSchema: {
17479
+ type: "object",
17480
+ properties: {
17481
+ x: { type: "number", description: "Document x in pixels." },
17482
+ y: { type: "number", description: "Document y in pixels." },
17483
+ size: { type: "integer", minimum: 1, maximum: 101, description: "Sample square in pixels (odd values centre on the point). Default 1." },
17484
+ setForeground: { type: "boolean", description: "Make the colour the paint foreground (default true)." }
17485
+ },
17486
+ required: ["x", "y"],
17487
+ additionalProperties: false
17488
+ },
17489
+ annotations: { title: "Sample a colour (Eyedropper)", ...SET },
17490
+ handler: (args) => {
17491
+ const color = controller.sampleColor({ x: args.x, y: args.y }, { size: args.size, setForeground: args.setForeground !== false });
17492
+ if (!color) throw new LayerOperationError(`Nothing to sample at (${args.x}, ${args.y}): the point is outside the canvas or transparent. Read the canvas size with get_image_info, then sample inside it.`);
17493
+ return { color, foreground: controller.getState().paint.foreground };
17494
+ }
17495
+ };
17496
+ return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool, setPaint, getPaint, sampleColor2];
17445
17497
  }
17446
17498
 
17447
17499
  // src/image-editor/color-tools.ts
@@ -20902,6 +20954,7 @@ function layerPanels(layer, options = {}) {
20902
20954
  };
20903
20955
  if (layer.type === "pixel") {
20904
20956
  effects();
20957
+ panels.push({ id: "color", title: "Color Adjustments" });
20905
20958
  panels.push({ id: "paint", title: "Brush Options" });
20906
20959
  } else if (layer.type === "adjustment") {
20907
20960
  panels.push({ id: "adjustment", title: adjustmentKindLabel(layer.kind) });
@@ -20911,7 +20964,10 @@ function layerPanels(layer, options = {}) {
20911
20964
  } else if (layer.type === "shape") {
20912
20965
  panels.push({ id: "shape", title: "Shape Options" });
20913
20966
  effects();
20914
- } else if (layer.type === "smart" || layer.type === "group") {
20967
+ } else if (layer.type === "smart") {
20968
+ effects();
20969
+ panels.push({ id: "color", title: "Color Adjustments" });
20970
+ } else if (layer.type === "group") {
20915
20971
  effects();
20916
20972
  }
20917
20973
  if (layer.mask || layer.vectorMask) {
@@ -21935,18 +21991,28 @@ var IMAGE_TOOL_GROUPS = [
21935
21991
  { id: "move", label: "Move", tools: ["move"] },
21936
21992
  { id: "select", label: "Select", tools: ["select"] },
21937
21993
  { id: "crop", label: "Crop", tools: ["crop"] },
21938
- { id: "brush", label: "Brush", tools: ["brush", "mixer"] },
21939
- { id: "eraser", label: "Eraser", tools: ["eraser"] },
21940
- { id: "repair", label: "Repair", tools: ["spot-heal", "heal", "clone"] },
21941
- { id: "history", label: "History Brush", tools: ["history"] },
21942
- { id: "focus", label: "Blur & Sharpen", tools: ["blur", "sharpen", "smudge"] },
21943
- { id: "tone", label: "Dodge & Burn", tools: ["dodge", "burn", "sponge"] },
21994
+ { id: "draw", label: "Draw", tools: ["brush", "pen", "mixer"] },
21995
+ { id: "erase", label: "Erase", tools: ["eraser", "history"] },
21996
+ {
21997
+ id: "retouch",
21998
+ label: "Retouch",
21999
+ tools: ["spot-heal", "heal", "clone", "dodge", "burn", "sponge", "blur", "sharpen", "smudge"],
22000
+ sections: [
22001
+ { label: "Fix", tools: ["spot-heal", "heal", "clone"] },
22002
+ { label: "Tone", tools: ["dodge", "burn", "sponge"] },
22003
+ { label: "Detail", tools: ["blur", "sharpen", "smudge"] }
22004
+ ]
22005
+ },
21944
22006
  { id: "shape", label: "Shape", tools: ["shape"] },
21945
- { id: "pen", label: "Pen", tools: ["path-pen"] },
22007
+ { id: "paths", label: "Paths", tools: ["path-pen", "path-select", "direct-select"] },
21946
22008
  { id: "type", label: "Type", tools: ["type"] },
21947
- { id: "path-select", label: "Path Selection", tools: ["path-select", "direct-select"] },
21948
- { id: "annotate", label: "Annotate", tools: ["pen", "line", "arrow", "rect", "circle"] }
22009
+ { id: "eyedropper", label: "Eyedropper", tools: ["eyedropper"] }
21949
22010
  ];
22011
+ function imageToolVariants(tool) {
22012
+ if (tool === "select") return SELECTION_TOOLS.map((t) => ({ tool: "select", id: t.id, label: t.label }));
22013
+ if (tool === "shape") return LIVE_SHAPE_KINDS.map((kind) => ({ tool: "shape", id: kind, label: SHAPE_KIND_LABELS[kind] }));
22014
+ return [];
22015
+ }
21950
22016
  function imageToolGroup(tool) {
21951
22017
  return IMAGE_TOOL_GROUPS.find((group) => group.tools.includes(tool)) ?? IMAGE_TOOL_GROUPS[0];
21952
22018
  }
@@ -21958,13 +22024,14 @@ var OTHER_TOOL_LABELS = {
21958
22024
  "path-pen": "Pen",
21959
22025
  "path-select": "Path Selection",
21960
22026
  "direct-select": "Direct Selection",
21961
- pen: "Freehand",
22027
+ pen: "Pencil",
21962
22028
  line: "Line",
21963
22029
  arrow: "Arrow",
21964
22030
  rect: "Rectangle",
21965
22031
  circle: "Ellipse",
21966
22032
  text: "Text",
21967
- watermark: "Watermark"
22033
+ watermark: "Watermark",
22034
+ eyedropper: "Eyedropper"
21968
22035
  };
21969
22036
  function imageToolLabel(tool) {
21970
22037
  return PAINT_TOOLS.find((t) => t.id === tool)?.label ?? OTHER_TOOL_LABELS[tool] ?? tool;
@@ -21977,7 +22044,8 @@ var IMAGE_TOOL_SHORTCUTS = {
21977
22044
  type: "T",
21978
22045
  "path-pen": "P",
21979
22046
  "path-select": "A",
21980
- "direct-select": "Shift+A"
22047
+ "direct-select": "Shift+A",
22048
+ eyedropper: "I"
21981
22049
  };
21982
22050
  function imageToolForShortcut(key2, shift = false) {
21983
22051
  const shortcut = `${shift ? "Shift+" : ""}${key2.toUpperCase()}`;
@@ -21991,6 +22059,7 @@ function imageToolCanvasMode(tool) {
21991
22059
  if (isPaintTool(tool)) return "paint";
21992
22060
  if (tool === "select") return "select";
21993
22061
  if (tool === "crop") return "crop";
22062
+ if (tool === "eyedropper") return "sample";
21994
22063
  return "annotate";
21995
22064
  }
21996
22065
  function imageToolPanel(tool) {
@@ -22002,12 +22071,37 @@ function imageToolPanel(tool) {
22002
22071
  if (tool === "shape" || tool === "type") return tool;
22003
22072
  return null;
22004
22073
  }
22074
+ function imageToolPanelIds(tool) {
22075
+ const own = imageToolPanel(tool);
22076
+ if (!own) return [];
22077
+ return own === "selection" ? [own, "channels"] : [own];
22078
+ }
22005
22079
  function imagePanelTool(panel) {
22006
22080
  for (const group of IMAGE_TOOL_GROUPS) {
22007
- for (const tool of group.tools) if (imageToolPanel(tool) === panel) return tool;
22081
+ for (const tool of group.tools) if (imageToolPanelIds(tool).includes(panel)) return tool;
22008
22082
  }
22009
22083
  return null;
22010
22084
  }
22085
+ var TOOL_ONLY_PANELS = /* @__PURE__ */ new Set(["paint", "selection", "canvas-size", "channels", "paths"]);
22086
+ var TOOL_PANEL_TITLES = {
22087
+ paint: "Brush Options",
22088
+ selection: "Selection",
22089
+ "canvas-size": "Composition Size",
22090
+ channels: "Channels",
22091
+ paths: "Paths",
22092
+ shape: "Shape Options",
22093
+ type: "Typography"
22094
+ };
22095
+ function imageToolPanels(tool, layer, options = {}) {
22096
+ const own = imageToolPanelIds(tool);
22097
+ const listed = layerPanels(layer, options);
22098
+ const first = own.map((id) => listed.find((p) => p.id === id) ?? { id, title: TOOL_PANEL_TITLES[id] ?? id });
22099
+ const rest = listed.filter((p) => !own.includes(p.id) && !TOOL_ONLY_PANELS.has(p.id));
22100
+ return [...first, ...rest];
22101
+ }
22102
+ function imageToolUsesColor(tool) {
22103
+ return tool === "brush" || tool === "mixer" || tool === "pen" || tool === "line" || tool === "arrow" || tool === "rect" || tool === "circle" || tool === "shape" || tool === "type" || tool === "eyedropper";
22104
+ }
22011
22105
 
22012
22106
  // src/image-editor/editorDocument.ts
22013
22107
  function createImageEditorDocument(controller, options = {}) {
@@ -22905,6 +22999,7 @@ export {
22905
22999
  LayerEffectError,
22906
23000
  LayerOperationError,
22907
23001
  MARCHING_ANTS_STEP_MS,
23002
+ MARGIN_FIT_MIN_GUTTER,
22908
23003
  MASK_EDGE_THRESHOLDS,
22909
23004
  MASK_FILE_ACCEPT,
22910
23005
  MAX_BATCH,
@@ -23406,6 +23501,10 @@ export {
23406
23501
  imageToolGroup,
23407
23502
  imageToolLabel,
23408
23503
  imageToolPanel,
23504
+ imageToolPanelIds,
23505
+ imageToolPanels,
23506
+ imageToolUsesColor,
23507
+ imageToolVariants,
23409
23508
  importPsd,
23410
23509
  inflate,
23411
23510
  inflateRaw,
@@ -1,20 +1,49 @@
1
1
  /**
2
- * The image editor's tools as a toolbar shows them: grouped like Photoshop's (a slot per group, the
3
- * rest of the group in a flyout), with labels and shortcuts, the canvas mode each tool draws in and
4
- * the layer panel (`layerPanels`) holding its options. Hosts build their toolbar from these instead
5
- * of listing tools themselves, so new tools show up without host changes.
2
+ * The image editor's tools as a toolbar shows them: grouped by what each tool is for (a slot per
3
+ * group, the rest of the group in a flyout or the host's tool options), with labels and shortcuts,
4
+ * the canvas mode each tool draws in and the layer panel (`layerPanels`) holding its options. Hosts
5
+ * build their toolbar from these instead of listing tools themselves, so new tools show up without
6
+ * host changes.
6
7
  */
7
8
  import type { ImageEditorTool } from './types.cjs';
9
+ import { type SelectionToolKind } from './selection/tool-options.cjs';
10
+ import { type ImageLayer, type LiveShapeKind } from './layers/model.cjs';
11
+ import { type LayerPanelDescriptor, type LayerPanelsOptions } from './layers/panel.cjs';
12
+ /** Tools of a group shown together under a heading (Retouch's Fix, Tone and Detail). */
13
+ export interface ImageToolSection {
14
+ label: string;
15
+ tools: readonly ImageEditorTool[];
16
+ }
8
17
  export interface ImageToolGroup {
9
18
  id: string;
10
19
  label: string;
11
20
  tools: readonly ImageEditorTool[];
21
+ /** The group's tools split by purpose, in `tools` order; absent when the group is one set. */
22
+ sections?: readonly ImageToolSection[];
12
23
  }
13
24
  /**
14
- * Every tool a toolbar offers, in toolbar order. The `text` and `watermark` annotations are left
15
- * out: they are added through the API (`addAnnotation`), not drawn.
25
+ * Every tool a toolbar offers, in toolbar order, grouped by intent: choose (Move, Select, Crop),
26
+ * put paint down (Draw), take it away (Erase), retouch (Fix, Tone, Detail), build shapes, paths and
27
+ * type, and pick colours (Eyedropper). The `text` and `watermark` annotations are left out: they
28
+ * are added through the API (`addAnnotation`), not drawn. So are the `line`, `arrow`, `rect` and
29
+ * `circle` annotations, which the Shape tool's live shapes cover (they still draw for API callers).
16
30
  */
17
31
  export declare const IMAGE_TOOL_GROUPS: readonly ImageToolGroup[];
32
+ /**
33
+ * One way of using a tool that a toolbar offers as a tool of its own: a selection tool (`select`
34
+ * with `SelectionToolOptions.tool`) or a shape kind (`shape` with `ShapeToolOptions.kind`).
35
+ */
36
+ export type ImageToolVariant = {
37
+ tool: 'select';
38
+ id: SelectionToolKind;
39
+ label: string;
40
+ } | {
41
+ tool: 'shape';
42
+ id: LiveShapeKind;
43
+ label: string;
44
+ };
45
+ /** The variants a tool offers (the selection tools, the shape kinds), or none. */
46
+ export declare function imageToolVariants(tool: ImageEditorTool): readonly ImageToolVariant[];
18
47
  /** The group a tool is in (the first group for a tool no group holds). */
19
48
  export declare function imageToolGroup(tool: ImageEditorTool): ImageToolGroup;
20
49
  export declare function imageToolLabel(tool: ImageEditorTool): string;
@@ -23,13 +52,32 @@ export declare const IMAGE_TOOL_SHORTCUTS: Partial<Record<ImageEditorTool, strin
23
52
  /** The tool a shortcut picks (the first tool listed for a shared key), or null. */
24
53
  export declare function imageToolForShortcut(key: string, shift?: boolean): ImageEditorTool | null;
25
54
  /** How a canvas handles pointer gestures for a tool (the Svelte and React canvases' `mode`). */
26
- export type ImageToolCanvasMode = 'transform' | 'crop' | 'select' | 'paint' | 'annotate';
55
+ export type ImageToolCanvasMode = 'transform' | 'crop' | 'select' | 'paint' | 'annotate' | 'sample';
27
56
  /**
28
57
  * The canvas mode a tool works in. Move picks and transforms layers; shapes, type, the path tools
29
- * and the annotations draw in `annotate`.
58
+ * and the annotations draw in `annotate`; the Eyedropper picks colours in `sample`.
30
59
  */
31
60
  export declare function imageToolCanvasMode(tool: ImageEditorTool): ImageToolCanvasMode;
32
61
  /** The `layerPanels` panel holding a tool's options, or null when it has none. */
33
62
  export declare function imageToolPanel(tool: ImageEditorTool): string | null;
34
- /** The first tool whose options are in a panel (in toolbar order), or null. */
63
+ /**
64
+ * The panels that work with a tool: the one holding its options (`imageToolPanel`) and, for the
65
+ * selection tools, Channels (where a selection is saved and loaded).
66
+ */
67
+ export declare function imageToolPanelIds(tool: ImageEditorTool): string[];
68
+ /** The first tool whose panels include `panel` (in toolbar order), or null. */
35
69
  export declare function imagePanelTool(panel: string): ImageEditorTool | null;
70
+ /**
71
+ * The panels a sidebar shows for the active tool and layer: the tool's own panels first
72
+ * (`imageToolPanelIds`: Brush Options only with a paint tool, Selection and Channels with Select,
73
+ * Composition Size with Crop, Paths with the path tools, Shape Options or Typography with Shape or
74
+ * Type), then the active layer's (`layerPanels`: its properties, effects, colour adjustments, mask,
75
+ * and its shape or type options when it is a shape or type layer). Move shows the layer's panels only.
76
+ */
77
+ export declare function imageToolPanels(tool: ImageEditorTool, layer: ImageLayer | null, options?: LayerPanelsOptions): LayerPanelDescriptor[];
78
+ /**
79
+ * Tools that put down a colour the host chooses: the brush and mixer (the paint foreground), the
80
+ * pencil and annotations, shapes and type (their fill or colour), and the Eyedropper that picks it.
81
+ * Hosts show their colour picker or palette for these.
82
+ */
83
+ export declare function imageToolUsesColor(tool: ImageEditorTool): boolean;
@@ -1,20 +1,49 @@
1
1
  /**
2
- * The image editor's tools as a toolbar shows them: grouped like Photoshop's (a slot per group, the
3
- * rest of the group in a flyout), with labels and shortcuts, the canvas mode each tool draws in and
4
- * the layer panel (`layerPanels`) holding its options. Hosts build their toolbar from these instead
5
- * of listing tools themselves, so new tools show up without host changes.
2
+ * The image editor's tools as a toolbar shows them: grouped by what each tool is for (a slot per
3
+ * group, the rest of the group in a flyout or the host's tool options), with labels and shortcuts,
4
+ * the canvas mode each tool draws in and the layer panel (`layerPanels`) holding its options. Hosts
5
+ * build their toolbar from these instead of listing tools themselves, so new tools show up without
6
+ * host changes.
6
7
  */
7
8
  import type { ImageEditorTool } from './types.js';
9
+ import { type SelectionToolKind } from './selection/tool-options.js';
10
+ import { type ImageLayer, type LiveShapeKind } from './layers/model.js';
11
+ import { type LayerPanelDescriptor, type LayerPanelsOptions } from './layers/panel.js';
12
+ /** Tools of a group shown together under a heading (Retouch's Fix, Tone and Detail). */
13
+ export interface ImageToolSection {
14
+ label: string;
15
+ tools: readonly ImageEditorTool[];
16
+ }
8
17
  export interface ImageToolGroup {
9
18
  id: string;
10
19
  label: string;
11
20
  tools: readonly ImageEditorTool[];
21
+ /** The group's tools split by purpose, in `tools` order; absent when the group is one set. */
22
+ sections?: readonly ImageToolSection[];
12
23
  }
13
24
  /**
14
- * Every tool a toolbar offers, in toolbar order. The `text` and `watermark` annotations are left
15
- * out: they are added through the API (`addAnnotation`), not drawn.
25
+ * Every tool a toolbar offers, in toolbar order, grouped by intent: choose (Move, Select, Crop),
26
+ * put paint down (Draw), take it away (Erase), retouch (Fix, Tone, Detail), build shapes, paths and
27
+ * type, and pick colours (Eyedropper). The `text` and `watermark` annotations are left out: they
28
+ * are added through the API (`addAnnotation`), not drawn. So are the `line`, `arrow`, `rect` and
29
+ * `circle` annotations, which the Shape tool's live shapes cover (they still draw for API callers).
16
30
  */
17
31
  export declare const IMAGE_TOOL_GROUPS: readonly ImageToolGroup[];
32
+ /**
33
+ * One way of using a tool that a toolbar offers as a tool of its own: a selection tool (`select`
34
+ * with `SelectionToolOptions.tool`) or a shape kind (`shape` with `ShapeToolOptions.kind`).
35
+ */
36
+ export type ImageToolVariant = {
37
+ tool: 'select';
38
+ id: SelectionToolKind;
39
+ label: string;
40
+ } | {
41
+ tool: 'shape';
42
+ id: LiveShapeKind;
43
+ label: string;
44
+ };
45
+ /** The variants a tool offers (the selection tools, the shape kinds), or none. */
46
+ export declare function imageToolVariants(tool: ImageEditorTool): readonly ImageToolVariant[];
18
47
  /** The group a tool is in (the first group for a tool no group holds). */
19
48
  export declare function imageToolGroup(tool: ImageEditorTool): ImageToolGroup;
20
49
  export declare function imageToolLabel(tool: ImageEditorTool): string;
@@ -23,13 +52,32 @@ export declare const IMAGE_TOOL_SHORTCUTS: Partial<Record<ImageEditorTool, strin
23
52
  /** The tool a shortcut picks (the first tool listed for a shared key), or null. */
24
53
  export declare function imageToolForShortcut(key: string, shift?: boolean): ImageEditorTool | null;
25
54
  /** How a canvas handles pointer gestures for a tool (the Svelte and React canvases' `mode`). */
26
- export type ImageToolCanvasMode = 'transform' | 'crop' | 'select' | 'paint' | 'annotate';
55
+ export type ImageToolCanvasMode = 'transform' | 'crop' | 'select' | 'paint' | 'annotate' | 'sample';
27
56
  /**
28
57
  * The canvas mode a tool works in. Move picks and transforms layers; shapes, type, the path tools
29
- * and the annotations draw in `annotate`.
58
+ * and the annotations draw in `annotate`; the Eyedropper picks colours in `sample`.
30
59
  */
31
60
  export declare function imageToolCanvasMode(tool: ImageEditorTool): ImageToolCanvasMode;
32
61
  /** The `layerPanels` panel holding a tool's options, or null when it has none. */
33
62
  export declare function imageToolPanel(tool: ImageEditorTool): string | null;
34
- /** The first tool whose options are in a panel (in toolbar order), or null. */
63
+ /**
64
+ * The panels that work with a tool: the one holding its options (`imageToolPanel`) and, for the
65
+ * selection tools, Channels (where a selection is saved and loaded).
66
+ */
67
+ export declare function imageToolPanelIds(tool: ImageEditorTool): string[];
68
+ /** The first tool whose panels include `panel` (in toolbar order), or null. */
35
69
  export declare function imagePanelTool(panel: string): ImageEditorTool | null;
70
+ /**
71
+ * The panels a sidebar shows for the active tool and layer: the tool's own panels first
72
+ * (`imageToolPanelIds`: Brush Options only with a paint tool, Selection and Channels with Select,
73
+ * Composition Size with Crop, Paths with the path tools, Shape Options or Typography with Shape or
74
+ * Type), then the active layer's (`layerPanels`: its properties, effects, colour adjustments, mask,
75
+ * and its shape or type options when it is a shape or type layer). Move shows the layer's panels only.
76
+ */
77
+ export declare function imageToolPanels(tool: ImageEditorTool, layer: ImageLayer | null, options?: LayerPanelsOptions): LayerPanelDescriptor[];
78
+ /**
79
+ * Tools that put down a colour the host chooses: the brush and mixer (the paint foreground), the
80
+ * pencil and annotations, shapes and type (their fill or colour), and the Eyedropper that picks it.
81
+ * Hosts show their colour picker or palette for these.
82
+ */
83
+ export declare function imageToolUsesColor(tool: ImageEditorTool): boolean;
@@ -282,7 +282,9 @@ export type ImageEditorTool = 'select' | 'crop' | 'pen' | 'rect' | 'circle' | 'a
282
282
  /** Live type layers, edited on the canvas (see `TypeToolGesture`). */
283
283
  | 'type'
284
284
  /** The path tools (see `PathEditGesture`): Path Selection, Direct Selection and the Pen for paths. */
285
- | 'path-select' | 'direct-select' | 'path-pen';
285
+ | 'path-select' | 'direct-select' | 'path-pen'
286
+ /** The Eyedropper: clicking the canvas picks the composite's colour as the paint foreground. */
287
+ | 'eyedropper';
286
288
  export type ImageEditorFit = 'contain' | 'margin';
287
289
  import type { DocumentColorMode, DocumentColorProfile, ProofSetup } from './color-management.cjs';
288
290
  import type { SelectionRecipeStep } from './selection/recipe.cjs';
@@ -282,7 +282,9 @@ export type ImageEditorTool = 'select' | 'crop' | 'pen' | 'rect' | 'circle' | 'a
282
282
  /** Live type layers, edited on the canvas (see `TypeToolGesture`). */
283
283
  | 'type'
284
284
  /** The path tools (see `PathEditGesture`): Path Selection, Direct Selection and the Pen for paths. */
285
- | 'path-select' | 'direct-select' | 'path-pen';
285
+ | 'path-select' | 'direct-select' | 'path-pen'
286
+ /** The Eyedropper: clicking the canvas picks the composite's colour as the paint foreground. */
287
+ | 'eyedropper';
286
288
  export type ImageEditorFit = 'contain' | 'margin';
287
289
  import type { DocumentColorMode, DocumentColorProfile, ProofSetup } from './color-management.js';
288
290
  import type { SelectionRecipeStep } from './selection/recipe.js';
@@ -7,7 +7,7 @@ var _chunkIGPEBXUDcjs = require('../chunk-IGPEBXUD.cjs');
7
7
 
8
8
 
9
9
 
10
- var _chunk2XGW6DKLcjs = require('../chunk-2XGW6DKL.cjs');
10
+ var _chunk5VQXSS2Scjs = require('../chunk-5VQXSS2S.cjs');
11
11
  require('../chunk-A2IS7EHE.cjs');
12
12
  require('../chunk-SS53NNZO.cjs');
13
13
 
@@ -1673,11 +1673,11 @@ function header(state, options) {
1673
1673
  return out;
1674
1674
  }
1675
1675
  function serializePaintDocument(state, options = {}) {
1676
- const { layers, assets } = _chunk2XGW6DKLcjs.serializeLayers.call(void 0, state.layers);
1676
+ const { layers, assets } = _chunk5VQXSS2Scjs.serializeLayers.call(void 0, state.layers);
1677
1677
  return { ...header(state, options), layers, assets };
1678
1678
  }
1679
1679
  async function serializePaintDocumentExternal(state, store, options = {}) {
1680
- const { layers, assets } = await _chunk2XGW6DKLcjs.serializeLayersExternal.call(void 0, state.layers, store);
1680
+ const { layers, assets } = await _chunk5VQXSS2Scjs.serializeLayersExternal.call(void 0, state.layers, store);
1681
1681
  return { ...header(state, options), layers, assets };
1682
1682
  }
1683
1683
  function paintLayersOnly(layers, problems) {
@@ -1714,7 +1714,7 @@ function deserializePaintDocument(raw) {
1714
1714
  const canvas = normalizeCanvas({ width: r.width, height: r.height, dpi: _nullishCoalesce(r.dpi, () => ( void 0)) });
1715
1715
  const paper = _chunkGNZI3KYKcjs.normalizePaper.call(void 0, _nullishCoalesce(r.paper, () => ( {})));
1716
1716
  const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
1717
- const layers = paintLayersOnly(_chunk2XGW6DKLcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1717
+ const layers = paintLayersOnly(_chunk5VQXSS2Scjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
1718
1718
  const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
1719
1719
  const activeLayerId = wanted && _chunkHB4ICNNAcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkHB4ICNNAcjs.flattenLayers.call(void 0, layers), 'access', _74 => _74.filter, 'call', _75 => _75((l) => l.type === "pixel"), 'access', _76 => _76.pop, 'call', _77 => _77(), 'optionalAccess', _78 => _78.id]), () => ( null));
1720
1720
  const wetEvents = Array.isArray(r.wetEvents) ? r.wetEvents.filter((e) => e && typeof e === "object" && Number.isInteger(e.clock) && typeof e.kind === "string") : [];
@@ -1730,7 +1730,7 @@ async function deserializePaintDocumentAsync(raw, store) {
1730
1730
  if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
1731
1731
  const r = raw;
1732
1732
  const problems = [];
1733
- const assets = await _chunk2XGW6DKLcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1733
+ const assets = await _chunk5VQXSS2Scjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
1734
1734
  const loaded = deserializePaintDocument({ ...r, assets });
1735
1735
  return { ...loaded, problems: [...problems, ...loaded.problems] };
1736
1736
  }
@@ -5649,7 +5649,7 @@ var PaintEditorController = (_class10 = class _PaintEditorController {
5649
5649
  return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
5650
5650
  });
5651
5651
  const layers = toStandard(dried);
5652
- const result = _chunk2XGW6DKLcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
5652
+ const result = _chunk5VQXSS2Scjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
5653
5653
  for (const [mode, names] of changed) {
5654
5654
  const label = _nullishCoalesce(_optionalChain([_chunkOLNIPTB3cjs.BLEND_MODES, 'access', _289 => _289.find, 'call', _290 => _290((m) => m.id === mode), 'optionalAccess', _291 => _291.label]), () => ( mode));
5655
5655
  const std = _nullishCoalesce(_optionalChain([_chunkOLNIPTB3cjs.BLEND_MODES, 'access', _292 => _292.find, 'call', _293 => _293((m) => m.id === _chunkOLNIPTB3cjs.standardBlendMode.call(void 0, mode)), 'optionalAccess', _294 => _294.label]), () => ( _chunkOLNIPTB3cjs.standardBlendMode.call(void 0, mode)));
@@ -7,7 +7,7 @@ import {
7
7
  resolveExternalAssets,
8
8
  serializeLayers,
9
9
  serializeLayersExternal
10
- } from "../chunk-SWKXPAJC.js";
10
+ } from "../chunk-JVNGCS26.js";
11
11
  import "../chunk-3CI7FRMK.js";
12
12
  import "../chunk-ZNA3IQZF.js";
13
13
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.228.2",
3
+ "version": "0.229.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -311,6 +311,7 @@ in the source frame at the document's bit depth, colour mode and profile. Read
311
311
  | `set_global_light` | Set the document light (`angle`, `altitude` in degrees) that every shadow and bevel with `useGlobalLight` follows. Returns the light. |
312
312
  | `paint_stroke` | Paint (`tool` brush), blend with the Mixer Brush (`tool` mixer) or erase (`tool` eraser) along `points` in document px (optional `pressure` 0..1) on a pixel layer or its mask (`channel`; the mixer paints pixels only); `color` #rrggbb (on a mask: black hides, white reveals; for the mixer, the reservoir colour) or `paint` (brush only: a gradient for this stroke); `brush` `{size, hardness, spacing, angle, roundness, opacity, flow, smoothing, pressureSize, pressureOpacity, blend}`; `mixer` `{wet, load, mix, loadEachStroke, cleanEachStroke, mixing: rgb \| pigment}` (tool mixer only). The selection limits the paint. One undo step per call. Returns the layer, channel, changed `bounds`, dab count and, for the mixer, `mixerLoad` `{reservoir, amount, pickup}`. |
313
313
  | `set_paint` | Set what the brush paints: one colour (it becomes the foreground) or gradient paint (`stops` sorted by offset, `space` pigment / oklab / srgb, `map`, `softness`). Errors say what is wrong. Returns `paint`, `foreground`, `background`, `brush`. |
314
+ | `sample_color` | The Eyedropper: read the composite colour at a document point (`size` averages a square; default 1 px) and, unless `setForeground` is false, make it the paint foreground. Use it to match a colour already in the image before painting or retouching. Fails outside the canvas or on transparent pixels. Returns `color` and `foreground`. |
314
315
  | `get_paint` | Read-only. What the brush paints (`paint`), the foreground and background colours and the brush settings. |
315
316
  | `get_mixer_brush` | Read-only. The Mixer Brush settings and what it carries (`reservoir` #rrggbb or null, `amount` left 0..1, `pickup` #rrggbb or null). Call it before a mixer stroke with `loadEachStroke: false`. |
316
317
  | `load_mixer_brush` | Fill the Mixer Brush reservoir with `color` (default the foreground), amount 1; keeps the pickup. View state, not an undo step. Use before strokes with `loadEachStroke: false`. Returns the load. |