@pixerate/editor 0.279.1 → 0.281.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/canvas/edge-animation-tools.d.cts +115 -0
- package/dist/canvas/edge-animation-tools.d.ts +115 -0
- package/dist/canvas/edgeAnimation.d.cts +164 -0
- package/dist/canvas/edgeAnimation.d.ts +164 -0
- package/dist/canvas/edgePaths.d.cts +63 -0
- package/dist/canvas/edgePaths.d.ts +63 -0
- package/dist/canvas/index.cjs +679 -9
- package/dist/canvas/index.d.cts +3 -0
- package/dist/canvas/index.d.ts +3 -0
- package/dist/canvas/index.js +678 -8
- package/dist/vector/commands.d.cts +25 -0
- package/dist/vector/commands.d.ts +25 -0
- package/dist/vector/index.cjs +61 -39
- package/dist/vector/index.js +61 -39
- package/package.json +5 -5
- package/skills/pixerate-canvas/SKILL.md +25 -2
- package/skills/pixerate-vector/SKILL.md +1 -1
|
@@ -4,6 +4,7 @@ import type { VectorBooleanOperation } from './types.cjs';
|
|
|
4
4
|
import { type VectorEffect } from './types.cjs';
|
|
5
5
|
import { type VectorBlendMode, type VectorFilter, type VectorPaintLayer, type VectorStrokeLayer } from './types.cjs';
|
|
6
6
|
import { type VectorMaskSummary } from './mask.cjs';
|
|
7
|
+
import { type VectorTracePixels } from './trace.cjs';
|
|
7
8
|
import { type VectorLens, type VectorSymmetry } from './types.cjs';
|
|
8
9
|
import { type VectorConnector } from './types.cjs';
|
|
9
10
|
import { type VectorFrame, type VectorResizing } from './types.cjs';
|
|
@@ -14,7 +15,31 @@ export interface VectorCommandOptions {
|
|
|
14
15
|
group?: string;
|
|
15
16
|
/** Command scope. Omit for global commands. */
|
|
16
17
|
scope?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Loads an image that `vector.traceImage` and the stylise commands (`vector.mosaicImage`, …)
|
|
20
|
+
* are given by reference instead of as a PNG data URL: an http(s) or asset URL, or a host id
|
|
21
|
+
* such as a canvas node's. Agents can name an image but cannot write out its bytes, so without
|
|
22
|
+
* this they can only trace PNGs they produced themselves. Return `null` when there is no such
|
|
23
|
+
* image; throw (or reject) with a sentence when it cannot be loaded. `maxPixels` is the most the
|
|
24
|
+
* commands trace: scale larger images down to fit.
|
|
25
|
+
*/
|
|
26
|
+
resolveImage?: VectorImageResolver;
|
|
27
|
+
/**
|
|
28
|
+
* What `resolveImage` accepts, for the `image` argument's description (default "an image
|
|
29
|
+
* URL"), e.g. "the id of an image node on the canvas, or an http(s) URL".
|
|
30
|
+
*/
|
|
31
|
+
imageReferences?: string;
|
|
17
32
|
}
|
|
33
|
+
/** An image a host loaded for a reference: RGBA pixels, or a PNG (data URL or bytes). */
|
|
34
|
+
export type VectorResolvedImage = {
|
|
35
|
+
image: VectorTracePixels;
|
|
36
|
+
} | {
|
|
37
|
+
png: string | Uint8Array;
|
|
38
|
+
};
|
|
39
|
+
/** See `VectorCommandOptions.resolveImage`. */
|
|
40
|
+
export type VectorImageResolver = (ref: string, options: {
|
|
41
|
+
maxPixels: number;
|
|
42
|
+
}) => VectorResolvedImage | null | undefined | Promise<VectorResolvedImage | null | undefined>;
|
|
18
43
|
/** A node as listed for agents. */
|
|
19
44
|
export interface VectorNodeListing {
|
|
20
45
|
id: string;
|
|
@@ -4,6 +4,7 @@ import type { VectorBooleanOperation } from './types.js';
|
|
|
4
4
|
import { type VectorEffect } from './types.js';
|
|
5
5
|
import { type VectorBlendMode, type VectorFilter, type VectorPaintLayer, type VectorStrokeLayer } from './types.js';
|
|
6
6
|
import { type VectorMaskSummary } from './mask.js';
|
|
7
|
+
import { type VectorTracePixels } from './trace.js';
|
|
7
8
|
import { type VectorLens, type VectorSymmetry } from './types.js';
|
|
8
9
|
import { type VectorConnector } from './types.js';
|
|
9
10
|
import { type VectorFrame, type VectorResizing } from './types.js';
|
|
@@ -14,7 +15,31 @@ export interface VectorCommandOptions {
|
|
|
14
15
|
group?: string;
|
|
15
16
|
/** Command scope. Omit for global commands. */
|
|
16
17
|
scope?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Loads an image that `vector.traceImage` and the stylise commands (`vector.mosaicImage`, …)
|
|
20
|
+
* are given by reference instead of as a PNG data URL: an http(s) or asset URL, or a host id
|
|
21
|
+
* such as a canvas node's. Agents can name an image but cannot write out its bytes, so without
|
|
22
|
+
* this they can only trace PNGs they produced themselves. Return `null` when there is no such
|
|
23
|
+
* image; throw (or reject) with a sentence when it cannot be loaded. `maxPixels` is the most the
|
|
24
|
+
* commands trace: scale larger images down to fit.
|
|
25
|
+
*/
|
|
26
|
+
resolveImage?: VectorImageResolver;
|
|
27
|
+
/**
|
|
28
|
+
* What `resolveImage` accepts, for the `image` argument's description (default "an image
|
|
29
|
+
* URL"), e.g. "the id of an image node on the canvas, or an http(s) URL".
|
|
30
|
+
*/
|
|
31
|
+
imageReferences?: string;
|
|
17
32
|
}
|
|
33
|
+
/** An image a host loaded for a reference: RGBA pixels, or a PNG (data URL or bytes). */
|
|
34
|
+
export type VectorResolvedImage = {
|
|
35
|
+
image: VectorTracePixels;
|
|
36
|
+
} | {
|
|
37
|
+
png: string | Uint8Array;
|
|
38
|
+
};
|
|
39
|
+
/** See `VectorCommandOptions.resolveImage`. */
|
|
40
|
+
export type VectorImageResolver = (ref: string, options: {
|
|
41
|
+
maxPixels: number;
|
|
42
|
+
}) => VectorResolvedImage | null | undefined | Promise<VectorResolvedImage | null | undefined>;
|
|
18
43
|
/** A node as listed for agents. */
|
|
19
44
|
export interface VectorNodeListing {
|
|
20
45
|
id: string;
|
package/dist/vector/index.cjs
CHANGED
|
@@ -16551,17 +16551,72 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16551
16551
|
}
|
|
16552
16552
|
}
|
|
16553
16553
|
];
|
|
16554
|
+
const resolveImage = options.resolveImage;
|
|
16555
|
+
const IMAGE_WHAT = resolveImage ? "an image (a PNG data URL, or a reference the editor loads)" : "a PNG (base64 data URL)";
|
|
16556
|
+
const IMAGE_PARAM = {
|
|
16557
|
+
type: "string",
|
|
16558
|
+
description: resolveImage ? `The image: a data:image/png;base64,\u2026 URL, or ${_nullishCoalesce(options.imageReferences, () => ( "an image URL"))} (up to ${_chunkMNHP3AQTcjs.VECTOR_TRACE_MAX_PIXELS / 1e6} million pixels).` : "The image as a data:image/png;base64,\u2026 URL (up to 4 million pixels)."
|
|
16559
|
+
};
|
|
16560
|
+
const decodePngBytes = (bytes, what) => {
|
|
16561
|
+
if (!_chunkW5M3J7PVcjs.isPng.call(void 0, bytes)) fail(`${what} is not a PNG.`);
|
|
16562
|
+
try {
|
|
16563
|
+
return _chunkW5M3J7PVcjs.decodePng.call(void 0, bytes, { maxPixels: _chunkMNHP3AQTcjs.VECTOR_TRACE_MAX_PIXELS });
|
|
16564
|
+
} catch (e) {
|
|
16565
|
+
return fail(`The PNG could not be read: ${e.message}`);
|
|
16566
|
+
}
|
|
16567
|
+
};
|
|
16568
|
+
const fromResolved = (ref, resolved) => {
|
|
16569
|
+
if (!resolved || typeof resolved !== "object") return fail(`There is no image "${ref}".`);
|
|
16570
|
+
if ("image" in resolved && resolved.image) return resolved.image;
|
|
16571
|
+
if ("png" in resolved && typeof resolved.png === "string") {
|
|
16572
|
+
const parsed = _chunkW5M3J7PVcjs.parseDataUrl.call(void 0, resolved.png);
|
|
16573
|
+
if (!parsed) fail(`The image "${ref}" was loaded as something other than a PNG data URL.`);
|
|
16574
|
+
return decodePngBytes(parsed.bytes, `The image "${ref}"`);
|
|
16575
|
+
}
|
|
16576
|
+
if ("png" in resolved && resolved.png instanceof Uint8Array) return decodePngBytes(resolved.png, `The image "${ref}"`);
|
|
16577
|
+
return fail(`The image "${ref}" was loaded as nothing usable (expected { image } or { png }).`);
|
|
16578
|
+
};
|
|
16579
|
+
const withImage = (image, use) => {
|
|
16580
|
+
if (typeof image !== "string" || !image.trim()) fail("image is empty.");
|
|
16581
|
+
if (/^data:/i.test(image)) {
|
|
16582
|
+
const parsed = _chunkW5M3J7PVcjs.parseDataUrl.call(void 0, image);
|
|
16583
|
+
if (!parsed || !_chunkW5M3J7PVcjs.isPng.call(void 0, parsed.bytes)) fail(resolveImage ? "image must be a PNG data URL (data:image/png;base64,\u2026) or a reference to an image." : "image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16584
|
+
return use(decodePngBytes(parsed.bytes, "image"));
|
|
16585
|
+
}
|
|
16586
|
+
if (!resolveImage) return fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16587
|
+
const ref = image.trim();
|
|
16588
|
+
const load = async () => {
|
|
16589
|
+
let resolved;
|
|
16590
|
+
try {
|
|
16591
|
+
resolved = await resolveImage(ref, { maxPixels: _chunkMNHP3AQTcjs.VECTOR_TRACE_MAX_PIXELS });
|
|
16592
|
+
} catch (e) {
|
|
16593
|
+
return fail(`The image "${ref}" could not be loaded: ${e instanceof Error ? e.message : String(e)}`);
|
|
16594
|
+
}
|
|
16595
|
+
return use(fromResolved(ref, resolved));
|
|
16596
|
+
};
|
|
16597
|
+
return load();
|
|
16598
|
+
};
|
|
16599
|
+
const imageResult = (r) => {
|
|
16600
|
+
check2(r);
|
|
16601
|
+
return {
|
|
16602
|
+
id: r.id,
|
|
16603
|
+
paths: r.ids.map((id) => {
|
|
16604
|
+
const n2 = controller.getNode(id);
|
|
16605
|
+
return { id, name: n2.name, fill: n2.fill, subpaths: n2.subpaths.length };
|
|
16606
|
+
})
|
|
16607
|
+
};
|
|
16608
|
+
};
|
|
16554
16609
|
const traceCommands = () => [
|
|
16555
16610
|
{
|
|
16556
16611
|
...base,
|
|
16557
16612
|
id: `${prefix}traceImage`,
|
|
16558
16613
|
label: "Image Trace\u2026",
|
|
16559
16614
|
keywords: ["trace", "image trace", "live trace", "vectorize", "bitmap to vector", "png to svg", "autotrace", "posterize"],
|
|
16560
|
-
description: "Traces
|
|
16615
|
+
description: "Traces " + IMAGE_WHAT + " into vector paths and adds them as one group: `threshold` mode makes one even-odd path where the image is dark (or opaque with channel alpha); `color` mode makes a stacked path per colour (median-cut to `colors`, the most common at the bottom). Placed with its top-left at x, y and `width` wide (default one unit per pixel). One undo step. Returns { id, paths: [{ id, name, fill, subpaths }] }.",
|
|
16561
16616
|
params: {
|
|
16562
16617
|
type: "object",
|
|
16563
16618
|
properties: {
|
|
16564
|
-
image:
|
|
16619
|
+
image: IMAGE_PARAM,
|
|
16565
16620
|
mode: { type: "string", enum: ["threshold", "color"], description: "threshold (one colour, default) or color (a path per colour)." },
|
|
16566
16621
|
channel: { type: "string", enum: ["luminance", "alpha"], description: "Threshold mode: trace dark pixels (luminance, default) or opaque ones (alpha)." },
|
|
16567
16622
|
threshold: { type: "number", minimum: 0, maximum: 1, description: "Threshold mode: inside where darkness (or alpha) reaches this (default 0.5)." },
|
|
@@ -16581,61 +16636,28 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16581
16636
|
undoable: true,
|
|
16582
16637
|
run: (_ctx, params) => {
|
|
16583
16638
|
const { image, ...options2 } = params;
|
|
16584
|
-
|
|
16585
|
-
if (!parsed || !_chunkW5M3J7PVcjs.isPng.call(void 0, parsed.bytes)) fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16586
|
-
let decoded2;
|
|
16587
|
-
try {
|
|
16588
|
-
decoded2 = _chunkW5M3J7PVcjs.decodePng.call(void 0, parsed.bytes, { maxPixels: _chunkMNHP3AQTcjs.VECTOR_TRACE_MAX_PIXELS });
|
|
16589
|
-
} catch (e) {
|
|
16590
|
-
return fail(`The PNG could not be read: ${e.message}`);
|
|
16591
|
-
}
|
|
16592
|
-
const r = controller.traceImage(decoded2, options2);
|
|
16593
|
-
check2(r);
|
|
16594
|
-
return {
|
|
16595
|
-
id: r.id,
|
|
16596
|
-
paths: r.ids.map((id) => {
|
|
16597
|
-
const n2 = controller.getNode(id);
|
|
16598
|
-
return { id, name: n2.name, fill: n2.fill, subpaths: n2.subpaths.length };
|
|
16599
|
-
})
|
|
16600
|
-
};
|
|
16639
|
+
return withImage(image, (pixels) => imageResult(controller.traceImage(pixels, options2)));
|
|
16601
16640
|
}
|
|
16602
16641
|
}
|
|
16603
16642
|
];
|
|
16604
16643
|
const IMAGE_PLACE = {
|
|
16605
|
-
image: { type: "string", description: "The picture as a data:image/png;base64,\u2026 URL (up to 4 million pixels)." },
|
|
16644
|
+
image: resolveImage ? IMAGE_PARAM : { type: "string", description: "The picture as a data:image/png;base64,\u2026 URL (up to 4 million pixels)." },
|
|
16606
16645
|
x: { type: "number", description: "Left edge in document units (default 0)." },
|
|
16607
16646
|
y: { type: "number", description: "Top edge in document units (default 0)." },
|
|
16608
16647
|
width: { type: "number", exclusiveMinimum: 0, description: "Width in document units (default: the image width in pixels)." },
|
|
16609
16648
|
name: { type: "string", description: "Group name (default: the style's name)." }
|
|
16610
16649
|
};
|
|
16611
|
-
const decodeImage = (image) => {
|
|
16612
|
-
const parsed = _chunkW5M3J7PVcjs.parseDataUrl.call(void 0, image);
|
|
16613
|
-
if (!parsed || !_chunkW5M3J7PVcjs.isPng.call(void 0, parsed.bytes)) fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16614
|
-
try {
|
|
16615
|
-
return _chunkW5M3J7PVcjs.decodePng.call(void 0, parsed.bytes, { maxPixels: _chunkMNHP3AQTcjs.VECTOR_TRACE_MAX_PIXELS });
|
|
16616
|
-
} catch (e) {
|
|
16617
|
-
return fail(`The PNG could not be read: ${e.message}`);
|
|
16618
|
-
}
|
|
16619
|
-
};
|
|
16620
16650
|
const stylize = (style, label2, keywords, description, props) => ({
|
|
16621
16651
|
...base,
|
|
16622
16652
|
id: `${prefix}${style}Image`,
|
|
16623
16653
|
label: label2,
|
|
16624
16654
|
keywords: ["stylize", "stylise", "bitmap to vector", "artistic", "png to svg", ...keywords],
|
|
16625
|
-
description: `${description} Added as one group placed with its top-left at x, y and width wide; colours of one kind share a compound path. One undo step. Returns { id, paths: [{ id, name, fill, subpaths }] }.`,
|
|
16655
|
+
description: `${resolveImage ? description.replace("a PNG", IMAGE_WHAT) : description} Added as one group placed with its top-left at x, y and width wide; colours of one kind share a compound path. One undo step. Returns { id, paths: [{ id, name, fill, subpaths }] }.`,
|
|
16626
16656
|
params: { type: "object", properties: { ...IMAGE_PLACE, ...props }, required: ["image"], additionalProperties: false },
|
|
16627
16657
|
undoable: true,
|
|
16628
16658
|
run: (_ctx, params) => {
|
|
16629
16659
|
const { image, ...options2 } = params;
|
|
16630
|
-
|
|
16631
|
-
check2(r);
|
|
16632
|
-
return {
|
|
16633
|
-
id: r.id,
|
|
16634
|
-
paths: r.ids.map((id) => {
|
|
16635
|
-
const n2 = controller.getNode(id);
|
|
16636
|
-
return { id, name: n2.name, fill: n2.fill, subpaths: n2.subpaths.length };
|
|
16637
|
-
})
|
|
16638
|
-
};
|
|
16660
|
+
return withImage(image, (pixels) => imageResult(controller.stylizeImage(pixels, style, options2)));
|
|
16639
16661
|
}
|
|
16640
16662
|
});
|
|
16641
16663
|
const CELL = (dflt, what) => ({ type: "number", minimum: 1, description: `${what} in image pixels (default ${dflt}); larger draws fewer, bigger shapes.` });
|
package/dist/vector/index.js
CHANGED
|
@@ -16551,17 +16551,72 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16551
16551
|
}
|
|
16552
16552
|
}
|
|
16553
16553
|
];
|
|
16554
|
+
const resolveImage = options.resolveImage;
|
|
16555
|
+
const IMAGE_WHAT = resolveImage ? "an image (a PNG data URL, or a reference the editor loads)" : "a PNG (base64 data URL)";
|
|
16556
|
+
const IMAGE_PARAM = {
|
|
16557
|
+
type: "string",
|
|
16558
|
+
description: resolveImage ? `The image: a data:image/png;base64,\u2026 URL, or ${options.imageReferences ?? "an image URL"} (up to ${VECTOR_TRACE_MAX_PIXELS / 1e6} million pixels).` : "The image as a data:image/png;base64,\u2026 URL (up to 4 million pixels)."
|
|
16559
|
+
};
|
|
16560
|
+
const decodePngBytes = (bytes, what) => {
|
|
16561
|
+
if (!isPng(bytes)) fail(`${what} is not a PNG.`);
|
|
16562
|
+
try {
|
|
16563
|
+
return decodePng(bytes, { maxPixels: VECTOR_TRACE_MAX_PIXELS });
|
|
16564
|
+
} catch (e) {
|
|
16565
|
+
return fail(`The PNG could not be read: ${e.message}`);
|
|
16566
|
+
}
|
|
16567
|
+
};
|
|
16568
|
+
const fromResolved = (ref, resolved) => {
|
|
16569
|
+
if (!resolved || typeof resolved !== "object") return fail(`There is no image "${ref}".`);
|
|
16570
|
+
if ("image" in resolved && resolved.image) return resolved.image;
|
|
16571
|
+
if ("png" in resolved && typeof resolved.png === "string") {
|
|
16572
|
+
const parsed = parseDataUrl(resolved.png);
|
|
16573
|
+
if (!parsed) fail(`The image "${ref}" was loaded as something other than a PNG data URL.`);
|
|
16574
|
+
return decodePngBytes(parsed.bytes, `The image "${ref}"`);
|
|
16575
|
+
}
|
|
16576
|
+
if ("png" in resolved && resolved.png instanceof Uint8Array) return decodePngBytes(resolved.png, `The image "${ref}"`);
|
|
16577
|
+
return fail(`The image "${ref}" was loaded as nothing usable (expected { image } or { png }).`);
|
|
16578
|
+
};
|
|
16579
|
+
const withImage = (image, use) => {
|
|
16580
|
+
if (typeof image !== "string" || !image.trim()) fail("image is empty.");
|
|
16581
|
+
if (/^data:/i.test(image)) {
|
|
16582
|
+
const parsed = parseDataUrl(image);
|
|
16583
|
+
if (!parsed || !isPng(parsed.bytes)) fail(resolveImage ? "image must be a PNG data URL (data:image/png;base64,\u2026) or a reference to an image." : "image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16584
|
+
return use(decodePngBytes(parsed.bytes, "image"));
|
|
16585
|
+
}
|
|
16586
|
+
if (!resolveImage) return fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16587
|
+
const ref = image.trim();
|
|
16588
|
+
const load = async () => {
|
|
16589
|
+
let resolved;
|
|
16590
|
+
try {
|
|
16591
|
+
resolved = await resolveImage(ref, { maxPixels: VECTOR_TRACE_MAX_PIXELS });
|
|
16592
|
+
} catch (e) {
|
|
16593
|
+
return fail(`The image "${ref}" could not be loaded: ${e instanceof Error ? e.message : String(e)}`);
|
|
16594
|
+
}
|
|
16595
|
+
return use(fromResolved(ref, resolved));
|
|
16596
|
+
};
|
|
16597
|
+
return load();
|
|
16598
|
+
};
|
|
16599
|
+
const imageResult = (r) => {
|
|
16600
|
+
check2(r);
|
|
16601
|
+
return {
|
|
16602
|
+
id: r.id,
|
|
16603
|
+
paths: r.ids.map((id) => {
|
|
16604
|
+
const n2 = controller.getNode(id);
|
|
16605
|
+
return { id, name: n2.name, fill: n2.fill, subpaths: n2.subpaths.length };
|
|
16606
|
+
})
|
|
16607
|
+
};
|
|
16608
|
+
};
|
|
16554
16609
|
const traceCommands = () => [
|
|
16555
16610
|
{
|
|
16556
16611
|
...base,
|
|
16557
16612
|
id: `${prefix}traceImage`,
|
|
16558
16613
|
label: "Image Trace\u2026",
|
|
16559
16614
|
keywords: ["trace", "image trace", "live trace", "vectorize", "bitmap to vector", "png to svg", "autotrace", "posterize"],
|
|
16560
|
-
description: "Traces
|
|
16615
|
+
description: "Traces " + IMAGE_WHAT + " into vector paths and adds them as one group: `threshold` mode makes one even-odd path where the image is dark (or opaque with channel alpha); `color` mode makes a stacked path per colour (median-cut to `colors`, the most common at the bottom). Placed with its top-left at x, y and `width` wide (default one unit per pixel). One undo step. Returns { id, paths: [{ id, name, fill, subpaths }] }.",
|
|
16561
16616
|
params: {
|
|
16562
16617
|
type: "object",
|
|
16563
16618
|
properties: {
|
|
16564
|
-
image:
|
|
16619
|
+
image: IMAGE_PARAM,
|
|
16565
16620
|
mode: { type: "string", enum: ["threshold", "color"], description: "threshold (one colour, default) or color (a path per colour)." },
|
|
16566
16621
|
channel: { type: "string", enum: ["luminance", "alpha"], description: "Threshold mode: trace dark pixels (luminance, default) or opaque ones (alpha)." },
|
|
16567
16622
|
threshold: { type: "number", minimum: 0, maximum: 1, description: "Threshold mode: inside where darkness (or alpha) reaches this (default 0.5)." },
|
|
@@ -16581,61 +16636,28 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16581
16636
|
undoable: true,
|
|
16582
16637
|
run: (_ctx, params) => {
|
|
16583
16638
|
const { image, ...options2 } = params;
|
|
16584
|
-
|
|
16585
|
-
if (!parsed || !isPng(parsed.bytes)) fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16586
|
-
let decoded2;
|
|
16587
|
-
try {
|
|
16588
|
-
decoded2 = decodePng(parsed.bytes, { maxPixels: VECTOR_TRACE_MAX_PIXELS });
|
|
16589
|
-
} catch (e) {
|
|
16590
|
-
return fail(`The PNG could not be read: ${e.message}`);
|
|
16591
|
-
}
|
|
16592
|
-
const r = controller.traceImage(decoded2, options2);
|
|
16593
|
-
check2(r);
|
|
16594
|
-
return {
|
|
16595
|
-
id: r.id,
|
|
16596
|
-
paths: r.ids.map((id) => {
|
|
16597
|
-
const n2 = controller.getNode(id);
|
|
16598
|
-
return { id, name: n2.name, fill: n2.fill, subpaths: n2.subpaths.length };
|
|
16599
|
-
})
|
|
16600
|
-
};
|
|
16639
|
+
return withImage(image, (pixels) => imageResult(controller.traceImage(pixels, options2)));
|
|
16601
16640
|
}
|
|
16602
16641
|
}
|
|
16603
16642
|
];
|
|
16604
16643
|
const IMAGE_PLACE = {
|
|
16605
|
-
image: { type: "string", description: "The picture as a data:image/png;base64,\u2026 URL (up to 4 million pixels)." },
|
|
16644
|
+
image: resolveImage ? IMAGE_PARAM : { type: "string", description: "The picture as a data:image/png;base64,\u2026 URL (up to 4 million pixels)." },
|
|
16606
16645
|
x: { type: "number", description: "Left edge in document units (default 0)." },
|
|
16607
16646
|
y: { type: "number", description: "Top edge in document units (default 0)." },
|
|
16608
16647
|
width: { type: "number", exclusiveMinimum: 0, description: "Width in document units (default: the image width in pixels)." },
|
|
16609
16648
|
name: { type: "string", description: "Group name (default: the style's name)." }
|
|
16610
16649
|
};
|
|
16611
|
-
const decodeImage = (image) => {
|
|
16612
|
-
const parsed = parseDataUrl(image);
|
|
16613
|
-
if (!parsed || !isPng(parsed.bytes)) fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16614
|
-
try {
|
|
16615
|
-
return decodePng(parsed.bytes, { maxPixels: VECTOR_TRACE_MAX_PIXELS });
|
|
16616
|
-
} catch (e) {
|
|
16617
|
-
return fail(`The PNG could not be read: ${e.message}`);
|
|
16618
|
-
}
|
|
16619
|
-
};
|
|
16620
16650
|
const stylize = (style, label2, keywords, description, props) => ({
|
|
16621
16651
|
...base,
|
|
16622
16652
|
id: `${prefix}${style}Image`,
|
|
16623
16653
|
label: label2,
|
|
16624
16654
|
keywords: ["stylize", "stylise", "bitmap to vector", "artistic", "png to svg", ...keywords],
|
|
16625
|
-
description: `${description} Added as one group placed with its top-left at x, y and width wide; colours of one kind share a compound path. One undo step. Returns { id, paths: [{ id, name, fill, subpaths }] }.`,
|
|
16655
|
+
description: `${resolveImage ? description.replace("a PNG", IMAGE_WHAT) : description} Added as one group placed with its top-left at x, y and width wide; colours of one kind share a compound path. One undo step. Returns { id, paths: [{ id, name, fill, subpaths }] }.`,
|
|
16626
16656
|
params: { type: "object", properties: { ...IMAGE_PLACE, ...props }, required: ["image"], additionalProperties: false },
|
|
16627
16657
|
undoable: true,
|
|
16628
16658
|
run: (_ctx, params) => {
|
|
16629
16659
|
const { image, ...options2 } = params;
|
|
16630
|
-
|
|
16631
|
-
check2(r);
|
|
16632
|
-
return {
|
|
16633
|
-
id: r.id,
|
|
16634
|
-
paths: r.ids.map((id) => {
|
|
16635
|
-
const n2 = controller.getNode(id);
|
|
16636
|
-
return { id, name: n2.name, fill: n2.fill, subpaths: n2.subpaths.length };
|
|
16637
|
-
})
|
|
16638
|
-
};
|
|
16660
|
+
return withImage(image, (pixels) => imageResult(controller.stylizeImage(pixels, style, options2)));
|
|
16639
16661
|
}
|
|
16640
16662
|
});
|
|
16641
16663
|
const CELL = (dflt, what) => ({ type: "number", minimum: 1, description: `${what} in image pixels (default ${dflt}); larger draws fewer, bigger shapes.` });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.281.0",
|
|
4
4
|
"description": "UI-agnostic rich text and media editing core library",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -494,7 +494,7 @@
|
|
|
494
494
|
],
|
|
495
495
|
"sideEffects": false,
|
|
496
496
|
"devDependencies": {
|
|
497
|
-
"@anthropic-ai/sdk": "1.
|
|
497
|
+
"@anthropic-ai/sdk": "1.6.0",
|
|
498
498
|
"@dagrejs/dagre": "^1.1.8",
|
|
499
499
|
"@tiptap/core": "^2.27.3",
|
|
500
500
|
"@tiptap/extension-character-count": "^2.27.3",
|
|
@@ -521,17 +521,17 @@
|
|
|
521
521
|
"@tiptap/suggestion": "^2.27.3",
|
|
522
522
|
"@types/dagre": "^0.7.54",
|
|
523
523
|
"@types/markdown-it": "^14.2.0",
|
|
524
|
-
"@types/node": "^26.6.
|
|
524
|
+
"@types/node": "^26.6.6",
|
|
525
525
|
"assemblyscript": "0.28.20",
|
|
526
526
|
"fast-check": "^4.10.2",
|
|
527
527
|
"markdown-it": "^15.0.3",
|
|
528
528
|
"markdown-it-task-lists": "^2.1.1",
|
|
529
|
-
"mermaid": "12.
|
|
529
|
+
"mermaid": "12.3.0",
|
|
530
530
|
"prosemirror-markdown": "^1.13.9",
|
|
531
531
|
"tiptap-markdown": "^0.8.10",
|
|
532
532
|
"tsup": "^9.0.0",
|
|
533
533
|
"typescript": "^5.7.3",
|
|
534
|
-
"vitest": "^5.0
|
|
534
|
+
"vitest": "^5.1.0",
|
|
535
535
|
"@pixerate/markdown-conformance": "0.2.0"
|
|
536
536
|
},
|
|
537
537
|
"peerDependencies": {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pixerate-canvas
|
|
3
|
-
description: How to build and edit a Pixerate design canvas or node graph through its agent tools - drawing freehand pen, highlighter and dry-brush strokes (canvas.drawFreehand), annotating with hand-drawn circles, underlines, arrows, brackets, strikes, crosses and ticks (canvas.circle, canvas.arrow), adding, connecting, updating and laying out nodes (canvas_add_node, canvas_layout), reading state with get_editor_state and looking at it with view_editor, reusable components with variants, instances and overrides (create_component, add_variant, set_instance_properties), align, distribute, lock and layer order, shape boolean operations (unite, subtract, offset path, outline stroke), auto layout frames,
|
|
3
|
+
description: How to build and edit a Pixerate design canvas or node graph through its agent tools - drawing freehand pen, highlighter and dry-brush strokes (canvas.drawFreehand), annotating with hand-drawn circles, underlines, arrows, brackets, strikes, crosses and ticks (canvas.circle, canvas.arrow), adding, connecting, updating and laying out nodes (canvas_add_node, canvas_layout), reading state with get_editor_state and looking at it with view_editor, reusable components with variants, instances and overrides (create_component, add_variant, set_instance_properties), align, distribute, lock and layer order, shape boolean operations (unite, subtract, offset path, outline stroke), auto layout frames, keyframe animation with easing and CSS export, animated edges (dots, dashes, beams: set_edge_animation). Use when working on a canvas, whiteboard, flow chart, node graph, frame, design system, component library, button variants, layout, alignment, z-order, a canvas animation or animated connections in a Pixerate editor.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Pixerate canvas
|
|
7
7
|
|
|
8
8
|
The canvas is an xyflow (React Flow / SvelteFlow) graph: an array of **nodes** and **edges**.
|
|
9
9
|
A node is `{ id, type, position: { x, y }, width?, height?, parentId?, zIndex?, data }`; edges are
|
|
10
|
-
`{ id, source, target, label? }
|
|
10
|
+
`{ id, source, target, label?, data? }`, with `edge.data.animation` holding an edge's animation
|
|
11
|
+
(dots, dash, beam or pulse; not the keyframe tracks of a node's `data.animation`). Everything a design feature knows lives in `node.data`, so it
|
|
11
12
|
survives copy, paste and JSON:
|
|
12
13
|
|
|
13
14
|
- `data.label` / `data.name`: text content and layer name. `data.shape`: a shape node
|
|
@@ -161,6 +162,20 @@ undo step and is not saved with the document. The host decides what the children
|
|
|
161
162
|
| `explode_node` | Explode `node` (an id from `list_explodable_nodes`). Unless `multiple`, the node exploded before collapses (it comes back in `collapsed`). Exploding an exploded node changes nothing (`changed: false`). Returns the ids now `exploded`. Then use `get_editor_state` or `capture_view` to read or see the children. |
|
|
162
163
|
| `collapse_node` | Collapse an exploded `node`. Returns the ids still `exploded` and those still `collapsing` (their children leave when the collapse animation ends). Fails when the node is not exploded. |
|
|
163
164
|
|
|
165
|
+
### Animate edges
|
|
166
|
+
|
|
167
|
+
Hosts that draw edges with `<AnimatedEdge>` (or `<EdgeAnimation>` in their own edge) add these.
|
|
168
|
+
An edge's animation is data (`edge.data.animation`): it saves, copies and undoes with the edge.
|
|
169
|
+
Use motion to say something: dots for data or messages flowing, dashes for "live" or "pending",
|
|
170
|
+
a beam for a request or trigger, a pulse for the active or highlighted path. Animate the few
|
|
171
|
+
edges that matter, not the whole graph.
|
|
172
|
+
|
|
173
|
+
| Tool | Use it to |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| `list_edge_animations` | List the edges (`id`, `source`, `target`, `label`, `animation` or null), the animation `kinds` with their `defaults`, and the `readOnly` reason if edges cannot change. Call it first: it is the source of edge ids. |
|
|
176
|
+
| `set_edge_animation` | Animate `edges` (ids from `list_edge_animations`) with one `animation`: `kind` (dots, dash, beam, pulse) and optional `speed` (canvas units per second), `duration` (ms per cycle), `direction` (forward = source to target, reverse, alternate), `color`, `size`, `count`, `length`, `gap`, `glow`, `trackOpacity`, `paused`. Replaces what those edges had; all of them change in one undo step. Returns the ids, the `animation` stored and `resolved`, with every default filled in. Then `capture_view` to see it. |
|
|
177
|
+
| `clear_edge_animation` | Stop the animation on `edges`, leaving the plain edge. One undo step. Returns the ids `cleared` and those `unchanged` (they had none). |
|
|
178
|
+
|
|
164
179
|
### Proposals (suggest, don't edit)
|
|
165
180
|
|
|
166
181
|
Hosts that let agents suggest additions add these. A proposal is new nodes and edges shown as ghosts
|
|
@@ -354,6 +369,14 @@ effects provider (for example Flair); without one the read tools say so and the
|
|
|
354
369
|
|
|
355
370
|
## Pitfalls
|
|
356
371
|
|
|
372
|
+
- **Direction follows the edge, not the screen.** `forward` runs from the edge's `source` to its
|
|
373
|
+
`target`; read them from `list_edge_animations` before choosing, and use `reverse` rather than
|
|
374
|
+
re-connecting the edge to flip the flow.
|
|
375
|
+
- **Keep speeds comparable.** `speed` is in canvas units per second, so edges of different lengths
|
|
376
|
+
move at the same pace; `duration` makes a long edge move faster than a short one. Use the same
|
|
377
|
+
speed for edges that carry the same kind of flow. Users who ask for reduced motion see the first
|
|
378
|
+
frame, held still, so motion must not be the only sign of state: pair it with a colour or label.
|
|
379
|
+
|
|
357
380
|
- **Proposals are not on the canvas.** Their ids are not in `get_editor_state` until accepted, and
|
|
358
381
|
`canvas_connect` or `canvas_update_node` on them fails. Put the whole idea in one proposal (an
|
|
359
382
|
edge between two proposed nodes goes in its `edges`), or wait for the user to accept it.
|
|
@@ -131,7 +131,7 @@ the pitfalls, and uses the commands below.
|
|
|
131
131
|
|
|
132
132
|
| Command | Use it to |
|
|
133
133
|
| --- | --- |
|
|
134
|
-
| `vector.traceImage` | Turn a PNG (data URL) into vector paths in one group: `threshold` mode for one-colour art (logos, scans, silhouettes; `channel: "alpha"` for cut-outs), `color` mode for a stacked path per colour (`colors` 2–32). Raise `tolerance` for fewer anchors, `cornerRoundness` 0 for sharp polygons. Place it with `x`, `y`, `width`. Returns `{ id, paths }`; check `subpaths` counts and simplify or delete specks after. |
|
|
134
|
+
| `vector.traceImage` | Turn a PNG (data URL) into vector paths in one group, or an image the host loads by reference (its `image` description says which: a canvas image node's id, a URL); pass the reference instead of writing the image out: `threshold` mode for one-colour art (logos, scans, silhouettes; `channel: "alpha"` for cut-outs), `color` mode for a stacked path per colour (`colors` 2–32). Raise `tolerance` for fewer anchors, `cornerRoundness` 0 for sharp polygons. Place it with `x`, `y`, `width`. Returns `{ id, paths }`; check `subpaths` counts and simplify or delete specks after. |
|
|
135
135
|
| `vector.mosaicImage` | Stylise a PNG as mosaic tiles (`cell` px, `shape` square / circle, `gap`, `colors`): pixel art, tiled murals. Returns `{ id, paths }`, one compound path per colour. |
|
|
136
136
|
| `vector.halftoneImage` | Stylise a PNG as halftone dots on a turned screen (`cell`, `angle`, ink `color` or null for the picture's colours): comic and pop-art looks, print effects. |
|
|
137
137
|
| `vector.lowPolyImage` | Stylise a PNG as low-poly triangles (`cell`, `jitter`, `seed`, `colors`): geometric portraits and backdrops. |
|