@slithy/prim-interface 0.3.2 → 0.4.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/README.md +34 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +13 -4
- package/dist/worker-entry.d.ts +14 -3
- package/dist/worker-entry.js +30 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,6 +6,21 @@ Browser-facing API for `@slithy/prim-lib`. Consumed by `apps/prim-demo`.
|
|
|
6
6
|
|
|
7
7
|
Wraps `prim-lib` in a `run()` / `runWorker()` API and provides save/copy/share helpers for exporting results. `run()` runs the optimizer on the main thread; `runWorker()` moves computation into a Web Worker, keeping the main thread responsive during long jobs. Both share the same callback API.
|
|
8
8
|
|
|
9
|
+
## Vite setup
|
|
10
|
+
|
|
11
|
+
This package ships a Web Worker and is incompatible with Vite's dependency optimizer. Add it to `optimizeDeps.exclude` in your Vite config:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// vite.config.ts
|
|
15
|
+
optimizeDeps: {
|
|
16
|
+
exclude: ['@slithy/prim-interface']
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The same applies to `electron-vite` projects — the exclusion goes under `renderer.optimizeDeps.exclude`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
9
24
|
## Exports
|
|
10
25
|
|
|
11
26
|
### `run(source, config, callbacks)` / `runWorker(source, config, callbacks)`
|
|
@@ -18,6 +33,7 @@ const job = run(imageUrl, config, { // main-thread
|
|
|
18
33
|
onStep({ raster, svg, svgString, stepData, progress }) {},
|
|
19
34
|
onComplete(serialized) {},
|
|
20
35
|
onStop() {},
|
|
36
|
+
onError(message) {},
|
|
21
37
|
});
|
|
22
38
|
|
|
23
39
|
const job = runWorker(imageUrl, config, { // off-thread
|
|
@@ -25,6 +41,7 @@ const job = runWorker(imageUrl, config, { // off-thread
|
|
|
25
41
|
onStep({ raster, svg, svgString, stepData, progress }) {},
|
|
26
42
|
onComplete(serialized) {},
|
|
27
43
|
onStop() {},
|
|
44
|
+
onError(message) {},
|
|
28
45
|
});
|
|
29
46
|
|
|
30
47
|
job.stop();
|
|
@@ -34,7 +51,7 @@ job.resume();
|
|
|
34
51
|
|
|
35
52
|
**`run()`** — runs the optimizer on the main thread via `requestAnimationFrame`. Simpler; no worker overhead. UI may feel sluggish during compute-heavy steps at high `shapes` / `mutations` settings.
|
|
36
53
|
|
|
37
|
-
**`runWorker()`** — moves all computation into a Web Worker using `OffscreenCanvas`. The main thread only handles DOM updates (appending the canvas/SVG, calling callbacks). Requires a bundler that understands `new Worker(new URL(..., import.meta.url))` — Vite handles this
|
|
54
|
+
**`runWorker()`** — moves all computation into a Web Worker using `OffscreenCanvas`. The main thread only handles DOM updates (appending the canvas/SVG, calling callbacks). Requires a bundler that understands `new Worker(new URL(..., import.meta.url))` — Vite handles this, but see the Vite setup section above for a required config change. Both fixed shapes (`Triangle`, `Rectangle`, etc.) and factory shapes (`makeNGon`, `makeRect`) are supported in `config.shapeTypes`.
|
|
38
55
|
|
|
39
56
|
### Config
|
|
40
57
|
|
|
@@ -49,6 +66,7 @@ interface Config {
|
|
|
49
66
|
viewSize: number // display/output resolution (px, longest edge)
|
|
50
67
|
allowUpscale?: boolean // allow output larger than source image (default: false)
|
|
51
68
|
pixelRatio?: number // device pixel ratio for the raster canvas (default: 1)
|
|
69
|
+
glyphChar?: string // Unicode character for the Glyph shape (default: '☺')
|
|
52
70
|
shapeTypes: ShapeConstructor[]
|
|
53
71
|
shapeWeights?: number[] // per-shape selection weights (same length as shapeTypes)
|
|
54
72
|
fill: 'auto' | string
|
|
@@ -61,6 +79,8 @@ interface Config {
|
|
|
61
79
|
|
|
62
80
|
**`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.
|
|
63
81
|
|
|
82
|
+
**`glyphChar`** — Unicode character used by the `Glyph` shape when running via `runWorker()`. Defaults to `'☺'` when omitted. Has no effect with `run()`, since the main-thread path accepts shape constructors directly (subclass `Glyph` with a custom character if needed).
|
|
83
|
+
|
|
64
84
|
**`shapeWeights`** — optional array of weights, one per entry in `shapeTypes`, controlling how often each shape type is used. Weights are relative (e.g. `[3, 1]` means 75% / 25%). When provided, the optimizer pre-allocates an exact number of slots per shape type (proportional to the weights) and shuffles them, guaranteeing the final distribution matches — rather than just biasing random selection.
|
|
65
85
|
|
|
66
86
|
### Callbacks
|
|
@@ -85,6 +105,10 @@ interface StepResult {
|
|
|
85
105
|
|
|
86
106
|
**`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()`.
|
|
87
107
|
|
|
108
|
+
**`onStop()`** — called when the job is stopped via `job.stop()`.
|
|
109
|
+
|
|
110
|
+
**`onError(message)`** — called if the job fails (e.g. image failed to load or decode). The job is considered stopped after this; no further callbacks will fire.
|
|
111
|
+
|
|
88
112
|
### Exit helpers
|
|
89
113
|
|
|
90
114
|
Higher-level (rasterize SVG at save time for crisp output):
|
|
@@ -122,4 +146,12 @@ const { raster, svg, svgString } = replayOutput(serializedData);
|
|
|
122
146
|
|
|
123
147
|
### Re-exports from prim-lib
|
|
124
148
|
|
|
125
|
-
`Triangle`, `Rectangle`, `Ellipse`, `Circle`, `Square`, `Hexagon`, `Glyph
|
|
149
|
+
**Shapes:** `Triangle`, `Rectangle`, `Ellipse`, `Circle`, `Square`, `Hexagon`, `Glyph`
|
|
150
|
+
|
|
151
|
+
**Factories:** `makeNGon`, `makeRect`
|
|
152
|
+
|
|
153
|
+
**Factory option types:** `NGonOptions`, `RectOptions`
|
|
154
|
+
|
|
155
|
+
**Functions:** `stepPerf`, `replayOutput`
|
|
156
|
+
|
|
157
|
+
**Types:** `RGB`, `SerializedOutput`, `StepData`, `ReplayResult`, `ShapeInterface`, `Bbox`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ShapeInterface, StepData, SerializedOutput } from '@slithy/prim-lib';
|
|
2
|
-
export { Bbox, Circle, Ellipse, Glyph, Hexagon, RGB, Rectangle, ReplayResult, SerializedOutput, ShapeInterface, Square, StepData, Triangle, replayOutput, stepPerf } from '@slithy/prim-lib';
|
|
2
|
+
export { Bbox, Circle, Ellipse, Glyph, Hexagon, NGonOptions, RGB, RectOptions, Rectangle, ReplayResult, SerializedOutput, ShapeInterface, Square, StepData, Triangle, makeNGon, makeRect, replayOutput, stepPerf } from '@slithy/prim-lib';
|
|
3
3
|
|
|
4
4
|
interface Config {
|
|
5
5
|
steps: number;
|
|
@@ -11,6 +11,7 @@ interface Config {
|
|
|
11
11
|
viewSize: number;
|
|
12
12
|
allowUpscale?: boolean;
|
|
13
13
|
pixelRatio?: number;
|
|
14
|
+
glyphChar?: string;
|
|
14
15
|
shapeTypes: Array<new (w: number, h: number) => ShapeInterface>;
|
|
15
16
|
shapeWeights?: number[];
|
|
16
17
|
fill: 'auto' | string;
|
|
@@ -34,6 +35,7 @@ interface RunCallbacks {
|
|
|
34
35
|
onStep?: (result: StepResult) => void;
|
|
35
36
|
onComplete?: (serialized: SerializedOutput) => void;
|
|
36
37
|
onStop?: () => void;
|
|
38
|
+
onError?: (message: string) => void;
|
|
37
39
|
}
|
|
38
40
|
interface JobHandle {
|
|
39
41
|
stop(): void;
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,8 @@ function run(source, cfg, callbacks = {}) {
|
|
|
58
58
|
};
|
|
59
59
|
optimizer.start();
|
|
60
60
|
}).catch((err) => {
|
|
61
|
-
|
|
61
|
+
if (source instanceof File) URL.revokeObjectURL(url);
|
|
62
|
+
callbacks.onError?.(err instanceof Error ? err.message : String(err));
|
|
62
63
|
});
|
|
63
64
|
return {
|
|
64
65
|
stop: () => {
|
|
@@ -123,15 +124,21 @@ function runWorker(source, cfg, callbacks = {}) {
|
|
|
123
124
|
} else if (msg.type === "stopped") {
|
|
124
125
|
if (isBlob) URL.revokeObjectURL(url);
|
|
125
126
|
callbacks.onStop?.();
|
|
127
|
+
} else if (msg.type === "error") {
|
|
128
|
+
if (isBlob) URL.revokeObjectURL(url);
|
|
129
|
+
callbacks.onError?.(msg.message);
|
|
126
130
|
}
|
|
127
131
|
};
|
|
128
132
|
worker.onerror = (e) => {
|
|
129
|
-
|
|
133
|
+
if (isBlob) URL.revokeObjectURL(url);
|
|
134
|
+
callbacks.onError?.(e.message ?? "Unknown worker error");
|
|
130
135
|
};
|
|
131
136
|
const { shapeTypes, pixelRatio: _pr, width: _w, height: _h, scale: _s, ...rest } = cfg;
|
|
132
137
|
const workerCfg = {
|
|
133
138
|
...rest,
|
|
134
|
-
shapeTypeNames: shapeTypes.map(
|
|
139
|
+
shapeTypeNames: shapeTypes.map(
|
|
140
|
+
(ctor) => "_shapeSpec" in ctor ? ctor._shapeSpec : ctor.name
|
|
141
|
+
)
|
|
135
142
|
};
|
|
136
143
|
worker.postMessage({ type: "start", url, cfg: workerCfg });
|
|
137
144
|
return {
|
|
@@ -226,7 +233,7 @@ async function share(canvas, options) {
|
|
|
226
233
|
}
|
|
227
234
|
|
|
228
235
|
// src/index.ts
|
|
229
|
-
import { Triangle, Rectangle, Ellipse, Circle, Square, Hexagon, Glyph, stepPerf, replayOutput } from "@slithy/prim-lib";
|
|
236
|
+
import { Triangle, Rectangle, Ellipse, Circle, Square, Hexagon, Glyph, stepPerf, replayOutput, makeNGon, makeRect } from "@slithy/prim-lib";
|
|
230
237
|
export {
|
|
231
238
|
Circle,
|
|
232
239
|
Ellipse,
|
|
@@ -238,6 +245,8 @@ export {
|
|
|
238
245
|
copyRaster,
|
|
239
246
|
copyRasterFromVector,
|
|
240
247
|
copyVector,
|
|
248
|
+
makeNGon,
|
|
249
|
+
makeRect,
|
|
241
250
|
replayOutput,
|
|
242
251
|
run,
|
|
243
252
|
runWorker,
|
package/dist/worker-entry.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { PreCfg, StepData, SerializedOutput } from '@slithy/prim-lib';
|
|
1
|
+
import { NGonOptions, RectOptions, PreCfg, StepData, SerializedOutput } from '@slithy/prim-lib';
|
|
2
2
|
|
|
3
|
+
type ShapeTypeSpec = string | {
|
|
4
|
+
f: 'ngon';
|
|
5
|
+
o: NGonOptions;
|
|
6
|
+
} | {
|
|
7
|
+
f: 'rect';
|
|
8
|
+
o: RectOptions;
|
|
9
|
+
};
|
|
3
10
|
type WorkerCfg = Omit<PreCfg, 'shapeTypes'> & {
|
|
4
|
-
shapeTypeNames:
|
|
11
|
+
shapeTypeNames: ShapeTypeSpec[];
|
|
12
|
+
glyphChar?: string;
|
|
5
13
|
};
|
|
6
14
|
type WorkerInbound = {
|
|
7
15
|
type: 'start';
|
|
@@ -33,6 +41,9 @@ type WorkerOutbound = {
|
|
|
33
41
|
serialized: SerializedOutput;
|
|
34
42
|
} | {
|
|
35
43
|
type: 'stopped';
|
|
44
|
+
} | {
|
|
45
|
+
type: 'error';
|
|
46
|
+
message: string;
|
|
36
47
|
};
|
|
37
48
|
|
|
38
|
-
export type { WorkerCfg, WorkerInbound, WorkerOutbound };
|
|
49
|
+
export type { ShapeTypeSpec, WorkerCfg, WorkerInbound, WorkerOutbound };
|
package/dist/worker-entry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/worker-entry.ts
|
|
2
|
-
import { Canvas, Optimizer, parseColor } from "@slithy/prim-lib";
|
|
2
|
+
import { Canvas, Optimizer, parseColor, makeNGon, makeRect } from "@slithy/prim-lib";
|
|
3
3
|
import { Triangle, Rectangle, Ellipse, Circle, Square, Hexagon, Glyph } from "@slithy/prim-lib";
|
|
4
4
|
var SHAPES = {
|
|
5
5
|
Triangle,
|
|
@@ -16,15 +16,38 @@ self.onmessage = async (e) => {
|
|
|
16
16
|
switch (msg.type) {
|
|
17
17
|
case "start": {
|
|
18
18
|
const { url, cfg: workerCfg } = msg;
|
|
19
|
-
const { shapeTypeNames, ...rest } = workerCfg;
|
|
20
|
-
const shapeTypes = shapeTypeNames.map((
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const { shapeTypeNames, glyphChar, ...rest } = workerCfg;
|
|
20
|
+
const shapeTypes = shapeTypeNames.map((spec) => {
|
|
21
|
+
if (typeof spec === "object") {
|
|
22
|
+
switch (spec.f) {
|
|
23
|
+
case "ngon":
|
|
24
|
+
return makeNGon(spec.o);
|
|
25
|
+
case "rect":
|
|
26
|
+
return makeRect(spec.o);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const ctor = SHAPES[spec];
|
|
30
|
+
if (!ctor) throw new Error(`[prim worker] Unknown shape type: ${spec}`);
|
|
31
|
+
if (spec === "Glyph" && glyphChar) {
|
|
32
|
+
const char = glyphChar;
|
|
33
|
+
return class GlyphWithChar extends Glyph {
|
|
34
|
+
constructor(w, h) {
|
|
35
|
+
super(w, h, char);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
23
39
|
return ctor;
|
|
24
40
|
});
|
|
25
41
|
const preCfg = { ...rest, shapeTypes };
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
let blob;
|
|
43
|
+
let bitmap;
|
|
44
|
+
try {
|
|
45
|
+
blob = await fetch(url).then((r) => r.blob());
|
|
46
|
+
bitmap = await createImageBitmap(blob);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
self.postMessage({ type: "error", message: err instanceof Error ? err.message : String(err) });
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
28
51
|
const original = Canvas.fromBitmap(bitmap, preCfg);
|
|
29
52
|
bitmap.close();
|
|
30
53
|
const computeCfg = { ...preCfg, width: preCfg.width, height: preCfg.height };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slithy/prim-interface",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Browser-facing API for primitive-based image reconstruction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"sideEffects": false,
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@slithy/prim-lib": "0.
|
|
21
|
+
"@slithy/prim-lib": "0.5.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@vitest/coverage-v8": "^4.1.2",
|