@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 +19 -9
- package/dist/index.d.ts +13 -8
- package/dist/index.js +101 -13
- package/package.json +8 -8
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
|
|
14
|
-
- **`Optimizer`** — runs the
|
|
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
|
-
- **
|
|
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;
|
|
112
|
-
- `Cfg.shapeTypes` holds shape constructors; shapes are chosen randomly each step
|
|
113
|
-
- `
|
|
114
|
-
- `
|
|
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:
|
|
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:
|
|
123
|
+
render(ctx: Ctx2D): void;
|
|
123
124
|
};
|
|
124
125
|
}
|
|
125
126
|
declare class Canvas {
|
|
126
|
-
node: HTMLCanvasElement;
|
|
127
|
-
ctx:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1093
|
-
svg.appendChild(
|
|
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
|
+
"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.
|
|
18
|
-
"jsdom": "^
|
|
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/
|
|
23
|
-
"@slithy/
|
|
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",
|