@slithy/prim-lib 0.3.2 → 0.4.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 CHANGED
@@ -10,15 +10,15 @@ Reconstructs an image by iteratively placing geometric shapes. Each step evaluat
10
10
 
11
11
  ### Classes
12
12
 
13
- - **`Canvas`** — wraps `HTMLCanvasElement`; handles image loading, pixel reads, drawing steps, and SVG output
14
- - **`Optimizer`** — runs the rAF loop; calls `onStep` after each shape is placed
13
+ - **`Canvas`** — wraps `HTMLCanvasElement` or `OffscreenCanvas` (environment-detected); handles image loading, pixel reads, drawing steps, and SVG output
14
+ - **`Optimizer`** — runs the step loop; calls `onStep` after each shape is placed. Accepts an optional `schedule` function (defaults to `requestAnimationFrame`; pass `fn => setTimeout(fn, 0)` for worker contexts)
15
15
  - **`State`** — holds the current canvas and its distance from the target
16
16
  - **`Step`** — a single candidate shape placement; computes best color and difference change
17
17
 
18
18
  ### Shapes
19
19
 
20
20
  - **`Shape`** — abstract base
21
- - **`Triangle`**, **`Rectangle`**, **`Ellipse`**, **`Square`**, **`Hexagon`**, **`Glyph`**, **`Debug`**
21
+ - **`Triangle`**, **`Rectangle`**, **`Ellipse`**, **`Circle`**, **`Square`**, **`Hexagon`**, **`Glyph`**, **`Debug`**
22
22
 
23
23
  ### Types
24
24
 
@@ -38,12 +38,14 @@ interface Cfg {
38
38
  allowUpscale?: boolean // allow output larger than source image (default: false)
39
39
  scale?: number // viewSize / computeSize ratio (set by Canvas.original)
40
40
  shapeTypes: Array<new (w: number, h: number) => ShapeInterface>
41
+ shapeWeights?: number[] // per-shape selection weights (same length as shapeTypes)
41
42
  fill: 'auto' | string
42
43
  }
43
44
  ```
44
45
 
45
46
  - **`PreCfg`** — `Cfg` with optional `width`/`height`; used before image dimensions are known
46
47
  - **`ShapeInterface`** — structural interface for shapes; includes `toData(alpha, color): StepData`
48
+ - **`Ctx2D`** — `CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D`; used for canvas operations that work in both main-thread and worker contexts
47
49
  - **`Bbox`**, **`Point`**, **`ImageDataLike`**, **`ShapeImageData`**
48
50
 
49
51
  **`RGB`** — `[number, number, number]` tuple
@@ -55,6 +57,7 @@ type StepData =
55
57
  | { t: 't'; a: number; c: RGB; pts: [number, number][] } // Triangle
56
58
  | { t: 'r'; a: number; c: RGB; pts: [number, number][] } // Rectangle
57
59
  | { t: 'e'; a: number; c: RGB; cx: number; cy: number; rx: number; ry: number } // Ellipse
60
+ | { t: 'c'; a: number; c: RGB; cx: number; cy: number; r: number } // Circle
58
61
  | { t: 's'; a: number; c: RGB; cx: number; cy: number; r: number } // Square
59
62
  | { t: 'h'; a: number; c: RGB; cx: number; cy: number; r: number; angle: number } // Hexagon
60
63
  | { t: 'sm'; a: number; c: RGB; cx: number; cy: number; fs: number; text: string } // Glyph
@@ -77,7 +80,7 @@ interface SerializedOutput {
77
80
 
78
81
  ```ts
79
82
  interface ReplayResult {
80
- raster: HTMLCanvasElement
83
+ raster: HTMLCanvasElement | OffscreenCanvas
81
84
  svg: SVGSVGElement
82
85
  svgString: string
83
86
  }
