import React, { useState, useRef, useEffect } from 'react'; import { Upload, Eraser, Wand2, Download, Trash2, Image as ImageIcon, RotateCcw } from 'lucide-react'; export default function App() { const [image, setImage] = useState(null); const [brushSize, setBrushSize] = useState(30); const [isProcessing, setIsProcessing] = useState(false); const [hasMask, setHasMask] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const mainCanvasRef = useRef(null); const maskCanvasRef = useRef(null); const containerRef = useRef(null); const isDrawing = useRef(false); const lastPos = useRef({ x: 0, y: 0 }); // Handle Image Upload const handleImageUpload = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { const img = new Image(); img.onload = () => { setImage(img); // Set the image state first to render the canvas elements setErrorMessage(''); }; img.src = event.target.result; }; reader.readAsDataURL(file); }; // Setup Canvases with max dimensions to prevent browser lag during processing useEffect(() => { if (image && mainCanvasRef.current && maskCanvasRef.current) { setupCanvases(image); } }, [image]); const setupCanvases = (img) => { const MAX_DIM = 1200; let width = img.width; let height = img.height; if (width > MAX_DIM || height > MAX_DIM) { const ratio = Math.min(MAX_DIM / width, MAX_DIM / height); width = Math.floor(width * ratio); height = Math.floor(height * ratio); } const mainCanvas = mainCanvasRef.current; const maskCanvas = maskCanvasRef.current; mainCanvas.width = width; mainCanvas.height = height; maskCanvas.width = width; maskCanvas.height = height; const ctx = mainCanvas.getContext('2d'); ctx.drawImage(img, 0, 0, width, height); const maskCtx = maskCanvas.getContext('2d'); maskCtx.clearRect(0, 0, width, height); setHasMask(false); }; // Get correct coordinates accounting for CSS scaling const getCoordinates = (e) => { const canvas = maskCanvasRef.current; if (!canvas) return { x: 0, y: 0 }; 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 }; }; // Drawing Handlers const startDrawing = (e) => { e.preventDefault(); if (!image || isProcessing) return; isDrawing.current = true; lastPos.current = getCoordinates(e); }; const draw = (e) => { e.preventDefault(); if (!isDrawing.current || !image || isProcessing || !maskCanvasRef.current) return; const currentPos = getCoordinates(e); const maskCtx = maskCanvasRef.current.getContext('2d'); maskCtx.beginPath(); maskCtx.moveTo(lastPos.current.x, lastPos.current.y); maskCtx.lineTo(currentPos.x, currentPos.y); maskCtx.strokeStyle = 'rgba(255, 50, 50, 0.7)'; // Red semi-transparent mask maskCtx.lineWidth = brushSize; maskCtx.lineCap = 'round'; maskCtx.lineJoin = 'round'; maskCtx.stroke(); lastPos.current = currentPos; if (!hasMask) setHasMask(true); }; const stopDrawing = () => { isDrawing.current = false; }; // Clear the drawn mask const clearMask = () => { const maskCanvas = maskCanvasRef.current; if(!maskCanvas) return; const ctx = maskCanvas.getContext('2d'); ctx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); setHasMask(false); }; // Reset to original image const resetImage = () => { if (image) { setupCanvases(image); setErrorMessage(''); } }; // Simulasi Inpainting menggunakan Pixel Sorting dan Blur const processInpainting = async () => { if (!image || !hasMask || isProcessing || !mainCanvasRef.current || !maskCanvasRef.current) return; setIsProcessing(true); setErrorMessage(''); // Give UI time to show loading state await new Promise(resolve => setTimeout(resolve, 50)); try { const mainCanvas = mainCanvasRef.current; const maskCanvas = maskCanvasRef.current; const width = mainCanvas.width; const height = mainCanvas.height; const mainCtx = mainCanvas.getContext('2d'); const maskCtx = maskCanvas.getContext('2d'); const mainImgData = mainCtx.getImageData(0, 0, width, height); const maskImgData = maskCtx.getImageData(0, 0, width, height); const data = mainImgData.data; const mask = maskImgData.data; // Simple Pixel Sorting/Smudging effect for masked areas for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const i = (y * width + x) * 4; if (mask[i + 3] > 0) { // If pixel is masked // Find nearby unmasked pixels to sample from (simplified approach) let sampleX = x; let sampleY = y; let found = false; // Search outwards for an unmasked pixel for (let r = 1; r < 50; r++) { // search radius for(let dx = -r; dx <= r; dx++){ for(let dy = -r; dy <= r; dy++){ let nx = x + dx; let ny = y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height) { let ni = (ny * width + nx) * 4; if(mask[ni + 3] === 0){ sampleX = nx; sampleY = ny; found = true; break; } } } if(found) break; } if(found) break; } if(found){ const sampleI = (sampleY * width + sampleX) * 4; data[i] = data[sampleI]; data[i+1] = data[sampleI+1]; data[i+2] = data[sampleI+2]; } } } } mainCtx.putImageData(mainImgData, 0, 0); // Apply a slight blur to smooth the edges (optional, but helps blend) // Note: This applies to the whole image, a more complex implementation would only blur the masked area. // mainCtx.filter = 'blur(2px)'; // mainCtx.drawImage(mainCanvas, 0, 0); // mainCtx.filter = 'none'; clearMask(); } catch (error) { console.error('Error during inpainting:', error); setErrorMessage(`Error: Terjadi kesalahan saat memproses gambar.`); } finally { setIsProcessing(false); } }; const downloadImage = () => { if (!image || !mainCanvasRef.current) return; const link = document.createElement('a'); link.download = 'hasil-tanpa-watermark.png'; link.href = mainCanvasRef.current.toDataURL('image/png'); link.click(); }; // Prevent scrolling when drawing on touch devices useEffect(() => { const canvas = maskCanvasRef.current; if (canvas) { const preventDefault = (e) => e.preventDefault(); canvas.addEventListener('touchstart', preventDefault, { passive: false }); canvas.addEventListener('touchmove', preventDefault, { passive: false }); return () => { canvas.removeEventListener('touchstart', preventDefault); canvas.removeEventListener('touchmove', preventDefault); }; } }, [image]); return (
{/* Navbar */}

AI Watermark Remover

PT PROTECHNO EDUKASI
{/* Main Workspace */}
{/* Toolbar Sidebar */} {/* Canvas Area */}
{!image ? (

Unggah gambar untuk mulai menghapus watermark.

) : (
{/* Wrapping div to maintain aspect ratio */}
{/* Processing Overlay */} {isProcessing && (

AI sedang menganalisis & mengisi latar...

)}
)}
); }