depixel 1.0.4 → 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 +345 -177
  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,12 +3,17 @@ 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;
@@ -149,7 +154,7 @@ function isContour(src, pL, pR) {
149
154
  return dist_sq > THRESHOLD_CONTOUR_SQ;
150
155
  }
151
156
 
152
- function buildSimilarityGraph(src, similarityThreshold) {
157
+ function buildSimilarityGraph(src, similarityThreshold, similarity) {
153
158
  const w = src.width;
154
159
  const h = src.height;
155
160
  const sgW = 2 * w + 1;
@@ -167,6 +172,24 @@ function buildSimilarityGraph(src, similarityThreshold) {
167
172
  return [floor((gx - 1) / 2), floor((gy - 1) / 2)];
168
173
  }
169
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
+ }
191
+ }
192
+
170
193
  for (let y = 0; y < sgH; y++) {
171
194
  for (let x = 0; x < sgW; x++) {
172
195
  if (x === 0 || x === sgW - 1 || y === 0 || y === sgH - 1) {
@@ -181,19 +204,19 @@ function buildSimilarityGraph(src, similarityThreshold) {
181
204
  let diagonal = 0;
182
205
  let pA = getPixelCoords(x - 1, y + 1);
183
206
  let pB = getPixelCoords(x + 1, y - 1);
184
- if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
207
+ if (isSimilar2(pA, pB)) {
185
208
  diagonal = EDGE_DIAGONAL_ULLR;
186
209
  }
187
210
  pA = getPixelCoords(x - 1, y - 1);
188
211
  pB = getPixelCoords(x + 1, y + 1);
189
- if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
212
+ if (isSimilar2(pA, pB)) {
190
213
  diagonal |= EDGE_DIAGONAL_LLUR;
191
214
  }
192
215
  setRG(x, y, diagonal, 0);
193
216
  } else if (evalX === 0 && evalY === 1) {
194
217
  const pA = getPixelCoords(x - 1, y);
195
218
  const pB = getPixelCoords(x + 1, y);
196
- if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
219
+ if (isSimilar2(pA, pB)) {
197
220
  setRG(x, y, EDGE_HORVERT, 0);
198
221
  } else {
199
222
  setRG(x, y, 0, 0);
@@ -201,7 +224,7 @@ function buildSimilarityGraph(src, similarityThreshold) {
201
224
  } else if (evalX === 1 && evalY === 0) {
202
225
  const pA = getPixelCoords(x, y - 1);
203
226
  const pB = getPixelCoords(x, y + 1);
204
- if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
227
+ if (isSimilar2(pA, pB)) {
205
228
  setRG(x, y, EDGE_HORVERT, 0);
206
229
  } else {
207
230
  setRG(x, y, 0, 0);
@@ -386,20 +409,20 @@ function eliminateCrossings(sim) {
386
409
  nNE = 8 * (2 - level) + (4 - level);
387
410
  nE = 8 * (3 - level) + (4 - level);
388
411
  if (currentComponent === 0) {
389
- if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
390
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
391
- else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
392
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
393
- else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
394
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
395
- 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); }
396
419
  }
397
420
  if (currentComponent !== 0) {
398
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
399
- if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
400
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
401
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
402
- 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); } }
403
426
  }
404
427
  if (level > 0) {
405
428
  for (let i = 0; i < level * 2; i++) {
@@ -414,16 +437,16 @@ function eliminateCrossings(sim) {
414
437
  nNE = 8 * (2 - level) + (i + 5 - level);
415
438
  nE = 8 * (3 - level) + (i + 5 - level);
416
439
  if (currentComponent === 0) {
417
- if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
418
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
419
- else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
420
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
421
- 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); }
422
445
  }
423
446
  if (currentComponent !== 0) {
424
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
425
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
426
- 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); } }
427
450
  }
428
451
  }
429
452
  }
@@ -441,20 +464,20 @@ function eliminateCrossings(sim) {
441
464
  nSE = 8 * (4 - level) + (5 + level);
442
465
  nS = 8 * (4 - level) + (4 + level);
443
466
  if (currentComponent === 0) {
444
- if (((nhood & WEST) === WEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
445
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
446
- else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
447
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
448
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
449
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
450
- 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); }
451
474
  }
452
475
  if (currentComponent !== 0) {
453
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
454
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
455
- if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
456
- if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
457
- 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); } }
458
481
  }
