@slithy/prim-lib 0.5.1 → 0.7.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 +47 -1
- package/dist/index.js +354 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ interface ShapeImageData {
|
|
|
23
23
|
*/
|
|
24
24
|
interface ShapeInterface {
|
|
25
25
|
bbox: Bbox;
|
|
26
|
+
tonalRange?: [number, number];
|
|
27
|
+
saturationRange?: [number, number];
|
|
28
|
+
hueCenter?: number;
|
|
29
|
+
hueTolerance?: number;
|
|
26
30
|
/** cfg is rarely used by implementations; accepts Partial to keep call sites flexible. */
|
|
27
31
|
mutate(cfg?: Partial<Cfg>): ShapeInterface;
|
|
28
32
|
toSVG(): SVGElement | undefined;
|
|
@@ -43,6 +47,7 @@ interface Cfg {
|
|
|
43
47
|
mutations: number;
|
|
44
48
|
steps: number;
|
|
45
49
|
fill: 'auto' | string;
|
|
50
|
+
outputFill?: string;
|
|
46
51
|
computeSize: number;
|
|
47
52
|
viewSize: number;
|
|
48
53
|
allowUpscale?: boolean;
|
|
@@ -193,6 +198,7 @@ declare class Optimizer {
|
|
|
193
198
|
_paused: boolean;
|
|
194
199
|
_stepPlan: ShapeCtor[];
|
|
195
200
|
_schedule: (fn: () => void) => void;
|
|
201
|
+
_rejectionStreak: number;
|
|
196
202
|
constructor(original: Canvas, cfg: Cfg, schedule?: (fn: () => void) => void);
|
|
197
203
|
start(): void;
|
|
198
204
|
stop(): void;
|
|
@@ -303,6 +309,10 @@ interface NGonOptions {
|
|
|
303
309
|
noise?: number;
|
|
304
310
|
sizeRange?: [number, number];
|
|
305
311
|
mutationScale?: number;
|
|
312
|
+
tonalRange?: [number, number];
|
|
313
|
+
saturationRange?: [number, number];
|
|
314
|
+
hueCenter?: number;
|
|
315
|
+
hueTolerance?: number;
|
|
306
316
|
}
|
|
307
317
|
declare function makeNGon(opts: NGonOptions): new (w: number, h: number) => ShapeInterface;
|
|
308
318
|
interface RectOptions {
|
|
@@ -311,14 +321,50 @@ interface RectOptions {
|
|
|
311
321
|
aspectRatio?: number;
|
|
312
322
|
rotatable?: boolean;
|
|
313
323
|
mutationScale?: number;
|
|
324
|
+
tonalRange?: [number, number];
|
|
325
|
+
saturationRange?: [number, number];
|
|
326
|
+
hueCenter?: number;
|
|
327
|
+
hueTolerance?: number;
|
|
314
328
|
}
|
|
315
329
|
declare function makeRect(opts?: Partial<RectOptions>): new (w: number, h: number) => ShapeInterface;
|
|
330
|
+
interface CircleOptions {
|
|
331
|
+
sizeRange?: [number, number];
|
|
332
|
+
mutationScale?: number;
|
|
333
|
+
tonalRange?: [number, number];
|
|
334
|
+
saturationRange?: [number, number];
|
|
335
|
+
hueCenter?: number;
|
|
336
|
+
hueTolerance?: number;
|
|
337
|
+
}
|
|
338
|
+
declare function makeCircle(opts?: Partial<CircleOptions>): new (w: number, h: number) => ShapeInterface;
|
|
339
|
+
interface EllipseOptions {
|
|
340
|
+
rxRange?: [number, number];
|
|
341
|
+
ryRange?: [number, number];
|
|
342
|
+
aspectRatio?: number;
|
|
343
|
+
mutationScale?: number;
|
|
344
|
+
tonalRange?: [number, number];
|
|
345
|
+
saturationRange?: [number, number];
|
|
346
|
+
hueCenter?: number;
|
|
347
|
+
hueTolerance?: number;
|
|
348
|
+
}
|
|
349
|
+
declare function makeEllipse(opts?: Partial<EllipseOptions>): new (w: number, h: number) => ShapeInterface;
|
|
350
|
+
interface GlyphOptions {
|
|
351
|
+
char?: string;
|
|
352
|
+
fontFamily?: string;
|
|
353
|
+
sizeRange?: [number, number];
|
|
354
|
+
mutationScale?: number;
|
|
355
|
+
tonalRange?: [number, number];
|
|
356
|
+
saturationRange?: [number, number];
|
|
357
|
+
hueCenter?: number;
|
|
358
|
+
hueTolerance?: number;
|
|
359
|
+
}
|
|
360
|
+
declare function makeGlyph(opts?: Partial<GlyphOptions>): new (w: number, h: number) => ShapeInterface;
|
|
316
361
|
declare class Debug extends Shape {
|
|
317
362
|
constructor(w: number, h: number);
|
|
318
363
|
render(ctx: CanvasRenderingContext2D): void;
|
|
319
364
|
}
|
|
320
365
|
|
|
321
366
|
declare const SVGNS = "http://www.w3.org/2000/svg";
|
|
367
|
+
declare function rgbToHsl(r: number, g: number, b: number): [number, number, number];
|
|
322
368
|
declare function parseColor(color: string): [number, number, number];
|
|
323
369
|
|
|
324
370
|
declare function clamp(x: number, min: number, max: number): number;
|
|
@@ -341,4 +387,4 @@ interface ReplayResult {
|
|
|
341
387
|
}
|
|
342
388
|
declare function replayOutput(data: SerializedOutput): ReplayResult;
|
|
343
389
|
|
|
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 };
|
|
390
|
+
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, rgbToHsl, stepDataToSVGElement, stepPerf };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,25 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
3
3
|
|
|
4
4
|
// src/util.ts
|
|
5
5
|
var SVGNS = "http://www.w3.org/2000/svg";
|
|
6
|
+
function rgbToHsl(r, g, b) {
|
|
7
|
+
const rn = r / 255, gn = g / 255, bn = b / 255;
|
|
8
|
+
const max = Math.max(rn, gn, bn);
|
|
9
|
+
const min = Math.min(rn, gn, bn);
|
|
10
|
+
const l = (max + min) / 2;
|
|
11
|
+
if (max === min) return [0, 0, l * 100];
|
|
12
|
+
const d = max - min;
|
|
13
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
14
|
+
let h;
|
|
15
|
+
if (max === rn) {
|
|
16
|
+
h = (gn - bn) / d + (gn < bn ? 6 : 0);
|
|
17
|
+
} else if (max === gn) {
|
|
18
|
+
h = (bn - rn) / d + 2;
|
|
19
|
+
} else {
|
|
20
|
+
h = (rn - gn) / d + 4;
|
|
21
|
+
}
|
|
22
|
+
return [h * 60, s * 100, l * 100];
|
|
23
|
+
}
|
|
24
|
+
__name(rgbToHsl, "rgbToHsl");
|
|
6
25
|
function parseColor(color) {
|
|
7
26
|
const m = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
8
27
|
if (!m) throw new Error(`Cannot parse color: ${color}`);
|
|
@@ -169,7 +188,7 @@ var Canvas = class _Canvas {
|
|
|
169
188
|
}
|
|
170
189
|
static empty(cfg, svg) {
|
|
171
190
|
if (svg) {
|
|
172
|
-
return this.svgRoot(cfg.width, cfg.height, cfg.fill);
|
|
191
|
+
return this.svgRoot(cfg.width, cfg.height, cfg.outputFill ?? cfg.fill);
|
|
173
192
|
} else {
|
|
174
193
|
return new this(cfg.width, cfg.height).fill(cfg.fill);
|
|
175
194
|
}
|
|
@@ -195,7 +214,11 @@ var Canvas = class _Canvas {
|
|
|
195
214
|
const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
196
215
|
const canvas = this.empty(fullCfg);
|
|
197
216
|
canvas.ctx.drawImage(img, 0, 0, fullCfg.width, fullCfg.height);
|
|
198
|
-
if (cfg.fill
|
|
217
|
+
if (cfg.fill === "transparent") {
|
|
218
|
+
cfg.outputFill = "transparent";
|
|
219
|
+
cfg.fill = "auto";
|
|
220
|
+
}
|
|
221
|
+
if (cfg.fill === "auto") {
|
|
199
222
|
cfg.fill = getFill(canvas.getImageData());
|
|
200
223
|
}
|
|
201
224
|
resolve(canvas);
|
|
@@ -217,6 +240,10 @@ var Canvas = class _Canvas {
|
|
|
217
240
|
const fullCfg = { ...cfg, width: cfg.width, height: cfg.height };
|
|
218
241
|
const canvas = this.empty(fullCfg);
|
|
219
242
|
canvas.ctx.drawImage(bitmap, 0, 0, fullCfg.width, fullCfg.height);
|
|
243
|
+
if (cfg.fill === "transparent") {
|
|
244
|
+
cfg.outputFill = "transparent";
|
|
245
|
+
cfg.fill = "auto";
|
|
246
|
+
}
|
|
220
247
|
if (cfg.fill === "auto") {
|
|
221
248
|
cfg.fill = getFill(canvas.getImageData());
|
|
222
249
|
}
|
|
@@ -355,6 +382,50 @@ var Step = class _Step {
|
|
|
355
382
|
compute(state) {
|
|
356
383
|
const pixels = state.canvas.node.width * state.canvas.node.height;
|
|
357
384
|
const offset = this.shape.bbox;
|
|
385
|
+
const { tonalRange, saturationRange, hueCenter, hueTolerance } = this.shape;
|
|
386
|
+
if (tonalRange || saturationRange || hueCenter !== void 0 && hueTolerance !== void 0) {
|
|
387
|
+
const { left, top, width, height } = offset;
|
|
388
|
+
const targetData = state.target.getImageData();
|
|
389
|
+
const fw = targetData.width;
|
|
390
|
+
const fh = targetData.height;
|
|
391
|
+
let rSum = 0, gSum = 0, bSum = 0, count = 0;
|
|
392
|
+
for (let sy = 0; sy < height; sy++) {
|
|
393
|
+
const fy = top + sy;
|
|
394
|
+
if (fy < 0 || fy >= fh) continue;
|
|
395
|
+
for (let sx = 0; sx < width; sx++) {
|
|
396
|
+
const fx = left + sx;
|
|
397
|
+
if (fx < 0 || fx >= fw) continue;
|
|
398
|
+
const i = 4 * (fx + fy * fw);
|
|
399
|
+
rSum += targetData.data[i];
|
|
400
|
+
gSum += targetData.data[i + 1];
|
|
401
|
+
bSum += targetData.data[i + 2];
|
|
402
|
+
count++;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (count > 0) {
|
|
406
|
+
if (tonalRange) {
|
|
407
|
+
const luma = (0.299 * rSum + 0.587 * gSum + 0.114 * bSum) / count;
|
|
408
|
+
if (luma < tonalRange[0] || luma > tonalRange[1]) {
|
|
409
|
+
this.distance = Infinity;
|
|
410
|
+
return Promise.resolve(this);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (saturationRange || hueCenter !== void 0 && hueTolerance !== void 0) {
|
|
414
|
+
const [h, s] = rgbToHsl(rSum / count, gSum / count, bSum / count);
|
|
415
|
+
if (saturationRange && (s < saturationRange[0] || s > saturationRange[1])) {
|
|
416
|
+
this.distance = Infinity;
|
|
417
|
+
return Promise.resolve(this);
|
|
418
|
+
}
|
|
419
|
+
if (hueCenter !== void 0 && hueTolerance !== void 0) {
|
|
420
|
+
const diff = Math.abs((h - hueCenter + 180 + 360) % 360 - 180);
|
|
421
|
+
if (diff > hueTolerance) {
|
|
422
|
+
this.distance = Infinity;
|
|
423
|
+
return Promise.resolve(this);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
358
429
|
const t0 = performance.now();
|
|
359
430
|
const shapeImageData = this.shape.rasterize(this.alpha).getImageData();
|
|
360
431
|
const t1 = performance.now();
|
|
@@ -955,8 +1026,12 @@ function makeNGon(opts) {
|
|
|
955
1026
|
static {
|
|
956
1027
|
__name(this, "NGonRegular");
|
|
957
1028
|
}
|
|
958
|
-
static _ngonOpts = { sides, regular: true, rotatable, noise, sizeRange, mutationScale, startAngle };
|
|
1029
|
+
static _ngonOpts = { sides, regular: true, rotatable, noise, sizeRange, mutationScale, startAngle, tonalRange: opts.tonalRange, saturationRange: opts.saturationRange, hueCenter: opts.hueCenter, hueTolerance: opts.hueTolerance };
|
|
959
1030
|
static _shapeSpec = { f: "ngon", o: NGonRegular._ngonOpts };
|
|
1031
|
+
tonalRange;
|
|
1032
|
+
saturationRange;
|
|
1033
|
+
hueCenter;
|
|
1034
|
+
hueTolerance;
|
|
960
1035
|
center;
|
|
961
1036
|
r;
|
|
962
1037
|
angle;
|
|
@@ -964,6 +1039,10 @@ function makeNGon(opts) {
|
|
|
964
1039
|
_cachedPoints;
|
|
965
1040
|
constructor(w, h) {
|
|
966
1041
|
super(w, h);
|
|
1042
|
+
this.tonalRange = opts.tonalRange;
|
|
1043
|
+
this.saturationRange = opts.saturationRange;
|
|
1044
|
+
this.hueCenter = opts.hueCenter;
|
|
1045
|
+
this.hueTolerance = opts.hueTolerance;
|
|
967
1046
|
this.center = Shape.randomPoint(w, h);
|
|
968
1047
|
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
969
1048
|
this.angle = rotatable ? Math.random() * (2 * Math.PI / sides) : startAngle;
|
|
@@ -1059,11 +1138,19 @@ function makeNGon(opts) {
|
|
|
1059
1138
|
static {
|
|
1060
1139
|
__name(this, "NGonIrregular");
|
|
1061
1140
|
}
|
|
1062
|
-
static _ngonOpts = { sides, regular: false, convex, sizeRange, mutationScale };
|
|
1141
|
+
static _ngonOpts = { sides, regular: false, convex, sizeRange, mutationScale, tonalRange: opts.tonalRange, saturationRange: opts.saturationRange, hueCenter: opts.hueCenter, hueTolerance: opts.hueTolerance };
|
|
1063
1142
|
static _shapeSpec = { f: "ngon", o: NGonIrregular._ngonOpts };
|
|
1143
|
+
tonalRange;
|
|
1144
|
+
saturationRange;
|
|
1145
|
+
hueCenter;
|
|
1146
|
+
hueTolerance;
|
|
1064
1147
|
points;
|
|
1065
1148
|
constructor(w, h) {
|
|
1066
1149
|
super(w, h);
|
|
1150
|
+
this.tonalRange = opts.tonalRange;
|
|
1151
|
+
this.saturationRange = opts.saturationRange;
|
|
1152
|
+
this.hueCenter = opts.hueCenter;
|
|
1153
|
+
this.hueTolerance = opts.hueTolerance;
|
|
1067
1154
|
const first = Shape.randomPoint(w, h);
|
|
1068
1155
|
this.points = [first];
|
|
1069
1156
|
for (let i = 1; i < sides; i++) {
|
|
@@ -1135,14 +1222,22 @@ function makeRect(opts) {
|
|
|
1135
1222
|
static {
|
|
1136
1223
|
__name(this, "Rect");
|
|
1137
1224
|
}
|
|
1138
|
-
static _rectOpts = { widthRange, heightRange, aspectRatio, rotatable, mutationScale };
|
|
1225
|
+
static _rectOpts = { widthRange, heightRange, aspectRatio, rotatable, mutationScale, tonalRange: opts?.tonalRange, saturationRange: opts?.saturationRange, hueCenter: opts?.hueCenter, hueTolerance: opts?.hueTolerance };
|
|
1139
1226
|
static _shapeSpec = { f: "rect", o: Rect._rectOpts };
|
|
1227
|
+
tonalRange;
|
|
1228
|
+
saturationRange;
|
|
1229
|
+
hueCenter;
|
|
1230
|
+
hueTolerance;
|
|
1140
1231
|
center;
|
|
1141
1232
|
hw;
|
|
1142
1233
|
hh;
|
|
1143
1234
|
angle;
|
|
1144
1235
|
constructor(w, h) {
|
|
1145
1236
|
super(w, h);
|
|
1237
|
+
this.tonalRange = opts?.tonalRange;
|
|
1238
|
+
this.saturationRange = opts?.saturationRange;
|
|
1239
|
+
this.hueCenter = opts?.hueCenter;
|
|
1240
|
+
this.hueTolerance = opts?.hueTolerance;
|
|
1146
1241
|
this.center = Shape.randomPoint(w, h);
|
|
1147
1242
|
this.hw = widthRange[0] + ~~(Math.random() * (widthRange[1] - widthRange[0]));
|
|
1148
1243
|
if (aspectRatio !== void 0) {
|
|
@@ -1248,6 +1343,247 @@ function makeRect(opts) {
|
|
|
1248
1343
|
return Rect;
|
|
1249
1344
|
}
|
|
1250
1345
|
__name(makeRect, "makeRect");
|
|
1346
|
+
function makeCircle(opts) {
|
|
1347
|
+
const sizeRange = opts?.sizeRange ?? [1, 20];
|
|
1348
|
+
const mutationScale = opts?.mutationScale ?? 20;
|
|
1349
|
+
class MadeCircle extends Shape {
|
|
1350
|
+
static {
|
|
1351
|
+
__name(this, "MadeCircle");
|
|
1352
|
+
}
|
|
1353
|
+
static _circleOpts = { sizeRange, mutationScale, tonalRange: opts?.tonalRange, saturationRange: opts?.saturationRange, hueCenter: opts?.hueCenter, hueTolerance: opts?.hueTolerance };
|
|
1354
|
+
static _shapeSpec = { f: "circle", o: MadeCircle._circleOpts };
|
|
1355
|
+
tonalRange;
|
|
1356
|
+
saturationRange;
|
|
1357
|
+
hueCenter;
|
|
1358
|
+
hueTolerance;
|
|
1359
|
+
center;
|
|
1360
|
+
r;
|
|
1361
|
+
constructor(w, h) {
|
|
1362
|
+
super(w, h);
|
|
1363
|
+
this.tonalRange = opts?.tonalRange;
|
|
1364
|
+
this.saturationRange = opts?.saturationRange;
|
|
1365
|
+
this.hueCenter = opts?.hueCenter;
|
|
1366
|
+
this.hueTolerance = opts?.hueTolerance;
|
|
1367
|
+
this.center = Shape.randomPoint(w, h);
|
|
1368
|
+
this.r = Math.max(1, sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
1369
|
+
this.computeBbox();
|
|
1370
|
+
}
|
|
1371
|
+
computeBbox() {
|
|
1372
|
+
this.bbox = {
|
|
1373
|
+
left: this.center[0] - this.r,
|
|
1374
|
+
top: this.center[1] - this.r,
|
|
1375
|
+
width: 2 * this.r || 1,
|
|
1376
|
+
height: 2 * this.r || 1
|
|
1377
|
+
};
|
|
1378
|
+
return this;
|
|
1379
|
+
}
|
|
1380
|
+
render(ctx) {
|
|
1381
|
+
ctx.beginPath();
|
|
1382
|
+
ctx.arc(this.center[0], this.center[1], this.r, 0, 2 * Math.PI);
|
|
1383
|
+
ctx.fill();
|
|
1384
|
+
}
|
|
1385
|
+
toSVG() {
|
|
1386
|
+
const node = document.createElementNS(SVGNS, "circle");
|
|
1387
|
+
node.setAttribute("cx", String(this.center[0]));
|
|
1388
|
+
node.setAttribute("cy", String(this.center[1]));
|
|
1389
|
+
node.setAttribute("r", String(this.r));
|
|
1390
|
+
return node;
|
|
1391
|
+
}
|
|
1392
|
+
toData(a, c) {
|
|
1393
|
+
return { t: "c", a, c, cx: this.center[0], cy: this.center[1], r: this.r };
|
|
1394
|
+
}
|
|
1395
|
+
mutate(_cfg) {
|
|
1396
|
+
const clone = new MadeCircle(0, 0);
|
|
1397
|
+
clone.center = [this.center[0], this.center[1]];
|
|
1398
|
+
clone.r = this.r;
|
|
1399
|
+
switch (Math.floor(Math.random() * 2)) {
|
|
1400
|
+
case 0: {
|
|
1401
|
+
const angle = Math.random() * 2 * Math.PI;
|
|
1402
|
+
const d = Math.random() * mutationScale;
|
|
1403
|
+
clone.center[0] += ~~(d * Math.cos(angle));
|
|
1404
|
+
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1405
|
+
break;
|
|
1406
|
+
}
|
|
1407
|
+
case 1:
|
|
1408
|
+
clone.r += (Math.random() - 0.5) * mutationScale;
|
|
1409
|
+
clone.r = Math.max(1, ~~clone.r);
|
|
1410
|
+
break;
|
|
1411
|
+
}
|
|
1412
|
+
return clone.computeBbox();
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return MadeCircle;
|
|
1416
|
+
}
|
|
1417
|
+
__name(makeCircle, "makeCircle");
|
|
1418
|
+
function makeEllipse(opts) {
|
|
1419
|
+
const rxRange = opts?.rxRange ?? [1, 20];
|
|
1420
|
+
const ryRange = opts?.ryRange ?? [1, 20];
|
|
1421
|
+
const aspectRatio = opts?.aspectRatio;
|
|
1422
|
+
const mutationScale = opts?.mutationScale ?? 20;
|
|
1423
|
+
class MadeEllipse extends Shape {
|
|
1424
|
+
static {
|
|
1425
|
+
__name(this, "MadeEllipse");
|
|
1426
|
+
}
|
|
1427
|
+
static _ellipseOpts = { rxRange, ryRange, aspectRatio, mutationScale, tonalRange: opts?.tonalRange, saturationRange: opts?.saturationRange, hueCenter: opts?.hueCenter, hueTolerance: opts?.hueTolerance };
|
|
1428
|
+
static _shapeSpec = { f: "ellipse", o: MadeEllipse._ellipseOpts };
|
|
1429
|
+
tonalRange;
|
|
1430
|
+
saturationRange;
|
|
1431
|
+
hueCenter;
|
|
1432
|
+
hueTolerance;
|
|
1433
|
+
center;
|
|
1434
|
+
rx;
|
|
1435
|
+
ry;
|
|
1436
|
+
constructor(w, h) {
|
|
1437
|
+
super(w, h);
|
|
1438
|
+
this.tonalRange = opts?.tonalRange;
|
|
1439
|
+
this.saturationRange = opts?.saturationRange;
|
|
1440
|
+
this.hueCenter = opts?.hueCenter;
|
|
1441
|
+
this.hueTolerance = opts?.hueTolerance;
|
|
1442
|
+
this.center = Shape.randomPoint(w, h);
|
|
1443
|
+
this.rx = Math.max(1, rxRange[0] + ~~(Math.random() * (rxRange[1] - rxRange[0])));
|
|
1444
|
+
this.ry = aspectRatio !== void 0 ? Math.max(1, Math.round(this.rx / aspectRatio)) : Math.max(1, ryRange[0] + ~~(Math.random() * (ryRange[1] - ryRange[0])));
|
|
1445
|
+
this.computeBbox();
|
|
1446
|
+
}
|
|
1447
|
+
computeBbox() {
|
|
1448
|
+
this.bbox = {
|
|
1449
|
+
left: this.center[0] - this.rx,
|
|
1450
|
+
top: this.center[1] - this.ry,
|
|
1451
|
+
width: 2 * this.rx || 1,
|
|
1452
|
+
height: 2 * this.ry || 1
|
|
1453
|
+
};
|
|
1454
|
+
return this;
|
|
1455
|
+
}
|
|
1456
|
+
render(ctx) {
|
|
1457
|
+
ctx.beginPath();
|
|
1458
|
+
ctx.ellipse(this.center[0], this.center[1], this.rx, this.ry, 0, 0, 2 * Math.PI, false);
|
|
1459
|
+
ctx.fill();
|
|
1460
|
+
}
|
|
1461
|
+
toSVG() {
|
|
1462
|
+
const node = document.createElementNS(SVGNS, "ellipse");
|
|
1463
|
+
node.setAttribute("cx", String(this.center[0]));
|
|
1464
|
+
node.setAttribute("cy", String(this.center[1]));
|
|
1465
|
+
node.setAttribute("rx", String(this.rx));
|
|
1466
|
+
node.setAttribute("ry", String(this.ry));
|
|
1467
|
+
return node;
|
|
1468
|
+
}
|
|
1469
|
+
toData(a, c) {
|
|
1470
|
+
return { t: "e", a, c, cx: this.center[0], cy: this.center[1], rx: this.rx, ry: this.ry };
|
|
1471
|
+
}
|
|
1472
|
+
mutate(_cfg) {
|
|
1473
|
+
const clone = new MadeEllipse(0, 0);
|
|
1474
|
+
clone.center = [this.center[0], this.center[1]];
|
|
1475
|
+
clone.rx = this.rx;
|
|
1476
|
+
clone.ry = this.ry;
|
|
1477
|
+
const mutCount = aspectRatio === void 0 ? 3 : 2;
|
|
1478
|
+
switch (Math.floor(Math.random() * mutCount)) {
|
|
1479
|
+
case 0: {
|
|
1480
|
+
const angle = Math.random() * 2 * Math.PI;
|
|
1481
|
+
const d = Math.random() * mutationScale;
|
|
1482
|
+
clone.center[0] += ~~(d * Math.cos(angle));
|
|
1483
|
+
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1484
|
+
break;
|
|
1485
|
+
}
|
|
1486
|
+
case 1:
|
|
1487
|
+
clone.rx += (Math.random() - 0.5) * mutationScale;
|
|
1488
|
+
clone.rx = Math.max(1, ~~clone.rx);
|
|
1489
|
+
if (aspectRatio !== void 0) {
|
|
1490
|
+
clone.ry = Math.max(1, Math.round(clone.rx / aspectRatio));
|
|
1491
|
+
}
|
|
1492
|
+
break;
|
|
1493
|
+
case 2:
|
|
1494
|
+
clone.ry += (Math.random() - 0.5) * mutationScale;
|
|
1495
|
+
clone.ry = Math.max(1, ~~clone.ry);
|
|
1496
|
+
break;
|
|
1497
|
+
}
|
|
1498
|
+
return clone.computeBbox();
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return MadeEllipse;
|
|
1502
|
+
}
|
|
1503
|
+
__name(makeEllipse, "makeEllipse");
|
|
1504
|
+
function makeGlyph(opts) {
|
|
1505
|
+
const char = opts?.char ?? "\u263A";
|
|
1506
|
+
const fontFamily = opts?.fontFamily ?? "sans-serif";
|
|
1507
|
+
const sizeRange = opts?.sizeRange ?? [10, 30];
|
|
1508
|
+
const mutationScale = opts?.mutationScale ?? 20;
|
|
1509
|
+
class MadeGlyph extends Shape {
|
|
1510
|
+
static {
|
|
1511
|
+
__name(this, "MadeGlyph");
|
|
1512
|
+
}
|
|
1513
|
+
static _glyphOpts = { char, fontFamily, sizeRange, mutationScale, tonalRange: opts?.tonalRange, saturationRange: opts?.saturationRange, hueCenter: opts?.hueCenter, hueTolerance: opts?.hueTolerance };
|
|
1514
|
+
static _shapeSpec = { f: "glyph", o: MadeGlyph._glyphOpts };
|
|
1515
|
+
tonalRange;
|
|
1516
|
+
saturationRange;
|
|
1517
|
+
hueCenter;
|
|
1518
|
+
hueTolerance;
|
|
1519
|
+
center;
|
|
1520
|
+
fontSize;
|
|
1521
|
+
constructor(w, h) {
|
|
1522
|
+
super(w, h);
|
|
1523
|
+
this.tonalRange = opts?.tonalRange;
|
|
1524
|
+
this.saturationRange = opts?.saturationRange;
|
|
1525
|
+
this.hueCenter = opts?.hueCenter;
|
|
1526
|
+
this.hueTolerance = opts?.hueTolerance;
|
|
1527
|
+
this.center = Shape.randomPoint(w, h);
|
|
1528
|
+
this.fontSize = Math.max(sizeRange[0], sizeRange[0] + ~~(Math.random() * (sizeRange[1] - sizeRange[0])));
|
|
1529
|
+
this.computeBbox();
|
|
1530
|
+
}
|
|
1531
|
+
computeBbox() {
|
|
1532
|
+
const tmp = new Canvas(1, 1);
|
|
1533
|
+
tmp.ctx.font = `${this.fontSize}px ${fontFamily}`;
|
|
1534
|
+
const w = ~~tmp.ctx.measureText(char).width;
|
|
1535
|
+
this.bbox = {
|
|
1536
|
+
left: ~~(this.center[0] - w / 2),
|
|
1537
|
+
top: ~~(this.center[1] - this.fontSize / 2),
|
|
1538
|
+
width: w || 1,
|
|
1539
|
+
height: this.fontSize
|
|
1540
|
+
};
|
|
1541
|
+
return this;
|
|
1542
|
+
}
|
|
1543
|
+
render(ctx) {
|
|
1544
|
+
ctx.textAlign = "center";
|
|
1545
|
+
ctx.textBaseline = "middle";
|
|
1546
|
+
ctx.font = `${this.fontSize}px ${fontFamily}`;
|
|
1547
|
+
ctx.fillText(char, this.center[0], this.center[1]);
|
|
1548
|
+
}
|
|
1549
|
+
toSVG() {
|
|
1550
|
+
const text = document.createElementNS(SVGNS, "text");
|
|
1551
|
+
text.appendChild(document.createTextNode(char));
|
|
1552
|
+
text.setAttribute("text-anchor", "middle");
|
|
1553
|
+
text.setAttribute("dominant-baseline", "central");
|
|
1554
|
+
text.setAttribute("font-size", String(this.fontSize));
|
|
1555
|
+
text.setAttribute("font-family", fontFamily);
|
|
1556
|
+
text.setAttribute("x", String(this.center[0]));
|
|
1557
|
+
text.setAttribute("y", String(this.center[1]));
|
|
1558
|
+
return text;
|
|
1559
|
+
}
|
|
1560
|
+
toData(a, c) {
|
|
1561
|
+
return { t: "sm", a, c, cx: this.center[0], cy: this.center[1], fs: this.fontSize, text: char };
|
|
1562
|
+
}
|
|
1563
|
+
mutate(_cfg) {
|
|
1564
|
+
const clone = new MadeGlyph(0, 0);
|
|
1565
|
+
clone.center = [this.center[0], this.center[1]];
|
|
1566
|
+
clone.fontSize = this.fontSize;
|
|
1567
|
+
switch (Math.floor(Math.random() * 2)) {
|
|
1568
|
+
case 0: {
|
|
1569
|
+
const angle = Math.random() * 2 * Math.PI;
|
|
1570
|
+
const d = Math.random() * mutationScale;
|
|
1571
|
+
clone.center[0] += ~~(d * Math.cos(angle));
|
|
1572
|
+
clone.center[1] += ~~(d * Math.sin(angle));
|
|
1573
|
+
break;
|
|
1574
|
+
}
|
|
1575
|
+
case 1: {
|
|
1576
|
+
const delta = Math.round((Math.random() - 0.5) * (mutationScale * 0.2));
|
|
1577
|
+
clone.fontSize = Math.max(sizeRange[0], Math.min(sizeRange[1], clone.fontSize + delta));
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
return clone.computeBbox();
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return MadeGlyph;
|
|
1585
|
+
}
|
|
1586
|
+
__name(makeGlyph, "makeGlyph");
|
|
1251
1587
|
var Debug = class extends Shape {
|
|
1252
1588
|
static {
|
|
1253
1589
|
__name(this, "Debug");
|
|
@@ -1291,12 +1627,14 @@ var Optimizer = class {
|
|
|
1291
1627
|
_paused;
|
|
1292
1628
|
_stepPlan;
|
|
1293
1629
|
_schedule;
|
|
1630
|
+
_rejectionStreak;
|
|
1294
1631
|
constructor(original, cfg, schedule = (fn) => requestAnimationFrame(fn)) {
|
|
1295
1632
|
this.cfg = cfg;
|
|
1296
1633
|
this.state = new State(original, Canvas.empty(cfg));
|
|
1297
1634
|
this._steps = 0;
|
|
1298
1635
|
this._stopped = false;
|
|
1299
1636
|
this._paused = false;
|
|
1637
|
+
this._rejectionStreak = 0;
|
|
1300
1638
|
this.onStep = () => {
|
|
1301
1639
|
};
|
|
1302
1640
|
this._stepPlan = buildStepPlan(cfg);
|
|
@@ -1320,7 +1658,13 @@ var Optimizer = class {
|
|
|
1320
1658
|
}
|
|
1321
1659
|
}
|
|
1322
1660
|
_addShape() {
|
|
1323
|
-
this._findBestStep().then((step) => step ? this._optimizeStep(step) :
|
|
1661
|
+
this._findBestStep().then((step) => step && step.distance < Infinity ? this._optimizeStep(step) : step).then((step) => {
|
|
1662
|
+
if (step && step.distance === Infinity && this._rejectionStreak < this.cfg.shapes) {
|
|
1663
|
+
this._rejectionStreak++;
|
|
1664
|
+
this._continue();
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
this._rejectionStreak = 0;
|
|
1324
1668
|
this._steps++;
|
|
1325
1669
|
if (step && step.distance < this.state.distance) {
|
|
1326
1670
|
this.state = step.apply(this.state);
|
|
@@ -1566,11 +1910,15 @@ export {
|
|
|
1566
1910
|
differenceToDistance,
|
|
1567
1911
|
distanceToDifference,
|
|
1568
1912
|
getFill,
|
|
1913
|
+
makeCircle,
|
|
1914
|
+
makeEllipse,
|
|
1915
|
+
makeGlyph,
|
|
1569
1916
|
makeNGon,
|
|
1570
1917
|
makeRect,
|
|
1571
1918
|
parseColor,
|
|
1572
1919
|
renderStepToCtx,
|
|
1573
1920
|
replayOutput,
|
|
1921
|
+
rgbToHsl,
|
|
1574
1922
|
stepDataToSVGElement,
|
|
1575
1923
|
stepPerf
|
|
1576
1924
|
};
|