import { useRef, useState, useCallback, useEffect } from 'react'; export function DrawingPadApp() { const canvasRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); const [color, setColor] = useState('#000000'); const [brushSize, setBrushSize] = useState(5); const [isEraser, setIsEraser] = useState(false); const startDrawing = useCallback((e: React.PointerEvent) => { setIsDrawing(true); const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; ctx.beginPath(); ctx.moveTo(x, y); }, []); const draw = useCallback((e: React.PointerEvent) => { if (!isDrawing) return; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; ctx.lineTo(x, y); ctx.lineWidth = brushSize; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; if (isEraser) { // Eraser mode: use destination-out to erase ctx.globalCompositeOperation = 'destination-out'; ctx.strokeStyle = 'rgba(0,0,0,1)'; // Color doesn't matter for erasing } else { // Drawing mode: normal composite operation ctx.globalCompositeOperation = 'source-over'; ctx.strokeStyle = color; } ctx.stroke(); }, [isDrawing, color, brushSize, isEraser]); const stopDrawing = useCallback(() => { setIsDrawing(false); }, []); const clearCanvas = useCallback(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; ctx.globalCompositeOperation = 'source-over'; ctx.clearRect(0, 0, canvas.width, canvas.height); // Restore white background ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); }, []); const saveDrawing = useCallback(() => { const canvas = canvasRef.current; if (!canvas) return; const dataUrl = canvas.toDataURL('image/png'); const link = document.createElement('a'); link.download = `drawing-${Date.now()}.png`; link.href = dataUrl; link.click(); }, []); // Set canvas size const setupCanvas = useCallback(() => { const canvas = canvasRef.current; if (!canvas) return; const rect = canvas.getBoundingClientRect(); const newWidth = rect.width; const newHeight = rect.height; // Check if dimensions actually changed if (canvas.width === newWidth && canvas.height === newHeight) { return; // No resize needed } // Save current canvas content before resizing (resizing clears the canvas) const ctx = canvas.getContext('2d'); if (!ctx) return; let savedImageData: ImageData | null = null; if (canvas.width > 0 && canvas.height > 0) { savedImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); } // Resize canvas (this clears it) canvas.width = newWidth; canvas.height = newHeight; // Restore white background ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Restore saved content if we had any if (savedImageData) { // Draw the saved content back (it might be smaller, so center it or scale it) ctx.putImageData(savedImageData, 0, 0); } }, []); // Set canvas size on mount and resize const canvasRefCallback = useCallback((canvas: HTMLCanvasElement | null) => { canvasRef.current = canvas; if (canvas) { setupCanvas(); } }, [setupCanvas]); useEffect(() => { const handleResize = () => { setupCanvas(); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [setupCanvas]); const colors = [ '#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF', '#FFA500', '#800080', '#FFC0CB', '#A52A2A', '#808080', '#FFFFFF' ]; return (
{colors.map((c) => (
setColor(e.target.value)} className="w-10 h-10 cursor-pointer" />
setBrushSize(Number(e.target.value))} className="w-32" /> {brushSize}px
); }