459
482
  if (level > 0) {
460
483
  for (let i = 0; i < level * 2; i++) {
@@ -469,16 +492,16 @@ function eliminateCrossings(sim) {
469
492
  nSE = 8 * (i + 5 - level) + (5 + level);
470
493
  nS = 8 * (i + 5 - level) + (4 + level);
471
494
  if (currentComponent === 0) {
472
- if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
473
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
474
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
475
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
476
- 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); }
477
500
  }
478
501
  if (currentComponent !== 0) {
479
- if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
480
- if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
481
- 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); } }
482
505
  }
483
506
  }
484
507
  }
@@ -496,20 +519,20 @@ function eliminateCrossings(sim) {
496
519
  nSW = 8 * (5 + level) + (3 + level);
497
520
  nW = 8 * (4 + level) + (3 + level);
498
521
  if (currentComponent === 0) {
499
- if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
500
- else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
501
- else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
502
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
503
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
504
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
505
- 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); }
506
529
  }
507
530
  if (currentComponent !== 0) {
508
- if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
509
- if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
510
- if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
511
- if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
512
- 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); } }
513
536
  }
514
537
 
515
538
  if (level > 0) {
@@ -525,18 +548,18 @@ function eliminateCrossings(sim) {
525
548
  nSW = 8 * (5 + level) + (i + 3 - level);
526
549
  nW = 8 * (4 + level) + (i + 3 - level);
527
550
  if (currentComponent === 0) {
528
- if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
529
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
530
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
531
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
532
- 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); }
533
556
  }
534
557
  if (currentComponent !== 0) {
535
- if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
536
- if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
537
- if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
538
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
539
- 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); } }
540
563
  }
541
564
  }
542
565
  }
@@ -554,20 +577,20 @@ function eliminateCrossings(sim) {
554
577
  nSE = 8 * (5 + level) + (4 - level);
555
578
  nE = 8 * (4 + level) + (4 - level);
556
579
  if (currentComponent === 0) {
557
- if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
558
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
559
- else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
560
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
561
- else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
562
- else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
563
- 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); }
564
587
  }
565
588
  if (currentComponent !== 0) {
566
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
567
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
568
- if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
569
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
570
- 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); } }
571
594
  }
572
595
 
573
596
  if (level > 0) {
@@ -583,18 +606,18 @@ function eliminateCrossings(sim) {
583
606
  nSW = 8 * (i + 5 - level) + (2 - level);
584
607
  nS = 8 * (i + 5 - level) + (3 - level);
585
608
  if (currentComponent === 0) {
586
- if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
587
- else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
588
- else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
589
- else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
590
- 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); }
591
614
  }
592
615
  if (currentComponent !== 0) {
593
- if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
594
- if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
595
- if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
596
- if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
597
- 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); } }
598
621
  }
599
622
  }
600
623
  }
