@slithy/prim-interface 0.1.1 → 0.2.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
@@ -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 // 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)
38
- viewSize: number // display resolution (px)
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
- - **`saveRaster(canvas, format?, quality?)`** downloads PNG or JPEG
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)`** — uses Web Share API if available
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`, `Smiley`, `stepPerf`
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, ShapeInterface, Smiley, Square, Triangle, stepPerf } from '@slithy/prim-lib';
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 result = Canvas.empty(viewCfg);
16
- result.ctx.scale(computeCfg.scale, computeCfg.scale);
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, Smiley, stepPerf } from "@slithy/prim-lib";
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.1.1",
3
+ "version": "0.2.0",
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.1.1"
17
+ "@slithy/prim-lib": "0.2.0"
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/tsconfig": "0.0.0",
26
- "@slithy/eslint-config": "0.0.0"
25
+ "@slithy/eslint-config": "0.0.0",
26
+ "@slithy/tsconfig": "0.0.0"
27
27
  },
28
28
  "author": {
29
29
  "name": "Matthew Campagna",