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.
- package/README.md +19 -2
- package/lib.js +406 -220
- 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
|
|
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]
|
|
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
|
-
|
|
101
|
-
|
|
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 (
|
|
108
|
-
if (
|
|
109
|
-
if (
|
|
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
|
|
131
|
-
return
|
|
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 [
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 ((
|
|
372
|
-
else if ((
|
|
373
|
-
else if ((
|
|
374
|
-
else if ((
|
|
375
|
-
else if ((
|
|
376
|
-
else if ((
|
|
377
|
-
else if ((
|
|
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 (
|
|
381
|
-
if (
|
|
382
|
-
if (
|
|
383
|
-
if (
|
|
384
|
-
if (
|
|
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 ((
|
|
400
|
-
else if ((
|
|
401
|
-
else if ((
|
|
402
|
-
else if ((
|
|
403
|
-
else if ((
|
|
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 (
|
|
407
|
-
if (
|
|
408
|
-
if (
|
|
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 ((
|
|
427
|
-
else if ((
|
|
428
|
-
else if ((
|
|
429
|
-
else if ((
|
|
430
|
-
else if ((
|
|
431
|
-
else if ((
|
|
432
|
-
else if ((
|
|
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 (
|
|
436
|
-
if (
|
|
437
|
-
if (
|
|
438
|
-
if (
|
|
439
|
-
if (
|
|
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 ((
|
|
455
|
-
else if ((
|
|
456
|
-
else if ((
|
|
457
|
-
else if ((
|
|
458
|
-
else if ((
|
|
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 (
|
|
462
|
-
if (
|
|
463
|
-
if (
|
|
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 ((
|
|
482
|
-
else if ((
|
|
483
|
-
else if ((
|
|
484
|
-
else if ((
|
|
485
|
-
else if ((
|
|
486
|
-
else if ((
|
|
487
|
-
else if ((
|
|
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 (
|
|
491
|
-
if (
|
|
492
|
-
if (
|
|
493
|
-
if (
|
|
494
|
-
if (
|
|
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 ((
|
|
511
|
-
else if ((
|
|
512
|
-
else if ((
|
|
513
|
-
else if ((
|
|
514
|
-
else if ((
|
|
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 (
|
|
518
|
-
if (
|
|
519
|
-
if (
|
|
520
|
-
if (
|
|
521
|
-
if (
|
|
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 ((
|
|
540
|
-
else if ((
|
|
541
|
-
else if ((
|
|
542
|
-
else if ((
|
|
543
|
-
else if ((
|
|
544
|
-
else if ((
|
|
545
|
-
else if ((
|
|
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 (
|
|
549
|
-
if (
|
|
550
|
-
if (
|
|
551
|
-
if (
|
|
552
|
-
if (
|
|
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 ((
|
|
569
|
-
else if ((
|
|
570
|
-
else if ((
|
|
571
|
-
else if ((
|
|
572
|
-
else if ((
|
|
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 (
|
|
576
|
-
if (
|
|
577
|
-
if (
|
|
578
|
-
if (
|
|
579
|
-
if (
|
|
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 =
|
|
727
|
-
const n2 =
|
|
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
|
|
1061
|
-
return
|
|
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 *
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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
|
|
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)
|
|
1187
|
-
const hasE = (flags & HAS_EASTERN_SPLINE)
|
|
1188
|
-
const hasS = (flags & HAS_SOUTHERN_SPLINE)
|
|
1189
|
-
const hasW = (flags & 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 ((
|
|
1234
|
+
if ((flags & DONT_OPTIMIZE_N) || (neighborflags & DONT_OPTIMIZE_S)) {
|
|
1194
1235
|
splineNoOpt = true;
|
|
1195
1236
|
}
|
|
1196
1237
|
if (!splineNoOpt) {
|
|
1197
|
-
if ((
|
|
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 ((
|
|
1247
|
+
if ((flags & DONT_OPTIMIZE_E) || (neighborflags & DONT_OPTIMIZE_W)) {
|
|
1207
1248
|
splineNoOpt = true;
|
|
1208
1249
|
}
|
|
1209
1250
|
if (!splineNoOpt) {
|
|
1210
|
-
if ((
|
|
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 ((
|
|
1260
|
+
if ((flags & DONT_OPTIMIZE_S) || (neighborflags & DONT_OPTIMIZE_N)) {
|
|
1220
1261
|
splineNoOpt = true;
|
|
1221
1262
|
}
|
|
1222
1263
|
if (!splineNoOpt) {
|
|
1223
|
-
if ((
|
|
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 ((
|
|
1273
|
+
if ((flags & DONT_OPTIMIZE_W) || (neighborflags & DONT_OPTIMIZE_E)) {
|
|
1233
1274
|
splineNoOpt = true;
|
|
1234
1275
|
}
|
|
1235
1276
|
if (!splineNoOpt) {
|
|
1236
|
-
if ((
|
|
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 [
|
|
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 (
|
|
1319
|
+
if (parentFlags & HAS_NORTHERN_SPLINE) {
|
|
1279
1320
|
splinePoints[countSp++] = getPos(parentNeighborIndices[0]);
|
|
1280
1321
|
}
|
|
1281
|
-
if (
|
|
1322
|
+
if (parentFlags & HAS_EASTERN_SPLINE) {
|
|
1282
1323
|
splinePoints[countSp++] = getPos(parentNeighborIndices[1]);
|
|
1283
1324
|
}
|
|
1284
|
-
if (
|
|
1325
|
+
if (parentFlags & HAS_SOUTHERN_SPLINE) {
|
|
1285
1326
|
splinePoints[countSp++] = getPos(parentNeighborIndices[2]);
|
|
1286
1327
|
}
|
|
1287
|
-
if (
|
|
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 =
|
|
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 (
|
|
1406
|
+
if (flags & HAS_NORTHERN_NEIGHBOR) {
|
|
1366
1407
|
v++;
|
|
1367
1408
|
}
|
|
1368
|
-
if (
|
|
1409
|
+
if (flags & HAS_EASTERN_NEIGHBOR) {
|
|
1369
1410
|
v++;
|
|
1370
1411
|
}
|
|
1371
|
-
if (
|
|
1412
|
+
if (flags & HAS_SOUTHERN_NEIGHBOR) {
|
|
1372
1413
|
v++;
|
|
1373
1414
|
}
|
|
1374
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 *
|
|
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 = [
|
|
1449
|
-
const URCoords = [
|
|
1450
|
-
const LLCoords = [
|
|
1451
|
-
const LRCoords = [
|
|
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 (
|
|
1544
|
+
if (node0flags & HAS_NORTHERN_NEIGHBOR) {
|
|
1485
1545
|
cpArray = getCPs(node0neighbors[0], NORTH);
|
|
1486
|
-
} else if (
|
|
1546
|
+
} else if (node0flags & HAS_EASTERN_NEIGHBOR) {
|
|
1487
1547
|
cpArray = getCPs(node0neighbors[1], EAST);
|
|
1488
|
-
} else if (
|
|
1548
|
+
} else if (node0flags & HAS_SOUTHERN_NEIGHBOR) {
|
|
1489
1549
|
cpArray = getCPs(node0neighbors[2], SOUTH);
|
|
1490
|
-
} else if (
|
|
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 (
|
|
1505
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
1541
|
-
if (
|
|
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 (
|
|
1545
|
-
if (
|
|
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 (
|
|
1551
|
-
if (
|
|
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 (
|
|
1557
|
-
if (
|
|
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 (
|
|
1700
|
+
if (node1flags & HAS_NORTHERN_NEIGHBOR) {
|
|
1641
1701
|
cpArray = getCPs(node1neighbors[0], NORTH);
|
|
1642
|
-
} else if (
|
|
1702
|
+
} else if (node1flags & HAS_EASTERN_NEIGHBOR) {
|
|
1643
1703
|
cpArray = getCPs(node1neighbors[1], EAST);
|
|
1644
|
-
} else if (
|
|
1704
|
+
} else if (node1flags & HAS_SOUTHERN_NEIGHBOR) {
|
|
1645
1705
|
cpArray = getCPs(node1neighbors[2], SOUTH);
|
|
1646
|
-
} else if (
|
|
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 (
|
|
1661
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
1758
|
+
const col = fetchPixelRGBA8(src, px, py);
|
|
1697
1759
|
const dx = cellSpaceCoords[0] - px;
|
|
1698
1760
|
const dy = cellSpaceCoords[1] - py;
|
|
1699
|
-
const
|
|
1700
|
-
const weight =
|
|
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 (
|
|
1778
|
+
if (edges & SOUTHWEST) {
|
|
1712
1779
|
addWeightedColor(ULCoords[0] - 1, ULCoords[1] - 1);
|
|
1713
1780
|
}
|
|
1714
|
-
if (
|
|
1781
|
+
if (edges & WEST) {
|
|
1715
1782
|
addWeightedColor(ULCoords[0] - 1, ULCoords[1]);
|
|
1716
1783
|
}
|
|
1717
|
-
if (
|
|
1784
|
+
if (edges & NORTHWEST) {
|
|
1718
1785
|
addWeightedColor(ULCoords[0] - 1, ULCoords[1] + 1);
|
|
1719
1786
|
}
|
|
1720
|
-
if (
|
|
1787
|
+
if (edges & NORTH) {
|
|
1721
1788
|
addWeightedColor(ULCoords[0], ULCoords[1] + 1);
|
|
1722
1789
|
}
|
|
1723
|
-
if (
|
|
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 (
|
|
1797
|
+
if (edges & NORTH) {
|
|
1731
1798
|
addWeightedColor(URCoords[0], URCoords[1] + 1);
|
|
1732
1799
|
}
|
|
1733
|
-
if (
|
|
1800
|
+
if (edges & NORTHEAST) {
|
|
1734
1801
|
addWeightedColor(URCoords[0] + 1, URCoords[1] + 1);
|
|
1735
1802
|
}
|
|
1736
|
-
if (
|
|
1803
|
+
if (edges & EAST) {
|
|
1737
1804
|
addWeightedColor(URCoords[0] + 1, URCoords[1]);
|
|
1738
1805
|
}
|
|
1739
|
-
if (
|
|
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 (
|
|
1813
|
+
if (edges & WEST) {
|
|
1747
1814
|
addWeightedColor(LLCoords[0] - 1, LLCoords[1]);
|
|
1748
1815
|
}
|
|
1749
|
-
if (
|
|
1816
|
+
if (edges & SOUTHWEST) {
|
|
1750
1817
|
addWeightedColor(LLCoords[0] - 1, LLCoords[1] - 1);
|
|
1751
1818
|
}
|
|
1752
|
-
if (
|
|
1819
|
+
if (edges & SOUTH) {
|
|
1753
1820
|
addWeightedColor(LLCoords[0], LLCoords[1] - 1);
|
|
1754
1821
|
}
|
|
1755
|
-
if (
|
|
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 (
|
|
1829
|
+
if (edges & NORTHEAST) {
|
|
1763
1830
|
addWeightedColor(LRCoords[0] + 1, LRCoords[1] + 1);
|
|
1764
1831
|
}
|
|
1765
|
-
if (
|
|
1832
|
+
if (edges & EAST) {
|
|
1766
1833
|
addWeightedColor(LRCoords[0] + 1, LRCoords[1]);
|
|
1767
1834
|
}
|
|
1768
|
-
if (
|
|
1835
|
+
if (edges & SOUTHWEST) {
|
|
1769
1836
|
addWeightedColor(LRCoords[0] - 1, LRCoords[1] - 1);
|
|
1770
1837
|
}
|
|
1771
|
-
if (
|
|
1838
|
+
if (edges & SOUTH) {
|
|
1772
1839
|
addWeightedColor(LRCoords[0], LRCoords[1] - 1);
|
|
1773
1840
|
}
|
|
1774
|
-
if (
|
|
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 =
|
|
1782
|
-
const ny =
|
|
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(
|
|
1790
|
-
out[outIdx + 1] = clampInt(
|
|
1791
|
-
out[outIdx + 2] = clampInt(
|
|
1792
|
-
out[outIdx + 3] = clampInt(
|
|
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
|
|
1873
|
+
return {
|
|
1874
|
+
data: out,
|
|
1875
|
+
width: outW,
|
|
1876
|
+
height: outH,
|
|
1877
|
+
allSplines,
|
|
1878
|
+
similarityData,
|
|
1879
|
+
};
|
|
1798
1880
|
}
|
|
1799
1881
|
|
|
1800
|
-
function
|
|
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 =
|
|
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
|
-
|
|
1815
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2005
|
+
const srcRow = min(inH - 1, max(0, y - borderPx)) * inW * 4;
|
|
1842
2006
|
const dstRow = y * padW * 4 + borderPx * 4;
|
|
1843
|
-
|
|
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 =
|
|
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 =
|
|
1854
|
-
const outW =
|
|
1855
|
-
const cropped =
|
|
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 =
|
|
1865
|
-
|
|
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 {
|
|
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 {
|
|
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;
|