@pixerate/editor 0.201.0 → 0.203.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.
- package/dist/agent/index.cjs +2 -2
- package/dist/agent/index.js +1 -1
- package/dist/{chunk-GLQQ2VIL.cjs → chunk-53GOGBTM.cjs} +23 -1
- package/dist/{chunk-3TNA7JKI.js → chunk-ZPJEH456.js} +22 -0
- package/dist/commands/builtins.d.cts +6 -0
- package/dist/commands/builtins.d.ts +6 -0
- package/dist/commands/index.cjs +4 -2
- package/dist/commands/index.js +4 -2
- package/dist/image-editor/controller.d.cts +24 -1
- package/dist/image-editor/controller.d.ts +24 -1
- package/dist/image-editor/index.cjs +226 -171
- package/dist/image-editor/index.js +78 -23
- package/dist/image-editor/selection/segmentation.d.cts +7 -0
- package/dist/image-editor/selection/segmentation.d.ts +7 -0
- package/dist/paint-editor/guide-tools.d.cts +2 -2
- package/dist/paint-editor/guide-tools.d.ts +2 -2
- package/dist/paint-editor/hosted.d.cts +34 -0
- package/dist/paint-editor/hosted.d.ts +34 -0
- package/dist/paint-editor/index.cjs +101 -10
- package/dist/paint-editor/index.d.cts +1 -0
- package/dist/paint-editor/index.d.ts +1 -0
- package/dist/paint-editor/index.js +96 -5
- package/dist/paint-editor/sticker-tools.d.cts +2 -2
- package/dist/paint-editor/sticker-tools.d.ts +2 -2
- package/dist/paint-editor/thick-tools.d.cts +2 -2
- package/dist/paint-editor/thick-tools.d.ts +2 -2
- package/dist/paint-editor/tools.d.cts +19 -2
- package/dist/paint-editor/tools.d.ts +19 -2
- package/dist/paint-editor/wet-tools.d.cts +2 -2
- package/dist/paint-editor/wet-tools.d.ts +2 -2
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +2 -2
- package/skills/pixerate-paint-editor/SKILL.md +9 -0
|
@@ -10071,6 +10071,46 @@ var ImageEditorController = class {
|
|
|
10071
10071
|
this.setSelection(sel, "replace");
|
|
10072
10072
|
return this.state.selection ?? null;
|
|
10073
10073
|
}
|
|
10074
|
+
/**
|
|
10075
|
+
* Asks the segmentation provider (which must be set) for `request.target` in the flattened image
|
|
10076
|
+
* and returns its results with masks decoded to pixels.
|
|
10077
|
+
*/
|
|
10078
|
+
async modelSegments(request) {
|
|
10079
|
+
const provider = this.segmentation;
|
|
10080
|
+
const { width, height } = this.naturalSize;
|
|
10081
|
+
const image = await this.toDataURL({ format: "image/png" });
|
|
10082
|
+
const results = await provider.segment({ ...request, image, width, height });
|
|
10083
|
+
request.signal?.throwIfAborted();
|
|
10084
|
+
const decoded = [];
|
|
10085
|
+
for (const r of results ?? []) decoded.push({ ...r, mask: r.mask !== void 0 ? await this.decodeMaskImage(r.mask) : void 0 });
|
|
10086
|
+
return decoded;
|
|
10087
|
+
}
|
|
10088
|
+
/**
|
|
10089
|
+
* The model's object for Object Selection: of what it returns, the pieces mostly inside the box
|
|
10090
|
+
* (clipped to the box grown by a tenth) or those under the point. `null` when none fits.
|
|
10091
|
+
*/
|
|
10092
|
+
async modelObject(target, signal) {
|
|
10093
|
+
const { width, height } = this.naturalSize;
|
|
10094
|
+
const decoded = await this.modelSegments(
|
|
10095
|
+
"rect" in target ? { target: "the main object inside the box", box: target.rect, maxSegments: 4, signal } : { target: "the object at the point", point: target.point, maxSegments: 4, signal }
|
|
10096
|
+
);
|
|
10097
|
+
let union = null;
|
|
10098
|
+
for (const r of decoded) {
|
|
10099
|
+
const one = segmentsToSelection(width, height, [r]);
|
|
10100
|
+
if (!one?.bounds) continue;
|
|
10101
|
+
if ("rect" in target) {
|
|
10102
|
+
const r0 = target.rect;
|
|
10103
|
+
const grow = 0.1 * Math.max(r0.width, r0.height);
|
|
10104
|
+
const box = rectSelection(width, height, { x: r0.x - grow, y: r0.y - grow, width: r0.width + 2 * grow, height: r0.height + 2 * grow }, { antiAlias: false });
|
|
10105
|
+
const inside = combineSelections(one, box, "intersect");
|
|
10106
|
+
if (!inside || selectionArea(inside) < selectionArea(one) * 0.5) continue;
|
|
10107
|
+
union = combineSelections(union, inside, "add");
|
|
10108
|
+
} else if (selectionValueAt(one, target.point.x, target.point.y) >= 0.5) {
|
|
10109
|
+
union = combineSelections(union, one, "add");
|
|
10110
|
+
}
|
|
10111
|
+
}
|
|
10112
|
+
return union;
|
|
10113
|
+
}
|
|
10074
10114
|
/**
|
|
10075
10115
|
* Object Selection: the object inside `target.rect` (natural pixels; what is outside the box is
|
|
10076
10116
|
* background) or under `target.point`, found by GrabCut (colour models of object and background
|
|
@@ -10088,10 +10128,17 @@ var ImageEditorController = class {
|
|
|
10088
10128
|
} else if (!(target.point.x >= 0 && target.point.y >= 0 && target.point.x < width && target.point.y < height)) {
|
|
10089
10129
|
throw new Error(`(${target.point.x}, ${target.point.y}) is outside the image (0..${width - 1}, 0..${height - 1}); click on the object.`);
|
|
10090
10130
|
}
|
|
10131
|
+
if (this.segmentation && options.useModel !== false) {
|
|
10132
|
+
const found = await this.modelObject(target, options.signal);
|
|
10133
|
+
if (found) {
|
|
10134
|
+
this.setSelection(found, options.mode ?? "replace");
|
|
10135
|
+
return { selection: this.state.selection ?? null, method: "model" };
|
|
10136
|
+
}
|
|
10137
|
+
}
|
|
10091
10138
|
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
10092
10139
|
const region = selectObjectIn(pixels, target, { enhanceEdge: options.enhanceEdge });
|
|
10093
10140
|
this.setSelection(region, options.mode ?? "replace");
|
|
10094
|
-
return this.state.selection ?? null;
|
|
10141
|
+
return { selection: this.state.selection ?? null, method: "grabcut" };
|
|
10095
10142
|
}
|
|
10096
10143
|
/**
|
|
10097
10144
|
* Select › Focus Area: selects the parts of the image that are in focus (fine detail above the
|
|
@@ -10111,11 +10158,18 @@ var ImageEditorController = class {
|
|
|
10111
10158
|
* (the selection is then unchanged), why.
|
|
10112
10159
|
*/
|
|
10113
10160
|
async selectSky(options = {}) {
|
|
10161
|
+
if (this.segmentation && options.useModel !== false) {
|
|
10162
|
+
const { width, height } = this.naturalSize;
|
|
10163
|
+
const sky = segmentsToSelection(width, height, await this.modelSegments({ target: "the sky", signal: options.signal }));
|
|
10164
|
+
if (!sky?.bounds) return { selection: this.state.selection ?? null, method: "model", note: "The segmentation model found no sky." };
|
|
10165
|
+
this.setSelection(sky, options.mode ?? "replace");
|
|
10166
|
+
return { selection: this.state.selection ?? null, method: "model" };
|
|
10167
|
+
}
|
|
10114
10168
|
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
10115
10169
|
const { selection, reason } = findSky(pixels, options);
|
|
10116
|
-
if (!selection.bounds) return { selection: this.state.selection ?? null, note: reason };
|
|
10170
|
+
if (!selection.bounds) return { selection: this.state.selection ?? null, method: "classical", note: reason };
|
|
10117
10171
|
this.setSelection(selection, options.mode ?? "replace");
|
|
10118
|
-
return { selection: this.state.selection ?? null };
|
|
10172
|
+
return { selection: this.state.selection ?? null, method: "classical" };
|
|
10119
10173
|
}
|
|
10120
10174
|
/**
|
|
10121
10175
|
* Supplies the pixels the selection tools sample (natural size RGBA), e.g. decoded in a worker
|
|
@@ -10306,13 +10360,7 @@ var ImageEditorController = class {
|
|
|
10306
10360
|
const description = target.trim();
|
|
10307
10361
|
if (!description) throw new Error("Describe the subject to select.");
|
|
10308
10362
|
const { width, height } = this.naturalSize;
|
|
10309
|
-
const
|
|
10310
|
-
const results = await provider.segment({ image, width, height, target: description, maxSegments: options.maxSegments, signal: options.signal });
|
|
10311
|
-
options.signal?.throwIfAborted();
|
|
10312
|
-
const decoded = [];
|
|
10313
|
-
for (const r of results ?? []) {
|
|
10314
|
-
decoded.push({ ...r, mask: r.mask !== void 0 ? await this.decodeMaskImage(r.mask) : void 0 });
|
|
10315
|
-
}
|
|
10363
|
+
const decoded = await this.modelSegments({ target: description, maxSegments: options.maxSegments, signal: options.signal });
|
|
10316
10364
|
const segments = decoded.map((r) => {
|
|
10317
10365
|
const one = segmentsToSelection(width, height, [r]);
|
|
10318
10366
|
return { label: r.label || description, bounds: one?.bounds ?? null, ...r.score !== void 0 ? { score: r.score } : {} };
|
|
@@ -13624,7 +13672,7 @@ function createImageSelectionTools(controller) {
|
|
|
13624
13672
|
const selectObjectTool = {
|
|
13625
13673
|
name: "select_object",
|
|
13626
13674
|
title: "Object Selection",
|
|
13627
|
-
description:
|
|
13675
|
+
description: 'Object Selection: selects the object inside rect (a box drawn loosely around it, natural image pixels; what lies outside the box is treated as background) or the object under point. With a host segmentation model it asks the model (falling back if nothing it returns fits); otherwise it finds the object from the colours of object and background and the edges between them (GrabCut). Combined with the selection by mode. A box is more reliable than a point: make it a little larger than the object on every side. Check with get_selection and capture_view, then fix with quick_select strokes or select_object with mode add / subtract. One undoable step. Returns the selection and method ("model" or "grabcut").',
|
|
13628
13676
|
inputSchema: {
|
|
13629
13677
|
type: "object",
|
|
13630
13678
|
properties: {
|
|
@@ -13638,20 +13686,25 @@ function createImageSelectionTools(controller) {
|
|
|
13638
13686
|
point: { ...pointSchema, description: "A point on the object (natural image pixels). Pass this or rect." },
|
|
13639
13687
|
mode: modeSchema,
|
|
13640
13688
|
sampleAllLayers: { type: "boolean", description: "Read every layer (default true); false reads only the active pixel layer." },
|
|
13641
|
-
enhanceEdge: { type: "boolean", description: "Smoother, less blocky edges (Auto-Enhance; default false)." }
|
|
13689
|
+
enhanceEdge: { type: "boolean", description: "Smoother, less blocky edges (Auto-Enhance; default false)." },
|
|
13690
|
+
useModel: { type: "boolean", description: "Use the host segmentation model when there is one (default true); false always uses GrabCut." }
|
|
13642
13691
|
},
|
|
13643
13692
|
additionalProperties: false
|
|
13644
13693
|
},
|
|
13645
|
-
annotations: writeAnnotations("Object Selection"),
|
|
13694
|
+
annotations: { ...writeAnnotations("Object Selection"), openWorldHint: true },
|
|
13646
13695
|
handler: async (args) => {
|
|
13647
13696
|
const rect = args.rect;
|
|
13648
13697
|
const point = args.point;
|
|
13649
13698
|
if (!rect && !point) throw new Error("Pass rect (a box around the object) or point (a point on it).");
|
|
13650
13699
|
if (rect && point) throw new Error("Pass rect or point, not both.");
|
|
13651
|
-
const options = {
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13700
|
+
const options = {
|
|
13701
|
+
mode: parseSelectionMode(args.mode),
|
|
13702
|
+
sampleAllLayers: args.sampleAllLayers,
|
|
13703
|
+
enhanceEdge: args.enhanceEdge,
|
|
13704
|
+
useModel: args.useModel
|
|
13705
|
+
};
|
|
13706
|
+
const { method } = rect ? await controller.selectObject({ rect: { x: num2(rect.x, NaN), y: num2(rect.y, NaN), width: num2(rect.width, NaN), height: num2(rect.height, NaN) } }, options) : await controller.selectObject({ point: { x: num2(point.x, NaN), y: num2(point.y, NaN) } }, options);
|
|
13707
|
+
return { ...summary(controller), method };
|
|
13655
13708
|
}
|
|
13656
13709
|
};
|
|
13657
13710
|
const selectFocusAreaTool = {
|
|
@@ -13684,26 +13737,28 @@ function createImageSelectionTools(controller) {
|
|
|
13684
13737
|
const selectSkyTool = {
|
|
13685
13738
|
name: "select_sky",
|
|
13686
13739
|
title: "Select the sky",
|
|
13687
|
-
description:
|
|
13740
|
+
description: 'Select \u203A Sky: selects the sky, combined with the selection by mode. With a host segmentation model it asks the model; otherwise it finds it classically (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. 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, method ("model" or "classical"; threshold and softness apply to classical), and a note when no sky was found (the selection is then unchanged).',
|
|
13688
13741
|
inputSchema: {
|
|
13689
13742
|
type: "object",
|
|
13690
13743
|
properties: {
|
|
13691
13744
|
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
13745
|
softness: { type: "number", minimum: 0, maximum: 100, description: "Soften the edge over this many natural pixels (default 4; 0 for a hard edge)." },
|
|
13693
13746
|
mode: modeSchema,
|
|
13694
|
-
sampleAllLayers: { type: "boolean", description: "Read every layer (default true); false reads only the active pixel layer." }
|
|
13747
|
+
sampleAllLayers: { type: "boolean", description: "Read every layer (default true); false reads only the active pixel layer." },
|
|
13748
|
+
useModel: { type: "boolean", description: "Use the host segmentation model when there is one (default true); false finds the sky classically." }
|
|
13695
13749
|
},
|
|
13696
13750
|
additionalProperties: false
|
|
13697
13751
|
},
|
|
13698
|
-
annotations: writeAnnotations("Select the sky", true),
|
|
13752
|
+
annotations: { ...writeAnnotations("Select the sky", true), openWorldHint: true },
|
|
13699
13753
|
handler: async (args) => {
|
|
13700
|
-
const { note } = await controller.selectSky({
|
|
13754
|
+
const { note, method } = await controller.selectSky({
|
|
13701
13755
|
threshold: args.threshold === void 0 ? void 0 : num2(args.threshold, 50),
|
|
13702
13756
|
softness: args.softness === void 0 ? void 0 : num2(args.softness, 4),
|
|
13703
13757
|
mode: parseSelectionMode(args.mode),
|
|
13704
|
-
sampleAllLayers: args.sampleAllLayers
|
|
13758
|
+
sampleAllLayers: args.sampleAllLayers,
|
|
13759
|
+
useModel: args.useModel
|
|
13705
13760
|
});
|
|
13706
|
-
return { ...summary(controller), ...note ? { note } : {} };
|
|
13761
|
+
return { ...summary(controller), method, ...note ? { note } : {} };
|
|
13707
13762
|
}
|
|
13708
13763
|
};
|
|
13709
13764
|
const magicWandTool = {
|
|
@@ -11,6 +11,13 @@ export interface SegmentationRequest {
|
|
|
11
11
|
target: string;
|
|
12
12
|
/** Return at most this many objects (the provider's default when absent). */
|
|
13
13
|
maxSegments?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Object Selection: the box (natural pixels) the wanted object is in. A box-prompted model (SAM)
|
|
16
|
+
* should segment the object inside it; others can read `target`, which describes it too.
|
|
17
|
+
*/
|
|
18
|
+
box?: CropBox;
|
|
19
|
+
/** Object Selection: a point (natural pixels) on the wanted object, for a point-prompted model. */
|
|
20
|
+
point?: ImagePoint;
|
|
14
21
|
signal?: AbortSignal;
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
@@ -11,6 +11,13 @@ export interface SegmentationRequest {
|
|
|
11
11
|
target: string;
|
|
12
12
|
/** Return at most this many objects (the provider's default when absent). */
|
|
13
13
|
maxSegments?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Object Selection: the box (natural pixels) the wanted object is in. A box-prompted model (SAM)
|
|
16
|
+
* should segment the object inside it; others can read `target`, which describes it too.
|
|
17
|
+
*/
|
|
18
|
+
box?: CropBox;
|
|
19
|
+
/** Object Selection: a point (natural pixels) on the wanted object, for a point-prompted model. */
|
|
20
|
+
point?: ImagePoint;
|
|
14
21
|
signal?: AbortSignal;
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
@@ -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 {
|
|
6
|
+
import type { PaintAgentTool } from './hosted.cjs';
|
|
7
7
|
import type { PaintEditorController } from './controller.cjs';
|
|
8
|
-
export declare function createPaintGuideTools(c: PaintEditorController):
|
|
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 {
|
|
6
|
+
import type { PaintAgentTool } from './hosted.js';
|
|
7
7
|
import type { PaintEditorController } from './controller.js';
|
|
8
|
-
export declare function createPaintGuideTools(c: PaintEditorController):
|
|
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;
|