depixel 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/lib.js +31 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,9 +7,10 @@ Based on the following paper:
|
|
|
7
7
|
And the [source code](https://github.com/falichs/Depixelizing-Pixel-Art-on-GPUs) included with this paper:
|
|
8
8
|
* [Depixelizing Pixel Art on GPUs](https://www.cg.tuwien.ac.at/research/publications/2014/KREUZER-2014-DPA/) by Felix Kreuzer
|
|
9
9
|
|
|
10
|
-
Improvements upon original
|
|
10
|
+
Improvements upon original GPU version:
|
|
11
11
|
* Add a threshold parameter to adjust how close two colors need to be to be consider similar
|
|
12
12
|
* Better handle alpha channels (treat as dissimilar from non-transparent pixels)
|
|
13
|
+
* Fix stretching/scaling due to texel misalignment
|
|
13
14
|
|
|
14
15
|
Notes
|
|
15
16
|
* Original GPU code is MIT licensed, the same license may apply here, all original code in this project is additionally released under the MIT License
|
package/lib.js
CHANGED
|
@@ -80,6 +80,16 @@ function fetchPixelRGBA(src, x, y) {
|
|
|
80
80
|
return [d[idx] / 255, d[idx + 1] / 255, d[idx + 2] / 255, d[idx + 3] / 255];
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function fetchPixelRGBA8(src, x, y) {
|
|
84
|
+
const w = src.width;
|
|
85
|
+
const h = src.height;
|
|
86
|
+
const cx = clampInt(x, 0, w - 1);
|
|
87
|
+
const cy = clampInt(y, 0, h - 1);
|
|
88
|
+
const idx = pixelIndex(cx, cy, w);
|
|
89
|
+
const d = src.data;
|
|
90
|
+
return [d[idx], d[idx + 1], d[idx + 2], d[idx + 3]];
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
function isSimilar(a, b, threshold) {
|
|
84
94
|
const yA = 0.299 * a[0] + 0.587 * a[1] + 0.114 * a[2];
|
|
85
95
|
const uA = 0.493 * (a[2] - yA);
|
|
@@ -89,6 +99,11 @@ function isSimilar(a, b, threshold) {
|
|
|
89
99
|
const vB = 0.877 * (b[0] - yB);
|
|
90
100
|
const t = Math.max(0, Math.min(255, threshold | 0)) / 255.0;
|
|
91
101
|
if (Math.abs(a[3] - b[3]) <= (32.0/255) * t) {
|
|
102
|
+
if (a[3] + b[3] <= (32.0/255) * t) {
|
|
103
|
+
// treat all alpha=0 pixels as similar, regardless of color
|
|
104
|
+
// note: need an option for this if processing images with masks or premultiplied alpha
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
92
107
|
if (Math.abs(yA - yB) <= (48.0 / 255.0) * t) {
|
|
93
108
|
if (Math.abs(uA - uB) <= (7.0 / 255.0) * t) {
|
|
94
109
|
if (Math.abs(vA - vB) <= (6.0 / 255.0) * t) {
|
|
@@ -1423,8 +1438,8 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
|
|
|
1423
1438
|
for (let ox = 0; ox < outW; ox++) {
|
|
1424
1439
|
let influencingPixels = [true, true, true, true];
|
|
1425
1440
|
const cellSpaceCoords = [
|
|
1426
|
-
(w
|
|
1427
|
-
(h
|
|
1441
|
+
(w * (ox + 0.5) / outW) - 0.5 + 0.00001,
|
|
1442
|
+
(h * (oy + 0.5) / outH) - 0.5 + 0.00001,
|
|
1428
1443
|
];
|
|
1429
1444
|
const fragmentBaseKnotIndex = (2 * Math.floor(cellSpaceCoords[0]) + Math.floor(cellSpaceCoords[1]) * 2 * (w - 1)) | 0;
|
|
1430
1445
|
const node0flags = cell.flags[fragmentBaseKnotIndex] | 0;
|
|
@@ -1763,10 +1778,13 @@ function gaussRasterize(src, sim, cell, positions, outW, outH) {
|
|
|
1763
1778
|
|
|
1764
1779
|
const outIdx = (oy * outW + ox) * 4;
|
|
1765
1780
|
if (weightSum === 0) {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
out[outIdx
|
|
1781
|
+
const nx = Math.round(cellSpaceCoords[0]);
|
|
1782
|
+
const ny = Math.round(cellSpaceCoords[1]);
|
|
1783
|
+
const col = fetchPixelRGBA8(src, nx, ny);
|
|
1784
|
+
out[outIdx] = col[0];
|
|
1785
|
+
out[outIdx + 1] = col[1];
|
|
1786
|
+
out[outIdx + 2] = col[2];
|
|
1787
|
+
out[outIdx + 3] = col[3];
|
|
1770
1788
|
} else {
|
|
1771
1789
|
out[outIdx] = clampInt(Math.round((colorSum[0] / weightSum) * 255), 0, 255);
|
|
1772
1790
|
out[outIdx + 1] = clampInt(Math.round((colorSum[1] / weightSum) * 255), 0, 255);
|
|
@@ -1815,30 +1833,18 @@ function scaleImage(src, opts) {
|
|
|
1815
1833
|
|
|
1816
1834
|
const borderPx = Math.max(0, Math.round(opts.borderPx || 0));
|
|
1817
1835
|
if (borderPx > 0) {
|
|
1818
|
-
const doInvisBorder = Math.min(
|
|
1819
|
-
src.data[3],
|
|
1820
|
-
src.data[(inW - 1) * 4 + 3],
|
|
1821
|
-
src.data[(inH - 1) * inW * 4 + 3],
|
|
1822
|
-
src.data[((inH - 1) * inW + (inW - 1)) * 4 + 3],
|
|
1823
|
-
) < 255;
|
|
1824
1836
|
const padW = inW + 2 * borderPx;
|
|
1825
1837
|
const padH = inH + 2 * borderPx;
|
|
1826
1838
|
const padData = new Uint8Array(padW * padH * 4);
|
|
1827
|
-
//
|
|
1839
|
+
// copy src into center, expand into borders
|
|
1828
1840
|
for (let y = 0; y < padH; y++) {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
padData[idx] = doInvisBorder ? 0 : 255;
|
|
1832
|
-
padData[idx + 1] = 0;
|
|
1833
|
-
padData[idx + 2] = doInvisBorder ? 0 : 255;
|
|
1834
|
-
padData[idx + 3] = doInvisBorder ? 0 : 255;
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
// copy src into center
|
|
1838
|
-
for (let y = 0; y < inH; y++) {
|
|
1839
|
-
const srcRow = y * inW * 4;
|
|
1840
|
-
const dstRow = (y + borderPx) * padW * 4 + borderPx * 4;
|
|
1841
|
+
const srcRow = Math.min(inH - 1, Math.max(0, y - borderPx)) * inW * 4;
|
|
1842
|
+
const dstRow = y * padW * 4 + borderPx * 4;
|
|
1841
1843
|
padData.set(src.data.subarray(srcRow, srcRow + inW * 4), dstRow);
|
|
1844
|
+
for (let ii = 0; ii < borderPx * 4; ++ii) {
|
|
1845
|
+
padData[dstRow - borderPx * 4 + ii] = src.data[srcRow + ii % 4];
|
|
1846
|
+
padData[dstRow + inW * 4 + ii] = src.data[srcRow + ii % 4];
|
|
1847
|
+
}
|
|
1842
1848
|
}
|
|
1843
1849
|
const scale = outH / inH;
|
|
1844
1850
|
const outHpad = Math.max(1, Math.round(padH * scale));
|