/** * Region Selector JavaScript – versione finale * Gestione immagini grandi, salvataggio /temp, coordinate in scala corretta */ const container = document.getElementById('canvas-container'); const backgroundImage = document.getElementById('background-image'); const confirmBtn = document.getElementById('confirm-btn'); const cancelBtn = document.getElementById('cancel-btn'); const closeBtn = document.getElementById('close-btn'); const uploadBtn = document.getElementById('upload-btn'); const imageUpload = document.getElementById('image-upload'); const imageName = document.getElementById('image-name'); let isDrawing = false; let currentRectangle = null; let baseX = 0, baseY = 0, baseWidth = 0, baseHeight = 0; let imageScale = 1; let onConfirmCallback = null; let onCancelCallback = null; let rectangleExists = false; // Aspect Ratio Mode let aspectRatioMode = "free"; // "free" o ratio specifico (es. "16:9") let aspectRatioValue = null; // Valore numerico (es. 16/9 = 1.777) // Fix Image Size - tracking dello stato let displayScaleFactor = 1.0; // Fattore di scala applicato alla preview let isImageFixed = false; // True quando immagine è stata "fixata" // ============================================================================ // INIT // ============================================================================ document.addEventListener('DOMContentLoaded', () => { uploadBtn.addEventListener('click', () => imageUpload.click()); imageUpload.addEventListener('change', handleImageUpload); container.addEventListener('mousedown', handleCanvasMouseDown); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', () => { isDrawing = false; if (rectangleExists) { updateDimensionsDisplay(); } }); confirmBtn.addEventListener('click', handleConfirm); cancelBtn.addEventListener('click', handleCancel); closeBtn.addEventListener('click', handleCancel); backgroundImage.addEventListener('dragstart', e => e.preventDefault()); // Aspect Ratio Mode Selector const aspectRatioSelect = document.getElementById('aspect-ratio-select'); const aspectRatioHint = document.getElementById('aspect-ratio-hint'); if (aspectRatioSelect) { aspectRatioSelect.addEventListener('change', (e) => { aspectRatioMode = e.target.value; // Calcola valore numerico const ratioMap = { "free": null, "1:1": 1/1, "3:4": 3/4, "5:8": 5/8, "9:16": 9/16, "9:21": 9/21, "4:3": 4/3, "3:2": 3/2, "16:9": 16/9, "21:9": 21/9, }; aspectRatioValue = ratioMap[aspectRatioMode]; // Aggiorna hint if (aspectRatioMode === "free") { aspectRatioHint.textContent = "Disegno libero - il ratio verrà calcolato"; aspectRatioHint.style.color = "#64748b"; aspectRatioHint.style.fontWeight = "normal"; } else { aspectRatioHint.textContent = `Rettangolo vincolato a ${aspectRatioMode}`; aspectRatioHint.style.color = "#16a34a"; aspectRatioHint.style.fontWeight = "600"; } console.log(`[AspectRatio] Mode: ${aspectRatioMode}, Value: ${aspectRatioValue}`); // Se c'è già un rettangolo e passi a modalità vincolata, adattalo if (rectangleExists && aspectRatioValue !== null) { adjustRectangleToAspectRatio(); } }); } // 🔹 Gestione immagini caricate da ComfyUI (backend scala se necessario) backgroundImage.addEventListener("load", () => { const naturalW = backgroundImage.naturalWidth; const naturalH = backgroundImage.naturalHeight; console.log(`[RegionSelector] Image loaded: ${naturalW}x${naturalH}`); // Usa i dati salvati da loadBackgroundImage() se disponibili const scaled = backgroundImage.dataset.scaled === "true"; const scaleFactor = parseFloat(backgroundImage.dataset.scaleFactor || "1"); if (scaled) { console.log(`[RegionSelector] Loaded scaled image with scale factor: ${scaleFactor}`); } else { console.log(`[RegionSelector] Loaded original image (≤ 1024px)`); } // Salva i metadati backgroundImage.dataset.originalScale = scaleFactor; // 🆕 Crea bottone Fix Image Size se immagine è più grande di 1024 const maxDim = Math.max(naturalW, naturalH); console.log(`[FixImage] maxDim = ${maxDim}, condition (>1024) = ${maxDim > 1024}`); if (maxDim > 1024) { console.log('[FixImage] Image is large, creating button...'); if (!document.getElementById('fix-image-btn')) { createFixImageButton(); showAutoFixSuggestion(maxDim); } else { console.log('[FixImage] Button already exists, skipping creation'); } } else { console.log('[FixImage] Image is small, no button needed'); } setTimeout(updateImageScale, 150); }); }); // ============================================================================ // FILE UPLOAD MANUALE // ============================================================================ function handleImageUpload(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { backgroundImage.src = event.target.result; imageName.textContent = `Immagine corrente: ${file.name}`; backgroundImage.onload = () => { backgroundImage.dataset.originalWidth = backgroundImage.naturalWidth; backgroundImage.dataset.originalHeight = backgroundImage.naturalHeight; backgroundImage.dataset.originalScale = 1; setTimeout(updateImageScale, 150); }; }; reader.readAsDataURL(file); } // ============================================================================ // UPDATE SCALE // ============================================================================ function updateImageScale() { if (!backgroundImage) return; const nW = backgroundImage.naturalWidth; const dW = backgroundImage.clientWidth || backgroundImage.width; if (!nW || !dW) return; imageScale = nW / dW; console.log(`[RegionSelector] imageScale=${imageScale.toFixed(3)}`); } // ============================================================================ // ADJUST RECTANGLE TO ASPECT RATIO // ============================================================================ function adjustRectangleToAspectRatio() { if (!aspectRatioValue || !rectangleExists) return; // Mantieni larghezza, adatta altezza al ratio baseHeight = baseWidth / aspectRatioValue; // Aggiorna visualizzazione updateRectanglePosition( baseX / imageScale, baseY / imageScale, baseWidth / imageScale, baseHeight / imageScale ); updateDimensionsDisplay(); console.log(`[AspectRatio] Adjusted to ${aspectRatioMode}: ${Math.round(baseWidth)}x${Math.round(baseHeight)}`); } // ============================================================================ // RECTANGLE DRAWING // ============================================================================ function handleCanvasMouseDown(e) { if (e.button !== 0) return; const rect = backgroundImage.getBoundingClientRect(); const imgX = e.clientX - rect.left; const imgY = e.clientY - rect.top; isDrawing = true; baseX = imgX * imageScale; // USA imageScale per ottenere coordinate reali baseY = imgY * imageScale; baseWidth = 1; baseHeight = 1; createNewRectangle(imgX, imgY, 1, 1); // Resetta le info updateDimensionsDisplay(); } function handleMouseMove(e) { if (!isDrawing) return; const rect = backgroundImage.getBoundingClientRect(); const imgX = e.clientX - rect.left; const imgY = e.clientY - rect.top; const realX = imgX * imageScale; const realY = imgY * imageScale; // Calcola dimensioni base let newWidth = Math.abs(realX - baseX); let newHeight = Math.abs(realY - baseY); // ⚙️ APPLICA VINCOLO SE NECESSARIO if (aspectRatioValue !== null) { // Modalità vincolata: forza l'aspect ratio // Mantieni la dimensione maggiore e adatta l'altra if (newWidth / aspectRatioValue > newHeight) { newHeight = newWidth / aspectRatioValue; } else { newWidth = newHeight * aspectRatioValue; } } // Altrimenti modalità "free": usa dimensioni naturali baseWidth = newWidth; baseHeight = newHeight; const x = Math.min(baseX, realX); const y = Math.min(baseY, realY); // Aggiorna visualizzazione updateRectanglePosition(x / imageScale, y / imageScale, baseWidth / imageScale, baseHeight / imageScale); // Aggiorna info updateDimensionsDisplay(); // Abilita conferma if (baseWidth > 10 && baseHeight > 10) { confirmBtn.disabled = false; } } function updateDimensionsDisplay() { const baseCoordinatesDiv = document.getElementById("base-coordinates"); if (!baseCoordinatesDiv) { console.warn("[RegionSelector] base-coordinates div not found!"); return; } if (!rectangleExists || baseWidth === 0 || baseHeight === 0) { baseCoordinatesDiv.innerHTML = `
Clicca e trascina sull'immagine per selezionare una regione
`; return; } const w = Math.round(baseWidth); const h = Math.round(baseHeight); const ratio = w / h; // 🎯 MODALITÀ CUSTOM: Calcola approssimazione let aspectRatioDisplay; if (aspectRatioMode === "free") { // Lista aspect ratio standard const standardRatios = [ { value: 21/9, label: "21:9 Landscape", display: "21:9" }, { value: 16/9, label: "16:9 Landscape", display: "16:9" }, { value: 3/2, label: "3:2 Landscape", display: "3:2" }, { value: 4/3, label: "4:3 Landscape", display: "4:3" }, { value: 1/1, label: "1:1 Square", display: "1:1" }, { value: 3/4, label: "3:4 Portrait", display: "3:4" }, { value: 5/8, label: "5:8 Portrait", display: "5:8" }, { value: 9/16, label: "9:16 Portrait", display: "9:16" }, { value: 9/21, label: "9:21 Portrait", display: "9:21" }, ]; // Trova il più vicino let closestRatio = standardRatios[0]; let minDiff = Math.abs(ratio - standardRatios[0].value); for (const r of standardRatios) { const diff = Math.abs(ratio - r.value); if (diff < minDiff) { minDiff = diff; closestRatio = r; } } const diffPercent = (minDiff / ratio) * 100; // Formato display basato su vicinanza if (diffPercent < 3) { // Molto vicino - mostra come esatto aspectRatioDisplay = `✓ ${closestRatio.label}`; } else if (diffPercent < 8) { // Abbastanza vicino - mostra approssimato aspectRatioDisplay = `~ ${closestRatio.label} (${ratio.toFixed(2)}:1)`; } else { // Troppo diverso - mostra custom + più vicino aspectRatioDisplay = `${ratio.toFixed(2)}:1 (vicino: ${closestRatio.display})`; } } else { // 🔒 MODALITÀ VINCOLATA: Mostra il vincolo attivo aspectRatioDisplay = `🔒 ${aspectRatioMode} (${ratio.toFixed(2)}:1)`; } // Aggiorna HTML baseCoordinatesDiv.innerHTML = `X: ${Math.round(baseX)} px
Y: ${Math.round(baseY)} px
Larghezza: ${w} px
Altezza: ${h} px
Aspect Ratio: ${aspectRatioDisplay}
`; } function createNewRectangle(x, y, width, height) { if (currentRectangle) currentRectangle.remove(); const rect = document.createElement("div"); rect.className = "rectangle border-inside"; rect.style.left = `${x}px`; rect.style.top = `${y}px`; rect.style.width = `${width}px`; rect.style.height = `${height}px`; container.appendChild(rect); currentRectangle = rect; rectangleExists = true; } function updateRectanglePosition(x, y, width, height) { if (!currentRectangle) return; currentRectangle.style.left = `${x}px`; currentRectangle.style.top = `${y}px`; currentRectangle.style.width = `${width}px`; currentRectangle.style.height = `${height}px`; } // ============================================================================ // EXPORT COORDINATES // ============================================================================ function getCoordinates() { if (!rectangleExists) return {}; // Leggi il fattore di scala salvato da loadBackgroundImage() const scaleFactor = parseFloat(backgroundImage.dataset.originalScale || "1"); // Le coordinate baseX, baseY, baseWidth, baseHeight sono relative all'immagine visualizzata // Se l'immagine è stata scalata dal backend, devi applicare il fattore di scala inverso // Per ottenere le coordinate dell'immagine ORIGINALE (prima del scaling) const coords = { x: Math.round(baseX / scaleFactor), // Dividi per scale factor y: Math.round(baseY / scaleFactor), width: Math.round(baseWidth / scaleFactor), height: Math.round(baseHeight / scaleFactor), imageScale: imageScale, scaleFactor: scaleFactor, // Salva il fattore di scala per riferimento displayedWidth: backgroundImage.clientWidth, displayedHeight: backgroundImage.clientHeight, naturalWidth: backgroundImage.naturalWidth, naturalHeight: backgroundImage.naturalHeight }; console.log("[RegionSelector] Exporting coordinates:", coords); return coords; } // ============================================================================ // ACTION BUTTONS // ============================================================================ function handleConfirm() { const coords = getCoordinates(); if (onConfirmCallback) onConfirmCallback(coords); console.log("[RegionSelector] Coordinates confirmed"); } function handleCancel() { if (onCancelCallback) onCancelCallback(); } // ============================================================================ // FIX IMAGE SIZE - FUNZIONI // ============================================================================ /** * Crea dinamicamente il bottone "Fix Image Size" nel panel di controllo. */ function createFixImageButton() { // Controlla se bottone esiste già if (document.getElementById('fix-image-btn')) { console.log('[FixImage] Button already exists'); return; } // Trova dove mettere il bottone: dopo aspect ratio hint const aspectRatioHint = document.getElementById('aspect-ratio-hint'); if (!aspectRatioHint) { console.error('[FixImage] Could not find aspect-ratio-hint'); return; } // Crea il nuovo control group per il bottone const newControlGroup = document.createElement('div'); newControlGroup.className = 'control-group'; // Crea il bottone const fixImageBtn = document.createElement('button'); fixImageBtn.id = 'fix-image-btn'; fixImageBtn.className = 'btn btn-primary'; fixImageBtn.textContent = '⚡ Fix Image Size'; fixImageBtn.addEventListener('click', () => { console.log('[FixImage] Button clicked, isImageFixed =', isImageFixed); if (isImageFixed) { resetImageScale(); } else { fixImageScale(); } }); // Crea la piccola descrizione const small = document.createElement('small'); small.textContent = 'Ridimensiona preview per selezione fluida'; // Aggiungi al control group newControlGroup.appendChild(fixImageBtn); newControlGroup.appendChild(small); // Inserisci dopo aspect ratio hint aspectRatioHint.parentNode.insertBefore(newControlGroup, aspectRatioHint.nextSibling); console.log('[FixImage] Button created dynamically'); } /** * Ridimensiona la preview dell'immagine a max 1024px mantenendo aspect ratio. */ function fixImageScale() { const naturalW = backgroundImage.naturalWidth; const naturalH = backgroundImage.naturalHeight; const maxDim = 1024; const maxCurrent = Math.max(naturalW, naturalH); if (maxCurrent <= maxDim) { alert("✓ Immagine già piccola (max: " + maxCurrent + "px)!"); return; } displayScaleFactor = maxDim / maxCurrent; const newW = Math.round(naturalW * displayScaleFactor); const newH = Math.round(naturalH * displayScaleFactor); console.log(`[FixImage] Scaling ${naturalW}x${naturalH} → ${newW}x${newH} (factor: ${displayScaleFactor.toFixed(3)})`); backgroundImage.style.width = `${newW}px`; backgroundImage.style.height = `${newH}px`; backgroundImage.style.maxWidth = 'none'; backgroundImage.style.maxHeight = 'none'; isImageFixed = true; const fixImageBtn = document.getElementById('fix-image-btn'); if (fixImageBtn) { fixImageBtn.textContent = "🔄 Reset Scale"; fixImageBtn.classList.remove('btn-primary'); fixImageBtn.classList.add('btn-warning'); } showScaleInfo(displayScaleFactor, newW, newH, naturalW, naturalH); const suggestion = document.getElementById('auto-fix-suggestion'); if (suggestion) { suggestion.style.display = 'none'; } setTimeout(() => { updateImageScale(); }, 100); } /** * Resetta la visualizzazione dell'immagine alle dimensioni originali. */ function resetImageScale() { displayScaleFactor = 1.0; console.log("[FixImage] Resetting to original scale"); backgroundImage.style.width = 'auto'; backgroundImage.style.height = 'auto'; backgroundImage.style.maxWidth = '100%'; backgroundImage.style.maxHeight = '100%'; isImageFixed = false; const fixImageBtn = document.getElementById('fix-image-btn'); if (fixImageBtn) { fixImageBtn.textContent = "⚡ Fix Image Size"; fixImageBtn.classList.remove('btn-warning'); fixImageBtn.classList.add('btn-primary'); } hideScaleInfo(); setTimeout(() => { updateImageScale(); }, 100); } /** * Mostra le informazioni sul fattore di scala applicato. */ function showScaleInfo(scale, displayW, displayH, originalW, originalH) { const infoDiv = document.getElementById('scale-info'); if (!infoDiv) return; const scalePercent = (scale * 100).toFixed(1); infoDiv.innerHTML = `📊 Preview Scale: ${scalePercent}%
🖼️ Display Size: ${displayW} × ${displayH} px
📐 Original Size: ${originalW} × ${originalH} px
✓ Selezione fluida attiva
Le coordinate saranno convertite automaticamente
`; infoDiv.style.display = 'block'; } /** * Nasconde le informazioni sul fattore di scala. */ function hideScaleInfo() { const infoDiv = document.getElementById('scale-info'); if (infoDiv) { infoDiv.style.display = 'none'; } } /** * Mostra un suggerimento per fixare immagini molto grandi. */ function showAutoFixSuggestion(maxDimension) { const suggestion = document.getElementById('auto-fix-suggestion'); if (!suggestion) return; const fixImageBtn = document.getElementById('fix-image-btn'); suggestion.innerHTML = `⚠️ Immagine molto grande (${maxDimension} px)
Per una selezione più fluida, clicca il bottone "Fix Image Size" qui sotto
`; suggestion.style.display = 'block'; if (fixImageBtn) { fixImageBtn.classList.add('pulse-animation'); setTimeout(() => { fixImageBtn.classList.remove('pulse-animation'); }, 5000); } } // ============================================================================ // PUBLIC API // ============================================================================ window.RegionSelector = { init: (onConfirm, onCancel) => { onConfirmCallback = onConfirm; onCancelCallback = onCancel; }, getCoordinates: () => getCoordinates() };