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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/lib.js +31 -25
  3. 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 - 1) * (ox / (outW - 1)),
1427
- (h - 1) * (oy / (outH - 1)),
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
- out[outIdx] = 0;
1767
- out[outIdx + 1] = 0;
1768
- out[outIdx + 2] = 0;
1769
- out[outIdx + 3] = 255;
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
- // fill magenta border (invisible if original image appears to have invisible edges)
1839
+ // copy src into center, expand into borders
1828
1840
  for (let y = 0; y < padH; y++) {
1829
- for (let x = 0; x < padW; x++) {
1830
- const idx = (y * padW + x) * 4;
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depixel",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Depixelizing Pixel Art by Kopf-Lischinski and Felix Kreuzer for Node.js",
5
5
  "main": "lib.js",
6
6
  "keywords": [