@pixerate/editor 0.200.0 → 0.202.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.js +1 -1
  3. package/dist/{chunk-GLQQ2VIL.cjs → chunk-53GOGBTM.cjs} +23 -1
  4. package/dist/{chunk-3TNA7JKI.js → chunk-ZPJEH456.js} +22 -0
  5. package/dist/commands/builtins.d.cts +6 -0
  6. package/dist/commands/builtins.d.ts +6 -0
  7. package/dist/commands/index.cjs +4 -2
  8. package/dist/commands/index.js +4 -2
  9. package/dist/image-editor/controller.d.cts +18 -0
  10. package/dist/image-editor/controller.d.ts +18 -0
  11. package/dist/image-editor/index.cjs +180 -19
  12. package/dist/image-editor/index.js +179 -18
  13. package/dist/image-editor/selection/segment/index.d.cts +1 -0
  14. package/dist/image-editor/selection/segment/index.d.ts +1 -0
  15. package/dist/image-editor/selection/segment/sky.d.cts +21 -0
  16. package/dist/image-editor/selection/segment/sky.d.ts +21 -0
  17. package/dist/paint-editor/guide-tools.d.cts +2 -2
  18. package/dist/paint-editor/guide-tools.d.ts +2 -2
  19. package/dist/paint-editor/hosted.d.cts +34 -0
  20. package/dist/paint-editor/hosted.d.ts +34 -0
  21. package/dist/paint-editor/index.cjs +101 -10
  22. package/dist/paint-editor/index.d.cts +1 -0
  23. package/dist/paint-editor/index.d.ts +1 -0
  24. package/dist/paint-editor/index.js +96 -5
  25. package/dist/paint-editor/sticker-tools.d.cts +2 -2
  26. package/dist/paint-editor/sticker-tools.d.ts +2 -2
  27. package/dist/paint-editor/thick-tools.d.cts +2 -2
  28. package/dist/paint-editor/thick-tools.d.ts +2 -2
  29. package/dist/paint-editor/tools.d.cts +19 -2
  30. package/dist/paint-editor/tools.d.ts +19 -2
  31. package/dist/paint-editor/wet-tools.d.cts +2 -2
  32. package/dist/paint-editor/wet-tools.d.ts +2 -2
  33. package/package.json +1 -1
  34. package/skills/pixerate-image-editor/SKILL.md +1 -0
  35. package/skills/pixerate-image-selection-masking/SKILL.md +4 -2
  36. package/skills/pixerate-paint-editor/SKILL.md +9 -0
@@ -3692,14 +3692,14 @@ function toLab(r, g, b) {
3692
3692
  function slicSuperpixels(work, target = 400, compactness = 10, rounds = 5) {
3693
3693
  const { width: w, height: h, rgb } = work;
3694
3694
  const n = w * h;
3695
- const lab = new Float32Array(n * 3);
3696
- for (let i = 0; i < n; i++) lab.set(toLab(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]), i * 3);
3695
+ const lab2 = new Float32Array(n * 3);
3696
+ for (let i = 0; i < n; i++) lab2.set(toLab(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]), i * 3);
3697
3697
  const S = Math.max(2, Math.round(Math.sqrt(n / target)));
3698
3698
  const seeds = [];
3699
3699
  for (let y = Math.floor(S / 2); y < h; y += S) {
3700
3700
  for (let x = Math.floor(S / 2); x < w; x += S) {
3701
3701
  const i = y * w + x;
3702
- seeds.push([lab[i * 3], lab[i * 3 + 1], lab[i * 3 + 2], x, y]);
3702
+ seeds.push([lab2[i * 3], lab2[i * 3 + 1], lab2[i * 3 + 2], x, y]);
3703
3703
  }
3704
3704
  }
3705
3705
  const k = seeds.length;
