depixel 1.0.3 → 1.0.5

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.
Files changed (3) hide show
  1. package/README.md +19 -2
  2. package/lib.js +406 -220
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,6 +11,8 @@ Improvements upon original GPU version:
11
11
  * Add a threshold parameter to adjust how close two colors need to be to be consider similar
12
12
  * Better handle alpha channels (treat as dissimilar from non-transparent pixels)
13
13
  * Fix stretching/scaling due to texel misalignment
14
+ * Add option to pass in a similarity map
15
+ * Relatedly, renderMode:similarityMask outputs a scaled up similarity map (for use in post-processing, etc)
14
16
 
15
17
  Notes
16
18
  * Original GPU code is MIT licensed, the same license may apply here, all original code in this project is additionally released under the MIT License
@@ -25,12 +27,27 @@ type Image = {
25
27
  data: Buffer; // Or Uint8Array - pixels in RGBA byte order
26
28
  width: number;
27
29
  height: number;
30
+ similarityData?: Buffer;
28
31
  };
29
32
 
30
33
  type Opts = {
31
34
  height: number;
32
- threshold?: number; // 0..255, lower = fewer similarity edges
33
- borderPx?: number; // pad input with this many pixels (1-2) - useful with `threshold=0` for complete hard edges
35
+ threshold?: number; // 0..255, lower = fewer similarity edges; default 255
36
+ // borderPx - pad input with this many pixels (1-2) - useful with
37
+ // `threshold=0` for complete hard edges
38
+ borderPx?: number;
39
+ // if `similarity` specified, uses values here instead or RGBA values to
40
+ // determine similarity. Note: `threshold` (default 3) is used against sum
41
+ // of RGB differences in this buffer instead the default of using a
42
+ // perceptual threshold based on color
43
+ // note: color still impacts shape of splines
44
+ similarity?: Buffer;
45
+ // if `outputSimilarityMask` and `similarity` is specified,
46
+ // then `similarityData` in the return will contain a scaled up version of
47
+ // the `similarity` mask (for use in post-processing, etc
48
+ outputSimilarityMask?: boolean; // default false
49
+ renderMode?: 'splines' | 'default'; // default default
50
+ doOpt?: boolean; // default true
34
51
  }
35
52
 
36
53
  function scaleImage(src: Image, opts: Opts): Image;
package/lib.js CHANGED
@@ -3,17 +3,34 @@ type Image = {
3
3
  data: Buffer; // Or Uint8Array - pixels in RGBA byte order
4
4
  width: number;
5
5
  height: number;
6
+ similarityData?: Buffer;
6
7
  };
7
8
 
8
9
  type Opts = {
9
10
  height: number;
10
11
  threshold?: number; // 0..255, lower = fewer similarity edges
11
12
  borderPx?: number; // pad input with this many pixels (1-2)
13
+ similarity?: Buffer; // if specified uses values here instead or RGBA values to determine similarity - threshold (default 3) is used against sum of RGB differences
14
+ outputSimilarityMask?: boolean; // if this and similarity are specified, return similarityData
15
+ renderMode?: 'splines' | 'default';
16
+ doOpt?: boolean; // default true
12
17
  }
13
18
 
14
19
  function scaleImage(src: Image, opts: Opts): Image;
15
20
  */
16
21
 
22
+ const {
23
+ abs,
24
+ ceil,
25
+ exp,
26
+ floor,
27
+ hypot,
28
+ max,
29
+ min,
30
+ round,
31
+ sign,
32
+ } = Math;
33
+
17
34
  const EDGE_HORVERT = 16;
18
35
  const EDGE_DIAGONAL_ULLR = 32;
19
36
  const EDGE_DIAGONAL_LLUR = 64;
@@ -55,6 +72,7 @@ const BRACKET_SEARCH_B = -0.1;
55
72
  const GOLD = 1.618034;
56
73
  const GLIMIT = 10.0;
57
74
  const TINY = 0.000000001;
75
+ const ONEo255 = 1/255;
58
76
 
