@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 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 automatically. Shape constructors in `config.shapeTypes` are mapped to names internally; only the built-in shapes (`Triangle`, `Rectangle`, `Ellipse`, `Circle`, `Square`, `Hexagon`, `Glyph`) are supported.
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`, `stepPerf`, `replayOutput`, `RGB`, `SerializedOutput`, `StepData`, `ReplayResult`
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
- console.error("[prim] run failed:", err);
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
- console.error("[prim worker]", e);
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((ctor) => ctor.name)
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,
@@ -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: string[];
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 };
@@ -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((name) => {
21
- const ctor = SHAPES[name];
22
- if (!ctor) throw new Error(`[prim worker] Unknown shape type: ${name}`);
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
- const blob = await fetch(url).then((r) => r.blob());
27
- const bitmap = await createImageBitmap(blob);
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.2",
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.4.1"
21
+ "@slithy/prim-lib": "0.5.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@vitest/coverage-v8": "^4.1.2",