You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, removeX = 0, removeY = 0, removeWidth = 0, removeHeight = 0, patchSize = 11, searchBorder = 20) {
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
// 1. --- Input Validation and Setup ---
if (removeWidth <= 0 || removeHeight <= 0 || width === 0 || height === 0) {
const canvas = document.createElement('canvas');
canvas.width = width || 1;
canvas.height = height || 1;
const ctx = canvas.getContext('2d');
if (width > 0 && height > 0) {
ctx.drawImage(originalImg, 0, 0);
}
return canvas;
}
if (patchSize % 2 === 0) {
patchSize++; // Ensure patch size is odd for a clear center pixel.
}
const patchRadius = Math.floor(patchSize / 2);
// 2. --- Canvas and Image Data Setup ---
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(originalImg, 0, 0);
const finalImageData = ctx.getImageData(0, 0, width, height);
const data = finalImageData.data;
const originalHoleMask = new Uint8Array(width * height).fill(0);
// 3. --- Define Hole, Source Region, and Pixels to Fill ---
const pixelsToFill = [];
const rX = Math.max(0, removeX);
const rY = Math.max(0, removeY);
const rW = Math.min(width - rX, removeWidth);
const rH = Math.min(height - rY, removeHeight);
for (let y = rY; y < rY + rH; y++) {
for (let x = rX; x < rX + rW; x++) {
const index = y * width + x;
originalHoleMask[index] = 1;
pixelsToFill.push({ x, y });
}
}
if (pixelsToFill.length === 0) {
return canvas;
}
// Pre-calculate all valid source patch center locations.
const sourcePatchLocations = [];
const searchXMin = Math.max(patchRadius, rX - searchBorder);
const searchYMin = Math.max(patchRadius, rY - searchBorder);
const searchXMax = Math.min(width - patchRadius - 1, rX + rW + searchBorder);
const searchYMax = Math.min(height - patchRadius - 1, rY + rH + searchBorder);
for (let y = searchYMin; y <= searchYMax; y++) {
for (let x = searchXMin; x <= searchXMax; x++) {
let isPatchValid = true;
checkPatch:
for (let dy = -patchRadius; dy <= patchRadius; dy++) {
for (let dx = -patchRadius; dx <= patchRadius; dx++) {
if (originalHoleMask[(y + dy) * width + (x + dx)]) {
isPatchValid = false;
break checkPatch;
}
}
}
if (isPatchValid) {
sourcePatchLocations.push({ x, y });
}
}
}
if (sourcePatchLocations.length === 0) {
console.error("No source region found to sample from. Try increasing searchBorder or reducing patchSize.");
return canvas;
}
// 4. --- Patch Matching and Filling ---
const findBestMatch = (tx, ty, currentHoleMask) => {
let bestMatch = { x: -1, y: -1, ssd: Infinity };
// Randomly sample source patches for performance.
const maxSamples = Math.min(sourcePatchLocations.length, 500);
for (let i = 0; i < maxSamples; i++) {
const source = sourcePatchLocations[Math.floor(Math.random() * sourcePatchLocations.length)];
let { x: sx, y: sy } = source;
let currentSsd = 0;
let pixelsCompared = 0;
for (let dy = -patchRadius; dy <= patchRadius; dy++) {
for (let dx = -patchRadius; dx <= patchRadius; dx++) {
const currentTx = tx + dx;
const currentTy = ty + dy;
if (currentTx < 0 || currentTx >= width || currentTy < 0 || currentTy >= height) continue;
if (currentHoleMask[currentTy * width + currentTx] === 0) { // If target pixel is known
const targetIdx = (currentTy * width + currentTx) * 4;
const sourceIdx = ((sy + dy) * width + (sx + dx)) * 4;
const dR = data[targetIdx] - data[sourceIdx];
const dG = data[targetIdx + 1] - data[sourceIdx + 1];
const dB = data[targetIdx + 2] - data[sourceIdx + 2];
currentSsd += dR * dR + dG * dG + dB * dB;
pixelsCompared++;
}
}
}
if (pixelsCompared > 0) {
const avgSsd = currentSsd / pixelsCompared;
if (avgSsd < bestMatch.ssd) {
bestMatch = { x: sx, y: sy, ssd: avgSsd };
}
}
}
return bestMatch;
};
const fillPass = (pixels, currentHoleMask) => {
for (const p of pixels) {
const bestMatch = findBestMatch(p.x, p.y, currentHoleMask);
if (bestMatch.x !== -1) {
const sourceColorIdx = (bestMatch.y * width + bestMatch.x) * 4;
const targetIdx = (p.y * width + p.x) * 4;
data[targetIdx] = data[sourceColorIdx];
data[targetIdx + 1] = data[sourceColorIdx + 1];
data[targetIdx + 2] = data[sourceColorIdx + 2];
// Mark this pixel as filled for subsequent pixels in this same pass
currentHoleMask[p.y * width + p.x] = 0;
}
}
};
// A simple raster scan (top-left to bottom-right) propagates information
// from the top and left of the hole.
let pass1HoleMask = new Uint8Array(originalHoleMask);
fillPass([...pixelsToFill], pass1HoleMask);
// 5. --- Finalize and Return Canvas ---
ctx.putImageData(finalImageData, 0, 0);
return canvas;
}
Apply Changes