@@ -3717,7 +3717,7 @@ function slicSuperpixels(work, target = 400, compactness = 10, rounds = 5) {
3717
3717
  for (let y = y0; y <= y1; y++) {
3718
3718
  for (let x = x0; x <= x1; x++) {
3719
3719
  const i = y * w + x;
3720
- const d = (lab[i * 3] - L) ** 2 + (lab[i * 3 + 1] - A) ** 2 + (lab[i * 3 + 2] - B) ** 2 + m2 * ((x - cx) ** 2 + (y - cy) ** 2);
3720
+ const d = (lab2[i * 3] - L) ** 2 + (lab2[i * 3 + 1] - A) ** 2 + (lab2[i * 3 + 2] - B) ** 2 + m2 * ((x - cx) ** 2 + (y - cy) ** 2);
3721
3721
  if (d < best[i]) {
3722
3722
  best[i] = d;
3723
3723
  label[i] = c;
@@ -3728,9 +3728,9 @@ function slicSuperpixels(work, target = 400, compactness = 10, rounds = 5) {
3728
3728
  const sum = new Float64Array(k * 6);
3729
3729
  for (let i = 0; i < n; i++) {
3730
3730
  const c = label[i];
3731
- sum[c * 6] += lab[i * 3];
3732
- sum[c * 6 + 1] += lab[i * 3 + 1];
3733
- sum[c * 6 + 2] += lab[i * 3 + 2];
3731
+ sum[c * 6] += lab2[i * 3];
3732
+ sum[c * 6 + 1] += lab2[i * 3 + 1];
3733
+ sum[c * 6 + 2] += lab2[i * 3 + 2];
3734
3734
  sum[c * 6 + 3] += i % w;
3735
3735
  sum[c * 6 + 4] += Math.floor(i / w);
3736
3736
  sum[c * 6 + 5]++;
@@ -3748,9 +3748,9 @@ function slicSuperpixels(work, target = 400, compactness = 10, rounds = 5) {
3748
3748
  const size = new Int32Array(count);
3749
3749
  for (let i = 0; i < n; i++) {
3750
3750
  const c = label[i] = remap[label[i]];
3751
- meanLab[c * 3] += lab[i * 3];
3752
- meanLab[c * 3 + 1] += lab[i * 3 + 1];
3753
- meanLab[c * 3 + 2] += lab[i * 3 + 2];
3751
+ meanLab[c * 3] += lab2[i * 3];
3752
+ meanLab[c * 3 + 1] += lab2[i * 3 + 1];
3753
+ meanLab[c * 3 + 2] += lab2[i * 3 + 2];
3754
3754
  centre[c * 2] += i % w;
3755
3755
  centre[c * 2 + 1] += Math.floor(i / w);
3756
3756
  size[c]++;
@@ -3763,8 +3763,8 @@ function slicSuperpixels(work, target = 400, compactness = 10, rounds = 5) {
3763
3763
  return { label, count, lab: meanLab, centre, size };
3764
3764
  }
3765
3765
  function superpixelSaliency(sp, width, height) {
3766
- const { count, lab, centre, size, label } = sp;
3767
- const dist = (a, b) => Math.hypot(lab[a * 3] - lab[b * 3], lab[a * 3 + 1] - lab[b * 3 + 1], lab[a * 3 + 2] - lab[b * 3 + 2]);
3766
+ const { count, lab: lab2, centre, size, label } = sp;
3767
+ const dist = (a, b) => Math.hypot(lab2[a * 3] - lab2[b * 3], lab2[a * 3 + 1] - lab2[b * 3 + 1], lab2[a * 3 + 2] - lab2[b * 3 + 2]);
3768
3768
  const onBorder = new Uint8Array(count);
3769
3769
  for (let x = 0; x < width; x++) {
3770
3770
  onBorder[label[x]] = 1;
@@ -4213,6 +4213,127 @@ function focusArea(pixels, options = {}) {
4213
4213
  return { selection: softMaskToSelection(work, mask), noiseLevel: Math.round(sigma * 255 * 10) / 10 };
4214
4214
  }
4215
4215
 
4216
+ // src/image-editor/selection/segment/sky.ts
4217
+ var SKY_SIZE = 768;
4218
+ function lab(r, g, b) {
4219
+ const lin = (v) => {
4220
+ v /= 255;
4221
+ return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
4222
+ };
4223
+ const R = lin(r);
4224
+ const G = lin(g);
4225
+ const B = lin(b);
4226
+ const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
4227
+ const x = f((0.4124 * R + 0.3576 * G + 0.1805 * B) / 0.95047);
4228
+ const y = f(0.2126 * R + 0.7152 * G + 0.0722 * B);
4229
+ const z = f((0.0193 * R + 0.1192 * G + 0.9505 * B) / 1.08883);
4230
+ return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
4231
+ }
4232
+ var sigmoid = (v) => 1 / (1 + Math.exp(-v));
4233
+ function findSky(pixels, options = {}) {
4234
+ const work = workImage(pixels, SKY_SIZE);
4235
+ const { width: w, height: h, rgb, scale } = work;
4236
+ const n = w * h;
4237
+ const L = new Float32Array(n);
4238
+ const A = new Float32Array(n);
4239
+ const B = new Float32Array(n);
4240
+ for (let i = 0; i < n; i++) {
4241
+ const [l, a, b] = lab(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
4242
+ L[i] = l;
4243
+ A[i] = a;
4244
+ B[i] = b;
4245
+ }
4246
+ const luma2 = boxMean(workLuma(work), w, h, 1);
4247
+ const grad = new Float32Array(n);
4248
+ for (let y = 0; y < h; y++) {
4249
+ for (let x = 0; x < w; x++) {
4250
+ const i = y * w + x;
4251
+ const gx = x + 1 < w ? luma2[i + 1] - luma2[i] : 0;
4252
+ const gy = y + 1 < h ? luma2[i + w] - luma2[i] : 0;
4253
+ grad[i] = Math.hypot(gx, gy);
4254
+ }
4255
+ }
4256
+ const roughR = Math.max(1, Math.round(6e-3 * Math.max(w, h)));
4257
+ const rough = boxMean(grad, w, h, roughR);
4258
+ const score = new Float32Array(n);
4259
+ for (let i = 0; i < n; i++) {
4260
+ const chroma = Math.hypot(A[i], B[i]);
4261
+ const blue = sigmoid((-B[i] - 6) / 4) * sigmoid((L[i] - 35) / 6);
4262
+ const grey = sigmoid((L[i] - 68) / 5) * sigmoid((18 - chroma) / 4);
4263
+ const smooth = Math.exp(-rough[i] / 0.02);
4264
+ const y = Math.floor(i / w) / h;
4265
+ const high = 1 - 0.35 * y;
4266
+ score[i] = Math.max(blue, grey) * smooth * high;
4267
+ }
4268
+ const threshold = Math.min(100, Math.max(0, options.threshold ?? 50));
4269
+ const t = 0.55 - (threshold - 50) * 6e-3;
4270
+ const sky = new Uint8Array(n);
4271
+ const stack = [];
4272
+ const seedRows = Math.max(1, Math.round(h * 0.04));
4273
+ for (let y = 0; y < seedRows; y++) {
4274
+ for (let x = 0; x < w; x++) {
4275
+ const i = y * w + x;
4276
+ if (score[i] >= t) {
4277
+ sky[i] = 1;
4278
+ stack.push(i);
4279
+ }
4280
+ }
4281
+ }
4282
+ if (!stack.length) return { selection: createSelectionMask(pixels.width, pixels.height), reason: "No sky was found along the top of the image." };
4283
+ const low = t * 0.7;
4284
+ while (stack.length) {
4285
+ const p = stack.pop();
4286
+ const x = p % w;
4287
+ for (const q of [x + 1 < w ? p + 1 : -1, x > 0 ? p - 1 : -1, p + w < n ? p + w : -1, p - w]) {
4288
+ if (q < 0 || sky[q] || score[q] < low) continue;
4289
+ if (Math.hypot(L[q] - L[p], A[q] - A[p], B[q] - B[p]) > 6) continue;
4290
+ sky[q] = 1;
4291
+ stack.push(q);
4292
+ }
4293
+ }
4294
+ let count = 0;
4295
+ const mean = [0, 0, 0];
4296
+ for (let i = 0; i < n; i++) {
4297
+ if (!sky[i]) continue;
4298
+ mean[0] += L[i];
4299
+ mean[1] += A[i];
4300
+ mean[2] += B[i];
4301
+ count++;
4302
+ }
4303
+ for (let k = 0; k < 3; k++) mean[k] /= count;
4304
+ let spread = 0;
4305
+ for (let i = 0; i < n; i++) if (sky[i]) spread += (L[i] - mean[0]) ** 2 + (A[i] - mean[1]) ** 2 + (B[i] - mean[2]) ** 2;
4306
+ const limit = Math.max(8, 2.5 * Math.sqrt(spread / count));
4307
+ for (let i = 0; i < n; i++) {
4308
+ if (sky[i] || score[i] < t) continue;
4309
+ if (Math.hypot(L[i] - mean[0], A[i] - mean[1], B[i] - mean[2]) <= limit) sky[i] = 1;
4310
+ }
4311
+ let front = [];
4312
+ for (let i = 0; i < n; i++) if (sky[i]) front.push(i);
4313
+ for (let step = 0; step <= roughR + 1 && front.length; step++) {
4314
+ const next = [];
4315
+ for (const p of front) {
4316
+ const x = p % w;
4317
+ for (const q of [x + 1 < w ? p + 1 : -1, x > 0 ? p - 1 : -1, p + w < n ? p + w : -1, p - w]) {
4318
+ if (q < 0 || sky[q]) continue;
4319
+ if (Math.hypot(L[q] - mean[0], A[q] - mean[1], B[q] - mean[2]) > limit) continue;
4320
+ sky[q] = 1;
4321
+ next.push(q);
4322
+ }
4323
+ }
4324
+ front = next;
4325
+ }
4326
+ if (count < n * 5e-3) return { selection: createSelectionMask(pixels.width, pixels.height), reason: "No sky was found along the top of the image." };
4327
+ let mask = Float32Array.from(sky);
4328
+ const softness = Math.max(0, options.softness ?? 4) * scale;
4329
+ if (softness > 0) {
4330
+ const fitted = guidedFilterColor(workPlanes(work), mask, w, h, Math.max(1, Math.round(softness * 2)), 1e-3);
4331
+ mask = new Float32Array(n);
4332
+ for (let i = 0; i < n; i++) mask[i] = Math.min(1, Math.max(0, fitted[i]));
4333
+ }
4334
+ return { selection: softMaskToSelection(work, mask) };
4335
+ }
4336
+
4216
4337
  // src/image-editor/layers/shape-type-ui.ts
4217
4338
  var DEFAULT_SHAPE_TOOL_OPTIONS = {
4218
4339
  kind: "rect",
@@ -5942,10 +6063,10 @@ function colorModePlanes(doc, mode) {
5942
6063
  }
5943
6064
  const t = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
5944
6065
  return (rgba, n) => {
5945
- const lab = new Uint8Array(n * 4);
5946
- t.convert8(rgba, 4, lab, 4, false);
6066
+ const lab2 = new Uint8Array(n * 4);
6067
+ t.convert8(rgba, 4, lab2, 4, false);
5947
6068
  const planes = [0, 1, 2].map(() => new Uint8Array(n));
5948
- for (let i = 0; i < n; i++) for (let c = 0; c < 3; c++) planes[c][i] = lab[i * 4 + c];
6069
+ for (let i = 0; i < n; i++) for (let c = 0; c < 3; c++) planes[c][i] = lab2[i * 4 + c];
5949
6070
  return planes;
5950
6071
  };
5951
6072
  }
@@ -9983,6 +10104,19 @@ var ImageEditorController = class {
9983
10104
  this.setSelection(selection, options.mode ?? "replace");
9984
10105
  return { selection: this.state.selection ?? null, noiseLevel };
9985
10106
  }
10107
+ /**
10108
+ * Select › Sky: selects the sky (blue, or bright and grey when overcast; smooth; grown down from
10109
+ * the top of the image, with patches seen through gaps), fitted to its edges, combined with the
10110
+ * selection by `mode` as one undo step. Resolves to the selection and, when no sky was found
10111
+ * (the selection is then unchanged), why.
10112
+ */
10113
+ async selectSky(options = {}) {
10114
+ const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
10115
+ const { selection, reason } = findSky(pixels, options);
10116
+ if (!selection.bounds) return { selection: this.state.selection ?? null, note: reason };
10117
+ this.setSelection(selection, options.mode ?? "replace");
10118
+ return { selection: this.state.selection ?? null };
10119
+ }
9986
10120
  /**
9987
10121
  * Supplies the pixels the selection tools sample (natural size RGBA), e.g. decoded in a worker
9988
10122
  * or in a test; `null` goes back to reading the source image. Without a canvas (Node, workers)
@@ -11500,8 +11634,8 @@ var ImageEditorController = class {
11500
11634
  const values = Array.from(out.subarray(0, t.outputs), (v) => Math.round(Math.min(1, Math.max(0, v)) * 1e4) / 1e4);
11501
11635
  const l = new Float32Array(3);
11502
11636
  toLab2.eval(rgb, l);
11503
- const lab = [Math.round(l[0] * 1e4) / 100, Math.round((l[1] * 255 - 128) * 100) / 100, Math.round((l[2] * 255 - 128) * 100) / 100];
11504
- const res = { input, values, lab };
11637
+ const lab2 = [Math.round(l[0] * 1e4) / 100, Math.round((l[1] * 255 - 128) * 100) / 100, Math.round((l[2] * 255 - 128) * 100) / 100];
11638
+ const res = { input, values, lab: lab2 };
11505
11639
  if (dst.colorSpace === "rgb") res.hex = `#${values.map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("")}`;
11506
11640
  return res;
11507
11641
  });
@@ -12883,7 +13017,7 @@ var DEPTH_MASK_CONTROLS = [
12883
13017
  // src/image-editor/playback.ts
12884
13018
  var IMAGE_PLAYBACK_KIND = "layer";
12885
13019
  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)$/;
12886
- var SELECT = /^(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_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)$/;
13020
+ var SELECT = /^(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)$/;
12887
13021
  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)$/;
12888
13022
  var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
12889
13023
  var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
@@ -13547,6 +13681,31 @@ function createImageSelectionTools(controller) {
13547
13681
  return { ...summary(controller), noiseLevel };
13548
13682
  }
13549
13683
  };
13684
+ const selectSkyTool = {
13685
+ name: "select_sky",
13686
+ title: "Select the sky",
13687
+ description: "Select \u203A Sky: selects the sky (blue, or bright and grey when overcast, smooth, reaching down from the top of the image, including patches seen through branches or gaps), with the edge fitted to the skyline, combined with the selection by mode. Use it to replace or recolour a sky or to adjust the land without it (invert_selection). Raise threshold to take in haze or a sunset glow, lower it if bright buildings come in. One undoable step. Returns the selection, and a note when no sky was found (the selection is then unchanged).",
13688
+ inputSchema: {
13689
+ type: "object",
13690
+ properties: {
13691
+ threshold: { type: "number", minimum: 0, maximum: 100, description: "How sky-like a pixel must be, 0..100 (default 50): higher takes in less sky-like pixels." },
13692
+ softness: { type: "number", minimum: 0, maximum: 100, description: "Soften the edge over this many natural pixels (default 4; 0 for a hard edge)." },
13693
+ mode: modeSchema,
13694
+ sampleAllLayers: { type: "boolean", description: "Read every layer (default true); false reads only the active pixel layer." }
13695
+ },
13696
+ additionalProperties: false
13697
+ },
13698
+ annotations: writeAnnotations("Select the sky", true),
13699
+ handler: async (args) => {
13700
+ const { note } = await controller.selectSky({
13701
+ threshold: args.threshold === void 0 ? void 0 : num2(args.threshold, 50),
13702
+ softness: args.softness === void 0 ? void 0 : num2(args.softness, 4),
13703
+ mode: parseSelectionMode(args.mode),
13704
+ sampleAllLayers: args.sampleAllLayers
13705
+ });
13706
+ return { ...summary(controller), ...note ? { note } : {} };
13707
+ }
13708
+ };
13550
13709
  const magicWandTool = {
13551
13710
  name: "magic_wand",
13552
13711
  title: "Magic wand",
@@ -14103,6 +14262,7 @@ function createImageSelectionTools(controller) {
14103
14262
  selectObjectTool,
14104
14263
  createImageSubjectSelectionTool(controller),
14105
14264
  selectFocusAreaTool,
14265
+ selectSkyTool,
14106
14266
  lineTool,
14107
14267
  magicWandTool,
14108
14268
  colorRangeTool,
@@ -20816,6 +20976,7 @@ export {
20816
20976
  findAdjustmentControl,
20817
20977
  findLayer,
20818
20978
  findLayerControl,
20979
+ findSky,
20819
20980
  findSubject,
20820
20981
  findWebGpu,
20821
20982
  finishTile,
@@ -5,3 +5,4 @@ export * from './grabcut.cjs';
5
5
  export * from './subject.cjs';
6
6
  export * from './guided.cjs';
7
7
  export * from './focus.cjs';
8
+ export * from './sky.cjs';
@@ -5,3 +5,4 @@ export * from './grabcut.js';
5
5
  export * from './subject.js';
6
6
  export * from './guided.js';
7
7
  export * from './focus.js';
8
+ export * from './sky.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Select › Sky without a segmentation model. Sky is smooth, sits at the top of the frame, and is
3
+ * blue, or bright and grey when overcast. Each pixel gets a score from those three cues; the sky
4
+ * is grown down from the top rows through high-scoring pixels; patches of the same colour seen
5
+ * through gaps (between branches, under a bridge) are added; and the edge is fitted to the image
6
+ * with the colour guided filter.
7
+ */
8
+ import { type PixelBuffer, type SelectionMask } from '../mask.cjs';
9
+ export interface SkyOptions {
10
+ /** 0..100 (default 50): higher takes in less sky-like pixels (haze, a sunset glow). */
11
+ threshold?: number;
12
+ /** Soften the edge over this many natural pixels (default 4; 0 for a hard edge). */
13
+ softness?: number;
14
+ }
15
+ export interface FoundSky {
16
+ selection: SelectionMask;
17
+ /** Why nothing was selected, when nothing was. */
18
+ reason?: string;
19
+ }
20
+ /** The sky in `pixels`, as a selection of the whole image (empty, with a reason, when there is none). */
21
+ export declare function findSky(pixels: PixelBuffer, options?: SkyOptions): FoundSky;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Select › Sky without a segmentation model. Sky is smooth, sits at the top of the frame, and is
3
+ * blue, or bright and grey when overcast. Each pixel gets a score from those three cues; the sky
4
+ * is grown down from the top rows through high-scoring pixels; patches of the same colour seen
5
+ * through gaps (between branches, under a bridge) are added; and the edge is fitted to the image
6
+ * with the colour guided filter.
7
+ */
8
+ import { type PixelBuffer, type SelectionMask } from '../mask.js';
9
+ export interface SkyOptions {
10
+ /** 0..100 (default 50): higher takes in less sky-like pixels (haze, a sunset glow). */
11
+ threshold?: number;
12
+ /** Soften the edge over this many natural pixels (default 4; 0 for a hard edge). */
13
+ softness?: number;
14
+ }
15
+ export interface FoundSky {
16
+ selection: SelectionMask;
17
+ /** Why nothing was selected, when nothing was. */
18
+ reason?: string;
19
+ }
20
+ /** The sky in `pixels`, as a selection of the whole image (empty, with a reason, when there is none). */
21
+ export declare function findSky(pixels: PixelBuffer, options?: SkyOptions): FoundSky;
@@ -3,6 +3,6 @@
3
3
  * images) and colour (harmonies, swatches, the mixing pad). Guides, swatches and the pad are
4
4
  * editor state saved with the document (the pad is not saved), not undo steps.
5
5
  */
6
- import type { AgentTool } from '../tools/registry.cjs';
6
+ import type { PaintAgentTool } from './hosted.cjs';
7
7
  import type { PaintEditorController } from './controller.cjs';
8
- export declare function createPaintGuideTools(c: PaintEditorController): AgentTool[];
8
+ export declare function createPaintGuideTools(c: PaintEditorController): PaintAgentTool[];
@@ -3,6 +3,6 @@
3
3
  * images) and colour (harmonies, swatches, the mixing pad). Guides, swatches and the pad are
4
4
  * editor state saved with the document (the pad is not saved), not undo steps.
5
5
  */
6
- import type { AgentTool } from '../tools/registry.js';
6
+ import type { PaintAgentTool } from './hosted.js';
7
7
  import type { PaintEditorController } from './controller.js';
8
- export declare function createPaintGuideTools(c: PaintEditorController): AgentTool[];
8
+ export declare function createPaintGuideTools(c: PaintEditorController): PaintAgentTool[];
@@ -0,0 +1,34 @@
1
+ import type { AgentTool } from '../tools/registry.cjs';
2
+ import type { PaintEditorController } from './controller.cjs';
3
+ /**
4
+ * Position fields of a paint tool, as paths: keys joined by `.`, a key ending in `[]` maps over
5
+ * that array, `''` is the value itself. Each path names an object whose numeric `x` / `y` are a
6
+ * position (a point, or the top-left of a rect); missing or `null` values are skipped.
7
+ */
8
+ export interface PaintToolCoordinates {
9
+ /** Argument fields, given in host coordinates and moved into document pixels. */
10
+ in?: readonly string[];
11
+ /** Result fields, in document pixels and moved out into host coordinates. */
12
+ out?: readonly string[];
13
+ }
14
+ /** A paint tool with its position fields. */
15
+ export type PaintAgentTool = AgentTool & {
16
+ coordinates?: PaintToolCoordinates;
17
+ };
18
+ export interface PaintOrigin {
19
+ x: number;
20
+ y: number;
21
+ }
22
+ /** Host coordinates → document pixels, at the given position fields. */
23
+ export declare const toDocument: <T>(value: T, paths: readonly string[], origin: PaintOrigin) => T;
24
+ /** Document pixels → host coordinates, at the given position fields. */
25
+ export declare const fromDocument: <T>(value: T, paths: readonly string[], origin: PaintOrigin) => T;
26
+ /** Tool prose in host terms: positions are canvas coordinates, not document pixels. */
27
+ export declare function hostedText(text: string): string;
28
+ /** `hostedText` over every `description` in a JSON schema. */
29
+ export declare function hostedSchema<T>(schema: T): T;
30
+ /**
31
+ * A controller that resolves `get` on every property access, so tools built over it never touch
32
+ * the document until a handler runs (a host can create the document lazily).
33
+ */
34
+ export declare function lazyController(get: () => PaintEditorController): PaintEditorController;
@@ -0,0 +1,34 @@
1
+ import type { AgentTool } from '../tools/registry.js';
2
+ import type { PaintEditorController } from './controller.js';
3
+ /**
4
+ * Position fields of a paint tool, as paths: keys joined by `.`, a key ending in `[]` maps over
5
+ * that array, `''` is the value itself. Each path names an object whose numeric `x` / `y` are a
6
+ * position (a point, or the top-left of a rect); missing or `null` values are skipped.
7
+ */
8
+ export interface PaintToolCoordinates {
9
+ /** Argument fields, given in host coordinates and moved into document pixels. */
10
+ in?: readonly string[];
11
+ /** Result fields, in document pixels and moved out into host coordinates. */
12
+ out?: readonly string[];
13
+ }
14
+ /** A paint tool with its position fields. */
15
+ export type PaintAgentTool = AgentTool & {
16
+ coordinates?: PaintToolCoordinates;
17
+ };
18
+ export interface PaintOrigin {
19
+ x: number;
20
+ y: number;
21
+ }
22
+ /** Host coordinates → document pixels, at the given position fields. */
23
+ export declare const toDocument: <T>(value: T, paths: readonly string[], origin: PaintOrigin) => T;
24
+ /** Document pixels → host coordinates, at the given position fields. */
25
+ export declare const fromDocument: <T>(value: T, paths: readonly string[], origin: PaintOrigin) => T;
26
+ /** Tool prose in host terms: positions are canvas coordinates, not document pixels. */
27
+ export declare function hostedText(text: string): string;
28
+ /** `hostedText` over every `description` in a JSON schema. */
29
+ export declare function hostedSchema<T>(schema: T): T;
30
+ /**
31
+ * A controller that resolves `get` on every property access, so tools built over it never touch
32
+ * the document until a handler runs (a host can create the document lazily).
33
+ */
34
+ export declare function lazyController(get: () => PaintEditorController): PaintEditorController;