import { useCallback } from 'react'; import html2canvas from 'html2canvas'; import { useToast } from '../contexts/ToastContext'; export function useScreenshot() { const toast = useToast(); const takeScreenshot = useCallback(async (username: string = 'Explorer') => { // 1. Find the container that includes both the Canvas and the HTML overlays (labels) const element = document.getElementById('cosmo-scene-container'); if (!element) { console.error('Scene container not found'); toast.error('无法找到截图区域'); return; } const toastId = toast.info('正在生成宇宙快照...', 0); try { // 2. Use html2canvas to capture the visual composite // We use a slightly lower scale if DPR is too high to save memory/performance, // but usually window.devicePixelRatio is fine (2 or 3). const capturedCanvas = await html2canvas(element, { backgroundColor: '#000000', useCORS: true, // Allow loading cross-origin images (textures) logging: false, scale: window.devicePixelRatio, allowTaint: true, // Needed if some textures are tainted (may block download if CORS fails) ignoreElements: (_el) => false, }); // 3. Create a fresh canvas for composition to ensure clean state const width = capturedCanvas.width; const height = capturedCanvas.height; const finalCanvas = document.createElement('canvas'); finalCanvas.width = width; finalCanvas.height = height; const ctx = finalCanvas.getContext('2d'); if (!ctx) { throw new Error('无法创建绘图上下文'); } // Draw the captured scene ctx.drawImage(capturedCanvas, 0, 0); // 4. Add Overlay / Watermark const now = new Date(); const dateStr = now.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }); const timeStr = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); // Calculate dynamic font sizes based on image width (e.g., for 4k screens) // Base logic: width 1920 -> size 32. Ratio ~ 0.016 const baseScale = width / 1920; const titleSize = Math.max(24, Math.floor(32 * baseScale)); const subTitleSize = Math.max(12, Math.floor(16 * baseScale)); const dateSize = Math.max(18, Math.floor(24 * baseScale)); const timeSize = Math.max(14, Math.floor(18 * baseScale)); // Margins const marginX = Math.max(20, Math.floor(40 * baseScale)); const marginY = Math.max(20, Math.floor(35 * baseScale)); const gradientHeight = Math.floor(200 * baseScale); // Background gradient for text legibility const gradient = ctx.createLinearGradient(0, height - gradientHeight, 0, height); gradient.addColorStop(0, 'transparent'); gradient.addColorStop(1, 'rgba(0,0,0,0.9)'); ctx.fillStyle = gradient; ctx.fillRect(0, height - gradientHeight, width, gradientHeight); // Text Settings common ctx.shadowColor = 'rgba(0,0,0,0.8)'; ctx.shadowBlur = 4; ctx.textBaseline = 'bottom'; // --- Right Side: User & App --- ctx.textAlign = 'right'; // Subtitle (Bottom) ctx.font = `${subTitleSize}px sans-serif`; ctx.fillStyle = '#aaaaaa'; ctx.fillText('DEEP SPACE EXPLORER', width - marginX, height - marginY); // Nickname@Cosmo (Above Subtitle) ctx.font = `bold ${titleSize}px sans-serif`; ctx.fillStyle = '#ffffff'; // Move up by subTitle height + padding ctx.fillText(`${username}@Cosmo`, width - marginX, height - marginY - subTitleSize - 10); // --- Left Side: Date & Time --- ctx.textAlign = 'left'; // Time (Bottom) ctx.font = `${timeSize}px monospace`; ctx.fillStyle = '#cccccc'; ctx.fillText(timeStr, marginX, height - marginY); // Date (Above Time) ctx.font = `bold ${dateSize}px monospace`; ctx.fillStyle = '#44aaff'; ctx.fillText(dateStr, marginX, height - marginY - timeSize - 10); // 5. Trigger Download const dataUrl = finalCanvas.toDataURL('image/png'); const link = document.createElement('a'); link.download = `Cosmo_Snapshot_${now.toISOString().slice(0,19).replace(/[:T]/g, '-')}.png`; link.href = dataUrl; link.click(); toast.success('宇宙快照已保存'); } catch (err) { console.error('Screenshot failed:', err); toast.error('截图失败,请稍后重试'); } finally { toast.removeToast(toastId); } }, [toast]); return { takeScreenshot }; }