您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a layer-based animation system to Drawaria, create and manage multiple drawing layers with PokeAPI integration
// ==UserScript== // @name Drawaria Layer Animation System // @namespace http://tampermonkey.net/ // @version 3.6 // @description Adds a layer-based animation system to Drawaria, create and manage multiple drawing layers with PokeAPI integration // @author YouTubeDrawaria // @match *://drawaria.online/* // @match *://*.drawaria.online/* // @grant none // @run-at document-idle // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // ==/UserScript== (function() { 'use strict'; class RealLayerAnimationSystem { constructor() { this.layers = [{ commands: [], visible: true, name: 'Layer 1' }]; this.currentLayer = 0; this.isDrawing = false; this.lastX = 0; this.lastY = 0; this.isAnimating = false; this.animationSpeed = 50; this.isLoopEnabled = false; // Nueva variable para controlar la calidad de importación this.highQualityImport = false; this.pixelProcessingStep = 2; this.targetImageResolution = 100; this.gameCanvas = null; this.gameCtx = null; this.previewCanvas = null; this.previewCtx = null; this.gameSocket = null; this.onionOpacity = 0.3; this.menuVisible = false; this.init(); } init() { this.captureGameWebSocket(); this.waitForGameCanvas(); this.createCompactInterface(); this.setupEventListeners(); } // ... [mantengo todos los métodos existentes sin cambios hasta setupEventListeners] captureGameWebSocket() { const originalSend = WebSocket.prototype.send; WebSocket.prototype.send = function (...args) { if (!this.url || !this.url.includes('drawaria')) { return originalSend.apply(this, args); } if (!window.layerAnimationSocket) { window.layerAnimationSocket = this; console.log('✅ WebSocket capturado para Layer Animation'); } return originalSend.apply(this, args); }; const checkExisting = () => { if (window.layerAnimationSocket && window.layerAnimationSocket.readyState === 1) { this.gameSocket = window.layerAnimationSocket; this.updateSocketStatus(); return; } setTimeout(checkExisting, 1000); }; checkExisting(); } sendRealDrawCommand(x1, y1, x2, y2, color, thickness) { if (!this.gameSocket || this.gameSocket.readyState !== 1) return false; const normX1 = (x1 / this.gameCanvas.width).toFixed(4); const normY1 = (y1 / this.gameCanvas.height).toFixed(4); const normX2 = (x2 / this.gameCanvas.width).toFixed(4); const normY2 = (y2 / this.gameCanvas.height).toFixed(4); const command = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${0 - thickness},"${color}",0,0,{}]]`; try { this.gameSocket.send(command); return true; } catch (error) { console.error('Error enviando comando:', error); return false; } } waitForGameCanvas() { const checkCanvas = () => { this.gameCanvas = document.getElementById('canvas'); if (this.gameCanvas) { this.gameCtx = this.gameCanvas.getContext('2d'); this.setupPreviewCanvas(); } else { setTimeout(checkCanvas, 100); } }; checkCanvas(); } setupPreviewCanvas() { this.previewCanvas = document.createElement('canvas'); this.previewCtx = this.previewCanvas.getContext('2d'); this.previewCanvas.width = 220; this.previewCanvas.height = 160; this.previewCanvas.id = 'layerPreviewCanvas'; this.previewCtx.lineCap = 'round'; this.previewCtx.lineJoin = 'round'; } createCompactInterface() { const html = ` <!-- Toggle Button --> <div id="layerToggleBtn" class="layer-toggle">🎬</div> <!-- Compact Panel --> <div id="layerCompactPanel" class="layer-compact-panel"> <div class="compact-header"> Layer Animator <span id="socketIndicator">⚫</span> </div> <div class="compact-canvas"> <div id="previewContainer"></div> </div> <div class="compact-controls"> <div class="layer-nav-compact"> <button id="prevLayer">◀</button> <span id="layerInfo">1/1</span> <button id="nextLayer">▶</button> </div> <div class="layer-actions-compact"> <button id="addLayer" title="Add Layer">➕</button> <button id="removeLayer" title="Remove Layer">➖</button> <button id="clearLayer" title="Clear Layer">🗑️</button> <button id="duplicateLayer" title="Duplicate Layer">📋</button> <button id="toggleLoopBtn" title="Toggle Loop Animation">🔄</button> <button id="toggleQualityBtn" title="Toggle High Quality Image Import">⭐</button> <button id="importImageBtn" title="Import Image(s)">🏞️</button> <button id="loadPokemonBtn" title="Load Random Pokemon Pixel Art">🎮</button> <input type="file" id="imageFileInput" multiple accept="image/*" style="display: none;"> </div> </div> <div class="onion-compact"> <label><input type="checkbox" id="onionToggle" checked> Onion</label> <input type="range" id="onionRange" min="10" max="100" value="30"> </div> <div class="animation-compact"> <button id="playBtn" class="play-btn-compact">▶️ PLAY</button> <button id="stopBtn" class="stop-btn-compact" disabled>⏹️ STOP</button> <div class="speed-compact"> <label class="speed-label">Speed: <span id="speedValue">18</span>ms</label> <input type="range" id="speedRange" min="3" max="500" value="18" class="speed-slider"> </div> </div> <div class="layer-list-compact" id="layerListCompact"></div> </div> `; document.body.insertAdjacentHTML('beforeend', html); const container = document.getElementById('previewContainer'); if (container && this.previewCanvas) { container.appendChild(this.previewCanvas); } this.addCompactStyles(); this.updateLayerList(); this.updateOnionSkin(); this.updateSocketStatus(); this.updateLoopButtonState(); this.updateQualityButtonState(); } addCompactStyles() { const css = ` .layer-toggle { position: fixed; top: 10px; right: 10px; width: 40px; height: 40px; background: linear-gradient(45deg, #4CAF50, #45a049); color: white; border: none; border-radius: 50%; cursor: pointer; z-index: 10000; display: flex; align-items: center; justify-content: center; font-size: 18px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); transition: all 0.3s ease; } .layer-toggle:hover { transform: scale(1.1); } .layer-compact-panel { position: fixed; top: 60px; right: 10px; width: 260px; background: linear-gradient(145deg, #2a2a2a, #1e1e1e); border: 2px solid #4CAF50; border-radius: 10px; padding: 12px; z-index: 9999; font-family: 'Arial', sans-serif; color: #fff; font-size: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); display: none; } .compact-header { text-align: center; font-weight: bold; margin-bottom: 10px; color: #4CAF50; border-bottom: 1px solid #444; padding-bottom: 5px; } .compact-canvas { margin-bottom: 10px; } #layerPreviewCanvas { width: 100%; height: 120px; border: 2px solid #555; border-radius: 5px; background: white; cursor: crosshair; display: block; } .compact-controls { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; } .layer-nav-compact { display: flex; justify-content: center; align-items: center; gap: 12px; } .layer-nav-compact button { background: #4CAF50; border: none; color: white; padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.3s; } .layer-nav-compact button:hover:not(:disabled) { background: #45a049; } .layer-nav-compact button:disabled { background: #666; cursor: not-allowed; } #layerInfo { background: #333; padding: 6px 12px; border-radius: 4px; min-width: 50px; text-align: center; font-size: 11px; font-weight: bold; } .layer-actions-compact { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; } .layer-actions-compact button { background: #555; border: none; color: white; padding: 6px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.2s; min-width: 30px; text-align: center; } .layer-actions-compact button:hover:not(:disabled) { background: #777; transform: scale(1.05); } .layer-actions-compact button:disabled { opacity: 0.5; cursor: not-allowed; } #toggleLoopBtn.active, #toggleQualityBtn.active { background: linear-gradient(45deg, #FFD700, #DAA520); color: #333; } #loadPokemonBtn { background: linear-gradient(45deg, #FF6B35, #F7931E); color: white; animation: pokemonGlow 2s infinite alternate; } #loadPokemonBtn:hover:not(:disabled) { background: linear-gradient(45deg, #F7931E, #FF6B35); transform: scale(1.1); } @keyframes pokemonGlow { 0% { box-shadow: 0 0 5px rgba(255,107,53,0.5); } 100% { box-shadow: 0 0 15px rgba(255,107,53,0.8); } } .onion-compact { background: #333; padding: 8px; border-radius: 5px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; } .onion-compact label { font-size: 11px; } .onion-compact input[type="range"] { width: 70px; } .animation-compact { margin-bottom: 10px; } .play-btn-compact, .stop-btn-compact { width: 100%; padding: 10px; margin-bottom: 8px; border: none; border-radius: 6px; font-size: 12px; font-weight: bold; cursor: pointer; transition: all 0.3s; } .play-btn-compact { background: linear-gradient(45deg, #4CAF50, #45a049); color: white; } .stop-btn-compact { background: linear-gradient(45deg, #f44336, #da190b); color: white; } .play-btn-compact:hover:not(:disabled) { transform: scale(1.02); } .stop-btn-compact:hover:not(:disabled) { transform: scale(1.02); } .play-btn-compact:disabled, .stop-btn-compact:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .speed-compact { background: #444; padding: 8px; border-radius: 5px; text-align: center; } .speed-label { display: block; font-size: 11px; margin-bottom: 5px; color: #ccc; } .speed-slider { width: 100%; height: 4px; background: #666; outline: none; border-radius: 2px; cursor: pointer; } .speed-slider::-webkit-slider-thumb { appearance: none; width: 14px; height: 14px; background: #4CAF50; border-radius: 50%; cursor: pointer; } .speed-slider::-moz-range-thumb { width: 14px; height: 14px; background: #4CAF50; border-radius: 50%; cursor: pointer; border: none; } #speedValue { font-weight: bold; color: #4CAF50; } .layer-list-compact { max-height: 120px; overflow-y: auto; background: #2a2a2a; border-radius: 5px; padding: 4px; } .layer-item-compact { padding: 5px 8px; border-bottom: 1px solid #444; cursor: pointer; font-size: 10px; display: flex; justify-content: space-between; align-items: center; border-radius: 3px; transition: background 0.2s; } .layer-item-compact:hover { background: #3a3a3a; } .layer-item-compact.active { background: #4CAF50; color: white; font-weight: bold; } .layer-item-compact.has-content { border-left: 3px solid #4CAF50; } .layer-visibility-compact { cursor: pointer; padding: 2px 4px; border-radius: 2px; background: #555; font-size: 10px; } #socketIndicator.connected { color: #4CAF50; } #socketIndicator.disconnected { color: #f44336; } .layer-list-compact::-webkit-scrollbar { width: 5px; } .layer-list-compact::-webkit-scrollbar-track { background: #1a1a1a; border-radius: 2px; } .layer-list-compact::-webkit-scrollbar-thumb { background: #555; border-radius: 2px; } .layer-list-compact::-webkit-scrollbar-thumb:hover { background: #777; } .pokemon-loading { animation: pokemonSpin 1s linear infinite; } @keyframes pokemonSpin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); } setupEventListeners() { document.getElementById('layerToggleBtn').addEventListener('click', () => this.togglePanel()); this.setupCanvasDrawing(); document.getElementById('prevLayer').addEventListener('click', () => this.previousLayer()); document.getElementById('nextLayer').addEventListener('click', () => this.nextLayer()); document.getElementById('addLayer').addEventListener('click', () => this.addLayer()); document.getElementById('removeLayer').addEventListener('click', () => this.removeLayer()); document.getElementById('clearLayer').addEventListener('click', () => this.clearCurrentLayer()); document.getElementById('duplicateLayer').addEventListener('click', () => this.duplicateCurrentLayer()); document.getElementById('toggleLoopBtn').addEventListener('click', () => this.toggleLoop()); document.getElementById('importImageBtn').addEventListener('click', () => this.importImages()); document.getElementById('imageFileInput').addEventListener('change', (e) => this.handleImageFiles(e.target.files)); document.getElementById('toggleQualityBtn').addEventListener('click', () => this.toggleQuality()); // Nuevo evento para el botón de Pokémon document.getElementById('loadPokemonBtn').addEventListener('click', () => this.loadRandomPokemon()); document.getElementById('onionToggle').addEventListener('change', () => this.updateOnionSkin()); document.getElementById('onionRange').addEventListener('input', (e) => { this.onionOpacity = e.target.value / 100; this.updateOnionSkin(); }); document.getElementById('playBtn').addEventListener('click', () => this.playRealAnimation()); document.getElementById('stopBtn').addEventListener('click', () => this.stopAnimation()); document.getElementById('speedRange').addEventListener('input', (e) => { this.animationSpeed = parseInt(e.target.value); document.getElementById('speedValue').textContent = this.animationSpeed; }); } // Nueva función para cargar Pokémon aleatorio usando PokeAPI async loadRandomPokemon() { const pokemonBtn = document.getElementById('loadPokemonBtn'); if (this.isAnimating) { console.log('No se puede cargar Pokémon durante la animación.'); return; } // Animación de loading pokemonBtn.classList.add('pokemon-loading'); pokemonBtn.disabled = true; try { // Generar ID aleatorio entre 1 y 1010 (cantidad aproximada de Pokémon en PokeAPI) const randomId = Math.floor(Math.random() * 1010) + 1; const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const pokemonData = await response.json(); const spriteUrl = pokemonData.sprites.front_default; if (!spriteUrl) { throw new Error('No sprite disponible para este Pokémon'); } console.log(`🎮 Cargando ${pokemonData.name.toUpperCase()} (ID: ${pokemonData.id})`); // Crear nueva imagen y procesarla const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = async () => { try { const newCommands = await this.processImageToCommands(img); if (newCommands.length > 0) { // Crear nueva capa para el Pokémon this.addLayer(); this.layers[this.currentLayer].name = `🎮 ${pokemonData.name.charAt(0).toUpperCase() + pokemonData.name.slice(1)}`; this.layers[this.currentLayer].commands = newCommands; console.log(`✅ ${pokemonData.name.toUpperCase()} importado exitosamente a la Capa ${this.currentLayer + 1}. Comandos: ${newCommands.length}`); this.updateUI(); } else { console.warn(`❌ No se pudieron generar comandos para ${pokemonData.name}`); } } catch (error) { console.error('Error al procesar el sprite del Pokémon:', error); } finally { // Restaurar estado del botón pokemonBtn.classList.remove('pokemon-loading'); pokemonBtn.disabled = false; } }; img.onerror = () => { console.error(`❌ No se pudo cargar el sprite de ${pokemonData.name}`); pokemonBtn.classList.remove('pokemon-loading'); pokemonBtn.disabled = false; }; img.src = spriteUrl; } catch (error) { console.error('Error al cargar Pokémon desde PokeAPI:', error); pokemonBtn.classList.remove('pokemon-loading'); pokemonBtn.disabled = false; } } // ... [resto de los métodos permanecen igual] togglePanel() { const panel = document.getElementById('layerCompactPanel'); this.menuVisible = !this.menuVisible; panel.style.display = this.menuVisible ? 'block' : 'none'; } setupCanvasDrawing() { if (!this.previewCanvas) return; this.previewCanvas.addEventListener('mousedown', (e) => this.startDrawing(e)); this.previewCanvas.addEventListener('mousemove', (e) => this.draw(e)); this.previewCanvas.addEventListener('mouseup', () => this.stopDrawing()); this.previewCanvas.addEventListener('mouseout', () => this.stopDrawing()); } getMousePos(e) { const rect = this.previewCanvas.getBoundingClientRect(); const scaleX = this.previewCanvas.width / rect.width; const scaleY = this.previewCanvas.height / rect.height; return { x: (e.clientX - rect.left) * scaleX, y: (e.clientY - rect.top) * scaleY }; } startDrawing(e) { this.isDrawing = true; const pos = this.getMousePos(e); this.lastX = pos.x; this.lastY = pos.y; } draw(e) { if (!this.isDrawing) return; const pos = this.getMousePos(e); const command = { type: 'line', x1: this.lastX, y1: this.lastY, x2: pos.x, y2: pos.y, color: this.getCurrentColor(), thickness: this.getCurrentThickness() }; this.layers[this.currentLayer].commands.push(command); this.drawCommand(this.previewCtx, command); this.lastX = pos.x; this.lastY = pos.y; this.updateLayerList(); } stopDrawing() { this.isDrawing = false; } getCurrentColor() { const colorPicker = document.querySelector('input[type="color"]'); return colorPicker ? colorPicker.value : '#000000'; } getCurrentThickness() { const thicknessPicker = document.querySelector('input[type="range"]'); return thicknessPicker ? parseInt(thicknessPicker.value) : 5; } drawCommand(ctx, command) { if (command.type === 'line') { ctx.strokeStyle = command.color; ctx.lineWidth = command.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(command.x1, command.y1); ctx.lineTo(command.x2, command.y2); ctx.stroke(); } } async playRealAnimation() { if (!this.gameCanvas || !this.gameSocket || this.isAnimating) { console.log('Canvas o WebSocket no disponible o animación ya en curso.'); return; } this.isAnimating = true; document.getElementById('playBtn').disabled = true; document.getElementById('stopBtn').disabled = false; this.disableLayerControls(true); do { await this.clearRealCanvas(); await this.delay(300); for (let layerIndex = 0; layerIndex < this.layers.length && this.isAnimating; layerIndex++) { const layer = this.layers[layerIndex]; if (!layer.visible || layer.commands.length === 0) continue; if (layerIndex > 0) { await this.clearRealCanvas(); await this.delay(200); } for (let i = 0; i < layer.commands.length && this.isAnimating; i++) { const command = layer.commands[i]; await this.executeRealCommand(command); await this.delay(this.animationSpeed); } if (this.isAnimating && layerIndex < this.layers.length - 1) { await this.delay(300); } } if (this.isAnimating && this.isLoopEnabled) { await this.delay(500); } } while (this.isAnimating && this.isLoopEnabled); this.stopAnimation(); } async executeRealCommand(command) { if (!this.gameCanvas || !this.gameSocket) return; const scaleX = this.gameCanvas.width / this.previewCanvas.width; const scaleY = this.gameCanvas.height / this.previewCanvas.height; const gameX1 = command.x1 * scaleX; const gameY1 = command.y1 * scaleY; const gameX2 = command.x2 * scaleX; const gameY2 = command.y2 * scaleY; this.gameCtx.strokeStyle = command.color; this.gameCtx.lineWidth = command.thickness + 5; this.gameCtx.lineCap = 'round'; this.gameCtx.beginPath(); this.gameCtx.moveTo(gameX1, gameY1); this.gameCtx.lineTo(gameX2, gameY2); this.gameCtx.stroke(); const success = this.sendRealDrawCommand( gameX1, gameY1, gameX2, gameY2, command.color, command.thickness ); if (!success) { console.log('Error enviando comando real'); } } async clearRealCanvas() { if (!this.gameSocket || !this.gameCtx || !this.gameCanvas) return; this.gameCtx.clearRect(0, 0, this.gameCanvas.width, this.gameCanvas.height); try { const clearThickness = Math.max(this.gameCanvas.width, this.gameCanvas.height) * 0.8; const clearColor = '#ffffff'; const steps = 3; for (let i = 0; i < steps; i++) { const y = (i / (steps - 1)) * this.gameCanvas.height; const success = this.sendRealDrawCommand( 0, y, this.gameCanvas.width, y, clearColor, clearThickness ); if (success) { await this.delay(10); } } } catch (error) { console.error('Error limpiando canvas real:', error); } } updateSocketStatus() { const indicator = document.getElementById('socketIndicator'); if (!indicator) return; if (this.gameSocket && this.gameSocket.readyState === 1) { indicator.textContent = '🟢'; indicator.className = 'connected'; } else { indicator.textContent = '🔴'; indicator.className = 'disconnected'; } setTimeout(() => this.updateSocketStatus(), 3000); } stopAnimation() { this.isAnimating = false; document.getElementById('playBtn').disabled = false; document.getElementById('stopBtn').disabled = true; this.disableLayerControls(false); } disableLayerControls(disable) { document.getElementById('prevLayer').disabled = disable; document.getElementById('nextLayer').disabled = disable; document.getElementById('addLayer').disabled = disable; document.getElementById('removeLayer').disabled = disable; document.getElementById('clearLayer').disabled = disable; document.getElementById('duplicateLayer').disabled = disable; document.getElementById('toggleLoopBtn').disabled = disable; document.getElementById('importImageBtn').disabled = disable; document.getElementById('toggleQualityBtn').disabled = disable; document.getElementById('loadPokemonBtn').disabled = disable; // Nuevo control document.getElementById('onionToggle').disabled = disable; document.getElementById('onionRange').disabled = disable; document.getElementById('speedRange').disabled = disable; } addLayer() { this.layers.push({ commands: [], visible: true, name: `Layer ${this.layers.length + 1}` }); this.currentLayer = this.layers.length - 1; this.updateUI(); console.log(`Capa ${this.currentLayer + 1} agregada.`); } removeLayer() { if (this.layers.length <= 1) { console.log('No se puede eliminar la última capa.'); return; } if (this.currentLayer === this.layers.length - 1) { this.currentLayer = Math.max(0, this.currentLayer - 1); } this.layers.splice(this.currentLayer, 1); this.updateUI(); console.log(`Capa eliminada. Capas restantes: ${this.layers.length}`); } previousLayer() { if (this.currentLayer > 0) { this.currentLayer--; this.updateUI(); } } nextLayer() { if (this.currentLayer < this.layers.length - 1) { this.currentLayer++; this.updateUI(); } } clearCurrentLayer() { this.layers[this.currentLayer].commands = []; this.updateOnionSkin(); this.updateLayerList(); console.log(`Capa ${this.currentLayer + 1} limpiada.`); } duplicateCurrentLayer() { const duplicated = { commands: [...this.layers[this.currentLayer].commands], visible: true, name: `${this.layers[this.currentLayer].name} Copy` }; this.layers.push(duplicated); this.currentLayer = this.layers.length - 1; this.updateUI(); console.log(`Capa ${this.currentLayer + 1} duplicada.`); } toggleLoop() { this.isLoopEnabled = !this.isLoopEnabled; this.updateLoopButtonState(); console.log(`Modo de bucle: ${this.isLoopEnabled ? 'ACTIVADO' : 'DESACTIVADO'}`); } updateLoopButtonState() { const loopBtn = document.getElementById('toggleLoopBtn'); if (loopBtn) { if (this.isLoopEnabled) { loopBtn.classList.add('active'); } else { loopBtn.classList.remove('active'); } } } toggleQuality() { this.highQualityImport = !this.highQualityImport; this.pixelProcessingStep = this.highQualityImport ? 1 : 2; this.targetImageResolution = this.highQualityImport ? 150 : 100; this.updateQualityButtonState(); console.log(`Calidad de importación de imagen: ${this.highQualityImport ? 'ALTA' : 'NORMAL'} (paso de píxel: ${this.pixelProcessingStep}, resolución: ${this.targetImageResolution}x${this.targetImageResolution})`); } updateQualityButtonState() { const qualityBtn = document.getElementById('toggleQualityBtn'); if (qualityBtn) { if (this.highQualityImport) { qualityBtn.classList.add('active'); } else { qualityBtn.classList.remove('active'); } } } importImages() { document.getElementById('imageFileInput').click(); } async handleImageFiles(files) { if (files.length === 0) return; for (let i = 0; i < files.length; i++) { const file = files[i]; if (!file.type.startsWith('image/')) { console.warn(`El archivo ${file.name} no es una imagen, se omitirá.`); continue; } console.log(`Procesando imagen: ${file.name}`); const reader = new FileReader(); reader.onload = async (e) => { const img = new Image(); img.onload = async () => { try { const newCommands = await this.processImageToCommands(img); if (newCommands.length > 0) { this.addLayer(); this.layers[this.currentLayer].name = `Image Layer (${file.name.substring(0,15)}...)`; this.layers[this.currentLayer].commands = newCommands; console.log(`Imagen "${file.name}" importada a la Capa ${this.currentLayer + 1}. Comandos generados: ${newCommands.length}`); this.updateUI(); } else { console.warn(`No se pudieron generar comandos de dibujo para la imagen "${file.name}".`); } } catch (error) { console.error('Error al procesar la imagen:', error); } }; img.onerror = () => { console.error(`No se pudo cargar la imagen: ${file.name}`); }; img.src = e.target.result; }; reader.readAsDataURL(file); } } async processImageToCommands(img) { const tempCanvas = document.createElement('canvas'); const tempCtx = tempCanvas.getContext('2d'); const maxWidth = this.targetImageResolution; const maxHeight = this.targetImageResolution; let width = img.width; let height = img.height; if (width > height) { if (width > maxWidth) { height *= maxWidth / width; width = maxWidth; } } else { if (height > maxHeight) { width *= maxHeight / height; height = maxHeight; } } tempCanvas.width = width; tempCanvas.height = height; tempCtx.imageSmoothingEnabled = this.highQualityImport; tempCtx.imageSmoothingQuality = this.highQualityImport ? 'high' : 'medium'; tempCtx.drawImage(img, 0, 0, width, height); const imageData = tempCtx.getImageData(0, 0, width, height); const data = imageData.data; const commands = []; const previewWidth = this.previewCanvas.width; const previewHeight = this.previewCanvas.height; const currentPixelStep = this.pixelProcessingStep; const baseThickness = currentPixelStep; for (let y = 0; y < height; y += currentPixelStep) { let currentSegment = null; for (let x = 0; x < width; x += currentPixelStep) { const i = (y * width + x) * 4; const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; const a = data[i + 3]; if (a < 50) { if (currentSegment) { const color = currentSegment.color; commands.push({ type: 'line', x1: (currentSegment.startX / width) * previewWidth, y1: (y / height) * previewHeight, x2: (x / width) * previewWidth, y2: (y / height) * previewHeight, color: color, thickness: baseThickness }); currentSegment = null; } continue; } const currentColor = `rgb(${r},${g},${b})`; if (currentSegment === null) { currentSegment = { startX: x, color: currentColor }; } else if (currentSegment.color !== currentColor) { commands.push({ type: 'line', x1: (currentSegment.startX / width) * previewWidth, y1: (y / height) * previewHeight, x2: (x / width) * previewWidth, y2: (y / height) * previewHeight, color: currentSegment.color, thickness: baseThickness }); currentSegment = { startX: x, color: currentColor }; } } if (currentSegment) { commands.push({ type: 'line', x1: (currentSegment.startX / width) * previewWidth, y1: (y / height) * previewHeight, x2: (width / width) * previewWidth, y2: (y / height) * previewHeight, color: currentSegment.color, thickness: baseThickness }); } } return commands; } updateUI() { this.updateLayerInfo(); this.updateOnionSkin(); this.updateLayerList(); this.updateButtonStates(); this.updateLoopButtonState(); this.updateQualityButtonState(); } updateLayerInfo() { const info = document.getElementById('layerInfo'); if (info) { info.textContent = `${this.currentLayer + 1}/${this.layers.length}`; } } updateButtonStates() { const prevBtn = document.getElementById('prevLayer'); const nextBtn = document.getElementById('nextLayer'); const removeBtn = document.getElementById('removeLayer'); const addBtn = document.getElementById('addLayer'); if (prevBtn) prevBtn.disabled = this.currentLayer === 0 || this.isAnimating; if (nextBtn) nextBtn.disabled = this.currentLayer === this.layers.length - 1 || this.isAnimating; if (removeBtn) removeBtn.disabled = this.layers.length <= 1 || this.isAnimating; if (addBtn) addBtn.disabled = this.isAnimating; document.getElementById('clearLayer').disabled = this.isAnimating; document.getElementById('duplicateLayer').disabled = this.isAnimating; document.getElementById('importImageBtn').disabled = this.isAnimating; document.getElementById('toggleQualityBtn').disabled = this.isAnimating; document.getElementById('loadPokemonBtn').disabled = this.isAnimating; } updateOnionSkin() { if (!this.previewCtx) return; this.previewCtx.clearRect(0, 0, this.previewCanvas.width, this.previewCanvas.height); const showOnion = document.getElementById('onionToggle')?.checked; if (showOnion) { if (this.currentLayer > 0) { this.previewCtx.globalAlpha = this.onionOpacity; this.layers[this.currentLayer - 1].commands.forEach(command => { this.drawCommand(this.previewCtx, {...command, color: '#ff6b6b'}); }); } if (this.currentLayer < this.layers.length - 1) { this.previewCtx.globalAlpha = this.onionOpacity; this.layers[this.currentLayer + 1].commands.forEach(command => { this.drawCommand(this.previewCtx, {...command, color: '#6bb6ff'}); }); } } this.previewCtx.globalAlpha = 1; this.layers[this.currentLayer].commands.forEach(command => { this.drawCommand(this.previewCtx, command); }); } updateLayerList() { const container = document.getElementById('layerListCompact'); if (!container) return; container.innerHTML = ''; this.layers.forEach((layer, index) => { const item = document.createElement('div'); item.className = `layer-item-compact ${index === this.currentLayer ? 'active' : ''} ${layer.commands.length > 0 ? 'has-content' : ''}`; item.innerHTML = ` <span>${layer.name || `L${index + 1}`} (${layer.commands.length})</span> <span class="layer-visibility-compact" data-layer="${index}">${layer.visible ? '👁️' : '🚫'}</span> `; item.addEventListener('click', (e) => { if (this.isAnimating) return; if (e.target.classList.contains('layer-visibility-compact')) { this.layers[index].visible = !this.layers[index].visible; this.updateLayerList(); } else { this.currentLayer = index; this.updateUI(); } }); container.appendChild(item); }); } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => new RealLayerAnimationSystem(), 3000); }); } else { setTimeout(() => new RealLayerAnimationSystem(), 3000); } })();