@slithy/prim-lib 0.8.2 → 0.9.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/LICENSE-COMMERCIAL.md +2 -2
- package/dist/index.d.ts +41 -22
- package/dist/index.js +275 -344
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -32,6 +32,42 @@ function clamp(x, min, max) {
|
|
|
32
32
|
return Math.max(min, Math.min(max, x));
|
|
33
33
|
}
|
|
34
34
|
__name(clamp, "clamp");
|
|
35
|
+
function rectCorners(cx, cy, hw, hh, angle) {
|
|
36
|
+
const cos = Math.cos(angle);
|
|
37
|
+
const sin = Math.sin(angle);
|
|
38
|
+
return [
|
|
39
|
+
[cx - hw * cos + hh * sin, cy - hw * sin - hh * cos],
|
|
40
|
+
[cx + hw * cos + hh * sin, cy + hw * sin - hh * cos],
|
|
41
|
+
[cx + hw * cos - hh * sin, cy + hw * sin + hh * cos],
|
|
42
|
+
[cx - hw * cos - hh * sin, cy - hw * sin + hh * cos]
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
__name(rectCorners, "rectCorners");
|
|
46
|
+
function regularPolygonPoints(cx, cy, sides, angle, radius) {
|
|
47
|
+
return Array.from({ length: sides }, (_, i) => {
|
|
48
|
+
const a = angle + i * 2 * Math.PI / sides;
|
|
49
|
+
const r = typeof radius === "function" ? radius(i) : radius;
|
|
50
|
+
return [cx + r * Math.cos(a), cy + r * Math.sin(a)];
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
__name(regularPolygonPoints, "regularPolygonPoints");
|
|
54
|
+
function bboxOfPoints(points) {
|
|
55
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
56
|
+
for (const [x, y] of points) {
|
|
57
|
+
if (x < minX) minX = x;
|
|
58
|
+
if (y < minY) minY = y;
|
|
59
|
+
if (x > maxX) maxX = x;
|
|
60
|
+
if (y > maxY) maxY = y;
|
|
61
|
+
}
|
|
62
|
+
return { left: minX, top: minY, width: maxX - minX || 1, height: maxY - minY || 1 };
|
|
63
|
+
}
|
|
64
|
+
__name(bboxOfPoints, "bboxOfPoints");
|
|
65
|
+
function randomPolarOffset(scale) {
|
|
66
|
+
const angle = Math.random() * 2 * Math.PI;
|
|
67
|
+
const radius = Math.random() * scale;
|
|
68
|
+
return [~~(radius * Math.cos(angle)), ~~(radius * Math.sin(angle))];
|
|
69
|
+
}
|
|
70
|
+
__name(randomPolarOffset, "randomPolarOffset");
|
|
35
71
|
function clampColor(x) {
|
|
36
72
|
return clamp(x, 0, 255);
|
|
37
73
|
}
|
|
@@ -67,13 +103,19 @@ function getFill(data) {
|
|
|
67
103
|
if (x > 0 && y > 0 && x < w - 1 && y < h - 1) {
|
|
68
104
|
continue;
|
|
69
105
|
}
|
|
70
|
-
count++;
|
|
71
106
|
i = 4 * (x + y * w);
|
|
107
|
+
if (d[i + 3] === 0) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
count++;
|
|
72
111
|
rgb[0] += d[i];
|
|
73
112
|
rgb[1] += d[i + 1];
|
|
74
113
|
rgb[2] += d[i + 2];
|
|
75
114
|
}
|
|
76
115
|
}
|
|
116
|
+
if (count === 0) {
|
|
117
|
+
return "rgb(255, 255, 255)";
|
|
118
|
+
}
|
|
77
119
|
rgb = rgb.map((x) => ~~(x / count)).map(clampColor);
|
|
78
120
|
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
|
|
79
121
|
}
|
|
@@ -172,16 +214,7 @@ var Canvas = class _Canvas {
|
|
|
172
214
|
static svgRoot(width, height, fill) {
|
|
173
215
|
const node = document.createElementNS(SVGNS, "svg");
|
|
174
216
|
node.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
175
|
-
|
|
176
|
-
const defs = document.createElementNS(SVGNS, "defs");
|
|
177
|
-
node.appendChild(defs);
|
|
178
|
-
const cp = document.createElementNS(SVGNS, "clipPath");
|
|
179
|
-
defs.appendChild(cp);
|
|
180
|
-
cp.setAttribute("id", "clip");
|
|
181
|
-
cp.setAttribute("clipPathUnits", "objectBoundingBox");
|
|
182
|
-
let rect = svgRect(width, height);
|
|
183
|
-
cp.appendChild(rect);
|
|
184
|
-
rect = svgRect(width, height);
|
|
217
|
+
const rect = svgRect(width, height);
|
|
185
218
|
rect.setAttribute("fill", fill);
|
|
186
219
|
node.appendChild(rect);
|
|
187
220
|
return node;
|
|
@@ -190,14 +223,14 @@ var Canvas = class _Canvas {
|
|
|
190
223
|
if (svg) {
|
|
191
224
|
return this.svgRoot(cfg.width, cfg.height, cfg.outputFill ?? cfg.fill);
|
|
192
225
|
} else {
|
|
193
|
-
return new this(cfg.width, cfg.height).fill(cfg.fill);
|
|
226
|
+
return new this(cfg.width, cfg.height, true).fill(cfg.fill);
|
|
194
227
|
}
|
|
195
228
|
}
|
|
196
229
|
static original(url, cfg) {
|
|
197
230
|
if (url == "test") {
|
|
198
231
|
return Promise.resolve(this.test(cfg));
|
|
199
232
|
}
|
|
200
|
-
return new Promise((resolve) => {
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
201
234
|
const img = new Image();
|
|
202
235
|
if (!url.startsWith("blob:") && !url.startsWith("data:")) {
|
|
203
236
|
img.crossOrigin = "anonymous";
|
|
@@ -207,12 +240,12 @@ var Canvas = class _Canvas {
|
|
|
207
240
|
const w = img.naturalWidth;
|
|
208
241
|
const h = img.naturalHeight;
|
|
209
242
|
const computeScale = getScale(w, h, cfg.computeSize, cfg.allowUpscale);
|
|
210
|
-
cfg.width = w / computeScale;
|
|
211
|
-
cfg.height = h / computeScale;
|
|
243
|
+
cfg.width = Math.round(w / computeScale);
|
|
244
|
+
cfg.height = Math.round(h / computeScale);
|
|
212
245
|
const viewScale = getScale(w, h, cfg.viewSize, cfg.allowUpscale);
|
|
213
246
|
cfg.scale = computeScale / viewScale;
|
|
214
247
|
const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
215
|
-
const canvas = this
|
|
248
|
+
const canvas = new this(fullCfg.width, fullCfg.height, true);
|
|
216
249
|
canvas.ctx.drawImage(img, 0, 0, fullCfg.width, fullCfg.height);
|
|
217
250
|
if (cfg.fill === "transparent") {
|
|
218
251
|
cfg.outputFill = "transparent";
|
|
@@ -221,11 +254,15 @@ var Canvas = class _Canvas {
|
|
|
221
254
|
if (cfg.fill === "auto") {
|
|
222
255
|
cfg.fill = getFill(canvas.getImageData());
|
|
223
256
|
}
|
|
257
|
+
canvas.ctx.globalCompositeOperation = "destination-over";
|
|
258
|
+
canvas.ctx.fillStyle = cfg.fill;
|
|
259
|
+
canvas.ctx.fillRect(0, 0, fullCfg.width, fullCfg.height);
|
|
260
|
+
canvas.ctx.globalCompositeOperation = "source-over";
|
|
261
|
+
canvas._imageData = null;
|
|
224
262
|
resolve(canvas);
|
|
225
263
|
};
|
|
226
|
-
img.onerror = (
|
|
227
|
-
|
|
228
|
-
alert("The image URL cannot be loaded. Does the server support CORS?");
|
|
264
|
+
img.onerror = () => {
|
|
265
|
+
reject(new Error("The image URL cannot be loaded. Does the server support CORS?"));
|
|
229
266
|
};
|
|
230
267
|
});
|
|
231
268
|
}
|
|
@@ -233,12 +270,12 @@ var Canvas = class _Canvas {
|
|
|
233
270
|
const w = bitmap.width;
|
|
234
271
|
const h = bitmap.height;
|
|
235
272
|
const computeScale = getScale(w, h, cfg.computeSize, cfg.allowUpscale);
|
|
236
|
-
cfg.width = w / computeScale;
|
|
237
|
-
cfg.height = h / computeScale;
|
|
273
|
+
cfg.width = Math.round(w / computeScale);
|
|
274
|
+
cfg.height = Math.round(h / computeScale);
|
|
238
275
|
const viewScale = getScale(w, h, cfg.viewSize, cfg.allowUpscale);
|
|
239
276
|
cfg.scale = computeScale / viewScale;
|
|
240
277
|
const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
241
|
-
const canvas = this
|
|
278
|
+
const canvas = new this(fullCfg.width, fullCfg.height, true);
|
|
242
279
|
canvas.ctx.drawImage(bitmap, 0, 0, fullCfg.width, fullCfg.height);
|
|
243
280
|
if (cfg.fill === "transparent") {
|
|
244
281
|
cfg.outputFill = "transparent";
|
|
@@ -247,6 +284,11 @@ var Canvas = class _Canvas {
|
|
|
247
284
|
if (cfg.fill === "auto") {
|
|
248
285
|
cfg.fill = getFill(canvas.getImageData());
|
|
249
286
|
}
|
|
287
|
+
canvas.ctx.globalCompositeOperation = "destination-over";
|
|
288
|
+
canvas.ctx.fillStyle = cfg.fill;
|
|
289
|
+
canvas.ctx.fillRect(0, 0, fullCfg.width, fullCfg.height);
|
|
290
|
+
canvas.ctx.globalCompositeOperation = "source-over";
|
|
291
|
+
canvas._imageData = null;
|
|
250
292
|
return canvas;
|
|
251
293
|
}
|
|
252
294
|
static test(cfg) {
|
|
@@ -280,22 +322,27 @@ var Canvas = class _Canvas {
|
|
|
280
322
|
el.width = width;
|
|
281
323
|
el.height = height;
|
|
282
324
|
this.node = el;
|
|
283
|
-
|
|
325
|
+
const ctx = el.getContext("2d", { willReadFrequently });
|
|
326
|
+
if (!ctx) throw new Error("Failed to acquire 2d rendering context");
|
|
327
|
+
this.ctx = ctx;
|
|
284
328
|
} else {
|
|
285
329
|
const el = new OffscreenCanvas(width, height);
|
|
286
330
|
this.node = el;
|
|
287
|
-
|
|
331
|
+
const ctx = el.getContext("2d", { willReadFrequently });
|
|
332
|
+
if (!ctx) throw new Error("Failed to acquire 2d rendering context");
|
|
333
|
+
this.ctx = ctx;
|
|
288
334
|
}
|
|
289
335
|
this._imageData = null;
|
|
290
336
|
}
|
|
291
337
|
clone() {
|
|
292
|
-
const other = new _Canvas(this.node.width, this.node.height);
|
|
338
|
+
const other = new _Canvas(this.node.width, this.node.height, true);
|
|
293
339
|
other.ctx.drawImage(this.node, 0, 0);
|
|
294
340
|
return other;
|
|
295
341
|
}
|
|
296
342
|
fill(color) {
|
|
297
343
|
this.ctx.fillStyle = color;
|
|
298
344
|
this.ctx.fillRect(0, 0, this.node.width, this.node.height);
|
|
345
|
+
this._imageData = null;
|
|
299
346
|
return this;
|
|
300
347
|
}
|
|
301
348
|
getImageData() {
|
|
@@ -313,7 +360,39 @@ var Canvas = class _Canvas {
|
|
|
313
360
|
const difference2 = this.difference(otherCanvas);
|
|
314
361
|
return differenceToDistance(difference2, this.node.width * this.node.height);
|
|
315
362
|
}
|
|
363
|
+
patchImageData(offset, shapeData, color) {
|
|
364
|
+
if (!this._imageData) return;
|
|
365
|
+
const [cr, cg, cb] = color;
|
|
366
|
+
const dst = this._imageData.data;
|
|
367
|
+
const src = shapeData.data;
|
|
368
|
+
const sw = shapeData.width, sh = shapeData.height;
|
|
369
|
+
const fw = this._imageData.width, fh = this._imageData.height;
|
|
370
|
+
for (let sy = 0; sy < sh; sy++) {
|
|
371
|
+
const fy = sy + offset.top;
|
|
372
|
+
if (fy < 0 || fy >= fh) continue;
|
|
373
|
+
for (let sx = 0; sx < sw; sx++) {
|
|
374
|
+
const fx = sx + offset.left;
|
|
375
|
+
if (fx < 0 || fx >= fw) continue;
|
|
376
|
+
const si = 4 * (sx + sy * sw);
|
|
377
|
+
const a = src[si + 3];
|
|
378
|
+
if (a === 0) continue;
|
|
379
|
+
const fi = 4 * (fx + fy * fw);
|
|
380
|
+
const alpha = a / 255, blend = 1 - alpha;
|
|
381
|
+
dst[fi] = cr * alpha + dst[fi] * blend;
|
|
382
|
+
dst[fi + 1] = cg * alpha + dst[fi + 1] * blend;
|
|
383
|
+
dst[fi + 2] = cb * alpha + dst[fi + 2] * blend;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
316
387
|
drawStep(step) {
|
|
388
|
+
if (this._imageData) {
|
|
389
|
+
try {
|
|
390
|
+
const shapeData = step.shape.rasterize(step.alpha).getImageData();
|
|
391
|
+
this.patchImageData(step.shape.bbox, shapeData, parseColor(step.color));
|
|
392
|
+
} catch {
|
|
393
|
+
this._imageData = null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
317
396
|
this.ctx.globalAlpha = step.alpha;
|
|
318
397
|
this.ctx.fillStyle = step.color;
|
|
319
398
|
step.shape.render(this.ctx);
|
|
@@ -375,8 +454,8 @@ var Step = class _Step {
|
|
|
375
454
|
}
|
|
376
455
|
/* apply this step to a state to get a new state. call only after .compute */
|
|
377
456
|
apply(state) {
|
|
378
|
-
|
|
379
|
-
return new State(state.target,
|
|
457
|
+
state.canvas.drawStep(this);
|
|
458
|
+
return new State(state.target, state.canvas, this.distance);
|
|
380
459
|
}
|
|
381
460
|
/* find optimal color and compute the resulting distance */
|
|
382
461
|
compute(state) {
|
|
@@ -451,7 +530,7 @@ var Step = class _Step {
|
|
|
451
530
|
}
|
|
452
531
|
/* return a slightly mutated step */
|
|
453
532
|
mutate() {
|
|
454
|
-
const newShape = this.shape.mutate(
|
|
533
|
+
const newShape = this.shape.mutate();
|
|
455
534
|
const mutated = new _Step(newShape, this.cfg);
|
|
456
535
|
if (this.cfg.mutateAlpha) {
|
|
457
536
|
const mutatedAlpha = this.alpha + (Math.random() - 0.5) * 0.08;
|
|
@@ -463,6 +542,15 @@ var Step = class _Step {
|
|
|
463
542
|
|
|
464
543
|
// src/shape.ts
|
|
465
544
|
var _rasterCanvas = null;
|
|
545
|
+
var _glyphMeasureCanvas = null;
|
|
546
|
+
function getGlyphMeasureCanvas() {
|
|
547
|
+
if (!_glyphMeasureCanvas) {
|
|
548
|
+
_glyphMeasureCanvas = new Canvas(1, 1);
|
|
549
|
+
}
|
|
550
|
+
return _glyphMeasureCanvas;
|
|
551
|
+
}
|
|
552
|
+
__name(getGlyphMeasureCanvas, "getGlyphMeasureCanvas");
|
|
553
|
+
var BBOX_PAD = 1;
|
|
466
554
|
var Shape = class {
|
|
467
555
|
static {
|
|
468
556
|
__name(this, "Shape");
|
|
@@ -490,7 +578,7 @@ var Shape = class {
|
|
|
490
578
|
constructor(_w, _h) {
|
|
491
579
|
this.bbox = { left: 0, top: 0, width: 0, height: 0 };
|
|
492
580
|
}
|
|
493
|
-
mutate(
|
|
581
|
+
mutate() {
|
|
494
582
|
return this;
|
|
495
583
|
}
|
|
496
584
|
toSVG() {
|
|
@@ -520,9 +608,41 @@ var Shape = class {
|
|
|
520
608
|
const data = ctx.getImageData(0, 0, w, h);
|
|
521
609
|
return { getImageData: /* @__PURE__ */ __name(() => data, "getImageData") };
|
|
522
610
|
}
|
|
611
|
+
/* Grow a tight bbox by BBOX_PAD on all sides and snap to integer bounds.
|
|
612
|
+
* Integer left/top/width/height are required: computeColorAndDifferenceChange
|
|
613
|
+
* and rasterize index/translate a flat pixel array directly from these. */
|
|
614
|
+
setBbox(tight) {
|
|
615
|
+
const left = Math.floor(tight.left) - BBOX_PAD;
|
|
616
|
+
const top = Math.floor(tight.top) - BBOX_PAD;
|
|
617
|
+
const right = Math.ceil(tight.left + tight.width) + BBOX_PAD;
|
|
618
|
+
const bottom = Math.ceil(tight.top + tight.height) + BBOX_PAD;
|
|
619
|
+
this.bbox = { left, top, width: Math.max(1, right - left), height: Math.max(1, bottom - top) };
|
|
620
|
+
return this;
|
|
621
|
+
}
|
|
523
622
|
render(_ctx) {
|
|
524
623
|
}
|
|
525
624
|
};
|
|
625
|
+
var ConstrainedShape = class extends Shape {
|
|
626
|
+
static {
|
|
627
|
+
__name(this, "ConstrainedShape");
|
|
628
|
+
}
|
|
629
|
+
tonalRange;
|
|
630
|
+
invertTonal;
|
|
631
|
+
saturationRange;
|
|
632
|
+
invertSaturation;
|
|
633
|
+
hueCenter;
|
|
634
|
+
hueTolerance;
|
|
635
|
+
invertHue;
|
|
636
|
+
applyConstraints(opts) {
|
|
637
|
+
this.tonalRange = opts?.tonalRange;
|
|
638
|
+
this.invertTonal = opts?.invertTonal;
|
|
639
|
+
this.saturationRange = opts?.saturationRange;
|
|
640
|
+
this.invertSaturation = opts?.invertSaturation;
|
|
641
|
+
this.hueCenter = opts?.hueCenter;
|
|
642
|
+
this.hueTolerance = opts?.hueTolerance;
|
|
643
|
+
this.invertHue = opts?.invertHue;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
526
646
|
var Polygon = class _Polygon extends Shape {
|
|
527
647
|
static {
|
|
528
648
|
__name(this, "Polygon");
|
|
@@ -557,33 +677,18 @@ var Polygon = class _Polygon extends Shape {
|
|
|
557
677
|
path.setAttribute("d", `${d}Z`);
|
|
558
678
|
return path;
|
|
559
679
|
}
|
|
560
|
-
mutate(
|
|
680
|
+
mutate() {
|
|
561
681
|
const clone = this._cloneEmpty();
|
|
562
682
|
clone.points = this.points.map(([x, y]) => [x, y]);
|
|
563
683
|
const index = Math.floor(Math.random() * this.points.length);
|
|
564
684
|
const point = clone.points[index];
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
point[
|
|
568
|
-
point[1] += ~~(radius * Math.sin(angle));
|
|
685
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
686
|
+
point[0] += dx;
|
|
687
|
+
point[1] += dy;
|
|
569
688
|
return clone.computeBbox();
|
|
570
689
|
}
|
|
571
690
|
computeBbox() {
|
|
572
|
-
|
|
573
|
-
for (const [x, y] of this.points) {
|
|
574
|
-
if (x < minX) minX = x;
|
|
575
|
-
if (y < minY) minY = y;
|
|
576
|
-
if (x > maxX) maxX = x;
|
|
577
|
-
if (y > maxY) maxY = y;
|
|
578
|
-
}
|
|
579
|
-
this.bbox = {
|
|
580
|
-
left: minX,
|
|
581
|
-
top: minY,
|
|
582
|
-
width: maxX - minX || 1,
|
|
583
|
-
/* fallback for deformed shapes */
|
|
584
|
-
height: maxY - minY || 1
|
|
585
|
-
};
|
|
586
|
-
return this;
|
|
691
|
+
return this.setBbox(bboxOfPoints(this.points));
|
|
587
692
|
}
|
|
588
693
|
_createPoints(w, h, count) {
|
|
589
694
|
const first = Shape.randomPoint(w, h);
|
|
@@ -626,7 +731,7 @@ var Rectangle = class _Rectangle extends Polygon {
|
|
|
626
731
|
toData(a, c) {
|
|
627
732
|
return { t: "r", a, c, pts: this.points.map(([x, y]) => [x, y]) };
|
|
628
733
|
}
|
|
629
|
-
mutate(
|
|
734
|
+
mutate() {
|
|
630
735
|
const clone = this._cloneEmpty();
|
|
631
736
|
clone.points = this.points.map(([x, y]) => [x, y]);
|
|
632
737
|
const amount = ~~((Math.random() - 0.5) * 20);
|
|
@@ -695,17 +800,16 @@ var Ellipse = class _Ellipse extends Shape {
|
|
|
695
800
|
toData(a, c) {
|
|
696
801
|
return { t: "e", a, c, cx: this.center[0], cy: this.center[1], rx: this.rx, ry: this.ry };
|
|
697
802
|
}
|
|
698
|
-
mutate(
|
|
803
|
+
mutate() {
|
|
699
804
|
const clone = new _Ellipse(0, 0);
|
|
700
805
|
clone.center = [this.center[0], this.center[1]];
|
|
701
806
|
clone.rx = this.rx;
|
|
702
807
|
clone.ry = this.ry;
|
|
703
808
|
switch (Math.floor(Math.random() * 3)) {
|
|
704
809
|
case 0: {
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
clone.center[
|
|
708
|
-
clone.center[1] += ~~(radius * Math.sin(angle));
|
|
810
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
811
|
+
clone.center[0] += dx;
|
|
812
|
+
clone.center[1] += dy;
|
|
709
813
|
break;
|
|
710
814
|
}
|
|
711
815
|
case 1:
|
|
@@ -720,13 +824,12 @@ var Ellipse = class _Ellipse extends Shape {
|
|
|
720
824
|
return clone.computeBbox();
|
|
721
825
|
}
|
|
722
826
|
computeBbox() {
|
|
723
|
-
this.
|
|
827
|
+
return this.setBbox({
|
|
724
828
|
left: this.center[0] - this.rx,
|
|
725
829
|
top: this.center[1] - this.ry,
|
|
726
830
|
width: 2 * this.rx,
|
|
727
831
|
height: 2 * this.ry
|
|
728
|
-
};
|
|
729
|
-
return this;
|
|
832
|
+
});
|
|
730
833
|
}
|
|
731
834
|
};
|
|
732
835
|
var Circle = class _Circle extends Shape {
|
|
@@ -742,13 +845,12 @@ var Circle = class _Circle extends Shape {
|
|
|
742
845
|
this.computeBbox();
|
|
743
846
|
}
|
|
744
847
|
computeBbox() {
|
|
745
|
-
this.
|
|
848
|
+
return this.setBbox({
|
|
746
849
|
left: this.center[0] - this.r,
|
|
747
850
|
top: this.center[1] - this.r,
|
|
748
851
|
width: 2 * this.r || 1,
|
|
749
852
|
height: 2 * this.r || 1
|
|
750
|
-
};
|
|
751
|
-
return this;
|
|
853
|
+
});
|
|
752
854
|
}
|
|
753
855
|
render(ctx) {
|
|
754
856
|
ctx.beginPath();
|
|
@@ -765,16 +867,15 @@ var Circle = class _Circle extends Shape {
|
|
|
765
867
|
toData(a, c) {
|
|
766
868
|
return { t: "c", a, c, cx: this.center[0], cy: this.center[1], r: this.r };
|
|
767
869
|
}
|
|
768
|
-
mutate(
|
|
870
|
+
mutate() {
|
|
769
871
|
const clone = new _Circle(0, 0);
|
|
770
872
|
clone.center = [this.center[0], this.center[1]];
|
|
771
873
|
clone.r = this.r;
|
|
772
874
|
switch (Math.floor(Math.random() * 2)) {
|
|
773
875
|
case 0: {
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
clone.center[
|
|
777
|
-
clone.center[1] += ~~(radius * Math.sin(angle));
|
|
876
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
877
|
+
clone.center[0] += dx;
|
|
878
|
+
clone.center[1] += dy;
|
|
778
879
|
break;
|
|
779
880
|
}
|
|
780
881
|
case 1:
|
|
@@ -800,16 +901,15 @@ var Glyph = class _Glyph extends Shape {
|
|
|
800
901
|
this.computeBbox();
|
|
801
902
|
}
|
|
802
903
|
computeBbox() {
|
|
803
|
-
const tmp =
|
|
904
|
+
const tmp = getGlyphMeasureCanvas();
|
|
804
905
|
tmp.ctx.font = `${this.fontSize}px sans-serif`;
|
|
805
906
|
const w = ~~tmp.ctx.measureText(this.text).width;
|
|
806
|
-
this.
|
|
907
|
+
return this.setBbox({
|
|
807
908
|
left: ~~(this.center[0] - w / 2),
|
|
808
909
|
top: ~~(this.center[1] - this.fontSize / 2),
|
|
809
910
|
width: w,
|
|
810
911
|
height: this.fontSize
|
|
811
|
-
};
|
|
812
|
-
return this;
|
|
912
|
+
});
|
|
813
913
|
}
|
|
814
914
|
render(ctx) {
|
|
815
915
|
ctx.textAlign = "center";
|
|
@@ -817,16 +917,15 @@ var Glyph = class _Glyph extends Shape {
|
|
|
817
917
|
ctx.font = `${this.fontSize}px sans-serif`;
|
|
818
918
|
ctx.fillText(this.text, this.center[0], this.center[1]);
|
|
819
919
|
}
|
|
820
|
-
mutate(
|
|
920
|
+
mutate() {
|
|
821
921
|
const clone = new _Glyph(0, 0, this.text);
|
|
822
922
|
clone.center = [this.center[0], this.center[1]];
|
|
823
923
|
clone.fontSize = this.fontSize;
|
|
824
924
|
switch (Math.floor(Math.random() * 2)) {
|
|
825
925
|
case 0: {
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
clone.center[
|
|
829
|
-
clone.center[1] += ~~(radius * Math.sin(angle));
|
|
926
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
927
|
+
clone.center[0] += dx;
|
|
928
|
+
clone.center[1] += dy;
|
|
830
929
|
break;
|
|
831
930
|
}
|
|
832
931
|
case 1:
|
|
@@ -848,7 +947,7 @@ var Glyph = class _Glyph extends Shape {
|
|
|
848
947
|
return text;
|
|
849
948
|
}
|
|
850
949
|
toData(a, c) {
|
|
851
|
-
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: this.text };
|
|
950
|
+
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: this.text, fontFamily: "sans-serif" };
|
|
852
951
|
}
|
|
853
952
|
};
|
|
854
953
|
var Square = class _Square extends Shape {
|
|
@@ -864,13 +963,12 @@ var Square = class _Square extends Shape {
|
|
|
864
963
|
this.computeBbox();
|
|
865
964
|
}
|
|
866
965
|
computeBbox() {
|
|
867
|
-
this.
|
|
966
|
+
return this.setBbox({
|
|
868
967
|
left: this.center[0] - this.r,
|
|
869
968
|
top: this.center[1] - this.r,
|
|
870
969
|
width: 2 * this.r || 1,
|
|
871
970
|
height: 2 * this.r || 1
|
|
872
|
-
};
|
|
873
|
-
return this;
|
|
971
|
+
});
|
|
874
972
|
}
|
|
875
973
|
render(ctx) {
|
|
876
974
|
ctx.fillRect(this.center[0] - this.r, this.center[1] - this.r, 2 * this.r, 2 * this.r);
|
|
@@ -886,16 +984,15 @@ var Square = class _Square extends Shape {
|
|
|
886
984
|
toData(a, c) {
|
|
887
985
|
return { t: "s", a, c, cx: this.center[0], cy: this.center[1], r: this.r };
|
|
888
986
|
}
|
|
889
|
-
mutate(
|
|
987
|
+
mutate() {
|
|
890
988
|
const clone = new _Square(0, 0);
|
|
891
989
|
clone.center = [this.center[0], this.center[1]];
|
|
892
990
|
clone.r = this.r;
|
|
893
991
|
switch (Math.floor(Math.random() * 2)) {
|
|
894
992
|
case 0: {
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
clone.center[
|
|
898
|
-
clone.center[1] += ~~(radius * Math.sin(angle));
|
|
993
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
994
|
+
clone.center[0] += dx;
|
|
995
|
+
clone.center[1] += dy;
|
|
899
996
|
break;
|
|
900
997
|
}
|
|
901
998
|
case 1:
|
|
@@ -924,33 +1021,14 @@ var Hexagon = class _Hexagon extends Shape {
|
|
|
924
1021
|
}
|
|
925
1022
|
_points() {
|
|
926
1023
|
if (!this._cachedPoints) {
|
|
927
|
-
this._cachedPoints =
|
|
928
|
-
const a = this.angle + i * Math.PI / 3;
|
|
929
|
-
return [
|
|
930
|
-
~~(this.center[0] + this.r * Math.cos(a)),
|
|
931
|
-
~~(this.center[1] + this.r * Math.sin(a))
|
|
932
|
-
];
|
|
933
|
-
});
|
|
1024
|
+
this._cachedPoints = regularPolygonPoints(this.center[0], this.center[1], 6, this.angle, this.r);
|
|
934
1025
|
}
|
|
935
1026
|
return this._cachedPoints;
|
|
936
1027
|
}
|
|
937
1028
|
computeBbox() {
|
|
938
1029
|
this._cachedPoints = null;
|
|
939
1030
|
const pts = this._points();
|
|
940
|
-
|
|
941
|
-
for (const [x, y] of pts) {
|
|
942
|
-
if (x < minX) minX = x;
|
|
943
|
-
if (y < minY) minY = y;
|
|
944
|
-
if (x > maxX) maxX = x;
|
|
945
|
-
if (y > maxY) maxY = y;
|
|
946
|
-
}
|
|
947
|
-
this.bbox = {
|
|
948
|
-
left: minX,
|
|
949
|
-
top: minY,
|
|
950
|
-
width: maxX - minX || 1,
|
|
951
|
-
height: maxY - minY || 1
|
|
952
|
-
};
|
|
953
|
-
return this;
|
|
1031
|
+
return this.setBbox(bboxOfPoints(pts));
|
|
954
1032
|
}
|
|
955
1033
|
render(ctx) {
|
|
956
1034
|
const pts = this._points();
|
|
@@ -967,17 +1045,16 @@ var Hexagon = class _Hexagon extends Shape {
|
|
|
967
1045
|
toData(a, c) {
|
|
968
1046
|
return { t: "h", a, c, cx: this.center[0], cy: this.center[1], r: this.r, angle: this.angle };
|
|
969
1047
|
}
|
|
970
|
-
mutate(
|
|
1048
|
+
mutate() {
|
|
971
1049
|
const clone = new _Hexagon(0, 0);
|
|
972
1050
|
clone.center = [this.center[0], this.center[1]];
|
|
973
1051
|
clone.r = this.r;
|
|
974
1052
|
clone.angle = this.angle;
|
|
975
1053
|
switch (Math.floor(Math.random() * 3)) {
|
|
976
1054
|
case 0: {
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
clone.center[
|
|
980
|
-
clone.center[1] += ~~(d * Math.sin(a));
|
|
1055
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
1056
|
+
clone.center[0] += dx;
|
|
1057
|
+
clone.center[1] += dy;
|
|
981
1058
|
break;
|
|
982
1059
|
}
|
|
983
1060
|
case 1:
|
|
@@ -1027,19 +1104,12 @@ function makeNGon(opts) {
|
|
|
1027
1104
|
const startAngle = opts.startAngle ?? defaultAngle;
|
|
1028
1105
|
if (sides < 3) throw new RangeError("makeNGon requires at least 3 sides");
|
|
1029
1106
|
if (regular) {
|
|
1030
|
-
class NGonRegular extends
|
|
1107
|
+
class NGonRegular extends ConstrainedShape {
|
|
1031
1108
|
static {
|
|
1032
1109
|
__name(this, "NGonRegular");
|
|
1033
1110
|
}
|
|
1034
1111
|
static _ngonOpts = { sides, regular: true, rotatable, noise, sizeRange, mutationScale, startAngle, tonalRange: opts.tonalRange, invertTonal: opts.invertTonal, saturationRange: opts.saturationRange, invertSaturation: opts.invertSaturation, hueCenter: opts.hueCenter, hueTolerance: opts.hueTolerance, invertHue: opts.invertHue };
|
|
1035
1112
|
static _shapeSpec = { f: "ngon", o: NGonRegular._ngonOpts };
|
|
1036
|
-
tonalRange;
|
|
1037
|
-
invertTonal;
|
|
1038
|
-
saturationRange;
|
|
1039
|
-
invertSaturation;
|
|
1040
|
-
hueCenter;
|
|
1041
|
-
hueTolerance;
|
|
1042
|
-
invertHue;
|
|
1043
1113
|
center;
|
|
1044
1114
|
r;
|
|
1045
1115
|
angle;
|
|
@@ -1047,13 +1117,7 @@ function makeNGon(opts) {
|
|
|
1047
1117
|
_cachedPoints;
|
|
1048
1118
|
constructor(w, h) {
|
|
1049
1119
|
super(w, h);
|
|
1050
|
-
this.
|
|
1051
|
-
this.invertTonal = opts.invertTonal;
|
|
1052
|
-
this.saturationRange = opts.saturationRange;
|
|
1053
|
-
this.invertSaturation = opts.invertSaturation;
|
|
1054
|
-
this.hueCenter = opts.hueCenter;
|
|
1055
|
-
this.hueTolerance = opts.hueTolerance;
|
|
1056
|
-
this.invertHue = opts.invertHue;
|
|
1120
|
+
this.applyConstraints(opts);
|
|
1057
1121
|
this.center = Shape.randomPoint(w, h);
|
|
1058
1122
|
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
1059
1123
|
this.angle = rotatable ? Math.random() * (2 * Math.PI / sides) : startAngle;
|
|
@@ -1077,20 +1141,7 @@ function makeNGon(opts) {
|
|
|
1077
1141
|
computeBbox() {
|
|
1078
1142
|
this._cachedPoints = null;
|
|
1079
1143
|
const pts = this._points();
|
|
1080
|
-
|
|
1081
|
-
for (const [x, y] of pts) {
|
|
1082
|
-
if (x < minX) minX = x;
|
|
1083
|
-
if (y < minY) minY = y;
|
|
1084
|
-
if (x > maxX) maxX = x;
|
|
1085
|
-
if (y > maxY) maxY = y;
|
|
1086
|
-
}
|
|
1087
|
-
this.bbox = {
|
|
1088
|
-
left: minX,
|
|
1089
|
-
top: minY,
|
|
1090
|
-
width: maxX - minX || 1,
|
|
1091
|
-
height: maxY - minY || 1
|
|
1092
|
-
};
|
|
1093
|
-
return this;
|
|
1144
|
+
return this.setBbox(bboxOfPoints(pts));
|
|
1094
1145
|
}
|
|
1095
1146
|
render(ctx) {
|
|
1096
1147
|
const pts = this._points();
|
|
@@ -1107,7 +1158,7 @@ function makeNGon(opts) {
|
|
|
1107
1158
|
toData(a, c) {
|
|
1108
1159
|
return { t: "p", a, c, pts: this._points().map(([x, y]) => [x, y]) };
|
|
1109
1160
|
}
|
|
1110
|
-
mutate(
|
|
1161
|
+
mutate() {
|
|
1111
1162
|
const clone = new NGonRegular(0, 0);
|
|
1112
1163
|
clone.center = [this.center[0], this.center[1]];
|
|
1113
1164
|
clone.r = this.r;
|
|
@@ -1116,10 +1167,9 @@ function makeNGon(opts) {
|
|
|
1116
1167
|
const mutCount = 2 + (rotatable ? 1 : 0) + (noise > 0 ? 1 : 0);
|
|
1117
1168
|
switch (Math.floor(Math.random() * mutCount)) {
|
|
1118
1169
|
case 0: {
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
clone.center[
|
|
1122
|
-
clone.center[1] += ~~(d * Math.sin(a));
|
|
1170
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1171
|
+
clone.center[0] += dx;
|
|
1172
|
+
clone.center[1] += dy;
|
|
1123
1173
|
break;
|
|
1124
1174
|
}
|
|
1125
1175
|
case 1:
|
|
@@ -1145,29 +1195,16 @@ function makeNGon(opts) {
|
|
|
1145
1195
|
}
|
|
1146
1196
|
return NGonRegular;
|
|
1147
1197
|
} else {
|
|
1148
|
-
class NGonIrregular extends
|
|
1198
|
+
class NGonIrregular extends ConstrainedShape {
|
|
1149
1199
|
static {
|
|
1150
1200
|
__name(this, "NGonIrregular");
|
|
1151
1201
|
}
|
|
1152
1202
|
static _ngonOpts = { sides, regular: false, convex, sizeRange, mutationScale, tonalRange: opts.tonalRange, invertTonal: opts.invertTonal, saturationRange: opts.saturationRange, invertSaturation: opts.invertSaturation, hueCenter: opts.hueCenter, hueTolerance: opts.hueTolerance, invertHue: opts.invertHue };
|
|
1153
1203
|
static _shapeSpec = { f: "ngon", o: NGonIrregular._ngonOpts };
|
|
1154
|
-
tonalRange;
|
|
1155
|
-
invertTonal;
|
|
1156
|
-
saturationRange;
|
|
1157
|
-
invertSaturation;
|
|
1158
|
-
hueCenter;
|
|
1159
|
-
hueTolerance;
|
|
1160
|
-
invertHue;
|
|
1161
1204
|
points;
|
|
1162
1205
|
constructor(w, h) {
|
|
1163
1206
|
super(w, h);
|
|
1164
|
-
this.
|
|
1165
|
-
this.invertTonal = opts.invertTonal;
|
|
1166
|
-
this.saturationRange = opts.saturationRange;
|
|
1167
|
-
this.invertSaturation = opts.invertSaturation;
|
|
1168
|
-
this.hueCenter = opts.hueCenter;
|
|
1169
|
-
this.hueTolerance = opts.hueTolerance;
|
|
1170
|
-
this.invertHue = opts.invertHue;
|
|
1207
|
+
this.applyConstraints(opts);
|
|
1171
1208
|
const first = Shape.randomPoint(w, h);
|
|
1172
1209
|
this.points = [first];
|
|
1173
1210
|
for (let i = 1; i < sides; i++) {
|
|
@@ -1182,20 +1219,7 @@ function makeNGon(opts) {
|
|
|
1182
1219
|
this.computeBbox();
|
|
1183
1220
|
}
|
|
1184
1221
|
computeBbox() {
|
|
1185
|
-
|
|
1186
|
-
for (const [x, y] of this.points) {
|
|
1187
|
-
if (x < minX) minX = x;
|
|
1188
|
-
if (y < minY) minY = y;
|
|
1189
|
-
if (x > maxX) maxX = x;
|
|
1190
|
-
if (y > maxY) maxY = y;
|
|
1191
|
-
}
|
|
1192
|
-
this.bbox = {
|
|
1193
|
-
left: minX,
|
|
1194
|
-
top: minY,
|
|
1195
|
-
width: maxX - minX || 1,
|
|
1196
|
-
height: maxY - minY || 1
|
|
1197
|
-
};
|
|
1198
|
-
return this;
|
|
1222
|
+
return this.setBbox(bboxOfPoints(this.points));
|
|
1199
1223
|
}
|
|
1200
1224
|
render(ctx) {
|
|
1201
1225
|
ctx.beginPath();
|
|
@@ -1212,15 +1236,14 @@ function makeNGon(opts) {
|
|
|
1212
1236
|
toData(a, c) {
|
|
1213
1237
|
return { t: "p", a, c, pts: this.points.map(([x, y]) => [x, y]) };
|
|
1214
1238
|
}
|
|
1215
|
-
mutate(
|
|
1239
|
+
mutate() {
|
|
1216
1240
|
const clone = new NGonIrregular(0, 0);
|
|
1217
1241
|
clone.points = this.points.map(([x, y]) => [x, y]);
|
|
1218
1242
|
const index = Math.floor(Math.random() * clone.points.length);
|
|
1219
1243
|
const point = clone.points[index];
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
point[
|
|
1223
|
-
point[1] += ~~(radius * Math.sin(angle));
|
|
1244
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1245
|
+
point[0] += dx;
|
|
1246
|
+
point[1] += dy;
|
|
1224
1247
|
if (convex) clone.points = convexHull(clone.points);
|
|
1225
1248
|
return clone.computeBbox();
|
|
1226
1249
|
}
|
|
@@ -1235,32 +1258,19 @@ function makeRect(opts) {
|
|
|
1235
1258
|
const aspectRatio = opts?.aspectRatio;
|
|
1236
1259
|
const rotatable = opts?.rotatable ?? false;
|
|
1237
1260
|
const mutationScale = opts?.mutationScale ?? 20;
|
|
1238
|
-
class Rect extends
|
|
1261
|
+
class Rect extends ConstrainedShape {
|
|
1239
1262
|
static {
|
|
1240
1263
|
__name(this, "Rect");
|
|
1241
1264
|
}
|
|
1242
1265
|
static _rectOpts = { widthRange, heightRange, aspectRatio, rotatable, mutationScale, tonalRange: opts?.tonalRange, invertTonal: opts?.invertTonal, saturationRange: opts?.saturationRange, invertSaturation: opts?.invertSaturation, hueCenter: opts?.hueCenter, hueTolerance: opts?.hueTolerance, invertHue: opts?.invertHue };
|
|
1243
1266
|
static _shapeSpec = { f: "rect", o: Rect._rectOpts };
|
|
1244
|
-
tonalRange;
|
|
1245
|
-
invertTonal;
|
|
1246
|
-
saturationRange;
|
|
1247
|
-
invertSaturation;
|
|
1248
|
-
hueCenter;
|
|
1249
|
-
hueTolerance;
|
|
1250
|
-
invertHue;
|
|
1251
1267
|
center;
|
|
1252
1268
|
hw;
|
|
1253
1269
|
hh;
|
|
1254
1270
|
angle;
|
|
1255
1271
|
constructor(w, h) {
|
|
1256
1272
|
super(w, h);
|
|
1257
|
-
this.
|
|
1258
|
-
this.invertTonal = opts?.invertTonal;
|
|
1259
|
-
this.saturationRange = opts?.saturationRange;
|
|
1260
|
-
this.invertSaturation = opts?.invertSaturation;
|
|
1261
|
-
this.hueCenter = opts?.hueCenter;
|
|
1262
|
-
this.hueTolerance = opts?.hueTolerance;
|
|
1263
|
-
this.invertHue = opts?.invertHue;
|
|
1273
|
+
this.applyConstraints(opts);
|
|
1264
1274
|
this.center = Shape.randomPoint(w, h);
|
|
1265
1275
|
this.hw = widthRange[0] + ~~(Math.random() * (widthRange[1] - widthRange[0]));
|
|
1266
1276
|
if (aspectRatio !== void 0) {
|
|
@@ -1272,41 +1282,18 @@ function makeRect(opts) {
|
|
|
1272
1282
|
this.computeBbox();
|
|
1273
1283
|
}
|
|
1274
1284
|
computeBbox() {
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
const w = ~~(this.hw * cos + this.hh * sin);
|
|
1278
|
-
const h = ~~(this.hw * sin + this.hh * cos);
|
|
1279
|
-
this.bbox = {
|
|
1280
|
-
left: this.center[0] - w,
|
|
1281
|
-
top: this.center[1] - h,
|
|
1282
|
-
width: 2 * w || 1,
|
|
1283
|
-
height: 2 * h || 1
|
|
1284
|
-
};
|
|
1285
|
-
return this;
|
|
1285
|
+
const corners = rectCorners(this.center[0], this.center[1], this.hw, this.hh, this.angle);
|
|
1286
|
+
return this.setBbox(bboxOfPoints(corners));
|
|
1286
1287
|
}
|
|
1287
1288
|
render(ctx) {
|
|
1288
|
-
const
|
|
1289
|
-
const sin = Math.sin(this.angle);
|
|
1290
|
-
const corners = [
|
|
1291
|
-
[this.center[0] - this.hw * cos + this.hh * sin, this.center[1] - this.hw * sin - this.hh * cos],
|
|
1292
|
-
[this.center[0] + this.hw * cos + this.hh * sin, this.center[1] + this.hw * sin - this.hh * cos],
|
|
1293
|
-
[this.center[0] + this.hw * cos - this.hh * sin, this.center[1] + this.hw * sin + this.hh * cos],
|
|
1294
|
-
[this.center[0] - this.hw * cos - this.hh * sin, this.center[1] - this.hw * sin + this.hh * cos]
|
|
1295
|
-
];
|
|
1289
|
+
const corners = rectCorners(this.center[0], this.center[1], this.hw, this.hh, this.angle);
|
|
1296
1290
|
ctx.beginPath();
|
|
1297
|
-
corners.forEach(([x, y], i) => i ? ctx.lineTo(
|
|
1291
|
+
corners.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y));
|
|
1298
1292
|
ctx.closePath();
|
|
1299
1293
|
ctx.fill();
|
|
1300
1294
|
}
|
|
1301
1295
|
toSVG() {
|
|
1302
|
-
const
|
|
1303
|
-
const sin = Math.sin(this.angle);
|
|
1304
|
-
const corners = [
|
|
1305
|
-
[this.center[0] - this.hw * cos + this.hh * sin, this.center[1] - this.hw * sin - this.hh * cos],
|
|
1306
|
-
[this.center[0] + this.hw * cos + this.hh * sin, this.center[1] + this.hw * sin - this.hh * cos],
|
|
1307
|
-
[this.center[0] + this.hw * cos - this.hh * sin, this.center[1] + this.hw * sin + this.hh * cos],
|
|
1308
|
-
[this.center[0] - this.hw * cos - this.hh * sin, this.center[1] - this.hw * sin + this.hh * cos]
|
|
1309
|
-
];
|
|
1296
|
+
const corners = rectCorners(this.center[0], this.center[1], this.hw, this.hh, this.angle);
|
|
1310
1297
|
const node = document.createElementNS(SVGNS, "polygon");
|
|
1311
1298
|
node.setAttribute("points", corners.map((p) => p.join(",")).join(" "));
|
|
1312
1299
|
return node;
|
|
@@ -1323,7 +1310,7 @@ function makeRect(opts) {
|
|
|
1323
1310
|
angle: this.angle
|
|
1324
1311
|
};
|
|
1325
1312
|
}
|
|
1326
|
-
mutate(
|
|
1313
|
+
mutate() {
|
|
1327
1314
|
const clone = new Rect(0, 0);
|
|
1328
1315
|
clone.center = [this.center[0], this.center[1]];
|
|
1329
1316
|
clone.hw = this.hw;
|
|
@@ -1332,10 +1319,9 @@ function makeRect(opts) {
|
|
|
1332
1319
|
const mutCount = 2 + (rotatable ? 1 : 0) + (aspectRatio === void 0 ? 1 : 0);
|
|
1333
1320
|
switch (Math.floor(Math.random() * mutCount)) {
|
|
1334
1321
|
case 0: {
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
clone.center[
|
|
1338
|
-
clone.center[1] += ~~(d * Math.sin(a));
|
|
1322
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1323
|
+
clone.center[0] += dx;
|
|
1324
|
+
clone.center[1] += dy;
|
|
1339
1325
|
break;
|
|
1340
1326
|
}
|
|
1341
1327
|
case 1:
|
|
@@ -1369,42 +1355,28 @@ __name(makeRect, "makeRect");
|
|
|
1369
1355
|
function makeCircle(opts) {
|
|
1370
1356
|
const sizeRange = opts?.sizeRange ?? [1, 20];
|
|
1371
1357
|
const mutationScale = opts?.mutationScale ?? 20;
|
|
1372
|
-
class MadeCircle extends
|
|
1358
|
+
class MadeCircle extends ConstrainedShape {
|
|
1373
1359
|
static {
|
|
1374
1360
|
__name(this, "MadeCircle");
|
|
1375
1361
|
}
|
|
1376
1362
|
static _circleOpts = { sizeRange, mutationScale, tonalRange: opts?.tonalRange, invertTonal: opts?.invertTonal, saturationRange: opts?.saturationRange, invertSaturation: opts?.invertSaturation, hueCenter: opts?.hueCenter, hueTolerance: opts?.hueTolerance, invertHue: opts?.invertHue };
|
|
1377
1363
|
static _shapeSpec = { f: "circle", o: MadeCircle._circleOpts };
|
|
1378
|
-
tonalRange;
|
|
1379
|
-
invertTonal;
|
|
1380
|
-
saturationRange;
|
|
1381
|
-
invertSaturation;
|
|
1382
|
-
hueCenter;
|
|
1383
|
-
hueTolerance;
|
|
1384
|
-
invertHue;
|
|
1385
1364
|
center;
|
|
1386
1365
|
r;
|
|
1387
1366
|
constructor(w, h) {
|
|
1388
1367
|
super(w, h);
|
|
1389
|
-
this.
|
|
1390
|
-
this.invertTonal = opts?.invertTonal;
|
|
1391
|
-
this.saturationRange = opts?.saturationRange;
|
|
1392
|
-
this.invertSaturation = opts?.invertSaturation;
|
|
1393
|
-
this.hueCenter = opts?.hueCenter;
|
|
1394
|
-
this.hueTolerance = opts?.hueTolerance;
|
|
1395
|
-
this.invertHue = opts?.invertHue;
|
|
1368
|
+
this.applyConstraints(opts);
|
|
1396
1369
|
this.center = Shape.randomPoint(w, h);
|
|
1397
1370
|
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
1398
1371
|
this.computeBbox();
|
|
1399
1372
|
}
|
|
1400
1373
|
computeBbox() {
|
|
1401
|
-
this.
|
|
1374
|
+
return this.setBbox({
|
|
1402
1375
|
left: this.center[0] - this.r,
|
|
1403
1376
|
top: this.center[1] - this.r,
|
|
1404
1377
|
width: 2 * this.r || 1,
|
|
1405
1378
|
height: 2 * this.r || 1
|
|
1406
|
-
};
|
|
1407
|
-
return this;
|
|
1379
|
+
});
|
|
1408
1380
|
}
|
|
1409
1381
|
render(ctx) {
|
|
1410
1382
|
ctx.beginPath();
|
|
@@ -1421,16 +1393,15 @@ function makeCircle(opts) {
|
|
|
1421
1393
|
toData(a, c) {
|
|
1422
1394
|
return { t: "c", a, c, cx: this.center[0], cy: this.center[1], r: this.r };
|
|
1423
1395
|
}
|
|
1424
|
-
mutate(
|
|
1396
|
+
mutate() {
|
|
1425
1397
|
const clone = new MadeCircle(0, 0);
|
|
1426
1398
|
clone.center = [this.center[0], this.center[1]];
|
|
1427
1399
|
clone.r = this.r;
|
|
1428
1400
|
switch (Math.floor(Math.random() * 2)) {
|
|
1429
1401
|
case 0: {
|
|
1430
|
-
const
|
|
1431
|
-
|
|
1432
|
-
clone.center[
|
|
1433
|
-
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1402
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1403
|
+
clone.center[0] += dx;
|
|
1404
|
+
clone.center[1] += dy;
|
|
1434
1405
|
break;
|
|
1435
1406
|
}
|
|
1436
1407
|
case 1:
|
|
@@ -1449,44 +1420,30 @@ function makeEllipse(opts) {
|
|
|
1449
1420
|
const ryRange = opts?.ryRange ?? [1, 20];
|
|
1450
1421
|
const aspectRatio = opts?.aspectRatio;
|
|
1451
1422
|
const mutationScale = opts?.mutationScale ?? 20;
|
|
1452
|
-
class MadeEllipse extends
|
|
1423
|
+
class MadeEllipse extends ConstrainedShape {
|
|
1453
1424
|
static {
|
|
1454
1425
|
__name(this, "MadeEllipse");
|
|
1455
1426
|
}
|
|
1456
1427
|
static _ellipseOpts = { rxRange, ryRange, aspectRatio, mutationScale, tonalRange: opts?.tonalRange, invertTonal: opts?.invertTonal, saturationRange: opts?.saturationRange, invertSaturation: opts?.invertSaturation, hueCenter: opts?.hueCenter, hueTolerance: opts?.hueTolerance, invertHue: opts?.invertHue };
|
|
1457
1428
|
static _shapeSpec = { f: "ellipse", o: MadeEllipse._ellipseOpts };
|
|
1458
|
-
tonalRange;
|
|
1459
|
-
invertTonal;
|
|
1460
|
-
saturationRange;
|
|
1461
|
-
invertSaturation;
|
|
1462
|
-
hueCenter;
|
|
1463
|
-
hueTolerance;
|
|
1464
|
-
invertHue;
|
|
1465
1429
|
center;
|
|
1466
1430
|
rx;
|
|
1467
1431
|
ry;
|
|
1468
1432
|
constructor(w, h) {
|
|
1469
1433
|
super(w, h);
|
|
1470
|
-
this.
|
|
1471
|
-
this.invertTonal = opts?.invertTonal;
|
|
1472
|
-
this.saturationRange = opts?.saturationRange;
|
|
1473
|
-
this.invertSaturation = opts?.invertSaturation;
|
|
1474
|
-
this.hueCenter = opts?.hueCenter;
|
|
1475
|
-
this.hueTolerance = opts?.hueTolerance;
|
|
1476
|
-
this.invertHue = opts?.invertHue;
|
|
1434
|
+
this.applyConstraints(opts);
|
|
1477
1435
|
this.center = Shape.randomPoint(w, h);
|
|
1478
1436
|
this.rx = Math.max(1, rxRange[0] + ~~(Math.random() * (rxRange[1] - rxRange[0])));
|
|
1479
1437
|
this.ry = aspectRatio !== void 0 ? Math.max(1, Math.round(this.rx / aspectRatio)) : Math.max(1, ryRange[0] + ~~(Math.random() * (ryRange[1] - ryRange[0])));
|
|
1480
1438
|
this.computeBbox();
|
|
1481
1439
|
}
|
|
1482
1440
|
computeBbox() {
|
|
1483
|
-
this.
|
|
1441
|
+
return this.setBbox({
|
|
1484
1442
|
left: this.center[0] - this.rx,
|
|
1485
1443
|
top: this.center[1] - this.ry,
|
|
1486
1444
|
width: 2 * this.rx || 1,
|
|
1487
1445
|
height: 2 * this.ry || 1
|
|
1488
|
-
};
|
|
1489
|
-
return this;
|
|
1446
|
+
});
|
|
1490
1447
|
}
|
|
1491
1448
|
render(ctx) {
|
|
1492
1449
|
ctx.beginPath();
|
|
@@ -1504,7 +1461,7 @@ function makeEllipse(opts) {
|
|
|
1504
1461
|
toData(a, c) {
|
|
1505
1462
|
return { t: "e", a, c, cx: this.center[0], cy: this.center[1], rx: this.rx, ry: this.ry };
|
|
1506
1463
|
}
|
|
1507
|
-
mutate(
|
|
1464
|
+
mutate() {
|
|
1508
1465
|
const clone = new MadeEllipse(0, 0);
|
|
1509
1466
|
clone.center = [this.center[0], this.center[1]];
|
|
1510
1467
|
clone.rx = this.rx;
|
|
@@ -1512,10 +1469,9 @@ function makeEllipse(opts) {
|
|
|
1512
1469
|
const mutCount = aspectRatio === void 0 ? 3 : 2;
|
|
1513
1470
|
switch (Math.floor(Math.random() * mutCount)) {
|
|
1514
1471
|
case 0: {
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
clone.center[
|
|
1518
|
-
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1472
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1473
|
+
clone.center[0] += dx;
|
|
1474
|
+
clone.center[1] += dy;
|
|
1519
1475
|
break;
|
|
1520
1476
|
}
|
|
1521
1477
|
case 1:
|
|
@@ -1541,45 +1497,31 @@ function makeGlyph(opts) {
|
|
|
1541
1497
|
const fontFamily = opts?.fontFamily ?? "sans-serif";
|
|
1542
1498
|
const sizeRange = opts?.sizeRange ?? [10, 30];
|
|
1543
1499
|
const mutationScale = opts?.mutationScale ?? 20;
|
|
1544
|
-
class MadeGlyph extends
|
|
1500
|
+
class MadeGlyph extends ConstrainedShape {
|
|
1545
1501
|
static {
|
|
1546
1502
|
__name(this, "MadeGlyph");
|
|
1547
1503
|
}
|
|
1548
1504
|
static _glyphOpts = { char, fontFamily, sizeRange, mutationScale, tonalRange: opts?.tonalRange, invertTonal: opts?.invertTonal, saturationRange: opts?.saturationRange, invertSaturation: opts?.invertSaturation, hueCenter: opts?.hueCenter, hueTolerance: opts?.hueTolerance, invertHue: opts?.invertHue };
|
|
1549
1505
|
static _shapeSpec = { f: "glyph", o: MadeGlyph._glyphOpts };
|
|
1550
|
-
tonalRange;
|
|
1551
|
-
invertTonal;
|
|
1552
|
-
saturationRange;
|
|
1553
|
-
invertSaturation;
|
|
1554
|
-
hueCenter;
|
|
1555
|
-
hueTolerance;
|
|
1556
|
-
invertHue;
|
|
1557
1506
|
center;
|
|
1558
1507
|
fontSize;
|
|
1559
1508
|
constructor(w, h) {
|
|
1560
1509
|
super(w, h);
|
|
1561
|
-
this.
|
|
1562
|
-
this.invertTonal = opts?.invertTonal;
|
|
1563
|
-
this.saturationRange = opts?.saturationRange;
|
|
1564
|
-
this.invertSaturation = opts?.invertSaturation;
|
|
1565
|
-
this.hueCenter = opts?.hueCenter;
|
|
1566
|
-
this.hueTolerance = opts?.hueTolerance;
|
|
1567
|
-
this.invertHue = opts?.invertHue;
|
|
1510
|
+
this.applyConstraints(opts);
|
|
1568
1511
|
this.center = Shape.randomPoint(w, h);
|
|
1569
|
-
this.fontSize =
|
|
1512
|
+
this.fontSize = sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0]));
|
|
1570
1513
|
this.computeBbox();
|
|
1571
1514
|
}
|
|
1572
1515
|
computeBbox() {
|
|
1573
|
-
const tmp =
|
|
1516
|
+
const tmp = getGlyphMeasureCanvas();
|
|
1574
1517
|
tmp.ctx.font = `${this.fontSize}px ${fontFamily}`;
|
|
1575
1518
|
const w = ~~tmp.ctx.measureText(char).width;
|
|
1576
|
-
this.
|
|
1519
|
+
return this.setBbox({
|
|
1577
1520
|
left: ~~(this.center[0] - w / 2),
|
|
1578
1521
|
top: ~~(this.center[1] - this.fontSize / 2),
|
|
1579
1522
|
width: w || 1,
|
|
1580
1523
|
height: this.fontSize
|
|
1581
|
-
};
|
|
1582
|
-
return this;
|
|
1524
|
+
});
|
|
1583
1525
|
}
|
|
1584
1526
|
render(ctx) {
|
|
1585
1527
|
ctx.textAlign = "center";
|
|
@@ -1599,18 +1541,17 @@ function makeGlyph(opts) {
|
|
|
1599
1541
|
return text;
|
|
1600
1542
|
}
|
|
1601
1543
|
toData(a, c) {
|
|
1602
|
-
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: char };
|
|
1544
|
+
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: char, fontFamily };
|
|
1603
1545
|
}
|
|
1604
|
-
mutate(
|
|
1546
|
+
mutate() {
|
|
1605
1547
|
const clone = new MadeGlyph(0, 0);
|
|
1606
1548
|
clone.center = [this.center[0], this.center[1]];
|
|
1607
1549
|
clone.fontSize = this.fontSize;
|
|
1608
1550
|
switch (Math.floor(Math.random() * 2)) {
|
|
1609
1551
|
case 0: {
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
clone.center[
|
|
1613
|
-
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1552
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1553
|
+
clone.center[0] += dx;
|
|
1554
|
+
clone.center[1] += dy;
|
|
1614
1555
|
break;
|
|
1615
1556
|
}
|
|
1616
1557
|
case 1: {
|
|
@@ -1643,6 +1584,7 @@ function buildStepPlan(cfg) {
|
|
|
1643
1584
|
const { shapeTypes, shapeWeights, steps } = cfg;
|
|
1644
1585
|
if (!shapeWeights || shapeWeights.length !== shapeTypes.length) return [];
|
|
1645
1586
|
const total = shapeWeights.reduce((sum, w) => sum + w, 0);
|
|
1587
|
+
if (total <= 0) return [];
|
|
1646
1588
|
const floats = shapeWeights.map((w) => w / total * steps);
|
|
1647
1589
|
const floors = floats.map(Math.floor);
|
|
1648
1590
|
const remainder = steps - floors.reduce((s, n) => s + n, 0);
|
|
@@ -1663,6 +1605,7 @@ var Optimizer = class {
|
|
|
1663
1605
|
cfg;
|
|
1664
1606
|
state;
|
|
1665
1607
|
onStep;
|
|
1608
|
+
onError;
|
|
1666
1609
|
_steps;
|
|
1667
1610
|
_stopped;
|
|
1668
1611
|
_paused;
|
|
@@ -1678,6 +1621,8 @@ var Optimizer = class {
|
|
|
1678
1621
|
this._rejectionStreak = 0;
|
|
1679
1622
|
this.onStep = () => {
|
|
1680
1623
|
};
|
|
1624
|
+
this.onError = () => {
|
|
1625
|
+
};
|
|
1681
1626
|
this._stepPlan = buildStepPlan(cfg);
|
|
1682
1627
|
this._schedule = schedule;
|
|
1683
1628
|
}
|
|
@@ -1714,7 +1659,10 @@ var Optimizer = class {
|
|
|
1714
1659
|
this.onStep(null);
|
|
1715
1660
|
}
|
|
1716
1661
|
this._continue();
|
|
1717
|
-
}).catch(() =>
|
|
1662
|
+
}).catch((error) => {
|
|
1663
|
+
this.onError(error);
|
|
1664
|
+
this.stop();
|
|
1665
|
+
});
|
|
1718
1666
|
}
|
|
1719
1667
|
_continue() {
|
|
1720
1668
|
if (this._stopped || this._paused) {
|
|
@@ -1740,28 +1688,20 @@ var Optimizer = class {
|
|
|
1740
1688
|
}
|
|
1741
1689
|
return Promise.all(promises).then(() => bestStep);
|
|
1742
1690
|
}
|
|
1743
|
-
_optimizeStep(step) {
|
|
1691
|
+
async _optimizeStep(step) {
|
|
1744
1692
|
const LIMIT = this.cfg.mutations;
|
|
1745
1693
|
let failedAttempts = 0;
|
|
1746
|
-
let resolve;
|
|
1747
1694
|
let bestStep = step;
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
if (
|
|
1751
|
-
|
|
1695
|
+
while (!this._stopped && failedAttempts < LIMIT) {
|
|
1696
|
+
const mutatedStep = await bestStep.mutate().compute(this.state);
|
|
1697
|
+
if (mutatedStep.distance < bestStep.distance) {
|
|
1698
|
+
failedAttempts = 0;
|
|
1699
|
+
bestStep = mutatedStep;
|
|
1700
|
+
} else {
|
|
1701
|
+
failedAttempts++;
|
|
1752
1702
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
failedAttempts = 0;
|
|
1756
|
-
bestStep = mutatedStep;
|
|
1757
|
-
} else {
|
|
1758
|
-
failedAttempts++;
|
|
1759
|
-
}
|
|
1760
|
-
tryMutation();
|
|
1761
|
-
});
|
|
1762
|
-
}, "tryMutation");
|
|
1763
|
-
tryMutation();
|
|
1764
|
-
return promise;
|
|
1703
|
+
}
|
|
1704
|
+
return bestStep;
|
|
1765
1705
|
}
|
|
1766
1706
|
};
|
|
1767
1707
|
|
|
@@ -1770,13 +1710,6 @@ function rgbString([r, g, b]) {
|
|
|
1770
1710
|
return `rgb(${r}, ${g}, ${b})`;
|
|
1771
1711
|
}
|
|
1772
1712
|
__name(rgbString, "rgbString");
|
|
1773
|
-
function hexPoints(cx, cy, r, angle) {
|
|
1774
|
-
return Array.from({ length: 6 }, (_, i) => {
|
|
1775
|
-
const a = angle + i * Math.PI / 3;
|
|
1776
|
-
return [~~(cx + r * Math.cos(a)), ~~(cy + r * Math.sin(a))];
|
|
1777
|
-
});
|
|
1778
|
-
}
|
|
1779
|
-
__name(hexPoints, "hexPoints");
|
|
1780
1713
|
function renderStepToCtx(data, ctx) {
|
|
1781
1714
|
ctx.globalAlpha = data.a;
|
|
1782
1715
|
ctx.fillStyle = rgbString(data.c);
|
|
@@ -1807,7 +1740,7 @@ function renderStepToCtx(data, ctx) {
|
|
|
1807
1740
|
break;
|
|
1808
1741
|
}
|
|
1809
1742
|
case "h": {
|
|
1810
|
-
const pts =
|
|
1743
|
+
const pts = regularPolygonPoints(data.cx, data.cy, 6, data.angle, data.r);
|
|
1811
1744
|
ctx.beginPath();
|
|
1812
1745
|
pts.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y));
|
|
1813
1746
|
ctx.closePath();
|
|
@@ -1817,23 +1750,20 @@ function renderStepToCtx(data, ctx) {
|
|
|
1817
1750
|
case "sm": {
|
|
1818
1751
|
ctx.textAlign = "center";
|
|
1819
1752
|
ctx.textBaseline = "middle";
|
|
1820
|
-
ctx.font = `${data.fs}px sans-serif`;
|
|
1753
|
+
ctx.font = `${data.fs}px ${data.fontFamily ?? "sans-serif"}`;
|
|
1821
1754
|
ctx.fillText(data.text, data.cx, data.cy);
|
|
1822
1755
|
break;
|
|
1823
1756
|
}
|
|
1824
1757
|
case "rc": {
|
|
1825
|
-
const
|
|
1826
|
-
const cos = Math.cos(angle);
|
|
1827
|
-
const sin = Math.sin(angle);
|
|
1758
|
+
const corners = rectCorners(data.cx, data.cy, data.hw, data.hh, data.angle);
|
|
1828
1759
|
ctx.beginPath();
|
|
1829
|
-
|
|
1830
|
-
ctx.lineTo(cx + hw * cos + hh * sin, cy + hw * sin - hh * cos);
|
|
1831
|
-
ctx.lineTo(cx + hw * cos - hh * sin, cy + hw * sin + hh * cos);
|
|
1832
|
-
ctx.lineTo(cx - hw * cos - hh * sin, cy - hw * sin + hh * cos);
|
|
1760
|
+
corners.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y));
|
|
1833
1761
|
ctx.closePath();
|
|
1834
1762
|
ctx.fill();
|
|
1835
1763
|
break;
|
|
1836
1764
|
}
|
|
1765
|
+
default:
|
|
1766
|
+
throw new Error("renderStepToCtx: unknown step type");
|
|
1837
1767
|
}
|
|
1838
1768
|
}
|
|
1839
1769
|
__name(renderStepToCtx, "renderStepToCtx");
|
|
@@ -1875,7 +1805,7 @@ function stepDataToSVGElement(data) {
|
|
|
1875
1805
|
}
|
|
1876
1806
|
case "h": {
|
|
1877
1807
|
node = document.createElementNS(SVGNS, "polygon");
|
|
1878
|
-
const pts =
|
|
1808
|
+
const pts = regularPolygonPoints(data.cx, data.cy, 6, data.angle, data.r);
|
|
1879
1809
|
node.setAttribute("points", pts.map((p) => p.join(",")).join(" "));
|
|
1880
1810
|
break;
|
|
1881
1811
|
}
|
|
@@ -1885,26 +1815,20 @@ function stepDataToSVGElement(data) {
|
|
|
1885
1815
|
node.setAttribute("text-anchor", "middle");
|
|
1886
1816
|
node.setAttribute("dominant-baseline", "central");
|
|
1887
1817
|
node.setAttribute("font-size", String(data.fs));
|
|
1888
|
-
node.setAttribute("font-family", "sans-serif");
|
|
1818
|
+
node.setAttribute("font-family", data.fontFamily ?? "sans-serif");
|
|
1889
1819
|
node.setAttribute("x", String(data.cx));
|
|
1890
1820
|
node.setAttribute("y", String(data.cy));
|
|
1891
1821
|
break;
|
|
1892
1822
|
}
|
|
1893
1823
|
case "rc": {
|
|
1894
|
-
const { cx, cy, hw, hh, angle } = data;
|
|
1895
|
-
const cos = Math.cos(angle);
|
|
1896
|
-
const sin = Math.sin(angle);
|
|
1897
1824
|
const fmt = /* @__PURE__ */ __name((n) => n.toFixed(2), "fmt");
|
|
1898
|
-
const pts =
|
|
1899
|
-
[cx - hw * cos + hh * sin, cy - hw * sin - hh * cos],
|
|
1900
|
-
[cx + hw * cos + hh * sin, cy + hw * sin - hh * cos],
|
|
1901
|
-
[cx + hw * cos - hh * sin, cy + hw * sin + hh * cos],
|
|
1902
|
-
[cx - hw * cos - hh * sin, cy - hw * sin + hh * cos]
|
|
1903
|
-
];
|
|
1825
|
+
const pts = rectCorners(data.cx, data.cy, data.hw, data.hh, data.angle);
|
|
1904
1826
|
node = document.createElementNS(SVGNS, "polygon");
|
|
1905
1827
|
node.setAttribute("points", pts.map(([x, y]) => `${fmt(x)},${fmt(y)}`).join(" "));
|
|
1906
1828
|
break;
|
|
1907
1829
|
}
|
|
1830
|
+
default:
|
|
1831
|
+
throw new Error("stepDataToSVGElement: unknown step type");
|
|
1908
1832
|
}
|
|
1909
1833
|
node.setAttribute("fill", color);
|
|
1910
1834
|
node.setAttribute("fill-opacity", opacity);
|
|
@@ -1912,6 +1836,9 @@ function stepDataToSVGElement(data) {
|
|
|
1912
1836
|
}
|
|
1913
1837
|
__name(stepDataToSVGElement, "stepDataToSVGElement");
|
|
1914
1838
|
function replayOutput(data) {
|
|
1839
|
+
if (data.v !== 1) {
|
|
1840
|
+
throw new Error(`Unsupported serialized output version: ${data.v}`);
|
|
1841
|
+
}
|
|
1915
1842
|
const fill = rgbString(data.fill);
|
|
1916
1843
|
const vw = data.w * data.scale;
|
|
1917
1844
|
const vh = data.h * data.scale;
|
|
@@ -1944,6 +1871,7 @@ export {
|
|
|
1944
1871
|
State,
|
|
1945
1872
|
Step,
|
|
1946
1873
|
Triangle,
|
|
1874
|
+
bboxOfPoints,
|
|
1947
1875
|
clamp,
|
|
1948
1876
|
clampColor,
|
|
1949
1877
|
computeColorAndDifferenceChange,
|
|
@@ -1957,6 +1885,9 @@ export {
|
|
|
1957
1885
|
makeNGon,
|
|
1958
1886
|
makeRect,
|
|
1959
1887
|
parseColor,
|
|
1888
|
+
randomPolarOffset,
|
|
1889
|
+
rectCorners,
|
|
1890
|
+
regularPolygonPoints,
|
|
1960
1891
|
renderStepToCtx,
|
|
1961
1892
|
replayOutput,
|
|
1962
1893
|
rgbToHsl,
|