From bb05cdb1e88eea6813ef5a4a0a4fe22aa779ad5f Mon Sep 17 00:00:00 2001 From: mercu-lore <66737692+mercu-lore@users.noreply.github.com> Date: Wed, 12 Nov 2025 15:38:17 +0100 Subject: [PATCH] Add files via upload --- web/region_selector.html | 162 +++-- web/region_selector.js | 94 ++- web/region_selector_core.js | 1019 +++++++++++++++--------------- web/region_selector_extension.js | 572 +++++++++++++++-- web/region_selector_widget.js | 55 +- 5 files changed, 1235 insertions(+), 667 deletions(-) diff --git a/web/region_selector.html b/web/region_selector.html index 18a4e05..bac5399 100644 --- a/web/region_selector.html +++ b/web/region_selector.html @@ -91,15 +91,17 @@ body { display: flex; flex: 1; overflow: hidden; + min-height: 0; + min-width: 0; } /* Control Panel */ .control-panel { - width: 320px; + width: 250px; background-color: var(--panel-bg); border-right: 1px solid var(--border-color); overflow-y: auto; - padding: 20px; + padding: 15px; } .control-panel h3 { @@ -177,6 +179,31 @@ input[type="range"]::-moz-range-thumb { transition: all 0.2s ease; } +/* Selettore aspect ratio */ +.aspect-ratio-select { + width: 100%; + padding: 8px; + border: 1px solid var(--border-color); + border-radius: 6px; + font-size: 13px; + background-color: white; + cursor: pointer; + transition: all 0.2s ease; +} + +.aspect-ratio-select:focus { + outline: 2px solid var(--primary-color); + outline-offset: 2px; +} + +#aspect-ratio-hint { + display: block; + margin-top: 5px; + font-size: 11px; + color: var(--text-light); + font-style: italic; +} + /* Buttons */ .btn { padding: 8px 12px; @@ -228,6 +255,32 @@ input[type="range"]::-moz-range-thumb { background-color: #475569; } +.btn-warning { + background-color: var(--warning-color); + border-color: var(--warning-color); + color: white; + width: 100%; +} + +.btn-warning:hover:not(:disabled) { + background-color: #c2410c; + border-color: #c2410c; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.pulse-animation { + animation: pulse 2s ease-in-out infinite; +} + +.info-box-warning { + background-color: #fff7ed; + border-color: #fed7aa; +} + .btn-success { background-color: var(--success-color); color: white; @@ -305,12 +358,12 @@ input[type="range"]::-moz-range-thumb { .info-box { background-color: #f9fafb; border: 1px solid var(--border-color); - padding: 12px; + padding: 8px; border-radius: 6px; font-family: 'Monaco', 'Courier New', monospace; - font-size: 12px; + font-size: 11px; color: var(--text-color); - line-height: 1.5; + line-height: 1.4; overflow-x: auto; } @@ -331,8 +384,8 @@ input[type="range"]::-moz-range-thumb { } .action-buttons .btn { - padding: 10px; - font-size: 13px; + padding: 8px; + font-size: 12px; font-weight: 600; } @@ -356,25 +409,32 @@ hr { background-color: #fafbfc; overflow: hidden; position: relative; + padding: 20px; + min-height: 0; + min-width: 0; } #canvas-container { position: relative; - display: inline-flex; + display: flex; align-items: center; justify-content: center; background-color: white; - border-radius: 8px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); overflow: hidden; - max-width: 90%; - max-height: 90%; + width: 100%; + height: 100%; + max-width: 100%; + max-height: 100%; } #background-image { display: block; + width: auto; + height: auto; max-width: 100%; max-height: 100%; + object-fit: contain; + object-position: center; user-select: none; -webkit-user-select: none; -webkit-user-drag: none; @@ -470,7 +530,7 @@ hr { /* Responsive Design */ @media (max-width: 1024px) { .control-panel { - width: 280px; + width: 220px; } } @@ -523,64 +583,34 @@ textarea:focus {
-

Controlli

+

Selezione

- +
- - - - Immagine corrente: picsum.photos + + + Disegno libero - il ratio verrà calcolato
-
- - -
- -
- - -
-
- 100% -
- Zoom del rettangolo selezionato + + - -
- - - Larghezza linea di selezione -
- - -
- -
- - -
-
- -
- - -
- + +
@@ -621,6 +651,6 @@ textarea:focus {
- + diff --git a/web/region_selector.js b/web/region_selector.js index 0fa73b1..1a7c88f 100644 --- a/web/region_selector.js +++ b/web/region_selector.js @@ -142,7 +142,11 @@ function handleCanvasMouseDown(e) { const imgRect = backgroundImage.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); - // Coordinate del click relative all'immagine + // Calcola il fattore di scala (rapporto tra dimensioni visuali e dimensioni reali) + const scaleX = backgroundImage.naturalWidth / imgRect.width; + const scaleY = backgroundImage.naturalHeight / imgRect.height; + + // Coordinate del click relative all'immagine (in coordinate visive) const imgX = e.clientX - imgRect.left; const imgY = e.clientY - imgRect.top; @@ -151,6 +155,10 @@ function handleCanvasMouseDown(e) { return; } + // Converti a coordinate reali dell'immagine + const realImgX = imgX * scaleX; + const realImgY = imgY * scaleY; + // Verifica se stai ridimensionando if (currentRectangle) { const handleRect = detectResizeHandle(e, currentRectangle); @@ -178,16 +186,20 @@ function handleCanvasMouseDown(e) { } } - // Inizia nuovo rettangolo + // Inizia nuovo rettangolo (usando coordinate reali) isDrawing = true; - startX = imgX; - startY = imgY; - baseX = imgX; - baseY = imgY; + startX = realImgX; + startY = realImgY; + baseX = realImgX; + baseY = realImgY; baseWidth = 1; baseHeight = 1; - createNewRectangle(imgX, imgY, 1, 1); + // Salva i fattori di scala globali per uso in handleMouseMove + window.scaleX = scaleX; + window.scaleY = scaleY; + + createNewRectangle(realImgX, realImgY, 1, 1); } function handleMouseMove(e) { @@ -196,16 +208,24 @@ function handleMouseMove(e) { const imgRect = backgroundImage.getBoundingClientRect(); + // Usa i fattori di scala salvati o calcolali se non disponibili + const scaleX = window.scaleX || (backgroundImage.naturalWidth / imgRect.width); + const scaleY = window.scaleY || (backgroundImage.naturalHeight / imgRect.height); + if (isResizing && currentRectangle && resizingEdge) { - handleResizeRectangle(e, imgRect); + handleResizeRectangle(e, imgRect, scaleX, scaleY); } else if (isDrawing) { const imgX = e.clientX - imgRect.left; const imgY = e.clientY - imgRect.top; - const width = Math.max(10, Math.abs(imgX - startX)); - const height = Math.max(10, Math.abs(imgY - startY)); - const x = Math.min(startX, imgX); - const y = Math.min(startY, imgY); + // Converti a coordinate reali + const realImgX = imgX * scaleX; + const realImgY = imgY * scaleY; + + const width = Math.max(10, Math.abs(realImgX - startX)); + const height = Math.max(10, Math.abs(realImgY - startY)); + const x = Math.min(startX, realImgX); + const y = Math.min(startY, realImgY); baseX = x; baseY = y; @@ -232,12 +252,22 @@ function createNewRectangle(x, y, width, height) { currentRectangle.remove(); } + // Calcola le coordinate visive basate sulle coordinate reali + const imgRect = backgroundImage.getBoundingClientRect(); + const scaleX = backgroundImage.naturalWidth / imgRect.width; + const scaleY = backgroundImage.naturalHeight / imgRect.height; + + const visualX = x / scaleX; + const visualY = y / scaleY; + const visualWidth = width / scaleX; + const visualHeight = height / scaleY; + const rect = document.createElement('div'); rect.className = `rectangle border-${borderPosition}`; - rect.style.left = x + 'px'; - rect.style.top = y + 'px'; - rect.style.width = width + 'px'; - rect.style.height = height + 'px'; + rect.style.left = visualX + 'px'; + rect.style.top = visualY + 'px'; + rect.style.width = visualWidth + 'px'; + rect.style.height = visualHeight + 'px'; rect.style.borderWidth = currentBorderWidth + 'px'; // Aggiungi resize handles @@ -259,17 +289,27 @@ function createNewRectangle(x, y, width, height) { 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'; + // Calcola le coordinate visive basate sulle coordinate reali + const imgRect = backgroundImage.getBoundingClientRect(); + const scaleX = backgroundImage.naturalWidth / imgRect.width; + const scaleY = backgroundImage.naturalHeight / imgRect.height; + + const visualX = x / scaleX; + const visualY = y / scaleY; + const visualWidth = width / scaleX; + const visualHeight = height / scaleY; + + currentRectangle.style.left = visualX + 'px'; + currentRectangle.style.top = visualY + 'px'; + currentRectangle.style.width = visualWidth + 'px'; + currentRectangle.style.height = visualHeight + 'px'; } -function handleResizeRectangle(e, imgRect) { +function handleResizeRectangle(e, imgRect, scaleX, scaleY) { if (!currentRectangle) return; - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; + const deltaX = (e.clientX - startX) * scaleX; + const deltaY = (e.clientY - startY) * scaleY; let newX = rectStartX; let newY = rectStartY; @@ -424,10 +464,10 @@ function updateAllDimensions() { Altezza: ${Math.round(baseHeight)} px

Percentuali:
- X%: ${((baseX / backgroundImage.width) * 100).toFixed(2)}%
- Y%: ${((baseY / backgroundImage.height) * 100).toFixed(2)}%
- W%: ${((baseWidth / backgroundImage.width) * 100).toFixed(2)}%
- H%: ${((baseHeight / backgroundImage.height) * 100).toFixed(2)}% + X%: ${((baseX / backgroundImage.naturalWidth) * 100).toFixed(2)}%
+ Y%: ${((baseY / backgroundImage.naturalHeight) * 100).toFixed(2)}%
+ W%: ${((baseWidth / backgroundImage.naturalWidth) * 100).toFixed(2)}%
+ H%: ${((baseHeight / backgroundImage.naturalHeight) * 100).toFixed(2)}% `; baseCoordinates.innerHTML = baseInfo; diff --git a/web/region_selector_core.js b/web/region_selector_core.js index d31b07a..b1f1900 100644 --- a/web/region_selector_core.js +++ b/web/region_selector_core.js @@ -1,584 +1,585 @@ /** - * Region Selector JavaScript - Logica interazione canvas editor + * Region Selector JavaScript – versione finale + * Gestione immagini grandi, salvataggio /temp, coordinate in scala corretta */ -// ============================================================================ -// DOM ELEMENTS -// ============================================================================ - const container = document.getElementById('canvas-container'); const backgroundImage = document.getElementById('background-image'); -const baseCoordinates = document.getElementById('base-coordinates'); -const dimensionsInfo = document.getElementById('dimensions-info'); -const currentDimensions = document.getElementById('current-dimensions'); -const zoomInBtn = document.getElementById('zoom-in-btn'); -const zoomOutBtn = document.getElementById('zoom-out-btn'); -const zoomValue = document.getElementById('zoom-value'); -const borderSlider = document.getElementById('border-slider'); -const borderValue = document.getElementById('border-value'); -const resetBtn = document.getElementById('reset-btn'); -const borderPositionRadios = document.querySelectorAll('input[name="border-position"]'); -const imageUpload = document.getElementById('image-upload'); -const uploadBtn = document.getElementById('upload-btn'); -const imageName = document.getElementById('image-name'); const confirmBtn = document.getElementById('confirm-btn'); const cancelBtn = document.getElementById('cancel-btn'); const closeBtn = document.getElementById('close-btn'); - -// ============================================================================ -// STATE VARIABLES -// ============================================================================ +const uploadBtn = document.getElementById('upload-btn'); +const imageUpload = document.getElementById('image-upload'); +const imageName = document.getElementById('image-name'); let isDrawing = false; -let isResizing = false; -let resizingEdge = null; -let startX = 0; -let startY = 0; -let rectStartX = 0; -let rectStartY = 0; -let rectStartWidth = 0; -let rectStartHeight = 0; let currentRectangle = null; -let currentZoom = 1; -let currentBorderWidth = 3; -let borderPosition = 'inside'; -let rectangleExists = false; - -// Coordinate base (senza zoom e bordi) -let baseWidth = 0; -let baseHeight = 0; -let baseX = 0; -let baseY = 0; - -const defaultBorderWidth = 3; -const zoomStep = 0.25; -const minZoom = 0.25; -const maxZoom = 4; - -// Callback per comunicazione con il nodo +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" // ============================================================================ -// INITIALIZATION +// 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()); -document.addEventListener('DOMContentLoaded', function() { - initializeEventListeners(); + // 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); + }); }); -function initializeEventListeners() { - // Upload button - uploadBtn.addEventListener('click', handleUploadClick); - imageUpload.addEventListener('change', handleImageUpload); - - // Zoom buttons - zoomInBtn.addEventListener('click', handleZoomIn); - zoomOutBtn.addEventListener('click', handleZoomOut); - - // Border controls - borderSlider.addEventListener('input', handleBorderChange); - borderPositionRadios.forEach(radio => { - radio.addEventListener('change', handleBorderPositionChange); - }); - - // Reset button - resetBtn.addEventListener('click', handleReset); - - // Canvas drawing - container.addEventListener('mousedown', handleCanvasMouseDown); - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - - // Action buttons - confirmBtn.addEventListener('click', handleConfirm); - cancelBtn.addEventListener('click', handleCancel); - closeBtn.addEventListener('click', handleCancel); - - // Prevent image drag - backgroundImage.addEventListener('dragstart', (e) => e.preventDefault()); -} - // ============================================================================ -// FILE UPLOAD +// FILE UPLOAD MANUALE // ============================================================================ - -function handleUploadClick() { - imageUpload.click(); -} - function handleImageUpload(e) { - const file = e.target.files[0]; - if (file && file.type.startsWith('image/')) { - const reader = new FileReader(); + const file = e.target.files[0]; + if (!file) return; - reader.onload = function(event) { - backgroundImage.src = event.target.result; - imageName.textContent = `Immagine corrente: ${file.name}`; + const reader = new FileReader(); + reader.onload = (event) => { + backgroundImage.src = event.target.result; + imageName.textContent = `Immagine corrente: ${file.name}`; - if (currentRectangle) { - if (confirm('Cambiando immagine il rettangolo verrà cancellato. Continuare?')) { - resetRectangle(); - } else { - imageUpload.value = ''; - return; - } - } - }; + backgroundImage.onload = () => { + backgroundImage.dataset.originalWidth = backgroundImage.naturalWidth; + backgroundImage.dataset.originalHeight = backgroundImage.naturalHeight; + backgroundImage.dataset.originalScale = 1; + setTimeout(updateImageScale, 150); + }; + }; + reader.readAsDataURL(file); +} - 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; // Solo left click - if (!container || !backgroundImage) return; + if (e.button !== 0) return; + const rect = backgroundImage.getBoundingClientRect(); + const imgX = e.clientX - rect.left; + const imgY = e.clientY - rect.top; - const rect = container.getBoundingClientRect(); - const imgRect = backgroundImage.getBoundingClientRect(); - const containerRect = container.getBoundingClientRect(); + isDrawing = true; + baseX = imgX * imageScale; // USA imageScale per ottenere coordinate reali + baseY = imgY * imageScale; + baseWidth = 1; + baseHeight = 1; + createNewRectangle(imgX, imgY, 1, 1); - // Coordinate del click relative all'immagine - const imgX = e.clientX - imgRect.left; - const imgY = e.clientY - imgRect.top; - - // Se il click è fuori dall'immagine, non fare nulla - if (imgX < 0 || imgY < 0 || imgX > imgRect.width || imgY > imgRect.height) { - return; - } - - // Verifica se stai ridimensionando - if (currentRectangle) { - const handleRect = detectResizeHandle(e, currentRectangle); - if (handleRect) { - isResizing = true; - resizingEdge = handleRect; - startX = e.clientX; - startY = e.clientY; - rectStartX = baseX; - rectStartY = baseY; - rectStartWidth = baseWidth; - rectStartHeight = baseHeight; - currentRectangle.classList.add('resizing'); - return; - } - - // Verifica se stai spostando il rettangolo - if (isClickInsideRectangle(e, currentRectangle, imgRect)) { - isDrawing = true; - startX = e.clientX; - startY = e.clientY; - rectStartX = baseX; - rectStartY = baseY; - return; - } - } - - // Inizia nuovo rettangolo - isDrawing = true; - startX = imgX; - startY = imgY; - baseX = imgX; - baseY = imgY; - baseWidth = 1; - baseHeight = 1; - - createNewRectangle(imgX, imgY, 1, 1); + // Resetta le info + updateDimensionsDisplay(); } function handleMouseMove(e) { - if (!isDrawing && !isResizing) return; - if (!container || !backgroundImage) return; + if (!isDrawing) return; + const rect = backgroundImage.getBoundingClientRect(); + const imgX = e.clientX - rect.left; + const imgY = e.clientY - rect.top; - const imgRect = backgroundImage.getBoundingClientRect(); + const realX = imgX * imageScale; + const realY = imgY * imageScale; - if (isResizing && currentRectangle && resizingEdge) { - handleResizeRectangle(e, imgRect); - } else if (isDrawing) { - const imgX = e.clientX - imgRect.left; - const imgY = e.clientY - imgRect.top; + // Calcola dimensioni base + let newWidth = Math.abs(realX - baseX); + let newHeight = Math.abs(realY - baseY); - const width = Math.max(10, Math.abs(imgX - startX)); - const height = Math.max(10, Math.abs(imgY - startY)); - const x = Math.min(startX, imgX); - const y = Math.min(startY, imgY); - - baseX = x; - baseY = y; - baseWidth = width; - baseHeight = height; - - updateRectanglePosition(x, y, width, height); - updateAllDimensions(); + // ⚙️ 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 handleMouseUp(e) { - if (isResizing && currentRectangle) { - currentRectangle.classList.remove('resizing'); +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; + } } - isDrawing = false; - isResizing = false; - resizingEdge = null; + + 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) { - // Rimuovi il vecchio rettangolo se esiste - if (currentRectangle) { - currentRectangle.remove(); - } - - const rect = document.createElement('div'); - rect.className = `rectangle border-${borderPosition}`; - rect.style.left = x + 'px'; - rect.style.top = y + 'px'; - rect.style.width = width + 'px'; - rect.style.height = height + 'px'; - rect.style.borderWidth = currentBorderWidth + 'px'; - - // Aggiungi resize handles - ['top-left', 'top-right', 'bottom-left', 'bottom-right'].forEach(position => { - const handle = document.createElement('div'); - handle.className = `resize-handle ${position}`; - rect.appendChild(handle); - }); - - container.appendChild(rect); - currentRectangle = rect; - rectangleExists = true; - - // Abilita bottoni - enableControls(); - updateAllDimensions(); + 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'; -} - -function handleResizeRectangle(e, imgRect) { - if (!currentRectangle) return; - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - let newX = rectStartX; - let newY = rectStartY; - let newWidth = rectStartWidth; - let newHeight = rectStartHeight; - - // Handle diverse per lato di resize - if (resizingEdge.includes('left')) { - newX = Math.max(0, rectStartX + deltaX); - newWidth = rectStartWidth - (newX - rectStartX); - } - if (resizingEdge.includes('right')) { - newWidth = Math.max(10, rectStartWidth + deltaX); - } - if (resizingEdge.includes('top')) { - newY = Math.max(0, rectStartY + deltaY); - newHeight = rectStartHeight - (newY - rectStartY); - } - if (resizingEdge.includes('bottom')) { - newHeight = Math.max(10, rectStartHeight + deltaY); - } - - newWidth = Math.max(10, newWidth); - newHeight = Math.max(10, newHeight); - - baseX = newX; - baseY = newY; - baseWidth = newWidth; - baseHeight = newHeight; - - updateRectanglePosition(newX, newY, newWidth, newHeight); - updateAllDimensions(); -} - -function isClickInsideRectangle(e, rect, imgRect) { - const rectBounds = rect.getBoundingClientRect(); - return e.clientX >= rectBounds.left && - e.clientX <= rectBounds.right && - e.clientY >= rectBounds.top && - e.clientY <= rectBounds.bottom; -} - -function detectResizeHandle(e, rect) { - const handles = rect.querySelectorAll('.resize-handle'); - for (let handle of handles) { - const handleRect = handle.getBoundingClientRect(); - const distance = Math.sqrt( - Math.pow(e.clientX - (handleRect.left + handleRect.width / 2), 2) + - Math.pow(e.clientY - (handleRect.top + handleRect.height / 2), 2) - ); - if (distance < 12) { - return handle.className.split(' ').find(c => c !== 'resize-handle'); - } - } - return null; + if (!currentRectangle) return; + currentRectangle.style.left = `${x}px`; + currentRectangle.style.top = `${y}px`; + currentRectangle.style.width = `${width}px`; + currentRectangle.style.height = `${height}px`; } // ============================================================================ -// ZOOM CONTROLS +// EXPORT COORDINATES // ============================================================================ - -function handleZoomIn() { - if (currentRectangle) { - currentZoom = Math.min(currentZoom + zoomStep, maxZoom); - updateZoom(); - updateAllDimensions(); - } -} - -function handleZoomOut() { - if (currentRectangle) { - currentZoom = Math.max(currentZoom - zoomStep, minZoom); - updateZoom(); - updateAllDimensions(); - } -} - -function updateZoom() { - const zoomPercent = Math.round(currentZoom * 100); - zoomValue.textContent = zoomPercent + '%'; - if (currentRectangle) { - currentRectangle.style.transform = `scale(${currentZoom})`; - } - - zoomInBtn.disabled = currentZoom >= maxZoom; - zoomOutBtn.disabled = currentZoom <= minZoom; -} - -// ============================================================================ -// BORDER CONTROLS -// ============================================================================ - -function handleBorderChange(e) { - currentBorderWidth = parseInt(e.target.value); - borderValue.textContent = currentBorderWidth + 'px'; - - if (currentRectangle) { - currentRectangle.style.borderWidth = currentBorderWidth + 'px'; - updateAllDimensions(); - } -} - -function handleBorderPositionChange(e) { - borderPosition = e.target.value; - - if (currentRectangle) { - currentRectangle.classList.remove('border-inside', 'border-outside'); - currentRectangle.classList.add(`border-${borderPosition}`); - updateAllDimensions(); - } -} - -// ============================================================================ -// RESET AND CLEAR -// ============================================================================ - -function handleReset() { - resetRectangle(); -} - -function resetRectangle() { - if (currentRectangle) { - currentRectangle.remove(); - currentRectangle = null; - } - - rectangleExists = false; - currentZoom = 1; - baseWidth = 0; - baseHeight = 0; - baseX = 0; - baseY = 0; - - baseCoordinates.innerHTML = '

Clicca e trascina sull\'immagine per disegnare un rettangolo

'; - dimensionsInfo.style.display = 'none'; - - disableControls(); -} - -// ============================================================================ -// UPDATE DISPLAY -// ============================================================================ - -function updateAllDimensions() { - if (!rectangleExists) return; - - // Coordinate base (senza zoom e bordi) - const baseInfo = ` - X: ${Math.round(baseX)} px
- Y: ${Math.round(baseY)} px
- Larghezza: ${Math.round(baseWidth)} px
- Altezza: ${Math.round(baseHeight)} px
-
- Percentuali:
- X%: ${((baseX / backgroundImage.width) * 100).toFixed(2)}%
- Y%: ${((baseY / backgroundImage.height) * 100).toFixed(2)}%
- W%: ${((baseWidth / backgroundImage.width) * 100).toFixed(2)}%
- H%: ${((baseHeight / backgroundImage.height) * 100).toFixed(2)}% - `; - - baseCoordinates.innerHTML = baseInfo; - - // Mostra info zoom e bordi se zoom > 1 o bordo > 0 - if (currentZoom > 1 || currentBorderWidth > 0) { - const zoomedWidth = baseWidth * currentZoom; - const zoomedHeight = baseHeight * currentZoom; - - const dimensionsHTML = ` - Con Zoom ${Math.round(currentZoom * 100)}%:
- Larghezza: ${Math.round(zoomedWidth)} px
- Altezza: ${Math.round(zoomedHeight)} px
-
- Bordo: ${currentBorderWidth}px (${borderPosition})
- `; - - currentDimensions.innerHTML = dimensionsHTML; - dimensionsInfo.style.display = 'block'; - } else { - dimensionsInfo.style.display = 'none'; - } -} - -// ============================================================================ -// BUTTON STATES -// ============================================================================ - -function enableControls() { - zoomInBtn.disabled = false; - zoomOutBtn.disabled = false; - resetBtn.disabled = false; - confirmBtn.disabled = false; -} - -function disableControls() { - zoomInBtn.disabled = true; - zoomOutBtn.disabled = true; - resetBtn.disabled = true; - confirmBtn.disabled = true; -} - -// ============================================================================ -// COORDINATE EXPORT -// ============================================================================ - function getCoordinates() { - if (!rectangleExists) { - return {}; - } + if (!rectangleExists) return {}; - return { - x: Math.round(baseX), - y: Math.round(baseY), - width: Math.round(baseWidth), - height: Math.round(baseHeight), - zoom: currentZoom, - borderWidth: currentBorderWidth, - borderPosition: borderPosition, - }; + // 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 coordinates = getCoordinates(); - - if (!rectangleExists) { - alert('Nessuna selezione effettuata'); - return; - } - - // Invia coordinate al callback - if (onConfirmCallback) { - onConfirmCallback(coordinates); - } - - // Log per debug - console.log('Coordinates confirmed:', coordinates); + const coords = getCoordinates(); + if (onConfirmCallback) onConfirmCallback(coords); + console.log("[RegionSelector] Coordinates confirmed"); } function handleCancel() { - if (onCancelCallback) { - onCancelCallback(); - } - // Non chiudere la finestra - il callback si occuperà di chiudere il modal + if (onCancelCallback) onCancelCallback(); } // ============================================================================ -// PUBLIC API (per integrazione ComfyUI) +// FIX IMAGE SIZE - FUNZIONI // ============================================================================ -window.RegionSelector = { - /** - * Inizializza il selettore di regioni con callback personalizzati - * @param {Function} onConfirm - Callback quando l'utente conferma (riceve coordinates) - * @param {Function} onCancel - Callback quando l'utente annulla - */ - init: function(onConfirm, onCancel) { - onConfirmCallback = onConfirm; - onCancelCallback = onCancel; - }, +/** + * 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; + } - /** - * Ottieni le coordinate attuali della selezione - * @returns {Object} Oggetto con x, y, width, height, zoom, borderWidth, borderPosition - */ - getCoordinates: function() { - return getCoordinates(); - }, + // Trova dove mettere il bottone: dopo aspect ratio hint + const aspectRatioHint = document.getElementById('aspect-ratio-hint'); - /** - * Imposta un'immagine da URL - * @param {String} imageUrl - URL dell'immagine - */ - setImage: function(imageUrl) { - if (currentRectangle) { - if (confirm('Cambiando immagine il rettangolo verrà cancellato. Continuare?')) { - resetRectangle(); - } else { - return; - } - } - backgroundImage.src = imageUrl; - imageName.textContent = 'Immagine caricata'; - }, + if (!aspectRatioHint) { + console.error('[FixImage] Could not find aspect-ratio-hint'); + return; + } - /** - * Carica una selezione precedente - * @param {Object} coordinates - Oggetto coordinate - */ - loadCoordinates: function(coordinates) { - if (!coordinates || !coordinates.x) return; + // Crea il nuovo control group per il bottone + const newControlGroup = document.createElement('div'); + newControlGroup.className = 'control-group'; - baseX = coordinates.x || 0; - baseY = coordinates.y || 0; - baseWidth = coordinates.width || 100; - baseHeight = coordinates.height || 100; - currentZoom = coordinates.zoom || 1; - currentBorderWidth = coordinates.borderWidth || 3; - borderPosition = coordinates.borderPosition || 'inside'; - - // Aggiorna UI - borderSlider.value = currentBorderWidth; - borderValue.textContent = currentBorderWidth + 'px'; - document.querySelector(`input[value="${borderPosition}"]`).checked = true; - updateZoom(); - - // Ridisegna - createNewRectangle(baseX, baseY, baseWidth, baseHeight); + // 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() }; diff --git a/web/region_selector_extension.js b/web/region_selector_extension.js index 1e7b1bf..5ff2b42 100644 --- a/web/region_selector_extension.js +++ b/web/region_selector_extension.js @@ -36,6 +36,7 @@ function initializeCanvasSelector(container, imageUrl) { // Variabili di stato let isDrawing = false; let isResizing = false; + let isDragging = false; let resizingEdge = null; let startX = 0; let startY = 0; @@ -43,6 +44,8 @@ function initializeCanvasSelector(container, imageUrl) { let rectStartY = 0; let rectStartWidth = 0; let rectStartHeight = 0; + let dragOffsetX = 0; + let dragOffsetY = 0; let currentRectangle = null; let currentBorderWidth = 0; let borderPosition = 'inside'; @@ -54,13 +57,49 @@ function initializeCanvasSelector(container, imageUrl) { let baseX = 0; let baseY = 0; + // 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" + const defaultBorderWidth = 3; - // Carica l'immagine iniziale + // ======================================================== + // Ridimensionamento immagini > 1024px lato backend + // ======================================================== if (imageUrl) { - backgroundImage.src = imageUrl; const filename = imageUrl.split('/').pop(); - imageName.textContent = `Image: ${filename}`; + + fetch("/region_selector/scale", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ filename }) + }) + .then(res => res.json()) + .then(data => { + if (data.path) { + backgroundImage.src = data.path; + backgroundImage.dataset.scaleFactor = data.scale || 1; + backgroundImage.dataset.scaled = data.scaled || false; + console.log(`[RegionSelectorExt] Immagine caricata - Scale: ${data.scale || 1}`); + } else if (data.error) { + console.warn(`[RegionSelectorExt] Scale error: ${data.error}`); + backgroundImage.src = imageUrl; + backgroundImage.dataset.scaleFactor = 1; + backgroundImage.dataset.scaled = false; + } + imageName.textContent = `Image: ${filename}`; + }) + .catch(e => { + console.error("[RegionSelectorExt] Errore scale:", e); + backgroundImage.src = imageUrl; + backgroundImage.dataset.scaleFactor = 1; + backgroundImage.dataset.scaled = false; + imageName.textContent = `Image: ${filename}`; + }); } // Disabilita drag dell'immagine @@ -81,8 +120,158 @@ function initializeCanvasSelector(container, imageUrl) { } }); - // Mouse down - inizio disegno + // Aspect Ratio Mode Selector + const aspectRatioSelect = container.querySelector('#aspect-ratio-select'); + const aspectRatioHint = container.querySelector('#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(); + } + }); + } + + // ======================================================== + // FIX IMAGE SIZE - BOTTONE DINAMICO + // ======================================================== + function createFixImageButton() { + // Bottone rimosso - la scala viene applicata automaticamente + // Niente da fare qui + console.log('[FixImage] Scale auto-applied, no button needed'); + } + + 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!"); + 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}`); + + backgroundImage.style.width = `${newW}px`; + backgroundImage.style.height = `${newH}px`; + backgroundImage.style.maxWidth = 'none'; + backgroundImage.style.maxHeight = 'none'; + + isImageFixed = true; + const fixImageBtn = container.querySelector('#fix-image-btn'); + if (fixImageBtn) { + fixImageBtn.textContent = "🔄 Reset Scale"; + fixImageBtn.classList.remove('btn-primary'); + fixImageBtn.classList.add('btn-warning'); + } + + const scaleInfo = container.querySelector('#scale-info'); + if (scaleInfo) { + const scalePercent = (displayScaleFactor * 100).toFixed(1); + scaleInfo.innerHTML = ` +

📊 Preview Scale: ${scalePercent}%

+

🖼️ Display Size: ${newW} × ${newH} px

+

📐 Original Size: ${naturalW} × ${naturalH} px

+

✓ Selezione fluida attiva

+ `; + scaleInfo.style.display = 'block'; + } + } + + 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 = container.querySelector('#fix-image-btn'); + if (fixImageBtn) { + fixImageBtn.textContent = "⚡ Fix Image Size"; + fixImageBtn.classList.remove('btn-warning'); + fixImageBtn.classList.add('btn-primary'); + } + + const scaleInfo = container.querySelector('#scale-info'); + if (scaleInfo) { + scaleInfo.style.display = 'none'; + } + } + + // Auto-fixa immagine se è grande (> 1024px) + setTimeout(() => { + const naturalW = backgroundImage.naturalWidth; + const naturalH = backgroundImage.naturalHeight; + const maxDim = Math.max(naturalW, naturalH); + + console.log(`[FixImage] Image size: ${naturalW}x${naturalH}, max: ${maxDim}`); + + if (maxDim > 1024) { + console.log('[FixImage] Large image detected, creating button and auto-fixing scale...'); + createFixImageButton(); + // Applica la scala automaticamente + setTimeout(() => { + fixImageScale(); + }, 100); + } + }, 500); + + // Mouse down - inizio disegno o drag canvasContainer.addEventListener('mousedown', (e) => { + // Se il rettangolo esiste e clicchi su di esso (non su un handle), inizia il drag + if (rectangleExists && e.target === currentRectangle) { + isDragging = true; + const rect = canvasContainer.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + dragOffsetX = mouseX - parseFloat(currentRectangle.style.left); + dragOffsetY = mouseY - parseFloat(currentRectangle.style.top); + canvasContainer.style.cursor = 'grab'; + return; + } + if (rectangleExists) return; if (e.target.classList.contains('resize-handle')) return; @@ -95,6 +284,23 @@ function initializeCanvasSelector(container, imageUrl) { currentRectangle = document.createElement('div'); currentRectangle.className = 'rectangle'; + // Se sei in modalità vincolata, resetta a free (l'utente sta facendo una nuova selezione) + if (aspectRatioMode !== "free") { + const aspectRatioSelect = container.querySelector('#aspect-ratio-select'); + if (aspectRatioSelect) { + aspectRatioSelect.value = "free"; + aspectRatioMode = "free"; + aspectRatioValue = null; + const aspectRatioHint = container.querySelector('#aspect-ratio-hint'); + if (aspectRatioHint) { + aspectRatioHint.textContent = "Disegno libero - il ratio verrà calcolato"; + aspectRatioHint.style.color = "#64748b"; + aspectRatioHint.style.fontWeight = "normal"; + } + console.log("[AspectRatio] Reset to FREE mode for new selection"); + } + } + if (borderPosition === 'outside') { currentRectangle.classList.add('border-outside'); currentRectangle.style.outlineWidth = currentBorderWidth + 'px'; @@ -119,27 +325,69 @@ function initializeCanvasSelector(container, imageUrl) { // Mouse move document.addEventListener('mousemove', (e) => { + // Drag del rettangolo + if (isDragging && currentRectangle) { + const rect = canvasContainer.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + + let newLeft = mouseX - dragOffsetX; + let newTop = mouseY - dragOffsetY; + + // Vincola il rettangolo dentro il canvas + const maxLeft = rect.width - parseFloat(currentRectangle.style.width); + const maxTop = rect.height - parseFloat(currentRectangle.style.height); + + newLeft = Math.max(0, Math.min(newLeft, maxLeft)); + newTop = Math.max(0, Math.min(newTop, maxTop)); + + currentRectangle.style.left = newLeft + 'px'; + currentRectangle.style.top = newTop + 'px'; + + baseX = newLeft; + baseY = newTop; + + updateAllDimensions(); + canvasContainer.style.cursor = 'grabbing'; + return; + } + if (isDrawing) { const rect = canvasContainer.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; - const width = currentX - startX; - const height = currentY - startY; + let width = currentX - startX; + let height = currentY - startY; + + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + // Modalità vincolata: forza l'aspect ratio + if (Math.abs(width) / aspectRatioValue > Math.abs(height)) { + height = (Math.abs(width) / aspectRatioValue) * (height < 0 ? -1 : 1); + } else { + width = (Math.abs(height) * aspectRatioValue) * (width < 0 ? -1 : 1); + } + } + + baseWidth = Math.abs(width); + baseHeight = Math.abs(height); if (width < 0) { - currentRectangle.style.left = currentX + 'px'; + currentRectangle.style.left = (currentX + width) + 'px'; currentRectangle.style.width = Math.abs(width) + 'px'; } else { currentRectangle.style.width = width + 'px'; } if (height < 0) { - currentRectangle.style.top = currentY + 'px'; + currentRectangle.style.top = (currentY + height) + 'px'; currentRectangle.style.height = Math.abs(height) + 'px'; } else { currentRectangle.style.height = height + 'px'; } + + updateAllDimensions(); } if (isResizing && currentRectangle) { @@ -147,79 +395,185 @@ function initializeCanvasSelector(container, imageUrl) { const deltaY = e.clientY - startY; switch(resizingEdge) { - case 'top': - const newTop = rectStartY + deltaY; - const newHeight = rectStartHeight - deltaY; - if (newHeight > 0) { - currentRectangle.style.top = newTop + 'px'; - currentRectangle.style.height = newHeight + 'px'; + case 'bottom-right': + let brNewWidth = rectStartWidth + deltaX; + let brNewHeight = rectStartHeight + deltaY; + + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + if (brNewWidth / aspectRatioValue > brNewHeight) { + brNewHeight = brNewWidth / aspectRatioValue; + } else { + brNewWidth = brNewHeight * aspectRatioValue; + } + } + + if (brNewHeight > 0 && brNewWidth > 0) { + currentRectangle.style.width = brNewWidth + 'px'; + currentRectangle.style.height = brNewHeight + 'px'; + baseWidth = brNewWidth; + baseHeight = brNewHeight; } break; - case 'bottom': - const bottomHeight = rectStartHeight + deltaY; - if (bottomHeight > 0) { - currentRectangle.style.height = bottomHeight + 'px'; + case 'bottom-left': + let blNewLeft = rectStartX + deltaX; + let blNewHeight = rectStartHeight + deltaY; + let blNewWidth = rectStartWidth - deltaX; + + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + if (blNewWidth / aspectRatioValue > blNewHeight) { + blNewHeight = blNewWidth / aspectRatioValue; + } else { + blNewWidth = blNewHeight * aspectRatioValue; + blNewLeft = rectStartX + (rectStartWidth - blNewWidth); + } + } + + if (blNewHeight > 0 && blNewWidth > 0) { + currentRectangle.style.left = blNewLeft + 'px'; + currentRectangle.style.height = blNewHeight + 'px'; + currentRectangle.style.width = blNewWidth + 'px'; + baseX = blNewLeft; + baseWidth = blNewWidth; + baseHeight = blNewHeight; } break; - case 'left': - const newLeft = rectStartX + deltaX; - const newWidth = rectStartWidth - deltaX; - if (newWidth > 0) { - currentRectangle.style.left = newLeft + 'px'; - currentRectangle.style.width = newWidth + 'px'; - } - break; + case 'top-right': + let trNewTop = rectStartY + deltaY; + let trNewHeight = rectStartHeight - deltaY; + let trNewWidth = rectStartWidth + deltaX; - case 'right': - const rightWidth = rectStartWidth + deltaX; - if (rightWidth > 0) { - currentRectangle.style.width = rightWidth + 'px'; + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + if (trNewWidth / aspectRatioValue > trNewHeight) { + trNewHeight = trNewWidth / aspectRatioValue; + trNewTop = rectStartY + (rectStartHeight - trNewHeight); + } else { + trNewWidth = trNewHeight * aspectRatioValue; + } + } + + if (trNewHeight > 0 && trNewWidth > 0) { + currentRectangle.style.top = trNewTop + 'px'; + currentRectangle.style.height = trNewHeight + 'px'; + currentRectangle.style.width = trNewWidth + 'px'; + baseY = trNewTop; + baseWidth = trNewWidth; + baseHeight = trNewHeight; } break; case 'top-left': - const tlNewTop = rectStartY + deltaY; - const tlNewLeft = rectStartX + deltaX; - const tlNewHeight = rectStartHeight - deltaY; - const tlNewWidth = rectStartWidth - deltaX; + let tlNewTop = rectStartY + deltaY; + let tlNewLeft = rectStartX + deltaX; + let tlNewHeight = rectStartHeight - deltaY; + let tlNewWidth = rectStartWidth - deltaX; + + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + if (tlNewWidth / aspectRatioValue > tlNewHeight) { + tlNewHeight = tlNewWidth / aspectRatioValue; + tlNewTop = rectStartY + (rectStartHeight - tlNewHeight); + } else { + tlNewWidth = tlNewHeight * aspectRatioValue; + tlNewLeft = rectStartX + (rectStartWidth - tlNewWidth); + } + } + if (tlNewHeight > 0 && tlNewWidth > 0) { currentRectangle.style.top = tlNewTop + 'px'; currentRectangle.style.left = tlNewLeft + 'px'; currentRectangle.style.height = tlNewHeight + 'px'; currentRectangle.style.width = tlNewWidth + 'px'; + baseX = tlNewLeft; + baseY = tlNewTop; + baseWidth = tlNewWidth; + baseHeight = tlNewHeight; } break; - case 'top-right': - const trNewTop = rectStartY + deltaY; - const trNewHeight = rectStartHeight - deltaY; - const trNewWidth = rectStartWidth + deltaX; - if (trNewHeight > 0 && trNewWidth > 0) { - currentRectangle.style.top = trNewTop + 'px'; - currentRectangle.style.height = trNewHeight + 'px'; - currentRectangle.style.width = trNewWidth + 'px'; + case 'right': + let rightWidth = rectStartWidth + deltaX; + let rightHeight = rightWidth / aspectRatioValue || rectStartHeight; + + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + rightHeight = rightWidth / aspectRatioValue; + } + + if (rightWidth > 0) { + currentRectangle.style.width = rightWidth + 'px'; + if (aspectRatioValue !== null && rightHeight > 0) { + currentRectangle.style.height = rightHeight + 'px'; + baseWidth = rightWidth; + baseHeight = rightHeight; + } } break; - case 'bottom-left': - const blNewLeft = rectStartX + deltaX; - const blNewHeight = rectStartHeight + deltaY; - const blNewWidth = rectStartWidth - deltaX; - if (blNewHeight > 0 && blNewWidth > 0) { - currentRectangle.style.left = blNewLeft + 'px'; - currentRectangle.style.height = blNewHeight + 'px'; - currentRectangle.style.width = blNewWidth + 'px'; + case 'left': + let newLeft = rectStartX + deltaX; + let newWidth = rectStartWidth - deltaX; + let newHeight = newWidth / aspectRatioValue || rectStartHeight; + + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + newHeight = newWidth / aspectRatioValue; + } + + if (newWidth > 0) { + currentRectangle.style.left = newLeft + 'px'; + currentRectangle.style.width = newWidth + 'px'; + if (aspectRatioValue !== null && newHeight > 0) { + currentRectangle.style.height = newHeight + 'px'; + baseX = newLeft; + baseWidth = newWidth; + baseHeight = newHeight; + } } break; - case 'bottom-right': - const brNewWidth = rectStartWidth + deltaX; - const brNewHeight = rectStartHeight + deltaY; - if (brNewHeight > 0 && brNewWidth > 0) { - currentRectangle.style.width = brNewWidth + 'px'; - currentRectangle.style.height = brNewHeight + 'px'; + case 'bottom': + let bottomHeight = rectStartHeight + deltaY; + let bottomWidth = bottomHeight * aspectRatioValue || rectStartWidth; + + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + bottomWidth = bottomHeight * aspectRatioValue; + } + + if (bottomHeight > 0) { + currentRectangle.style.height = bottomHeight + 'px'; + if (aspectRatioValue !== null && bottomWidth > 0) { + currentRectangle.style.width = bottomWidth + 'px'; + baseWidth = bottomWidth; + baseHeight = bottomHeight; + } + } + break; + + case 'top': + let newTop = rectStartY + deltaY; + let topNewHeight = rectStartHeight - deltaY; + let topNewWidth = topNewHeight * aspectRatioValue || rectStartWidth; + + // ⚙️ APPLICA VINCOLO SE NECESSARIO + if (aspectRatioValue !== null) { + topNewWidth = topNewHeight * aspectRatioValue; + } + + if (topNewHeight > 0) { + currentRectangle.style.top = newTop + 'px'; + currentRectangle.style.height = topNewHeight + 'px'; + if (aspectRatioValue !== null && topNewWidth > 0) { + currentRectangle.style.width = topNewWidth + 'px'; + baseY = newTop; + baseWidth = topNewWidth; + baseHeight = topNewHeight; + } } break; } @@ -271,6 +625,11 @@ function initializeCanvasSelector(container, imageUrl) { resizingEdge = null; canvasContainer.style.cursor = 'default'; } + + if (isDragging) { + isDragging = false; + canvasContainer.style.cursor = 'default'; + } }); function resetRectangle() { @@ -286,6 +645,21 @@ function initializeCanvasSelector(container, imageUrl) { baseCoordinates.innerHTML = '