@@ -1192,7 +1215,7 @@ function optimizeCellGraph(cell, width, height) {
1192
1215
 
1193
1216
  for (let i = 0; i < count; i++) {
1194
1217
  const flags = cell.flags[i];
1195
- if (flags > 16 && flags < 512) {
1218
+ if (flags & (HAS_NORTHERN_SPLINE|HAS_EASTERN_SPLINE|HAS_SOUTHERN_SPLINE|HAS_WESTERN_SPLINE)) {
1196
1219
  const base = i * 4;
1197
1220
  const neighbors = [cell.neighbors[base], cell.neighbors[base + 1], cell.neighbors[base + 2], cell.neighbors[base + 3]];
1198
1221
  const pos = getPosOpt(i);
@@ -1201,18 +1224,18 @@ function optimizeCellGraph(cell, width, height) {
1201
1224
  let splineCount = 0;
1202
1225
  let splineNoOpt = false;
1203
1226
 
1204
- const hasN = (flags & HAS_NORTHERN_SPLINE) === HAS_NORTHERN_SPLINE;
1205
- const hasE = (flags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE;
1206
- const hasS = (flags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE;
1207
- 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);
1208
1231
 
1209
1232
  if (hasN) {
1210
1233
  const neighborflags = cell.flags[neighbors[0]] | 0;
1211
- 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)) {
1212
1235
  splineNoOpt = true;
1213
1236
  }
1214
1237
  if (!splineNoOpt) {
1215
- 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)) {
1216
1239
  splineNeighbors[splineCount++] = getPos(neighbors[0] + 1);
1217
1240
  } else {
1218
1241
  splineNeighbors[splineCount++] = getPos(neighbors[0]);
@@ -1221,11 +1244,11 @@ function optimizeCellGraph(cell, width, height) {
1221
1244
  }
1222
1245
  if (hasE) {
1223
1246
  const neighborflags = cell.flags[neighbors[1]] | 0;
1224
- 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)) {
1225
1248
  splineNoOpt = true;
1226
1249
  }
1227
1250
  if (!splineNoOpt) {
1228
- 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)) {
1229
1252
  splineNeighbors[splineCount++] = getPos(neighbors[1] + 1);
1230
1253
  } else {
1231
1254
  splineNeighbors[splineCount++] = getPos(neighbors[1]);
@@ -1234,11 +1257,11 @@ function optimizeCellGraph(cell, width, height) {
1234
1257
  }
1235
1258
  if (hasS) {
1236
1259
  const neighborflags = cell.flags[neighbors[2]] | 0;
1237
- 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)) {
1238
1261
  splineNoOpt = true;
1239
1262
  }
1240
1263
  if (!splineNoOpt) {
1241
- 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)) {
1242
1265
  splineNeighbors[splineCount++] = getPos(neighbors[2] + 1);
1243
1266
  } else {
1244
1267
  splineNeighbors[splineCount++] = getPos(neighbors[2]);
@@ -1247,11 +1270,11 @@ function optimizeCellGraph(cell, width, height) {
1247
1270
  }
1248
1271
  if (hasW) {
1249
1272
  const neighborflags = cell.flags[neighbors[3]] | 0;
1250
- 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)) {
1251
1274
  splineNoOpt = true;
1252
1275
  }
1253
1276
  if (!splineNoOpt) {
1254
- 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)) {
1255
1278
  splineNeighbors[splineCount++] = getPos(neighbors[3] + 1);
1256
1279
  } else {
1257
1280
  splineNeighbors[splineCount++] = getPos(neighbors[3]);
@@ -1276,7 +1299,7 @@ function computeCorrectedPositions(cell, optimized) {
1276
1299
  corrected.set(optimized);
1277
1300
 
1278
1301
  function getPos(idx) {
1279
- return [cell.pos[idx * 2], cell.pos[idx * 2 + 1]];
1302
+ return [optimized[idx * 2], optimized[idx * 2 + 1]];
1280
1303
  }
1281
1304
 
1282
1305
  function calcAdjustedPoint(p0, p1, p2) {
@@ -1293,16 +1316,16 @@ function computeCorrectedPositions(cell, optimized) {
1293
1316
  const parentNeighborIndices = [cell.neighbors[base], cell.neighbors[base + 1], cell.neighbors[base + 2], cell.neighbors[base + 3]];
1294
1317
  const splinePoints = [null, null];
1295
1318
  let countSp = 0;
1296
- if ((parentFlags & HAS_NORTHERN_SPLINE) === HAS_NORTHERN_SPLINE) {
1319
+ if (parentFlags & HAS_NORTHERN_SPLINE) {
1297
1320
  splinePoints[countSp++] = getPos(parentNeighborIndices[0]);
1298
1321
  }
1299
- if ((parentFlags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE) {
1322
+ if (parentFlags & HAS_EASTERN_SPLINE) {
1300
1323
  splinePoints[countSp++] = getPos(parentNeighborIndices[1]);
1301
1324
  }
1302
- if ((parentFlags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE) {
1325
+ if (parentFlags & HAS_SOUTHERN_SPLINE) {
1303
1326
  splinePoints[countSp++] = getPos(parentNeighborIndices[2]);
1304
1327
  }
1305
- if ((parentFlags & HAS_WESTERN_SPLINE) === HAS_WESTERN_SPLINE) {
1328
+ if (parentFlags & HAS_WESTERN_SPLINE) {
1306
1329
  splinePoints[countSp++] = getPos(parentNeighborIndices[3]);
1307
1330
  }
1308
1331
  if (countSp === 2) {
@@ -1319,10 +1342,10 @@ function computeCorrectedPositions(cell, optimized) {
1319
1342
  return corrected;
1320
1343
  }
1321
1344
 
1322
- function gaussRasterize(src, sim, cell, positions, outW, outH) {
1345
+ function gaussRasterize(src, sim, cell, positions, outW, outH, needSplines, outputSimilarityMask, similarity) {
1323
1346
  const w = src.width;
1324
1347
  const h = src.height;
1325
- const out = new Uint8Array(outW * outH * 4);
1348
+ const out = Buffer.alloc(outW * outH * 4);
1326
1349
 
1327
1350
  function getG(x, y) {
1328
1351
  if (x < 0 || y < 0 || x >= sim.w || y >= sim.h) {
@@ -1380,16 +1403,16 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1380
1403
 
1381
1404
  function computeValence(flags) {
1382
1405
  let v = 0;
1383
- if ((flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
1406
+ if (flags & HAS_NORTHERN_NEIGHBOR) {
1384
1407
  v++;
1385
1408
  }
1386
- if ((flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1409
+ if (flags & HAS_EASTERN_NEIGHBOR) {
1387
1410
  v++;
1388
1411
  }
1389
- if ((flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1412
+ if (flags & HAS_SOUTHERN_NEIGHBOR) {
1390
1413
  v++;
1391
1414
  }
1392
- if ((flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1415
+ if (flags & HAS_WESTERN_NEIGHBOR) {
1393
1416
  v++;
1394
1417
  }
1395
1418
  return v;
@@ -1422,7 +1445,7 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1422
1445
  if ((node0neighborFlags & checkFwd[0]) === checkFwd[0]) {
1423
1446
  const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, dir);
1424
1447
  const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
1425
- if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
1448
+ if (neighborsNeighborflags & HAS_CORRECTED_POSITION) {
1426
1449
  cpArray[1] = neighborsNeighborIndex + 1;
1427
1450
  } else {
1428
1451
  cpArray[1] = neighborsNeighborIndex;
@@ -1430,7 +1453,7 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1430
1453
  } else if ((node0neighborFlags & checkFwd[1]) === checkFwd[1]) {
1431
1454
  const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, chkdirs[0]);
1432
1455
  const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
1433
- if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
1456
+ if (neighborsNeighborflags & HAS_CORRECTED_POSITION) {
1434
1457
  cpArray[1] = neighborsNeighborIndex + 1;
1435
1458
  } else {
1436
1459
  cpArray[1] = neighborsNeighborIndex;
@@ -1438,20 +1461,32 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1438
1461
  } else if ((node0neighborFlags & checkFwd[2]) === checkFwd[2]) {
1439
1462
  const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, chkdirs[1]);
1440
1463
  const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
1441
- if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
1464
+ if (neighborsNeighborflags & HAS_CORRECTED_POSITION) {
1442
1465
  cpArray[1] = neighborsNeighborIndex + 1;
1443
1466
  } else {
1444
1467
  cpArray[1] = neighborsNeighborIndex;
1445
1468
  }
1446
1469
  }
1447
1470
  } else {
1448
- if ((node0neighborFlags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
1471
+ if (node0neighborFlags & HAS_CORRECTED_POSITION) {
1449
1472
  cpArray[0]++;
1450
1473
  }
1451
1474
  }
1452
1475
  return cpArray;
1453
1476
  }
1454
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
+
1455
1490
  for (let oy = 0; oy < outH; oy++) {
1456
1491
  for (let ox = 0; ox < outW; ox++) {
1457
1492
  let influencingPixels = [true, true, true, true];
@@ -1469,6 +1504,13 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1469
1504
  const LRCoords = [ceil(cellSpaceCoords[0]), floor(cellSpaceCoords[1])];
1470
1505
 
1471
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
+ }
1472
1514
  let pointA = calcSplinePoint(p0, p1, p2, 0.0);
1473
1515
  for (let t = STEP; t < (1.0 + STEP); t += STEP) {
1474
1516
  const pointB = calcSplinePoint(p0, p1, p2, t);
@@ -1499,13 +1541,13 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1499
1541
  let node0pos = getPos(fragmentBaseKnotIndex);
1500
1542
  if (node0valence === 1) {
1501
1543
  let cpArray = [-1, -1];
1502
- if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
1544
+ if (node0flags & HAS_NORTHERN_NEIGHBOR) {
1503
1545
  cpArray = getCPs(node0neighbors[0], NORTH);
1504
- } else if ((node0flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1546
+ } else if (node0flags & HAS_EASTERN_NEIGHBOR) {
1505
1547
  cpArray = getCPs(node0neighbors[1], EAST);
1506
- } else if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1548
+ } else if (node0flags & HAS_SOUTHERN_NEIGHBOR) {
1507
1549
  cpArray = getCPs(node0neighbors[2], SOUTH);
1508
- } else if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1550
+ } else if (node0flags & HAS_WESTERN_NEIGHBOR) {
1509
1551
  cpArray = getCPs(node0neighbors[3], WEST);
1510
1552
  }
1511
1553
  const p1pos = getPos(cpArray[0]);
@@ -1519,18 +1561,18 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1519
1561
  } else if (node0valence === 2) {
1520
1562
  let cpArray = [-1, -1, -1, -1];
1521
1563
  let foundFirst = false;
1522
- if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) { cpArray[0] = getCPs(node0neighbors[0], NORTH)[0]; cpArray[1] = getCPs(node0neighbors[0], NORTH)[1]; foundFirst = true; }
1523
- 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) {
1524
1566
  const tmp = getCPs(node0neighbors[1], EAST);
1525
1567
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; }
1526
1568
  else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1527
1569
  }
1528
- if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1570
+ if (node0flags & HAS_SOUTHERN_NEIGHBOR) {
1529
1571
  const tmp = getCPs(node0neighbors[2], SOUTH);
1530
1572
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; }
1531
1573
  else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1532
1574
  }
1533
- if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1575
+ if (node0flags & HAS_WESTERN_NEIGHBOR) {
1534
1576
  const tmp = getCPs(node0neighbors[3], WEST);
1535
1577
  cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
1536
1578
  }
@@ -1555,24 +1597,24 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1555
1597
  let foundFirst = false;
1556
1598
  let tBaseDir = 0;
1557
1599
  let tBaseNeighborIndex = -1;
1558
- if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
1559
- 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; }
1560
1602
  else { tBaseDir = NORTH; tBaseNeighborIndex = node0neighbors[0]; }
1561
1603
  }
1562
- if ((node0flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1563
- if ((node0flags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE) {
1604
+ if (node0flags & HAS_EASTERN_NEIGHBOR) {
1605
+ if (node0flags & HAS_EASTERN_SPLINE) {
1564
1606
  const tmp = getCPs(node0neighbors[1], EAST);
1565
1607
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1566
1608
  } else { tBaseDir = EAST; tBaseNeighborIndex = node0neighbors[1]; }
1567
1609
  }
1568
- if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1569
- if ((node0flags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE) {
1610
+ if (node0flags & HAS_SOUTHERN_NEIGHBOR) {
1611
+ if (node0flags & HAS_SOUTHERN_SPLINE) {
1570
1612
  const tmp = getCPs(node0neighbors[2], SOUTH);
1571
1613
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1572
1614
  } else { tBaseDir = SOUTH; tBaseNeighborIndex = node0neighbors[2]; }
1573
1615
  }
1574
- if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1575
- if ((node0flags & HAS_WESTERN_SPLINE) === HAS_WESTERN_SPLINE) {
1616
+ if (node0flags & HAS_WESTERN_NEIGHBOR) {
1617
+ if (node0flags & HAS_WESTERN_SPLINE) {
1576
1618
  const tmp = getCPs(node0neighbors[3], WEST);
1577
1619
  cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
1578
1620
  } else { tBaseDir = WEST; tBaseNeighborIndex = node0neighbors[3]; }
@@ -1655,13 +1697,13 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1655
1697
  const node1pos = getPos(fragmentBaseKnotIndex + 1);
1656
1698
  if (node1valence === 1) {
1657
1699
  let cpArray = [-1, -1];
1658
- if ((node1flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
1700
+ if (node1flags & HAS_NORTHERN_NEIGHBOR) {
1659
1701
  cpArray = getCPs(node1neighbors[0], NORTH);
1660
- } else if ((node1flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
1702
+ } else if (node1flags & HAS_EASTERN_NEIGHBOR) {
1661
1703
  cpArray = getCPs(node1neighbors[1], EAST);
1662
- } else if ((node1flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1704
+ } else if (node1flags & HAS_SOUTHERN_NEIGHBOR) {
1663
1705
  cpArray = getCPs(node1neighbors[2], SOUTH);
1664
- } else if ((node1flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1706
+ } else if (node1flags & HAS_WESTERN_NEIGHBOR) {
1665
1707
  cpArray = getCPs(node1neighbors[3], WEST);
1666
1708
  }
1667
1709
  const p1pos = getPos(cpArray[0]);
@@ -1675,16 +1717,16 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1675
1717
  } else if (node1valence === 2) {
1676
1718
  let cpArray = [-1, -1, -1, -1];
1677
1719
  let foundFirst = false;
1678
- if ((node1flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) { const tmp = getCPs(node1neighbors[0], NORTH); cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1679
- 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) {
1680
1722
  const tmp = getCPs(node1neighbors[1], EAST);
1681
1723
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1682
1724
  }
1683
- if ((node1flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
1725
+ if (node1flags & HAS_SOUTHERN_NEIGHBOR) {
1684
1726
  const tmp = getCPs(node1neighbors[2], SOUTH);
1685
1727
  if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
1686
1728
  }
1687
- if ((node1flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
1729
+ if (node1flags & HAS_WESTERN_NEIGHBOR) {
1688
1730
  const tmp = getCPs(node1neighbors[3], WEST);
1689
1731
  cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
1690
1732
  }
@@ -1709,6 +1751,8 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1709
1751
 
1710
1752
  let colorSum = [0, 0, 0, 0];
1711
1753
  let weightSum = 0.0;
1754
+ let maxWeight = 0;
1755
+ let maxSimilarity = null;
1712
1756
 
1713
1757
  function addWeightedColor(px, py) {
1714
1758
  const col = fetchPixelRGBA8(src, px, py);
@@ -1721,75 +1765,80 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1721
1765
  colorSum[2] += col[2] * weight;
1722
1766
  colorSum[3] += col[3] * weight;
1723
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
+ }
1724
1773
  }
1725
1774
 
1726
1775
  if (influencingPixels[0]) {
1727
1776
  addWeightedColor(ULCoords[0], ULCoords[1]);
1728
1777
  const edges = getG(2 * ULCoords[0] + 1, 2 * ULCoords[1] + 1);
1729
- if ((edges & SOUTHWEST) === SOUTHWEST) {
1778
+ if (edges & SOUTHWEST) {
1730
1779
  addWeightedColor(ULCoords[0] - 1, ULCoords[1] - 1);
1731
1780
  }
1732
- if ((edges & WEST) === WEST) {
1781
+ if (edges & WEST) {
1733
1782
  addWeightedColor(ULCoords[0] - 1, ULCoords[1]);
1734
1783
  }
1735
- if ((edges & NORTHWEST) === NORTHWEST) {
1784
+ if (edges & NORTHWEST) {
1736
1785
  addWeightedColor(ULCoords[0] - 1, ULCoords[1] + 1);
1737
1786
  }
1738
- if ((edges & NORTH) === NORTH) {
1787
+ if (edges & NORTH) {
1739
1788
  addWeightedColor(ULCoords[0], ULCoords[1] + 1);
1740
1789
  }
1741
- if ((edges & NORTHEAST) === NORTHEAST) {
1790
+ if (edges & NORTHEAST) {
1742
1791
  addWeightedColor(ULCoords[0] + 1, ULCoords[1] + 1);
1743
1792
  }
1744
1793
  }
1745
1794
  if (influencingPixels[1]) {
1746
1795
  addWeightedColor(URCoords[0], URCoords[1]);
1747
1796
  const edges = getG(2 * URCoords[0] + 1, 2 * URCoords[1] + 1);
1748
- if ((edges & NORTH) === NORTH) {
1797
+ if (edges & NORTH) {
1749
1798
  addWeightedColor(URCoords[0], URCoords[1] + 1);
1750
1799
  }
1751
- if ((edges & NORTHEAST) === NORTHEAST) {
1800
+ if (edges & NORTHEAST) {
1752
1801
  addWeightedColor(URCoords[0] + 1, URCoords[1] + 1);
1753
1802
  }
1754
- if ((edges & EAST) === EAST) {
1803
+ if (edges & EAST) {
1755
1804
  addWeightedColor(URCoords[0] + 1, URCoords[1]);
1756
1805
  }
1757
- if ((edges & SOUTHEAST) === SOUTHEAST) {
1806
+ if (edges & SOUTHEAST) {
1758
1807
  addWeightedColor(URCoords[0] + 1, URCoords[1] - 1);
1759
1808
  }
1760
1809
  }
1761
1810
  if (influencingPixels[2]) {
1762
1811
  addWeightedColor(LLCoords[0], LLCoords[1]);
1763
1812
  const edges = getG(2 * LLCoords[0] + 1, 2 * LLCoords[1] + 1);
1764
- if ((edges & WEST) === WEST) {
1813
+ if (edges & WEST) {
1765
1814
  addWeightedColor(LLCoords[0] - 1, LLCoords[1]);
1766
1815
  }
1767
- if ((edges & SOUTHWEST) === SOUTHWEST) {
1816
+ if (edges & SOUTHWEST) {
1768
1817
  addWeightedColor(LLCoords[0] - 1, LLCoords[1] - 1);
1769
1818
  }
1770
- if ((edges & SOUTH) === SOUTH) {
1819
+ if (edges & SOUTH) {
1771
1820
  addWeightedColor(LLCoords[0], LLCoords[1] - 1);
1772
1821
  }
1773
- if ((edges & SOUTHEAST) === SOUTHEAST) {
1822
+ if (edges & SOUTHEAST) {
1774
1823
  addWeightedColor(LLCoords[0] + 1, LLCoords[1] - 1);
1775
1824
  }
1776
1825
  }
1777
1826
  if (influencingPixels[3]) {
1778
1827
  addWeightedColor(LRCoords[0], LRCoords[1]);
1779
1828
  const edges = getG(2 * LRCoords[0] + 1, 2 * LRCoords[1] + 1);
1780
- if ((edges & NORTHEAST) === NORTHEAST) {
1829
+ if (edges & NORTHEAST) {
1781
1830
  addWeightedColor(LRCoords[0] + 1, LRCoords[1] + 1);
1782
1831
  }
1783
- if ((edges & EAST) === EAST) {
1832
+ if (edges & EAST) {
1784
1833
  addWeightedColor(LRCoords[0] + 1, LRCoords[1]);
1785
1834
  }
1786
- if ((edges & SOUTHWEST) === SOUTHWEST) {
1835
+ if (edges & SOUTHWEST) {
1787
1836
  addWeightedColor(LRCoords[0] - 1, LRCoords[1] - 1);
1788
1837
  }
1789
- if ((edges & SOUTH) === SOUTH) {
1838
+ if (edges & SOUTH) {
1790
1839
  addWeightedColor(LRCoords[0], LRCoords[1] - 1);
1791
1840
  }
1792
- if ((edges & SOUTHEAST) === SOUTHEAST) {
1841
+ if (edges & SOUTHEAST) {
1793
1842
  addWeightedColor(LRCoords[0] + 1, LRCoords[1] - 1);
1794
1843
  }
1795
1844
  }
@@ -1803,38 +1852,127 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
1803
1852
  out[outIdx + 1] = col[1];
1804
1853
  out[outIdx + 2] = col[2];
1805
1854
  out[outIdx + 3] = col[3];
1855
+ if (outputSimilarityMask) {
1856
+ maxSimilarity = fetchPixelRGBA8(simImage, nx, ny);
1857
+ }
1806
1858
  } else {
1807
1859
  out[outIdx] = clampInt(round((colorSum[0] / weightSum)), 0, 255);
1808
1860
  out[outIdx + 1] = clampInt(round((colorSum[1] / weightSum)), 0, 255);
1809
1861
  out[outIdx + 2] = clampInt(round((colorSum[2] / weightSum)), 0, 255);
1810
1862
  out[outIdx + 3] = clampInt(round((colorSum[3] / weightSum)), 0, 255);
1811
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];
1869
+ }
1812
1870
  }
1813
1871
  }
1814
1872
 
1815
- return out;
1873
+ return {
1874
+ data: out,
1875
+ width: outW,
1876
+ height: outH,
1877
+ allSplines,
1878
+ similarityData,
1879
+ };
1880
+ }
1881
+
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
+ }
1816
1942
  }
1817
1943
 
1818
- function runPipeline(src, outH, threshold) {
1944
+ function runPipeline(src, outH, threshold, similarity, renderMode, doOpt, outputSimilarityMask) {
1819
1945
  const inW = src.width | 0;
1820
1946
  const inH = src.height | 0;
1821
1947
  const outHeight = outH | 0;
1822
1948
  const outWidth = max(1, round((inW / inH) * outHeight));
1823
1949
 
1824
- const similarityThreshold = ((typeof threshold === "number") ? threshold : 255) / 255;
1950
+ const similarityThreshold = similarity ? threshold : ((typeof threshold === "number") ? threshold : 255) / 255;
1825
1951
 
1826
- const sim0 = buildSimilarityGraph(src, similarityThreshold);
1952
+ const sim0 = buildSimilarityGraph(src, similarityThreshold, similarity);
1827
1953
  const sim1 = valenceUpdate(sim0);
1828
1954
  const sim2 = eliminateCrossings(sim1);
1829
1955
  const sim3 = valenceUpdate(sim2);
1830
1956
 
1831
1957
  const cell = computeCellGraph(src, sim3);
1832
- const optimized = optimizeCellGraph(cell, inW, inH);
1833
- 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
+ }
1834
1965
 
1835
- 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
+ }
1836
1974
 
1837
- return { data: outData, width: outWidth, height: outHeight };
1975
+ return out;
1838
1976
  }
1839
1977
 
1840
1978
  function scaleImage(src, opts) {
@@ -1847,30 +1985,47 @@ function scaleImage(src, opts) {
1847
1985
  const inW = src.width | 0;
1848
1986
  const inH = src.height | 0;
1849
1987
  const outH = opts.height | 0;
1850
- 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
+ }
1851
1996
 
1852
1997
  const borderPx = max(0, round(opts.borderPx || 0));
1853
1998
  if (borderPx > 0) {
1854
1999
  const padW = inW + 2 * borderPx;
1855
2000
  const padH = inH + 2 * borderPx;
1856
- const padData = new Uint8Array(padW * padH * 4);
2001
+ const padData = Buffer.alloc(padW * padH * 4);
2002
+ const padSimilarity = similarity && Buffer.alloc(padW * padH * 4);
1857
2003
  // copy src into center, expand into borders
1858
2004
  for (let y = 0; y < padH; y++) {
1859
2005
  const srcRow = min(inH - 1, max(0, y - borderPx)) * inW * 4;
1860
2006
  const dstRow = y * padW * 4 + borderPx * 4;
1861
- 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
+ }
1862
2011
  for (let ii = 0; ii < borderPx * 4; ++ii) {
1863
2012
  padData[dstRow - borderPx * 4 + ii] = src.data[srcRow + ii % 4];
1864
- 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
+ }
1865
2018
  }
1866
2019
  }
1867
2020
  const scale = outH / inH;
1868
2021
  const outHpad = max(1, round(padH * scale));
1869
- const padded = runPipeline({ data: padData, width: padW, height: padH }, outHpad, threshold);
2022
+ const padded = runPipeline({ data: padData, width: padW, height: padH }, outHpad, threshold, padSimilarity, renderMode, doOpt, outputSimilarityMask);
1870
2023
 
1871
2024
  const padOut = max(0, round(borderPx * scale));
1872
2025
  const outW = max(1, round((inW / inH) * outH));
1873
- const cropped = new Uint8Array(outW * outH * 4);
2026
+ const cropped = Buffer.alloc(outW * outH * 4);
2027
+ const croppedSimilarity = padded.similarityData && Buffer.alloc(outW * outH * 4);
2028
+ const srcData = padded.data;
1874
2029
 
1875
2030
  for (let y = 0; y < outH; y++) {
1876
2031
  const srcY = y + padOut;
@@ -1880,14 +2035,27 @@ function scaleImage(src, opts) {
1880
2035
  const srcRow = (srcY * padded.width + padOut) * 4;
1881
2036
  const dstRow = y * outW * 4;
1882
2037
  const len = min(outW, max(0, padded.width - padOut)) * 4;
1883
- cropped.set(padded.data.subarray(srcRow, srcRow + len), dstRow);
2038
+ srcData.copy(cropped, dstRow, srcRow, srcRow + len);
2039
+ if (padded.similarityData) {
2040
+ padded.similarityData.copy(croppedSimilarity, dstRow, srcRow, srcRow + len);
2041
+ }
1884
2042
  }
1885
2043
 
1886
- return { data: Buffer.from(cropped), width: outW, height: outH };
2044
+ return {
2045
+ data: cropped,
2046
+ width: outW,
2047
+ height: outH,
2048
+ similarityData: croppedSimilarity,
2049
+ };
1887
2050
  }
1888
2051
 
1889
- const result = runPipeline(src, outH, threshold);
1890
- 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
+ };
1891
2059
  }
1892
2060
 
1893
2061
  exports.scaleImage = scaleImage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depixel",
3
- "version": "1.0.4",
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": [