depixel 0.0.1
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 +61 -0
- package/lib.js +1861 -0
- package/package.json +20 -0
package/lib.js
ADDED
|
@@ -0,0 +1,1861 @@
|
|
|
1
|
+
/* TypeScript API:
|
|
2
|
+
type Image = {
|
|
3
|
+
data: Buffer; // Or Uint8Array - pixels in RGBA byte order
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type Opts = {
|
|
9
|
+
height: number;
|
|
10
|
+
threshold?: number; // 0..255, lower = fewer similarity edges
|
|
11
|
+
borderPx?: number; // pad input with this many pixels (1-2)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function scaleImage(src: Image, opts: Opts): Image;
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const EDGE_HORVERT = 16;
|
|
18
|
+
const EDGE_DIAGONAL_ULLR = 32;
|
|
19
|
+
const EDGE_DIAGONAL_LLUR = 64;
|
|
20
|
+
|
|
21
|
+
const HAS_NORTHERN_NEIGHBOR = 1;
|
|
22
|
+
const HAS_EASTERN_NEIGHBOR = 2;
|
|
23
|
+
const HAS_SOUTHERN_NEIGHBOR = 4;
|
|
24
|
+
const HAS_WESTERN_NEIGHBOR = 8;
|
|
25
|
+
const HAS_NORTHERN_SPLINE = 16;
|
|
26
|
+
const HAS_EASTERN_SPLINE = 32;
|
|
27
|
+
const HAS_SOUTHERN_SPLINE = 64;
|
|
28
|
+
const HAS_WESTERN_SPLINE = 128;
|
|
29
|
+
const HAS_CORRECTED_POSITION = 256;
|
|
30
|
+
const DONT_OPTIMIZE_N = 512;
|
|
31
|
+
const DONT_OPTIMIZE_E = 1024;
|
|
32
|
+
const DONT_OPTIMIZE_S = 2048;
|
|
33
|
+
const DONT_OPTIMIZE_W = 4096;
|
|
34
|
+
|
|
35
|
+
const NORTH = 128;
|
|
36
|
+
const NORTHEAST = 64;
|
|
37
|
+
const EAST = 32;
|
|
38
|
+
const SOUTHEAST = 16;
|
|
39
|
+
const SOUTH = 8;
|
|
40
|
+
const SOUTHWEST = 4;
|
|
41
|
+
const WEST = 2;
|
|
42
|
+
const NORTHWEST = 1;
|
|
43
|
+
|
|
44
|
+
const STEP = 0.2;
|
|
45
|
+
const GAUSS_MULTIPLIER = 2.5;
|
|
46
|
+
|
|
47
|
+
const POSITIONAL_ENERGY_SCALING = 2.5;
|
|
48
|
+
|
|
49
|
+
const LIMIT_SEARCH_ITERATIONS = 20.0;
|
|
50
|
+
const R = 0.61803399;
|
|
51
|
+
const C = 1 - R;
|
|
52
|
+
const TOL = 0.0001;
|
|
53
|
+
const BRACKET_SEARCH_A = 0.1;
|
|
54
|
+
const BRACKET_SEARCH_B = -0.1;
|
|
55
|
+
const GOLD = 1.618034;
|
|
56
|
+
const GLIMIT = 10.0;
|
|
57
|
+
const TINY = 0.000000001;
|
|
58
|
+
|
|
59
|
+
function clampInt(v, lo, hi) {
|
|
60
|
+
if (v < lo) {
|
|
61
|
+
return lo;
|
|
62
|
+
}
|
|
63
|
+
if (v > hi) {
|
|
64
|
+
return hi;
|
|
65
|
+
}
|
|
66
|
+
return v;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function pixelIndex(x, y, w) {
|
|
70
|
+
return (y * w + x) * 4;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function fetchPixelRGBA(src, x, y) {
|
|
74
|
+
const w = src.width;
|
|
75
|
+
const h = src.height;
|
|
76
|
+
const cx = clampInt(x, 0, w - 1);
|
|
77
|
+
const cy = clampInt(y, 0, h - 1);
|
|
78
|
+
const idx = pixelIndex(cx, cy, w);
|
|
79
|
+
const d = src.data;
|
|
80
|
+
return [d[idx] / 255, d[idx + 1] / 255, d[idx + 2] / 255, d[idx + 3] / 255];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isSimilar(a, b, threshold) {
|
|
84
|
+
const yA = 0.299 * a[0] + 0.587 * a[1] + 0.114 * a[2];
|
|
85
|
+
const uA = 0.493 * (a[2] - yA);
|
|
86
|
+
const vA = 0.877 * (a[0] - yA);
|
|
87
|
+
const yB = 0.299 * b[0] + 0.587 * b[1] + 0.114 * b[2];
|
|
88
|
+
const uB = 0.493 * (b[2] - yB);
|
|
89
|
+
const vB = 0.877 * (b[0] - yB);
|
|
90
|
+
const t = Math.max(0, Math.min(255, threshold | 0)) / 255.0;
|
|
91
|
+
if (Math.abs(yA - yB) <= (48.0 / 255.0) * t) {
|
|
92
|
+
if (Math.abs(uA - uB) <= (7.0 / 255.0) * t) {
|
|
93
|
+
if (Math.abs(vA - vB) <= (6.0 / 255.0) * t) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isContour(src, pL, pR) {
|
|
102
|
+
const a = fetchPixelRGBA(src, pL[0], pL[1]);
|
|
103
|
+
const b = fetchPixelRGBA(src, pR[0], pR[1]);
|
|
104
|
+
const yA = 0.299 * a[0] + 0.587 * a[1] + 0.114 * a[2];
|
|
105
|
+
const uA = 0.493 * (a[2] - yA);
|
|
106
|
+
const vA = 0.877 * (a[0] - yA);
|
|
107
|
+
const yB = 0.299 * b[0] + 0.587 * b[1] + 0.114 * b[2];
|
|
108
|
+
const uB = 0.493 * (b[2] - yB);
|
|
109
|
+
const vB = 0.877 * (b[0] - yB);
|
|
110
|
+
const dy = yA - yB;
|
|
111
|
+
const du = uA - uB;
|
|
112
|
+
const dv = vA - vB;
|
|
113
|
+
const dist = Math.sqrt(dy * dy + du * du + dv * dv);
|
|
114
|
+
return dist > 100.0 / 255.0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildSimilarityGraph(src, similarityThreshold) {
|
|
118
|
+
const w = src.width;
|
|
119
|
+
const h = src.height;
|
|
120
|
+
const sgW = 2 * w + 1;
|
|
121
|
+
const sgH = 2 * h + 1;
|
|
122
|
+
const r = new Int32Array(sgW * sgH);
|
|
123
|
+
const g = new Int32Array(sgW * sgH);
|
|
124
|
+
|
|
125
|
+
function setRG(x, y, rv, gv) {
|
|
126
|
+
const idx = y * sgW + x;
|
|
127
|
+
r[idx] = rv | 0;
|
|
128
|
+
g[idx] = gv | 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getPixelCoords(gx, gy) {
|
|
132
|
+
return [Math.floor((gx - 1) / 2), Math.floor((gy - 1) / 2)];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (let y = 0; y < sgH; y++) {
|
|
136
|
+
for (let x = 0; x < sgW; x++) {
|
|
137
|
+
if (x === 0 || x === sgW - 1 || y === 0 || y === sgH - 1) {
|
|
138
|
+
setRG(x, y, 0, 0);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const evalX = x & 1;
|
|
142
|
+
const evalY = y & 1;
|
|
143
|
+
if (evalX === 1 && evalY === 1) {
|
|
144
|
+
setRG(x, y, 0, 0);
|
|
145
|
+
} else if (evalX === 0 && evalY === 0) {
|
|
146
|
+
let diagonal = 0;
|
|
147
|
+
let pA = getPixelCoords(x - 1, y + 1);
|
|
148
|
+
let pB = getPixelCoords(x + 1, y - 1);
|
|
149
|
+
if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
|
|
150
|
+
diagonal = EDGE_DIAGONAL_ULLR;
|
|
151
|
+
}
|
|
152
|
+
pA = getPixelCoords(x - 1, y - 1);
|
|
153
|
+
pB = getPixelCoords(x + 1, y + 1);
|
|
154
|
+
if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
|
|
155
|
+
diagonal |= EDGE_DIAGONAL_LLUR;
|
|
156
|
+
}
|
|
157
|
+
setRG(x, y, diagonal, 0);
|
|
158
|
+
} else if (evalX === 0 && evalY === 1) {
|
|
159
|
+
const pA = getPixelCoords(x - 1, y);
|
|
160
|
+
const pB = getPixelCoords(x + 1, y);
|
|
161
|
+
if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
|
|
162
|
+
setRG(x, y, EDGE_HORVERT, 0);
|
|
163
|
+
} else {
|
|
164
|
+
setRG(x, y, 0, 0);
|
|
165
|
+
}
|
|
166
|
+
} else if (evalX === 1 && evalY === 0) {
|
|
167
|
+
const pA = getPixelCoords(x, y - 1);
|
|
168
|
+
const pB = getPixelCoords(x, y + 1);
|
|
169
|
+
if (isSimilar(fetchPixelRGBA(src, pA[0], pA[1]), fetchPixelRGBA(src, pB[0], pB[1]), similarityThreshold)) {
|
|
170
|
+
setRG(x, y, EDGE_HORVERT, 0);
|
|
171
|
+
} else {
|
|
172
|
+
setRG(x, y, 0, 0);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
setRG(x, y, 0, 0);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { r, g, w: sgW, h: sgH };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function valenceUpdate(sim) {
|
|
184
|
+
const sgW = sim.w;
|
|
185
|
+
const sgH = sim.h;
|
|
186
|
+
const rIn = sim.r;
|
|
187
|
+
const gIn = sim.g;
|
|
188
|
+
const rOut = new Int32Array(sgW * sgH);
|
|
189
|
+
const gOut = new Int32Array(sgW * sgH);
|
|
190
|
+
|
|
191
|
+
function getR(x, y) {
|
|
192
|
+
if (x < 0 || y < 0 || x >= sgW || y >= sgH) {
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
return rIn[y * sgW + x] | 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (let y = 0; y < sgH; y++) {
|
|
199
|
+
for (let x = 0; x < sgW; x++) {
|
|
200
|
+
const idx = y * sgW + x;
|
|
201
|
+
if (x === 0 || x === sgW - 1 || y === 0 || y === sgH - 1) {
|
|
202
|
+
rOut[idx] = 0;
|
|
203
|
+
gOut[idx] = 0;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const evalX = x & 1;
|
|
207
|
+
const evalY = y & 1;
|
|
208
|
+
if (evalX === 1 && evalY === 1) {
|
|
209
|
+
let valence = 0;
|
|
210
|
+
let edges = 0;
|
|
211
|
+
let edgeValue = getR(x - 1, y + 1);
|
|
212
|
+
if ((edgeValue & EDGE_DIAGONAL_ULLR) === EDGE_DIAGONAL_ULLR) {
|
|
213
|
+
valence++;
|
|
214
|
+
edges |= NORTHWEST;
|
|
215
|
+
}
|
|
216
|
+
if (getR(x, y + 1) > 0) {
|
|
217
|
+
valence++;
|
|
218
|
+
edges |= NORTH;
|
|
219
|
+
}
|
|
220
|
+
edgeValue = getR(x + 1, y + 1);
|
|
221
|
+
if ((edgeValue & EDGE_DIAGONAL_LLUR) === EDGE_DIAGONAL_LLUR) {
|
|
222
|
+
valence++;
|
|
223
|
+
edges |= NORTHEAST;
|
|
224
|
+
}
|
|
225
|
+
if (getR(x + 1, y) > 0) {
|
|
226
|
+
valence++;
|
|
227
|
+
edges |= EAST;
|
|
228
|
+
}
|
|
229
|
+
edgeValue = getR(x + 1, y - 1);
|
|
230
|
+
if ((edgeValue & EDGE_DIAGONAL_ULLR) === EDGE_DIAGONAL_ULLR) {
|
|
231
|
+
valence++;
|
|
232
|
+
edges |= SOUTHEAST;
|
|
233
|
+
}
|
|
234
|
+
if (getR(x, y - 1) > 0) {
|
|
235
|
+
valence++;
|
|
236
|
+
edges |= SOUTH;
|
|
237
|
+
}
|
|
238
|
+
edgeValue = getR(x - 1, y - 1);
|
|
239
|
+
if ((edgeValue & EDGE_DIAGONAL_LLUR) === EDGE_DIAGONAL_LLUR) {
|
|
240
|
+
valence++;
|
|
241
|
+
edges |= SOUTHWEST;
|
|
242
|
+
}
|
|
243
|
+
if (getR(x - 1, y) > 0) {
|
|
244
|
+
valence++;
|
|
245
|
+
edges |= WEST;
|
|
246
|
+
}
|
|
247
|
+
rOut[idx] = valence;
|
|
248
|
+
gOut[idx] = edges;
|
|
249
|
+
} else {
|
|
250
|
+
rOut[idx] = rIn[idx];
|
|
251
|
+
gOut[idx] = gIn[idx];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return { r: rOut, g: gOut, w: sgW, h: sgH };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function eliminateCrossings(sim) {
|
|
259
|
+
const sgW = sim.w;
|
|
260
|
+
const sgH = sim.h;
|
|
261
|
+
const rIn = sim.r;
|
|
262
|
+
const gIn = sim.g;
|
|
263
|
+
const rOut = new Int32Array(sgW * sgH);
|
|
264
|
+
const gOut = new Int32Array(sgW * sgH);
|
|
265
|
+
|
|
266
|
+
function getR(x, y) {
|
|
267
|
+
if (x < 0 || y < 0 || x >= sgW || y >= sgH) {
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
return rIn[y * sgW + x] | 0;
|
|
271
|
+
}
|
|
272
|
+
function getG(x, y) {
|
|
273
|
+
if (x < 0 || y < 0 || x >= sgW || y >= sgH) {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
return gIn[y * sgW + x] | 0;
|
|
277
|
+
}
|
|
278
|
+
function setRG(x, y, rv, gv) {
|
|
279
|
+
const idx = y * sgW + x;
|
|
280
|
+
rOut[idx] = rv | 0;
|
|
281
|
+
gOut[idx] = gv | 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (let y = 0; y < sgH; y++) {
|
|
285
|
+
for (let x = 0; x < sgW; x++) {
|
|
286
|
+
const fragmentValue = getR(x, y);
|
|
287
|
+
|
|
288
|
+
let voteA = 0;
|
|
289
|
+
let voteB = 0;
|
|
290
|
+
let componentSizeA = 2;
|
|
291
|
+
let componentSizeB = 2;
|
|
292
|
+
|
|
293
|
+
function countForComponent(c) {
|
|
294
|
+
if (c === 1) {
|
|
295
|
+
componentSizeA++;
|
|
296
|
+
} else if (c === 2) {
|
|
297
|
+
componentSizeB++;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function voteIslands() {
|
|
302
|
+
if (getR(x - 1, y + 1) === 1) {
|
|
303
|
+
voteA += 5;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (getR(x + 1, y - 1) === 1) {
|
|
307
|
+
voteA += 5;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (getR(x - 1, y - 1) === 1) {
|
|
311
|
+
voteB += 5;
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (getR(x + 1, y + 1) === 1) {
|
|
315
|
+
voteB += 5;
|
|
316
|
+
return; // eslint-disable-line no-useless-return
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function voteSparsePixels() {
|
|
321
|
+
const lArray = new Int32Array([
|
|
322
|
+
0,0,0,0,0,0,0,0,
|
|
323
|
+
0,0,0,0,0,0,0,0,
|
|
324
|
+
0,0,0,0,0,0,0,0,
|
|
325
|
+
0,0,0,1,2,0,0,0,
|
|
326
|
+
0,0,0,2,1,0,0,0,
|
|
327
|
+
0,0,0,0,0,0,0,0,
|
|
328
|
+
0,0,0,0,0,0,0,0,
|
|
329
|
+
0,0,0,0,0,0,0,0
|
|
330
|
+
]);
|
|
331
|
+
|
|
332
|
+
let nNW = 0;
|
|
333
|
+
let nW = 0;
|
|
334
|
+
let nSW = 0;
|
|
335
|
+
let nS = 0;
|
|
336
|
+
let nSE = 0;
|
|
337
|
+
let nE = 0;
|
|
338
|
+
let nNE = 0;
|
|
339
|
+
let nN = 0;
|
|
340
|
+
for (let level = 0; level < 2; level++) {
|
|
341
|
+
let xOFFSET = -(1 + 2 * level);
|
|
342
|
+
let yOFFSET = 1 + (2 * level);
|
|
343
|
+
let nhood = getG(x + xOFFSET, y + yOFFSET);
|
|
344
|
+
let currentComponentIndex = 8 * (3 - level) + (3 - level);
|
|
345
|
+
let currentComponent = lArray[currentComponentIndex];
|
|
346
|
+
nS = 8 * (4 - level) + (3 - level);
|
|
347
|
+
nSW = 8 * (4 - level) + (2 - level);
|
|
348
|
+
nW = 8 * (3 - level) + (2 - level);
|
|
349
|
+
nNW = 8 * (2 - level) + (2 - level);
|
|
350
|
+
nN = 8 * (2 - level) + (3 - level);
|
|
351
|
+
nNE = 8 * (2 - level) + (4 - level);
|
|
352
|
+
nE = 8 * (3 - level) + (4 - level);
|
|
353
|
+
if (currentComponent === 0) {
|
|
354
|
+
if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
355
|
+
else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
356
|
+
else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
357
|
+
else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
358
|
+
else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
359
|
+
else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
360
|
+
else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
361
|
+
}
|
|
362
|
+
if (currentComponent !== 0) {
|
|
363
|
+
if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
|
|
364
|
+
if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
|
|
365
|
+
if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
|
|
366
|
+
if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
|
|
367
|
+
if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
|
|
368
|
+
}
|
|
369
|
+
if (level > 0) {
|
|
370
|
+
for (let i = 0; i < level * 2; i++) {
|
|
371
|
+
xOFFSET = -(2 * level - 1) + 2 * i;
|
|
372
|
+
yOFFSET = 1 + 2 * level;
|
|
373
|
+
nhood = getG(x + xOFFSET, y + yOFFSET);
|
|
374
|
+
currentComponentIndex = 8 * (3 - level) + (i + 4 - level);
|
|
375
|
+
currentComponent = lArray[currentComponentIndex];
|
|
376
|
+
nW = 8 * (3 - level) + (i + 3 - level);
|
|
377
|
+
nNW = 8 * (2 - level) + (i + 3 - level);
|
|
378
|
+
nN = 8 * (2 - level) + (i + 4 - level);
|
|
379
|
+
nNE = 8 * (2 - level) + (i + 5 - level);
|
|
380
|
+
nE = 8 * (3 - level) + (i + 5 - level);
|
|
381
|
+
if (currentComponent === 0) {
|
|
382
|
+
if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
383
|
+
else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
384
|
+
else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
385
|
+
else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
386
|
+
else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
387
|
+
}
|
|
388
|
+
if (currentComponent !== 0) {
|
|
389
|
+
if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
|
|
390
|
+
if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
|
|
391
|
+
if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
xOFFSET = (1 + 2 * level);
|
|
397
|
+
yOFFSET = 1 + (2 * level);
|
|
398
|
+
nhood = getG(x + xOFFSET, y + yOFFSET);
|
|
399
|
+
currentComponentIndex = 8 * (3 - level) + (4 + level);
|
|
400
|
+
currentComponent = lArray[currentComponentIndex];
|
|
401
|
+
nW = 8 * (3 - level) + (3 + level);
|
|
402
|
+
nNW = 8 * (2 - level) + (3 + level);
|
|
403
|
+
nN = 8 * (2 - level) + (4 + level);
|
|
404
|
+
nNE = 8 * (2 - level) + (5 + level);
|
|
405
|
+
nE = 8 * (3 - level) + (5 + level);
|
|
406
|
+
nSE = 8 * (4 - level) + (5 + level);
|
|
407
|
+
nS = 8 * (4 - level) + (4 + level);
|
|
408
|
+
if (currentComponent === 0) {
|
|
409
|
+
if (((nhood & WEST) === WEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
410
|
+
else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
411
|
+
else if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
412
|
+
else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
413
|
+
else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
414
|
+
else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
415
|
+
else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
416
|
+
}
|
|
417
|
+
if (currentComponent !== 0) {
|
|
418
|
+
if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
|
|
419
|
+
if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
|
|
420
|
+
if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
|
|
421
|
+
if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
|
|
422
|
+
if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
|
|
423
|
+
}
|
|
424
|
+
if (level > 0) {
|
|
425
|
+
for (let i = 0; i < level * 2; i++) {
|
|
426
|
+
xOFFSET = 1 + 2 * level;
|
|
427
|
+
yOFFSET = 2 * level - 1 - 2 * i;
|
|
428
|
+
nhood = getG(x + xOFFSET, y + yOFFSET);
|
|
429
|
+
currentComponentIndex = 8 * (i + 4 - level) + (4 + level);
|
|
430
|
+
currentComponent = lArray[currentComponentIndex];
|
|
431
|
+
nN = 8 * (i + 3 - level) + (4 + level);
|
|
432
|
+
nNE = 8 * (i + 3 - level) + (5 + level);
|
|
433
|
+
nE = 8 * (i + 4 - level) + (5 + level);
|
|
434
|
+
nSE = 8 * (i + 5 - level) + (5 + level);
|
|
435
|
+
nS = 8 * (i + 5 - level) + (4 + level);
|
|
436
|
+
if (currentComponent === 0) {
|
|
437
|
+
if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
438
|
+
else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
439
|
+
else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
440
|
+
else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
441
|
+
else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
442
|
+
}
|
|
443
|
+
if (currentComponent !== 0) {
|
|
444
|
+
if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
|
|
445
|
+
if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
|
|
446
|
+
if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
xOFFSET = (1 + 2 * level);
|
|
452
|
+
yOFFSET = -(1 + 2 * level);
|
|
453
|
+
nhood = getG(x + xOFFSET, y + yOFFSET);
|
|
454
|
+
currentComponentIndex = 8 * (4 + level) + (4 + level);
|
|
455
|
+
currentComponent = lArray[currentComponentIndex];
|
|
456
|
+
nN = 8 * (3 + level) + (4 + level);
|
|
457
|
+
nNE = 8 * (3 + level) + (5 + level);
|
|
458
|
+
nE = 8 * (4 + level) + (5 + level);
|
|
459
|
+
nSE = 8 * (5 + level) + (5 + level);
|
|
460
|
+
nS = 8 * (5 + level) + (4 + level);
|
|
461
|
+
nSW = 8 * (5 + level) + (3 + level);
|
|
462
|
+
nW = 8 * (4 + level) + (3 + level);
|
|
463
|
+
if (currentComponent === 0) {
|
|
464
|
+
if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
465
|
+
else if (((nhood & NORTHEAST) === NORTHEAST) && (lArray[nNE] !== 0)) { currentComponent = lArray[nNE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
466
|
+
else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
467
|
+
else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
468
|
+
else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
469
|
+
else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
470
|
+
else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
471
|
+
}
|
|
472
|
+
if (currentComponent !== 0) {
|
|
473
|
+
if ((nhood & NORTHEAST) === NORTHEAST) { if (lArray[nNE] === 0) { lArray[nNE] = currentComponent; countForComponent(currentComponent); } }
|
|
474
|
+
if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
|
|
475
|
+
if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
|
|
476
|
+
if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
|
|
477
|
+
if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (level > 0) {
|
|
481
|
+
for (let i = 0; i < level * 2; i++) {
|
|
482
|
+
xOFFSET = -(2 * level - 1) + 2 * i;
|
|
483
|
+
yOFFSET = -(1 + 2 * level);
|
|
484
|
+
nhood = getG(x + xOFFSET, y + yOFFSET);
|
|
485
|
+
currentComponentIndex = 8 * (4 + level) + (i + 4 - level);
|
|
486
|
+
currentComponent = lArray[currentComponentIndex];
|
|
487
|
+
nE = 8 * (4 + level) + (i + 5 - level);
|
|
488
|
+
nSE = 8 * (5 + level) + (i + 5 - level);
|
|
489
|
+
nS = 8 * (5 + level) + (i + 4 - level);
|
|
490
|
+
nSW = 8 * (5 + level) + (i + 3 - level);
|
|
491
|
+
nW = 8 * (4 + level) + (i + 3 - level);
|
|
492
|
+
if (currentComponent === 0) {
|
|
493
|
+
if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
494
|
+
else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
495
|
+
else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
496
|
+
else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
497
|
+
else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
498
|
+
}
|
|
499
|
+
if (currentComponent !== 0) {
|
|
500
|
+
if ((nhood & EAST) === EAST) { if (lArray[nE] === 0) { lArray[nE] = currentComponent; countForComponent(currentComponent); } }
|
|
501
|
+
if ((nhood & SOUTHEAST) === SOUTHEAST) { if (lArray[nSE] === 0) { lArray[nSE] = currentComponent; countForComponent(currentComponent); } }
|
|
502
|
+
if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
|
|
503
|
+
if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
|
|
504
|
+
if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
xOFFSET = -(1 + 2 * level);
|
|
510
|
+
yOFFSET = -(1 + 2 * level);
|
|
511
|
+
nhood = getG(x + xOFFSET, y + yOFFSET);
|
|
512
|
+
currentComponentIndex = 8 * (4 + level) + (3 - level);
|
|
513
|
+
currentComponent = lArray[currentComponentIndex];
|
|
514
|
+
nN = 8 * (3 + level) + (3 - level);
|
|
515
|
+
nNW = 8 * (3 + level) + (2 - level);
|
|
516
|
+
nW = 8 * (4 + level) + (2 - level);
|
|
517
|
+
nSW = 8 * (5 + level) + (2 - level);
|
|
518
|
+
nS = 8 * (5 + level) + (3 - level);
|
|
519
|
+
nSE = 8 * (5 + level) + (4 - level);
|
|
520
|
+
nE = 8 * (4 + level) + (4 - level);
|
|
521
|
+
if (currentComponent === 0) {
|
|
522
|
+
if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
523
|
+
else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
524
|
+
else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
525
|
+
else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
526
|
+
else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
527
|
+
else if (((nhood & SOUTHEAST) === SOUTHEAST) && (lArray[nSE] !== 0)) { currentComponent = lArray[nSE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
528
|
+
else if (((nhood & EAST) === EAST) && (lArray[nE] !== 0)) { currentComponent = lArray[nE]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
529
|
+
}
|
|
530
|
+
if (currentComponent !== 0) {
|
|
531
|
+
if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
|
|
532
|
+
if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
|
|
533
|
+
if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
|
|
534
|
+
if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
|
|
535
|
+
if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (level > 0) {
|
|
539
|
+
for (let i = 0; i < level * 2; i++) {
|
|
540
|
+
xOFFSET = -(1 + 2 * level);
|
|
541
|
+
yOFFSET = 2 * level - 1 - 2 * i;
|
|
542
|
+
nhood = getG(x + xOFFSET, y + yOFFSET);
|
|
543
|
+
currentComponentIndex = 8 * (i + 4 - level) + (3 - level);
|
|
544
|
+
currentComponent = lArray[currentComponentIndex];
|
|
545
|
+
nN = 8 * (i + 3 - level) + (3 - level);
|
|
546
|
+
nNW = 8 * (i + 3 - level) + (2 - level);
|
|
547
|
+
nW = 8 * (i + 4 - level) + (2 - level);
|
|
548
|
+
nSW = 8 * (i + 5 - level) + (2 - level);
|
|
549
|
+
nS = 8 * (i + 5 - level) + (3 - level);
|
|
550
|
+
if (currentComponent === 0) {
|
|
551
|
+
if (((nhood & NORTH) === NORTH) && (lArray[nN] !== 0)) { currentComponent = lArray[nN]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
552
|
+
else if (((nhood & NORTHWEST) === NORTHWEST) && (lArray[nNW] !== 0)) { currentComponent = lArray[nNW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
553
|
+
else if (((nhood & WEST) === WEST) && (lArray[nW] !== 0)) { currentComponent = lArray[nW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
554
|
+
else if (((nhood & SOUTHWEST) === SOUTHWEST) && (lArray[nSW] !== 0)) { currentComponent = lArray[nSW]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
555
|
+
else if (((nhood & SOUTH) === SOUTH) && (lArray[nS] !== 0)) { currentComponent = lArray[nS]; lArray[currentComponentIndex] = currentComponent; countForComponent(currentComponent); }
|
|
556
|
+
}
|
|
557
|
+
if (currentComponent !== 0) {
|
|
558
|
+
if ((nhood & NORTH) === NORTH) { if (lArray[nN] === 0) { lArray[nN] = currentComponent; countForComponent(currentComponent); } }
|
|
559
|
+
if ((nhood & NORTHWEST) === NORTHWEST) { if (lArray[nNW] === 0) { lArray[nNW] = currentComponent; countForComponent(currentComponent); } }
|
|
560
|
+
if ((nhood & WEST) === WEST) { if (lArray[nW] === 0) { lArray[nW] = currentComponent; countForComponent(currentComponent); } }
|
|
561
|
+
if ((nhood & SOUTHWEST) === SOUTHWEST) { if (lArray[nSW] === 0) { lArray[nSW] = currentComponent; countForComponent(currentComponent); } }
|
|
562
|
+
if ((nhood & SOUTH) === SOUTH) { if (lArray[nS] === 0) { lArray[nS] = currentComponent; countForComponent(currentComponent); } }
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (componentSizeA < componentSizeB) {
|
|
569
|
+
voteA += (componentSizeB - componentSizeA);
|
|
570
|
+
} else if (componentSizeA > componentSizeB) {
|
|
571
|
+
voteB += (componentSizeA - componentSizeB);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function traceNodes(nodeCoords, predecessorNodeDirection) {
|
|
576
|
+
let totalLength = 0;
|
|
577
|
+
let currentNodeCoords = nodeCoords.slice();
|
|
578
|
+
let currentNodeValueX = getR(currentNodeCoords[0], currentNodeCoords[1]);
|
|
579
|
+
let currentNodeValueY = getG(currentNodeCoords[0], currentNodeCoords[1]);
|
|
580
|
+
let nextNodeCoords = [0, 0];
|
|
581
|
+
let directionToCurrentNode = 0;
|
|
582
|
+
while (currentNodeValueX === 2) {
|
|
583
|
+
const nextNodeDirection = currentNodeValueY ^ predecessorNodeDirection;
|
|
584
|
+
switch (nextNodeDirection) {
|
|
585
|
+
case 1: nextNodeCoords = [currentNodeCoords[0] - 1, currentNodeCoords[1] + 1]; directionToCurrentNode = 16; break;
|
|
586
|
+
case 2: nextNodeCoords = [currentNodeCoords[0] - 1, currentNodeCoords[1]]; directionToCurrentNode = 32; break;
|
|
587
|
+
case 4: nextNodeCoords = [currentNodeCoords[0] - 1, currentNodeCoords[1] - 1]; directionToCurrentNode = 64; break;
|
|
588
|
+
case 8: nextNodeCoords = [currentNodeCoords[0], currentNodeCoords[1] - 1]; directionToCurrentNode = 128; break;
|
|
589
|
+
case 16: nextNodeCoords = [currentNodeCoords[0] + 1, currentNodeCoords[1] - 1]; directionToCurrentNode = 1; break;
|
|
590
|
+
case 32: nextNodeCoords = [currentNodeCoords[0] + 1, currentNodeCoords[1]]; directionToCurrentNode = 2; break;
|
|
591
|
+
case 64: nextNodeCoords = [currentNodeCoords[0] + 1, currentNodeCoords[1] + 1]; directionToCurrentNode = 4; break;
|
|
592
|
+
case 128: nextNodeCoords = [currentNodeCoords[0], currentNodeCoords[1] + 1]; directionToCurrentNode = 8; break;
|
|
593
|
+
default: directionToCurrentNode = predecessorNodeDirection; nextNodeCoords = currentNodeCoords; return 0;
|
|
594
|
+
}
|
|
595
|
+
currentNodeCoords = nextNodeCoords;
|
|
596
|
+
predecessorNodeDirection = directionToCurrentNode;
|
|
597
|
+
currentNodeValueX = getR(currentNodeCoords[0], currentNodeCoords[1]);
|
|
598
|
+
currentNodeValueY = getG(currentNodeCoords[0], currentNodeCoords[1]);
|
|
599
|
+
totalLength++;
|
|
600
|
+
}
|
|
601
|
+
return totalLength;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function voteCurves() {
|
|
605
|
+
let lengthA = 1;
|
|
606
|
+
let lengthB = 1;
|
|
607
|
+
const A1 = [x - 1, y + 1];
|
|
608
|
+
const A2 = [x + 1, y - 1];
|
|
609
|
+
const B1 = [x - 1, y - 1];
|
|
610
|
+
const B2 = [x + 1, y + 1];
|
|
611
|
+
lengthA += traceNodes(A1, 16);
|
|
612
|
+
lengthA += traceNodes(A2, 1);
|
|
613
|
+
lengthB += traceNodes(B1, 64);
|
|
614
|
+
lengthB += traceNodes(B2, 4);
|
|
615
|
+
if (lengthA === lengthB) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (lengthA > lengthB) {
|
|
619
|
+
voteA += (lengthA - lengthB);
|
|
620
|
+
} else {
|
|
621
|
+
voteB += (lengthB - lengthA);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function isFullyConnectedCD() {
|
|
626
|
+
return getR(x, y + 1) !== 0;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function isFullyConnectedD() {
|
|
630
|
+
if (getR(x, y + 1) === EDGE_HORVERT) {
|
|
631
|
+
if (getR(x + 1, y) === EDGE_HORVERT) {
|
|
632
|
+
if (getR(x, y - 1) === EDGE_HORVERT) {
|
|
633
|
+
if (getR(x - 1, y) === EDGE_HORVERT) {
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (fragmentValue === 96) {
|
|
643
|
+
if (isFullyConnectedCD()) {
|
|
644
|
+
setRG(x, y, 0, 0);
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
voteCurves();
|
|
648
|
+
voteIslands();
|
|
649
|
+
voteSparsePixels();
|
|
650
|
+
if (voteA === voteB) {
|
|
651
|
+
setRG(x, y, 0, 0);
|
|
652
|
+
} else if (voteA > voteB) {
|
|
653
|
+
setRG(x, y, EDGE_DIAGONAL_ULLR, 0);
|
|
654
|
+
} else {
|
|
655
|
+
setRG(x, y, EDGE_DIAGONAL_LLUR, 0);
|
|
656
|
+
}
|
|
657
|
+
} else if (fragmentValue === EDGE_DIAGONAL_ULLR || fragmentValue === EDGE_DIAGONAL_LLUR) {
|
|
658
|
+
if (isFullyConnectedD()) {
|
|
659
|
+
setRG(x, y, 0, 0);
|
|
660
|
+
} else {
|
|
661
|
+
setRG(x, y, fragmentValue, getG(x, y));
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
setRG(x, y, fragmentValue, getG(x, y));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return { r: rOut, g: gOut, w: sgW, h: sgH };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function computeCellGraph(src, sim) {
|
|
673
|
+
const w = src.width;
|
|
674
|
+
const h = src.height;
|
|
675
|
+
const dy = w - 1;
|
|
676
|
+
const count = (w - 1) * (h - 1) * 2;
|
|
677
|
+
const pos = new Float32Array(count * 2);
|
|
678
|
+
const neighbors = new Int32Array(count * 4);
|
|
679
|
+
const flags = new Int32Array(count);
|
|
680
|
+
|
|
681
|
+
function getSimR(x, y) {
|
|
682
|
+
if (x < 0 || y < 0 || x >= sim.w || y >= sim.h) {
|
|
683
|
+
return 0;
|
|
684
|
+
}
|
|
685
|
+
return sim.r[y * sim.w + x] | 0;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function getNeighborIndex(cx, cy, dir, targetSector) {
|
|
689
|
+
let index = -1;
|
|
690
|
+
if (dir === 'N') {
|
|
691
|
+
index = ((cy + 1) * dy + cx) * 2 + targetSector;
|
|
692
|
+
} else if (dir === 'E') {
|
|
693
|
+
index = (cy * dy + cx + 1) * 2 + targetSector;
|
|
694
|
+
} else if (dir === 'S') {
|
|
695
|
+
index = ((cy - 1) * dy + cx) * 2 + targetSector;
|
|
696
|
+
} else if (dir === 'W') {
|
|
697
|
+
index = (cy * dy + cx - 1) * 2 + targetSector;
|
|
698
|
+
} else if (dir === 'C') {
|
|
699
|
+
index = (cy * dy + cx) * 2 + targetSector;
|
|
700
|
+
}
|
|
701
|
+
return index;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function calcAdjustedPoint(p0, p1, p2) {
|
|
705
|
+
return [0.125 * p0[0] + 0.75 * p1[0] + 0.125 * p2[0], 0.125 * p0[1] + 0.75 * p1[1] + 0.125 * p2[1]];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function checkForCorner(s1, s2) {
|
|
709
|
+
const n1 = Math.hypot(s1[0], s1[1]);
|
|
710
|
+
const n2 = Math.hypot(s2[0], s2[1]);
|
|
711
|
+
if (n1 === 0 || n2 === 0) {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
const dp = (s1[0] / n1) * (s2[0] / n2) + (s1[1] / n1) * (s2[1] / n2);
|
|
715
|
+
if (dp > -0.7072 && dp < -0.7070) {
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
if (dp > -0.3163 && dp < -0.3161) {
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
if (dp > -0.0001 && dp < 0.0001) {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
for (let cy = 0; cy < h - 1; cy++) {
|
|
728
|
+
for (let cx = 0; cx < w - 1; cx++) {
|
|
729
|
+
const simX = cx * 2 + 2;
|
|
730
|
+
const simY = cy * 2 + 2;
|
|
731
|
+
const eCenter = getSimR(simX, simY);
|
|
732
|
+
const eNorth = getSimR(simX, simY + 1);
|
|
733
|
+
const eNorthCenter = getSimR(simX, simY + 2);
|
|
734
|
+
const eEast = getSimR(simX + 1, simY);
|
|
735
|
+
const eEastCenter = getSimR(simX + 2, simY);
|
|
736
|
+
const eSouth = getSimR(simX, simY - 1);
|
|
737
|
+
const eSouthCenter = getSimR(simX, simY - 2);
|
|
738
|
+
const eWest = getSimR(simX - 1, simY);
|
|
739
|
+
const eWestCenter = getSimR(simX - 2, simY);
|
|
740
|
+
|
|
741
|
+
let v0_pos = [-1, -1];
|
|
742
|
+
let v1_pos = [-1, -1];
|
|
743
|
+
let v0_neighbors = [-1, -1, -1, -1];
|
|
744
|
+
let v1_neighbors = [-1, -1, -1, -1];
|
|
745
|
+
let v0_flags = 0;
|
|
746
|
+
let v1_flags = 0;
|
|
747
|
+
|
|
748
|
+
let ignoreN = false;
|
|
749
|
+
let ignoreE = false;
|
|
750
|
+
let ignoreS = false;
|
|
751
|
+
let ignoreW = false;
|
|
752
|
+
if (cy > h - 3) {
|
|
753
|
+
ignoreN = true;
|
|
754
|
+
}
|
|
755
|
+
if (cx > w - 3) {
|
|
756
|
+
ignoreE = true;
|
|
757
|
+
}
|
|
758
|
+
if (cy < 1) {
|
|
759
|
+
ignoreS = true;
|
|
760
|
+
}
|
|
761
|
+
if (cx < 1) {
|
|
762
|
+
ignoreW = true;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
let neighborsFound = false;
|
|
766
|
+
let nNeighborsFound = false;
|
|
767
|
+
let wNeighborsFound = false;
|
|
768
|
+
let sNeighborsFound = false;
|
|
769
|
+
let eNeighborsFound = false;
|
|
770
|
+
let neighborCount = 0;
|
|
771
|
+
let nNeighborIndex = -1;
|
|
772
|
+
let wNeighborIndex = -1;
|
|
773
|
+
let sNeighborIndex = -1;
|
|
774
|
+
let eNeighborIndex = -1;
|
|
775
|
+
|
|
776
|
+
let nVector = [0, 0];
|
|
777
|
+
let eVector = [0, 0];
|
|
778
|
+
let sVector = [0, 0];
|
|
779
|
+
let wVector = [0, 0];
|
|
780
|
+
|
|
781
|
+
if (!ignoreN && eNorth === 0) {
|
|
782
|
+
nNeighborsFound = true;
|
|
783
|
+
neighborsFound = true;
|
|
784
|
+
neighborCount++;
|
|
785
|
+
if (eNorthCenter === EDGE_DIAGONAL_ULLR) {
|
|
786
|
+
nNeighborIndex = getNeighborIndex(cx, cy, 'N', 0);
|
|
787
|
+
nVector = [-0.25, 0.75];
|
|
788
|
+
} else if (eNorthCenter === EDGE_DIAGONAL_LLUR) {
|
|
789
|
+
nNeighborIndex = getNeighborIndex(cx, cy, 'N', 1);
|
|
790
|
+
nVector = [0.25, 0.75];
|
|
791
|
+
} else {
|
|
792
|
+
nNeighborIndex = getNeighborIndex(cx, cy, 'N', 0);
|
|
793
|
+
nVector = [0.0, 1.0];
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (!ignoreW && eWest === 0) {
|
|
797
|
+
wNeighborsFound = true;
|
|
798
|
+
neighborsFound = true;
|
|
799
|
+
neighborCount++;
|
|
800
|
+
if (eWestCenter === EDGE_DIAGONAL_ULLR) {
|
|
801
|
+
wNeighborIndex = getNeighborIndex(cx, cy, 'W', 1);
|
|
802
|
+
wVector = [-0.75, 0.25];
|
|
803
|
+
} else if (eWestCenter === EDGE_DIAGONAL_LLUR) {
|
|
804
|
+
wNeighborIndex = getNeighborIndex(cx, cy, 'W', 1);
|
|
805
|
+
wVector = [-0.75, -0.25];
|
|
806
|
+
} else {
|
|
807
|
+
wNeighborIndex = getNeighborIndex(cx, cy, 'W', 0);
|
|
808
|
+
wVector = [-1.0, 0.0];
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (!ignoreS && eSouth === 0) {
|
|
812
|
+
sNeighborsFound = true;
|
|
813
|
+
neighborsFound = true;
|
|
814
|
+
neighborCount++;
|
|
815
|
+
if (eSouthCenter === EDGE_DIAGONAL_ULLR) {
|
|
816
|
+
sNeighborIndex = getNeighborIndex(cx, cy, 'S', 1);
|
|
817
|
+
sVector = [0.25, -0.75];
|
|
818
|
+
} else if (eSouthCenter === EDGE_DIAGONAL_LLUR) {
|
|
819
|
+
sNeighborIndex = getNeighborIndex(cx, cy, 'S', 0);
|
|
820
|
+
sVector = [-0.25, -0.75];
|
|
821
|
+
} else {
|
|
822
|
+
sNeighborIndex = getNeighborIndex(cx, cy, 'S', 0);
|
|
823
|
+
sVector = [0.0, -1.0];
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (!ignoreE && eEast === 0) {
|
|
827
|
+
eNeighborsFound = true;
|
|
828
|
+
neighborsFound = true;
|
|
829
|
+
neighborCount++;
|
|
830
|
+
if (eEastCenter === EDGE_DIAGONAL_ULLR) {
|
|
831
|
+
eNeighborIndex = getNeighborIndex(cx, cy, 'E', 0);
|
|
832
|
+
eVector = [0.75, -0.25];
|
|
833
|
+
} else if (eEastCenter === EDGE_DIAGONAL_LLUR) {
|
|
834
|
+
eNeighborIndex = getNeighborIndex(cx, cy, 'E', 0);
|
|
835
|
+
eVector = [0.75, 0.25];
|
|
836
|
+
} else {
|
|
837
|
+
eNeighborIndex = getNeighborIndex(cx, cy, 'E', 0);
|
|
838
|
+
eVector = [1.0, 0.0];
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (neighborsFound) {
|
|
843
|
+
const LLPixelColorIndex = [cx, cy];
|
|
844
|
+
const ULPixelColorIndex = [cx, cy + 1];
|
|
845
|
+
const LRPixelColorIndex = [cx + 1, cy];
|
|
846
|
+
const URPixelColorIndex = [cx + 1, cy + 1];
|
|
847
|
+
|
|
848
|
+
const centerPos = [cx + 0.5, cy + 0.5];
|
|
849
|
+
|
|
850
|
+
if (eCenter === EDGE_DIAGONAL_ULLR) {
|
|
851
|
+
let twoNeighbors = true;
|
|
852
|
+
v0_pos = [centerPos[0] - 0.25, centerPos[1] - 0.25];
|
|
853
|
+
let sIndex = sNeighborIndex;
|
|
854
|
+
let wIndex = wNeighborIndex;
|
|
855
|
+
if (sNeighborsFound) {
|
|
856
|
+
v0_flags = HAS_SOUTHERN_NEIGHBOR | HAS_SOUTHERN_SPLINE;
|
|
857
|
+
} else {
|
|
858
|
+
twoNeighbors = false;
|
|
859
|
+
}
|
|
860
|
+
if (wNeighborsFound) {
|
|
861
|
+
v0_flags |= HAS_WESTERN_NEIGHBOR | HAS_WESTERN_SPLINE;
|
|
862
|
+
} else {
|
|
863
|
+
twoNeighbors = false;
|
|
864
|
+
}
|
|
865
|
+
if (twoNeighbors) {
|
|
866
|
+
if (checkForCorner([sVector[0] + 0.25, sVector[1] + 0.25], [wVector[0] + 0.25, wVector[1] + 0.25])) {
|
|
867
|
+
v0_flags |= DONT_OPTIMIZE_S | DONT_OPTIMIZE_W;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
v0_neighbors = [-1, -1, sIndex, wIndex];
|
|
871
|
+
|
|
872
|
+
twoNeighbors = true;
|
|
873
|
+
v1_pos = [centerPos[0] + 0.25, centerPos[1] + 0.25];
|
|
874
|
+
let nIndex = nNeighborIndex;
|
|
875
|
+
let eIndex = eNeighborIndex;
|
|
876
|
+
if (nNeighborsFound) {
|
|
877
|
+
v1_flags = HAS_NORTHERN_NEIGHBOR | HAS_NORTHERN_SPLINE;
|
|
878
|
+
} else {
|
|
879
|
+
twoNeighbors = false;
|
|
880
|
+
}
|
|
881
|
+
if (eNeighborsFound) {
|
|
882
|
+
v1_flags |= HAS_EASTERN_NEIGHBOR | HAS_EASTERN_SPLINE;
|
|
883
|
+
} else {
|
|
884
|
+
twoNeighbors = false;
|
|
885
|
+
}
|
|
886
|
+
if (twoNeighbors) {
|
|
887
|
+
if (checkForCorner([nVector[0] - 0.25, nVector[1] - 0.25], [eVector[0] - 0.25, eVector[1] - 0.25])) {
|
|
888
|
+
v1_flags |= DONT_OPTIMIZE_N | DONT_OPTIMIZE_E;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
v1_neighbors = [nIndex, eIndex, -1, -1];
|
|
892
|
+
} else if (eCenter === EDGE_DIAGONAL_LLUR) {
|
|
893
|
+
let twoNeighbors = true;
|
|
894
|
+
v0_pos = [centerPos[0] - 0.25, centerPos[1] + 0.25];
|
|
895
|
+
let nIndex = nNeighborIndex;
|
|
896
|
+
let wIndex = wNeighborIndex;
|
|
897
|
+
if (nNeighborsFound) {
|
|
898
|
+
v0_flags = HAS_NORTHERN_NEIGHBOR | HAS_NORTHERN_SPLINE;
|
|
899
|
+
} else {
|
|
900
|
+
twoNeighbors = false;
|
|
901
|
+
}
|
|
902
|
+
if (wNeighborsFound) {
|
|
903
|
+
v0_flags |= HAS_WESTERN_NEIGHBOR | HAS_WESTERN_SPLINE;
|
|
904
|
+
} else {
|
|
905
|
+
twoNeighbors = false;
|
|
906
|
+
}
|
|
907
|
+
if (twoNeighbors) {
|
|
908
|
+
if (checkForCorner([nVector[0] + 0.25, nVector[1] - 0.25], [wVector[0] + 0.25, wVector[1] - 0.25])) {
|
|
909
|
+
v0_flags |= DONT_OPTIMIZE_N | DONT_OPTIMIZE_W;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
v0_neighbors = [nIndex, -1, -1, wIndex];
|
|
913
|
+
|
|
914
|
+
twoNeighbors = true;
|
|
915
|
+
v1_pos = [centerPos[0] + 0.25, centerPos[1] - 0.25];
|
|
916
|
+
let sIndex = sNeighborIndex;
|
|
917
|
+
let eIndex = eNeighborIndex;
|
|
918
|
+
if (sNeighborsFound) {
|
|
919
|
+
v1_flags = HAS_SOUTHERN_NEIGHBOR | HAS_SOUTHERN_SPLINE;
|
|
920
|
+
} else {
|
|
921
|
+
twoNeighbors = false;
|
|
922
|
+
}
|
|
923
|
+
if (eNeighborsFound) {
|
|
924
|
+
v1_flags |= HAS_EASTERN_NEIGHBOR | HAS_EASTERN_SPLINE;
|
|
925
|
+
} else {
|
|
926
|
+
twoNeighbors = false;
|
|
927
|
+
}
|
|
928
|
+
if (twoNeighbors) {
|
|
929
|
+
if (checkForCorner([sVector[0] - 0.25, sVector[1] + 0.25], [eVector[0] - 0.25, eVector[1] + 0.25])) {
|
|
930
|
+
v1_flags |= DONT_OPTIMIZE_S | DONT_OPTIMIZE_E;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
v1_neighbors = [-1, eIndex, sIndex, -1];
|
|
934
|
+
} else {
|
|
935
|
+
v0_pos = [centerPos[0], centerPos[1]];
|
|
936
|
+
let nIndex = nNeighborIndex;
|
|
937
|
+
let eIndex = eNeighborIndex;
|
|
938
|
+
let sIndex = sNeighborIndex;
|
|
939
|
+
let wIndex = wNeighborIndex;
|
|
940
|
+
if (nNeighborsFound) {
|
|
941
|
+
v0_flags |= HAS_NORTHERN_NEIGHBOR;
|
|
942
|
+
}
|
|
943
|
+
if (eNeighborsFound) {
|
|
944
|
+
v0_flags |= HAS_EASTERN_NEIGHBOR;
|
|
945
|
+
}
|
|
946
|
+
if (sNeighborsFound) {
|
|
947
|
+
v0_flags |= HAS_SOUTHERN_NEIGHBOR;
|
|
948
|
+
}
|
|
949
|
+
if (wNeighborsFound) {
|
|
950
|
+
v0_flags |= HAS_WESTERN_NEIGHBOR;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (neighborCount === 2) {
|
|
954
|
+
if (nNeighborsFound) {
|
|
955
|
+
v0_flags |= HAS_NORTHERN_SPLINE;
|
|
956
|
+
}
|
|
957
|
+
if (eNeighborsFound) {
|
|
958
|
+
v0_flags |= HAS_EASTERN_SPLINE;
|
|
959
|
+
}
|
|
960
|
+
if (sNeighborsFound) {
|
|
961
|
+
v0_flags |= HAS_SOUTHERN_SPLINE;
|
|
962
|
+
}
|
|
963
|
+
if (wNeighborsFound) {
|
|
964
|
+
v0_flags |= HAS_WESTERN_SPLINE;
|
|
965
|
+
}
|
|
966
|
+
} else if (neighborCount === 3) {
|
|
967
|
+
let contours = 0;
|
|
968
|
+
let contourCount = 0;
|
|
969
|
+
const p = [null, null, null];
|
|
970
|
+
if (nNeighborsFound && isContour(src, ULPixelColorIndex, URPixelColorIndex)) {
|
|
971
|
+
p[contourCount++] = nVector;
|
|
972
|
+
contours |= HAS_NORTHERN_SPLINE;
|
|
973
|
+
}
|
|
974
|
+
if (eNeighborsFound && isContour(src, URPixelColorIndex, LRPixelColorIndex)) {
|
|
975
|
+
p[contourCount++] = eVector;
|
|
976
|
+
contours |= HAS_EASTERN_SPLINE;
|
|
977
|
+
}
|
|
978
|
+
if (sNeighborsFound && isContour(src, LRPixelColorIndex, LLPixelColorIndex)) {
|
|
979
|
+
p[contourCount++] = sVector;
|
|
980
|
+
contours |= HAS_SOUTHERN_SPLINE;
|
|
981
|
+
}
|
|
982
|
+
if (wNeighborsFound && isContour(src, LLPixelColorIndex, ULPixelColorIndex)) {
|
|
983
|
+
p[contourCount++] = wVector;
|
|
984
|
+
contours |= HAS_WESTERN_SPLINE;
|
|
985
|
+
}
|
|
986
|
+
if (contourCount === 2) {
|
|
987
|
+
v0_flags |= contours | HAS_CORRECTED_POSITION;
|
|
988
|
+
v1_pos = calcAdjustedPoint([centerPos[0] + p[0][0], centerPos[1] + p[0][1]], centerPos, [centerPos[0] + p[1][0], centerPos[1] + p[1][1]]);
|
|
989
|
+
v1_flags = -1;
|
|
990
|
+
} else {
|
|
991
|
+
if (nNeighborsFound && sNeighborsFound) {
|
|
992
|
+
v0_flags |= HAS_NORTHERN_SPLINE | HAS_SOUTHERN_SPLINE | HAS_CORRECTED_POSITION;
|
|
993
|
+
v1_pos = calcAdjustedPoint([centerPos[0] + nVector[0], centerPos[1] + nVector[1]], centerPos, [centerPos[0] + sVector[0], centerPos[1] + sVector[1]]);
|
|
994
|
+
v1_flags = -1;
|
|
995
|
+
} else {
|
|
996
|
+
v0_flags |= HAS_EASTERN_SPLINE | HAS_WESTERN_SPLINE | HAS_CORRECTED_POSITION;
|
|
997
|
+
v1_pos = calcAdjustedPoint([centerPos[0] + eVector[0], centerPos[1] + eVector[1]], centerPos, [centerPos[0] + wVector[0], centerPos[1] + wVector[1]]);
|
|
998
|
+
v1_flags = -1;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
v0_neighbors = [nIndex, eIndex, sIndex, wIndex];
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const baseIndex = (cy * (w - 1) + cx) * 2;
|
|
1007
|
+
pos[baseIndex * 2] = v0_pos[0];
|
|
1008
|
+
pos[baseIndex * 2 + 1] = v0_pos[1];
|
|
1009
|
+
neighbors[baseIndex * 4] = v0_neighbors[0];
|
|
1010
|
+
neighbors[baseIndex * 4 + 1] = v0_neighbors[1];
|
|
1011
|
+
neighbors[baseIndex * 4 + 2] = v0_neighbors[2];
|
|
1012
|
+
neighbors[baseIndex * 4 + 3] = v0_neighbors[3];
|
|
1013
|
+
flags[baseIndex] = v0_flags;
|
|
1014
|
+
|
|
1015
|
+
pos[(baseIndex + 1) * 2] = v1_pos[0];
|
|
1016
|
+
pos[(baseIndex + 1) * 2 + 1] = v1_pos[1];
|
|
1017
|
+
neighbors[(baseIndex + 1) * 4] = v1_neighbors[0];
|
|
1018
|
+
neighbors[(baseIndex + 1) * 4 + 1] = v1_neighbors[1];
|
|
1019
|
+
neighbors[(baseIndex + 1) * 4 + 2] = v1_neighbors[2];
|
|
1020
|
+
neighbors[(baseIndex + 1) * 4 + 3] = v1_neighbors[3];
|
|
1021
|
+
flags[baseIndex + 1] = v1_flags;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
return { pos, neighbors, flags };
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function optimizeCellGraph(cell, width, height) {
|
|
1029
|
+
const count = cell.flags.length;
|
|
1030
|
+
const optimized = new Float32Array(cell.pos.length);
|
|
1031
|
+
optimized.set(cell.pos);
|
|
1032
|
+
|
|
1033
|
+
function getPos(idx) {
|
|
1034
|
+
return [cell.pos[idx * 2], cell.pos[idx * 2 + 1]];
|
|
1035
|
+
}
|
|
1036
|
+
function getPosOpt(idx) {
|
|
1037
|
+
return [optimized[idx * 2], optimized[idx * 2 + 1]];
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function calcPositionalEnergy(pNew, pOld) {
|
|
1041
|
+
const dx = pNew[0] - pOld[0];
|
|
1042
|
+
const dy = pNew[1] - pOld[1];
|
|
1043
|
+
const dist = POSITIONAL_ENERGY_SCALING * Math.hypot(dx, dy);
|
|
1044
|
+
return dist * dist * dist * dist;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function calcSegmentCurveEnergy(node1, node2, node3) {
|
|
1048
|
+
const tx = node1[0] - 2 * node2[0] + node3[0];
|
|
1049
|
+
const ty = node1[1] - 2 * node2[1] + node3[1];
|
|
1050
|
+
return tx * tx + ty * ty;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function calcGradient(node1, node2, node3) {
|
|
1054
|
+
return [8 * node2[0] - 4 * node1[0] - 4 * node3[0], 8 * node2[1] - 4 * node1[1] - 4 * node3[1]];
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function findBracket(pos, splineNeighbors, gradient) {
|
|
1058
|
+
let ax = BRACKET_SEARCH_A;
|
|
1059
|
+
let bx = BRACKET_SEARCH_B;
|
|
1060
|
+
let pOpt = [pos[0] - gradient[0] * ax, pos[1] - gradient[1] * ax];
|
|
1061
|
+
let fa = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1062
|
+
pOpt = [pos[0] - gradient[0] * bx, pos[1] - gradient[1] * bx];
|
|
1063
|
+
let fb = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1064
|
+
if (fb > fa) {
|
|
1065
|
+
let dum = ax; ax = bx; bx = dum;
|
|
1066
|
+
dum = fb; fb = fa; fa = dum;
|
|
1067
|
+
}
|
|
1068
|
+
let cx = bx + GOLD * (bx - ax);
|
|
1069
|
+
pOpt = [pos[0] - gradient[0] * cx, pos[1] - gradient[1] * cx];
|
|
1070
|
+
let fc = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1071
|
+
while (fb > fc) {
|
|
1072
|
+
const r = (bx - ax) * (fb - fc);
|
|
1073
|
+
const q = (bx - cx) * (fb - fa);
|
|
1074
|
+
const qr = q - r;
|
|
1075
|
+
let u = bx - ((bx - cx) * q - (bx - ax) * r) / (2.0 * Math.sign(qr) * Math.max(Math.abs(qr), TINY));
|
|
1076
|
+
const ulim = bx + GLIMIT * (cx - bx);
|
|
1077
|
+
let fu;
|
|
1078
|
+
if ((bx - u) * (u - cx) > 0.0) {
|
|
1079
|
+
pOpt = [pos[0] - gradient[0] * u, pos[1] - gradient[1] * u];
|
|
1080
|
+
fu = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1081
|
+
if (fu < fc) {
|
|
1082
|
+
return [bx, u, cx];
|
|
1083
|
+
}
|
|
1084
|
+
if (fu > fb) {
|
|
1085
|
+
return [ax, bx, u];
|
|
1086
|
+
}
|
|
1087
|
+
u = cx + GOLD * (cx - bx);
|
|
1088
|
+
pOpt = [pos[0] - gradient[0] * u, pos[1] - gradient[1] * u];
|
|
1089
|
+
fu = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1090
|
+
} else if ((cx - u) * (u - ulim) > 0.0) {
|
|
1091
|
+
pOpt = [pos[0] - gradient[0] * u, pos[1] - gradient[1] * u];
|
|
1092
|
+
fu = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1093
|
+
if (fu < fc) {
|
|
1094
|
+
let dum = cx + GOLD * (cx - bx);
|
|
1095
|
+
bx = cx; cx = u; u = dum;
|
|
1096
|
+
fb = fc; fc = fu;
|
|
1097
|
+
pOpt = [pos[0] - gradient[0] * u, pos[1] - gradient[1] * u];
|
|
1098
|
+
fu = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1099
|
+
}
|
|
1100
|
+
} else if ((u - ulim) * (ulim - cx) >= 0.0) {
|
|
1101
|
+
u = ulim;
|
|
1102
|
+
pOpt = [pos[0] - gradient[0] * u, pos[1] - gradient[1] * u];
|
|
1103
|
+
fu = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1104
|
+
} else {
|
|
1105
|
+
u = cx + GOLD * (cx - bx);
|
|
1106
|
+
pOpt = [pos[0] - gradient[0] * u, pos[1] - gradient[1] * u];
|
|
1107
|
+
fu = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1108
|
+
}
|
|
1109
|
+
ax = bx; bx = cx; cx = u;
|
|
1110
|
+
fa = fb; fb = fc; fc = fu;
|
|
1111
|
+
}
|
|
1112
|
+
return [ax, bx, cx];
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function searchOffset(pos, splineNeighbors) {
|
|
1116
|
+
let gradient = calcGradient(splineNeighbors[0], pos, splineNeighbors[1]);
|
|
1117
|
+
const glen = Math.hypot(gradient[0], gradient[1]);
|
|
1118
|
+
if (glen <= 0.0) {
|
|
1119
|
+
return [0, 0, 0];
|
|
1120
|
+
}
|
|
1121
|
+
gradient = [gradient[0] / glen, gradient[1] / glen];
|
|
1122
|
+
const bracket = findBracket(pos, splineNeighbors, gradient);
|
|
1123
|
+
let x0 = bracket[0];
|
|
1124
|
+
let x1 = 0;
|
|
1125
|
+
let x2 = 0;
|
|
1126
|
+
let x3 = bracket[2];
|
|
1127
|
+
if (Math.abs(bracket[2] - bracket[1]) > Math.abs(bracket[1] - bracket[0])) {
|
|
1128
|
+
x1 = bracket[1];
|
|
1129
|
+
x2 = bracket[1] + C * (bracket[2] - bracket[1]);
|
|
1130
|
+
} else {
|
|
1131
|
+
x1 = bracket[1] - C * (bracket[1] - bracket[0]);
|
|
1132
|
+
x2 = bracket[1];
|
|
1133
|
+
}
|
|
1134
|
+
let pOpt = [pos[0] - gradient[0] * x1, pos[1] - gradient[1] * x1];
|
|
1135
|
+
let f1 = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1136
|
+
pOpt = [pos[0] - gradient[0] * x2, pos[1] - gradient[1] * x2];
|
|
1137
|
+
let f2 = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1138
|
+
let counter = 0;
|
|
1139
|
+
let fx;
|
|
1140
|
+
while (Math.abs(x3 - x0) > TOL * (Math.abs(x1) + Math.abs(x2)) && (counter < LIMIT_SEARCH_ITERATIONS)) {
|
|
1141
|
+
counter++;
|
|
1142
|
+
if (f2 < f1) {
|
|
1143
|
+
x0 = x1; x1 = x2; x2 = R * x1 + C * x3;
|
|
1144
|
+
pOpt = [pos[0] - gradient[0] * x2, pos[1] - gradient[1] * x2];
|
|
1145
|
+
fx = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1146
|
+
f1 = f2; f2 = fx;
|
|
1147
|
+
} else {
|
|
1148
|
+
x3 = x2; x2 = x1; x1 = R * x2 + C * x0;
|
|
1149
|
+
pOpt = [pos[0] - gradient[0] * x1, pos[1] - gradient[1] * x1];
|
|
1150
|
+
fx = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1151
|
+
f2 = f1; f1 = fx;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const offset = (f1 < f2) ? x1 : x2;
|
|
1155
|
+
return [gradient[0], gradient[1], offset];
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
for (let i = 0; i < count; i++) {
|
|
1159
|
+
const flags = cell.flags[i];
|
|
1160
|
+
if (flags > 16 && flags < 512) {
|
|
1161
|
+
const base = i * 4;
|
|
1162
|
+
const neighbors = [cell.neighbors[base], cell.neighbors[base + 1], cell.neighbors[base + 2], cell.neighbors[base + 3]];
|
|
1163
|
+
const pos = getPosOpt(i);
|
|
1164
|
+
|
|
1165
|
+
let splineNeighbors = [null, null];
|
|
1166
|
+
let splineCount = 0;
|
|
1167
|
+
let splineNoOpt = false;
|
|
1168
|
+
|
|
1169
|
+
const hasN = (flags & HAS_NORTHERN_SPLINE) === HAS_NORTHERN_SPLINE;
|
|
1170
|
+
const hasE = (flags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE;
|
|
1171
|
+
const hasS = (flags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE;
|
|
1172
|
+
const hasW = (flags & HAS_WESTERN_SPLINE) === HAS_WESTERN_SPLINE;
|
|
1173
|
+
|
|
1174
|
+
if (hasN) {
|
|
1175
|
+
const neighborflags = cell.flags[neighbors[0]] | 0;
|
|
1176
|
+
if (((flags & DONT_OPTIMIZE_N) === DONT_OPTIMIZE_N) || ((neighborflags & DONT_OPTIMIZE_S) === DONT_OPTIMIZE_S)) {
|
|
1177
|
+
splineNoOpt = true;
|
|
1178
|
+
}
|
|
1179
|
+
if (!splineNoOpt) {
|
|
1180
|
+
if (((neighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) && ((neighborflags & HAS_SOUTHERN_SPLINE) !== HAS_SOUTHERN_SPLINE)) {
|
|
1181
|
+
splineNeighbors[splineCount++] = getPos(neighbors[0] + 1);
|
|
1182
|
+
} else {
|
|
1183
|
+
splineNeighbors[splineCount++] = getPos(neighbors[0]);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
if (hasE) {
|
|
1188
|
+
const neighborflags = cell.flags[neighbors[1]] | 0;
|
|
1189
|
+
if (((flags & DONT_OPTIMIZE_E) === DONT_OPTIMIZE_E) || ((neighborflags & DONT_OPTIMIZE_W) === DONT_OPTIMIZE_W)) {
|
|
1190
|
+
splineNoOpt = true;
|
|
1191
|
+
}
|
|
1192
|
+
if (!splineNoOpt) {
|
|
1193
|
+
if (((neighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) && ((neighborflags & HAS_WESTERN_SPLINE) !== HAS_WESTERN_SPLINE)) {
|
|
1194
|
+
splineNeighbors[splineCount++] = getPos(neighbors[1] + 1);
|
|
1195
|
+
} else {
|
|
1196
|
+
splineNeighbors[splineCount++] = getPos(neighbors[1]);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (hasS) {
|
|
1201
|
+
const neighborflags = cell.flags[neighbors[2]] | 0;
|
|
1202
|
+
if (((flags & DONT_OPTIMIZE_S) === DONT_OPTIMIZE_S) || ((neighborflags & DONT_OPTIMIZE_N) === DONT_OPTIMIZE_N)) {
|
|
1203
|
+
splineNoOpt = true;
|
|
1204
|
+
}
|
|
1205
|
+
if (!splineNoOpt) {
|
|
1206
|
+
if (((neighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) && ((neighborflags & HAS_NORTHERN_SPLINE) !== HAS_NORTHERN_SPLINE)) {
|
|
1207
|
+
splineNeighbors[splineCount++] = getPos(neighbors[2] + 1);
|
|
1208
|
+
} else {
|
|
1209
|
+
splineNeighbors[splineCount++] = getPos(neighbors[2]);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
if (hasW) {
|
|
1214
|
+
const neighborflags = cell.flags[neighbors[3]] | 0;
|
|
1215
|
+
if (((flags & DONT_OPTIMIZE_W) === DONT_OPTIMIZE_W) || ((neighborflags & DONT_OPTIMIZE_E) === DONT_OPTIMIZE_E)) {
|
|
1216
|
+
splineNoOpt = true;
|
|
1217
|
+
}
|
|
1218
|
+
if (!splineNoOpt) {
|
|
1219
|
+
if (((neighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) && ((neighborflags & HAS_EASTERN_SPLINE) !== HAS_EASTERN_SPLINE)) {
|
|
1220
|
+
splineNeighbors[splineCount++] = getPos(neighbors[3] + 1);
|
|
1221
|
+
} else {
|
|
1222
|
+
splineNeighbors[splineCount++] = getPos(neighbors[3]);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (splineCount === 2 && !splineNoOpt) {
|
|
1228
|
+
const shift = searchOffset(pos, splineNeighbors);
|
|
1229
|
+
optimized[i * 2] = pos[0] - shift[0] * shift[2];
|
|
1230
|
+
optimized[i * 2 + 1] = pos[1] - shift[1] * shift[2];
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
return optimized;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function computeCorrectedPositions(cell, optimized) {
|
|
1239
|
+
const count = cell.flags.length;
|
|
1240
|
+
const corrected = new Float32Array(optimized.length);
|
|
1241
|
+
corrected.set(optimized);
|
|
1242
|
+
|
|
1243
|
+
function getPos(idx) {
|
|
1244
|
+
return [cell.pos[idx * 2], cell.pos[idx * 2 + 1]];
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function calcAdjustedPoint(p0, p1, p2) {
|
|
1248
|
+
return [0.125 * p0[0] + 0.75 * p1[0] + 0.125 * p2[0], 0.125 * p0[1] + 0.75 * p1[1] + 0.125 * p2[1]];
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
for (let i = 0; i < count; i++) {
|
|
1252
|
+
const flags = cell.flags[i];
|
|
1253
|
+
if (flags === -1) {
|
|
1254
|
+
const parentId = i - 1;
|
|
1255
|
+
const parentPosition = getPos(parentId);
|
|
1256
|
+
const parentFlags = cell.flags[parentId] | 0;
|
|
1257
|
+
const base = parentId * 4;
|
|
1258
|
+
const parentNeighborIndices = [cell.neighbors[base], cell.neighbors[base + 1], cell.neighbors[base + 2], cell.neighbors[base + 3]];
|
|
1259
|
+
const splinePoints = [null, null];
|
|
1260
|
+
let countSp = 0;
|
|
1261
|
+
if ((parentFlags & HAS_NORTHERN_SPLINE) === HAS_NORTHERN_SPLINE) {
|
|
1262
|
+
splinePoints[countSp++] = getPos(parentNeighborIndices[0]);
|
|
1263
|
+
}
|
|
1264
|
+
if ((parentFlags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE) {
|
|
1265
|
+
splinePoints[countSp++] = getPos(parentNeighborIndices[1]);
|
|
1266
|
+
}
|
|
1267
|
+
if ((parentFlags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE) {
|
|
1268
|
+
splinePoints[countSp++] = getPos(parentNeighborIndices[2]);
|
|
1269
|
+
}
|
|
1270
|
+
if ((parentFlags & HAS_WESTERN_SPLINE) === HAS_WESTERN_SPLINE) {
|
|
1271
|
+
splinePoints[countSp++] = getPos(parentNeighborIndices[3]);
|
|
1272
|
+
}
|
|
1273
|
+
if (countSp === 2) {
|
|
1274
|
+
const p = calcAdjustedPoint(splinePoints[0], parentPosition, splinePoints[1]);
|
|
1275
|
+
corrected[i * 2] = p[0];
|
|
1276
|
+
corrected[i * 2 + 1] = p[1];
|
|
1277
|
+
} else {
|
|
1278
|
+
corrected[i * 2] = parentPosition[0];
|
|
1279
|
+
corrected[i * 2 + 1] = parentPosition[1];
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return corrected;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function gaussRasterize(src, sim, cell, positions, outW, outH) {
|
|
1288
|
+
const w = src.width;
|
|
1289
|
+
const h = src.height;
|
|
1290
|
+
const out = new Uint8Array(outW * outH * 4);
|
|
1291
|
+
|
|
1292
|
+
function getG(x, y) {
|
|
1293
|
+
if (x < 0 || y < 0 || x >= sim.w || y >= sim.h) {
|
|
1294
|
+
return 0;
|
|
1295
|
+
}
|
|
1296
|
+
return sim.g[y * sim.w + x] | 0;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function getPos(idx) {
|
|
1300
|
+
return [positions[idx * 2], positions[idx * 2 + 1]];
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function getNeighborIndex(sourceIndex, dir) {
|
|
1304
|
+
const base = sourceIndex * 4;
|
|
1305
|
+
if (dir === NORTH) {
|
|
1306
|
+
return cell.neighbors[base];
|
|
1307
|
+
}
|
|
1308
|
+
if (dir === EAST) {
|
|
1309
|
+
return cell.neighbors[base + 1];
|
|
1310
|
+
}
|
|
1311
|
+
if (dir === SOUTH) {
|
|
1312
|
+
return cell.neighbors[base + 2];
|
|
1313
|
+
}
|
|
1314
|
+
if (dir === WEST) {
|
|
1315
|
+
return cell.neighbors[base + 3];
|
|
1316
|
+
}
|
|
1317
|
+
return -1;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function calcSplinePoint(p0, p1, p2, t) {
|
|
1321
|
+
const t2 = 0.5 * t * t;
|
|
1322
|
+
const a = t2 - t + 0.5;
|
|
1323
|
+
const b = -2.0 * t2 + t + 0.5;
|
|
1324
|
+
return [a * p0[0] + b * p1[0] + t2 * p2[0], a * p0[1] + b * p1[1] + t2 * p2[1]];
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
function intersects(a0, a1, b0, b1) {
|
|
1328
|
+
const r = [a1[0] - a0[0], a1[1] - a0[1]];
|
|
1329
|
+
const s = [b1[0] - b0[0], b1[1] - b0[1]];
|
|
1330
|
+
const rXs = r[0] * s[1] - r[1] * s[0];
|
|
1331
|
+
if (rXs === 0.0) {
|
|
1332
|
+
return false;
|
|
1333
|
+
}
|
|
1334
|
+
const ba = [b0[0] - a0[0], b0[1] - a0[1]];
|
|
1335
|
+
const t = (ba[0] * s[1] - ba[1] * s[0]) / rXs;
|
|
1336
|
+
if (t < 0.0 || t > 1.0) {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1339
|
+
const u = (ba[0] * r[1] - ba[1] * r[0]) / rXs;
|
|
1340
|
+
if (u < 0.0 || u > 1.0) {
|
|
1341
|
+
return false;
|
|
1342
|
+
}
|
|
1343
|
+
return true;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function computeValence(flags) {
|
|
1347
|
+
let v = 0;
|
|
1348
|
+
if ((flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
|
|
1349
|
+
v++;
|
|
1350
|
+
}
|
|
1351
|
+
if ((flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
|
|
1352
|
+
v++;
|
|
1353
|
+
}
|
|
1354
|
+
if ((flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
|
|
1355
|
+
v++;
|
|
1356
|
+
}
|
|
1357
|
+
if ((flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
|
|
1358
|
+
v++;
|
|
1359
|
+
}
|
|
1360
|
+
return v;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function getCPs(node0neighborIndex, dir) {
|
|
1364
|
+
let cpArray = [node0neighborIndex, -1];
|
|
1365
|
+
let checkFwd = [0, 0, 0];
|
|
1366
|
+
let chkdirs = [0, 0];
|
|
1367
|
+
let checkBack = 0;
|
|
1368
|
+
if (dir === NORTH) {
|
|
1369
|
+
checkFwd = [HAS_NORTHERN_SPLINE, HAS_EASTERN_SPLINE, HAS_WESTERN_SPLINE];
|
|
1370
|
+
checkBack = HAS_SOUTHERN_SPLINE;
|
|
1371
|
+
chkdirs = [EAST, WEST];
|
|
1372
|
+
} else if (dir === EAST) {
|
|
1373
|
+
checkFwd = [HAS_EASTERN_SPLINE, HAS_SOUTHERN_SPLINE, HAS_NORTHERN_SPLINE];
|
|
1374
|
+
checkBack = HAS_WESTERN_SPLINE;
|
|
1375
|
+
chkdirs = [SOUTH, NORTH];
|
|
1376
|
+
} else if (dir === SOUTH) {
|
|
1377
|
+
checkFwd = [HAS_SOUTHERN_SPLINE, HAS_WESTERN_SPLINE, HAS_EASTERN_SPLINE];
|
|
1378
|
+
checkBack = HAS_NORTHERN_SPLINE;
|
|
1379
|
+
chkdirs = [WEST, EAST];
|
|
1380
|
+
} else if (dir === WEST) {
|
|
1381
|
+
checkFwd = [HAS_WESTERN_SPLINE, HAS_NORTHERN_SPLINE, HAS_SOUTHERN_SPLINE];
|
|
1382
|
+
checkBack = HAS_EASTERN_SPLINE;
|
|
1383
|
+
chkdirs = [NORTH, SOUTH];
|
|
1384
|
+
}
|
|
1385
|
+
const node0neighborFlags = cell.flags[node0neighborIndex] | 0;
|
|
1386
|
+
if ((node0neighborFlags & checkBack) === checkBack) {
|
|
1387
|
+
if ((node0neighborFlags & checkFwd[0]) === checkFwd[0]) {
|
|
1388
|
+
const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, dir);
|
|
1389
|
+
const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
|
|
1390
|
+
if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
|
|
1391
|
+
cpArray[1] = neighborsNeighborIndex + 1;
|
|
1392
|
+
} else {
|
|
1393
|
+
cpArray[1] = neighborsNeighborIndex;
|
|
1394
|
+
}
|
|
1395
|
+
} else if ((node0neighborFlags & checkFwd[1]) === checkFwd[1]) {
|
|
1396
|
+
const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, chkdirs[0]);
|
|
1397
|
+
const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
|
|
1398
|
+
if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
|
|
1399
|
+
cpArray[1] = neighborsNeighborIndex + 1;
|
|
1400
|
+
} else {
|
|
1401
|
+
cpArray[1] = neighborsNeighborIndex;
|
|
1402
|
+
}
|
|
1403
|
+
} else if ((node0neighborFlags & checkFwd[2]) === checkFwd[2]) {
|
|
1404
|
+
const neighborsNeighborIndex = getNeighborIndex(node0neighborIndex, chkdirs[1]);
|
|
1405
|
+
const neighborsNeighborflags = cell.flags[neighborsNeighborIndex] | 0;
|
|
1406
|
+
if ((neighborsNeighborflags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
|
|
1407
|
+
cpArray[1] = neighborsNeighborIndex + 1;
|
|
1408
|
+
} else {
|
|
1409
|
+
cpArray[1] = neighborsNeighborIndex;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
} else {
|
|
1413
|
+
if ((node0neighborFlags & HAS_CORRECTED_POSITION) === HAS_CORRECTED_POSITION) {
|
|
1414
|
+
cpArray[0]++;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return cpArray;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
for (let oy = 0; oy < outH; oy++) {
|
|
1421
|
+
for (let ox = 0; ox < outW; ox++) {
|
|
1422
|
+
let influencingPixels = [true, true, true, true];
|
|
1423
|
+
const cellSpaceCoords = [
|
|
1424
|
+
(w - 1) * (ox / (outW - 1)),
|
|
1425
|
+
(h - 1) * (oy / (outH - 1)),
|
|
1426
|
+
];
|
|
1427
|
+
const fragmentBaseKnotIndex = (2 * Math.floor(cellSpaceCoords[0]) + Math.floor(cellSpaceCoords[1]) * 2 * (w - 1)) | 0;
|
|
1428
|
+
const node0flags = cell.flags[fragmentBaseKnotIndex] | 0;
|
|
1429
|
+
let hasCorrectedPosition = false;
|
|
1430
|
+
|
|
1431
|
+
const ULCoords = [Math.floor(cellSpaceCoords[0]), Math.ceil(cellSpaceCoords[1])];
|
|
1432
|
+
const URCoords = [Math.ceil(cellSpaceCoords[0]), Math.ceil(cellSpaceCoords[1])];
|
|
1433
|
+
const LLCoords = [Math.floor(cellSpaceCoords[0]), Math.floor(cellSpaceCoords[1])];
|
|
1434
|
+
const LRCoords = [Math.ceil(cellSpaceCoords[0]), Math.floor(cellSpaceCoords[1])];
|
|
1435
|
+
|
|
1436
|
+
function findSegmentIntersections(p0, p1, p2) {
|
|
1437
|
+
let pointA = calcSplinePoint(p0, p1, p2, 0.0);
|
|
1438
|
+
for (let t = STEP; t < (1.0 + STEP); t += STEP) {
|
|
1439
|
+
const pointB = calcSplinePoint(p0, p1, p2, t);
|
|
1440
|
+
if (intersects(cellSpaceCoords, ULCoords, pointA, pointB)) {
|
|
1441
|
+
influencingPixels[0] = false;
|
|
1442
|
+
}
|
|
1443
|
+
if (intersects(cellSpaceCoords, URCoords, pointA, pointB)) {
|
|
1444
|
+
influencingPixels[1] = false;
|
|
1445
|
+
}
|
|
1446
|
+
if (intersects(cellSpaceCoords, LLCoords, pointA, pointB)) {
|
|
1447
|
+
influencingPixels[2] = false;
|
|
1448
|
+
}
|
|
1449
|
+
if (intersects(cellSpaceCoords, LRCoords, pointA, pointB)) {
|
|
1450
|
+
influencingPixels[3] = false;
|
|
1451
|
+
}
|
|
1452
|
+
pointA = pointB;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (node0flags > 0) {
|
|
1457
|
+
const node0neighbors = [
|
|
1458
|
+
cell.neighbors[fragmentBaseKnotIndex * 4],
|
|
1459
|
+
cell.neighbors[fragmentBaseKnotIndex * 4 + 1],
|
|
1460
|
+
cell.neighbors[fragmentBaseKnotIndex * 4 + 2],
|
|
1461
|
+
cell.neighbors[fragmentBaseKnotIndex * 4 + 3],
|
|
1462
|
+
];
|
|
1463
|
+
const node0valence = computeValence(node0flags);
|
|
1464
|
+
let node0pos = getPos(fragmentBaseKnotIndex);
|
|
1465
|
+
if (node0valence === 1) {
|
|
1466
|
+
let cpArray = [-1, -1];
|
|
1467
|
+
if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
|
|
1468
|
+
cpArray = getCPs(node0neighbors[0], NORTH);
|
|
1469
|
+
} else if ((node0flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
|
|
1470
|
+
cpArray = getCPs(node0neighbors[1], EAST);
|
|
1471
|
+
} else if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
|
|
1472
|
+
cpArray = getCPs(node0neighbors[2], SOUTH);
|
|
1473
|
+
} else if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
|
|
1474
|
+
cpArray = getCPs(node0neighbors[3], WEST);
|
|
1475
|
+
}
|
|
1476
|
+
const p1pos = getPos(cpArray[0]);
|
|
1477
|
+
findSegmentIntersections(node0pos, node0pos, p1pos);
|
|
1478
|
+
if (cpArray[1] > -1) {
|
|
1479
|
+
const p2pos = getPos(cpArray[1]);
|
|
1480
|
+
findSegmentIntersections(node0pos, p1pos, p2pos);
|
|
1481
|
+
} else {
|
|
1482
|
+
findSegmentIntersections(node0pos, p1pos, p1pos);
|
|
1483
|
+
}
|
|
1484
|
+
} else if (node0valence === 2) {
|
|
1485
|
+
let cpArray = [-1, -1, -1, -1];
|
|
1486
|
+
let foundFirst = false;
|
|
1487
|
+
if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) { cpArray[0] = getCPs(node0neighbors[0], NORTH)[0]; cpArray[1] = getCPs(node0neighbors[0], NORTH)[1]; foundFirst = true; }
|
|
1488
|
+
if ((node0flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
|
|
1489
|
+
const tmp = getCPs(node0neighbors[1], EAST);
|
|
1490
|
+
if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; }
|
|
1491
|
+
else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
|
|
1492
|
+
}
|
|
1493
|
+
if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
|
|
1494
|
+
const tmp = getCPs(node0neighbors[2], SOUTH);
|
|
1495
|
+
if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; }
|
|
1496
|
+
else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
|
|
1497
|
+
}
|
|
1498
|
+
if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
|
|
1499
|
+
const tmp = getCPs(node0neighbors[3], WEST);
|
|
1500
|
+
cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
|
|
1501
|
+
}
|
|
1502
|
+
const pm1pos = getPos(cpArray[0]);
|
|
1503
|
+
const p1pos = getPos(cpArray[2]);
|
|
1504
|
+
findSegmentIntersections(pm1pos, node0pos, p1pos);
|
|
1505
|
+
if (cpArray[1] > -1) {
|
|
1506
|
+
const pm2pos = getPos(cpArray[1]);
|
|
1507
|
+
findSegmentIntersections(node0pos, pm1pos, pm2pos);
|
|
1508
|
+
} else {
|
|
1509
|
+
findSegmentIntersections(node0pos, pm1pos, pm1pos);
|
|
1510
|
+
}
|
|
1511
|
+
if (cpArray[3] > -1) {
|
|
1512
|
+
const p2pos = getPos(cpArray[3]);
|
|
1513
|
+
findSegmentIntersections(node0pos, p1pos, p2pos);
|
|
1514
|
+
} else {
|
|
1515
|
+
findSegmentIntersections(node0pos, p1pos, p1pos);
|
|
1516
|
+
}
|
|
1517
|
+
} else if (node0valence === 3) {
|
|
1518
|
+
hasCorrectedPosition = true;
|
|
1519
|
+
let cpArray = [-1, -1, -1, -1];
|
|
1520
|
+
let foundFirst = false;
|
|
1521
|
+
let tBaseDir = 0;
|
|
1522
|
+
let tBaseNeighborIndex = -1;
|
|
1523
|
+
if ((node0flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
|
|
1524
|
+
if ((node0flags & HAS_NORTHERN_SPLINE) === HAS_NORTHERN_SPLINE) { const tmp = getCPs(node0neighbors[0], NORTH); cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
|
|
1525
|
+
else { tBaseDir = NORTH; tBaseNeighborIndex = node0neighbors[0]; }
|
|
1526
|
+
}
|
|
1527
|
+
if ((node0flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
|
|
1528
|
+
if ((node0flags & HAS_EASTERN_SPLINE) === HAS_EASTERN_SPLINE) {
|
|
1529
|
+
const tmp = getCPs(node0neighbors[1], EAST);
|
|
1530
|
+
if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
|
|
1531
|
+
} else { tBaseDir = EAST; tBaseNeighborIndex = node0neighbors[1]; }
|
|
1532
|
+
}
|
|
1533
|
+
if ((node0flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
|
|
1534
|
+
if ((node0flags & HAS_SOUTHERN_SPLINE) === HAS_SOUTHERN_SPLINE) {
|
|
1535
|
+
const tmp = getCPs(node0neighbors[2], SOUTH);
|
|
1536
|
+
if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
|
|
1537
|
+
} else { tBaseDir = SOUTH; tBaseNeighborIndex = node0neighbors[2]; }
|
|
1538
|
+
}
|
|
1539
|
+
if ((node0flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
|
|
1540
|
+
if ((node0flags & HAS_WESTERN_SPLINE) === HAS_WESTERN_SPLINE) {
|
|
1541
|
+
const tmp = getCPs(node0neighbors[3], WEST);
|
|
1542
|
+
cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
|
|
1543
|
+
} else { tBaseDir = WEST; tBaseNeighborIndex = node0neighbors[3]; }
|
|
1544
|
+
}
|
|
1545
|
+
const pm1pos = getPos(cpArray[0]);
|
|
1546
|
+
const p1pos = getPos(cpArray[2]);
|
|
1547
|
+
findSegmentIntersections(pm1pos, node0pos, p1pos);
|
|
1548
|
+
if (cpArray[1] > -1) {
|
|
1549
|
+
const pm2pos = getPos(cpArray[1]);
|
|
1550
|
+
findSegmentIntersections(node0pos, pm1pos, pm2pos);
|
|
1551
|
+
} else {
|
|
1552
|
+
findSegmentIntersections(node0pos, pm1pos, pm1pos);
|
|
1553
|
+
}
|
|
1554
|
+
if (cpArray[3] > -1) {
|
|
1555
|
+
const p2pos = getPos(cpArray[3]);
|
|
1556
|
+
findSegmentIntersections(node0pos, p1pos, p2pos);
|
|
1557
|
+
} else {
|
|
1558
|
+
findSegmentIntersections(node0pos, p1pos, p1pos);
|
|
1559
|
+
}
|
|
1560
|
+
const tCP = getCPs(tBaseNeighborIndex, tBaseDir);
|
|
1561
|
+
node0pos = getPos(fragmentBaseKnotIndex + 1);
|
|
1562
|
+
const p1pos2 = getPos(tCP[0]);
|
|
1563
|
+
findSegmentIntersections(node0pos, node0pos, p1pos2);
|
|
1564
|
+
if (tCP[1] > -1) {
|
|
1565
|
+
const p2pos2 = getPos(tCP[1]);
|
|
1566
|
+
findSegmentIntersections(node0pos, p1pos2, p2pos2);
|
|
1567
|
+
} else {
|
|
1568
|
+
findSegmentIntersections(node0pos, p1pos2, p1pos2);
|
|
1569
|
+
}
|
|
1570
|
+
} else {
|
|
1571
|
+
let cpArray = getCPs(node0neighbors[0], NORTH);
|
|
1572
|
+
let p1pos = getPos(cpArray[0]);
|
|
1573
|
+
findSegmentIntersections(node0pos, node0pos, p1pos);
|
|
1574
|
+
if (cpArray[1] > -1) {
|
|
1575
|
+
let p2pos = getPos(cpArray[1]);
|
|
1576
|
+
findSegmentIntersections(node0pos, p1pos, p2pos);
|
|
1577
|
+
} else {
|
|
1578
|
+
findSegmentIntersections(node0pos, p1pos, p1pos);
|
|
1579
|
+
}
|
|
1580
|
+
cpArray = getCPs(node0neighbors[1], EAST);
|
|
1581
|
+
p1pos = getPos(cpArray[0]);
|
|
1582
|
+
findSegmentIntersections(node0pos, node0pos, p1pos);
|
|
1583
|
+
if (cpArray[1] > -1) {
|
|
1584
|
+
let p2pos = getPos(cpArray[1]);
|
|
1585
|
+
findSegmentIntersections(node0pos, p1pos, p2pos);
|
|
1586
|
+
} else {
|
|
1587
|
+
findSegmentIntersections(node0pos, p1pos, p1pos);
|
|
1588
|
+
}
|
|
1589
|
+
cpArray = getCPs(node0neighbors[2], SOUTH);
|
|
1590
|
+
p1pos = getPos(cpArray[0]);
|
|
1591
|
+
findSegmentIntersections(node0pos, node0pos, p1pos);
|
|
1592
|
+
if (cpArray[1] > -1) {
|
|
1593
|
+
let p2pos = getPos(cpArray[1]);
|
|
1594
|
+
findSegmentIntersections(node0pos, p1pos, p2pos);
|
|
1595
|
+
} else {
|
|
1596
|
+
findSegmentIntersections(node0pos, p1pos, p1pos);
|
|
1597
|
+
}
|
|
1598
|
+
cpArray = getCPs(node0neighbors[3], WEST);
|
|
1599
|
+
p1pos = getPos(cpArray[0]);
|
|
1600
|
+
findSegmentIntersections(node0pos, node0pos, p1pos);
|
|
1601
|
+
if (cpArray[1] > -1) {
|
|
1602
|
+
let p2pos = getPos(cpArray[1]);
|
|
1603
|
+
findSegmentIntersections(node0pos, p1pos, p2pos);
|
|
1604
|
+
} else {
|
|
1605
|
+
findSegmentIntersections(node0pos, p1pos, p1pos);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (!hasCorrectedPosition) {
|
|
1611
|
+
const node1flags = cell.flags[fragmentBaseKnotIndex + 1] | 0;
|
|
1612
|
+
if (node1flags > 0) {
|
|
1613
|
+
const node1neighbors = [
|
|
1614
|
+
cell.neighbors[(fragmentBaseKnotIndex + 1) * 4],
|
|
1615
|
+
cell.neighbors[(fragmentBaseKnotIndex + 1) * 4 + 1],
|
|
1616
|
+
cell.neighbors[(fragmentBaseKnotIndex + 1) * 4 + 2],
|
|
1617
|
+
cell.neighbors[(fragmentBaseKnotIndex + 1) * 4 + 3],
|
|
1618
|
+
];
|
|
1619
|
+
const node1valence = computeValence(node1flags);
|
|
1620
|
+
const node1pos = getPos(fragmentBaseKnotIndex + 1);
|
|
1621
|
+
if (node1valence === 1) {
|
|
1622
|
+
let cpArray = [-1, -1];
|
|
1623
|
+
if ((node1flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) {
|
|
1624
|
+
cpArray = getCPs(node1neighbors[0], NORTH);
|
|
1625
|
+
} else if ((node1flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
|
|
1626
|
+
cpArray = getCPs(node1neighbors[1], EAST);
|
|
1627
|
+
} else if ((node1flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
|
|
1628
|
+
cpArray = getCPs(node1neighbors[2], SOUTH);
|
|
1629
|
+
} else if ((node1flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
|
|
1630
|
+
cpArray = getCPs(node1neighbors[3], WEST);
|
|
1631
|
+
}
|
|
1632
|
+
const p1pos = getPos(cpArray[0]);
|
|
1633
|
+
findSegmentIntersections(node1pos, node1pos, p1pos);
|
|
1634
|
+
if (cpArray[1] > -1) {
|
|
1635
|
+
const p2pos = getPos(cpArray[1]);
|
|
1636
|
+
findSegmentIntersections(node1pos, p1pos, p2pos);
|
|
1637
|
+
} else {
|
|
1638
|
+
findSegmentIntersections(node1pos, p1pos, p1pos);
|
|
1639
|
+
}
|
|
1640
|
+
} else if (node1valence === 2) {
|
|
1641
|
+
let cpArray = [-1, -1, -1, -1];
|
|
1642
|
+
let foundFirst = false;
|
|
1643
|
+
if ((node1flags & HAS_NORTHERN_NEIGHBOR) === HAS_NORTHERN_NEIGHBOR) { const tmp = getCPs(node1neighbors[0], NORTH); cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
|
|
1644
|
+
if ((node1flags & HAS_EASTERN_NEIGHBOR) === HAS_EASTERN_NEIGHBOR) {
|
|
1645
|
+
const tmp = getCPs(node1neighbors[1], EAST);
|
|
1646
|
+
if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
|
|
1647
|
+
}
|
|
1648
|
+
if ((node1flags & HAS_SOUTHERN_NEIGHBOR) === HAS_SOUTHERN_NEIGHBOR) {
|
|
1649
|
+
const tmp = getCPs(node1neighbors[2], SOUTH);
|
|
1650
|
+
if (foundFirst) { cpArray[2] = tmp[0]; cpArray[3] = tmp[1]; } else { cpArray[0] = tmp[0]; cpArray[1] = tmp[1]; foundFirst = true; }
|
|
1651
|
+
}
|
|
1652
|
+
if ((node1flags & HAS_WESTERN_NEIGHBOR) === HAS_WESTERN_NEIGHBOR) {
|
|
1653
|
+
const tmp = getCPs(node1neighbors[3], WEST);
|
|
1654
|
+
cpArray[2] = tmp[0]; cpArray[3] = tmp[1];
|
|
1655
|
+
}
|
|
1656
|
+
const pm1pos = getPos(cpArray[0]);
|
|
1657
|
+
const p1pos = getPos(cpArray[2]);
|
|
1658
|
+
findSegmentIntersections(pm1pos, node1pos, p1pos);
|
|
1659
|
+
if (cpArray[1] > -1) {
|
|
1660
|
+
const pm2pos = getPos(cpArray[1]);
|
|
1661
|
+
findSegmentIntersections(node1pos, pm1pos, pm2pos);
|
|
1662
|
+
} else {
|
|
1663
|
+
findSegmentIntersections(node1pos, pm1pos, pm1pos);
|
|
1664
|
+
}
|
|
1665
|
+
if (cpArray[3] > -1) {
|
|
1666
|
+
const p2pos = getPos(cpArray[3]);
|
|
1667
|
+
findSegmentIntersections(node1pos, p1pos, p2pos);
|
|
1668
|
+
} else {
|
|
1669
|
+
findSegmentIntersections(node1pos, p1pos, p1pos);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
let colorSum = [0, 0, 0, 0];
|
|
1676
|
+
let weightSum = 0.0;
|
|
1677
|
+
|
|
1678
|
+
function addWeightedColor(px, py) {
|
|
1679
|
+
const col = fetchPixelRGBA(src, px, py);
|
|
1680
|
+
const dx = cellSpaceCoords[0] - px;
|
|
1681
|
+
const dy = cellSpaceCoords[1] - py;
|
|
1682
|
+
const dist = Math.hypot(dx, dy);
|
|
1683
|
+
const weight = Math.exp(-(dist * dist) * GAUSS_MULTIPLIER);
|
|
1684
|
+
colorSum[0] += col[0] * weight;
|
|
1685
|
+
colorSum[1] += col[1] * weight;
|
|
1686
|
+
colorSum[2] += col[2] * weight;
|
|
1687
|
+
colorSum[3] += col[3] * weight;
|
|
1688
|
+
weightSum += weight;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
if (influencingPixels[0]) {
|
|
1692
|
+
addWeightedColor(ULCoords[0], ULCoords[1]);
|
|
1693
|
+
const edges = getG(2 * ULCoords[0] + 1, 2 * ULCoords[1] + 1);
|
|
1694
|
+
if ((edges & SOUTHWEST) === SOUTHWEST) {
|
|
1695
|
+
addWeightedColor(ULCoords[0] - 1, ULCoords[1] - 1);
|
|
1696
|
+
}
|
|
1697
|
+
if ((edges & WEST) === WEST) {
|
|
1698
|
+
addWeightedColor(ULCoords[0] - 1, ULCoords[1]);
|
|
1699
|
+
}
|
|
1700
|
+
if ((edges & NORTHWEST) === NORTHWEST) {
|
|
1701
|
+
addWeightedColor(ULCoords[0] - 1, ULCoords[1] + 1);
|
|
1702
|
+
}
|
|
1703
|
+
if ((edges & NORTH) === NORTH) {
|
|
1704
|
+
addWeightedColor(ULCoords[0], ULCoords[1] + 1);
|
|
1705
|
+
}
|
|
1706
|
+
if ((edges & NORTHEAST) === NORTHEAST) {
|
|
1707
|
+
addWeightedColor(ULCoords[0] + 1, ULCoords[1] + 1);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (influencingPixels[1]) {
|
|
1711
|
+
addWeightedColor(URCoords[0], URCoords[1]);
|
|
1712
|
+
const edges = getG(2 * URCoords[0] + 1, 2 * URCoords[1] + 1);
|
|
1713
|
+
if ((edges & NORTH) === NORTH) {
|
|
1714
|
+
addWeightedColor(URCoords[0], URCoords[1] + 1);
|
|
1715
|
+
}
|
|
1716
|
+
if ((edges & NORTHEAST) === NORTHEAST) {
|
|
1717
|
+
addWeightedColor(URCoords[0] + 1, URCoords[1] + 1);
|
|
1718
|
+
}
|
|
1719
|
+
if ((edges & EAST) === EAST) {
|
|
1720
|
+
addWeightedColor(URCoords[0] + 1, URCoords[1]);
|
|
1721
|
+
}
|
|
1722
|
+
if ((edges & SOUTHEAST) === SOUTHEAST) {
|
|
1723
|
+
addWeightedColor(URCoords[0] + 1, URCoords[1] - 1);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
if (influencingPixels[2]) {
|
|
1727
|
+
addWeightedColor(LLCoords[0], LLCoords[1]);
|
|
1728
|
+
const edges = getG(2 * LLCoords[0] + 1, 2 * LLCoords[1] + 1);
|
|
1729
|
+
if ((edges & WEST) === WEST) {
|
|
1730
|
+
addWeightedColor(LLCoords[0] - 1, LLCoords[1]);
|
|
1731
|
+
}
|
|
1732
|
+
if ((edges & SOUTHWEST) === SOUTHWEST) {
|
|
1733
|
+
addWeightedColor(LLCoords[0] - 1, LLCoords[1] - 1);
|
|
1734
|
+
}
|
|
1735
|
+
if ((edges & SOUTH) === SOUTH) {
|
|
1736
|
+
addWeightedColor(LLCoords[0], LLCoords[1] - 1);
|
|
1737
|
+
}
|
|
1738
|
+
if ((edges & SOUTHEAST) === SOUTHEAST) {
|
|
1739
|
+
addWeightedColor(LLCoords[0] + 1, LLCoords[1] - 1);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
if (influencingPixels[3]) {
|
|
1743
|
+
addWeightedColor(LRCoords[0], LRCoords[1]);
|
|
1744
|
+
const edges = getG(2 * LRCoords[0] + 1, 2 * LRCoords[1] + 1);
|
|
1745
|
+
if ((edges & NORTHEAST) === NORTHEAST) {
|
|
1746
|
+
addWeightedColor(LRCoords[0] + 1, LRCoords[1] + 1);
|
|
1747
|
+
}
|
|
1748
|
+
if ((edges & EAST) === EAST) {
|
|
1749
|
+
addWeightedColor(LRCoords[0] + 1, LRCoords[1]);
|
|
1750
|
+
}
|
|
1751
|
+
if ((edges & SOUTHWEST) === SOUTHWEST) {
|
|
1752
|
+
addWeightedColor(LRCoords[0] - 1, LRCoords[1] - 1);
|
|
1753
|
+
}
|
|
1754
|
+
if ((edges & SOUTH) === SOUTH) {
|
|
1755
|
+
addWeightedColor(LRCoords[0], LRCoords[1] - 1);
|
|
1756
|
+
}
|
|
1757
|
+
if ((edges & SOUTHEAST) === SOUTHEAST) {
|
|
1758
|
+
addWeightedColor(LRCoords[0] + 1, LRCoords[1] - 1);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
const outIdx = (oy * outW + ox) * 4;
|
|
1763
|
+
if (weightSum === 0) {
|
|
1764
|
+
out[outIdx] = 0;
|
|
1765
|
+
out[outIdx + 1] = 0;
|
|
1766
|
+
out[outIdx + 2] = 0;
|
|
1767
|
+
out[outIdx + 3] = 255;
|
|
1768
|
+
} else {
|
|
1769
|
+
out[outIdx] = clampInt(Math.round((colorSum[0] / weightSum) * 255), 0, 255);
|
|
1770
|
+
out[outIdx + 1] = clampInt(Math.round((colorSum[1] / weightSum) * 255), 0, 255);
|
|
1771
|
+
out[outIdx + 2] = clampInt(Math.round((colorSum[2] / weightSum) * 255), 0, 255);
|
|
1772
|
+
out[outIdx + 3] = clampInt(Math.round((colorSum[3] / weightSum) * 255), 0, 255);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
return out;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function runPipeline(src, outH, threshold) {
|
|
1781
|
+
const inW = src.width | 0;
|
|
1782
|
+
const inH = src.height | 0;
|
|
1783
|
+
const outHeight = outH | 0;
|
|
1784
|
+
const outWidth = Math.max(1, Math.round((inW / inH) * outHeight));
|
|
1785
|
+
|
|
1786
|
+
const similarityThreshold = (typeof threshold === "number") ? threshold : 255;
|
|
1787
|
+
|
|
1788
|
+
const sim0 = buildSimilarityGraph(src, similarityThreshold);
|
|
1789
|
+
const sim1 = valenceUpdate(sim0);
|
|
1790
|
+
const sim2 = eliminateCrossings(sim1);
|
|
1791
|
+
const sim3 = valenceUpdate(sim2);
|
|
1792
|
+
|
|
1793
|
+
const cell = computeCellGraph(src, sim3);
|
|
1794
|
+
const optimized = optimizeCellGraph(cell, inW, inH);
|
|
1795
|
+
const corrected = computeCorrectedPositions(cell, optimized);
|
|
1796
|
+
|
|
1797
|
+
const outData = gaussRasterize(src, sim3, cell, corrected, outWidth, outHeight);
|
|
1798
|
+
|
|
1799
|
+
return { data: outData, width: outWidth, height: outHeight };
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
function scaleImage(src, opts) {
|
|
1803
|
+
if (!src || !src.data || typeof src.width !== 'number' || typeof src.height !== 'number') {
|
|
1804
|
+
throw new Error('Invalid src image');
|
|
1805
|
+
}
|
|
1806
|
+
if (!opts || typeof opts.height !== 'number') {
|
|
1807
|
+
throw new Error('opts.height is required');
|
|
1808
|
+
}
|
|
1809
|
+
const inW = src.width | 0;
|
|
1810
|
+
const inH = src.height | 0;
|
|
1811
|
+
const outH = opts.height | 0;
|
|
1812
|
+
const threshold = opts.threshold;
|
|
1813
|
+
|
|
1814
|
+
const borderPx = Math.max(0, Math.round(opts.borderPx || 0));
|
|
1815
|
+
if (borderPx > 0) {
|
|
1816
|
+
const padW = inW + 2 * borderPx;
|
|
1817
|
+
const padH = inH + 2 * borderPx;
|
|
1818
|
+
const padData = new Uint8Array(padW * padH * 4);
|
|
1819
|
+
// fill magenta border
|
|
1820
|
+
for (let y = 0; y < padH; y++) {
|
|
1821
|
+
for (let x = 0; x < padW; x++) {
|
|
1822
|
+
const idx = (y * padW + x) * 4;
|
|
1823
|
+
padData[idx] = 255;
|
|
1824
|
+
padData[idx + 1] = 0;
|
|
1825
|
+
padData[idx + 2] = 255;
|
|
1826
|
+
padData[idx + 3] = 255;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
// copy src into center
|
|
1830
|
+
for (let y = 0; y < inH; y++) {
|
|
1831
|
+
const srcRow = y * inW * 4;
|
|
1832
|
+
const dstRow = (y + borderPx) * padW * 4 + borderPx * 4;
|
|
1833
|
+
padData.set(src.data.subarray(srcRow, srcRow + inW * 4), dstRow);
|
|
1834
|
+
}
|
|
1835
|
+
const scale = outH / inH;
|
|
1836
|
+
const outHpad = Math.max(1, Math.round(padH * scale));
|
|
1837
|
+
const padded = runPipeline({ data: padData, width: padW, height: padH }, outHpad, threshold);
|
|
1838
|
+
|
|
1839
|
+
const padOut = Math.max(0, Math.round(borderPx * scale));
|
|
1840
|
+
const outW = Math.max(1, Math.round((inW / inH) * outH));
|
|
1841
|
+
const cropped = new Uint8Array(outW * outH * 4);
|
|
1842
|
+
|
|
1843
|
+
for (let y = 0; y < outH; y++) {
|
|
1844
|
+
const srcY = y + padOut;
|
|
1845
|
+
if (srcY < 0 || srcY >= padded.height) {
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
const srcRow = (srcY * padded.width + padOut) * 4;
|
|
1849
|
+
const dstRow = y * outW * 4;
|
|
1850
|
+
const len = Math.min(outW, Math.max(0, padded.width - padOut)) * 4;
|
|
1851
|
+
cropped.set(padded.data.subarray(srcRow, srcRow + len), dstRow);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
return { data: Buffer.from(cropped), width: outW, height: outH };
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
const result = runPipeline(src, outH, threshold);
|
|
1858
|
+
return { data: Buffer.from(result.data), width: result.width, height: result.height };
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
exports.scaleImage = scaleImage;
|