Click and drag to select

'; } + function adjustRectangleToAspectRatio() { + if (!aspectRatioValue || !rectangleExists || !currentRectangle) return; + + // Mantieni larghezza, adatta altezza al ratio + baseHeight = baseWidth / aspectRatioValue; + + // Aggiorna visualizzazione + currentRectangle.style.width = baseWidth + 'px'; + currentRectangle.style.height = baseHeight + 'px'; + + updateAllDimensions(); + + console.log(`[AspectRatio] Adjusted to ${aspectRatioMode}: ${Math.round(baseWidth)}x${Math.round(baseHeight)}`); + } + function addResizeHandles() { const handles = ['top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right']; handles.forEach(position => { @@ -319,19 +693,75 @@ function initializeCanvasSelector(container, imageUrl) { const baseX2 = baseX + baseWidth; const baseY2 = baseY + baseHeight; + 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)`; + } + baseCoordinates.innerHTML = ` 📍 Coordinates:
x1 = ${Math.round(baseX1)}px, y1 = ${Math.round(baseY1)}px
x2 = ${Math.round(baseX2)}px, y2 = ${Math.round(baseY2)}px

📏 Size:
- Width: ${Math.round(baseWidth)}px
- Height: ${Math.round(baseHeight)}px + Width: ${w}px
+ Height: ${h}px
+
+ ✨ Aspect Ratio: ${aspectRatioDisplay} `; } return { getCoordinates: () => { + // Leggi il fattore di scala salvato dal backend + const scaleFactor = parseFloat(backgroundImage.dataset.scaleFactor || "1"); + const baseX1 = baseX; const baseY1 = baseY; const baseX2 = baseX + baseWidth; @@ -361,6 +791,7 @@ function initializeCanvasSelector(container, imageUrl) { y2: Math.round(effectiveY2), borderWidth: currentBorderWidth, borderPosition: borderPosition, + displayScaleFactor: displayScaleFactor, }; }, getState: () => ({ @@ -621,6 +1052,31 @@ async function openRegionDialog(node, app) { Image: ${imageName}
+ +
+ + + Disegno libero - il ratio verrà calcolato +