@slithy/prim-interface 0.1.1 → 0.2.1
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/README.md +56 -12
- package/dist/index.d.ts +10 -4
- package/dist/index.js +56 -6
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@ Starts a reconstruction job. Returns a `JobHandle` for stopping, pausing, and re
|
|
|
15
15
|
```ts
|
|
16
16
|
const job = run(imageUrl, config, {
|
|
17
17
|
onStart(raster, svg) {},
|
|
18
|
-
onStep({ raster, svg, svgString, progress }) {},
|
|
19
|
-
onComplete() {},
|
|
18
|
+
onStep({ raster, svg, svgString, stepData, progress }) {},
|
|
19
|
+
onComplete(serialized) {},
|
|
20
20
|
onStop() {},
|
|
21
21
|
});
|
|
22
22
|
|
|
@@ -29,13 +29,15 @@ job.resume();
|
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
interface Config {
|
|
32
|
-
steps: number
|
|
33
|
-
shapes: number
|
|
34
|
-
mutations: number
|
|
35
|
-
alpha: number
|
|
36
|
-
mutateAlpha: boolean
|
|
37
|
-
computeSize: number
|
|
38
|
-
viewSize: number
|
|
32
|
+
steps: number // shapes in the final image
|
|
33
|
+
shapes: number // candidate shapes evaluated per step
|
|
34
|
+
mutations: number // random mutations tried per candidate
|
|
35
|
+
alpha: number // shape opacity
|
|
36
|
+
mutateAlpha: boolean // whether opacity is mutated per candidate
|
|
37
|
+
computeSize: number // internal render resolution (px, longest edge)
|
|
38
|
+
viewSize: number // display/output resolution (px, longest edge)
|
|
39
|
+
allowUpscale?: boolean // allow output larger than source image (default: false)
|
|
40
|
+
pixelRatio?: number // device pixel ratio for the raster canvas (default: 1)
|
|
39
41
|
shapeTypes: ShapeConstructor[]
|
|
40
42
|
fill: 'auto' | string
|
|
41
43
|
}
|
|
@@ -43,14 +45,56 @@ interface Config {
|
|
|
43
45
|
|
|
44
46
|
`width`, `height`, and `scale` are set automatically by `run()` after loading the source image.
|
|
45
47
|
|
|
48
|
+
**`allowUpscale`** — by default, output is capped at source image dimensions. Set to `true` to allow `viewSize` and `computeSize` to exceed the source, upscaling the output.
|
|
49
|
+
|
|
50
|
+
**`pixelRatio`** — pass `window.devicePixelRatio` to produce a HiDPI raster canvas. The canvas pixel dimensions are `viewSize × pixelRatio`; set its CSS size to `viewSize` for a 1:1 device pixel mapping. Does not affect the SVG output or the optimizer; only the raster display canvas.
|
|
51
|
+
|
|
52
|
+
### Callbacks
|
|
53
|
+
|
|
54
|
+
**`onStart(raster, svg)`** — called once after the source image loads. `raster` is the display canvas; `svg` is the live SVG element. Both update in place as shapes are added.
|
|
55
|
+
|
|
56
|
+
**`onStep(result)`** — called after each accepted shape:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
interface StepResult {
|
|
60
|
+
raster: HTMLCanvasElement
|
|
61
|
+
svg: SVGSVGElement
|
|
62
|
+
svgString: string // serialized SVG markup
|
|
63
|
+
stepData: StepData // structured data for this shape
|
|
64
|
+
progress: {
|
|
65
|
+
current: number // shapes placed so far
|
|
66
|
+
total: number // cfg.steps
|
|
67
|
+
similarity: number // % similarity to source (0–100)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**`onComplete(serialized)`** — called when all steps finish. Receives a `SerializedOutput` — a compact, storage-ready representation of the full run that can be replayed via `replayOutput()`.
|
|
73
|
+
|
|
46
74
|
### Exit helpers
|
|
47
75
|
|
|
48
|
-
-
|
|
76
|
+
Higher-level (rasterize SVG at save time for crisp output):
|
|
77
|
+
|
|
78
|
+
- **`saveRasterFromVector(svgString, width, height, format?, quality?)`** — downloads PNG or JPEG rasterized from the SVG
|
|
79
|
+
- **`copyRasterFromVector(svgString, width, height)`** — copies a PNG rasterized from the SVG to the clipboard
|
|
80
|
+
- **`shareFromVector(svgString, width, height)`** — shares a PNG rasterized from the SVG via the Web Share API
|
|
81
|
+
|
|
82
|
+
Lower-level (operate directly on a canvas or SVG string):
|
|
83
|
+
|
|
84
|
+
- **`saveRaster(canvas, format?, quality?)`** — downloads PNG or JPEG from a canvas
|
|
49
85
|
- **`saveVector(svgString)`** — downloads SVG
|
|
50
86
|
- **`copyRaster(canvas)`** — copies PNG to clipboard
|
|
51
87
|
- **`copyVector(svgString)`** — copies SVG markup to clipboard
|
|
52
|
-
- **`share(canvas)`** —
|
|
88
|
+
- **`share(canvas)`** — shares via Web Share API if available
|
|
89
|
+
|
|
90
|
+
### `replayOutput(data: SerializedOutput): ReplayResult`
|
|
91
|
+
|
|
92
|
+
Reconstructs a raster canvas and SVG from a `SerializedOutput` without re-running the optimizer. Use this to restore saved results from localStorage or a database.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const { raster, svg, svgString } = replayOutput(serializedData);
|
|
96
|
+
```
|
|
53
97
|
|
|
54
98
|
### Re-exports from prim-lib
|
|
55
99
|
|
|
56
|
-
`Triangle`, `Rectangle`, `Ellipse`, `Hexagon`, `
|
|
100
|
+
`Triangle`, `Rectangle`, `Ellipse`, `Square`, `Hexagon`, `Glyph`, `stepPerf`, `replayOutput`, `RGB`, `SerializedOutput`, `StepData`, `ReplayResult`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ShapeInterface } from '@slithy/prim-lib';
|
|
2
|
-
export { Bbox, Ellipse, Hexagon, Rectangle,
|
|
1
|
+
import { ShapeInterface, StepData, SerializedOutput } from '@slithy/prim-lib';
|
|
2
|
+
export { Bbox, Ellipse, Glyph, Hexagon, RGB, Rectangle, ReplayResult, SerializedOutput, ShapeInterface, Square, StepData, Triangle, replayOutput, stepPerf } from '@slithy/prim-lib';
|
|
3
3
|
|
|
4
4
|
interface Config {
|
|
5
5
|
steps: number;
|
|
@@ -9,6 +9,8 @@ interface Config {
|
|
|
9
9
|
mutateAlpha: boolean;
|
|
10
10
|
computeSize: number;
|
|
11
11
|
viewSize: number;
|
|
12
|
+
allowUpscale?: boolean;
|
|
13
|
+
pixelRatio?: number;
|
|
12
14
|
shapeTypes: Array<new (w: number, h: number) => ShapeInterface>;
|
|
13
15
|
fill: 'auto' | string;
|
|
14
16
|
width?: number;
|
|
@@ -19,6 +21,7 @@ interface StepResult {
|
|
|
19
21
|
raster: HTMLCanvasElement;
|
|
20
22
|
svg: SVGSVGElement;
|
|
21
23
|
svgString: string;
|
|
24
|
+
stepData: StepData;
|
|
22
25
|
progress: {
|
|
23
26
|
current: number;
|
|
24
27
|
total: number;
|
|
@@ -28,7 +31,7 @@ interface StepResult {
|
|
|
28
31
|
interface RunCallbacks {
|
|
29
32
|
onStart?: (raster: HTMLCanvasElement, svg: SVGSVGElement) => void;
|
|
30
33
|
onStep?: (result: StepResult) => void;
|
|
31
|
-
onComplete?: () => void;
|
|
34
|
+
onComplete?: (serialized: SerializedOutput) => void;
|
|
32
35
|
onStop?: () => void;
|
|
33
36
|
}
|
|
34
37
|
interface JobHandle {
|
|
@@ -40,9 +43,12 @@ interface JobHandle {
|
|
|
40
43
|
declare function run(source: string | File, cfg: Config, callbacks?: RunCallbacks): JobHandle;
|
|
41
44
|
|
|
42
45
|
declare function saveRaster(canvas: HTMLCanvasElement, format?: 'png' | 'jpeg', quality?: number): void;
|
|
46
|
+
declare function saveRasterFromVector(svgString: string, width: number, height: number, format?: 'png' | 'jpeg', quality?: number): Promise<void>;
|
|
47
|
+
declare function copyRasterFromVector(svgString: string, width: number, height: number): Promise<void>;
|
|
48
|
+
declare function shareFromVector(svgString: string, width: number, height: number): Promise<void>;
|
|
43
49
|
declare function saveVector(svgString: string): void;
|
|
44
50
|
declare function copyVector(svgString: string): Promise<void>;
|
|
45
51
|
declare function copyRaster(canvas: HTMLCanvasElement): Promise<void>;
|
|
46
52
|
declare function share(canvas: HTMLCanvasElement): Promise<void>;
|
|
47
53
|
|
|
48
|
-
export { type Config, type JobHandle, type RunCallbacks, type StepResult, copyRaster, copyVector, run, saveRaster, saveVector, share };
|
|
54
|
+
export { type Config, type JobHandle, type RunCallbacks, type StepResult, copyRaster, copyRasterFromVector, copyVector, run, saveRaster, saveRasterFromVector, saveVector, share, shareFromVector };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/run.ts
|
|
2
|
-
import { Canvas, Optimizer } from "@slithy/prim-lib";
|
|
2
|
+
import { Canvas, Optimizer, parseColor } from "@slithy/prim-lib";
|
|
3
3
|
function run(source, cfg, callbacks = {}) {
|
|
4
4
|
const url = source instanceof File ? URL.createObjectURL(source) : source;
|
|
5
5
|
let optimizer = null;
|
|
@@ -11,14 +11,17 @@ function run(source, cfg, callbacks = {}) {
|
|
|
11
11
|
}
|
|
12
12
|
if (source instanceof File) URL.revokeObjectURL(url);
|
|
13
13
|
const computeCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
14
|
+
const pixelRatio = cfg.pixelRatio ?? 1;
|
|
14
15
|
const viewCfg = { ...computeCfg, width: computeCfg.scale * computeCfg.width, height: computeCfg.scale * computeCfg.height };
|
|
15
|
-
const
|
|
16
|
-
result.
|
|
16
|
+
const deviceViewCfg = { ...viewCfg, width: viewCfg.width * pixelRatio, height: viewCfg.height * pixelRatio };
|
|
17
|
+
const result = Canvas.empty(deviceViewCfg);
|
|
18
|
+
result.ctx.scale(computeCfg.scale * pixelRatio, computeCfg.scale * pixelRatio);
|
|
17
19
|
const svg = Canvas.empty(computeCfg, true);
|
|
18
20
|
svg.setAttribute("width", String(viewCfg.width));
|
|
19
21
|
svg.setAttribute("height", String(viewCfg.height));
|
|
20
22
|
callbacks.onStart?.(result.node, svg);
|
|
21
23
|
const serializer = new XMLSerializer();
|
|
24
|
+
const accumulatedSteps = [];
|
|
22
25
|
optimizer = new Optimizer(original, computeCfg);
|
|
23
26
|
let stepCount = 0;
|
|
24
27
|
optimizer.onStep = (step) => {
|
|
@@ -26,10 +29,13 @@ function run(source, cfg, callbacks = {}) {
|
|
|
26
29
|
if (step) {
|
|
27
30
|
result.drawStep(step);
|
|
28
31
|
svg.appendChild(step.toSVG());
|
|
32
|
+
const stepData = step.serialize();
|
|
33
|
+
accumulatedSteps.push(stepData);
|
|
29
34
|
callbacks.onStep?.({
|
|
30
35
|
raster: result.node,
|
|
31
36
|
svg,
|
|
32
37
|
svgString: serializer.serializeToString(svg),
|
|
38
|
+
stepData,
|
|
33
39
|
progress: {
|
|
34
40
|
current: stepCount,
|
|
35
41
|
total: cfg.steps,
|
|
@@ -38,7 +44,14 @@ function run(source, cfg, callbacks = {}) {
|
|
|
38
44
|
});
|
|
39
45
|
}
|
|
40
46
|
if (stepCount >= cfg.steps) {
|
|
41
|
-
callbacks.onComplete?.(
|
|
47
|
+
callbacks.onComplete?.({
|
|
48
|
+
v: 1,
|
|
49
|
+
w: computeCfg.width,
|
|
50
|
+
h: computeCfg.height,
|
|
51
|
+
scale: computeCfg.scale,
|
|
52
|
+
fill: parseColor(cfg.fill),
|
|
53
|
+
steps: accumulatedSteps
|
|
54
|
+
});
|
|
42
55
|
}
|
|
43
56
|
};
|
|
44
57
|
optimizer.start();
|
|
@@ -63,12 +76,45 @@ function triggerDownload(url, filename) {
|
|
|
63
76
|
a.download = filename;
|
|
64
77
|
a.click();
|
|
65
78
|
}
|
|
79
|
+
function svgToCanvas(svgString, width, height) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const sized = svgString.replace(/(<svg[^>]*\s)width="[^"]*"/, `$1width="${width}"`).replace(/(<svg[^>]*\s)height="[^"]*"/, `$1height="${height}"`);
|
|
82
|
+
const blob = new Blob([sized], { type: "image/svg+xml" });
|
|
83
|
+
const url = URL.createObjectURL(blob);
|
|
84
|
+
const img = new Image();
|
|
85
|
+
img.onload = () => {
|
|
86
|
+
const canvas = document.createElement("canvas");
|
|
87
|
+
canvas.width = width;
|
|
88
|
+
canvas.height = height;
|
|
89
|
+
canvas.getContext("2d").drawImage(img, 0, 0, width, height);
|
|
90
|
+
URL.revokeObjectURL(url);
|
|
91
|
+
resolve(canvas);
|
|
92
|
+
};
|
|
93
|
+
img.onerror = () => {
|
|
94
|
+
URL.revokeObjectURL(url);
|
|
95
|
+
reject(new Error("Failed to rasterize SVG"));
|
|
96
|
+
};
|
|
97
|
+
img.src = url;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
66
100
|
function saveRaster(canvas, format = "png", quality = 0.92) {
|
|
67
101
|
const mimeType = format === "jpeg" ? "image/jpeg" : "image/png";
|
|
68
102
|
const ext = format === "jpeg" ? "jpg" : "png";
|
|
69
103
|
const dataUrl = canvas.toDataURL(mimeType, quality);
|
|
70
104
|
triggerDownload(dataUrl, `output.${ext}`);
|
|
71
105
|
}
|
|
106
|
+
async function saveRasterFromVector(svgString, width, height, format = "png", quality = 0.92) {
|
|
107
|
+
const canvas = await svgToCanvas(svgString, width, height);
|
|
108
|
+
saveRaster(canvas, format, quality);
|
|
109
|
+
}
|
|
110
|
+
async function copyRasterFromVector(svgString, width, height) {
|
|
111
|
+
const canvas = await svgToCanvas(svgString, width, height);
|
|
112
|
+
return copyRaster(canvas);
|
|
113
|
+
}
|
|
114
|
+
async function shareFromVector(svgString, width, height) {
|
|
115
|
+
const canvas = await svgToCanvas(svgString, width, height);
|
|
116
|
+
return share(canvas);
|
|
117
|
+
}
|
|
72
118
|
function saveVector(svgString) {
|
|
73
119
|
const blob = new Blob([svgString], { type: "image/svg+xml" });
|
|
74
120
|
const url = URL.createObjectURL(blob);
|
|
@@ -91,19 +137,23 @@ async function share(canvas) {
|
|
|
91
137
|
}
|
|
92
138
|
|
|
93
139
|
// src/index.ts
|
|
94
|
-
import { Triangle, Rectangle, Ellipse, Square, Hexagon,
|
|
140
|
+
import { Triangle, Rectangle, Ellipse, Square, Hexagon, Glyph, stepPerf, replayOutput } from "@slithy/prim-lib";
|
|
95
141
|
export {
|
|
96
142
|
Ellipse,
|
|
143
|
+
Glyph,
|
|
97
144
|
Hexagon,
|
|
98
145
|
Rectangle,
|
|
99
|
-
Smiley,
|
|
100
146
|
Square,
|
|
101
147
|
Triangle,
|
|
102
148
|
copyRaster,
|
|
149
|
+
copyRasterFromVector,
|
|
103
150
|
copyVector,
|
|
151
|
+
replayOutput,
|
|
104
152
|
run,
|
|
105
153
|
saveRaster,
|
|
154
|
+
saveRasterFromVector,
|
|
106
155
|
saveVector,
|
|
107
156
|
share,
|
|
157
|
+
shareFromVector,
|
|
108
158
|
stepPerf
|
|
109
159
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slithy/prim-interface",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Browser-facing API for primitive-based image reconstruction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"sideEffects": false,
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@slithy/prim-lib": "0.
|
|
17
|
+
"@slithy/prim-lib": "0.2.1"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@vitest/coverage-v8": "^4.1.0",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"tsup": "^8",
|
|
23
23
|
"typescript": "^5",
|
|
24
24
|
"vitest": "^4",
|
|
25
|
-
"@slithy/
|
|
26
|
-
"@slithy/
|
|
25
|
+
"@slithy/eslint-config": "0.0.0",
|
|
26
|
+
"@slithy/tsconfig": "0.0.0"
|
|
27
27
|
},
|
|
28
28
|
"author": {
|
|
29
29
|
"name": "Matthew Campagna",
|