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.
Files changed (3) hide show
  1. package/README.md +61 -0
  2. package/lib.js +1861 -0
  3. 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;