@slithy/prim-lib 0.5.0 → 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 +283 -7
- 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,7 +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 };
|
|
993
|
+
static _shapeSpec = { f: "ngon", o: NGonRegular._ngonOpts };
|
|
994
|
+
tonalRange;
|
|
959
995
|
center;
|
|
960
996
|
r;
|
|
961
997
|
angle;
|
|
@@ -963,6 +999,7 @@ function makeNGon(opts) {
|
|
|
963
999
|
_cachedPoints;
|
|
964
1000
|
constructor(w, h) {
|
|
965
1001
|
super(w, h);
|
|
1002
|
+
this.tonalRange = opts.tonalRange;
|
|
966
1003
|
this.center = Shape.randomPoint(w, h);
|
|
967
1004
|
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
968
1005
|
this.angle = rotatable ? Math.random() * (2 * Math.PI / sides) : startAngle;
|
|
@@ -1058,10 +1095,13 @@ function makeNGon(opts) {
|
|
|
1058
1095
|
static {
|
|
1059
1096
|
__name(this, "NGonIrregular");
|
|
1060
1097
|
}
|
|
1061
|
-
static _ngonOpts = { sides, regular: false, convex, sizeRange, mutationScale };
|
|
1098
|
+
static _ngonOpts = { sides, regular: false, convex, sizeRange, mutationScale, tonalRange: opts.tonalRange };
|
|
1099
|
+
static _shapeSpec = { f: "ngon", o: NGonIrregular._ngonOpts };
|
|
1100
|
+
tonalRange;
|
|
1062
1101
|
points;
|
|
1063
1102
|
constructor(w, h) {
|
|
1064
1103
|
super(w, h);
|
|
1104
|
+
this.tonalRange = opts.tonalRange;
|
|
1065
1105
|
const first = Shape.randomPoint(w, h);
|
|
1066
1106
|
this.points = [first];
|
|
1067
1107
|
for (let i = 1; i < sides; i++) {
|
|
@@ -1133,14 +1173,16 @@ function makeRect(opts) {
|
|
|
1133
1173
|
static {
|
|
1134
1174
|
__name(this, "Rect");
|
|
1135
1175
|
}
|
|
1136
|
-
static _rectOpts = { widthRange, heightRange, aspectRatio, rotatable, mutationScale };
|
|
1137
|
-
static _shapeSpec = { f: "rect" };
|
|
1176
|
+
static _rectOpts = { widthRange, heightRange, aspectRatio, rotatable, mutationScale, tonalRange: opts?.tonalRange };
|
|
1177
|
+
static _shapeSpec = { f: "rect", o: Rect._rectOpts };
|
|
1178
|
+
tonalRange;
|
|
1138
1179
|
center;
|
|
1139
1180
|
hw;
|
|
1140
1181
|
hh;
|
|
1141
1182
|
angle;
|
|
1142
1183
|
constructor(w, h) {
|
|
1143
1184
|
super(w, h);
|
|
1185
|
+
this.tonalRange = opts?.tonalRange;
|
|
1144
1186
|
this.center = Shape.randomPoint(w, h);
|
|
1145
1187
|
this.hw = widthRange[0] + ~~(Math.random() * (widthRange[1] - widthRange[0]));
|
|
1146
1188
|
if (aspectRatio !== void 0) {
|
|
@@ -1246,6 +1288,229 @@ function makeRect(opts) {
|
|
|
1246
1288
|
return Rect;
|
|
1247
1289
|
}
|
|
1248
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");
|
|
1249
1514
|
var Debug = class extends Shape {
|
|
1250
1515
|
static {
|
|
1251
1516
|
__name(this, "Debug");
|
|
@@ -1289,12 +1554,14 @@ var Optimizer = class {
|
|
|
1289
1554
|
_paused;
|
|
1290
1555
|
_stepPlan;
|
|
1291
1556
|
_schedule;
|
|
1557
|
+
_rejectionStreak;
|
|
1292
1558
|
constructor(original, cfg, schedule = (fn) => requestAnimationFrame(fn)) {
|
|
1293
1559
|
this.cfg = cfg;
|
|
1294
1560
|
this.state = new State(original, Canvas.empty(cfg));
|
|
1295
1561
|
this._steps = 0;
|
|
1296
1562
|
this._stopped = false;
|
|
1297
1563
|
this._paused = false;
|
|
1564
|
+
this._rejectionStreak = 0;
|
|
1298
1565
|
this.onStep = () => {
|
|
1299
1566
|
};
|
|
1300
1567
|
this._stepPlan = buildStepPlan(cfg);
|
|
@@ -1318,7 +1585,13 @@ var Optimizer = class {
|
|
|
1318
1585
|
}
|
|
1319
1586
|
}
|
|
1320
1587
|
_addShape() {
|
|
1321
|
-
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;
|
|
1322
1595
|
this._steps++;
|
|
1323
1596
|
if (step && step.distance < this.state.distance) {
|
|
1324
1597
|
this.state = step.apply(this.state);
|
|
@@ -1564,6 +1837,9 @@ export {
|
|
|
1564
1837
|
differenceToDistance,
|
|
1565
1838
|
distanceToDifference,
|
|
1566
1839
|
getFill,
|
|
1840
|
+
makeCircle,
|
|
1841
|
+
makeEllipse,
|
|
1842
|
+
makeGlyph,
|
|
1567
1843
|
makeNGon,
|
|
1568
1844
|
makeRect,
|
|
1569
1845
|
parseColor,
|