document.addEventListener('DOMContentLoaded', () => { // ------------------------ // 1. SELECTORES DE ELEMENTOS HTML // ------------------------ const addLayerFile = document.getElementById('addLayerFile'); const preview = document.getElementById('preview'); const sheetView = document.getElementById('sheetView'); if (!preview || !sheetView) { alert("ERROR: Faltan elementos Canvas en el HTML ('preview' o 'sheetView'). Revisar index.html."); return; } const ctx = preview.getContext('2d'); const sheetCtx = sheetView.getContext('2d'); const frameW = document.getElementById('frameW'); const frameH = document.getElementById('frameH'); const framesCountInput = document.getElementById('framesCount'); const speedInput = document.getElementById('speed'); const showGrid = document.getElementById('showGrid'); const addFrameBtn = document.getElementById('addFrame'); const removeFrameBtn = document.getElementById('removeFrame'); const playFramesBtn = document.getElementById('playFrames'); const exportGIFBtn = document.getElementById('exportGIF'); const exportWebmBtn = document.getElementById('exportWebm'); // Nuevo selector // SELECTORES DE BOTONES DE AJUSTE DIRECCIONAL const adjustLeftBtn = document.getElementById('adjustLeftBtn'); const adjustRightBtn = document.getElementById('adjustRightBtn'); const adjustUpBtn = document.getElementById('adjustUpBtn'); const adjustDownBtn = document.getElementById('adjustDownBtn'); let frames = []; let currentFrame = 0; let animationInterval; let uploadedImage; // Variables de estado let currentSheetX = 0; let currentSheetY = 0; let isDragging = false; let lastTouchX = 0; let lastTouchY = 0; // Tamaño mínimo de frame const MIN_FRAME_SIZE = 16; const STEP = 8; // Pixeles de ajuste y movimiento // ------------------------ // 2. LÓGICA DE DIBUJO // ------------------------ function drawCurrentFramePreview(x, y) { if (!uploadedImage) return; const fW = parseInt(frameW.value); const fH = parseInt(frameH.value); ctx.clearRect(0, 0, preview.width, preview.height); ctx.drawImage(uploadedImage, x, y, fW, fH, 0, 0, fW, fH); if (showGrid.checked) drawGrid(); } function drawSheetView() { if (!uploadedImage) return; const sheetW = uploadedImage.width; const sheetH = uploadedImage.height; const canvasW = sheetView.width; const canvasH = sheetView.height; const fW = parseInt(frameW.value); const fH = parseInt(frameH.value); const scaleX = canvasW / sheetW; const scaleY = canvasH / sheetH; const scale = Math.min(scaleX, scaleY); const scaledW = sheetW * scale; const scaledH = sheetH * scale; const offsetX = (canvasW - scaledW) / 2; const offsetY = (canvasH - scaledH) / 2; sheetCtx.clearRect(0, 0, canvasW, canvasH); sheetCtx.drawImage(uploadedImage, offsetX, offsetY, scaledW, scaledH); sheetCtx.strokeStyle = 'red'; sheetCtx.lineWidth = 2; sheetCtx.strokeRect( offsetX + currentSheetX * scale, offsetY + currentSheetY * scale, fW * scale, fH * scale ); } function drawGrid() { ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.strokeRect(0, 0, preview.width, preview.height); } // ------------------------ // 3. EVENTO DE CARGA DE IMAGEN // ------------------------ addLayerFile.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; if (animationInterval) clearInterval(animationInterval); frames = []; framesCountInput.value = 0; currentSheetX = 0; currentSheetY = 0; const reader = new FileReader(); reader.onload = (event) => { const img = new Image(); img.onload = () => { uploadedImage = img; const fW = parseInt(frameW.value); const fH = parseInt(frameH.value); if (isNaN(fW) || isNaN(fH) || fW < MIN_FRAME_SIZE || fH < MIN_FRAME_SIZE) { // Forzar a valores iniciales válidos frameW.value = 48; frameH.value = 48; } preview.width = parseInt(frameW.value); preview.height = parseInt(frameH.value); sheetView.width = 400; sheetView.height = 400; drawSheetView(); drawCurrentFramePreview(currentSheetX, currentSheetY); }; img.onerror = () => { alert("ERROR: La imagen no pudo cargarse."); uploadedImage = null; }; img.src = event.target.result; }; reader.readAsDataURL(file); }); // ------------------------ // 4. CONTROL MANUAL DE FRAMES (Añadir/Remover/Reproducir) // ------------------------ addFrameBtn.addEventListener('click', () => { if (!uploadedImage) return alert("Sube una imagen primero"); const fW = parseInt(frameW.value); const fH = parseInt(frameH.value); if (currentSheetY >= uploadedImage.height) { alert("Ya se recorrió toda la imagen. El proceso se detiene."); return; } const canvas = document.createElement('canvas'); canvas.width = fW; canvas.height = fH; const context = canvas.getContext('2d'); context.drawImage(uploadedImage, currentSheetX, currentSheetY, fW, fH, 0, 0, fW, fH); frames.push(canvas); framesCountInput.value = frames.length; currentSheetX += fW; if (currentSheetX >= uploadedImage.width) { currentSheetX = 0; currentSheetY += fH; } drawCurrentFramePreview(currentSheetX, currentSheetY); drawSheetView(); if (animationInterval) clearInterval(animationInterval); }); removeFrameBtn.addEventListener('click', () => { if (frames.length === 0) return; frames.pop(); framesCountInput.value = frames.length; const fW = parseInt(frameW.value); const fH = parseInt(frameH.value); const framesPerRow = Math.floor(uploadedImage.width / fW); if (currentSheetX === 0) { currentSheetY = Math.max(0, currentSheetY - fH); currentSheetX = (framesPerRow - 1) * fW; } else { currentSheetX = Math.max(0, currentSheetX - fW); } drawCurrentFramePreview(currentSheetX, currentSheetY); drawSheetView(); }); playFramesBtn.addEventListener('click', () => { if (frames.length === 0) { alert("No hay frames en la lista, ¡añádelos manualmente!"); return; } playFrames(); }); function playFrames() { if (frames.length === 0) return; if (animationInterval) clearInterval(animationInterval); currentFrame = 0; const speed = parseInt(speedInput.value) || 100; animationInterval = setInterval(() => { ctx.clearRect(0, 0, preview.width, preview.height); ctx.drawImage(frames[currentFrame], 0, 0, preview.width, preview.height); if (showGrid.checked) drawGrid(); currentFrame = (currentFrame + 1) % frames.length; }, speed); } // ------------------------ // 5. EXPORTACIÓN GIF/WebM // ------------------------ exportGIFBtn.addEventListener('click', exportGIF); function exportGIF() { if (frames.length === 0) return alert("No hay frames para exportar."); if (typeof GIF === 'undefined') return alert("ERROR: La librería GIF.js no está cargada."); // ... (código de exportación GIF) ... } // NUEVA FUNCIÓN: EXPORTACIÓN WEBM exportWebmBtn.addEventListener('click', exportWebM); function exportWebM() { if (frames.length === 0) return alert("No hay frames para exportar."); // La API MediaRecorder permite grabar frames como video WebM const chunks = []; const canvasRecorder = document.createElement('canvas'); canvasRecorder.width = parseInt(frameW.value); canvasRecorder.height = parseInt(frameH.value); const contextRecorder = canvasRecorder.getContext('2d'); const speed = parseInt(speedInput.value) || 100; const frameRate = 1000 / speed; // cuadros por segundo if (!MediaRecorder.isTypeSupported('video/webm')) { alert("ERROR: Tu navegador no soporta la grabación en WebM. Intenta exportar a GIF."); return; } exportWebmBtn.textContent = 'Codificando WebM...'; exportWebmBtn.disabled = true; // Capturar los frames uno por uno let frameIndex = 0; const captureInterval = setInterval(() => { if (frameIndex >= frames.length) { clearInterval(captureInterval); mediaRecorder.stop(); return; } contextRecorder.clearRect(0, 0, canvasRecorder.width, canvasRecorder.height); contextRecorder.drawImage(frames[frameIndex], 0, 0, canvasRecorder.width, canvasRecorder.height); frameIndex++; }, speed); // Iniciar la grabación const stream = canvasRecorder.captureStream(frameRate); const mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' }); mediaRecorder.ondataavailable = (e) => chunks.push(e.data); mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: 'video/webm' }); let link = document.getElementById('webmDownload'); if (!link) { link = document.createElement('a'); link.id = 'webmDownload'; link.textContent = 'WebM listo: hacer clic para descargar'; document.getElementById('downloadLinks').appendChild(link); } link.href = URL.createObjectURL(blob); link.download = 'sprite.webm'; exportWebmBtn.textContent = 'Exportar WebM'; exportWebmBtn.disabled = false; // Reanudar animación de preview si estaba activa playFrames(); }; mediaRecorder.start(speed); // Grabar cada 'speed' milisegundos // Limpiar la animación de preview mientras se graba if (animationInterval) clearInterval(animationInterval); } // ------------------------ // 7. LÓGICA DE AJUSTE POR BOTONES DIRECCIONALES // ------------------------ // Función central para actualizar el estado del frame y redibujar function updateFrame(newW, newH) { newW = Math.max(MIN_FRAME_SIZE, Math.min(newW, uploadedImage.width)); newH = Math.max(MIN_FRAME_SIZE, Math.min(newH, uploadedImage.height)); frameW.value = newW; frameH.value = newH; preview.width = newW; preview.height = newH; // Asegurar que el recuadro no se salga de los límites const finalW = parseInt(frameW.value); const finalH = parseInt(frameH.value); currentSheetX = Math.max(0, Math.min(currentSheetX, uploadedImage.width - finalW)); currentSheetY = Math.max(0, Math.min(currentSheetY, uploadedImage.height - finalH)); drawSheetView(); drawCurrentFramePreview(currentSheetX, currentSheetY); } // Handlers para los botones de ajuste // ANCHO (-) if (adjustLeftBtn) { adjustLeftBtn.addEventListener('click', () => { if (!uploadedImage) return alert("Sube una imagen primero."); const currentW = parseInt(frameW.value); // Ajustar el ancho y MOVER la posición de captura a la izquierda para mantener el borde derecho fijo. updateFrame(currentW - STEP, parseInt(frameH.value)); // Asegurar que currentSheetX se ajuste hacia la derecha por el cambio de tamaño currentSheetX = Math.max(0, currentSheetX + STEP); updateFrame(currentW - STEP, parseInt(frameH.value)); // Llamada repetida para aplicar currentSheetX y límites }); } // ANCHO (+) if (adjustRightBtn) { adjustRightBtn.addEventListener('click', () => { if (!uploadedImage) return alert("Sube una imagen primero."); const currentW = parseInt(frameW.value); // Solo ajustar el ancho (el borde izquierdo se mantiene) updateFrame(currentW + STEP, parseInt(frameH.value)); }); } // ALTO (-) if (adjustUpBtn) { adjustUpBtn.addEventListener('click', () => { if (!uploadedImage) return alert("Sube una imagen primero."); const currentH = parseInt(frameH.value); // Ajustar el alto y MOVER la posición de captura hacia arriba para mantener el borde inferior fijo. updateFrame(parseInt(frameW.value), currentH - STEP); // Asegurar que currentSheetY se ajuste hacia abajo por el cambio de tamaño currentSheetY = Math.max(0, currentSheetY + STEP); updateFrame(parseInt(frameW.value), currentH - STEP); // Llamada repetida para aplicar currentSheetY y límites }); } // ALTO (+) if (adjustDownBtn) { adjustDownBtn.addEventListener('click', () => { if (!uploadedImage) return alert("Sube una imagen primero."); const currentH = parseInt(frameH.value); // Solo ajustar el alto (el borde superior se mantiene) updateFrame(parseInt(frameW.value), currentH + STEP); }); } // ------------------------ // 8. CONTROL TÁCTIL (Solo MOVER - 1 Dedo) // ------------------------ // Función auxiliar para getDistance (mantener por si se necesita) function getDistance(touch1, touch2) { const dx = touch1.clientX - touch2.clientX; const dy = touch1.clientY - touch2.clientY; return Math.sqrt(dx * dx + dy * dy); } sheetView.addEventListener('touchstart', (e) => { e.preventDefault(); if (!uploadedImage) return; if (e.touches.length === 1) { isDragging = true; const touch = e.touches[0]; lastTouchX = touch.clientX; lastTouchY = touch.clientY; } }); sheetView.addEventListener('touchmove', (e) => { e.preventDefault(); if (!uploadedImage || !isDragging || e.touches.length !== 1) return; // LÓGICA DE DRAG (MOVIMIENTO - 1 Dedo) const touch = e.touches[0]; const dx = touch.clientX - lastTouchX; const dy = touch.clientY - lastTouchY; const sheetW = uploadedImage.width; const canvasW = sheetView.width; const scale = Math.min(canvasW / sheetW, canvasW / uploadedImage.height); const fW = parseInt(frameW.value); const fH = parseInt(frameH.value); let imgDx = Math.round(dx / scale); let imgDy = Math.round(dy / scale); // Mover solo si el arrastre es significativo (para evitar jitter) if (Math.abs(imgDx) >= STEP) { let framesMovedX = Math.round(imgDx / STEP); currentSheetX += framesMovedX * STEP; lastTouchX = touch.clientX; } if (Math.abs(imgDy) >= STEP) { let framesMovedY = Math.round(imgDy / STEP); currentSheetY += framesMovedY * STEP; lastTouchY = touch.clientY; } // Asegurar límites currentSheetX = Math.max(0, Math.min(currentSheetX, uploadedImage.width - fW)); currentSheetY = Math.max(0, Math.min(currentSheetY, uploadedImage.height - fH)); drawSheetView(); drawCurrentFramePreview(currentSheetX, currentSheetY); }); sheetView.addEventListener('touchend', (e) => { isDragging = false; }); });