@pixerate/editor 0.206.0 → 0.207.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.
@@ -5320,7 +5320,7 @@ function segmentsToSelection(width, height, results) {
5320
5320
  }
5321
5321
 
5322
5322
  // src/image-editor/selection/refine-edits.ts
5323
- var REFINE_EDIT_KINDS = ["quick-select", "brush", "refine-brush"];
5323
+ var REFINE_EDIT_KINDS = ["quick-select", "brush", "refine-brush", "lasso", "object"];
5324
5324
  function brushStrokeMask(width, height, points, size, hardness = 1) {
5325
5325
  const r = Math.max(0.5, size / 2);
5326
5326
  const solid = r * Math.min(1, Math.max(0, hardness));
@@ -5361,7 +5361,19 @@ function applyRefineEdits(pixels, selection, edits, width, height) {
5361
5361
  let sel = selection?.bounds ? selection : null;
5362
5362
  let band = null;
5363
5363
  for (const e of edits) {
5364
+ if (e.kind === "object") {
5365
+ if (!pixels) throw new Error("Object Selection needs the image pixels.");
5366
+ if (!e.rect === !e.point) throw new Error("An object edit takes rect (a box around the object) or point (on it), not both.");
5367
+ const region2 = selectObjectIn(pixels, e.rect ? { rect: e.rect } : { point: e.point });
5368
+ sel = combineSelections(sel, region2, e.mode ?? "add");
5369
+ continue;
5370
+ }
5364
5371
  if (!e.points.length) throw new Error(`A ${e.kind} stroke needs at least one point.`);
5372
+ if (e.kind === "lasso") {
5373
+ if (e.points.length < 3) throw new Error("A lasso edit needs at least 3 points.");
5374
+ sel = combineSelections(sel, polygonSelection(width, height, e.points, {}), e.mode ?? "add");
5375
+ continue;
5376
+ }
5365
5377
  if (e.kind === "refine-brush") {
5366
5378
  band = combineSelections(band, brushStrokeMask(width, height, e.points, e.size ?? 40), "add");
5367
5379
  continue;
@@ -13709,13 +13721,25 @@ function createImageSelectionTools(controller) {
13709
13721
  items: {
13710
13722
  type: "object",
13711
13723
  properties: {
13712
- kind: { type: "string", enum: ["quick-select", "brush", "refine-brush"], description: "quick-select grows to edges; brush paints directly; refine-brush marks soft detail (hair) for the edge to be re-estimated." },
13713
- points: { type: "array", minItems: 1, items: pointSchema, description: "The stroke path in natural image pixels (one point is a dab)." },
13724
+ kind: {
13725
+ type: "string",
13726
+ enum: ["quick-select", "brush", "refine-brush", "lasso", "object"],
13727
+ description: "quick-select grows to edges; brush paints directly; refine-brush marks soft detail (hair) for the edge to be re-estimated; lasso adds or removes a polygon; object adds or removes the object in rect or under point."
13728
+ },
13729
+ points: { type: "array", minItems: 1, items: pointSchema, description: "The stroke path (or lasso outline, 3+ points) in natural image pixels; one point is a dab. Not for object." },
13730
+ rect: {
13731
+ type: "object",
13732
+ properties: rectProperties,
13733
+ required: ["x", "y", "width", "height"],
13734
+ additionalProperties: false,
13735
+ description: "object: a box around the object, in natural image pixels."
13736
+ },
13737
+ point: { ...pointSchema, description: "object: a point on the object, in natural image pixels." },
13714
13738
  size: { type: "number", minimum: 1, maximum: 2500, description: "Brush diameter in natural image pixels (default 30 quick-select, 20 brush, 40 refine-brush)." },
13715
- mode: { type: "string", enum: ["add", "subtract"], description: "quick-select and brush: add (default) or subtract." },
13739
+ mode: { type: "string", enum: ["add", "subtract"], description: "quick-select, brush, lasso and object: add (default) or subtract." },
13716
13740
  hardness: { type: "number", minimum: 0, maximum: 1, description: "brush: the solid share of the radius, 0..1 (default 1)." }
13717
13741
  },
13718
- required: ["kind", "points"],
13742
+ required: ["kind"],
13719
13743
  additionalProperties: false
13720
13744
  }
13721
13745
  },
@@ -13725,8 +13749,13 @@ function createImageSelectionTools(controller) {
13725
13749
  },
13726
13750
  annotations: writeAnnotations("Refine the selection edge"),
13727
13751
  handler: async (params) => {
13728
- const edits = Array.isArray(params.edits) ? params.edits.map((e) => ({ ...e, points: (e.points ?? []).map((p) => ({ x: num2(p?.x, NaN), y: num2(p?.y, NaN) })) })) : [];
13752
+ const edits = Array.isArray(params.edits) ? params.edits.map((e) => e.kind === "object" ? { ...e } : { ...e, points: (e.points ?? []).map((p) => ({ x: num2(p?.x, NaN), y: num2(p?.y, NaN) })) }) : [];
13729
13753
  for (const e of edits) {
13754
+ if (e.kind === "object") {
13755
+ if (!e.rect === !e.point) throw new Error("An object edit takes rect (a box around the object) or point (on it).");
13756
+ continue;
13757
+ }
13758
+ if (!e.points.length) throw new Error(`A ${e.kind} edit needs points.`);
13730
13759
  if (!e.points.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) throw new Error("Each edit needs points of { x, y } in natural image pixels.");
13731
13760
  if (e.kind === "refine-brush" && e.mode !== void 0) throw new Error("refine-brush strokes have no mode; they mark where the edge is re-estimated.");
13732
13761
  }
@@ -19127,6 +19156,374 @@ function downloadMaskImage(controller, source, name = "mask") {
19127
19156
  return { fileName, width, height };
19128
19157
  }
19129
19158
 
19159
+ // src/image-editor/selection/select-and-mask.ts
19160
+ var SELECT_AND_MASK_VIEWS = [
19161
+ { id: "onion-skin", label: "Onion Skin", key: "o", opacity: 0.5 },
19162
+ { id: "marching-ants", label: "Marching Ants", key: "m", opacity: 1 },
19163
+ { id: "overlay", label: "Overlay", key: "v", opacity: 0.5 },
19164
+ { id: "on-black", label: "On Black", key: "a", opacity: 1 },
19165
+ { id: "on-white", label: "On White", key: "t", opacity: 1 },
19166
+ { id: "black-white", label: "Black & White", key: "k", opacity: 1 },
19167
+ { id: "on-layers", label: "On Layers", key: "y", opacity: 1 }
19168
+ ];
19169
+ var SELECT_AND_MASK_TOOLS = [
19170
+ { id: "quick-select", label: "Quick Selection", key: "w" },
19171
+ { id: "refine-brush", label: "Refine Edge Brush", key: "r" },
19172
+ { id: "brush", label: "Brush", key: "b" },
19173
+ { id: "object", label: "Object Selection", key: "q" },
19174
+ { id: "lasso", label: "Lasso", key: "l" },
19175
+ { id: "hand", label: "Hand", key: "h" },
19176
+ { id: "zoom", label: "Zoom", key: "z" }
19177
+ ];
19178
+ var DEFAULT_SELECT_AND_MASK_SETTINGS = {
19179
+ params: { ...DEFAULT_REFINE_EDGE_PARAMS },
19180
+ output: "selection",
19181
+ decontaminate: 0,
19182
+ refineMode: "color",
19183
+ view: "onion-skin",
19184
+ opacity: 0.5,
19185
+ showEdge: false,
19186
+ showOriginal: false,
19187
+ realTime: true,
19188
+ highQuality: false,
19189
+ remember: false,
19190
+ tool: "quick-select",
19191
+ brushSize: 30,
19192
+ brushMode: "add"
19193
+ };
19194
+ var PREVIEW_SIZE = 1024;
19195
+ var remembered = null;
19196
+ function scalePixels(pixels, w, h) {
19197
+ if (w === pixels.width && h === pixels.height) return pixels;
19198
+ const { width: W, height: H, data } = pixels;
19199
+ const sum = new Float64Array(w * h * 4);
19200
+ const count = new Float64Array(w * h);
19201
+ for (let y = 0; y < H; y++) {
19202
+ const row = Math.min(h - 1, Math.floor(y * h / H)) * w;
19203
+ for (let x = 0; x < W; x++) {
19204
+ const o = row + Math.min(w - 1, Math.floor(x * w / W));
19205
+ const p = (y * W + x) * 4;
19206
+ for (let c = 0; c < 4; c++) sum[o * 4 + c] += data[p + c];
19207
+ count[o]++;
19208
+ }
19209
+ }
19210
+ const out = new Uint8ClampedArray(w * h * 4);
19211
+ for (let i = 0; i < w * h; i++) for (let c = 0; c < 4; c++) out[i * 4 + c] = sum[i * 4 + c] / (count[i] || 1);
19212
+ return { width: w, height: h, data: out };
19213
+ }
19214
+ function scaleEdit(edit, s) {
19215
+ const pt = (p) => ({ x: p.x * s, y: p.y * s });
19216
+ if (edit.kind === "object") {
19217
+ return { ...edit, rect: edit.rect && { x: edit.rect.x * s, y: edit.rect.y * s, width: edit.rect.width * s, height: edit.rect.height * s }, point: edit.point && pt(edit.point) };
19218
+ }
19219
+ const scaled = { ...edit, points: edit.points.map(pt) };
19220
+ if ("size" in edit && edit.size !== void 0) scaled.size = Math.max(1, edit.size * s);
19221
+ return scaled;
19222
+ }
19223
+ function scaleParams(p, s) {
19224
+ return { ...p, radius: p.radius * s, feather: p.feather * s };
19225
+ }
19226
+ var SelectAndMaskSession = class {
19227
+ constructor(controller) {
19228
+ this.controller = controller;
19229
+ this.base = controller.getSelection();
19230
+ this.settingsValue = remembered ? { ...remembered, params: { ...remembered.params }, showOriginal: false } : { ...DEFAULT_SELECT_AND_MASK_SETTINGS, params: { ...DEFAULT_SELECT_AND_MASK_SETTINGS.params } };
19231
+ }
19232
+ controller;
19233
+ settingsValue;
19234
+ editsList = [];
19235
+ /** The edit being painted, shown in the preview but not yet in `edits`. */
19236
+ live = null;
19237
+ dragStart = null;
19238
+ pixels = null;
19239
+ previewPixels = null;
19240
+ base;
19241
+ previewMask = null;
19242
+ baseMask = null;
19243
+ listeners = /* @__PURE__ */ new Set();
19244
+ /** Why the preview could not be made (no pixels), for the UI. */
19245
+ error = null;
19246
+ /** A note for the UI, such as object aware refinement applying only on OK. */
19247
+ note = null;
19248
+ /** Bumped each time the preview changes, so a UI can redraw. */
19249
+ revision = 0;
19250
+ get settings() {
19251
+ return this.settingsValue;
19252
+ }
19253
+ get edits() {
19254
+ return this.editsList;
19255
+ }
19256
+ /** Reads the image and makes the first preview. Resolves to false when the pixels are unavailable. */
19257
+ async open() {
19258
+ try {
19259
+ this.pixels = await this.controller.getSamplePixels({});
19260
+ } catch (err) {
19261
+ this.error = err instanceof Error ? err.message : String(err);
19262
+ }
19263
+ if (!this.pixels) {
19264
+ this.error ??= "The image pixels are not available, so Select and Mask cannot preview.";
19265
+ this.emit();
19266
+ return false;
19267
+ }
19268
+ this.rescale();
19269
+ this.recompute();
19270
+ return true;
19271
+ }
19272
+ subscribe(listener) {
19273
+ this.listeners.add(listener);
19274
+ return () => this.listeners.delete(listener);
19275
+ }
19276
+ emit() {
19277
+ this.revision++;
19278
+ for (const l of this.listeners) l();
19279
+ }
19280
+ /** Preview size and the scale from natural pixels to it. */
19281
+ get previewSize() {
19282
+ const p = this.previewPixels;
19283
+ if (!p || !this.pixels) return { width: 0, height: 0, scale: 1 };
19284
+ return { width: p.width, height: p.height, scale: p.width / this.pixels.width };
19285
+ }
19286
+ /** Changes settings; the preview follows. Choosing a view also sets its usual opacity. */
19287
+ update(changes) {
19288
+ const next = { ...this.settingsValue, ...changes, params: { ...this.settingsValue.params, ...changes.params ?? {} } };
19289
+ if (changes.view && changes.view !== this.settingsValue.view && changes.opacity === void 0) {
19290
+ next.opacity = SELECT_AND_MASK_VIEWS.find((v) => v.id === changes.view).opacity;
19291
+ }
19292
+ const rescale = next.highQuality !== this.settingsValue.highQuality;
19293
+ this.settingsValue = next;
19294
+ if (rescale) this.rescale();
19295
+ this.recompute();
19296
+ }
19297
+ rescale() {
19298
+ const px2 = this.pixels;
19299
+ if (!px2) return;
19300
+ const s = this.settingsValue.highQuality ? 1 : Math.min(1, PREVIEW_SIZE / Math.max(px2.width, px2.height));
19301
+ const w = Math.max(1, Math.round(px2.width * s));
19302
+ const h = Math.max(1, Math.round(px2.height * s));
19303
+ this.previewPixels = scalePixels(px2, w, h);
19304
+ this.baseMask = this.base?.bounds ? s === 1 ? this.base : resizeSelection(this.base, w, h) : null;
19305
+ }
19306
+ /** Recomputes the preview mask from the base selection, the strokes and the settings. */
19307
+ recompute() {
19308
+ const px2 = this.previewPixels;
19309
+ if (!px2) return;
19310
+ const { scale } = this.previewSize;
19311
+ const edits = [...this.editsList, ...this.live ? [this.live] : []].map((e) => scaleEdit(e, scale));
19312
+ this.note = this.settingsValue.refineMode === "object" ? "Object aware refinement asks the segmentation model on OK; the preview refines by colour." : null;
19313
+ try {
19314
+ const { selection, refineBrush } = applyRefineEdits(px2, this.baseMask, edits, px2.width, px2.height);
19315
+ this.previewMask = selection?.bounds ? refineSelection(px2, selection, scaleParams(this.settingsValue.params, scale), refineBrush) : null;
19316
+ this.error = null;
19317
+ } catch (err) {
19318
+ this.previewMask = null;
19319
+ this.error = err instanceof Error ? err.message : String(err);
19320
+ }
19321
+ this.emit();
19322
+ }
19323
+ /** Coverage 0..1 of the preview at (x, y) in preview pixels (for tests and hover readouts). */
19324
+ coverageAt(x, y) {
19325
+ const m = this.settingsValue.showOriginal ? this.baseMask : this.previewMask;
19326
+ if (!m) return 0;
19327
+ const xi = Math.floor(x);
19328
+ const yi = Math.floor(y);
19329
+ if (xi < 0 || yi < 0 || xi >= m.width || yi >= m.height) return 0;
19330
+ return m.data[yi * m.width + xi] / 255;
19331
+ }
19332
+ /** The preview drawn in the current view mode: RGBA at {@link previewSize}. */
19333
+ render() {
19334
+ const px2 = this.previewPixels;
19335
+ if (!px2) return new Uint8ClampedArray(0);
19336
+ const { width: w, height: h } = px2;
19337
+ const n = w * h;
19338
+ const s = this.settingsValue;
19339
+ const mask = (s.showOriginal ? this.baseMask : this.previewMask) ?? createSelectionMask(w, h);
19340
+ const m = mask.data;
19341
+ const out = new Uint8ClampedArray(n * 4);
19342
+ const k = Math.min(1, Math.max(0, s.opacity));
19343
+ let edgeBand = null;
19344
+ if (s.showEdge) {
19345
+ edgeBand = new Uint8Array(n);
19346
+ const r = Math.max(1, Math.round(Math.max(s.params.radius, 2) * this.previewSize.scale));
19347
+ for (let y = 0; y < h; y++) {
19348
+ for (let x = 0; x < w; x++) {
19349
+ const v = m[y * w + x];
19350
+ if (v <= 3 || v >= 252) continue;
19351
+ for (let j = Math.max(0, y - r); j <= Math.min(h - 1, y + r); j++) edgeBand.fill(1, j * w + Math.max(0, x - r), j * w + Math.min(w, x + r + 1));
19352
+ }
19353
+ }
19354
+ }
19355
+ for (let i = 0; i < n; i++) {
19356
+ const a = m[i] / 255;
19357
+ const p = i * 4;
19358
+ const r = px2.data[p];
19359
+ const g = px2.data[p + 1];
19360
+ const b = px2.data[p + 2];
19361
+ let o;
19362
+ const x = i % w;
19363
+ const y = (i - x) / w;
19364
+ const checker = ((x >> 3) + (y >> 3)) % 2 ? 204 : 255;
19365
+ const hide = (1 - a) * k;
19366
+ switch (s.view) {
19367
+ case "black-white":
19368
+ o = [m[i], m[i], m[i]];
19369
+ break;
19370
+ case "overlay":
19371
+ o = [r + (255 - r) * hide, g * (1 - hide), b * (1 - hide)];
19372
+ break;
19373
+ case "on-black":
19374
+ o = [r * (1 - hide), g * (1 - hide), b * (1 - hide)];
19375
+ break;
19376
+ case "on-white":
19377
+ o = [r + (255 - r) * hide, g + (255 - g) * hide, b + (255 - b) * hide];
19378
+ break;
19379
+ case "marching-ants": {
19380
+ o = [r, g, b];
19381
+ const inside = m[i] >= 128;
19382
+ const edge = x > 0 && m[i - 1] >= 128 !== inside || x + 1 < w && m[i + 1] >= 128 !== inside || y > 0 && m[i - w] >= 128 !== inside || y + 1 < h && m[i + w] >= 128 !== inside;
19383
+ if (edge) {
19384
+ const v = (x + y >> 2) % 2 ? 0 : 255;
19385
+ o = [v, v, v];
19386
+ }
19387
+ break;
19388
+ }
19389
+ default:
19390
+ o = [r + (checker - r) * hide, g + (checker - g) * hide, b + (checker - b) * hide];
19391
+ }
19392
+ if (edgeBand && !edgeBand[i]) o = [o[0] * 0.15, o[1] * 0.15, o[2] * 0.15];
19393
+ out[p] = o[0];
19394
+ out[p + 1] = o[1];
19395
+ out[p + 2] = o[2];
19396
+ out[p + 3] = 255;
19397
+ }
19398
+ return out;
19399
+ }
19400
+ /**
19401
+ * Pointer down at `point` (natural pixels) with the current tool: starts a stroke (Quick
19402
+ * Selection, brushes, lasso) or a box (Object Selection). Alt (or the subtract mode) removes.
19403
+ * Hand and Zoom are the UI's own; they return false here.
19404
+ */
19405
+ begin(point, modifiers = {}) {
19406
+ const s = this.settingsValue;
19407
+ const mode = modifiers.altKey ? s.brushMode === "add" ? "subtract" : "add" : s.brushMode;
19408
+ this.dragStart = point;
19409
+ switch (s.tool) {
19410
+ case "quick-select":
19411
+ this.live = { kind: "quick-select", points: [point], size: s.brushSize, mode };
19412
+ break;
19413
+ case "brush":
19414
+ this.live = { kind: "brush", points: [point], size: s.brushSize, mode };
19415
+ break;
19416
+ case "refine-brush":
19417
+ this.live = { kind: "refine-brush", points: [point], size: s.brushSize };
19418
+ break;
19419
+ case "lasso":
19420
+ this.live = { kind: "lasso", points: [point], mode };
19421
+ break;
19422
+ case "object":
19423
+ this.live = { kind: "object", point, mode };
19424
+ break;
19425
+ default:
19426
+ this.dragStart = null;
19427
+ return false;
19428
+ }
19429
+ if (s.realTime && s.tool !== "lasso" && s.tool !== "object") this.recompute();
19430
+ return true;
19431
+ }
19432
+ /** Pointer move while a stroke is down. */
19433
+ move(point) {
19434
+ const live = this.live;
19435
+ if (!live || !this.dragStart) return;
19436
+ if (live.kind === "object") {
19437
+ const a = this.dragStart;
19438
+ const rect = { x: Math.min(a.x, point.x), y: Math.min(a.y, point.y), width: Math.abs(point.x - a.x), height: Math.abs(point.y - a.y) };
19439
+ this.live = rect.width >= 4 && rect.height >= 4 ? { kind: "object", rect, mode: live.mode } : { kind: "object", point: a, mode: live.mode };
19440
+ return;
19441
+ }
19442
+ const last = live.points[live.points.length - 1];
19443
+ if (Math.hypot(point.x - last.x, point.y - last.y) < 1) return;
19444
+ live.points.push(point);
19445
+ if (this.settingsValue.realTime && live.kind !== "lasso") this.recompute();
19446
+ }
19447
+ /** Pointer up: the stroke becomes an edit (one step of the workspace's own undo). */
19448
+ end() {
19449
+ const live = this.live;
19450
+ this.live = null;
19451
+ this.dragStart = null;
19452
+ if (!live) return;
19453
+ if (live.kind === "lasso" && live.points.length < 3) return;
19454
+ this.editsList.push(live);
19455
+ this.recompute();
19456
+ }
19457
+ /** Removes the last stroke (Ctrl/⌘+Z in the workspace). Returns false when there is none. */
19458
+ undoEdit() {
19459
+ if (!this.editsList.length) return false;
19460
+ this.editsList.pop();
19461
+ this.recompute();
19462
+ return true;
19463
+ }
19464
+ /** Clears the strokes and goes back to the default settings (Reset). */
19465
+ reset() {
19466
+ this.editsList.length = 0;
19467
+ this.settingsValue = { ...DEFAULT_SELECT_AND_MASK_SETTINGS, params: { ...DEFAULT_SELECT_AND_MASK_SETTINGS.params }, tool: this.settingsValue.tool };
19468
+ this.rescale();
19469
+ this.recompute();
19470
+ }
19471
+ /**
19472
+ * Photoshop's keys in the workspace: a view letter, F (next view), X (all views off and on:
19473
+ * Show Original), J (Show Edge), P (Show Original), a tool letter, [ and ] (brush size).
19474
+ * Returns true when the key was used.
19475
+ */
19476
+ handleKey(key) {
19477
+ const k = key.toLowerCase();
19478
+ const view = SELECT_AND_MASK_VIEWS.find((v) => v.key === k);
19479
+ if (view) {
19480
+ this.update({ view: view.id });
19481
+ return true;
19482
+ }
19483
+ const tool = SELECT_AND_MASK_TOOLS.find((t) => t.key === k);
19484
+ if (tool) {
19485
+ this.update({ tool: tool.id });
19486
+ return true;
19487
+ }
19488
+ if (k === "f") {
19489
+ const i = SELECT_AND_MASK_VIEWS.findIndex((v) => v.id === this.settingsValue.view);
19490
+ this.update({ view: SELECT_AND_MASK_VIEWS[(i + 1) % SELECT_AND_MASK_VIEWS.length].id });
19491
+ return true;
19492
+ }
19493
+ if (k === "p" || k === "x") {
19494
+ this.update({ showOriginal: !this.settingsValue.showOriginal });
19495
+ return true;
19496
+ }
19497
+ if (k === "j") {
19498
+ this.update({ showEdge: !this.settingsValue.showEdge });
19499
+ return true;
19500
+ }
19501
+ if (k === "[" || k === "]") {
19502
+ const size = this.settingsValue.brushSize;
19503
+ this.settingsValue = { ...this.settingsValue, brushSize: Math.max(1, Math.round(k === "]" ? size * 1.25 : size / 1.25)) };
19504
+ this.emit();
19505
+ return true;
19506
+ }
19507
+ return false;
19508
+ }
19509
+ /**
19510
+ * OK: applies the strokes and settings to the image as one undo step (the same call an agent
19511
+ * makes) and remembers the settings when asked to.
19512
+ */
19513
+ async commit() {
19514
+ const s = this.settingsValue;
19515
+ remembered = s.remember ? { ...s, params: { ...s.params } } : null;
19516
+ return this.controller.refineSelectionTo(s.params, s.output, {
19517
+ edits: [...this.editsList],
19518
+ decontaminate: s.output === "new-layer" || s.output === "new-layer-with-mask" ? s.decontaminate : 0,
19519
+ refineMode: s.refineMode
19520
+ });
19521
+ }
19522
+ };
19523
+ function forgetSelectAndMaskSettings() {
19524
+ remembered = null;
19525
+ }
19526
+
19130
19527
  // src/image-editor/mosaic-filter.ts
19131
19528
  function resolveMosaicRegionBounds(region, imgWidth, imgHeight) {
19132
19529
  if (imgWidth <= 0 || imgHeight <= 0 || region.width <= 0 || region.height <= 0) {
@@ -20826,6 +21223,7 @@ export {
20826
21223
  DEFAULT_REFINE_EDGE_PARAMS,
20827
21224
  DEFAULT_RETOUCH,
20828
21225
  DEFAULT_SELECTION_TOOL_OPTIONS,
21226
+ DEFAULT_SELECT_AND_MASK_SETTINGS,
20829
21227
  DEFAULT_SHAPE_TOOL_OPTIONS,
20830
21228
  DEFAULT_SMUDGE,
20831
21229
  DEFAULT_TEXT_STYLE,
@@ -20943,6 +21341,7 @@ export {
20943
21341
  PIGMENT_WAVELENGTHS,
20944
21342
  PLANE,
20945
21343
  PLANE_BYTES,
21344
+ PREVIEW_SIZE,
20946
21345
  PROOF_PROFILE_OPTIONS,
20947
21346
  PSD_BLEND_KEYS,
20948
21347
  PSD_FILE_ACCEPT,
@@ -20980,6 +21379,8 @@ export {
20980
21379
  SELECTION_MODE_LABELS,
20981
21380
  SELECTION_MODIFY_OPS,
20982
21381
  SELECTION_TOOLS,
21382
+ SELECT_AND_MASK_TOOLS,
21383
+ SELECT_AND_MASK_VIEWS,
20983
21384
  SET_PLANES,
20984
21385
  SHADOW_AMBIENT,
20985
21386
  SHAPE_KIND_LABELS,
@@ -20993,6 +21394,7 @@ export {
20993
21394
  SRGB_TO_LINEAR,
20994
21395
  STRIP,
20995
21396
  SUPPORTED_ADJUSTMENT_KEYS,
21397
+ SelectAndMaskSession,
20996
21398
  SelectionGesture,
20997
21399
  ShapeDrawGesture,
20998
21400
  SmudgeTip,
@@ -21314,6 +21716,7 @@ export {
21314
21716
  focusArea,
21315
21717
  followLayerMove,
21316
21718
  fontOf,
21719
+ forgetSelectAndMaskSettings,
21317
21720
  formatControlValue,
21318
21721
  fullSelection,
21319
21722
  gaussianBlur,
@@ -15,3 +15,4 @@ export * from './path.cjs';
15
15
  export * from './magnetic.cjs';
16
16
  export * from './segment/index.cjs';
17
17
  export * from './refine-edits.cjs';
18
+ export * from './select-and-mask.cjs';
@@ -15,3 +15,4 @@ export * from './path.js';
15
15
  export * from './magnetic.js';
16
16
  export * from './segment/index.js';
17
17
  export * from './refine-edits.js';
18
+ export * from './select-and-mask.js';
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The Select and Mask workspace's strokes as plain data, so an agent can send what a person
3
3
  * paints: Quick Selection strokes (grow to the region's edges), Brush strokes (paint the
4
- * selection in or out directly) and Refine Edge Brush strokes (mark where the edge should be
5
- * re-estimated from the image, e.g. over hair).
4
+ * selection in or out directly), Refine Edge Brush strokes (mark where the edge should be
5
+ * re-estimated from the image, e.g. over hair), Lasso outlines and Object Selection boxes.
6
6
  */
7
- import type { ImagePoint } from '../types.cjs';
7
+ import type { CropBox, ImagePoint } from '../types.cjs';
8
8
  import { type PixelBuffer, type SelectionMask } from './mask.cjs';
9
9
  /** One stroke in the workspace, in natural image pixels. */
10
10
  export type RefineEdit = {
@@ -22,8 +22,17 @@ export type RefineEdit = {
22
22
  kind: 'refine-brush';
23
23
  points: ImagePoint[];
24
24
  size?: number;
25
+ } | {
26
+ kind: 'lasso';
27
+ points: ImagePoint[];
28
+ mode?: 'add' | 'subtract';
29
+ } | {
30
+ kind: 'object';
31
+ rect?: CropBox;
32
+ point?: ImagePoint;
33
+ mode?: 'add' | 'subtract';
25
34
  };
26
- export declare const REFINE_EDIT_KINDS: readonly ["quick-select", "brush", "refine-brush"];
35
+ export declare const REFINE_EDIT_KINDS: readonly ["quick-select", "brush", "refine-brush", "lasso", "object"];
27
36
  /**
28
37
  * Coverage of a round brush of diameter `size` dragged along `points` (one point is a dab), over
29
38
  * a `width × height` image. `hardness` 0..1 (default 1): the solid share of the radius; the rest
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The Select and Mask workspace's strokes as plain data, so an agent can send what a person
3
3
  * paints: Quick Selection strokes (grow to the region's edges), Brush strokes (paint the
4
- * selection in or out directly) and Refine Edge Brush strokes (mark where the edge should be
5
- * re-estimated from the image, e.g. over hair).
4
+ * selection in or out directly), Refine Edge Brush strokes (mark where the edge should be
5
+ * re-estimated from the image, e.g. over hair), Lasso outlines and Object Selection boxes.
6
6
  */
7
- import type { ImagePoint } from '../types.js';
7
+ import type { CropBox, ImagePoint } from '../types.js';
8
8
  import { type PixelBuffer, type SelectionMask } from './mask.js';
9
9
  /** One stroke in the workspace, in natural image pixels. */
10
10
  export type RefineEdit = {
@@ -22,8 +22,17 @@ export type RefineEdit = {
22
22
  kind: 'refine-brush';
23
23
  points: ImagePoint[];
24
24
  size?: number;
25
+ } | {
26
+ kind: 'lasso';
27
+ points: ImagePoint[];
28
+ mode?: 'add' | 'subtract';
29
+ } | {
30
+ kind: 'object';
31
+ rect?: CropBox;
32
+ point?: ImagePoint;
33
+ mode?: 'add' | 'subtract';
25
34
  };
26
- export declare const REFINE_EDIT_KINDS: readonly ["quick-select", "brush", "refine-brush"];
35
+ export declare const REFINE_EDIT_KINDS: readonly ["quick-select", "brush", "refine-brush", "lasso", "object"];
27
36
  /**
28
37
  * Coverage of a round brush of diameter `size` dragged along `points` (one point is a dab), over
29
38
  * a `width × height` image. `hardness` 0..1 (default 1): the solid share of the radius; the rest