depixel 1.0.3 → 1.0.4
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/lib.js +62 -44
- package/package.json +1 -1
package/lib.js
CHANGED
|
@@ -14,6 +14,18 @@ type Opts = {
|
|
|
14
14
|
function scaleImage(src: Image, opts: Opts): Image;
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
const {
|
|
18
|
+
abs,
|
|
19
|
+
ceil,
|
|
20
|
+
exp,
|
|
21
|
+
floor,
|
|
22
|
+
hypot,
|
|
23
|
+
max,
|
|
24
|
+
min,
|
|
25
|
+
round,
|
|
26
|
+
sign,
|
|
27
|
+
} = Math;
|
|
28
|
+
|
|
17
29
|
const EDGE_HORVERT = 16;
|
|
18
30
|
const EDGE_DIAGONAL_ULLR = 32;
|
|
19
31
|
const EDGE_DIAGONAL_LLUR = 64;
|
|
@@ -55,6 +67,7 @@ const BRACKET_SEARCH_B = -0.1;
|
|
|
55
67
|
const GOLD = 1.618034;
|
|
56
68
|
const GLIMIT = 10.0;
|
|
57
69
|
const TINY = 0.000000001;
|
|
70
|
+
const ONEo255 = 1/255;
|
|
58
71
|
|
|
59
72
|
function clampInt(v, lo, hi) {
|
|
60
73
|
if (v < lo) {
|
|
@@ -77,7 +90,7 @@ function fetchPixelRGBA(src, x, y) {
|
|
|
77
90
|
const cy = clampInt(y, 0, h - 1);
|
|
78
91
|
const idx = pixelIndex(cx, cy, w);
|
|
79
92
|
const d = src.data;
|
|
80
|
-
return [d[idx]
|
|
93
|
+
return [d[idx] * ONEo255, d[idx + 1] * ONEo255, d[idx + 2] * ONEo255, d[idx + 3] * ONEo255];
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
function fetchPixelRGBA8(src, x, y) {
|
|
@@ -90,6 +103,10 @@ function fetchPixelRGBA8(src, x, y) {
|
|
|
90
103
|
return [d[idx], d[idx + 1], d[idx + 2], d[idx + 3]];
|
|
91
104
|
}
|
|
92
105
|
|
|
106
|
+
const THRESHOLD_a = 32 / 255;
|
|
107
|
+
const THRESHOLD_y = 48 / 255;
|
|
108
|
+
const THRESHOLD_u = 7 / 255;
|
|
109
|
+
const THRESHOLD_v = 6 / 255;
|
|
93
110
|
function isSimilar(a, b, threshold) {
|
|
94
111
|
const yA = 0.299 * a[0] + 0.587 * a[1] + 0.114 * a[2];
|
|
95
112
|
const uA = 0.493 * (a[2] - yA);
|
|
@@ -97,16 +114,15 @@ function isSimilar(a, b, threshold) {
|
|
|
97
114
|
const yB = 0.299 * b[0] + 0.587 * b[1] + 0.114 * b[2];
|
|
98
115
|
const uB = 0.493 * (b[2] - yB);
|
|
99
116
|
const vB = 0.877 * (b[0] - yB);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (a[3] + b[3] <= (32.0/255) * t) {
|
|
117
|
+
if (abs(a[3] - b[3]) <= THRESHOLD_a * threshold) {
|
|
118
|
+
if (a[3] + b[3] <= THRESHOLD_a * threshold) {
|
|
103
119
|
// treat all alpha=0 pixels as similar, regardless of color
|
|
104
120
|
// note: need an option for this if processing images with masks or premultiplied alpha
|
|
105
121
|
return true;
|
|
106
122
|
}
|
|
107
|
-
if (
|
|
108
|
-
if (
|
|
109
|
-
if (
|
|
123
|
+
if (abs(yA - yB) <= THRESHOLD_y * threshold) {
|
|
124
|
+
if (abs(uA - uB) <= THRESHOLD_u * threshold) {
|
|
125
|
+
if (abs(vA - vB) <= THRESHOLD_v * threshold) {
|
|
110
126
|
return true;
|
|
111
127
|
}
|
|
112
128
|
}
|
|
@@ -115,6 +131,8 @@ function isSimilar(a, b, threshold) {
|
|
|
115
131
|
return false;
|
|
116
132
|
}
|
|
117
133
|
|
|
134
|
+
const THRESHOLD_CONTOUR = 100 / 255;
|
|
135
|
+
const THRESHOLD_CONTOUR_SQ = THRESHOLD_CONTOUR * THRESHOLD_CONTOUR;
|
|
118
136
|
function isContour(src, pL, pR) {
|
|
119
137
|
const a = fetchPixelRGBA(src, pL[0], pL[1]);
|
|
120
138
|
const b = fetchPixelRGBA(src, pR[0], pR[1]);
|
|
@@ -127,8 +145,8 @@ function isContour(src, pL, pR) {
|
|
|
127
145
|
const dy = yA - yB;
|
|
128
146
|
const du = uA - uB;
|
|
129
147
|
const dv = vA - vB;
|
|
130
|
-
const
|
|
131
|
-
return
|
|
148
|
+
const dist_sq = dy * dy + du * du + dv * dv;
|
|
149
|
+
return dist_sq > THRESHOLD_CONTOUR_SQ;
|
|
132
150
|
}
|
|
133
151
|
|
|
134
152
|
function buildSimilarityGraph(src, similarityThreshold) {
|
|
@@ -146,7 +164,7 @@ function buildSimilarityGraph(src, similarityThreshold) {
|
|
|
146
164
|
}
|
|
147
165
|
|
|
148
166
|
function getPixelCoords(gx, gy) {
|
|
149
|
-
return [
|
|
167
|
+
return [floor((gx - 1) / 2), floor((gy - 1) / 2)];
|
|
150
168
|
}
|
|
151
169
|
|
|
152
170
|
for (let y = 0; y < sgH; y++) {
|
|
@@ -723,8 +741,8 @@ function computeCellGraph(src, sim) {
|
|
|
723
741
|
}
|
|
724
742
|
|
|
725
743
|
function checkForCorner(s1, s2) {
|
|
726
|
-
const n1 =
|
|
727
|
-
const n2 =
|
|
744
|
+
const n1 = hypot(s1[0], s1[1]);
|
|
745
|
+
const n2 = hypot(s2[0], s2[1]);
|
|
728
746
|
if (n1 === 0 || n2 === 0) {
|
|
729
747
|
return false;
|
|
730
748
|
}
|
|
@@ -1057,8 +1075,8 @@ function optimizeCellGraph(cell, width, height) {
|
|
|
1057
1075
|
function calcPositionalEnergy(pNew, pOld) {
|
|
1058
1076
|
const dx = pNew[0] - pOld[0];
|
|
1059
1077
|
const dy = pNew[1] - pOld[1];
|
|
1060
|
-
const
|
|
1061
|
-
return
|
|
1078
|
+
const distSq = POSITIONAL_ENERGY_SCALING * POSITIONAL_ENERGY_SCALING * (dx * dx + dy * dy);
|
|
1079
|
+
return distSq * distSq;
|
|
1062
1080
|
}
|
|
1063
1081
|
|
|
1064
1082
|
function calcSegmentCurveEnergy(node1, node2, node3) {
|
|
@@ -1089,7 +1107,7 @@ function optimizeCellGraph(cell, width, height) {
|
|
|
1089
1107
|
const r = (bx - ax) * (fb - fc);
|
|
1090
1108
|
const q = (bx - cx) * (fb - fa);
|
|
1091
1109
|
const qr = q - r;
|
|
1092
|
-
let u = bx - ((bx - cx) * q - (bx - ax) * r) / (2.0 *
|
|
1110
|
+
let u = bx - ((bx - cx) * q - (bx - ax) * r) / (2.0 * sign(qr) * max(abs(qr), TINY));
|
|
1093
1111
|
const ulim = bx + GLIMIT * (cx - bx);
|
|
1094
1112
|
let fu;
|
|
1095
1113
|
if ((bx - u) * (u - cx) > 0.0) {
|
|
@@ -1131,7 +1149,7 @@ function optimizeCellGraph(cell, width, height) {
|
|
|
1131
1149
|
|
|
1132
1150
|
function searchOffset(pos, splineNeighbors) {
|
|
1133
1151
|
let gradient = calcGradient(splineNeighbors[0], pos, splineNeighbors[1]);
|
|
1134
|
-
const glen =
|
|
1152
|
+
const glen = hypot(gradient[0], gradient[1]);
|
|
1135
1153
|
if (glen <= 0.0) {
|
|
1136
1154
|
return [0, 0, 0];
|
|
1137
1155
|
}
|
|
@@ -1141,7 +1159,7 @@ function optimizeCellGraph(cell, width, height) {
|
|
|
1141
1159
|
let x1 = 0;
|
|
1142
1160
|
let x2 = 0;
|
|
1143
1161
|
let x3 = bracket[2];
|
|
1144
|
-
if (
|
|
1162
|
+
if (abs(bracket[2] - bracket[1]) > abs(bracket[1] - bracket[0])) {
|
|
1145
1163
|
x1 = bracket[1];
|
|
1146
1164
|
x2 = bracket[1] + C * (bracket[2] - bracket[1]);
|
|
1147
1165
|
} else {
|
|
@@ -1154,7 +1172,7 @@ function optimizeCellGraph(cell, width, height) {
|
|
|
1154
1172
|
let f2 = calcSegmentCurveEnergy(splineNeighbors[0], pOpt, splineNeighbors[1]) + calcPositionalEnergy(pOpt, pos);
|
|
1155
1173
|
let counter = 0;
|
|
1156
1174
|
let fx;
|
|
1157
|
-
while (
|
|
1175
|
+
while (abs(x3 - x0) > TOL * (abs(x1) + abs(x2)) && (counter < LIMIT_SEARCH_ITERATIONS)) {
|
|
1158
1176
|
counter++;
|
|
1159
1177
|
if (f2 < f1) {
|
|
1160
1178
|
x0 = x1; x1 = x2; x2 = R * x1 + C * x3;
|
|
@@ -1441,29 +1459,29 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
|
|
|
1441
1459
|
(w * (ox + 0.5) / outW) - 0.5 + 0.00001,
|
|
1442
1460
|
(h * (oy + 0.5) / outH) - 0.5 + 0.00001,
|
|
1443
1461
|
];
|
|
1444
|
-
const fragmentBaseKnotIndex = (2 *
|
|
1462
|
+
const fragmentBaseKnotIndex = (2 * floor(cellSpaceCoords[0]) + floor(cellSpaceCoords[1]) * 2 * (w - 1)) | 0;
|
|
1445
1463
|
const node0flags = cell.flags[fragmentBaseKnotIndex] | 0;
|
|
1446
1464
|
let hasCorrectedPosition = false;
|
|
1447
1465
|
|
|
1448
|
-
const ULCoords = [
|
|
1449
|
-
const URCoords = [
|
|
1450
|
-
const LLCoords = [
|
|
1451
|
-
const LRCoords = [
|
|
1466
|
+
const ULCoords = [floor(cellSpaceCoords[0]), ceil(cellSpaceCoords[1])];
|
|
1467
|
+
const URCoords = [ceil(cellSpaceCoords[0]), ceil(cellSpaceCoords[1])];
|
|
1468
|
+
const LLCoords = [floor(cellSpaceCoords[0]), floor(cellSpaceCoords[1])];
|
|
1469
|
+
const LRCoords = [ceil(cellSpaceCoords[0]), floor(cellSpaceCoords[1])];
|
|
1452
1470
|
|
|
1453
1471
|
function findSegmentIntersections(p0, p1, p2) {
|
|
1454
1472
|
let pointA = calcSplinePoint(p0, p1, p2, 0.0);
|
|
1455
1473
|
for (let t = STEP; t < (1.0 + STEP); t += STEP) {
|
|
1456
1474
|
const pointB = calcSplinePoint(p0, p1, p2, t);
|
|
1457
|
-
if (intersects(cellSpaceCoords, ULCoords, pointA, pointB)) {
|
|
1475
|
+
if (influencingPixels[0] && intersects(cellSpaceCoords, ULCoords, pointA, pointB)) {
|
|
1458
1476
|
influencingPixels[0] = false;
|
|
1459
1477
|
}
|
|
1460
|
-
if (intersects(cellSpaceCoords, URCoords, pointA, pointB)) {
|
|
1478
|
+
if (influencingPixels[1] && intersects(cellSpaceCoords, URCoords, pointA, pointB)) {
|
|
1461
1479
|
influencingPixels[1] = false;
|
|
1462
1480
|
}
|
|
1463
|
-
if (intersects(cellSpaceCoords, LLCoords, pointA, pointB)) {
|
|
1481
|
+
if (influencingPixels[2] && intersects(cellSpaceCoords, LLCoords, pointA, pointB)) {
|
|
1464
1482
|
influencingPixels[2] = false;
|
|
1465
1483
|
}
|
|
1466
|
-
if (intersects(cellSpaceCoords, LRCoords, pointA, pointB)) {
|
|
1484
|
+
if (influencingPixels[3] && intersects(cellSpaceCoords, LRCoords, pointA, pointB)) {
|
|
1467
1485
|
influencingPixels[3] = false;
|
|
1468
1486
|
}
|
|
1469
1487
|
pointA = pointB;
|
|
@@ -1693,11 +1711,11 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
|
|
|
1693
1711
|
let weightSum = 0.0;
|
|
1694
1712
|
|
|
1695
1713
|
function addWeightedColor(px, py) {
|
|
1696
|
-
const col =
|
|
1714
|
+
const col = fetchPixelRGBA8(src, px, py);
|
|
1697
1715
|
const dx = cellSpaceCoords[0] - px;
|
|
1698
1716
|
const dy = cellSpaceCoords[1] - py;
|
|
1699
|
-
const
|
|
1700
|
-
const weight =
|
|
1717
|
+
const distSq = dx * dx + dy * dy;
|
|
1718
|
+
const weight = exp(-distSq * GAUSS_MULTIPLIER);
|
|
1701
1719
|
colorSum[0] += col[0] * weight;
|
|
1702
1720
|
colorSum[1] += col[1] * weight;
|
|
1703
1721
|
colorSum[2] += col[2] * weight;
|
|
@@ -1778,18 +1796,18 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
|
|
|
1778
1796
|
|
|
1779
1797
|
const outIdx = (oy * outW + ox) * 4;
|
|
1780
1798
|
if (weightSum === 0) {
|
|
1781
|
-
const nx =
|
|
1782
|
-
const ny =
|
|
1799
|
+
const nx = round(cellSpaceCoords[0]);
|
|
1800
|
+
const ny = round(cellSpaceCoords[1]);
|
|
1783
1801
|
const col = fetchPixelRGBA8(src, nx, ny);
|
|
1784
1802
|
out[outIdx] = col[0];
|
|
1785
1803
|
out[outIdx + 1] = col[1];
|
|
1786
1804
|
out[outIdx + 2] = col[2];
|
|
1787
1805
|
out[outIdx + 3] = col[3];
|
|
1788
1806
|
} else {
|
|
1789
|
-
out[outIdx] = clampInt(
|
|
1790
|
-
out[outIdx + 1] = clampInt(
|
|
1791
|
-
out[outIdx + 2] = clampInt(
|
|
1792
|
-
out[outIdx + 3] = clampInt(
|
|
1807
|
+
out[outIdx] = clampInt(round((colorSum[0] / weightSum)), 0, 255);
|
|
1808
|
+
out[outIdx + 1] = clampInt(round((colorSum[1] / weightSum)), 0, 255);
|
|
1809
|
+
out[outIdx + 2] = clampInt(round((colorSum[2] / weightSum)), 0, 255);
|
|
1810
|
+
out[outIdx + 3] = clampInt(round((colorSum[3] / weightSum)), 0, 255);
|
|
1793
1811
|
}
|
|
1794
1812
|
}
|
|
1795
1813
|
}
|
|
@@ -1801,9 +1819,9 @@ function runPipeline(src, outH, threshold) {
|
|
|
1801
1819
|
const inW = src.width | 0;
|
|
1802
1820
|
const inH = src.height | 0;
|
|
1803
1821
|
const outHeight = outH | 0;
|
|
1804
|
-
const outWidth =
|
|
1822
|
+
const outWidth = max(1, round((inW / inH) * outHeight));
|
|
1805
1823
|
|
|
1806
|
-
const similarityThreshold = (typeof threshold === "number") ? threshold : 255;
|
|
1824
|
+
const similarityThreshold = ((typeof threshold === "number") ? threshold : 255) / 255;
|
|
1807
1825
|
|
|
1808
1826
|
const sim0 = buildSimilarityGraph(src, similarityThreshold);
|
|
1809
1827
|
const sim1 = valenceUpdate(sim0);
|
|
@@ -1831,14 +1849,14 @@ function scaleImage(src, opts) {
|
|
|
1831
1849
|
const outH = opts.height | 0;
|
|
1832
1850
|
const threshold = opts.threshold;
|
|
1833
1851
|
|
|
1834
|
-
const borderPx =
|
|
1852
|
+
const borderPx = max(0, round(opts.borderPx || 0));
|
|
1835
1853
|
if (borderPx > 0) {
|
|
1836
1854
|
const padW = inW + 2 * borderPx;
|
|
1837
1855
|
const padH = inH + 2 * borderPx;
|
|
1838
1856
|
const padData = new Uint8Array(padW * padH * 4);
|
|
1839
1857
|
// copy src into center, expand into borders
|
|
1840
1858
|
for (let y = 0; y < padH; y++) {
|
|
1841
|
-
const srcRow =
|
|
1859
|
+
const srcRow = min(inH - 1, max(0, y - borderPx)) * inW * 4;
|
|
1842
1860
|
const dstRow = y * padW * 4 + borderPx * 4;
|
|
1843
1861
|
padData.set(src.data.subarray(srcRow, srcRow + inW * 4), dstRow);
|
|
1844
1862
|
for (let ii = 0; ii < borderPx * 4; ++ii) {
|
|
@@ -1847,11 +1865,11 @@ function scaleImage(src, opts) {
|
|
|
1847
1865
|
}
|
|
1848
1866
|
}
|
|
1849
1867
|
const scale = outH / inH;
|
|
1850
|
-
const outHpad =
|
|
1868
|
+
const outHpad = max(1, round(padH * scale));
|
|
1851
1869
|
const padded = runPipeline({ data: padData, width: padW, height: padH }, outHpad, threshold);
|
|
1852
1870
|
|
|
1853
|
-
const padOut =
|
|
1854
|
-
const outW =
|
|
1871
|
+
const padOut = max(0, round(borderPx * scale));
|
|
1872
|
+
const outW = max(1, round((inW / inH) * outH));
|
|
1855
1873
|
const cropped = new Uint8Array(outW * outH * 4);
|
|
1856
1874
|
|
|
1857
1875
|
for (let y = 0; y < outH; y++) {
|
|
@@ -1861,7 +1879,7 @@ function scaleImage(src, opts) {
|
|
|
1861
1879
|
}
|
|
1862
1880
|
const srcRow = (srcY * padded.width + padOut) * 4;
|
|
1863
1881
|
const dstRow = y * outW * 4;
|
|
1864
|
-
const len =
|
|
1882
|
+
const len = min(outW, max(0, padded.width - padOut)) * 4;
|
|
1865
1883
|
cropped.set(padded.data.subarray(srcRow, srcRow + len), dstRow);
|
|
1866
1884
|
}
|
|
1867
1885
|
|