@slithy/prim-lib 0.5.1 → 0.6.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/dist/index.d.ts +28 -1
- package/dist/index.js +280 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ interface ShapeImageData {
|
|
|
23
23
|
*/
|
|
24
24
|
interface ShapeInterface {
|
|
25
25
|
bbox: Bbox;
|
|
26
|
+
tonalRange?: [number, number];
|
|
26
27
|
/** cfg is rarely used by implementations; accepts Partial to keep call sites flexible. */
|
|
27
28
|
mutate(cfg?: Partial<Cfg>): ShapeInterface;
|
|
28
29
|
toSVG(): SVGElement | undefined;
|
|
@@ -43,6 +44,7 @@ interface Cfg {
|
|
|
43
44
|
mutations: number;
|
|
44
45
|
steps: number;
|
|
45
46
|
fill: 'auto' | string;
|
|
47
|
+
outputFill?: string;
|
|
46
48
|
computeSize: number;
|
|
47
49
|
viewSize: number;
|
|
48
50
|
allowUpscale?: boolean;
|
|
@@ -193,6 +195,7 @@ declare class Optimizer {
|
|
|
193
195
|
_paused: boolean;
|
|
194
196
|
_stepPlan: ShapeCtor[];
|
|
195
197
|
_schedule: (fn: () => void) => void;
|
|
198
|
+
_rejectionStreak: number;
|
|
196
199
|
constructor(original: Canvas, cfg: Cfg, schedule?: (fn: () => void) => void);
|
|
197
200
|
start(): void;
|
|
198
201
|
stop(): void;
|
|
@@ -303,6 +306,7 @@ interface NGonOptions {
|
|
|
303
306
|
noise?: number;
|
|
304
307
|
sizeRange?: [number, number];
|
|
305
308
|
mutationScale?: number;
|
|
309
|
+
tonalRange?: [number, number];
|
|
306
310
|
}
|
|
307
311
|
declare function makeNGon(opts: NGonOptions): new (w: number, h: number) => ShapeInterface;
|
|
308
312
|
interface RectOptions {
|
|
@@ -311,8 +315,31 @@ interface RectOptions {
|
|
|
311
315
|
aspectRatio?: number;
|
|
312
316
|
rotatable?: boolean;
|
|
313
317
|
mutationScale?: number;
|
|
318
|
+
tonalRange?: [number, number];
|
|
314
319
|
}
|
|
315
320
|
declare function makeRect(opts?: Partial<RectOptions>): new (w: number, h: number) => ShapeInterface;
|
|
321
|
+
interface CircleOptions {
|
|
322
|
+
sizeRange?: [number, number];
|
|
323
|
+
mutationScale?: number;
|
|
324
|
+
tonalRange?: [number, number];
|
|
325
|
+
}
|
|
326
|
+
declare function makeCircle(opts?: Partial<CircleOptions>): new (w: number, h: number) => ShapeInterface;
|
|
327
|
+
interface EllipseOptions {
|
|
328
|
+
rxRange?: [number, number];
|
|
329
|
+
ryRange?: [number, number];
|
|
330
|
+
aspectRatio?: number;
|
|
331
|
+
mutationScale?: number;
|
|
332
|
+
tonalRange?: [number, number];
|
|
333
|
+
}
|
|
334
|
+
declare function makeEllipse(opts?: Partial<EllipseOptions>): new (w: number, h: number) => ShapeInterface;
|
|
335
|
+
interface GlyphOptions {
|
|
336
|
+
char?: string;
|
|
337
|
+
fontFamily?: string;
|
|
338
|
+
sizeRange?: [number, number];
|
|
339
|
+
mutationScale?: number;
|
|
340
|
+
tonalRange?: [number, number];
|
|
341
|
+
}
|
|
342
|
+
declare function makeGlyph(opts?: Partial<GlyphOptions>): new (w: number, h: number) => ShapeInterface;
|
|
316
343
|
declare class Debug extends Shape {
|
|
317
344
|
constructor(w: number, h: number);
|
|
318
345
|
render(ctx: CanvasRenderingContext2D): void;
|
|
@@ -341,4 +368,4 @@ interface ReplayResult {
|
|
|
341
368
|
}
|
|
342
369
|
declare function replayOutput(data: SerializedOutput): ReplayResult;
|
|
343
370
|
|
|
344
|
-
export { type Bbox, Canvas, type Cfg, Circle, type Ctx2D, Debug, Ellipse, Glyph, Hexagon, type ImageDataLike, type NGonOptions, Optimizer, type Point, type PreCfg, type RGB, type RectOptions, Rectangle, type ReplayResult, SVGNS, type SerializedOutput, Shape, type ShapeImageData, type ShapeInterface, Square, State, Step, type StepData, Triangle, clamp, clampColor, computeColorAndDifferenceChange, difference, differenceToDistance, distanceToDifference, getFill, makeNGon, makeRect, parseColor, renderStepToCtx, replayOutput, stepDataToSVGElement, stepPerf };
|
|
371
|
+
export { type Bbox, Canvas, type Cfg, Circle, type CircleOptions, type Ctx2D, Debug, Ellipse, type EllipseOptions, Glyph, type GlyphOptions, Hexagon, type ImageDataLike, type NGonOptions, Optimizer, type Point, type PreCfg, type RGB, type RectOptions, Rectangle, type ReplayResult, SVGNS, type SerializedOutput, Shape, type ShapeImageData, type ShapeInterface, Square, State, Step, type StepData, Triangle, clamp, clampColor, computeColorAndDifferenceChange, difference, differenceToDistance, distanceToDifference, getFill, makeCircle, makeEllipse, makeGlyph, makeNGon, makeRect, parseColor, renderStepToCtx, replayOutput, stepDataToSVGElement, stepPerf };
|
package/dist/index.js
CHANGED
|
@@ -169,7 +169,7 @@ var Canvas = class _Canvas {
|
|
|
169
169
|
}
|
|
170
170
|
static empty(cfg, svg) {
|
|
171
171
|
if (svg) {
|
|
172
|
-
return this.svgRoot(cfg.width, cfg.height, cfg.fill);
|
|
172
|
+
return this.svgRoot(cfg.width, cfg.height, cfg.outputFill ?? cfg.fill);
|
|
173
173
|
} else {
|
|
174
174
|
return new this(cfg.width, cfg.height).fill(cfg.fill);
|
|
175
175
|
}
|
|
@@ -195,7 +195,11 @@ var Canvas = class _Canvas {
|
|
|
195
195
|
const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
196
196
|
const canvas = this.empty(fullCfg);
|
|
197
197
|
canvas.ctx.drawImage(img, 0, 0, fullCfg.width, fullCfg.height);
|
|
198
|
-
if (cfg.fill
|
|
198
|
+
if (cfg.fill === "transparent") {
|
|
199
|
+
cfg.outputFill = "transparent";
|
|
200
|
+
cfg.fill = "auto";
|
|
201
|
+
}
|
|
202
|
+
if (cfg.fill === "auto") {
|
|
199
203
|
cfg.fill = getFill(canvas.getImageData());
|
|
200
204
|
}
|
|
201
205
|
resolve(canvas);
|
|
@@ -217,6 +221,10 @@ var Canvas = class _Canvas {
|
|
|
217
221
|
const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
218
222
|
const canvas = this.empty(fullCfg);
|
|
219
223
|
canvas.ctx.drawImage(bitmap, 0, 0, fullCfg.width, fullCfg.height);
|
|
224
|
+
if (cfg.fill === "transparent") {
|
|
225
|
+
cfg.outputFill = "transparent";
|
|
226
|
+
cfg.fill = "auto";
|
|
227
|
+
}
|
|
220
228
|
if (cfg.fill === "auto") {
|
|
221
229
|
cfg.fill = getFill(canvas.getImageData());
|
|
222
230
|
}
|
|
@@ -355,6 +363,32 @@ var Step = class _Step {
|
|
|
355
363
|
compute(state) {
|
|
356
364
|
const pixels = state.canvas.node.width * state.canvas.node.height;
|
|
357
365
|
const offset = this.shape.bbox;
|
|
366
|
+
const tonalRange = this.shape.tonalRange;
|
|
367
|
+
if (tonalRange) {
|
|
368
|
+
const { left, top, width, height } = offset;
|
|
369
|
+
const targetData = state.target.getImageData();
|
|
370
|
+
const fw = targetData.width;
|
|
371
|
+
const fh = targetData.height;
|
|
372
|
+
let sum = 0, count = 0;
|
|
373
|
+
for (let sy = 0; sy < height; sy++) {
|
|
374
|
+
const fy = top + sy;
|
|
375
|
+
if (fy < 0 || fy >= fh) continue;
|
|
376
|
+
for (let sx = 0; sx < width; sx++) {
|
|
377
|
+
const fx = left + sx;
|
|
378
|
+
if (fx < 0 || fx >= fw) continue;
|
|
379
|
+
const i = 4 * (fx + fy * fw);
|
|
380
|
+
sum += 0.299 * targetData.data[i] + 0.587 * targetData.data[i + 1] + 0.114 * targetData.data[i + 2];
|
|
381
|
+
count++;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (count > 0) {
|
|
385
|
+
const luma = sum / count;
|
|
386
|
+
if (luma < tonalRange[0] || luma > tonalRange[1]) {
|
|
387
|
+
this.distance = Infinity;
|
|
388
|
+
return Promise.resolve(this);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
358
392
|
const t0 = performance.now();
|
|
359
393
|
const shapeImageData = this.shape.rasterize(this.alpha).getImageData();
|
|
360
394
|
const t1 = performance.now();
|
|
@@ -955,8 +989,9 @@ function makeNGon(opts) {
|
|
|
955
989
|
static {
|
|
956
990
|
__name(this, "NGonRegular");
|
|
957
991
|
}
|
|
958
|
-
static _ngonOpts = { sides, regular: true, rotatable, noise, sizeRange, mutationScale, startAngle };
|
|
992
|
+
static _ngonOpts = { sides, regular: true, rotatable, noise, sizeRange, mutationScale, startAngle, tonalRange: opts.tonalRange };
|
|
959
993
|
static _shapeSpec = { f: "ngon", o: NGonRegular._ngonOpts };
|
|
994
|
+
tonalRange;
|
|
960
995
|
center;
|
|
961
996
|
r;
|
|
962
997
|
angle;
|
|
@@ -964,6 +999,7 @@ function makeNGon(opts) {
|
|
|
964
999
|
_cachedPoints;
|
|
965
1000
|
constructor(w, h) {
|
|
966
1001
|
super(w, h);
|
|
1002
|
+
this.tonalRange = opts.tonalRange;
|
|
967
1003
|
this.center = Shape.randomPoint(w, h);
|
|
968
1004
|
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
969
1005
|
this.angle = rotatable ? Math.random() * (2 * Math.PI / sides) : startAngle;
|
|
@@ -1059,11 +1095,13 @@ function makeNGon(opts) {
|
|
|
1059
1095
|
static {
|
|
1060
1096
|
__name(this, "NGonIrregular");
|
|
1061
1097
|
}
|
|
1062
|
-
static _ngonOpts = { sides, regular: false, convex, sizeRange, mutationScale };
|
|
1098
|
+
static _ngonOpts = { sides, regular: false, convex, sizeRange, mutationScale, tonalRange: opts.tonalRange };
|
|
1063
1099
|
static _shapeSpec = { f: "ngon", o: NGonIrregular._ngonOpts };
|
|
1100
|
+
tonalRange;
|
|
1064
1101
|
points;
|
|
1065
1102
|
constructor(w, h) {
|
|
1066
1103
|
super(w, h);
|
|
1104
|
+
this.tonalRange = opts.tonalRange;
|
|
1067
1105
|
const first = Shape.randomPoint(w, h);
|
|
1068
1106
|
this.points = [first];
|
|
1069
1107
|
for (let i = 1; i < sides; i++) {
|
|
@@ -1135,14 +1173,16 @@ function makeRect(opts) {
|
|
|
1135
1173
|
static {
|
|
1136
1174
|
__name(this, "Rect");
|
|
1137
1175
|
}
|
|
1138
|
-
static _rectOpts = { widthRange, heightRange, aspectRatio, rotatable, mutationScale };
|
|
1176
|
+
static _rectOpts = { widthRange, heightRange, aspectRatio, rotatable, mutationScale, tonalRange: opts?.tonalRange };
|
|
1139
1177
|
static _shapeSpec = { f: "rect", o: Rect._rectOpts };
|
|
1178
|
+
tonalRange;
|
|
1140
1179
|
center;
|
|
1141
1180
|
hw;
|
|
1142
1181
|
hh;
|
|
1143
1182
|
angle;
|
|
1144
1183
|
constructor(w, h) {
|
|
1145
1184
|
super(w, h);
|
|
1185
|
+
this.tonalRange = opts?.tonalRange;
|
|
1146
1186
|
this.center = Shape.randomPoint(w, h);
|
|
1147
1187
|
this.hw = widthRange[0] + ~~(Math.random() * (widthRange[1] - widthRange[0]));
|
|
1148
1188
|
if (aspectRatio !== void 0) {
|
|
@@ -1248,6 +1288,229 @@ function makeRect(opts) {
|
|
|
1248
1288
|
return Rect;
|
|
1249
1289
|
}
|
|
1250
1290
|
__name(makeRect, "makeRect");
|
|
1291
|
+
function makeCircle(opts) {
|
|
1292
|
+
const sizeRange = opts?.sizeRange ?? [1, 20];
|
|
1293
|
+
const mutationScale = opts?.mutationScale ?? 20;
|
|
1294
|
+
class MadeCircle extends Shape {
|
|
1295
|
+
static {
|
|
1296
|
+
__name(this, "MadeCircle");
|
|
1297
|
+
}
|
|
1298
|
+
static _circleOpts = { sizeRange, mutationScale, tonalRange: opts?.tonalRange };
|
|
1299
|
+
static _shapeSpec = { f: "circle", o: MadeCircle._circleOpts };
|
|
1300
|
+
tonalRange;
|
|
1301
|
+
center;
|
|
1302
|
+
r;
|
|
1303
|
+
constructor(w, h) {
|
|
1304
|
+
super(w, h);
|
|
1305
|
+
this.tonalRange = opts?.tonalRange;
|
|
1306
|
+
this.center = Shape.randomPoint(w, h);
|
|
1307
|
+
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
1308
|
+
this.computeBbox();
|
|
1309
|
+
}
|
|
1310
|
+
computeBbox() {
|
|
1311
|
+
this.bbox = {
|
|
1312
|
+
left: this.center[0] - this.r,
|
|
1313
|
+
top: this.center[1] - this.r,
|
|
1314
|
+
width: 2 * this.r || 1,
|
|
1315
|
+
height: 2 * this.r || 1
|
|
1316
|
+
};
|
|
1317
|
+
return this;
|
|
1318
|
+
}
|
|
1319
|
+
render(ctx) {
|
|
1320
|
+
ctx.beginPath();
|
|
1321
|
+
ctx.arc(this.center[0], this.center[1], this.r, 0, 2 * Math.PI);
|
|
1322
|
+
ctx.fill();
|
|
1323
|
+
}
|
|
1324
|
+
toSVG() {
|
|
1325
|
+
const node = document.createElementNS(SVGNS, "circle");
|
|
1326
|
+
node.setAttribute("cx", String(this.center[0]));
|
|
1327
|
+
node.setAttribute("cy", String(this.center[1]));
|
|
1328
|
+
node.setAttribute("r", String(this.r));
|
|
1329
|
+
return node;
|
|
1330
|
+
}
|
|
1331
|
+
toData(a, c) {
|
|
1332
|
+
return { t: "c", a, c, cx: this.center[0], cy: this.center[1], r: this.r };
|
|
1333
|
+
}
|
|
1334
|
+
mutate(_cfg) {
|
|
1335
|
+
const clone = new MadeCircle(0, 0);
|
|
1336
|
+
clone.center = [this.center[0], this.center[1]];
|
|
1337
|
+
clone.r = this.r;
|
|
1338
|
+
switch (Math.floor(Math.random() * 2)) {
|
|
1339
|
+
case 0: {
|
|
1340
|
+
const angle = Math.random() * 2 * Math.PI;
|
|
1341
|
+
const d = Math.random() * mutationScale;
|
|
1342
|
+
clone.center[0] += ~~(d * Math.cos(angle));
|
|
1343
|
+
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1344
|
+
break;
|
|
1345
|
+
}
|
|
1346
|
+
case 1:
|
|
1347
|
+
clone.r += (Math.random() - 0.5) * mutationScale;
|
|
1348
|
+
clone.r = Math.max(1, ~~clone.r);
|
|
1349
|
+
break;
|
|
1350
|
+
}
|
|
1351
|
+
return clone.computeBbox();
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
return MadeCircle;
|
|
1355
|
+
}
|
|
1356
|
+
__name(makeCircle, "makeCircle");
|
|
1357
|
+
function makeEllipse(opts) {
|
|
1358
|
+
const rxRange = opts?.rxRange ?? [1, 20];
|
|
1359
|
+
const ryRange = opts?.ryRange ?? [1, 20];
|
|
1360
|
+
const aspectRatio = opts?.aspectRatio;
|
|
1361
|
+
const mutationScale = opts?.mutationScale ?? 20;
|
|
1362
|
+
class MadeEllipse extends Shape {
|
|
1363
|
+
static {
|
|
1364
|
+
__name(this, "MadeEllipse");
|
|
1365
|
+
}
|
|
1366
|
+
static _ellipseOpts = { rxRange, ryRange, aspectRatio, mutationScale, tonalRange: opts?.tonalRange };
|
|
1367
|
+
static _shapeSpec = { f: "ellipse", o: MadeEllipse._ellipseOpts };
|
|
1368
|
+
tonalRange;
|
|
1369
|
+
center;
|
|
1370
|
+
rx;
|
|
1371
|
+
ry;
|
|
1372
|
+
constructor(w, h) {
|
|
1373
|
+
super(w, h);
|
|
1374
|
+
this.tonalRange = opts?.tonalRange;
|
|
1375
|
+
this.center = Shape.randomPoint(w, h);
|
|
1376
|
+
this.rx = Math.max(1, rxRange[0] + ~~(Math.random() * (rxRange[1] - rxRange[0])));
|
|
1377
|
+
this.ry = aspectRatio !== void 0 ? Math.max(1, Math.round(this.rx / aspectRatio)) : Math.max(1, ryRange[0] + ~~(Math.random() * (ryRange[1] - ryRange[0])));
|
|
1378
|
+
this.computeBbox();
|
|
1379
|
+
}
|
|
1380
|
+
computeBbox() {
|
|
1381
|
+
this.bbox = {
|
|
1382
|
+
left: this.center[0] - this.rx,
|
|
1383
|
+
top: this.center[1] - this.ry,
|
|
1384
|
+
width: 2 * this.rx || 1,
|
|
1385
|
+
height: 2 * this.ry || 1
|
|
1386
|
+
};
|
|
1387
|
+
return this;
|
|
1388
|
+
}
|
|
1389
|
+
render(ctx) {
|
|
1390
|
+
ctx.beginPath();
|
|
1391
|
+
ctx.ellipse(this.center[0], this.center[1], this.rx, this.ry, 0, 0, 2 * Math.PI, false);
|
|
1392
|
+
ctx.fill();
|
|
1393
|
+
}
|
|
1394
|
+
toSVG() {
|
|
1395
|
+
const node = document.createElementNS(SVGNS, "ellipse");
|
|
1396
|
+
node.setAttribute("cx", String(this.center[0]));
|
|
1397
|
+
node.setAttribute("cy", String(this.center[1]));
|
|
1398
|
+
node.setAttribute("rx", String(this.rx));
|
|
1399
|
+
node.setAttribute("ry", String(this.ry));
|
|
1400
|
+
return node;
|
|
1401
|
+
}
|
|
1402
|
+
toData(a, c) {
|
|
1403
|
+
return { t: "e", a, c, cx: this.center[0], cy: this.center[1], rx: this.rx, ry: this.ry };
|
|
1404
|
+
}
|
|
1405
|
+
mutate(_cfg) {
|
|
1406
|
+
const clone = new MadeEllipse(0, 0);
|
|
1407
|
+
clone.center = [this.center[0], this.center[1]];
|
|
1408
|
+
clone.rx = this.rx;
|
|
1409
|
+
clone.ry = this.ry;
|
|
1410
|
+
const mutCount = aspectRatio === void 0 ? 3 : 2;
|
|
1411
|
+
switch (Math.floor(Math.random() * mutCount)) {
|
|
1412
|
+
case 0: {
|
|
1413
|
+
const angle = Math.random() * 2 * Math.PI;
|
|
1414
|
+
const d = Math.random() * mutationScale;
|
|
1415
|
+
clone.center[0] += ~~(d * Math.cos(angle));
|
|
1416
|
+
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
case 1:
|
|
1420
|
+
clone.rx += (Math.random() - 0.5) * mutationScale;
|
|
1421
|
+
clone.rx = Math.max(1, ~~clone.rx);
|
|
1422
|
+
if (aspectRatio !== void 0) {
|
|
1423
|
+
clone.ry = Math.max(1, Math.round(clone.rx / aspectRatio));
|
|
1424
|
+
}
|
|
1425
|
+
break;
|
|
1426
|
+
case 2:
|
|
1427
|
+
clone.ry += (Math.random() - 0.5) * mutationScale;
|
|
1428
|
+
clone.ry = Math.max(1, ~~clone.ry);
|
|
1429
|
+
break;
|
|
1430
|
+
}
|
|
1431
|
+
return clone.computeBbox();
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return MadeEllipse;
|
|
1435
|
+
}
|
|
1436
|
+
__name(makeEllipse, "makeEllipse");
|
|
1437
|
+
function makeGlyph(opts) {
|
|
1438
|
+
const char = opts?.char ?? "\u263A";
|
|
1439
|
+
const fontFamily = opts?.fontFamily ?? "sans-serif";
|
|
1440
|
+
const sizeRange = opts?.sizeRange ?? [10, 30];
|
|
1441
|
+
const mutationScale = opts?.mutationScale ?? 20;
|
|
1442
|
+
class MadeGlyph extends Shape {
|
|
1443
|
+
static {
|
|
1444
|
+
__name(this, "MadeGlyph");
|
|
1445
|
+
}
|
|
1446
|
+
static _glyphOpts = { char, fontFamily, sizeRange, mutationScale, tonalRange: opts?.tonalRange };
|
|
1447
|
+
static _shapeSpec = { f: "glyph", o: MadeGlyph._glyphOpts };
|
|
1448
|
+
tonalRange;
|
|
1449
|
+
center;
|
|
1450
|
+
fontSize;
|
|
1451
|
+
constructor(w, h) {
|
|
1452
|
+
super(w, h);
|
|
1453
|
+
this.tonalRange = opts?.tonalRange;
|
|
1454
|
+
this.center = Shape.randomPoint(w, h);
|
|
1455
|
+
this.fontSize = Math.max(sizeRange[0], sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
1456
|
+
this.computeBbox();
|
|
1457
|
+
}
|
|
1458
|
+
computeBbox() {
|
|
1459
|
+
const tmp = new Canvas(1, 1);
|
|
1460
|
+
tmp.ctx.font = `${this.fontSize}px ${fontFamily}`;
|
|
1461
|
+
const w = ~~tmp.ctx.measureText(char).width;
|
|
1462
|
+
this.bbox = {
|
|
1463
|
+
left: ~~(this.center[0] - w / 2),
|
|
1464
|
+
top: ~~(this.center[1] - this.fontSize / 2),
|
|
1465
|
+
width: w || 1,
|
|
1466
|
+
height: this.fontSize
|
|
1467
|
+
};
|
|
1468
|
+
return this;
|
|
1469
|
+
}
|
|
1470
|
+
render(ctx) {
|
|
1471
|
+
ctx.textAlign = "center";
|
|
1472
|
+
ctx.textBaseline = "middle";
|
|
1473
|
+
ctx.font = `${this.fontSize}px ${fontFamily}`;
|
|
1474
|
+
ctx.fillText(char, this.center[0], this.center[1]);
|
|
1475
|
+
}
|
|
1476
|
+
toSVG() {
|
|
1477
|
+
const text = document.createElementNS(SVGNS, "text");
|
|
1478
|
+
text.appendChild(document.createTextNode(char));
|
|
1479
|
+
text.setAttribute("text-anchor", "middle");
|
|
1480
|
+
text.setAttribute("dominant-baseline", "central");
|
|
1481
|
+
text.setAttribute("font-size", String(this.fontSize));
|
|
1482
|
+
text.setAttribute("font-family", fontFamily);
|
|
1483
|
+
text.setAttribute("x", String(this.center[0]));
|
|
1484
|
+
text.setAttribute("y", String(this.center[1]));
|
|
1485
|
+
return text;
|
|
1486
|
+
}
|
|
1487
|
+
toData(a, c) {
|
|
1488
|
+
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: char };
|
|
1489
|
+
}
|
|
1490
|
+
mutate(_cfg) {
|
|
1491
|
+
const clone = new MadeGlyph(0, 0);
|
|
1492
|
+
clone.center = [this.center[0], this.center[1]];
|
|
1493
|
+
clone.fontSize = this.fontSize;
|
|
1494
|
+
switch (Math.floor(Math.random() * 2)) {
|
|
1495
|
+
case 0: {
|
|
1496
|
+
const angle = Math.random() * 2 * Math.PI;
|
|
1497
|
+
const d = Math.random() * mutationScale;
|
|
1498
|
+
clone.center[0] += ~~(d * Math.cos(angle));
|
|
1499
|
+
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
case 1: {
|
|
1503
|
+
const delta = Math.round((Math.random() - 0.5) * (mutationScale * 0.2));
|
|
1504
|
+
clone.fontSize = Math.max(sizeRange[0], Math.min(sizeRange[1], clone.fontSize + delta));
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
return clone.computeBbox();
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
return MadeGlyph;
|
|
1512
|
+
}
|
|
1513
|
+
__name(makeGlyph, "makeGlyph");
|
|
1251
1514
|
var Debug = class extends Shape {
|
|
1252
1515
|
static {
|
|
1253
1516
|
__name(this, "Debug");
|
|
@@ -1291,12 +1554,14 @@ var Optimizer = class {
|
|
|
1291
1554
|
_paused;
|
|
1292
1555
|
_stepPlan;
|
|
1293
1556
|
_schedule;
|
|
1557
|
+
_rejectionStreak;
|
|
1294
1558
|
constructor(original, cfg, schedule = (fn) => requestAnimationFrame(fn)) {
|
|
1295
1559
|
this.cfg = cfg;
|
|
1296
1560
|
this.state = new State(original, Canvas.empty(cfg));
|
|
1297
1561
|
this._steps = 0;
|
|
1298
1562
|
this._stopped = false;
|
|
1299
1563
|
this._paused = false;
|
|
1564
|
+
this._rejectionStreak = 0;
|
|
1300
1565
|
this.onStep = () => {
|
|
1301
1566
|
};
|
|
1302
1567
|
this._stepPlan = buildStepPlan(cfg);
|
|
@@ -1320,7 +1585,13 @@ var Optimizer = class {
|
|
|
1320
1585
|
}
|
|
1321
1586
|
}
|
|
1322
1587
|
_addShape() {
|
|
1323
|
-
this._findBestStep().then((step) => step ? this._optimizeStep(step) :
|
|
1588
|
+
this._findBestStep().then((step) => step && step.distance < Infinity ? this._optimizeStep(step) : step).then((step) => {
|
|
1589
|
+
if (step && step.distance === Infinity && this._rejectionStreak < this.cfg.shapes) {
|
|
1590
|
+
this._rejectionStreak++;
|
|
1591
|
+
this._continue();
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
this._rejectionStreak = 0;
|
|
1324
1595
|
this._steps++;
|
|
1325
1596
|
if (step && step.distance < this.state.distance) {
|
|
1326
1597
|
this.state = step.apply(this.state);
|
|
@@ -1566,6 +1837,9 @@ export {
|
|
|
1566
1837
|
differenceToDistance,
|
|
1567
1838
|
distanceToDifference,
|
|
1568
1839
|
getFill,
|
|
1840
|
+
makeCircle,
|
|
1841
|
+
makeEllipse,
|
|
1842
|
+
makeGlyph,
|
|
1569
1843
|
makeNGon,
|
|
1570
1844
|
makeRect,
|
|
1571
1845
|
parseColor,
|