You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, initialBucketSize = "50", soilStyle = "dark") {
// Create main container
const container = document.createElement('div');
container.style.fontFamily = '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.alignItems = 'center';
container.style.gap = '15px';
container.style.padding = '20px';
container.style.backgroundColor = '#2c3e50';
container.style.color = '#ecf0f1';
container.style.border = '5px solid #e67e22';
container.style.borderRadius = '12px';
container.style.maxWidth = '100%';
container.style.boxSizing = 'border-box';
// Title and Instructions
const title = document.createElement('h2');
title.innerText = '🚜 Image Excavator Tool';
title.style.margin = '0';
title.style.color = '#f1c40f';
container.appendChild(title);
const instructions = document.createElement('p');
instructions.innerText = 'Копательная машина: Click and drag over the image to excavate holes and reveal the ground underneath!';
instructions.style.margin = '0';
instructions.style.textAlign = 'center';
container.appendChild(instructions);
// Toolbar
const toolbar = document.createElement('div');
toolbar.style.display = 'flex';
toolbar.style.flexWrap = 'wrap';
toolbar.style.gap = '10px';
toolbar.style.alignItems = 'center';
toolbar.style.justifyContent = 'center';
container.appendChild(toolbar);
// Size Slider
const sizeLabel = document.createElement('label');
sizeLabel.innerText = 'Bucket Size:';
sizeLabel.style.fontWeight = 'bold';
const sizeInput = document.createElement('input');
sizeInput.type = 'range';
sizeInput.min = '10';
sizeInput.max = '200';
sizeInput.value = initialBucketSize;
toolbar.appendChild(sizeLabel);
toolbar.appendChild(sizeInput);
// Buttons styling helper
const styleButton = (btn) => {
btn.style.padding = '8px 12px';
btn.style.backgroundColor = '#e67e22';
btn.style.color = '#fff';
btn.style.border = 'none';
btn.style.borderRadius = '4px';
btn.style.cursor = 'pointer';
btn.style.fontWeight = 'bold';
btn.onmouseover = () => btn.style.backgroundColor = '#d35400';
btn.onmouseout = () => btn.style.backgroundColor = '#e67e22';
};
// Auto Dig Button
const autoDigBtn = document.createElement('button');
autoDigBtn.innerText = 'Auto Excavate';
styleButton(autoDigBtn);
toolbar.appendChild(autoDigBtn);
// Reset Button
const resetBtn = document.createElement('button');
resetBtn.innerText = 'Refill (Reset)';
styleButton(resetBtn);
toolbar.appendChild(resetBtn);
// Export Button
const exportBtn = document.createElement('button');
exportBtn.innerText = 'Export Image';
styleButton(exportBtn);
toolbar.appendChild(exportBtn);
// Canvas Container (simulates the underground soil)
const canvasContainer = document.createElement('div');
canvasContainer.style.position = 'relative';
canvasContainer.style.boxShadow = 'inset 0 0 20px rgba(0,0,0,0.8), 0 10px 20px rgba(0,0,0,0.5)';
canvasContainer.style.borderRadius = '4px';
canvasContainer.style.overflow = 'hidden';
// Procedural Dirt Background using CSS gradients
const bgColor = soilStyle === 'dark' ? '#271911' : '#5D4037';
const highlightColor = soilStyle === 'dark' ? '#3e2723' : '#795548';
canvasContainer.style.backgroundColor = bgColor;
canvasContainer.style.backgroundImage = `
radial-gradient(circle at 20% 30%, ${highlightColor} 2px, transparent 4px),
radial-gradient(circle at 70% 60%, ${highlightColor} 3px, transparent 5px),
radial-gradient(circle at 40% 80%, ${highlightColor} 2px, transparent 4px),
radial-gradient(circle at 80% 20%, ${highlightColor} 4px, transparent 6px)
`;
canvasContainer.style.backgroundSize = '50px 50px';
canvasContainer.style.cursor = 'crosshair';
// The Working Canvas
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
canvas.style.display = 'block';
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto'; // Maintain aspect ratio when scaling down
canvas.style.touchAction = 'none'; // Prevent scrolling while touching
canvasContainer.appendChild(canvas);
container.appendChild(canvasContainer);
const ctx = canvas.getContext('2d');
// Function to initialize/reset the image
const drawOriginal = () => {
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(originalImg, 0, 0);
};
drawOriginal();
let isDigging = false;
let autoDigInterval = null;
// The core "Dig" function (makes pixels transparent based on bucket shape)
const dig = (x, y) => {
const size = parseInt(sizeInput.value, 10);
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
// Main bucket bite (roughly square)
ctx.rect(x - size / 2, y - size / 2, size, size);
// Bucket teeth (jagged edges at the bottom)
const teeth = 5;
const toothWidth = size / teeth;
for (let i = 0; i < teeth; i++) {
ctx.rect((x - size / 2) + (i * toothWidth), y + size / 2, toothWidth * 0.7, size / 2.5);
}
ctx.fill();
// Scattered dirt/rubble around the bite
for (let i = 0; i < 15; i++) {
const rx = x + (Math.random() - 0.5) * size * 2.2;
const ry = y + (Math.random() - 0.5) * size * 2.2;
const rs = Math.random() * (size / 6);
ctx.beginPath();
ctx.arc(rx, ry, rs, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalCompositeOperation = 'source-over';
};
// Calculate scaling if canvas is constrained by CSS max-width
const getCoords = (e) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
let clientX = e.clientX;
let clientY = e.clientY;
if (e.touches && e.touches.length > 0) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
}
return {
x: (clientX - rect.left) * scaleX,
y: (clientY - rect.top) * scaleY
};
};
// Mouse & Touch Event Handlers
const onStart = (e) => {
if(e.cancelable) e.preventDefault();
clearInterval(autoDigInterval);
isDigging = true;
const { x, y } = getCoords(e);
dig(x, y);
};
const onMove = (e) => {
if (!isDigging) return;
if(e.cancelable) e.preventDefault();
const { x, y } = getCoords(e);
dig(x, y);
};
const onEnd = () => {
isDigging = false;
};
// Attach listeners
canvas.addEventListener('mousedown', onStart);
canvas.addEventListener('mousemove', onMove);
canvas.addEventListener('mouseup', onEnd);
canvas.addEventListener('mouseleave', onEnd);
canvas.addEventListener('touchstart', onStart, { passive: false });
canvas.addEventListener('touchmove', onMove, { passive: false });
canvas.addEventListener('touchend', onEnd);
canvas.addEventListener('touchcancel', onEnd);
// Button event listeners
resetBtn.addEventListener('click', () => {
clearInterval(autoDigInterval);
drawOriginal();
});
autoDigBtn.addEventListener('click', () => {
clearInterval(autoDigInterval); // Reset if already running
let x = canvas.width / 2;
let y = canvas.height / 2;
let step = 0;
const maxSteps = 40;
autoDigInterval = setInterval(() => {
dig(x, y);
const size = parseInt(sizeInput.value, 10);
// Random walk simulating a wandering excavator machine
x += (Math.random() - 0.5) * size * 1.5;
y += (Math.random() - 0.5) * size * 1.5;
// Keep machine in bounds
x = Math.max(size, Math.min(canvas.width - size, x));
y = Math.max(size, Math.min(canvas.height - size, y));
step++;
if (step >= maxSteps) {
clearInterval(autoDigInterval);
}
}, 60);
});
exportBtn.addEventListener('click', () => {
const link = document.createElement('a');
link.download = 'excavated-image.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
return container;
}
Apply Changes