59
77
  function clampInt(v, lo, hi) {
60
78
  if (v < lo) {
@@ -77,7 +95,7 @@ function fetchPixelRGBA(src, x, y) {
77
95
  const cy = clampInt(y, 0, h - 1);
78
96
  const idx = pixelIndex(cx, cy, w);
79
97
  const d = src.data;
80
- return [d[idx] / 255, d[idx + 1] / 255, d[idx + 2] / 255, d[idx + 3] / 255];
98
+ return [d[idx] * ONEo255, d[idx + 1] * ONEo255, d[idx + 2] * ONEo255, d[idx + 3] * ONEo255];
81
99
  }
82
100
 
83
101
  function fetchPixelRGBA8(src, x, y) {
@@ -90,6 +108,10 @@ function fetchPixelRGBA8(src, x, y) {
90
108
  return [d[idx], d[idx + 1], d[idx + 2], d[idx + 3]];
91
109
  }
92
110
 
111
+ const THRESHOLD_a = 32 / 255;
112
+ const THRESHOLD_y = 48 / 255;
113
+ const THRESHOLD_u = 7 / 255;
114
+ const THRESHOLD_v = 6 / 255;
93
115
  function isSimilar(a, b, threshold) {
94
116
  const yA = 0.299 * a[0] + 0.587 * a[1] + 0.114 * a[2];
95
117
  const uA = 0.493 * (a[2] - yA);
@@ -97,16 +119,15 @@ function isSimilar(a, b, threshold) {
97
119
  const yB = 0.299 * b[0] + 0.587 * b[1] + 0.114 * b[2];
98
120
  const uB = 0.493 * (b[2] - yB);
99
121
  const vB = 0.877 * (b[0] - yB);
100
- const t = Math.max(0, Math.min(255, threshold | 0)) / 255.0;
101
- if (Math.abs(a[3] - b[3]) <= (32.0/255) * t) {
102
- if (a[3] + b[3] <= (32.0/255) * t) {
122
+ if (abs(a[3] - b[3]) <= THRESHOLD_a * threshold) {
123
+ if (a[3] + b[3] <= THRESHOLD_a * threshold) {
103
124
  // treat all alpha=0 pixels as similar, regardless of color
104
125
  // note: need an option for this if processing images with masks or premultiplied alpha
105
126
  return true;
106
127
  }
107
- if (Math.abs(yA - yB) <= (48.0 / 255.0) * t) {
108
- if (Math.abs(uA - uB) <= (7.0 / 255.0) * t) {
109
- if (Math.abs(vA - vB) <= (6.0 / 255.0) * t) {
128
+ if (abs(yA - yB) <= THRESHOLD_y * threshold) {
129
+ if (abs(uA - uB) <= THRESHOLD_u * threshold) {
130
+ if (abs(vA - vB) <= THRESHOLD_v * threshold) {
110
131
  return true;
111
132
  }
112
133
  }
@@ -115,6 +136,8 @@ function isSimilar(a, b, threshold) {
115
136
  return false;
116
137
  }
117
138
 
139
+ const THRESHOLD_CONTOUR = 100 / 255;
140
+ const THRESHOLD_CONTOUR_SQ = THRESHOLD_CONTOUR * THRESHOLD_CONTOUR;
118
141
  function isContour(src, pL, pR) {
119
142
  const a = fetchPixelRGBA(src, pL[0], pL[1]);
120
143
  const b = fetchPixelRGBA(src, pR[0], pR[1]);
@@ -127,11 +150,11 @@ function isContour(src, pL, pR) {
127
150
  const dy = yA - yB;
128
151
  const du = uA - uB;
129
152
  const dv = vA - vB;
130
- const dist = Math.sqrt(dy * dy + du * du + dv * dv);
131
- return dist > 100.0 / 255.0;
153
+ const dist_sq = dy * dy + du * du + dv * dv;
154
+ return dist_sq > THRESHOLD_CONTOUR_SQ;
132
155
  }
133
156
 
134
- function buildSimilarityGraph(src, similarityThreshold) {
157
+ function buildSimilarityGraph(src, similarityThreshold, similarity) {
135
158
  const w = src.width;
136
159
  const h = src.height;
137
160
  const sgW = 2 * w + 1;
@@ -146,7 +169,25 @@ function buildSimilarityGraph(src, similarityThreshold) {
146
169
  }
147
170
 
148
171
  function getPixelCoords(gx, gy) {
149
- return [Math.floor((gx - 1) / 2), Math.floor((gy - 1) / 2)];
172
+ return [floor((gx - 1) / 2), floor((gy - 1) / 2)];
173
+ }
174
+
175
+ let isSimilar2;
176
+ if (similarity) {
177
+ let simImage = {
178
+ ...src,
179
+ data: similarity,
180
+ };
181
+ isSimilar2 = function(p1, p2) {
182
+ let v1 = fetchPixelRGBA8(simImage, p1[0], p1[1]);
183
+ let v2 = fetchPixelRGBA8(simImage, p2[0], p2[1]);
184
+ let sum = abs(v1[0] - v2[0]) + abs(v1[1] - v2[1]) + abs(v1[2] - v2[2]);
185
+ return sum <= similarityThreshold;
186
+ }
187
+ } else {
188
+ isSimilar2 = function(p1, p2) {
189
+ return isSimilar(fetchPixelRGBA(src, p1[0], p1[1]), fetchPixelRGBA(src, p2[0], p2[1]), similarityThreshold);
190
+ }
150
191
  }
151
192
 
152
193
  for (let y = 0; y < sgH; y++) {
@@ -163,19 +204,19 @@ function buildSimilarityGraph(src, similarityThreshold) {
163
204
  let diagonal = 0;
164
205
  let pA = getPixelCoords(x - 1, y + 1);
165
206
  let pB = getPixelCoords(x + 1, y - 1);
166
- if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
207
+ if (isSimilar2(pA, pB)) {
167
208
  diagonal = EDGE_DIAGONAL_ULLR;
168
209
  }
169
210
  pA = getPixelCoords(x - 1, y - 1);
170
211
  pB = getPixelCoords(x + 1, y + 1);
171
- if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
212
+ if (isSimilar2(pA, pB)) {
172
213
  diagonal |= EDGE_DIAGONAL_LLUR;
173
214
  }
174
215
  setRG(x, y, diagonal, 0);
175
216
  } else if (evalX === 0 && evalY === 1) {
176
217
  const pA = getPixelCoords(x - 1, y);
177
218
  const pB = getPixelCoords(x + 1, y);
178
- if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
219
+ if (isSimilar2(pA, pB)) {
179
220
  setRG(x, y, EDGE_HORVERT, 0);
180
221
  } else {
181
222
  setRG(x, y, 0, 0);
@@ -183,7 +224,7 @@ function buildSimilarityGraph(src, similarityThreshold) {
183
224
  } else if (evalX === 1 && evalY === 0) {
184
225
  const pA = getPixelCoords(x, y - 1);
185
226
  const pB = getPixelCoords(x, y + 1);
186
- if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
227
+ if (isSimilar2(pA, pB)) {
187
228
  setRG(x, y, EDGE_HORVERT, 0);
188
229
  } else {
189
230
  setRG(x, y, 0, 0);
@@ -368,20 +409,20 @@ function eliminateCrossings(sim) {
368
409
  nNE = 8 * (2 - level) + (4 - level);
369
410
  nE = 8 * (3 - level) + (4 - level);
370
411
  if (currentComponent === 0) {
371
- if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
372
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
373
- else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
374
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
375
- else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
376
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
377
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
412
+ if ((nhood & SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
413
+ else if ((nhood & SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
414
+ else if ((nhood & WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
415
+ else if ((nhood & NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
416
+ else if ((nhood & NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
417
+ else if ((nhood & NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
418
+ else if ((nhood & EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
378
419
  }
379
420
  if (currentComponent !== 0) {
380
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
381
- if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
382
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
383
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
384
- if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
421
+ if (nhood & SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
422
+ if (nhood & WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
423
+ if (nhood & NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
424
+ if (nhood & NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
425
+ if (nhood & NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
385
426
  }
386
427
  if (level > 0) {
387
428
  for (let i = 0; i < level * 2; i++) {
@@ -396,16 +437,16 @@ function eliminateCrossings(sim) {
396
437
  nNE = 8 * (2 - level) + (i + 5 - level);
397
438
  nE = 8 * (3 - level) + (i + 5 - level);
398
439
  if (currentComponent === 0) {
399
- if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
400
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
401
- else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
402
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
403
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
440
+ if ((nhood & WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
441
+ else if ((nhood & NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
442
+ else if ((nhood & NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
443
+ else if ((nhood & NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
444
+ else if ((nhood & EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
404
445
  }
405
446
  if (currentComponent !== 0) {
406
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
407
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
408
- if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
447
+ if (nhood & NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
448
+ if (nhood & NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
449
+ if (nhood & NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
409
450
  }
410
451
  }
411
452
  }
@@ -423,20 +464,20 @@ function eliminateCrossings(sim) {
423
464
  nSE = 8 * (4 - level) + (5 + level);
424
465
  nS = 8 * (4 - level) + (4 + level);
425
466
  if (currentComponent === 0) {
426
- if (((nhood & WEST) === WEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
427
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
428
- else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
429
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
430
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
431
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
432
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
467
+ if ((nhood & WEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
468
+ else if ((nhood & NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
469
+ else if ((nhood & NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
470
+ else if ((nhood & NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
471
+ else if ((nhood & EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
472
+ else if ((nhood & SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
473
+ else if ((nhood & SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
433
474
  }
434
475
  if (currentComponent !== 0) {
435
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
436
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
437
- if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
438
- if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
439
- if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
476
+ if (nhood & NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
477
+ if (nhood & NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
478
+ if (nhood & NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
479
+ if (nhood & EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
480
+ if (nhood & SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
440
481
  }
441
482
  if (level > 0) {
442
483
  for (let i = 0; i < level * 2; i++) {
@@ -451,16 +492,16 @@ function eliminateCrossings(sim) {
451
492
  nSE = 8 * (i + 5 - level) + (5 + level);
452
493
  nS = 8 * (i + 5 - level) + (4 + level);
453
494
  if (currentComponent === 0) {
454
- if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
455
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
456
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
457
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
458
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
495
+ if ((nhood & NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
496
+ else if ((nhood & NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
497
+ else if ((nhood & EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
498
+ else if ((nhood & SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
499
+ else if ((nhood & SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
459
500
  }
460
501
  if (currentComponent !== 0) {
461
- if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
462
- if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
463
- if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
502
+ if (nhood & NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
503
+ if (nhood & EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
504
+ if (nhood & SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
464
505
  }
465
506
  }
466
507
  }
@@ -478,20 +519,20 @@ function eliminateCrossings(sim) {
478
519
  nSW = 8 * (5 + level) + (3 + level);
479
520
  nW = 8 * (4 + level) + (3 + level);
480
521
  if (currentComponent === 0) {
481
- if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
482
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
483
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
484
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
485
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
486
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
487
- else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
522
+ if ((nhood & NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
523
+ else if ((nhood & NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
524
+ else if ((nhood & EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
525
+ else if ((nhood & SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
526
+ else if ((nhood & SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
527
+ else if ((nhood & SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
528
+ else if ((nhood & WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
488
529
  }
489
530
  if (currentComponent !== 0) {
490
- if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
491
- if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
492
- if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
493
- if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
494
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
531
+ if (nhood & NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
532
+ if (nhood & EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
533
+ if (nhood & SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
534
+ if (nhood & SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
535
+ if (nhood & SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
495
536
  }
496
537
 
497
538
  if (level > 0) {
@@ -507,18 +548,18 @@ function eliminateCrossings(sim) {
507
548
  nSW = 8 * (5 + level) + (i + 3 - level);
508
549
  nW = 8 * (4 + level) + (i + 3 - level);
509
550
  if (currentComponent === 0) {
510
- if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
511
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
512
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
513
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
514
- else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
551
+ if ((nhood & EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
552
+ else if ((nhood & SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
553
+ else if ((nhood & SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
554
+ else if ((nhood & SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
555
+ else if ((nhood & WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
515
556
  }
516
557
  if (currentComponent !== 0) {
517
- if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
518
- if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
519
- if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
520
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
521
- if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
558
+ if (nhood & EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
559
+ if (nhood & SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
560
+ if (nhood & SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
561
+ if (nhood & SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
562
+ if (nhood & WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
522
563
  }
523
564
  }
524
565
  }
@@ -536,20 +577,20 @@ function eliminateCrossings(sim) {
536
577
  nSE = 8 * (5 + level) + (4 - level);
537
578
  nE = 8 * (4 + level) + (4 - level);
538
579
  if (currentComponent === 0) {
539
- if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
540
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
541
- else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
542
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
543
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
544
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
545
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
580
+ if ((nhood & NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
581
+ else if ((nhood & NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
582
+ else if ((nhood & WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
583
+ else if ((nhood & SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
584
+ else if ((nhood & SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
585
+ else if ((nhood & SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
586
+ else if ((nhood & EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
546
587
  }
547
588
  if (currentComponent !== 0) {
548
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
549
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
550
- if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
551
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
552
- if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
589
+ if (nhood & NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
590
+ if (nhood & NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
591
+ if (nhood & WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
592
+ if (nhood & SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
593
+ if (nhood & SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
553
594
  }
554
595
 
555
596
  if (level > 0) {
@@ -565,18 +606,18 @@ function eliminateCrossings(sim) {
565
606
  nSW = 8 * (i + 5 - level) + (2 - level);
566
607
  nS = 8 * (i + 5 - level) + (3 - level);
567
608
  if (currentComponent === 0) {
568
- if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
569
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
570
- else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
571
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
572
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
609
+ if ((nhood & NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
610
+ else if ((nhood & NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
611
+ else if ((nhood & WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
612
+ else if ((nhood & SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
613
+ else if ((nhood & SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
573
614
  }
574
615
  if (currentComponent !== 0) {
575
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
576
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
577
- if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
578
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
579
- if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
616
+ if (nhood & NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
617
+ if (nhood & NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
618
+ if (nhood & WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
619
+ if (nhood & SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
620
+ if (nhood & SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
580
621
  }
581
622
  }
582
623
  }
@@ -723,8 +764,8 @@ function computeCellGraph(src, sim) {
723
764
  }
724
765
 
725
766
  function checkForCorner(s1, s2) {
726
- const n1 = Math.hypot(s1[0], s1[1]);
727
- const n2 = Math.hypot(s2[0], s2[1]);
767
+ const n1 = hypot(s1[0], s1[1]);
768
+ const n2 = hypot(s2[0], s2[1]);
728
769
  if (n1 === 0 || n2 === 0) {
729
770
  return false;
730
771
  }
@@ -1057,8 +1098,8 @@ function optimizeCellGraph(cell, width, height) {
1057
1098
  function calcPositionalEnergy(pNew, pOld) {
1058
1099
  const dx = pNew[0] - pOld[0];
1059
1100
  const dy = pNew[1] - pOld[1];
1060
- const dist = POSITIONAL_ENERGY_SCALING * Math.hypot(dx, dy);
1061
- return dist * dist * dist * dist;
1101
+ const distSq = POSITIONAL_ENERGY_SCALING * POSITIONAL_ENERGY_SCALING * (dx * dx + dy * dy);
1102
+ return distSq * distSq;
1062
1103
  }
1063
1104
 
1064
1105
  function calcSegmentCurveEnergy(node1, node2, node3) {
@@ -1089,7 +1130,7 @@ function optimizeCellGraph(cell, width, height) {
1089
1130
  const r = (bx - ax) * (fb - fc);
1090
1131
  const q = (bx - cx) * (fb - fa);
1091
1132
  const qr = q - r;
1092
- let u = bx - ((bx - cx) * q - (bx - ax) * r) / (2.0 * Math.sign(qr) * Math.max(Math.abs(qr), TINY));
1133
+ let u = bx - ((bx - cx) * q - (bx - ax) * r) / (2.0 * sign(qr) * max(abs(qr), TINY));
1093
1134
  const ulim = bx + GLIMIT * (cx - bx);
1094
1135
  let fu;
1095
1136
  if ((bx - u) * (u - cx) > 0.0) {
@@ -1131,7 +1172,7 @@ function optimizeCellGraph(cell, width, height) {
1131
1172
 
1132
1173
  function searchOffset(pos, splineNeighbors) {
1133
1174
  let gradient = calcGradient(splineNeighbors[0], pos, splineNeighbors[1]);
1134
- const glen = Math.hypot(gradient[0], gradient[1]);
1175
+ const glen = hypot(gradient[0], gradient[1]);
1135
1176
  if (glen <= 0.0) {
1136
1177
  return [0, 0, 0];
1137
1178
  }
@@ -1141,7 +1182,7 @@ function optimizeCellGraph(cell, width, height) {
1141
1182
  let x1 = 0;
1142
1183
  let x2 = 0;
1143
1184
  let x3 = bracket[2];
1144
- if (Math.abs(bracket[2] - bracket[1]) > Math.abs(bracket[1] - bracket[0])) {
1185
+ if (abs(bracket[2] - bracket[1]) > abs(bracket[1] - bracket[0])) {
1145
1186
  x1 = bracket[1];
1146
1187
  x2 = bracket[1] + C * (bracket[2] - bracket[1]);
1147
1188
  } else {
@@ -1154,7 +1195,7 @@ function optimizeCellGraph(cell, width, height) {
1154
1195
  let f2 = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
1155
1196
  let counter = 0;
1156
1197
  let fx;
1157
- while (Math.abs(x3 - x0) > TOL * (Math.abs(x1) + Math.abs(x2)) && (counter < LIMIT_SEARCH_ITERATIONS)) {
1198
+ while (abs(x3 - x0) > TOL * (abs(x1) + abs(x2)) && (counter < LIMIT_SEARCH_ITERATIONS)) {
1158
1199
  counter++;
1159
1200
  if (f2 < f1) {
1160
1201
  x0 = x1; x1 = x2; x2 = R * x1 + C * x3;
@@ -1174,7 +1215,7 @@ function optimizeCellGraph(cell, width, height) {
1174
1215
 
1175
1216
  for (let i = 0; i < count; i++) {
1176
1217
  const flags = cell.flags[i];
1177
- if (flags > 16 && flags < 512) {
1218
+ if (flags & (HAS_NORTHERN_SPLINE|HAS_EASTERN_SPLINE|HAS_SOUTHERN_SPLINE|HAS_WESTERN_SPLINE)) {
1178
1219
  const base = i * 4;
1179
1220
  const neighbors = [cell.neighbors[base], cell.neighbors[base + 1], cell.neighbors[base + 2], cell.neighbors[base + 3]];
1180
1221
  const pos = getPosOpt(i);
@@ -1183,18 +1224,18 @@ function optimizeCellGraph(cell, width, height) {
1183
1224
  let splineCount = 0;
1184
1225
  let splineNoOpt = false;
1185
1226
 
1186
- const hasN = (flags & HAS_NORTHERN_SPLINE) === HAS_NORTHERN_SPLINE;
1187
- const hasE = (flags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE;
1188
- const hasS = (flags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE;
1189
- const hasW = (flags & HAS_WESTERN_SPLINE) === HAS_WESTERN_SPLINE;
1227
+ const hasN = Boolean(flags & HAS_NORTHERN_SPLINE);
1228
+ const hasE = Boolean(flags & HAS_EASTERN_SPLINE);
1229
+ const hasS = Boolean(flags & HAS_SOUTHERN_SPLINE);
1230
+ const hasW = Boolean(flags & HAS_WESTERN_SPLINE);
1190
1231
 
1191
1232
  if (hasN) {
1192
1233
  const neighborflags = cell.flags[neighbors[0]] | 0;
1193
- if (((flags & DONT_OPTIMIZE_N) === DONT_OPTIMIZE_N) || ((neighborflags & DONT_OPTIMIZE_S) === DONT_OPTIMIZE_S)) {
1234
+ if ((flags & DONT_OPTIMIZE_N) || (neighborflags & DONT_OPTIMIZE_S)) {
1194
1235
  splineNoOpt = true;
1195
1236
  }
1196
1237
  if (!splineNoOpt) {
1197
- if (((neighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) && ((neighborflags & HAS_SOUTHERN_SPLINE) !== HAS_SOUTHERN_SPLINE)) {
1238
+ if ((neighborflags & HAS_CORRECTED_POSITION) && !(neighborflags & HAS_SOUTHERN_SPLINE)) {
1198
1239
  splineNeighbors[splineCount++] = getPos(neighbors[0] + 1);
1199
1240
  } else {
1200
1241
  splineNeighbors[splineCount++] = getPos(neighbors[0]);
@@ -1203,11 +1244,11 @@ function optimizeCellGraph(cell, width, height) {
1203
1244
  }
1204
1245
  if (hasE) {
1205
1246
  const neighborflags = cell.flags[neighbors[1]] | 0;
1206
- if (((flags & DONT_OPTIMIZE_E) === DONT_OPTIMIZE_E) || ((neighborflags & DONT_OPTIMIZE_W) === DONT_OPTIMIZE_W)) {
1247
+ if ((flags & DONT_OPTIMIZE_E) || (neighborflags & DONT_OPTIMIZE_W)) {
1207
1248
  splineNoOpt = true;
1208
1249
  }
1209
1250
  if (!splineNoOpt) {
1210
- if (((neighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) && ((neighborflags & HAS_WESTERN_SPLINE) !== HAS_WESTERN_SPLINE)) {
1251
+ if ((neighborflags & HAS_CORRECTED_POSITION) && !(neighborflags & HAS_WESTERN_SPLINE)) {
1211
1252
  splineNeighbors[splineCount++] = getPos(neighbors[1] + 1);
1212
1253
  } else {
1213
1254
  splineNeighbors[splineCount++] = getPos(neighbors[1]);
@@ -1216,11 +1257,11 @@ function optimizeCellGraph(cell, width, height) {
1216
1257
  }
1217
1258
  if (hasS) {
1218
1259
  const neighborflags = cell.flags[neighbors[2]] | 0;
1219
- if (((flags & DONT_OPTIMIZE_S) === DONT_OPTIMIZE_S) || ((neighborflags & DONT_OPTIMIZE_N) === DONT_OPTIMIZE_N)) {
1260
+ if ((flags & DONT_OPTIMIZE_S) || (neighborflags & DONT_OPTIMIZE_N)) {
1220
1261
  splineNoOpt = true;
1221
1262
  }
1222
1263
  if (!splineNoOpt) {
1223
- if (((neighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) && ((neighborflags & HAS_NORTHERN_SPLINE) !== HAS_NORTHERN_SPLINE)) {
1264
+ if ((neighborflags & HAS_CORRECTED_POSITION) && !(neighborflags & HAS_NORTHERN_SPLINE)) {
1224
1265
  splineNeighbors[splineCount++] = getPos(neighbors[2] + 1);
1225
1266
  } else {
1226
1267
  splineNeighbors[splineCount++] = getPos(neighbors[2]);
@@ -1229,11 +1270,11 @@ function optimizeCellGraph(cell, width, height) {
1229
1270
  }
1230
1271
  if (hasW) {
1231
1272
  const neighborflags = cell.flags[neighbors[3]] | 0;
1232
- if (((flags & DONT_OPTIMIZE_W) === DONT_OPTIMIZE_W) || ((neighborflags & DONT_OPTIMIZE_E) === DONT_OPTIMIZE_E)) {
1273
+ if ((flags & DONT_OPTIMIZE_W) || (neighborflags & DONT_OPTIMIZE_E)) {
1233
1274
  splineNoOpt = true;
1234
1275
  }
1235
1276
  if (!splineNoOpt) {
1236
- if (((neighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) && ((neighborflags & HAS_EASTERN_SPLINE) !== HAS_EASTERN_SPLINE)) {
1277
+ if ((neighborflags & HAS_CORRECTED_POSITION) && !(neighborflags & HAS_EASTERN_SPLINE)) {
1237
1278
  splineNeighbors[splineCount++] = getPos(neighbors[3] + 1);
1238
1279
  } else {
1239
1280
  splineNeighbors[splineCount++] = getPos(neighbors[3]);
@@ -1258,7 +1299,7 @@ function computeCorrectedPositions(cell, optimized) {
1258
1299
  corrected.set(optimized);
1259
1300
 
1260
1301
  function getPos(idx) {
1261
- return [cell.pos[idx * 2], cell.pos[idx * 2 + 1]];
1302
+ return [optimized[idx * 2], optimized[idx * 2 + 1]];
1262
1303
  }
1263
1304
 
1264
1305
  function calcAdjustedPoint(p0, p1, p2) {
@@ -1275,16 +1316,16 @@ function computeCorrectedPositions(cell, optimized) {
1275
1316
  const parentNeighborIndices = [cell.neighbors[base], cell.neighbors[base + 1], cell.neighbors[base + 2], cell.neighbors[base + 3]];
1276
1317
  const splinePoints = [null, null];
1277
1318
  let countSp = 0;
1278
- if ((parentFlags & HAS_NORTHERN_SPLINE) === HAS_NORTHERN_SPLINE) {
1319
+ if (parentFlags & HAS_NORTHERN_SPLINE) {
1279
1320
  splinePoints[countSp++] = getPos(parentNeighborIndices[0]);
1280
1321
  }
1281
- if ((parentFlags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE) {
1322
+ if (parentFlags & HAS_EASTERN_SPLINE) {
1282
1323
  splinePoints[countSp++] = getPos(parentNeighborIndices[1]);
1283
1324
  }
1284
- if ((parentFlags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE) {
1325
+ if (parentFlags & HAS_SOUTHERN_SPLINE) {
1285
1326
  splinePoints[countSp++] = getPos(parentNeighborIndices[2]);
1286
1327
  }
1287
- if ((parentFlags & HAS_WESTERN_SPLINE) === HAS_WESTERN_SPLINE) {
1328
+ if (parentFlags & HAS_WESTERN_SPLINE) {
1288
1329
  splinePoints[countSp++] = getPos(parentNeighborIndices[3]);
1289
1330
  }
1290
1331
  if (countSp === 2) {
@@ -1301,10 +1342,10 @@ function computeCorrectedPositions(cell, optimized) {
1301
1342
  return corrected;
1302
1343
  }
1303
1344
 
1304
- function gaussRasterize(src, sim, cell, positions, outW, outH) {
1345
+ function gaussRasterize(src, sim, cell, positions, outW, outH, needSplines, outputSimilarityMask, similarity) {
1305
1346
  const w = src.width;
1306
1347
  const h = src.height;
1307
- const out = new Uint8Array(outW * outH * 4);
1348
+ const out = Buffer.alloc(outW * outH * 4);
1308
1349
 
1309
1350
  function getG(x, y) {
1310
1351
  if (x < 0 || y < 0 || x >= sim.w || y >= sim.h) {
@@ -1362,16 +1403,16 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1362
1403
 
1363
1404
  function computeValence(flags) {
1364
1405
  let v = 0;
1365
- if ((flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
1406
+ if (flags & HAS_NORTHERN_NEIGHBOR) {
1366
1407
  v++;
1367
1408
  }
1368
- if ((flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1409
+ if (flags & HAS_EASTERN_NEIGHBOR) {
1369
1410
  v++;
1370
1411
  }
1371
- if ((flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1412
+ if (flags & HAS_SOUTHERN_NEIGHBOR) {
1372
1413
  v++;
1373
1414
  }
1374
- if ((flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1415
+ if (flags & HAS_WESTERN_NEIGHBOR) {
1375
1416
  v++;
1376
1417
  }
1377
1418
  return v;
@@ -1404,7 +1445,7 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1404
1445
  if ((node0neighborFlags & checkFwd[0]) === checkFwd[0]) {
1405
1446
  const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, dir);
1406
1447
  const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
1407
- if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
1448
+ if (neighborsNeighborflags & HAS_CORRECTED_POSITION) {
1408
1449
  cpArray[1] = neighborsNeighborIndex + 1;
1409
1450
  } else {
1410
1451
  cpArray[1] = neighborsNeighborIndex;
@@ -1412,7 +1453,7 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1412
1453
  } else if ((node0neighborFlags & checkFwd[1]) === checkFwd[1]) {
1413
1454
  const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, chkdirs[0]);
1414
1455
  const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
1415
- if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
1456
+ if (neighborsNeighborflags & HAS_CORRECTED_POSITION) {
1416
1457
  cpArray[1] = neighborsNeighborIndex + 1;
1417
1458
  } else {
1418
1459
  cpArray[1] = neighborsNeighborIndex;
@@ -1420,20 +1461,32 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1420
1461
  } else if ((node0neighborFlags & checkFwd[2]) === checkFwd[2]) {
1421
1462
  const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, chkdirs[1]);
1422
1463
  const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
1423
- if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
1464
+ if (neighborsNeighborflags & HAS_CORRECTED_POSITION) {
1424
1465
  cpArray[1] = neighborsNeighborIndex + 1;
1425
1466
  } else {
1426
1467
  cpArray[1] = neighborsNeighborIndex;
1427
1468
  }
1428
1469
  }
1429
1470
  } else {
1430
- if ((node0neighborFlags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
1471
+ if (node0neighborFlags & HAS_CORRECTED_POSITION) {
1431
1472
  cpArray[0]++;
1432
1473
  }
1433
1474
  }
1434
1475
  return cpArray;
1435
1476
  }
1436
1477
 
1478
+ const allSplines = needSplines ? [] : null;
1479
+ const allSplinesDone = {};
1480
+ let similarityData;
1481
+ let simImage;
1482
+ if (outputSimilarityMask) {
1483
+ similarityData = Buffer.alloc(outW * outH * 4);
1484
+ simImage = {
1485
+ ...src,
1486
+ data: similarity,
1487
+ };
1488
+ }
1489
+
1437
1490
  for (let oy = 0; oy < outH; oy++) {
1438
1491
  for (let ox = 0; ox < outW; ox++) {
1439
1492
  let influencingPixels = [true, true, true, true];
@@ -1441,29 +1494,36 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1441
1494
  (w * (ox + 0.5) / outW) - 0.5 + 0.00001,
1442
1495
  (h * (oy + 0.5) / outH) - 0.5 + 0.00001,
1443
1496
  ];
1444
- const fragmentBaseKnotIndex = (2 * Math.floor(cellSpaceCoords[0]) + Math.floor(cellSpaceCoords[1]) * 2 * (w - 1)) | 0;
1497
+ const fragmentBaseKnotIndex = (2 * floor(cellSpaceCoords[0]) + floor(cellSpaceCoords[1]) * 2 * (w - 1)) | 0;
1445
1498
  const node0flags = cell.flags[fragmentBaseKnotIndex] | 0;
1446
1499
  let hasCorrectedPosition = false;
1447
1500
 
1448
- const ULCoords = [Math.floor(cellSpaceCoords[0]), Math.ceil(cellSpaceCoords[1])];
1449
- const URCoords = [Math.ceil(cellSpaceCoords[0]), Math.ceil(cellSpaceCoords[1])];
1450
- const LLCoords = [Math.floor(cellSpaceCoords[0]), Math.floor(cellSpaceCoords[1])];
1451
- const LRCoords = [Math.ceil(cellSpaceCoords[0]), Math.floor(cellSpaceCoords[1])];
1501
+ const ULCoords = [floor(cellSpaceCoords[0]), ceil(cellSpaceCoords[1])];
1502
+ const URCoords = [ceil(cellSpaceCoords[0]), ceil(cellSpaceCoords[1])];
1503
+ const LLCoords = [floor(cellSpaceCoords[0]), floor(cellSpaceCoords[1])];
1504
+ const LRCoords = [ceil(cellSpaceCoords[0]), floor(cellSpaceCoords[1])];
1452
1505
 
1453
1506
  function findSegmentIntersections(p0, p1, p2) {
1507
+ if (allSplines) {
1508
+ const key = [p0.join(), p1.join(), p2.join()].join();
1509
+ if (!allSplinesDone[key]) {
1510
+ allSplinesDone[key] = true;
1511
+ allSplines.push([p0, p1, p2]);
1512
+ }
1513
+ }
1454
1514
  let pointA = calcSplinePoint(p0, p1, p2, 0.0);
1455
1515
  for (let t = STEP; t < (1.0 + STEP); t += STEP) {
1456
1516
  const pointB = calcSplinePoint(p0, p1, p2, t);
1457
- if (intersects(cellSpaceCoords, ULCoords, pointA, pointB)) {
1517
+ if (influencingPixels[0] && intersects(cellSpaceCoords, ULCoords, pointA, pointB)) {
1458
1518
  influencingPixels[0] = false;
1459
1519
  }
1460
- if (intersects(cellSpaceCoords, URCoords, pointA, pointB)) {
1520
+ if (influencingPixels[1] && intersects(cellSpaceCoords, URCoords, pointA, pointB)) {
1461
1521
  influencingPixels[1] = false;
1462
1522
  }
1463
- if (intersects(cellSpaceCoords, LLCoords, pointA, pointB)) {
1523
+ if (influencingPixels[2] && intersects(cellSpaceCoords, LLCoords, pointA, pointB)) {
1464
1524
  influencingPixels[2] = false;
1465
1525
  }
1466
- if (intersects(cellSpaceCoords, LRCoords, pointA, pointB)) {
1526
+ if (influencingPixels[3] && intersects(cellSpaceCoords, LRCoords, pointA, pointB)) {
1467
1527
  influencingPixels[3] = false;
1468
1528
  }
1469
1529
  pointA = pointB;
@@ -1481,13 +1541,13 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1481
1541
  let node0pos = getPos(fragmentBaseKnotIndex);
1482
1542
  if (node0valence === 1) {
1483
1543
  let cpArray = [-1, -1];
1484
- if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
1544
+ if (node0flags & HAS_NORTHERN_NEIGHBOR) {
1485
1545
  cpArray = getCPs(node0neighbors[0], NORTH);
1486
- } else if ((node0flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1546
+ } else if (node0flags & HAS_EASTERN_NEIGHBOR) {
1487
1547
  cpArray = getCPs(node0neighbors[1], EAST);
1488
- } else if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1548
+ } else if (node0flags & HAS_SOUTHERN_NEIGHBOR) {
1489
1549
  cpArray = getCPs(node0neighbors[2], SOUTH);
1490
- } else if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1550
+ } else if (node0flags & HAS_WESTERN_NEIGHBOR) {
1491
1551
  cpArray = getCPs(node0neighbors[3], WEST);
1492
1552
  }
1493
1553
  const p1pos = getPos(cpArray[0]);
@@ -1501,18 +1561,18 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1501
1561
  } else if (node0valence === 2) {
1502
1562
  let cpArray = [-1, -1, -1, -1];
1503
1563
  let foundFirst = false;
1504
- if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) { cpArray[0] = getCPs(node0neighbors[0], NORTH)[0]; cpArray[1] = getCPs(node0neighbors[0], NORTH)[1]; foundFirst = true; }
1505
- if ((node0flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1564
+ if (node0flags & HAS_NORTHERN_NEIGHBOR) { cpArray[0] = getCPs(node0neighbors[0], NORTH)[0]; cpArray[1] = getCPs(node0neighbors[0], NORTH)[1]; foundFirst = true; }
1565
+ if (node0flags & HAS_EASTERN_NEIGHBOR) {
1506
1566
  const tmp = getCPs(node0neighbors[1], EAST);
1507
1567
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; }
1508
1568
  else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1509
1569
  }
1510
- if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1570
+ if (node0flags & HAS_SOUTHERN_NEIGHBOR) {
1511
1571
  const tmp = getCPs(node0neighbors[2], SOUTH);
1512
1572
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; }
1513
1573
  else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1514
1574
  }
1515
- if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1575
+ if (node0flags & HAS_WESTERN_NEIGHBOR) {
1516
1576
  const tmp = getCPs(node0neighbors[3], WEST);
1517
1577
  cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
1518
1578
  }
@@ -1537,24 +1597,24 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1537
1597
  let foundFirst = false;
1538
1598
  let tBaseDir = 0;
1539
1599
  let tBaseNeighborIndex = -1;
1540
- if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
1541
- if ((node0flags & HAS_NORTHERN_SPLINE) === HAS_NORTHERN_SPLINE) { const tmp = getCPs(node0neighbors[0], NORTH); cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1600
+ if (node0flags & HAS_NORTHERN_NEIGHBOR) {
1601
+ if (node0flags & HAS_NORTHERN_SPLINE) { const tmp = getCPs(node0neighbors[0], NORTH); cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1542
1602
  else { tBaseDir = NORTH; tBaseNeighborIndex = node0neighbors[0]; }
1543
1603
  }
1544
- if ((node0flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1545
- if ((node0flags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE) {
1604
+ if (node0flags & HAS_EASTERN_NEIGHBOR) {
1605
+ if (node0flags & HAS_EASTERN_SPLINE) {
1546
1606
  const tmp = getCPs(node0neighbors[1], EAST);
1547
1607
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1548
1608
  } else { tBaseDir = EAST; tBaseNeighborIndex = node0neighbors[1]; }
1549
1609
  }
1550
- if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1551
- if ((node0flags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE) {
1610
+ if (node0flags & HAS_SOUTHERN_NEIGHBOR) {
1611
+ if (node0flags & HAS_SOUTHERN_SPLINE) {
1552
1612
  const tmp = getCPs(node0neighbors[2], SOUTH);
1553
1613
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1554
1614
  } else { tBaseDir = SOUTH; tBaseNeighborIndex = node0neighbors[2]; }
1555
1615
  }
1556
- if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1557
- if ((node0flags & HAS_WESTERN_SPLINE) === HAS_WESTERN_SPLINE) {
1616
+ if (node0flags & HAS_WESTERN_NEIGHBOR) {
1617
+ if (node0flags & HAS_WESTERN_SPLINE) {
1558
1618
  const tmp = getCPs(node0neighbors[3], WEST);
1559
1619
  cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
1560
1620
  } else { tBaseDir = WEST; tBaseNeighborIndex = node0neighbors[3]; }
@@ -1637,13 +1697,13 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1637
1697
  const node1pos = getPos(fragmentBaseKnotIndex + 1);
1638
1698
  if (node1valence === 1) {
1639
1699
  let cpArray = [-1, -1];
1640
- if ((node1flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
1700
+ if (node1flags & HAS_NORTHERN_NEIGHBOR) {
1641
1701
  cpArray = getCPs(node1neighbors[0], NORTH);
1642
- } else if ((node1flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1702
+ } else if (node1flags & HAS_EASTERN_NEIGHBOR) {
1643
1703
  cpArray = getCPs(node1neighbors[1], EAST);
1644
- } else if ((node1flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1704
+ } else if (node1flags & HAS_SOUTHERN_NEIGHBOR) {
1645
1705
  cpArray = getCPs(node1neighbors[2], SOUTH);
1646
- } else if ((node1flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1706
+ } else if (node1flags & HAS_WESTERN_NEIGHBOR) {
1647
1707
  cpArray = getCPs(node1neighbors[3], WEST);
1648
1708
  }
1649
1709
  const p1pos = getPos(cpArray[0]);
@@ -1657,16 +1717,16 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1657
1717
  } else if (node1valence === 2) {
1658
1718
  let cpArray = [-1, -1, -1, -1];
1659
1719
  let foundFirst = false;
1660
- if ((node1flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) { const tmp = getCPs(node1neighbors[0], NORTH); cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1661
- if ((node1flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1720
+ if (node1flags & HAS_NORTHERN_NEIGHBOR) { const tmp = getCPs(node1neighbors[0], NORTH); cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1721
+ if (node1flags & HAS_EASTERN_NEIGHBOR) {
1662
1722
  const tmp = getCPs(node1neighbors[1], EAST);
1663
1723
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1664
1724
  }
1665
- if ((node1flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1725
+ if (node1flags & HAS_SOUTHERN_NEIGHBOR) {
1666
1726
  const tmp = getCPs(node1neighbors[2], SOUTH);
1667
1727
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1668
1728
  }
1669
- if ((node1flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1729
+ if (node1flags & HAS_WESTERN_NEIGHBOR) {
1670
1730
  const tmp = getCPs(node1neighbors[3], WEST);
1671
1731
  cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
1672
1732
  }
@@ -1691,132 +1751,228 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1691
1751
 
1692
1752
  let colorSum = [0, 0, 0, 0];
1693
1753
  let weightSum = 0.0;
1754
+ let maxWeight = 0;
1755
+ let maxSimilarity = null;
1694
1756
 
1695
1757
  function addWeightedColor(px, py) {
1696
- const col = fetchPixelRGBA(src, px, py);
1758
+ const col = fetchPixelRGBA8(src, px, py);
1697
1759
  const dx = cellSpaceCoords[0] - px;
1698
1760
  const dy = cellSpaceCoords[1] - py;
1699
- const dist = Math.hypot(dx, dy);
1700
- const weight = Math.exp(-(dist * dist) * GAUSS_MULTIPLIER);
1761
+ const distSq = dx * dx + dy * dy;
1762
+ const weight = exp(-distSq * GAUSS_MULTIPLIER);
1701
1763
  colorSum[0] += col[0] * weight;
1702
1764
  colorSum[1] += col[1] * weight;
1703
1765
  colorSum[2] += col[2] * weight;
1704
1766
  colorSum[3] += col[3] * weight;
1705
1767
  weightSum += weight;
1768
+ if (outputSimilarityMask && weight >= maxWeight) {
1769
+ // note: would also be reasonable to blend this like we blend the colors
1770
+ maxWeight = weight;
1771
+ maxSimilarity = fetchPixelRGBA8(simImage, px, py);
1772
+ }
1706
1773
  }
1707
1774
 
1708
1775
  if (influencingPixels[0]) {
1709
1776
  addWeightedColor(ULCoords[0], ULCoords[1]);
1710
1777
  const edges = getG(2 * ULCoords[0] + 1, 2 * ULCoords[1] + 1);
1711
- if ((edges & SOUTHWEST) === SOUTHWEST) {
1778
+ if (edges & SOUTHWEST) {
1712
1779
  addWeightedColor(ULCoords[0] - 1, ULCoords[1] - 1);
1713
1780
  }
1714
- if ((edges & WEST) === WEST) {
1781
+ if (edges & WEST) {
1715
1782
  addWeightedColor(ULCoords[0] - 1, ULCoords[1]);
1716
1783
  }
1717
- if ((edges & NORTHWEST) === NORTHWEST) {
1784
+ if (edges & NORTHWEST) {
1718
1785
  addWeightedColor(ULCoords[0] - 1, ULCoords[1] + 1);
1719
1786
  }
1720
- if ((edges & NORTH) === NORTH) {
1787
+ if (edges & NORTH) {
1721
1788
  addWeightedColor(ULCoords[0], ULCoords[1] + 1);
1722
1789
  }
1723
- if ((edges & NORTHEAST) === NORTHEAST) {
1790
+ if (edges & NORTHEAST) {
1724
1791
  addWeightedColor(ULCoords[0] + 1, ULCoords[1] + 1);
1725
1792
  }
1726
1793
  }
1727
1794
  if (influencingPixels[1]) {
1728
1795
  addWeightedColor(URCoords[0], URCoords[1]);
1729
1796
  const edges = getG(2 * URCoords[0] + 1, 2 * URCoords[1] + 1);
1730
- if ((edges & NORTH) === NORTH) {
1797
+ if (edges & NORTH) {
1731
1798
  addWeightedColor(URCoords[0], URCoords[1] + 1);
1732
1799
  }
1733
- if ((edges & NORTHEAST) === NORTHEAST) {
1800
+ if (edges & NORTHEAST) {
1734
1801
  addWeightedColor(URCoords[0] + 1, URCoords[1] + 1);
1735
1802
  }
1736
- if ((edges & EAST) === EAST) {
1803
+ if (edges & EAST) {
1737
1804
  addWeightedColor(URCoords[0] + 1, URCoords[1]);
1738
1805
  }
1739
- if ((edges & SOUTHEAST) === SOUTHEAST) {
1806
+ if (edges & SOUTHEAST) {
1740
1807
  addWeightedColor(URCoords[0] + 1, URCoords[1] - 1);
1741
1808
  }
1742
1809
  }
1743
1810
  if (influencingPixels[2]) {
1744
1811
  addWeightedColor(LLCoords[0], LLCoords[1]);
1745
1812
  const edges = getG(2 * LLCoords[0] + 1, 2 * LLCoords[1] + 1);
1746
- if ((edges & WEST) === WEST) {
1813
+ if (edges & WEST) {
1747
1814
  addWeightedColor(LLCoords[0] - 1, LLCoords[1]);
1748
1815
  }
1749
- if ((edges & SOUTHWEST) === SOUTHWEST) {
1816
+ if (edges & SOUTHWEST) {
1750
1817
  addWeightedColor(LLCoords[0] - 1, LLCoords[1] - 1);
1751
1818
  }
1752
- if ((edges & SOUTH) === SOUTH) {
1819
+ if (edges & SOUTH) {
1753
1820
  addWeightedColor(LLCoords[0], LLCoords[1] - 1);
1754
1821
  }
1755
- if ((edges & SOUTHEAST) === SOUTHEAST) {
1822
+ if (edges & SOUTHEAST) {
1756
1823
  addWeightedColor(LLCoords[0] + 1, LLCoords[1] - 1);
1757
1824
  }
1758
1825
  }
1759
1826
  if (influencingPixels[3]) {
1760
1827
  addWeightedColor(LRCoords[0], LRCoords[1]);
1761
1828
  const edges = getG(2 * LRCoords[0] + 1, 2 * LRCoords[1] + 1);
1762
- if ((edges & NORTHEAST) === NORTHEAST) {
1829
+ if (edges & NORTHEAST) {
1763
1830
  addWeightedColor(LRCoords[0] + 1, LRCoords[1] + 1);
1764
1831
  }
1765
- if ((edges & EAST) === EAST) {
1832
+ if (edges & EAST) {
1766
1833
  addWeightedColor(LRCoords[0] + 1, LRCoords[1]);
1767
1834
  }
1768
- if ((edges & SOUTHWEST) === SOUTHWEST) {
1835
+ if (edges & SOUTHWEST) {
1769
1836
  addWeightedColor(LRCoords[0] - 1, LRCoords[1] - 1);
1770
1837
  }
1771
- if ((edges & SOUTH) === SOUTH) {
1838
+ if (edges & SOUTH) {
1772
1839
  addWeightedColor(LRCoords[0], LRCoords[1] - 1);
1773
1840
  }
1774
- if ((edges & SOUTHEAST) === SOUTHEAST) {
1841
+ if (edges & SOUTHEAST) {
1775
1842
  addWeightedColor(LRCoords[0] + 1, LRCoords[1] - 1);
1776
1843
  }
1777
1844
  }
1778
1845
 
1779
1846
  const outIdx = (oy * outW + ox) * 4;
1780
1847
  if (weightSum === 0) {
1781
- const nx = Math.round(cellSpaceCoords[0]);
1782
- const ny = Math.round(cellSpaceCoords[1]);
1848
+ const nx = round(cellSpaceCoords[0]);
1849
+ const ny = round(cellSpaceCoords[1]);
1783
1850
  const col = fetchPixelRGBA8(src, nx, ny);
1784
1851
  out[outIdx] = col[0];
1785
1852
  out[outIdx + 1] = col[1];
1786
1853
  out[outIdx + 2] = col[2];
1787
1854
  out[outIdx + 3] = col[3];
1855
+ if (outputSimilarityMask) {
1856
+ maxSimilarity = fetchPixelRGBA8(simImage, nx, ny);
1857
+ }
1788
1858
  } else {
1789
- out[outIdx] = clampInt(Math.round((colorSum[0] / weightSum) * 255), 0, 255);
1790
- out[outIdx + 1] = clampInt(Math.round((colorSum[1] / weightSum) * 255), 0, 255);
1791
- out[outIdx + 2] = clampInt(Math.round((colorSum[2] / weightSum) * 255), 0, 255);
1792
- out[outIdx + 3] = clampInt(Math.round((colorSum[3] / weightSum) * 255), 0, 255);
1859
+ out[outIdx] = clampInt(round((colorSum[0] / weightSum)), 0, 255);
1860
+ out[outIdx + 1] = clampInt(round((colorSum[1] / weightSum)), 0, 255);
1861
+ out[outIdx + 2] = clampInt(round((colorSum[2] / weightSum)), 0, 255);
1862
+ out[outIdx + 3] = clampInt(round((colorSum[3] / weightSum)), 0, 255);
1863
+ }
1864
+ if (outputSimilarityMask) {
1865
+ similarityData[outIdx] = maxSimilarity[0];
1866
+ similarityData[outIdx + 1] = maxSimilarity[1];
1867
+ similarityData[outIdx + 2] = maxSimilarity[2];
1868
+ similarityData[outIdx + 3] = maxSimilarity[3];
1793
1869
  }
1794
1870
  }
1795
1871
  }
1796
1872
 
1797
- return out;
1873
+ return {
1874
+ data: out,
1875
+ width: outW,
1876
+ height: outH,
1877
+ allSplines,
1878
+ similarityData,
1879
+ };
1798
1880
  }
1799
1881
 
1800
- function runPipeline(src, outH, threshold) {
1882
+ function renderSplines(out, outW, outH, inW, inH, allSplines) {
1883
+ for (let ii = 0; ii < out.length; ++ii) {
1884
+ out[ii] = 0;
1885
+ }
1886
+
1887
+ function mapToOutput(p) {
1888
+ const x = (p[0] / (inW - 1)) * (outW - 1);
1889
+ const y = (p[1] / (inH - 1)) * (outH - 1);
1890
+ return [x, y];
1891
+ }
1892
+
1893
+ function drawLine(p0, p1) {
1894
+ let x0 = round(p0[0]);
1895
+ let y0 = round(p0[1]);
1896
+ let x1 = round(p1[0]);
1897
+ let y1 = round(p1[1]);
1898
+ const dx = abs(x1 - x0);
1899
+ const dy = abs(y1 - y0);
1900
+ const sx = x0 < x1 ? 1 : -1;
1901
+ const sy = y0 < y1 ? 1 : -1;
1902
+ let err = dx - dy;
1903
+ while (true) {
1904
+ if (x0 >= 0 && x0 < outW && y0 >= 0 && y0 < outH) {
1905
+ const idx = (y0 * outW + x0) * 4;
1906
+ out[idx] = 0;
1907
+ out[idx + 1] = 0;
1908
+ out[idx + 2] = 0;
1909
+ out[idx + 3] = 255;
1910
+ }
1911
+ if (x0 === x1 && y0 === y1) {
1912
+ break;
1913
+ }
1914
+ const e2 = 2 * err;
1915
+ if (e2 > -dy) { err -= dy; x0 += sx; }
1916
+ if (e2 < dx) { err += dx; y0 += sy; }
1917
+ }
1918
+ }
1919
+
1920
+ function calcSplinePoint(p0, p1, p2, t) {
1921
+ const t2 = 0.5 * t * t;
1922
+ const a = t2 - t + 0.5;
1923
+ const b = -2.0 * t2 + t + 0.5;
1924
+ return [a * p0[0] + b * p1[0] + t2 * p2[0], a * p0[1] + b * p1[1] + t2 * p2[1]];
1925
+ }
1926
+
1927
+ for (let i = 0; i < allSplines.length; i++) {
1928
+ const spline = allSplines[i];
1929
+ const p0 = spline[0];
1930
+ const p1 = spline[1];
1931
+ const p2 = spline[2];
1932
+ let prev = null;
1933
+ for (let t = 0.0; t < (1.0 + STEP); t += STEP) {
1934
+ const p = calcSplinePoint(p0, p1, p2, t);
1935
+ const outP = mapToOutput(p);
1936
+ if (prev) {
1937
+ drawLine(prev, outP);
1938
+ }
1939
+ prev = outP;
1940
+ }
1941
+ }
1942
+ }
1943
+
1944
+ function runPipeline(src, outH, threshold, similarity, renderMode, doOpt, outputSimilarityMask) {
1801
1945
  const inW = src.width | 0;
1802
1946
  const inH = src.height | 0;
1803
1947
  const outHeight = outH | 0;
1804
- const outWidth = Math.max(1, Math.round((inW / inH) * outHeight));
1948
+ const outWidth = max(1, round((inW / inH) * outHeight));
1805
1949
 
1806
- const similarityThreshold = (typeof threshold === "number") ? threshold : 255;
1950
+ const similarityThreshold = similarity ? threshold : ((typeof threshold === "number") ? threshold : 255) / 255;
1807
1951
 
1808
- const sim0 = buildSimilarityGraph(src, similarityThreshold);
1952
+ const sim0 = buildSimilarityGraph(src, similarityThreshold, similarity);
1809
1953
  const sim1 = valenceUpdate(sim0);
1810
1954
  const sim2 = eliminateCrossings(sim1);
1811
1955
  const sim3 = valenceUpdate(sim2);
1812
1956
 
1813
1957
  const cell = computeCellGraph(src, sim3);
1814
- const optimized = optimizeCellGraph(cell, inW, inH);
1815
- const corrected = computeCorrectedPositions(cell, optimized);
1958
+ let corrected;
1959
+ if (doOpt) {
1960
+ const optimized = optimizeCellGraph(cell, inW, inH);
1961
+ corrected = computeCorrectedPositions(cell, optimized);
1962
+ } else {
1963
+ corrected = computeCorrectedPositions(cell, cell.pos);
1964
+ }
1816
1965
 
1817
- const outData = gaussRasterize(src, sim3, cell, corrected, outWidth, outHeight);
1966
+ const out = gaussRasterize(
1967
+ src,
1968
+ sim3, cell, corrected, outWidth, outHeight,
1969
+ renderMode === 'splines',
1970
+ outputSimilarityMask, similarity);
1971
+ if (renderMode === 'splines') {
1972
+ renderSplines(out.data, outWidth, outHeight, inW, inH, out.allSplines);
1973
+ }
1818
1974
 
1819
- return { data: outData, width: outWidth, height: outHeight };
1975
+ return out;
1820
1976
  }
1821
1977
 
1822
1978
  function scaleImage(src, opts) {
@@ -1829,30 +1985,47 @@ function scaleImage(src, opts) {
1829
1985
  const inW = src.width | 0;
1830
1986
  const inH = src.height | 0;
1831
1987
  const outH = opts.height | 0;
1832
- const threshold = opts.threshold;
1988
+ const similarity = opts.similarity;
1989
+ const threshold = typeof opts.threshold === 'number' ? opts.threshold : (similarity ? 3 : 255);
1990
+ const renderMode = opts.renderMode || 'default';
1991
+ const outputSimilarityMask = opts.outputSimilarityMask || false;
1992
+ const doOpt = opts.doOpt ?? true;
1993
+ if (outputSimilarityMask && !similarity) {
1994
+ throw new Error('outputSimilarityMask requires a similarity mask input');
1995
+ }
1833
1996
 
1834
- const borderPx = Math.max(0, Math.round(opts.borderPx || 0));
1997
+ const borderPx = max(0, round(opts.borderPx || 0));
1835
1998
  if (borderPx > 0) {
1836
1999
  const padW = inW + 2 * borderPx;
1837
2000
  const padH = inH + 2 * borderPx;
1838
- const padData = new Uint8Array(padW * padH * 4);
2001
+ const padData = Buffer.alloc(padW * padH * 4);
2002
+ const padSimilarity = similarity && Buffer.alloc(padW * padH * 4);
1839
2003
  // copy src into center, expand into borders
1840
2004
  for (let y = 0; y < padH; y++) {
1841
- const srcRow = Math.min(inH - 1, Math.max(0, y - borderPx)) * inW * 4;
2005
+ const srcRow = min(inH - 1, max(0, y - borderPx)) * inW * 4;
1842
2006
  const dstRow = y * padW * 4 + borderPx * 4;
1843
- padData.set(src.data.subarray(srcRow, srcRow + inW * 4), dstRow);
2007
+ src.data.copy(padData, dstRow, srcRow, srcRow + inW * 4);
2008
+ if (padSimilarity) {
2009
+ similarity.copy(padSimilarity, dstRow, srcRow, srcRow + inW * 4);
2010
+ }
1844
2011
  for (let ii = 0; ii < borderPx * 4; ++ii) {
1845
2012
  padData[dstRow - borderPx * 4 + ii] = src.data[srcRow + ii % 4];
1846
- padData[dstRow + inW * 4 + ii] = src.data[srcRow + ii % 4];
2013
+ padData[dstRow + inW * 4 + ii] = src.data[srcRow + (inW - 1) * 4 + ii % 4];
2014
+ if (padSimilarity) {
2015
+ padSimilarity[dstRow - borderPx * 4 + ii] = similarity[srcRow + ii % 4];
2016
+ padSimilarity[dstRow + inW * 4 + ii] = similarity[srcRow + (inW - 1) * 4 + ii % 4];
2017
+ }
1847
2018
  }
1848
2019
  }
1849
2020
  const scale = outH / inH;
1850
- const outHpad = Math.max(1, Math.round(padH * scale));
1851
- const padded = runPipeline({ data: padData, width: padW, height: padH }, outHpad, threshold);
2021
+ const outHpad = max(1, round(padH * scale));
2022
+ const padded = runPipeline({ data: padData, width: padW, height: padH }, outHpad, threshold, padSimilarity, renderMode, doOpt, outputSimilarityMask);
1852
2023
 
1853
- const padOut = Math.max(0, Math.round(borderPx * scale));
1854
- const outW = Math.max(1, Math.round((inW / inH) * outH));
1855
- const cropped = new Uint8Array(outW * outH * 4);
2024
+ const padOut = max(0, round(borderPx * scale));
2025
+ const outW = max(1, round((inW / inH) * outH));
2026
+ const cropped = Buffer.alloc(outW * outH * 4);
2027
+ const croppedSimilarity = padded.similarityData && Buffer.alloc(outW * outH * 4);
2028
+ const srcData = padded.data;
1856
2029
 
1857
2030
  for (let y = 0; y < outH; y++) {
1858
2031
  const srcY = y + padOut;
@@ -1861,15 +2034,28 @@ function scaleImage(src, opts) {
1861
2034
  }
1862
2035
  const srcRow = (srcY * padded.width + padOut) * 4;
1863
2036
  const dstRow = y * outW * 4;
1864
- const len = Math.min(outW, Math.max(0, padded.width - padOut)) * 4;
1865
- cropped.set(padded.data.subarray(srcRow, srcRow + len), dstRow);
2037
+ const len = min(outW, max(0, padded.width - padOut)) * 4;
2038
+ srcData.copy(cropped, dstRow, srcRow, srcRow + len);
2039
+ if (padded.similarityData) {
2040
+ padded.similarityData.copy(croppedSimilarity, dstRow, srcRow, srcRow + len);
2041
+ }
1866
2042
  }
1867
2043
 
1868
- return { data: Buffer.from(cropped), width: outW, height: outH };
2044
+ return {
2045
+ data: cropped,
2046
+ width: outW,
2047
+ height: outH,
2048
+ similarityData: croppedSimilarity,
2049
+ };
1869
2050
  }
1870
2051
 
1871
- const result = runPipeline(src, outH, threshold);
1872
- return { data: Buffer.from(result.data), width: result.width, height: result.height };
2052
+ const result = runPipeline(src, outH, threshold, similarity, renderMode, doOpt, outputSimilarityMask);
2053
+ return {
2054
+ data: result.data,
2055
+ width: result.width,
2056
+ height: result.height,
2057
+ similarityData: result.similarityData,
2058
+ };
1873
2059
  }
1874
2060
 
1875
2061
  exports.scaleImage = scaleImage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depixel",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Depixelizing Pixel Art by Kopf-Lischinski and Felix Kreuzer for Node.js",
5
5
  "main": "lib.js",
6
6
  "keywords": [