@slithy/prim-lib 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-COMMERCIAL.md +2 -2
- package/dist/index.d.ts +41 -22
- package/dist/index.js +274 -335
- 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
|
}
|
|
@@ -178,7 +220,6 @@ var Canvas = class _Canvas {
|
|
|
178
220
|
const cp = document.createElementNS(SVGNS, "clipPath");
|
|
179
221
|
defs.appendChild(cp);
|
|
180
222
|
cp.setAttribute("id", "clip");
|
|
181
|
-
cp.setAttribute("clipPathUnits", "objectBoundingBox");
|
|
182
223
|
let rect = svgRect(width, height);
|
|
183
224
|
cp.appendChild(rect);
|
|
184
225
|
rect = svgRect(width, height);
|
|
@@ -190,14 +231,14 @@ var Canvas = class _Canvas {
|
|
|
190
231
|
if (svg) {
|
|
191
232
|
return this.svgRoot(cfg.width, cfg.height, cfg.outputFill ?? cfg.fill);
|
|
192
233
|
} else {
|
|
193
|
-
return new this(cfg.width, cfg.height).fill(cfg.fill);
|
|
234
|
+
return new this(cfg.width, cfg.height, true).fill(cfg.fill);
|
|
194
235
|
}
|
|
195
236
|
}
|
|
196
237
|
static original(url, cfg) {
|
|
197
238
|
if (url == "test") {
|
|
198
239
|
return Promise.resolve(this.test(cfg));
|
|
199
240
|
}
|
|
200
|
-
return new Promise((resolve) => {
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
201
242
|
const img = new Image();
|
|
202
243
|
if (!url.startsWith("blob:") && !url.startsWith("data:")) {
|
|
203
244
|
img.crossOrigin = "anonymous";
|
|
@@ -207,12 +248,12 @@ var Canvas = class _Canvas {
|
|
|
207
248
|
const w = img.naturalWidth;
|
|
208
249
|
const h = img.naturalHeight;
|
|
209
250
|
const computeScale = getScale(w, h, cfg.computeSize, cfg.allowUpscale);
|
|
210
|
-
cfg.width = w / computeScale;
|
|
211
|
-
cfg.height = h / computeScale;
|
|
251
|
+
cfg.width = Math.round(w / computeScale);
|
|
252
|
+
cfg.height = Math.round(h / computeScale);
|
|
212
253
|
const viewScale = getScale(w, h, cfg.viewSize, cfg.allowUpscale);
|
|
213
254
|
cfg.scale = computeScale / viewScale;
|
|
214
255
|
const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
215
|
-
const canvas = this
|
|
256
|
+
const canvas = new this(fullCfg.width, fullCfg.height, true);
|
|
216
257
|
canvas.ctx.drawImage(img, 0, 0, fullCfg.width, fullCfg.height);
|
|
217
258
|
if (cfg.fill === "transparent") {
|
|
218
259
|
cfg.outputFill = "transparent";
|
|
@@ -221,11 +262,15 @@ var Canvas = class _Canvas {
|
|
|
221
262
|
if (cfg.fill === "auto") {
|
|
222
263
|
cfg.fill = getFill(canvas.getImageData());
|
|
223
264
|
}
|
|
265
|
+
canvas.ctx.globalCompositeOperation = "destination-over";
|
|
266
|
+
canvas.ctx.fillStyle = cfg.fill;
|
|
267
|
+
canvas.ctx.fillRect(0, 0, fullCfg.width, fullCfg.height);
|
|
268
|
+
canvas.ctx.globalCompositeOperation = "source-over";
|
|
269
|
+
canvas._imageData = null;
|
|
224
270
|
resolve(canvas);
|
|
225
271
|
};
|
|
226
|
-
img.onerror = (
|
|
227
|
-
|
|
228
|
-
alert("The image URL cannot be loaded. Does the server support CORS?");
|
|
272
|
+
img.onerror = () => {
|
|
273
|
+
reject(new Error("The image URL cannot be loaded. Does the server support CORS?"));
|
|
229
274
|
};
|
|
230
275
|
});
|
|
231
276
|
}
|
|
@@ -233,12 +278,12 @@ var Canvas = class _Canvas {
|
|
|
233
278
|
const w = bitmap.width;
|
|
234
279
|
const h = bitmap.height;
|
|
235
280
|
const computeScale = getScale(w, h, cfg.computeSize, cfg.allowUpscale);
|
|
236
|
-
cfg.width = w / computeScale;
|
|
237
|
-
cfg.height = h / computeScale;
|
|
281
|
+
cfg.width = Math.round(w / computeScale);
|
|
282
|
+
cfg.height = Math.round(h / computeScale);
|
|
238
283
|
const viewScale = getScale(w, h, cfg.viewSize, cfg.allowUpscale);
|
|
239
284
|
cfg.scale = computeScale / viewScale;
|
|
240
285
|
const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
241
|
-
const canvas = this
|
|
286
|
+
const canvas = new this(fullCfg.width, fullCfg.height, true);
|
|
242
287
|
canvas.ctx.drawImage(bitmap, 0, 0, fullCfg.width, fullCfg.height);
|
|
243
288
|
if (cfg.fill === "transparent") {
|
|
244
289
|
cfg.outputFill = "transparent";
|
|
@@ -247,6 +292,11 @@ var Canvas = class _Canvas {
|
|
|
247
292
|
if (cfg.fill === "auto") {
|
|
248
293
|
cfg.fill = getFill(canvas.getImageData());
|
|
249
294
|
}
|
|
295
|
+
canvas.ctx.globalCompositeOperation = "destination-over";
|
|
296
|
+
canvas.ctx.fillStyle = cfg.fill;
|
|
297
|
+
canvas.ctx.fillRect(0, 0, fullCfg.width, fullCfg.height);
|
|
298
|
+
canvas.ctx.globalCompositeOperation = "source-over";
|
|
299
|
+
canvas._imageData = null;
|
|
250
300
|
return canvas;
|
|
251
301
|
}
|
|
252
302
|
static test(cfg) {
|
|
@@ -280,22 +330,27 @@ var Canvas = class _Canvas {
|
|
|
280
330
|
el.width = width;
|
|
281
331
|
el.height = height;
|
|
282
332
|
this.node = el;
|
|
283
|
-
|
|
333
|
+
const ctx = el.getContext("2d", { willReadFrequently });
|
|
334
|
+
if (!ctx) throw new Error("Failed to acquire 2d rendering context");
|
|
335
|
+
this.ctx = ctx;
|
|
284
336
|
} else {
|
|
285
337
|
const el = new OffscreenCanvas(width, height);
|
|
286
338
|
this.node = el;
|
|
287
|
-
|
|
339
|
+
const ctx = el.getContext("2d", { willReadFrequently });
|
|
340
|
+
if (!ctx) throw new Error("Failed to acquire 2d rendering context");
|
|
341
|
+
this.ctx = ctx;
|
|
288
342
|
}
|
|
289
343
|
this._imageData = null;
|
|
290
344
|
}
|
|
291
345
|
clone() {
|
|
292
|
-
const other = new _Canvas(this.node.width, this.node.height);
|
|
346
|
+
const other = new _Canvas(this.node.width, this.node.height, true);
|
|
293
347
|
other.ctx.drawImage(this.node, 0, 0);
|
|
294
348
|
return other;
|
|
295
349
|
}
|
|
296
350
|
fill(color) {
|
|
297
351
|
this.ctx.fillStyle = color;
|
|
298
352
|
this.ctx.fillRect(0, 0, this.node.width, this.node.height);
|
|
353
|
+
this._imageData = null;
|
|
299
354
|
return this;
|
|
300
355
|
}
|
|
301
356
|
getImageData() {
|
|
@@ -313,7 +368,39 @@ var Canvas = class _Canvas {
|
|
|
313
368
|
const difference2 = this.difference(otherCanvas);
|
|
314
369
|
return differenceToDistance(difference2, this.node.width * this.node.height);
|
|
315
370
|
}
|
|
371
|
+
patchImageData(offset, shapeData, color) {
|
|
372
|
+
if (!this._imageData) return;
|
|
373
|
+
const [cr, cg, cb] = color;
|
|
374
|
+
const dst = this._imageData.data;
|
|
375
|
+
const src = shapeData.data;
|
|
376
|
+
const sw = shapeData.width, sh = shapeData.height;
|
|
377
|
+
const fw = this._imageData.width, fh = this._imageData.height;
|
|
378
|
+
for (let sy = 0; sy < sh; sy++) {
|
|
379
|
+
const fy = sy + offset.top;
|
|
380
|
+
if (fy < 0 || fy >= fh) continue;
|
|
381
|
+
for (let sx = 0; sx < sw; sx++) {
|
|
382
|
+
const fx = sx + offset.left;
|
|
383
|
+
if (fx < 0 || fx >= fw) continue;
|
|
384
|
+
const si = 4 * (sx + sy * sw);
|
|
385
|
+
const a = src[si + 3];
|
|
386
|
+
if (a === 0) continue;
|
|
387
|
+
const fi = 4 * (fx + fy * fw);
|
|
388
|
+
const alpha = a / 255, blend = 1 - alpha;
|
|
389
|
+
dst[fi] = cr * alpha + dst[fi] * blend;
|
|
390
|
+
dst[fi + 1] = cg * alpha + dst[fi + 1] * blend;
|
|
391
|
+
dst[fi + 2] = cb * alpha + dst[fi + 2] * blend;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
316
395
|
drawStep(step) {
|
|
396
|
+
if (this._imageData) {
|
|
397
|
+
try {
|
|
398
|
+
const shapeData = step.shape.rasterize(step.alpha).getImageData();
|
|
399
|
+
this.patchImageData(step.shape.bbox, shapeData, parseColor(step.color));
|
|
400
|
+
} catch {
|
|
401
|
+
this._imageData = null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
317
404
|
this.ctx.globalAlpha = step.alpha;
|
|
318
405
|
this.ctx.fillStyle = step.color;
|
|
319
406
|
step.shape.render(this.ctx);
|
|
@@ -375,8 +462,8 @@ var Step = class _Step {
|
|
|
375
462
|
}
|
|
376
463
|
/* apply this step to a state to get a new state. call only after .compute */
|
|
377
464
|
apply(state) {
|
|
378
|
-
|
|
379
|
-
return new State(state.target,
|
|
465
|
+
state.canvas.drawStep(this);
|
|
466
|
+
return new State(state.target, state.canvas, this.distance);
|
|
380
467
|
}
|
|
381
468
|
/* find optimal color and compute the resulting distance */
|
|
382
469
|
compute(state) {
|
|
@@ -451,7 +538,7 @@ var Step = class _Step {
|
|
|
451
538
|
}
|
|
452
539
|
/* return a slightly mutated step */
|
|
453
540
|
mutate() {
|
|
454
|
-
const newShape = this.shape.mutate(
|
|
541
|
+
const newShape = this.shape.mutate();
|
|
455
542
|
const mutated = new _Step(newShape, this.cfg);
|
|
456
543
|
if (this.cfg.mutateAlpha) {
|
|
457
544
|
const mutatedAlpha = this.alpha + (Math.random() - 0.5) * 0.08;
|
|
@@ -463,6 +550,15 @@ var Step = class _Step {
|
|
|
463
550
|
|
|
464
551
|
// src/shape.ts
|
|
465
552
|
var _rasterCanvas = null;
|
|
553
|
+
var _glyphMeasureCanvas = null;
|
|
554
|
+
function getGlyphMeasureCanvas() {
|
|
555
|
+
if (!_glyphMeasureCanvas) {
|
|
556
|
+
_glyphMeasureCanvas = new Canvas(1, 1);
|
|
557
|
+
}
|
|
558
|
+
return _glyphMeasureCanvas;
|
|
559
|
+
}
|
|
560
|
+
__name(getGlyphMeasureCanvas, "getGlyphMeasureCanvas");
|
|
561
|
+
var BBOX_PAD = 1;
|
|
466
562
|
var Shape = class {
|
|
467
563
|
static {
|
|
468
564
|
__name(this, "Shape");
|
|
@@ -490,7 +586,7 @@ var Shape = class {
|
|
|
490
586
|
constructor(_w, _h) {
|
|
491
587
|
this.bbox = { left: 0, top: 0, width: 0, height: 0 };
|
|
492
588
|
}
|
|
493
|
-
mutate(
|
|
589
|
+
mutate() {
|
|
494
590
|
return this;
|
|
495
591
|
}
|
|
496
592
|
toSVG() {
|
|
@@ -520,9 +616,41 @@ var Shape = class {
|
|
|
520
616
|
const data = ctx.getImageData(0, 0, w, h);
|
|
521
617
|
return { getImageData: /* @__PURE__ */ __name(() => data, "getImageData") };
|
|
522
618
|
}
|
|
619
|
+
/* Grow a tight bbox by BBOX_PAD on all sides and snap to integer bounds.
|
|
620
|
+
* Integer left/top/width/height are required: computeColorAndDifferenceChange
|
|
621
|
+
* and rasterize index/translate a flat pixel array directly from these. */
|
|
622
|
+
setBbox(tight) {
|
|
623
|
+
const left = Math.floor(tight.left) - BBOX_PAD;
|
|
624
|
+
const top = Math.floor(tight.top) - BBOX_PAD;
|
|
625
|
+
const right = Math.ceil(tight.left + tight.width) + BBOX_PAD;
|
|
626
|
+
const bottom = Math.ceil(tight.top + tight.height) + BBOX_PAD;
|
|
627
|
+
this.bbox = { left, top, width: Math.max(1, right - left), height: Math.max(1, bottom - top) };
|
|
628
|
+
return this;
|
|
629
|
+
}
|
|
523
630
|
render(_ctx) {
|
|
524
631
|
}
|
|
525
632
|
};
|
|
633
|
+
var ConstrainedShape = class extends Shape {
|
|
634
|
+
static {
|
|
635
|
+
__name(this, "ConstrainedShape");
|
|
636
|
+
}
|
|
637
|
+
tonalRange;
|
|
638
|
+
invertTonal;
|
|
639
|
+
saturationRange;
|
|
640
|
+
invertSaturation;
|
|
641
|
+
hueCenter;
|
|
642
|
+
hueTolerance;
|
|
643
|
+
invertHue;
|
|
644
|
+
applyConstraints(opts) {
|
|
645
|
+
this.tonalRange = opts?.tonalRange;
|
|
646
|
+
this.invertTonal = opts?.invertTonal;
|
|
647
|
+
this.saturationRange = opts?.saturationRange;
|
|
648
|
+
this.invertSaturation = opts?.invertSaturation;
|
|
649
|
+
this.hueCenter = opts?.hueCenter;
|
|
650
|
+
this.hueTolerance = opts?.hueTolerance;
|
|
651
|
+
this.invertHue = opts?.invertHue;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
526
654
|
var Polygon = class _Polygon extends Shape {
|
|
527
655
|
static {
|
|
528
656
|
__name(this, "Polygon");
|
|
@@ -557,33 +685,18 @@ var Polygon = class _Polygon extends Shape {
|
|
|
557
685
|
path.setAttribute("d", `${d}Z`);
|
|
558
686
|
return path;
|
|
559
687
|
}
|
|
560
|
-
mutate(
|
|
688
|
+
mutate() {
|
|
561
689
|
const clone = this._cloneEmpty();
|
|
562
690
|
clone.points = this.points.map(([x, y]) => [x, y]);
|
|
563
691
|
const index = Math.floor(Math.random() * this.points.length);
|
|
564
692
|
const point = clone.points[index];
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
point[
|
|
568
|
-
point[1] += ~~(radius * Math.sin(angle));
|
|
693
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
694
|
+
point[0] += dx;
|
|
695
|
+
point[1] += dy;
|
|
569
696
|
return clone.computeBbox();
|
|
570
697
|
}
|
|
571
698
|
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;
|
|
699
|
+
return this.setBbox(bboxOfPoints(this.points));
|
|
587
700
|
}
|
|
588
701
|
_createPoints(w, h, count) {
|
|
589
702
|
const first = Shape.randomPoint(w, h);
|
|
@@ -626,7 +739,7 @@ var Rectangle = class _Rectangle extends Polygon {
|
|
|
626
739
|
toData(a, c) {
|
|
627
740
|
return { t: "r", a, c, pts: this.points.map(([x, y]) => [x, y]) };
|
|
628
741
|
}
|
|
629
|
-
mutate(
|
|
742
|
+
mutate() {
|
|
630
743
|
const clone = this._cloneEmpty();
|
|
631
744
|
clone.points = this.points.map(([x, y]) => [x, y]);
|
|
632
745
|
const amount = ~~((Math.random() - 0.5) * 20);
|
|
@@ -695,17 +808,16 @@ var Ellipse = class _Ellipse extends Shape {
|
|
|
695
808
|
toData(a, c) {
|
|
696
809
|
return { t: "e", a, c, cx: this.center[0], cy: this.center[1], rx: this.rx, ry: this.ry };
|
|
697
810
|
}
|
|
698
|
-
mutate(
|
|
811
|
+
mutate() {
|
|
699
812
|
const clone = new _Ellipse(0, 0);
|
|
700
813
|
clone.center = [this.center[0], this.center[1]];
|
|
701
814
|
clone.rx = this.rx;
|
|
702
815
|
clone.ry = this.ry;
|
|
703
816
|
switch (Math.floor(Math.random() * 3)) {
|
|
704
817
|
case 0: {
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
clone.center[
|
|
708
|
-
clone.center[1] += ~~(radius * Math.sin(angle));
|
|
818
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
819
|
+
clone.center[0] += dx;
|
|
820
|
+
clone.center[1] += dy;
|
|
709
821
|
break;
|
|
710
822
|
}
|
|
711
823
|
case 1:
|
|
@@ -720,13 +832,12 @@ var Ellipse = class _Ellipse extends Shape {
|
|
|
720
832
|
return clone.computeBbox();
|
|
721
833
|
}
|
|
722
834
|
computeBbox() {
|
|
723
|
-
this.
|
|
835
|
+
return this.setBbox({
|
|
724
836
|
left: this.center[0] - this.rx,
|
|
725
837
|
top: this.center[1] - this.ry,
|
|
726
838
|
width: 2 * this.rx,
|
|
727
839
|
height: 2 * this.ry
|
|
728
|
-
};
|
|
729
|
-
return this;
|
|
840
|
+
});
|
|
730
841
|
}
|
|
731
842
|
};
|
|
732
843
|
var Circle = class _Circle extends Shape {
|
|
@@ -742,13 +853,12 @@ var Circle = class _Circle extends Shape {
|
|
|
742
853
|
this.computeBbox();
|
|
743
854
|
}
|
|
744
855
|
computeBbox() {
|
|
745
|
-
this.
|
|
856
|
+
return this.setBbox({
|
|
746
857
|
left: this.center[0] - this.r,
|
|
747
858
|
top: this.center[1] - this.r,
|
|
748
859
|
width: 2 * this.r || 1,
|
|
749
860
|
height: 2 * this.r || 1
|
|
750
|
-
};
|
|
751
|
-
return this;
|
|
861
|
+
});
|
|
752
862
|
}
|
|
753
863
|
render(ctx) {
|
|
754
864
|
ctx.beginPath();
|
|
@@ -765,16 +875,15 @@ var Circle = class _Circle extends Shape {
|
|
|
765
875
|
toData(a, c) {
|
|
766
876
|
return { t: "c", a, c, cx: this.center[0], cy: this.center[1], r: this.r };
|
|
767
877
|
}
|
|
768
|
-
mutate(
|
|
878
|
+
mutate() {
|
|
769
879
|
const clone = new _Circle(0, 0);
|
|
770
880
|
clone.center = [this.center[0], this.center[1]];
|
|
771
881
|
clone.r = this.r;
|
|
772
882
|
switch (Math.floor(Math.random() * 2)) {
|
|
773
883
|
case 0: {
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
clone.center[
|
|
777
|
-
clone.center[1] += ~~(radius * Math.sin(angle));
|
|
884
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
885
|
+
clone.center[0] += dx;
|
|
886
|
+
clone.center[1] += dy;
|
|
778
887
|
break;
|
|
779
888
|
}
|
|
780
889
|
case 1:
|
|
@@ -800,16 +909,15 @@ var Glyph = class _Glyph extends Shape {
|
|
|
800
909
|
this.computeBbox();
|
|
801
910
|
}
|
|
802
911
|
computeBbox() {
|
|
803
|
-
const tmp =
|
|
912
|
+
const tmp = getGlyphMeasureCanvas();
|
|
804
913
|
tmp.ctx.font = `${this.fontSize}px sans-serif`;
|
|
805
914
|
const w = ~~tmp.ctx.measureText(this.text).width;
|
|
806
|
-
this.
|
|
915
|
+
return this.setBbox({
|
|
807
916
|
left: ~~(this.center[0] - w / 2),
|
|
808
917
|
top: ~~(this.center[1] - this.fontSize / 2),
|
|
809
918
|
width: w,
|
|
810
919
|
height: this.fontSize
|
|
811
|
-
};
|
|
812
|
-
return this;
|
|
920
|
+
});
|
|
813
921
|
}
|
|
814
922
|
render(ctx) {
|
|
815
923
|
ctx.textAlign = "center";
|
|
@@ -817,16 +925,15 @@ var Glyph = class _Glyph extends Shape {
|
|
|
817
925
|
ctx.font = `${this.fontSize}px sans-serif`;
|
|
818
926
|
ctx.fillText(this.text, this.center[0], this.center[1]);
|
|
819
927
|
}
|
|
820
|
-
mutate(
|
|
928
|
+
mutate() {
|
|
821
929
|
const clone = new _Glyph(0, 0, this.text);
|
|
822
930
|
clone.center = [this.center[0], this.center[1]];
|
|
823
931
|
clone.fontSize = this.fontSize;
|
|
824
932
|
switch (Math.floor(Math.random() * 2)) {
|
|
825
933
|
case 0: {
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
clone.center[
|
|
829
|
-
clone.center[1] += ~~(radius * Math.sin(angle));
|
|
934
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
935
|
+
clone.center[0] += dx;
|
|
936
|
+
clone.center[1] += dy;
|
|
830
937
|
break;
|
|
831
938
|
}
|
|
832
939
|
case 1:
|
|
@@ -848,7 +955,7 @@ var Glyph = class _Glyph extends Shape {
|
|
|
848
955
|
return text;
|
|
849
956
|
}
|
|
850
957
|
toData(a, c) {
|
|
851
|
-
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: this.text };
|
|
958
|
+
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: this.text, fontFamily: "sans-serif" };
|
|
852
959
|
}
|
|
853
960
|
};
|
|
854
961
|
var Square = class _Square extends Shape {
|
|
@@ -864,13 +971,12 @@ var Square = class _Square extends Shape {
|
|
|
864
971
|
this.computeBbox();
|
|
865
972
|
}
|
|
866
973
|
computeBbox() {
|
|
867
|
-
this.
|
|
974
|
+
return this.setBbox({
|
|
868
975
|
left: this.center[0] - this.r,
|
|
869
976
|
top: this.center[1] - this.r,
|
|
870
977
|
width: 2 * this.r || 1,
|
|
871
978
|
height: 2 * this.r || 1
|
|
872
|
-
};
|
|
873
|
-
return this;
|
|
979
|
+
});
|
|
874
980
|
}
|
|
875
981
|
render(ctx) {
|
|
876
982
|
ctx.fillRect(this.center[0] - this.r, this.center[1] - this.r, 2 * this.r, 2 * this.r);
|
|
@@ -886,16 +992,15 @@ var Square = class _Square extends Shape {
|
|
|
886
992
|
toData(a, c) {
|
|
887
993
|
return { t: "s", a, c, cx: this.center[0], cy: this.center[1], r: this.r };
|
|
888
994
|
}
|
|
889
|
-
mutate(
|
|
995
|
+
mutate() {
|
|
890
996
|
const clone = new _Square(0, 0);
|
|
891
997
|
clone.center = [this.center[0], this.center[1]];
|
|
892
998
|
clone.r = this.r;
|
|
893
999
|
switch (Math.floor(Math.random() * 2)) {
|
|
894
1000
|
case 0: {
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
clone.center[
|
|
898
|
-
clone.center[1] += ~~(radius * Math.sin(angle));
|
|
1001
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
1002
|
+
clone.center[0] += dx;
|
|
1003
|
+
clone.center[1] += dy;
|
|
899
1004
|
break;
|
|
900
1005
|
}
|
|
901
1006
|
case 1:
|
|
@@ -924,33 +1029,14 @@ var Hexagon = class _Hexagon extends Shape {
|
|
|
924
1029
|
}
|
|
925
1030
|
_points() {
|
|
926
1031
|
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
|
-
});
|
|
1032
|
+
this._cachedPoints = regularPolygonPoints(this.center[0], this.center[1], 6, this.angle, this.r);
|
|
934
1033
|
}
|
|
935
1034
|
return this._cachedPoints;
|
|
936
1035
|
}
|
|
937
1036
|
computeBbox() {
|
|
938
1037
|
this._cachedPoints = null;
|
|
939
1038
|
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;
|
|
1039
|
+
return this.setBbox(bboxOfPoints(pts));
|
|
954
1040
|
}
|
|
955
1041
|
render(ctx) {
|
|
956
1042
|
const pts = this._points();
|
|
@@ -967,17 +1053,16 @@ var Hexagon = class _Hexagon extends Shape {
|
|
|
967
1053
|
toData(a, c) {
|
|
968
1054
|
return { t: "h", a, c, cx: this.center[0], cy: this.center[1], r: this.r, angle: this.angle };
|
|
969
1055
|
}
|
|
970
|
-
mutate(
|
|
1056
|
+
mutate() {
|
|
971
1057
|
const clone = new _Hexagon(0, 0);
|
|
972
1058
|
clone.center = [this.center[0], this.center[1]];
|
|
973
1059
|
clone.r = this.r;
|
|
974
1060
|
clone.angle = this.angle;
|
|
975
1061
|
switch (Math.floor(Math.random() * 3)) {
|
|
976
1062
|
case 0: {
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
clone.center[
|
|
980
|
-
clone.center[1] += ~~(d * Math.sin(a));
|
|
1063
|
+
const [dx, dy] = randomPolarOffset(20);
|
|
1064
|
+
clone.center[0] += dx;
|
|
1065
|
+
clone.center[1] += dy;
|
|
981
1066
|
break;
|
|
982
1067
|
}
|
|
983
1068
|
case 1:
|
|
@@ -1027,19 +1112,12 @@ function makeNGon(opts) {
|
|
|
1027
1112
|
const startAngle = opts.startAngle ?? defaultAngle;
|
|
1028
1113
|
if (sides < 3) throw new RangeError("makeNGon requires at least 3 sides");
|
|
1029
1114
|
if (regular) {
|
|
1030
|
-
class NGonRegular extends
|
|
1115
|
+
class NGonRegular extends ConstrainedShape {
|
|
1031
1116
|
static {
|
|
1032
1117
|
__name(this, "NGonRegular");
|
|
1033
1118
|
}
|
|
1034
1119
|
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
1120
|
static _shapeSpec = { f: "ngon", o: NGonRegular._ngonOpts };
|
|
1036
|
-
tonalRange;
|
|
1037
|
-
invertTonal;
|
|
1038
|
-
saturationRange;
|
|
1039
|
-
invertSaturation;
|
|
1040
|
-
hueCenter;
|
|
1041
|
-
hueTolerance;
|
|
1042
|
-
invertHue;
|
|
1043
1121
|
center;
|
|
1044
1122
|
r;
|
|
1045
1123
|
angle;
|
|
@@ -1047,13 +1125,7 @@ function makeNGon(opts) {
|
|
|
1047
1125
|
_cachedPoints;
|
|
1048
1126
|
constructor(w, h) {
|
|
1049
1127
|
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;
|
|
1128
|
+
this.applyConstraints(opts);
|
|
1057
1129
|
this.center = Shape.randomPoint(w, h);
|
|
1058
1130
|
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
1059
1131
|
this.angle = rotatable ? Math.random() * (2 * Math.PI / sides) : startAngle;
|
|
@@ -1077,20 +1149,7 @@ function makeNGon(opts) {
|
|
|
1077
1149
|
computeBbox() {
|
|
1078
1150
|
this._cachedPoints = null;
|
|
1079
1151
|
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;
|
|
1152
|
+
return this.setBbox(bboxOfPoints(pts));
|
|
1094
1153
|
}
|
|
1095
1154
|
render(ctx) {
|
|
1096
1155
|
const pts = this._points();
|
|
@@ -1107,7 +1166,7 @@ function makeNGon(opts) {
|
|
|
1107
1166
|
toData(a, c) {
|
|
1108
1167
|
return { t: "p", a, c, pts: this._points().map(([x, y]) => [x, y]) };
|
|
1109
1168
|
}
|
|
1110
|
-
mutate(
|
|
1169
|
+
mutate() {
|
|
1111
1170
|
const clone = new NGonRegular(0, 0);
|
|
1112
1171
|
clone.center = [this.center[0], this.center[1]];
|
|
1113
1172
|
clone.r = this.r;
|
|
@@ -1116,10 +1175,9 @@ function makeNGon(opts) {
|
|
|
1116
1175
|
const mutCount = 2 + (rotatable ? 1 : 0) + (noise > 0 ? 1 : 0);
|
|
1117
1176
|
switch (Math.floor(Math.random() * mutCount)) {
|
|
1118
1177
|
case 0: {
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
clone.center[
|
|
1122
|
-
clone.center[1] += ~~(d * Math.sin(a));
|
|
1178
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1179
|
+
clone.center[0] += dx;
|
|
1180
|
+
clone.center[1] += dy;
|
|
1123
1181
|
break;
|
|
1124
1182
|
}
|
|
1125
1183
|
case 1:
|
|
@@ -1145,29 +1203,16 @@ function makeNGon(opts) {
|
|
|
1145
1203
|
}
|
|
1146
1204
|
return NGonRegular;
|
|
1147
1205
|
} else {
|
|
1148
|
-
class NGonIrregular extends
|
|
1206
|
+
class NGonIrregular extends ConstrainedShape {
|
|
1149
1207
|
static {
|
|
1150
1208
|
__name(this, "NGonIrregular");
|
|
1151
1209
|
}
|
|
1152
1210
|
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
1211
|
static _shapeSpec = { f: "ngon", o: NGonIrregular._ngonOpts };
|
|
1154
|
-
tonalRange;
|
|
1155
|
-
invertTonal;
|
|
1156
|
-
saturationRange;
|
|
1157
|
-
invertSaturation;
|
|
1158
|
-
hueCenter;
|
|
1159
|
-
hueTolerance;
|
|
1160
|
-
invertHue;
|
|
1161
1212
|
points;
|
|
1162
1213
|
constructor(w, h) {
|
|
1163
1214
|
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;
|
|
1215
|
+
this.applyConstraints(opts);
|
|
1171
1216
|
const first = Shape.randomPoint(w, h);
|
|
1172
1217
|
this.points = [first];
|
|
1173
1218
|
for (let i = 1; i < sides; i++) {
|
|
@@ -1182,20 +1227,7 @@ function makeNGon(opts) {
|
|
|
1182
1227
|
this.computeBbox();
|
|
1183
1228
|
}
|
|
1184
1229
|
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;
|
|
1230
|
+
return this.setBbox(bboxOfPoints(this.points));
|
|
1199
1231
|
}
|
|
1200
1232
|
render(ctx) {
|
|
1201
1233
|
ctx.beginPath();
|
|
@@ -1212,15 +1244,14 @@ function makeNGon(opts) {
|
|
|
1212
1244
|
toData(a, c) {
|
|
1213
1245
|
return { t: "p", a, c, pts: this.points.map(([x, y]) => [x, y]) };
|
|
1214
1246
|
}
|
|
1215
|
-
mutate(
|
|
1247
|
+
mutate() {
|
|
1216
1248
|
const clone = new NGonIrregular(0, 0);
|
|
1217
1249
|
clone.points = this.points.map(([x, y]) => [x, y]);
|
|
1218
1250
|
const index = Math.floor(Math.random() * clone.points.length);
|
|
1219
1251
|
const point = clone.points[index];
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
point[
|
|
1223
|
-
point[1] += ~~(radius * Math.sin(angle));
|
|
1252
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1253
|
+
point[0] += dx;
|
|
1254
|
+
point[1] += dy;
|
|
1224
1255
|
if (convex) clone.points = convexHull(clone.points);
|
|
1225
1256
|
return clone.computeBbox();
|
|
1226
1257
|
}
|
|
@@ -1235,32 +1266,19 @@ function makeRect(opts) {
|
|
|
1235
1266
|
const aspectRatio = opts?.aspectRatio;
|
|
1236
1267
|
const rotatable = opts?.rotatable ?? false;
|
|
1237
1268
|
const mutationScale = opts?.mutationScale ?? 20;
|
|
1238
|
-
class Rect extends
|
|
1269
|
+
class Rect extends ConstrainedShape {
|
|
1239
1270
|
static {
|
|
1240
1271
|
__name(this, "Rect");
|
|
1241
1272
|
}
|
|
1242
1273
|
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
1274
|
static _shapeSpec = { f: "rect", o: Rect._rectOpts };
|
|
1244
|
-
tonalRange;
|
|
1245
|
-
invertTonal;
|
|
1246
|
-
saturationRange;
|
|
1247
|
-
invertSaturation;
|
|
1248
|
-
hueCenter;
|
|
1249
|
-
hueTolerance;
|
|
1250
|
-
invertHue;
|
|
1251
1275
|
center;
|
|
1252
1276
|
hw;
|
|
1253
1277
|
hh;
|
|
1254
1278
|
angle;
|
|
1255
1279
|
constructor(w, h) {
|
|
1256
1280
|
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;
|
|
1281
|
+
this.applyConstraints(opts);
|
|
1264
1282
|
this.center = Shape.randomPoint(w, h);
|
|
1265
1283
|
this.hw = widthRange[0] + ~~(Math.random() * (widthRange[1] - widthRange[0]));
|
|
1266
1284
|
if (aspectRatio !== void 0) {
|
|
@@ -1272,41 +1290,18 @@ function makeRect(opts) {
|
|
|
1272
1290
|
this.computeBbox();
|
|
1273
1291
|
}
|
|
1274
1292
|
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;
|
|
1293
|
+
const corners = rectCorners(this.center[0], this.center[1], this.hw, this.hh, this.angle);
|
|
1294
|
+
return this.setBbox(bboxOfPoints(corners));
|
|
1286
1295
|
}
|
|
1287
1296
|
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
|
-
];
|
|
1297
|
+
const corners = rectCorners(this.center[0], this.center[1], this.hw, this.hh, this.angle);
|
|
1296
1298
|
ctx.beginPath();
|
|
1297
|
-
corners.forEach(([x, y], i) => i ? ctx.lineTo(
|
|
1299
|
+
corners.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y));
|
|
1298
1300
|
ctx.closePath();
|
|
1299
1301
|
ctx.fill();
|
|
1300
1302
|
}
|
|
1301
1303
|
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
|
-
];
|
|
1304
|
+
const corners = rectCorners(this.center[0], this.center[1], this.hw, this.hh, this.angle);
|
|
1310
1305
|
const node = document.createElementNS(SVGNS, "polygon");
|
|
1311
1306
|
node.setAttribute("points", corners.map((p) => p.join(",")).join(" "));
|
|
1312
1307
|
return node;
|
|
@@ -1323,7 +1318,7 @@ function makeRect(opts) {
|
|
|
1323
1318
|
angle: this.angle
|
|
1324
1319
|
};
|
|
1325
1320
|
}
|
|
1326
|
-
mutate(
|
|
1321
|
+
mutate() {
|
|
1327
1322
|
const clone = new Rect(0, 0);
|
|
1328
1323
|
clone.center = [this.center[0], this.center[1]];
|
|
1329
1324
|
clone.hw = this.hw;
|
|
@@ -1332,10 +1327,9 @@ function makeRect(opts) {
|
|
|
1332
1327
|
const mutCount = 2 + (rotatable ? 1 : 0) + (aspectRatio === void 0 ? 1 : 0);
|
|
1333
1328
|
switch (Math.floor(Math.random() * mutCount)) {
|
|
1334
1329
|
case 0: {
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
clone.center[
|
|
1338
|
-
clone.center[1] += ~~(d * Math.sin(a));
|
|
1330
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1331
|
+
clone.center[0] += dx;
|
|
1332
|
+
clone.center[1] += dy;
|
|
1339
1333
|
break;
|
|
1340
1334
|
}
|
|
1341
1335
|
case 1:
|
|
@@ -1369,42 +1363,28 @@ __name(makeRect, "makeRect");
|
|
|
1369
1363
|
function makeCircle(opts) {
|
|
1370
1364
|
const sizeRange = opts?.sizeRange ?? [1, 20];
|
|
1371
1365
|
const mutationScale = opts?.mutationScale ?? 20;
|
|
1372
|
-
class MadeCircle extends
|
|
1366
|
+
class MadeCircle extends ConstrainedShape {
|
|
1373
1367
|
static {
|
|
1374
1368
|
__name(this, "MadeCircle");
|
|
1375
1369
|
}
|
|
1376
1370
|
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
1371
|
static _shapeSpec = { f: "circle", o: MadeCircle._circleOpts };
|
|
1378
|
-
tonalRange;
|
|
1379
|
-
invertTonal;
|
|
1380
|
-
saturationRange;
|
|
1381
|
-
invertSaturation;
|
|
1382
|
-
hueCenter;
|
|
1383
|
-
hueTolerance;
|
|
1384
|
-
invertHue;
|
|
1385
1372
|
center;
|
|
1386
1373
|
r;
|
|
1387
1374
|
constructor(w, h) {
|
|
1388
1375
|
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;
|
|
1376
|
+
this.applyConstraints(opts);
|
|
1396
1377
|
this.center = Shape.randomPoint(w, h);
|
|
1397
1378
|
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
1398
1379
|
this.computeBbox();
|
|
1399
1380
|
}
|
|
1400
1381
|
computeBbox() {
|
|
1401
|
-
this.
|
|
1382
|
+
return this.setBbox({
|
|
1402
1383
|
left: this.center[0] - this.r,
|
|
1403
1384
|
top: this.center[1] - this.r,
|
|
1404
1385
|
width: 2 * this.r || 1,
|
|
1405
1386
|
height: 2 * this.r || 1
|
|
1406
|
-
};
|
|
1407
|
-
return this;
|
|
1387
|
+
});
|
|
1408
1388
|
}
|
|
1409
1389
|
render(ctx) {
|
|
1410
1390
|
ctx.beginPath();
|
|
@@ -1421,16 +1401,15 @@ function makeCircle(opts) {
|
|
|
1421
1401
|
toData(a, c) {
|
|
1422
1402
|
return { t: "c", a, c, cx: this.center[0], cy: this.center[1], r: this.r };
|
|
1423
1403
|
}
|
|
1424
|
-
mutate(
|
|
1404
|
+
mutate() {
|
|
1425
1405
|
const clone = new MadeCircle(0, 0);
|
|
1426
1406
|
clone.center = [this.center[0], this.center[1]];
|
|
1427
1407
|
clone.r = this.r;
|
|
1428
1408
|
switch (Math.floor(Math.random() * 2)) {
|
|
1429
1409
|
case 0: {
|
|
1430
|
-
const
|
|
1431
|
-
|
|
1432
|
-
clone.center[
|
|
1433
|
-
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1410
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1411
|
+
clone.center[0] += dx;
|
|
1412
|
+
clone.center[1] += dy;
|
|
1434
1413
|
break;
|
|
1435
1414
|
}
|
|
1436
1415
|
case 1:
|
|
@@ -1449,44 +1428,30 @@ function makeEllipse(opts) {
|
|
|
1449
1428
|
const ryRange = opts?.ryRange ?? [1, 20];
|
|
1450
1429
|
const aspectRatio = opts?.aspectRatio;
|
|
1451
1430
|
const mutationScale = opts?.mutationScale ?? 20;
|
|
1452
|
-
class MadeEllipse extends
|
|
1431
|
+
class MadeEllipse extends ConstrainedShape {
|
|
1453
1432
|
static {
|
|
1454
1433
|
__name(this, "MadeEllipse");
|
|
1455
1434
|
}
|
|
1456
1435
|
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
1436
|
static _shapeSpec = { f: "ellipse", o: MadeEllipse._ellipseOpts };
|
|
1458
|
-
tonalRange;
|
|
1459
|
-
invertTonal;
|
|
1460
|
-
saturationRange;
|
|
1461
|
-
invertSaturation;
|
|
1462
|
-
hueCenter;
|
|
1463
|
-
hueTolerance;
|
|
1464
|
-
invertHue;
|
|
1465
1437
|
center;
|
|
1466
1438
|
rx;
|
|
1467
1439
|
ry;
|
|
1468
1440
|
constructor(w, h) {
|
|
1469
1441
|
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;
|
|
1442
|
+
this.applyConstraints(opts);
|
|
1477
1443
|
this.center = Shape.randomPoint(w, h);
|
|
1478
1444
|
this.rx = Math.max(1, rxRange[0] + ~~(Math.random() * (rxRange[1] - rxRange[0])));
|
|
1479
1445
|
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
1446
|
this.computeBbox();
|
|
1481
1447
|
}
|
|
1482
1448
|
computeBbox() {
|
|
1483
|
-
this.
|
|
1449
|
+
return this.setBbox({
|
|
1484
1450
|
left: this.center[0] - this.rx,
|
|
1485
1451
|
top: this.center[1] - this.ry,
|
|
1486
1452
|
width: 2 * this.rx || 1,
|
|
1487
1453
|
height: 2 * this.ry || 1
|
|
1488
|
-
};
|
|
1489
|
-
return this;
|
|
1454
|
+
});
|
|
1490
1455
|
}
|
|
1491
1456
|
render(ctx) {
|
|
1492
1457
|
ctx.beginPath();
|
|
@@ -1504,7 +1469,7 @@ function makeEllipse(opts) {
|
|
|
1504
1469
|
toData(a, c) {
|
|
1505
1470
|
return { t: "e", a, c, cx: this.center[0], cy: this.center[1], rx: this.rx, ry: this.ry };
|
|
1506
1471
|
}
|
|
1507
|
-
mutate(
|
|
1472
|
+
mutate() {
|
|
1508
1473
|
const clone = new MadeEllipse(0, 0);
|
|
1509
1474
|
clone.center = [this.center[0], this.center[1]];
|
|
1510
1475
|
clone.rx = this.rx;
|
|
@@ -1512,10 +1477,9 @@ function makeEllipse(opts) {
|
|
|
1512
1477
|
const mutCount = aspectRatio === void 0 ? 3 : 2;
|
|
1513
1478
|
switch (Math.floor(Math.random() * mutCount)) {
|
|
1514
1479
|
case 0: {
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
clone.center[
|
|
1518
|
-
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1480
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1481
|
+
clone.center[0] += dx;
|
|
1482
|
+
clone.center[1] += dy;
|
|
1519
1483
|
break;
|
|
1520
1484
|
}
|
|
1521
1485
|
case 1:
|
|
@@ -1541,45 +1505,31 @@ function makeGlyph(opts) {
|
|
|
1541
1505
|
const fontFamily = opts?.fontFamily ?? "sans-serif";
|
|
1542
1506
|
const sizeRange = opts?.sizeRange ?? [10, 30];
|
|
1543
1507
|
const mutationScale = opts?.mutationScale ?? 20;
|
|
1544
|
-
class MadeGlyph extends
|
|
1508
|
+
class MadeGlyph extends ConstrainedShape {
|
|
1545
1509
|
static {
|
|
1546
1510
|
__name(this, "MadeGlyph");
|
|
1547
1511
|
}
|
|
1548
1512
|
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
1513
|
static _shapeSpec = { f: "glyph", o: MadeGlyph._glyphOpts };
|
|
1550
|
-
tonalRange;
|
|
1551
|
-
invertTonal;
|
|
1552
|
-
saturationRange;
|
|
1553
|
-
invertSaturation;
|
|
1554
|
-
hueCenter;
|
|
1555
|
-
hueTolerance;
|
|
1556
|
-
invertHue;
|
|
1557
1514
|
center;
|
|
1558
1515
|
fontSize;
|
|
1559
1516
|
constructor(w, h) {
|
|
1560
1517
|
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;
|
|
1518
|
+
this.applyConstraints(opts);
|
|
1568
1519
|
this.center = Shape.randomPoint(w, h);
|
|
1569
|
-
this.fontSize =
|
|
1520
|
+
this.fontSize = sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0]));
|
|
1570
1521
|
this.computeBbox();
|
|
1571
1522
|
}
|
|
1572
1523
|
computeBbox() {
|
|
1573
|
-
const tmp =
|
|
1524
|
+
const tmp = getGlyphMeasureCanvas();
|
|
1574
1525
|
tmp.ctx.font = `${this.fontSize}px ${fontFamily}`;
|
|
1575
1526
|
const w = ~~tmp.ctx.measureText(char).width;
|
|
1576
|
-
this.
|
|
1527
|
+
return this.setBbox({
|
|
1577
1528
|
left: ~~(this.center[0] - w / 2),
|
|
1578
1529
|
top: ~~(this.center[1] - this.fontSize / 2),
|
|
1579
1530
|
width: w || 1,
|
|
1580
1531
|
height: this.fontSize
|
|
1581
|
-
};
|
|
1582
|
-
return this;
|
|
1532
|
+
});
|
|
1583
1533
|
}
|
|
1584
1534
|
render(ctx) {
|
|
1585
1535
|
ctx.textAlign = "center";
|
|
@@ -1599,18 +1549,17 @@ function makeGlyph(opts) {
|
|
|
1599
1549
|
return text;
|
|
1600
1550
|
}
|
|
1601
1551
|
toData(a, c) {
|
|
1602
|
-
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: char };
|
|
1552
|
+
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: char, fontFamily };
|
|
1603
1553
|
}
|
|
1604
|
-
mutate(
|
|
1554
|
+
mutate() {
|
|
1605
1555
|
const clone = new MadeGlyph(0, 0);
|
|
1606
1556
|
clone.center = [this.center[0], this.center[1]];
|
|
1607
1557
|
clone.fontSize = this.fontSize;
|
|
1608
1558
|
switch (Math.floor(Math.random() * 2)) {
|
|
1609
1559
|
case 0: {
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
clone.center[
|
|
1613
|
-
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1560
|
+
const [dx, dy] = randomPolarOffset(mutationScale);
|
|
1561
|
+
clone.center[0] += dx;
|
|
1562
|
+
clone.center[1] += dy;
|
|
1614
1563
|
break;
|
|
1615
1564
|
}
|
|
1616
1565
|
case 1: {
|
|
@@ -1643,6 +1592,7 @@ function buildStepPlan(cfg) {
|
|
|
1643
1592
|
const { shapeTypes, shapeWeights, steps } = cfg;
|
|
1644
1593
|
if (!shapeWeights || shapeWeights.length !== shapeTypes.length) return [];
|
|
1645
1594
|
const total = shapeWeights.reduce((sum, w) => sum + w, 0);
|
|
1595
|
+
if (total <= 0) return [];
|
|
1646
1596
|
const floats = shapeWeights.map((w) => w / total * steps);
|
|
1647
1597
|
const floors = floats.map(Math.floor);
|
|
1648
1598
|
const remainder = steps - floors.reduce((s, n) => s + n, 0);
|
|
@@ -1663,6 +1613,7 @@ var Optimizer = class {
|
|
|
1663
1613
|
cfg;
|
|
1664
1614
|
state;
|
|
1665
1615
|
onStep;
|
|
1616
|
+
onError;
|
|
1666
1617
|
_steps;
|
|
1667
1618
|
_stopped;
|
|
1668
1619
|
_paused;
|
|
@@ -1678,6 +1629,8 @@ var Optimizer = class {
|
|
|
1678
1629
|
this._rejectionStreak = 0;
|
|
1679
1630
|
this.onStep = () => {
|
|
1680
1631
|
};
|
|
1632
|
+
this.onError = () => {
|
|
1633
|
+
};
|
|
1681
1634
|
this._stepPlan = buildStepPlan(cfg);
|
|
1682
1635
|
this._schedule = schedule;
|
|
1683
1636
|
}
|
|
@@ -1714,7 +1667,10 @@ var Optimizer = class {
|
|
|
1714
1667
|
this.onStep(null);
|
|
1715
1668
|
}
|
|
1716
1669
|
this._continue();
|
|
1717
|
-
}).catch(() =>
|
|
1670
|
+
}).catch((error) => {
|
|
1671
|
+
this.onError(error);
|
|
1672
|
+
this.stop();
|
|
1673
|
+
});
|
|
1718
1674
|
}
|
|
1719
1675
|
_continue() {
|
|
1720
1676
|
if (this._stopped || this._paused) {
|
|
@@ -1740,28 +1696,20 @@ var Optimizer = class {
|
|
|
1740
1696
|
}
|
|
1741
1697
|
return Promise.all(promises).then(() => bestStep);
|
|
1742
1698
|
}
|
|
1743
|
-
_optimizeStep(step) {
|
|
1699
|
+
async _optimizeStep(step) {
|
|
1744
1700
|
const LIMIT = this.cfg.mutations;
|
|
1745
1701
|
let failedAttempts = 0;
|
|
1746
|
-
let resolve;
|
|
1747
1702
|
let bestStep = step;
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
if (
|
|
1751
|
-
|
|
1703
|
+
while (!this._stopped && failedAttempts < LIMIT) {
|
|
1704
|
+
const mutatedStep = await bestStep.mutate().compute(this.state);
|
|
1705
|
+
if (mutatedStep.distance < bestStep.distance) {
|
|
1706
|
+
failedAttempts = 0;
|
|
1707
|
+
bestStep = mutatedStep;
|
|
1708
|
+
} else {
|
|
1709
|
+
failedAttempts++;
|
|
1752
1710
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
failedAttempts = 0;
|
|
1756
|
-
bestStep = mutatedStep;
|
|
1757
|
-
} else {
|
|
1758
|
-
failedAttempts++;
|
|
1759
|
-
}
|
|
1760
|
-
tryMutation();
|
|
1761
|
-
});
|
|
1762
|
-
}, "tryMutation");
|
|
1763
|
-
tryMutation();
|
|
1764
|
-
return promise;
|
|
1711
|
+
}
|
|
1712
|
+
return bestStep;
|
|
1765
1713
|
}
|
|
1766
1714
|
};
|
|
1767
1715
|
|
|
@@ -1770,13 +1718,6 @@ function rgbString([r, g, b]) {
|
|
|
1770
1718
|
return `rgb(${r}, ${g}, ${b})`;
|
|
1771
1719
|
}
|
|
1772
1720
|
__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
1721
|
function renderStepToCtx(data, ctx) {
|
|
1781
1722
|
ctx.globalAlpha = data.a;
|
|
1782
1723
|
ctx.fillStyle = rgbString(data.c);
|
|
@@ -1807,7 +1748,7 @@ function renderStepToCtx(data, ctx) {
|
|
|
1807
1748
|
break;
|
|
1808
1749
|
}
|
|
1809
1750
|
case "h": {
|
|
1810
|
-
const pts =
|
|
1751
|
+
const pts = regularPolygonPoints(data.cx, data.cy, 6, data.angle, data.r);
|
|
1811
1752
|
ctx.beginPath();
|
|
1812
1753
|
pts.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y));
|
|
1813
1754
|
ctx.closePath();
|
|
@@ -1817,23 +1758,20 @@ function renderStepToCtx(data, ctx) {
|
|
|
1817
1758
|
case "sm": {
|
|
1818
1759
|
ctx.textAlign = "center";
|
|
1819
1760
|
ctx.textBaseline = "middle";
|
|
1820
|
-
ctx.font = `${data.fs}px sans-serif`;
|
|
1761
|
+
ctx.font = `${data.fs}px ${data.fontFamily ?? "sans-serif"}`;
|
|
1821
1762
|
ctx.fillText(data.text, data.cx, data.cy);
|
|
1822
1763
|
break;
|
|
1823
1764
|
}
|
|
1824
1765
|
case "rc": {
|
|
1825
|
-
const
|
|
1826
|
-
const cos = Math.cos(angle);
|
|
1827
|
-
const sin = Math.sin(angle);
|
|
1766
|
+
const corners = rectCorners(data.cx, data.cy, data.hw, data.hh, data.angle);
|
|
1828
1767
|
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);
|
|
1768
|
+
corners.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y));
|
|
1833
1769
|
ctx.closePath();
|
|
1834
1770
|
ctx.fill();
|
|
1835
1771
|
break;
|
|
1836
1772
|
}
|
|
1773
|
+
default:
|
|
1774
|
+
throw new Error("renderStepToCtx: unknown step type");
|
|
1837
1775
|
}
|
|
1838
1776
|
}
|
|
1839
1777
|
__name(renderStepToCtx, "renderStepToCtx");
|
|
@@ -1875,7 +1813,7 @@ function stepDataToSVGElement(data) {
|
|
|
1875
1813
|
}
|
|
1876
1814
|
case "h": {
|
|
1877
1815
|
node = document.createElementNS(SVGNS, "polygon");
|
|
1878
|
-
const pts =
|
|
1816
|
+
const pts = regularPolygonPoints(data.cx, data.cy, 6, data.angle, data.r);
|
|
1879
1817
|
node.setAttribute("points", pts.map((p) => p.join(",")).join(" "));
|
|
1880
1818
|
break;
|
|
1881
1819
|
}
|
|
@@ -1885,26 +1823,20 @@ function stepDataToSVGElement(data) {
|
|
|
1885
1823
|
node.setAttribute("text-anchor", "middle");
|
|
1886
1824
|
node.setAttribute("dominant-baseline", "central");
|
|
1887
1825
|
node.setAttribute("font-size", String(data.fs));
|
|
1888
|
-
node.setAttribute("font-family", "sans-serif");
|
|
1826
|
+
node.setAttribute("font-family", data.fontFamily ?? "sans-serif");
|
|
1889
1827
|
node.setAttribute("x", String(data.cx));
|
|
1890
1828
|
node.setAttribute("y", String(data.cy));
|
|
1891
1829
|
break;
|
|
1892
1830
|
}
|
|
1893
1831
|
case "rc": {
|
|
1894
|
-
const { cx, cy, hw, hh, angle } = data;
|
|
1895
|
-
const cos = Math.cos(angle);
|
|
1896
|
-
const sin = Math.sin(angle);
|
|
1897
1832
|
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
|
-
];
|
|
1833
|
+
const pts = rectCorners(data.cx, data.cy, data.hw, data.hh, data.angle);
|
|
1904
1834
|
node = document.createElementNS(SVGNS, "polygon");
|
|
1905
1835
|
node.setAttribute("points", pts.map(([x, y]) => `${fmt(x)},${fmt(y)}`).join(" "));
|
|
1906
1836
|
break;
|
|
1907
1837
|
}
|
|
1838
|
+
default:
|
|
1839
|
+
throw new Error("stepDataToSVGElement: unknown step type");
|
|
1908
1840
|
}
|
|
1909
1841
|
node.setAttribute("fill", color);
|
|
1910
1842
|
node.setAttribute("fill-opacity", opacity);
|
|
@@ -1912,6 +1844,9 @@ function stepDataToSVGElement(data) {
|
|
|
1912
1844
|
}
|
|
1913
1845
|
__name(stepDataToSVGElement, "stepDataToSVGElement");
|
|
1914
1846
|
function replayOutput(data) {
|
|
1847
|
+
if (data.v !== 1) {
|
|
1848
|
+
throw new Error(`Unsupported serialized output version: ${data.v}`);
|
|
1849
|
+
}
|
|
1915
1850
|
const fill = rgbString(data.fill);
|
|
1916
1851
|
const vw = data.w * data.scale;
|
|
1917
1852
|
const vh = data.h * data.scale;
|
|
@@ -1944,6 +1879,7 @@ export {
|
|
|
1944
1879
|
State,
|
|
1945
1880
|
Step,
|
|
1946
1881
|
Triangle,
|
|
1882
|
+
bboxOfPoints,
|
|
1947
1883
|
clamp,
|
|
1948
1884
|
clampColor,
|
|
1949
1885
|
computeColorAndDifferenceChange,
|
|
@@ -1957,6 +1893,9 @@ export {
|
|
|
1957
1893
|
makeNGon,
|
|
1958
1894
|
makeRect,
|
|
1959
1895
|
parseColor,
|
|
1896
|
+
randomPolarOffset,
|
|
1897
|
+
rectCorners,
|
|
1898
|
+
regularPolygonPoints,
|
|
1960
1899
|
renderStepToCtx,
|
|
1961
1900
|
replayOutput,
|
|
1962
1901
|
rgbToHsl,
|