quadtreeInpaint = function (pixelData, maskData) {
let [w,h] = [pixelData.width, pixelData.height];
console.assert(w == maskData.width && h == maskData.height);
let k = Math.ceil(Math.log2(Math.max(w,h)));
let n = 1<<k;
let base = new ImageData(n,n);
let src = new Uint32Array (pixelData.data.buffer);
let dst = new Uint32Array (base.data.buffer);
let mask = new Uint32Array (maskData.data.buffer);
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
let i = y*w+x;
let j = y*n+x;
if (!mask[i]) dst[j] = src[i];
}
}
let halve = (original,n) => {
let m = n/2;
let half = new ImageData(m,m);
let src = new Uint32Array (original.data.buffer);
let dst = new Uint32Array (half.data.buffer);
let rgbaWord = new Uint32Array(1);
let rgba = new Uint8Array(rgbaWord.buffer);
let average = (words) => {
let [R,G,B,A] = [0,0,0,0];
let sum = 0;
for (let w of words) {
rgbaWord [0] = w;
if (rgba[3]) {
sum++;
R += rgba[0];
G += rgba[1];
B += rgba[2];
//break;
}
}
if (sum>0) {
rgba.set([Math.round(R/sum),Math.round(G/sum),Math.round(B/sum),255])
}
else {
rgbaWord [0] = 0
}
return rgbaWord [0];
}
for (let x = 0; x < m; x++) {
for (let y = 0; y < m; y++) {
let words = [src[2*y*n+2*x],src[2*y*n+2*x+1],
src[2*y*n+n+2*x],src[2*y*n+n+2*x+1]]
if (x > 0 && y > 0) {
words.push (src[2*y*n+2*x-1],src[2*y*n+n+2*x-1],
src[2*y*n-n+2*x],src[2*y*n-n+2*x+1],src[2*y*n-n+2*x-1])
}
dst[y*m+x] = average(words)
}
}
return half
}
// Replace original pixels with pixels of halved image if masked
let double = (original, half, n) => {
let m = n/2;
let src = new Uint32Array (original.data.buffer);
let dst = new Uint32Array (half.data.buffer);
let rgbaWord = new Uint32Array(1);
let rgba = new Uint8Array(rgbaWord.buffer);
let weightedAverage = (words_weights) => {
let [R,G,B,A] = [0,0,0,0];
let sum = 0;
for (let [w,p] of words_weights) {
rgbaWord [0] = w;
if (rgba[3]) {
sum+=p;
R += rgba[0]*p;
G += rgba[1]*p;
B += rgba[2]*p;
//break;
}
}
if (sum>0) {
rgba.set([Math.round(R/sum),Math.round(G/sum),Math.round(B/sum),255])
}
else {
rgbaWord [0] = 0
}
return rgbaWord [0];
}
for (let x = 0; x < m; x++) {
for (let y = 0; y < m; y++) {
let fallback = dst[y*m+x];
if (x+1 < m && y+1 < m) {
fallback = weightedAverage ([[fallback,9], [dst[y*m+x+1],4], [dst[y*m+m+x],4], [dst[y*m+m+x+1],1]])
}
for (let i of [2*y*n+2*x,2*y*n+2*x+1,2*y*n+n+2*x,2*y*n+n+2*x+1]) {
rgbaWord[0] = src[i];
if (rgba[3] == 0) src[i] = fallback;
}
}
}
}
// The algorithm, proper
let pyramid = [base];
let N = n;
for (let i = 0; i < k; i++) {
let top = pyramid[i];
pyramid[i+1] = halve(top,N);
N = N/2;
}
for (let i = k; i > 0; i--) {
let bottom = pyramid[i];
N = N*2;
double (pyramid[i-1],pyramid[i],N)
}
// Build return image
let result = new ImageData(w,h);
let res = new Uint32Array (result.data.buffer);
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
let i = y*w+x;
let j = y*n+x;
res[i] = dst[j]
}
}
return result
}