@@ -87,6 +90,10 @@ interface ReplayResult {
87
90
 
88
91
  **`replayOutput(data: SerializedOutput): ReplayResult`** — reconstructs canvas and SVG natively from serialized output, without re-running the optimizer. Useful for restoring saved results from localStorage or a database.
89
92
 
93
+ **`renderStepToCtx(data: StepData, ctx: Ctx2D): void`** — renders a single `StepData` onto a 2D canvas context. Used internally by `replayOutput` and by `runWorker` to incrementally apply worker-posted steps to the display canvas.
94
+
95
+ **`stepDataToSVGElement(data: StepData): SVGElement`** — creates a DOM `SVGElement` from a `StepData`. Used internally by `replayOutput` and by `runWorker` to build the live SVG incrementally on the main thread.
96
+
90
97
  ### Utilities
91
98
 
92
99
  - **`getFill(data: ImageDataLike): string`** — computes a fill color (average of corner pixels) from image data
@@ -101,14 +108,17 @@ interface ReplayResult {
101
108
  - **ESM only** — no CommonJS; no `importScripts()`
102
109
  - **Canvas reuse** — the original creates a new `<canvas>` element for every shape rasterization call (~58,000 per run at default settings). `prim-lib` reuses a single module-level canvas, growing it only when a larger bbox is seen. This delivered a ~27× speedup (37 s → ~0.8 s rasterize time per run)
103
110
  - **`willReadFrequently: true`** — canvas contexts used for `getImageData` are created with this flag, keeping pixel data in CPU memory and suppressing browser warnings
104
- - **Workers removed** — the original included worker stubs (`importScripts()`-based, not ESM-compatible). Workers were investigated and benchmarked; after the canvas reuse optimization, a batched worker implementation matched single-threaded performance. Workers are not used
111
+ - **Worker-compatible** — `Canvas` detects its environment: in a browser context it creates `HTMLCanvasElement`; in a worker it creates `OffscreenCanvas` (same 2D API). `Canvas.fromBitmap()` loads an image from an `ImageBitmap` (worker-compatible) rather than `new Image()`. `Optimizer` accepts an injectable `schedule` function so callers can substitute `setTimeout` for `requestAnimationFrame` in worker contexts. The original included `importScripts()`-based worker stubs (not ESM-compatible) which were removed; worker support is now done properly via `prim-interface`'s `runWorker()`
105
112
  - **Architecture split** — the original is a single-layer library. Here, `prim-lib` is the pure algorithm (no knowledge of how images arrive or where results go), and `prim-interface` is the adapter that wires it to the browser
106
113
  - **`PreCfg` / `Cfg` distinction** — `Canvas.original()` accepts `PreCfg` (optional `width`/`height`) and resolves to a fully-populated `Cfg`, making the config lifecycle explicit in the types
107
114
  - **Serialization** — `StepData` / `SerializedOutput` allow completed runs to be stored compactly and replayed via `replayOutput()` without re-running the optimizer
115
+ - **Additional shapes** — `Circle`, `Square`, `Hexagon`, and `Glyph` are not in the original; `Circle` is a uniform-radius variant of `Ellipse`
116
+ - **Shape weighting** — `shapeWeights` allows biased shape selection with a guaranteed distribution: the optimizer pre-allocates exact per-shape step counts (largest-remainder rounding) and shuffles them, so the final mix always matches the requested weights
108
117
 
109
118
  ## Architecture notes
110
119
 
111
- - `Shape.rasterize()` reuses a single module-level canvas (`_rasterCanvas`) grown to the max shape size seen; avoids per-call `createElement('canvas')` which was the dominant cost (27x speedup)
112
- - `Cfg.shapeTypes` holds shape constructors; shapes are chosen randomly each step
113
- - `PreCfg` exists to bridge the gap between call time (dimensions unknown) and runtime (dimensions set by `Canvas.original()`)
114
- - `Canvas.svgRoot()` is a static helper shared by `Canvas.empty()` and `replayOutput()` to create the SVG root with clip path and background fill
120
+ - `Shape.rasterize()` reuses a single module-level canvas (`_rasterCanvas`) grown to the max shape size seen; in a worker, this is an `OffscreenCanvas` (environment-detected by the `Canvas` constructor). Avoids per-call canvas creation which was the dominant cost (27x speedup)
121
+ - `Cfg.shapeTypes` holds shape constructors; shapes are chosen randomly each step unless `shapeWeights` is set
122
+ - When `shapeWeights` is provided (same length as `shapeTypes`), `Optimizer` builds a step plan at construction time: each shape type is allocated an exact number of slots proportional to its weight (using largest-remainder rounding), then the plan is Fisher-Yates shuffled. This guarantees the final shape distribution matches the weights, rather than just biasing random selection
123
+ - `PreCfg` exists to bridge the gap between call time (dimensions unknown) and runtime (dimensions set by `Canvas.original()` or `Canvas.fromBitmap()`)
124
+ - `Canvas.svgRoot()` is a static helper shared by `Canvas.empty()` and `replayOutput()` to create the SVG root with clip path and background fill; it uses DOM APIs and is main-thread only
package/dist/index.d.ts CHANGED
@@ -30,7 +30,7 @@ interface ShapeInterface {
30
30
  rasterize(alpha: number): {
31
31
  getImageData(): ImageDataLike;
32
32
  };
33
- render(ctx: CanvasRenderingContext2D): void;
33
+ render(ctx: Ctx2D): void;
34
34
  }
35
35
  interface Cfg {
36
36
  width: number;
@@ -57,6 +57,7 @@ type PreCfg = Omit<Cfg, 'width' | 'height'> & {
57
57
  height?: number;
58
58
  };
59
59
  type RGB = [number, number, number];
60
+ type Ctx2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
60
61
  type StepData = {
61
62
  t: 't';
62
63
  a: number;
@@ -119,17 +120,18 @@ interface DrawableStep {
119
120
  alpha: number;
120
121
  color: string;
121
122
  shape: {
122
- render(ctx: CanvasRenderingContext2D): void;
123
+ render(ctx: Ctx2D): void;
123
124
  };
124
125
  }
125
126
  declare class Canvas {
126
- node: HTMLCanvasElement;
127
- ctx: CanvasRenderingContext2D;
127
+ node: HTMLCanvasElement | OffscreenCanvas;
128
+ ctx: Ctx2D;
128
129
  _imageData: ImageData | null;
129
130
  static svgRoot(width: number, height: number, fill: string): SVGSVGElement;
130
131
  static empty(cfg: Cfg, svg: true): SVGSVGElement;
131
132
  static empty(cfg: Cfg, svg?: false): Canvas;
132
133
  static original(url: string, cfg: PreCfg): Promise<Canvas>;
134
+ static fromBitmap(bitmap: ImageBitmap, cfg: PreCfg): Canvas;
133
135
  static test(cfg: PreCfg): Canvas;
134
136
  constructor(width: number, height: number, willReadFrequently?: boolean);
135
137
  clone(): Canvas;
@@ -176,7 +178,8 @@ declare class Optimizer {
176
178
  _stopped: boolean;
177
179
  _paused: boolean;
178
180
  _stepPlan: ShapeCtor[];
179
- constructor(original: Canvas, cfg: Cfg);
181
+ _schedule: (fn: () => void) => void;
182
+ constructor(original: Canvas, cfg: Cfg, schedule?: (fn: () => void) => void);
180
183
  start(): void;
181
184
  stop(): void;
182
185
  pause(): void;
@@ -198,7 +201,7 @@ declare class Shape implements ShapeInterface {
198
201
  rasterize(alpha: number): {
199
202
  getImageData(): ImageDataLike;
200
203
  };
201
- render(_ctx: CanvasRenderingContext2D): void;
204
+ render(_ctx: Ctx2D): void;
202
205
  }
203
206
  declare class Polygon extends Shape {
204
207
  points: Point[];
@@ -296,11 +299,13 @@ declare function computeColorAndDifferenceChange(offset: Bbox, imageData: ShapeI
296
299
  differenceChange: number;
297
300
  };
298
301
 
302
+ declare function renderStepToCtx(data: StepData, ctx: Ctx2D): void;
303
+ declare function stepDataToSVGElement(data: StepData): SVGElement;
299
304
  interface ReplayResult {
300
- raster: HTMLCanvasElement;
305
+ raster: HTMLCanvasElement | OffscreenCanvas;
301
306
  svg: SVGSVGElement;
302
307
  svgString: string;
303
308
  }
304
309
  declare function replayOutput(data: SerializedOutput): ReplayResult;
305
310
 
306
- export { type Bbox, Canvas, type Cfg, Circle, Debug, Ellipse, Glyph, Hexagon, type ImageDataLike, Optimizer, type Point, type PreCfg, type RGB, Rectangle, type ReplayResult, SVGNS, type SerializedOutput, Shape, type ShapeImageData, type ShapeInterface, Square, State, Step, type StepData, Triangle, clamp, clampColor, computeColorAndDifferenceChange, difference, differenceToDistance, distanceToDifference, getFill, parseColor, replayOutput, stepPerf };
311
+ export { type Bbox, Canvas, type Cfg, Circle, type Ctx2D, Debug, Ellipse, Glyph, Hexagon, type ImageDataLike, Optimizer, type Point, type PreCfg, type RGB, Rectangle, type ReplayResult, SVGNS, type SerializedOutput, Shape, type ShapeImageData, type ShapeInterface, Square, State, Step, type StepData, Triangle, clamp, clampColor, computeColorAndDifferenceChange, difference, differenceToDistance, distanceToDifference, getFill, parseColor, renderStepToCtx, replayOutput, stepDataToSVGElement, stepPerf };
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
1
4
  // src/util.ts
2
5
  var SVGNS = "http://www.w3.org/2000/svg";
3
6
  function parseColor(color) {
@@ -5,18 +8,23 @@ function parseColor(color) {
5
8
  if (!m) throw new Error(`Cannot parse color: ${color}`);
6
9
  return [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])];
7
10
  }
11
+ __name(parseColor, "parseColor");
8
12
  function clamp(x, min, max) {
9
13
  return Math.max(min, Math.min(max, x));
10
14
  }
15
+ __name(clamp, "clamp");
11
16
  function clampColor(x) {
12
17
  return clamp(x, 0, 255);
13
18
  }
19
+ __name(clampColor, "clampColor");
14
20
  function distanceToDifference(distance, pixels) {
15
21
  return Math.pow(distance * 255, 2) * (3 * pixels);
16
22
  }
23
+ __name(distanceToDifference, "distanceToDifference");
17
24
  function differenceToDistance(difference2, pixels) {
18
25
  return Math.sqrt(difference2 / (3 * pixels)) / 255;
19
26
  }
27
+ __name(differenceToDistance, "differenceToDistance");
20
28
  function difference(data, dataOther) {
21
29
  let sum = 0, dr = 0, dg = 0, db = 0;
22
30
  for (let i = 0; i < data.data.length; i += 4) {
@@ -27,6 +35,7 @@ function difference(data, dataOther) {
27
35
  }
28
36
  return sum;
29
37
  }
38
+ __name(difference, "difference");
30
39
  function getFill(data) {
31
40
  const w = data.width;
32
41
  const h = data.height;
@@ -49,6 +58,7 @@ function getFill(data) {
49
58
  rgb = rgb.map((x) => ~~(x / count)).map(clampColor);
50
59
  return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
51
60
  }
61
+ __name(getFill, "getFill");
52
62
  function computeColorAndDifferenceChange(offset, imageData, alpha) {
53
63
  const { shape, current, target } = imageData;
54
64
  const shapeData = shape.data;
@@ -116,12 +126,14 @@ function computeColorAndDifferenceChange(offset, imageData, alpha) {
116
126
  }
117
127
  return { color: `rgb(${cr}, ${cg}, ${cb})`, differenceChange: sum };
118
128
  }
129
+ __name(computeColorAndDifferenceChange, "computeColorAndDifferenceChange");
119
130
 
120
131
  // src/canvas.ts
121
132
  function getScale(width, height, limit, allowUpscale = false) {
122
133
  const scale = Math.max(width / limit, height / limit);
123
134
  return allowUpscale ? scale : Math.max(scale, 1);
124
135
  }
136
+ __name(getScale, "getScale");
125
137
  function svgRect(w, h) {
126
138
  const node = document.createElementNS(SVGNS, "rect");
127
139
  node.setAttribute("x", "0");
@@ -130,7 +142,11 @@ function svgRect(w, h) {
130
142
  node.setAttribute("height", String(h));
131
143
  return node;
132
144
  }
145
+ __name(svgRect, "svgRect");
133
146
  var Canvas = class _Canvas {
147
+ static {
148
+ __name(this, "Canvas");
149
+ }
134
150
  node;
135
151
  ctx;
136
152
  _imageData;
@@ -190,6 +206,22 @@ var Canvas = class _Canvas {
190
206
  };
191
207
  });
192
208
  }
209
+ static fromBitmap(bitmap, cfg) {
210
+ const w = bitmap.width;
211
+ const h = bitmap.height;
212
+ const computeScale = getScale(w, h, cfg.computeSize, cfg.allowUpscale);
213
+ cfg.width = w / computeScale;
214
+ cfg.height = h / computeScale;
215
+ const viewScale = getScale(w, h, cfg.viewSize, cfg.allowUpscale);
216
+ cfg.scale = computeScale / viewScale;
217
+ const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
218
+ const canvas = this.empty(fullCfg);
219
+ canvas.ctx.drawImage(bitmap, 0, 0, fullCfg.width, fullCfg.height);
220
+ if (cfg.fill === "auto") {
221
+ cfg.fill = getFill(canvas.getImageData());
222
+ }
223
+ return canvas;
224
+ }
193
225
  static test(cfg) {
194
226
  cfg.width = cfg.computeSize;
195
227
  cfg.height = cfg.computeSize;
@@ -216,10 +248,17 @@ var Canvas = class _Canvas {
216
248
  return canvas;
217
249
  }
218
250
  constructor(width, height, willReadFrequently = false) {
219
- this.node = document.createElement("canvas");
220
- this.node.width = width;
221
- this.node.height = height;
222
- this.ctx = this.node.getContext("2d", { willReadFrequently });
251
+ if (typeof document !== "undefined") {
252
+ const el = document.createElement("canvas");
253
+ el.width = width;
254
+ el.height = height;
255
+ this.node = el;
256
+ this.ctx = el.getContext("2d", { willReadFrequently });
257
+ } else {
258
+ const el = new OffscreenCanvas(width, height);
259
+ this.node = el;
260
+ this.ctx = el.getContext("2d", { willReadFrequently });
261
+ }
223
262
  this._imageData = null;
224
263
  }
225
264
  clone() {
@@ -257,6 +296,9 @@ var Canvas = class _Canvas {
257
296
 
258
297
  // src/state.ts
259
298
  var State = class {
299
+ static {
300
+ __name(this, "State");
301
+ }
260
302
  target;
261
303
  canvas;
262
304
  distance;
@@ -279,6 +321,9 @@ var stepPerf = {
279
321
  }
280
322
  };
281
323
  var Step = class _Step {
324
+ static {
325
+ __name(this, "Step");
326
+ }
282
327
  shape;
283
328
  cfg;
284
329
  alpha;
@@ -343,6 +388,9 @@ var Step = class _Step {
343
388
  // src/shape.ts
344
389
  var _rasterCanvas = null;
345
390
  var Shape = class {
391
+ static {
392
+ __name(this, "Shape");
393
+ }
346
394
  bbox;
347
395
  static randomPoint(width, height) {
348
396
  return [~~(Math.random() * width), ~~(Math.random() * height)];
@@ -394,12 +442,15 @@ var Shape = class {
394
442
  ctx.translate(-this.bbox.left, -this.bbox.top);
395
443
  this.render(ctx);
396
444
  const data = ctx.getImageData(0, 0, w, h);
397
- return { getImageData: () => data };
445
+ return { getImageData: /* @__PURE__ */ __name(() => data, "getImageData") };
398
446
  }
399
447
  render(_ctx) {
400
448
  }
401
449
  };
402
450
  var Polygon = class _Polygon extends Shape {
451
+ static {
452
+ __name(this, "Polygon");
453
+ }
403
454
  points;
404
455
  constructor(w, h, count) {
405
456
  super(w, h);
@@ -473,6 +524,9 @@ var Polygon = class _Polygon extends Shape {
473
524
  }
474
525
  };
475
526
  var Triangle = class _Triangle extends Polygon {
527
+ static {
528
+ __name(this, "Triangle");
529
+ }
476
530
  constructor(w, h) {
477
531
  super(w, h, 3);
478
532
  }
@@ -484,6 +538,9 @@ var Triangle = class _Triangle extends Polygon {
484
538
  }
485
539
  };
486
540
  var Rectangle = class _Rectangle extends Polygon {
541
+ static {
542
+ __name(this, "Rectangle");
543
+ }
487
544
  constructor(w, h) {
488
545
  super(w, h, 4);
489
546
  }
@@ -533,6 +590,9 @@ var Rectangle = class _Rectangle extends Polygon {
533
590
  }
534
591
  };
535
592
  var Ellipse = class _Ellipse extends Shape {
593
+ static {
594
+ __name(this, "Ellipse");
595
+ }
536
596
  center;
537
597
  rx;
538
598
  ry;
@@ -594,6 +654,9 @@ var Ellipse = class _Ellipse extends Shape {
594
654
  }
595
655
  };
596
656
  var Circle = class _Circle extends Shape {
657
+ static {
658
+ __name(this, "Circle");
659
+ }
597
660
  center;
598
661
  r;
599
662
  constructor(w, h) {
@@ -647,6 +710,9 @@ var Circle = class _Circle extends Shape {
647
710
  }
648
711
  };
649
712
  var Glyph = class _Glyph extends Shape {
713
+ static {
714
+ __name(this, "Glyph");
715
+ }
650
716
  center;
651
717
  text;
652
718
  fontSize;
@@ -710,6 +776,9 @@ var Glyph = class _Glyph extends Shape {
710
776
  }
711
777
  };
712
778
  var Square = class _Square extends Shape {
779
+ static {
780
+ __name(this, "Square");
781
+ }
713
782
  center;
714
783
  r;
715
784
  constructor(w, h) {
@@ -762,6 +831,9 @@ var Square = class _Square extends Shape {
762
831
  }
763
832
  };
764
833
  var Hexagon = class _Hexagon extends Shape {
834
+ static {
835
+ __name(this, "Hexagon");
836
+ }
765
837
  center;
766
838
  r;
767
839
  angle;
@@ -844,6 +916,9 @@ var Hexagon = class _Hexagon extends Shape {
844
916
  }
845
917
  };
846
918
  var Debug = class extends Shape {
919
+ static {
920
+ __name(this, "Debug");
921
+ }
847
922
  constructor(w, h) {
848
923
  super(w, h);
849
924
  this.bbox = { left: 0, top: 0, width: w, height: h };
@@ -870,7 +945,11 @@ function buildStepPlan(cfg) {
870
945
  }
871
946
  return plan;
872
947
  }
948
+ __name(buildStepPlan, "buildStepPlan");
873
949
  var Optimizer = class {
950
+ static {
951
+ __name(this, "Optimizer");
952
+ }
874
953
  cfg;
875
954
  state;
876
955
  onStep;
@@ -878,7 +957,8 @@ var Optimizer = class {
878
957
  _stopped;
879
958
  _paused;
880
959
  _stepPlan;
881
- constructor(original, cfg) {
960
+ _schedule;
961
+ constructor(original, cfg, schedule = (fn) => requestAnimationFrame(fn)) {
882
962
  this.cfg = cfg;
883
963
  this.state = new State(original, Canvas.empty(cfg));
884
964
  this._steps = 0;
@@ -887,6 +967,7 @@ var Optimizer = class {
887
967
  this.onStep = () => {
888
968
  };
889
969
  this._stepPlan = buildStepPlan(cfg);
970
+ this._schedule = schedule;
890
971
  }
891
972
  start() {
892
973
  this._stopped = false;
@@ -922,7 +1003,7 @@ var Optimizer = class {
922
1003
  return;
923
1004
  }
924
1005
  if (this._steps < this.cfg.steps) {
925
- requestAnimationFrame(() => this._addShape());
1006
+ this._schedule(() => this._addShape());
926
1007
  }
927
1008
  }
928
1009
  _findBestStep() {
@@ -947,7 +1028,7 @@ var Optimizer = class {
947
1028
  let resolve;
948
1029
  let bestStep = step;
949
1030
  const promise = new Promise((r) => resolve = r);
950
- const tryMutation = () => {
1031
+ const tryMutation = /* @__PURE__ */ __name(() => {
951
1032
  if (failedAttempts >= LIMIT) {
952
1033
  return resolve(bestStep);
953
1034
  }
@@ -960,7 +1041,7 @@ var Optimizer = class {
960
1041
  }
961
1042
  tryMutation();
962
1043
  });
963
- };
1044
+ }, "tryMutation");
964
1045
  tryMutation();
965
1046
  return promise;
966
1047
  }
@@ -970,13 +1051,15 @@ var Optimizer = class {
970
1051
  function rgbString([r, g, b]) {
971
1052
  return `rgb(${r}, ${g}, ${b})`;
972
1053
  }
1054
+ __name(rgbString, "rgbString");
973
1055
  function hexPoints(cx, cy, r, angle) {
974
1056
  return Array.from({ length: 6 }, (_, i) => {
975
1057
  const a = angle + i * Math.PI / 3;
976
1058
  return [~~(cx + r * Math.cos(a)), ~~(cy + r * Math.sin(a))];
977
1059
  });
978
1060
  }
979
- function renderStep(data, ctx) {
1061
+ __name(hexPoints, "hexPoints");
1062
+ function renderStepToCtx(data, ctx) {
980
1063
  ctx.globalAlpha = data.a;
981
1064
  ctx.fillStyle = rgbString(data.c);
982
1065
  switch (data.t) {
@@ -1021,7 +1104,8 @@ function renderStep(data, ctx) {
1021
1104
  }
1022
1105
  }
1023
1106
  }
1024
- function stepToSVG(data) {
1107
+ __name(renderStepToCtx, "renderStepToCtx");
1108
+ function stepDataToSVGElement(data) {
1025
1109
  const color = rgbString(data.c);
1026
1110
  const opacity = data.a.toFixed(2);
1027
1111
  let node;
@@ -1078,6 +1162,7 @@ function stepToSVG(data) {
1078
1162
  node.setAttribute("fill-opacity", opacity);
1079
1163
  return node;
1080
1164
  }
1165
+ __name(stepDataToSVGElement, "stepDataToSVGElement");
1081
1166
  function replayOutput(data) {
1082
1167
  const fill = rgbString(data.fill);
1083
1168
  const vw = data.w * data.scale;
@@ -1089,12 +1174,13 @@ function replayOutput(data) {
1089
1174
  svg.setAttribute("width", String(vw));
1090
1175
  svg.setAttribute("height", String(vh));
1091
1176
  for (const step of data.steps) {
1092
- renderStep(step, raster.ctx);
1093
- svg.appendChild(stepToSVG(step));
1177
+ renderStepToCtx(step, raster.ctx);
1178
+ svg.appendChild(stepDataToSVGElement(step));
1094
1179
  }
1095
1180
  const svgString = new XMLSerializer().serializeToString(svg);
1096
1181
  return { raster: raster.node, svg, svgString };
1097
1182
  }
1183
+ __name(replayOutput, "replayOutput");
1098
1184
  export {
1099
1185
  Canvas,
1100
1186
  Circle,
@@ -1118,6 +1204,8 @@ export {
1118
1204
  distanceToDifference,
1119
1205
  getFill,
1120
1206
  parseColor,
1207
+ renderStepToCtx,
1121
1208
  replayOutput,
1209
+ stepDataToSVGElement,
1122
1210
  stepPerf
1123
1211
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slithy/prim-lib",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "Core engine for primitive-based image reconstruction.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -14,13 +14,13 @@
14
14
  ],
15
15
  "sideEffects": false,
16
16
  "devDependencies": {
17
- "@vitest/coverage-v8": "^4.1.0",
18
- "jsdom": "^28",
17
+ "@vitest/coverage-v8": "^4.1.2",
18
+ "jsdom": "^29.0.1",
19
19
  "tsup": "^8",
20
20
  "typescript": "^5",
21
- "vitest": "^4",
22
- "@slithy/eslint-config": "0.0.0",
23
- "@slithy/tsconfig": "0.0.0"
21
+ "vitest": "^4.1.2",
22
+ "@slithy/tsconfig": "0.0.0",
23
+ "@slithy/eslint-config": "0.0.0"
24
24
  },
25
25
  "author": {
26
26
  "name": "Matthew Campagna",
@@ -44,8 +44,8 @@
44
44
  },
45
45
  "scripts": {
46
46
  "clean": "rm -rf dist",
47
- "build": "rm -rf dist && tsup src/index.ts --format esm --dts",
48
- "dev": "tsup src/index.ts --format esm --watch",
47
+ "build": "rm -rf dist && tsup src/index.ts --format esm --dts --keep-names",
48
+ "dev": "tsup src/index.ts --format esm --watch --keep-names",
49
49
  "typecheck": "tsc --noEmit",
50
50
  "lint": "eslint .",
51
51
  "test": "vitest run",