@pixerate/editor 0.257.0 → 0.259.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/brushes/contract.d.cts +24 -0
- package/dist/brushes/contract.d.ts +24 -0
- package/dist/brushes/index.cjs +116 -1
- package/dist/brushes/index.d.cts +1 -0
- package/dist/brushes/index.d.ts +1 -0
- package/dist/brushes/index.js +115 -0
- package/dist/paint-editor/collaboration.d.cts +38 -0
- package/dist/paint-editor/collaboration.d.ts +38 -0
- package/dist/paint-editor/controller.d.cts +31 -0
- package/dist/paint-editor/controller.d.ts +31 -0
- package/dist/paint-editor/index.cjs +365 -288
- package/dist/paint-editor/index.js +77 -0
- package/dist/paint-editor/stroke.d.cts +2 -0
- package/dist/paint-editor/stroke.d.ts +2 -0
- package/package.json +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +1 -0
|
@@ -3055,6 +3055,7 @@ var PaintStrokeSession = class _PaintStrokeSession {
|
|
|
3055
3055
|
if (filter) points = points.map(filter);
|
|
3056
3056
|
for (const p of points) this.points.push(p);
|
|
3057
3057
|
this.lastRaw = points[points.length - 1];
|
|
3058
|
+
this.host.sampled?.(this, points);
|
|
3058
3059
|
this.lightTwin?.push(points);
|
|
3059
3060
|
if (this.wetDriver) {
|
|
3060
3061
|
this.wetUpdate(this.wetDriver.push(points));
|
|
@@ -5854,6 +5855,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5854
5855
|
encodeImage;
|
|
5855
5856
|
listeners = /* @__PURE__ */ new Set();
|
|
5856
5857
|
commitListeners = /* @__PURE__ */ new Set();
|
|
5858
|
+
sampleListeners = /* @__PURE__ */ new Set();
|
|
5857
5859
|
/** Origins set by `withOrigin`, innermost last. */
|
|
5858
5860
|
origins = [];
|
|
5859
5861
|
/** `group` in progress: commits fold into one step, recorded when it ends. */
|
|
@@ -5968,6 +5970,27 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
5968
5970
|
this.listeners.add(listener);
|
|
5969
5971
|
return () => this.listeners.delete(listener);
|
|
5970
5972
|
}
|
|
5973
|
+
/**
|
|
5974
|
+
* Hears the user's live strokes as they are painted (`beginStroke` sessions, not strokes painted
|
|
5975
|
+
* from data): when the pen goes down, each batch of samples, and the end or cancel. For agents
|
|
5976
|
+
* that paint along in real time. Returns an unsubscribe function.
|
|
5977
|
+
*/
|
|
5978
|
+
onStrokeSample(listener) {
|
|
5979
|
+
this.sampleListeners.add(listener);
|
|
5980
|
+
return () => {
|
|
5981
|
+
this.sampleListeners.delete(listener);
|
|
5982
|
+
};
|
|
5983
|
+
}
|
|
5984
|
+
notifySample(phase, session, added = [], strokeId) {
|
|
5985
|
+
if (!this.sampleListeners.size || this.previewing) return;
|
|
5986
|
+
const sample = { phase, layerId: session.layerId, brush: session.init.brush, color: session.init.colorHex, points: session.points, added, ...strokeId !== void 0 ? { strokeId } : {} };
|
|
5987
|
+
for (const l of [...this.sampleListeners]) l(sample);
|
|
5988
|
+
}
|
|
5989
|
+
/** The stroke being painted now, or `null`: its layer, brush, colour and samples so far. */
|
|
5990
|
+
getLiveStroke() {
|
|
5991
|
+
const s = this.session;
|
|
5992
|
+
return s ? { layerId: s.layerId, brushId: s.init.brush.id, brushName: s.init.brush.name, color: s.init.colorHex, points: s.points } : null;
|
|
5993
|
+
}
|
|
5971
5994
|
/** Hears each undo step recorded, merged, undone or redone, with who made it. Returns an unsubscribe function. */
|
|
5972
5995
|
onCommit(listener) {
|
|
5973
5996
|
this.commitListeners.add(listener);
|
|
@@ -7700,6 +7723,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7700
7723
|
this.liveThick = ch ? { layerId, channels: ch } : null;
|
|
7701
7724
|
this.emit(rects);
|
|
7702
7725
|
},
|
|
7726
|
+
sampled: (s, points) => this.notifySample("points", s, points),
|
|
7703
7727
|
commit: (s, surface, cancelled) => {
|
|
7704
7728
|
this.session = null;
|
|
7705
7729
|
this.live = null;
|
|
@@ -7719,6 +7743,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7719
7743
|
this.wet.syncDisplays();
|
|
7720
7744
|
}
|
|
7721
7745
|
this.emit();
|
|
7746
|
+
this.notifySample(cancelled ? "cancel" : "end", s, [], cancelled ? void 0 : null);
|
|
7722
7747
|
return;
|
|
7723
7748
|
}
|
|
7724
7749
|
const stroke = this.record(s);
|
|
@@ -7738,6 +7763,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7738
7763
|
const wetLight = wetStroke && this.wet.world?.hasLane(l.id) ? this.wet.world.lightDisplay(l.id) : null;
|
|
7739
7764
|
const light = withLayerLight(this.doc.light, l.id, s.lightChannel() ?? wetLight);
|
|
7740
7765
|
this.commit(`${brush.name} stroke`, this.withWetEvents(withLiveStroke({ ...this.doc, layers: mapLayer(this.doc.layers, l.id, () => ({ ...l, surface })), strokes: [...this.doc.strokes, stroke], ...thick ? { thick } : {}, ...stickers ? { stickers } : {}, ...light ? { light } : {} }, stroke)), wetStroke ? before : this.doc);
|
|
7766
|
+
this.notifySample("end", s, [], stroke.id);
|
|
7741
7767
|
}
|
|
7742
7768
|
};
|
|
7743
7769
|
let session;
|
|
@@ -7749,6 +7775,7 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7749
7775
|
}
|
|
7750
7776
|
this.session = session;
|
|
7751
7777
|
this.emit([]);
|
|
7778
|
+
this.notifySample("begin", session);
|
|
7752
7779
|
return session;
|
|
7753
7780
|
}
|
|
7754
7781
|
/** The current live stroke, if any. */
|
|
@@ -11536,6 +11563,23 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
11536
11563
|
return r.mixerLoad ? { ...r, mixerLoad: loadResult(r.mixerLoad) } : r;
|
|
11537
11564
|
}
|
|
11538
11565
|
});
|
|
11566
|
+
add({
|
|
11567
|
+
name: "get_live_stroke",
|
|
11568
|
+
title: "Get the stroke being painted",
|
|
11569
|
+
description: "Reads the stroke the user is painting right now, if any: its layer, brush id and name, colour, and the pen samples so far (document pixels; at most the last `limit`). Returns live: false when no stroke is in progress. For agents that paint along with the user: hosts stream the same samples to them live (onStrokeSample); this is a snapshot.",
|
|
11570
|
+
inputSchema: {
|
|
11571
|
+
type: "object",
|
|
11572
|
+
properties: { limit: { type: "integer", minimum: 1, maximum: 5e3, description: "Most recent samples to return (default 500)." } },
|
|
11573
|
+
additionalProperties: false
|
|
11574
|
+
},
|
|
11575
|
+
annotations: { title: "Get the stroke being painted", ...READ6 },
|
|
11576
|
+
handler: (args) => {
|
|
11577
|
+
const live = c.getLiveStroke();
|
|
11578
|
+
if (!live) return { live: false };
|
|
11579
|
+
const limit = args.limit ?? 500;
|
|
11580
|
+
return { live: true, layerId: live.layerId, brushId: live.brushId, brushName: live.brushName, color: live.color, total: live.points.length, points: live.points.slice(-limit) };
|
|
11581
|
+
}
|
|
11582
|
+
});
|
|
11539
11583
|
add({
|
|
11540
11584
|
name: "list_strokes",
|
|
11541
11585
|
title: "List recorded strokes",
|
|
@@ -12967,6 +13011,37 @@ function paintProposalGhost(proposal) {
|
|
|
12967
13011
|
if (!b || !b.width || !b.height || proposal.ghost.length !== b.width * b.height * 4) return null;
|
|
12968
13012
|
return { x: b.x, y: b.y, width: b.width, height: b.height, data: proposal.ghost };
|
|
12969
13013
|
}
|
|
13014
|
+
function createPaintAgentStrokes() {
|
|
13015
|
+
let state = { strokes: [] };
|
|
13016
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
13017
|
+
const set = (strokes) => {
|
|
13018
|
+
state = { strokes };
|
|
13019
|
+
listeners.forEach((l) => l(state));
|
|
13020
|
+
};
|
|
13021
|
+
return {
|
|
13022
|
+
getState: () => state,
|
|
13023
|
+
subscribe(listener) {
|
|
13024
|
+
listeners.add(listener);
|
|
13025
|
+
return () => {
|
|
13026
|
+
listeners.delete(listener);
|
|
13027
|
+
};
|
|
13028
|
+
},
|
|
13029
|
+
set(stroke) {
|
|
13030
|
+
const i = state.strokes.findIndex((s) => s.id === stroke.id);
|
|
13031
|
+
set(i < 0 ? [...state.strokes, stroke] : state.strokes.map((s, j) => j === i ? stroke : s));
|
|
13032
|
+
},
|
|
13033
|
+
remove(id) {
|
|
13034
|
+
if (state.strokes.some((s) => s.id === id)) set(state.strokes.filter((s) => s.id !== id));
|
|
13035
|
+
},
|
|
13036
|
+
clear(origin) {
|
|
13037
|
+
const keep = origin ? state.strokes.filter((s) => s.origin !== origin) : [];
|
|
13038
|
+
if (keep.length !== state.strokes.length) set(keep);
|
|
13039
|
+
}
|
|
13040
|
+
};
|
|
13041
|
+
}
|
|
13042
|
+
function paintAgentStrokePoints(stroke) {
|
|
13043
|
+
return stroke.points.map((p) => `${Math.round(p.x * 10) / 10},${Math.round(p.y * 10) / 10}`).join(" ");
|
|
13044
|
+
}
|
|
12970
13045
|
|
|
12971
13046
|
// src/paint-editor/collaboration-tools.ts
|
|
12972
13047
|
var READ7 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
@@ -13256,6 +13331,7 @@ export {
|
|
|
13256
13331
|
colorHarmony2 as colorHarmony,
|
|
13257
13332
|
composeAffine,
|
|
13258
13333
|
contactSheet,
|
|
13334
|
+
createPaintAgentStrokes,
|
|
13259
13335
|
createPaintBrushLibraryToolProvider,
|
|
13260
13336
|
createPaintChangeFeed,
|
|
13261
13337
|
createPaintCompositor,
|
|
@@ -13348,6 +13424,7 @@ export {
|
|
|
13348
13424
|
normalizeWetParams,
|
|
13349
13425
|
overPixels,
|
|
13350
13426
|
padSampleRadius,
|
|
13427
|
+
paintAgentStrokePoints,
|
|
13351
13428
|
paintGradientMappingFor,
|
|
13352
13429
|
paintPlaybackPanel,
|
|
13353
13430
|
paintProposalGhost,
|
|
@@ -26,6 +26,8 @@ export interface StrokeSessionHost {
|
|
|
26
26
|
update(layerId: string, surface: TiledSurface, rects: RasterRect[]): void;
|
|
27
27
|
/** Ends the stroke: `surface` is the new pixels, or `null` when nothing changed / it was cancelled. */
|
|
28
28
|
commit(session: PaintStrokeSession, surface: TiledSurface | null, cancelled: boolean): void;
|
|
29
|
+
/** Hears the raw samples as they arrive (after the input filter), before they are painted. */
|
|
30
|
+
sampled?(session: PaintStrokeSession, points: readonly PenPoint[]): void;
|
|
29
31
|
}
|
|
30
32
|
export interface StrokeSessionInit {
|
|
31
33
|
layerId: string;
|
|
@@ -26,6 +26,8 @@ export interface StrokeSessionHost {
|
|
|
26
26
|
update(layerId: string, surface: TiledSurface, rects: RasterRect[]): void;
|
|
27
27
|
/** Ends the stroke: `surface` is the new pixels, or `null` when nothing changed / it was cancelled. */
|
|
28
28
|
commit(session: PaintStrokeSession, surface: TiledSurface | null, cancelled: boolean): void;
|
|
29
|
+
/** Hears the raw samples as they arrive (after the input filter), before they are painted. */
|
|
30
|
+
sampled?(session: PaintStrokeSession, points: readonly PenPoint[]): void;
|
|
29
31
|
}
|
|
30
32
|
export interface StrokeSessionInit {
|
|
31
33
|
layerId: string;
|
package/package.json
CHANGED
|
@@ -321,6 +321,7 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
|
|
|
321
321
|
| --- | --- |
|
|
322
322
|
| `paint_stroke` | Paint one stroke through `points` (document px; `pressure` 0..1, `t` ms, `tiltX` / `tiltY` or `altitude` / `azimuth`, `twist`) with `brushId` (default the active brush), optional per-stroke `brush` facet changes (as `set_brush` takes), `color` or `paint` (a gradient for this stroke), `layerId`, `seed`, `snapToPerspective`. The symmetry guide adds its copies; a sampling tracing image supplies the colour when `color` is missing. Returns `layerId`, `bounds`, `dabs`, `strokeId` and, for brushes that pick up paint, `mixerLoad` (what the brush carries now). Refused on locked, hidden or group layers, with the fix in the message. |
|
|
323
323
|
| `list_strokes` | Read the recorded strokes, oldest first (default the last 100): id, layer, brush, colour, point count, seed, bounds, `live`. With `ids`, their points too; with `layerId`, one layer's. With `live: true`, the editable strokes of stroke layers as they are now, bottom first: the ids for the stroke layer tools. |
|
|
324
|
+
| `get_live_stroke` | Read the stroke the user is painting right now (layer, brush, colour, samples so far; `live: false` when none). For painting along with the user; hosts stream the samples live. |
|
|
324
325
|
| `stroke_at` | Read the top-most stroke of a stroke layer that painted the pixel at `x`, `y`, or null. Use it to pick one stroke among overlapping ones. |
|
|
325
326
|
| `update_stroke` | Change a stroke on a stroke layer: `brushId` (keeps its size), `brush` facets (as `set_brush` takes), `color`, `paint` (null drops a gradient), `seed`. Redraws exactly. One undo step. Returns the stroke and the changed `bounds`. |
|
|
326
327
|
| `edit_stroke_points` | Replace a stroke's `points`, or `transform` it (`dx`, `dy`, `scale`, `rotate`, `flipX`, about its centre or `origin`). One undo step. Returns the stroke with its points and the changed `bounds`. |
|