anycoder-ebfd1a6c / index.html
iammrrobot420's picture
Upload folder using huggingface_hub
28d7bd7 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart Inpaint Studio - Privacy & Object Removal</title>
<!-- Importing Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Importing FontAwesome for Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #4F46E5;
--primary-hover: #4338ca;
--bg-dark: #0f172a;
--bg-card: #1e293b;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--border: #334155;
--success: #10b981;
--danger: #ef4444;
--radius: 12px;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
outline: none;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-dark);
color: var(--text-main);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
header {
background-color: rgba(30, 41, 59, 0.8);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
}
.logo {
font-weight: 700;
font-size: 1.25rem;
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text-main);
}
.logo i {
color: var(--primary);
}
.anycoder-link {
font-size: 0.875rem;
color: var(--text-muted);
text-decoration: none;
transition: color 0.2s;
}
.anycoder-link:hover {
color: var(--primary);
}
/* Main Layout */
main {
flex: 1;
display: grid;
grid-template-columns: 320px 1fr;
gap: 2rem;
padding: 2rem;
max-width: 1600px;
margin: 0 auto;
width: 100%;
}
@media (max-width: 900px) {
main {
grid-template-columns: 1fr;
}
}
/* Sidebar Controls */
aside {
background-color: var(--bg-card);
border-radius: var(--radius);
padding: 1.5rem;
border: 1px solid var(--border);
height: fit-content;
display: flex;
flex-direction: column;
gap: 1.5rem;
box-shadow: var(--shadow);
}
h2 {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
label {
font-size: 0.875rem;
color: var(--text-muted);
font-weight: 500;
}
/* Custom File Upload */
.file-upload {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
border: 2px dashed var(--border);
border-radius: var(--radius);
cursor: pointer;
transition: all 0.2s;
background-color: rgba(255,255,255,0.02);
text-align: center;
}
.file-upload:hover {
border-color: var(--primary);
background-color: rgba(79, 70, 229, 0.05);
}
.file-upload input {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.file-upload i {
font-size: 2rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
}
.file-upload p {
font-size: 0.875rem;
color: var(--text-muted);
}
/* Brush Size Slider */
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
background: var(--border);
border-radius: 3px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
transition: transform 0.1s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
.value-display {
font-size: 0.875rem;
color: var(--text-main);
float: right;
}
/* Mode Selection */
.mode-options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.mode-btn {
background-color: rgba(255,255,255,0.05);
border: 1px solid var(--border);
color: var(--text-muted);
padding: 0.75rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}
.mode-btn:hover {
background-color: rgba(255,255,255,0.1);
}
.mode-btn.active {
background-color: var(--primary);
border-color: var(--primary);
color: white;
}
/* Action Buttons */
.btn {
padding: 0.875rem;
border-radius: 8px;
border: none;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 0.95rem;
}
.btn-primary {
background-color: var(--primary);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
}
.btn-secondary {
background-color: transparent;
border: 1px solid var(--border);
color: var(--text-main);
}
.btn-secondary:hover {
background-color: rgba(255,255,255,0.05);
border-color: var(--text-muted);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Canvas Area */
.canvas-container {
background-color: #000;
border-radius: var(--radius);
border: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
min-height: 500px;
box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
}
/* We stack canvases: Bottom = Image, Top = Drawing/Mask */
canvas {
max-width: 100%;
max-height: 80vh;
cursor: crosshair;
position: absolute;
}
#imageCanvas {
z-index: 1;
}
#maskCanvas {
z-index: 2;
}
.empty-state {
text-align: center;
color: var(--text-muted);
z-index: 0;
}
.empty-state i {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
/* Toast Notification */
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background-color: var(--bg-card);
border-left: 4px solid var(--success);
padding: 1rem 1.5rem;
border-radius: 8px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
transform: translateY(150%);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 1000;
display: flex;
align-items: center;
gap: 0.75rem;
}
.toast.show {
transform: translateY(0);
}
.toast i {
color: var(--success);
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(15, 23, 42, 0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.loading-overlay.active {
opacity: 1;
pointer-events: all;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255,255,255,0.1);
border-left-color: var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<header>
<div class="logo">
<i class="fa-solid fa-wand-magic-sparkles"></i>
<span>Smart Inpaint Studio</span>
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
Built with anycoder <i class="fa-solid fa-arrow-up-right-from-square" style="font-size: 0.7em;"></i>
</a>
</header>
<main>
<!-- Sidebar Controls -->
<aside>
<div class="control-group">
<h2><i class="fa-solid fa-upload"></i> Source Image</h2>
<label class="file-upload">
<input type="file" id="imageUpload" accept="image/*">
<i class="fa-solid fa-image"></i>
<p>Click to upload or drag & drop</p>
</label>
</div>
<div class="control-group">
<h2><i class="fa-solid fa-paintbrush"></i> Brush Settings</h2>
<label>Brush Size <span id="brushSizeVal" class="value-display">30px</span></label>
<input type="range" id="brushSize" min="5" max="100" value="30">
</div>
<div class="control-group">
<h2><i class="fa-solid fa-layer-group"></i> Fill Mode</h2>
<div class="mode-options">
<div class="mode-btn active" data-mode="blur">
<i class="fa-solid fa-droplet"></i>
<span>Smart Blur</span>
</div>
<div class="mode-btn" data-mode="pixelate">
<i class="fa-solid fa-border-all"></i>
<span>Pixelate</span>
</div>
<div class="mode-btn" data-mode="remove">
<i class="fa-solid fa-eraser"></i>
<span>Object Fill</span>
</div>
<div class="mode-btn" data-mode="color">
<i class="fa-solid fa-fill-drip"></i>
<span>Solid Color</span>
</div>
</div>
</div>
<div class="control-group" style="margin-top: auto;">
<button id="processBtn" class="btn btn-primary" disabled>
<i class="fa-solid fa-bolt"></i> Apply Inpainting
</button>
<button id="resetBtn" class="btn btn-secondary" disabled>
<i class="fa-solid fa-rotate-left"></i> Reset Mask
</button>
<button id="downloadBtn" class="btn btn-secondary" disabled>
<i class="fa-solid fa-download"></i> Download Result
</button>
</div>
</aside>
<!-- Canvas Area -->
<div class="canvas-container" id="canvasWrapper">
<div class="empty-state" id="emptyState">
<i class="fa-regular fa-image"></i>
<p>Upload an image to start editing</p>
</div>
<canvas id="imageCanvas"></canvas>
<canvas id="maskCanvas"></canvas>
<div class="loading-overlay" id="loader">
<div class="spinner"></div>
<p>Processing pixels...</p>
</div>
</div>
</main>
<div class="toast" id="toast">
<i class="fa-solid fa-circle-check"></i>
<span id="toastMsg">Action completed successfully</span>
</div>
<script>
/**
* Smart Inpaint Studio
* Handles image upload, canvas masking, and client-side pixel manipulation.
*/
const imageUpload = document.getElementById('imageUpload');
const brushSizeInput = document.getElementById('brushSize');
const brushSizeVal = document.getElementById('brushSizeVal');
const processBtn = document.getElementById('processBtn');
const resetBtn = document.getElementById('resetBtn');
const downloadBtn = document.getElementById('downloadBtn');
const canvasWrapper = document.getElementById('canvasWrapper');
const imageCanvas = document.getElementById('imageCanvas');
const maskCanvas = document.getElementById('maskCanvas');
const ctxImg = imageCanvas.getContext('2d');
const ctxMask = maskCanvas.getContext('2d');
const emptyState = document.getElementById('emptyState');
const loader = document.getElementById('loader');
const modeBtns = document.querySelectorAll('.mode-btn');
// State
let isDrawing = false;
let img = new Image();
let currentMode = 'blur'; // blur, pixelate, remove, color
let hasImage = false;
let maskData = null; // Store mask data for processing
// Initialize
function init() {
resizeCanvases();
window.addEventListener('resize', resizeCanvases);
}
function resizeCanvases() {
// Canvases are sized based on the image, not the window,
// but CSS handles the display size.
if (hasImage) {
fitImageToScreen();
}
}
// --- Event Listeners ---
imageUpload.addEventListener('change', handleImageUpload);
brushSizeInput.addEventListener('input', (e) => {
brushSizeVal.textContent = `${e.target.value}px`;
});
// Mode Selection
modeBtns.forEach(btn => {
btn.addEventListener('click', () => {
modeBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentMode = btn.dataset.mode;
});
});
// Canvas Drawing Events
maskCanvas.addEventListener('mousedown', startDrawing);
maskCanvas.addEventListener('mousemove', draw);
maskCanvas.addEventListener('mouseup', stopDrawing);
maskCanvas.addEventListener('mouseleave', stopDrawing);
// Touch support
maskCanvas.addEventListener('touchstart', (e) => {
e.preventDefault(); // Prevent scrolling
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousedown', {
clientX: touch.clientX,
clientY: touch.clientY
});
maskCanvas.dispatchEvent(mouseEvent);
}, { passive: false });
maskCanvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousemove', {
clientX: touch.clientX,
clientY: touch.clientY
});
maskCanvas.dispatchEvent(mouseEvent);
}, { passive: false });
maskCanvas.addEventListener('touchend', () => {
const mouseEvent = new MouseEvent('mouseup', {});
maskCanvas.dispatchEvent(mouseEvent);
});
processBtn.addEventListener('click', processInpainting);
resetBtn.addEventListener('click', resetMask);
downloadBtn.addEventListener('click', downloadImage);
// --- Core Functions ---
function handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
img = new Image();
img.onload = () => {
hasImage = true;
emptyState.style.display = 'none';
imageCanvas.style.display = 'block';
maskCanvas.style.display = 'block';
// Set canvas dimensions to match image
imageCanvas.width = img.width;
imageCanvas.height = img.height;
maskCanvas.width = img.width;
maskCanvas.height = img.height;
// Draw image
ctxImg.drawImage(img, 0, 0);
// Clear mask
ctxMask.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
// Enable buttons
processBtn.disabled = false;
resetBtn.disabled = false;
downloadBtn.disabled = true; // Disable download until processed
fitImageToScreen();
showToast('Image uploaded successfully');
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
function fitImageToScreen() {
// CSS handles the visual scaling (max-width: 100%),
// we just need to ensure the coordinate mapping is correct.
}
function getMousePos(canvas, evt) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
return {
x: (evt.clientX - rect.left) * scaleX,
y: (evt.clientY - rect.top) * scaleY
};
}
function startDrawing(e) {
if (!hasImage) return;
isDrawing = true;
draw(e);
}
function draw(e) {
if (!isDrawing) return;
const pos = getMousePos(maskCanvas, e);
const size = parseInt(brushSizeInput.value);
ctxMask.lineCap = 'round';
ctxMask.lineJoin = 'round';
ctxMask.lineWidth = size;
ctxMask.strokeStyle = 'rgba(255, 0, 0, 0.5)'; // Semi-transparent red for mask
ctxMask.lineTo(pos.x, pos.y);
ctxMask.stroke();
ctxMask.beginPath();
ctxMask.moveTo(pos.x, pos.y);
}
function stopDrawing() {
if (isDrawing) {
isDrawing = false;
ctxMask.beginPath();
}
}
function resetMask() {
ctxMask.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
// Restore original image if we processed it
if (hasImage) {
ctxImg.drawImage(img, 0, 0);
downloadBtn.disabled = true;
}
}
// --- The "Inpainting" Logic (Client-Side Simulation) ---
function processInpainting() {
if (!hasImage) return;
// Show loading
loader.classList.add('active');
// Use setTimeout to allow UI to update (show loader) before heavy calculation
setTimeout(() => {
const width = imageCanvas.width;
const height = imageCanvas.height;
const imgData = ctxImg.getImageData(0, 0, width, height);
const maskImageData = ctxMask.getImageData(0, 0, width, height);
const data = imgData.data;
const maskData = maskImageData.data;
// Create a temporary array to store modified pixels so we don't read pixels we just wrote
const outputBuffer = new Uint8ClampedArray(data);
// Identify masked pixels
// In our mask canvas, we drew with alpha > 0.
// We check the Alpha channel of the mask canvas (index 3 + 4n)
const maskedPixels = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const index = (y * width + x) * 4;
// If mask alpha is significant
if (maskData[index + 3] > 0) {
maskedPixels.push({x, y});
}
}
}
if (maskedPixels.length === 0) {
loader.classList.remove('active');
showToast('No mask detected. Please paint over an area.');
return;
}
// Apply effect based on mode
if (currentMode === 'blur') {
applySmartBlur(outputBuffer, width, height, maskedPixels, maskData);
} else if (currentMode === 'pixelate') {
applyPixelate(outputBuffer, width, height, maskedPixels);
} else if (currentMode === 'color') {
applySolidColor(outputBuffer, width, height, maskedPixels, [0, 0, 0]); // Black fill
} else if (currentMode === 'remove') {
applyTextureFill(outputBuffer, width, height, maskedPixels, maskData);
}
// Put data back
const finalImageData = new ImageData(outputBuffer, width, height);
ctxImg.putImageData(finalImageData, 0, 0);
// Clear mask for visual clarity
ctxMask.clearRect(0, 0, width, height);
loader.classList.remove('active');
downloadBtn.disabled = false;
showToast('Inpainting applied!');
}, 100);
}
// Algorithm 1: Smart Blur (Averages surrounding non-masked pixels)
function applySmartBlur(buffer, width, height, pixels, maskData) {
const radius = 10;
const data = buffer;
pixels.forEach(p => {
const idx = (p.y * width + p.x) * 4;
let rSum = 0, gSum = 0, bSum = 0, count = 0;
// Look at neighbors
for (let dy = -radius; dy <= radius; dy++) {
for (let dx = -radius; dx <= radius; dx++) {
const ny = p.y + dy;
const nx = p.x + dx;
if (ny >= 0 && ny < height && nx >= 0 && nx < width) {
const nIdx = (ny * width + nx) * 4;
// Only sample from pixels that are NOT masked (mask alpha is 0)
// This creates the "inpainting" effect by pulling from edges inward
if (maskData[nIdx + 3] === 0) {
rSum += data[nIdx];
gSum += data[nIdx + 1];
bSum += data[nIdx + 2];
count++;
}
}
}
}
if (count > 0) {
data[idx] = rSum / count;
data[idx + 1] = gSum / count;
data[idx + 2] = bSum / count;
}
});
}
// Algorithm 2: Pixelate
function applyPixelate(buffer, width, height, pixels) {
const size = 15; // Pixel block size
const data = buffer;
// Group pixels into blocks
const blocks = {};
pixels.forEach(p => {
const bx = Math.floor(p.x / size);
const by = Math.floor(p.y / size);
const key = `${bx},${by}`;
if (!blocks[key]) blocks[key] = [];
blocks[key].push(p);
});
// Process each block
Object.values(blocks).forEach(blockPixels => {
// Find center of block
const cx = blockPixels[0].x;
const cy = blockPixels[0].y;
// Get color from center (approximate)
const centerIdx = (cy * width + cx) * 4;
const r = data[centerIdx];
const g = data[centerIdx + 1];
const b = data[centerIdx + 2];
// Fill block
blockPixels.forEach(p => {
const idx = (p.y * width + p.x) * 4;
data[idx] = r;
data[idx + 1] = g;
data[idx + 2] = b;
});
});
}
// Algorithm 3: Texture Fill (Simple Patch Match simulation)
function applyTextureFill(buffer, width, height, pixels, maskData) {
// For simplicity in this demo, we just pull pixels from the left side of the image
// or mirrored position to simulate "filling" space.
const data = buffer;
pixels.forEach(p => {
const idx = (p.y * width + p.x) * 4;
// Try to find a valid neighbor to the left
let sourceX = p.x - 1;
let valid = false;
// Search leftwards
while(sourceX >= 0) {
const sIdx = (p.y * width + sourceX) * 4;
if (maskData[sIdx + 3] === 0) {
// Found a non-masked pixel, copy it
data[idx] = data[sIdx];
data[idx+1] = data[sIdx+1];
data[idx+2] = data[sIdx+2];
valid = true;
break;
}
sourceX--;
}
// If no left pixel, try top
if (!valid) {
let sourceY = p.y - 1;
while(sourceY >= 0) {
const sIdx = (sourceY * width + p.x) * 4;
if (maskData[sIdx + 3] === 0) {
data[idx] = data[sIdx];
data[idx+1] = data[sIdx+1];
data[idx+2] = data[sIdx+2];
break;
}
sourceY--;
}
}
});
}
function applySolidColor(buffer, width, height, pixels, color) {
pixels.forEach(p => {
const idx = (p.y * width + p.x) * 4;
buffer[idx] = color[0]; // R
buffer[idx + 1] = color[1]; // G
buffer[idx + 2] = color[2]; // B
});
}
function downloadImage() {
const link = document.createElement('a');
link.download = 'inpaint-result.png';
link.href = imageCanvas.toDataURL();
link.click();
}
function showToast(message) {
const toast = document.getElementById('toast');
const msg = document.getElementById('toastMsg');
msg.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Initialize on load
init();
</script>
</body>
</html>