diff --git a/README.md b/README.md index 5ccd5e3..85d4809 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,19 @@ This is an improved fork of the original [BoxBox](https://github.com/mercu-lore/ - Now works with processing nodes (Brightness, Blur, etc.) between LoadImage and BoxSelector - Searches up to 20 levels deep in the node graph +5. **NumPy 2.0 Compatibility** + - Updated backend logic with explicit casting (`np.uint8`, `np.float32`) + - Fixed binary incompatibility issues in coordinate processing + +6. **ComfyUI v0.12.2+ (Modern API) Support** + - Re-engineered frontend to use the modern `window.comfyAPI` system + - Eliminated "Legacy API" deprecation warnings + - Improved dialog instantiation with robust fallback mechanisms + +7. **Adaptive Window Scaling** + - Fixed coordinate misalignment when the browser window is resized + - System now dynamically calculates total scale (Server Scale * Browser CSS Scale) + ### 🆕 New Features 1. **Aspect Ratio Memory** diff --git a/box_reinsert_node.py b/box_reinsert_node.py index fecf8ad..34b5bc0 100644 --- a/box_reinsert_node.py +++ b/box_reinsert_node.py @@ -107,38 +107,31 @@ class BoxReinsertNode: crop_width = box_x_end - box_x_start crop_height = box_y_end - box_y_start - # Converti immagini a PIL - original_np = (original_image[0].cpu().numpy() * 255).astype(np.uint8) + # Converti immagini a PIL con casting esplicito + # original_image es (B, H, W, C) + original_np = (original_image[0].detach().cpu().numpy() * 255).round().astype(np.uint8) original_pil = Image.fromarray(original_np) - generated_np = (generated_image[0].cpu().numpy() * 255).astype(np.uint8) + generated_np = (generated_image[0].detach().cpu().numpy() * 255).round().astype(np.uint8) generated_pil = Image.fromarray(generated_np) print(f"[BoxReinsertNode] Generated image size: {generated_pil.size}") print(f"[BoxReinsertNode] Target crop size: {crop_width}x{crop_height}") print(f"[BoxReinsertNode] Use resize: {use_resize}") - # Step 1: Ridimensiona solo se resize_metadata è fornito + # Step 1: Ridimensiona if use_resize: - # Annulla il resize fatto da BoxResize - print(f"[BoxReinsertNode] Resizing generated image back to crop dimensions...") generated_resized = generated_pil.resize((crop_width, crop_height), Image.Resampling.LANCZOS) - print(f"[BoxReinsertNode] Generated image resized from {generated_pil.size} to {generated_resized.size}") else: - # Bypassa il resize e usa direttamente l'immagine generata generated_resized = generated_pil - print(f"[BoxReinsertNode] Bypassing resize, using generated image as-is") # Step 2: Rimetti nel punto originale - # Crea una copia dell'immagine originale final_image = original_pil.copy() - - # Incolla l'immagine generata nel punto corretto final_image.paste(generated_resized, (box_x_start, box_y_start)) - # Converti back a tensor - final_np = np.array(final_image).astype(np.float32) / 255.0 - final_tensor = torch.from_numpy(final_np).unsqueeze(0) + # Converti back a tensor con casting sicuro per NumPy 2.0 + final_np_array = np.array(final_image, dtype=np.float32) / 255.0 + final_tensor = torch.from_numpy(final_np_array).unsqueeze(0) # Assicurati che il tensor abbia le giuste dimensioni channels = original_image.shape[3] diff --git a/region_selector_node.py b/region_selector_node.py index 3caaede..bfe868f 100644 --- a/region_selector_node.py +++ b/region_selector_node.py @@ -38,16 +38,31 @@ class RegionSelectorNode: return (image, self.last_metadata) -def scale_image_if_needed(filename, max_size=1024): - input_path = folder_paths.get_input_directory() - full_path = os.path.join(input_path, filename) +def scale_image_if_needed(filename, type="input", subfolder="", max_size=1024): + if type == "output": + base_dir = folder_paths.get_output_directory() + elif type == "temp": + base_dir = folder_paths.get_temp_directory() + else: + base_dir = folder_paths.get_input_directory() + + if subfolder: + full_path = os.path.join(base_dir, subfolder, filename) + else: + full_path = os.path.join(base_dir, filename) + if not os.path.exists(full_path): - return {"error": "file not found"} + return {"error": f"file not found: {full_path}"} with Image.open(full_path) as im: w, h = im.size + # Use comma as separator for filename, type, subfolder to assume default behaviour or backward compatibility + path_params = f"filename={filename}&type={type}" + if subfolder: + path_params += f"&subfolder={subfolder}" + if w <= max_size and h <= max_size: - return {"scaled": False, "path": f"/view?filename={filename}&type=input"} + return {"scaled": False, "path": f"/view?{path_params}"} scale_factor = min(max_size / w, max_size / h) new_size = (int(w * scale_factor), int(h * scale_factor)) @@ -59,6 +74,9 @@ def scale_image_if_needed(filename, max_size=1024): # Sanitize filename: replace path separators with underscores safe_filename = filename.replace('/', '_').replace('\\', '_') + if subfolder: + safe_filename = f"{subfolder}_{safe_filename}".replace('/', '_').replace('\\', '_') + scaled_name = f"scaled_{safe_filename}" scaled_path = os.path.join(temp_dir, scaled_name) im_resized.save(scaled_path) @@ -71,15 +89,26 @@ def scale_image_if_needed(filename, max_size=1024): async def scale_image_endpoint(request): from aiohttp import web try: - data = await request.json() + # Intentar leer el JSON de forma segura + try: + data = await request.json() + except: + # Fallback si el content-type no es exacto o el cuerpo está malformado + post_data = await request.post() + data = dict(post_data) + filename = data.get("filename") + type_ = data.get("type", "input") + subfolder = data.get("subfolder", "") + if not filename: - return web.json_response({"error": "filename missing"}) - result = scale_image_if_needed(filename) + return web.json_response({"error": "filename missing"}, status=400) + + result = scale_image_if_needed(filename, type_, subfolder) return web.json_response(result) except Exception as e: - print(f"[RegionSelector] Error in scale endpoint: {e}") - return web.json_response({"error": str(e)}) + print(f"[BoxBox] Error in scale endpoint: {e}") + return web.json_response({"error": str(e)}, status=500) NODE_CLASS_MAPPINGS = {"BoxSelector": RegionSelectorNode} diff --git a/web/canvas_selector.js b/web/canvas_selector.js index f148ab0..3ba38cd 100644 --- a/web/canvas_selector.js +++ b/web/canvas_selector.js @@ -1,516 +1,5 @@ /** - * Canvas Selector - Funzionalità complete di selezione rettangoli - * Basato sul codice di canva_html + * Canvas Selector - Placeholder + * Functionality moved to region_selector_extension.js to avoid duplicate code and synchronization issues. */ - -function initializeCanvasSelector(container, imageUrl) { - console.log("[CanvasSelector] Initializing with image:", imageUrl); - - // Riferimenti agli elementi DOM (cercati dentro il container) - const canvasContainer = container.querySelector('#canvas-container'); - const backgroundImage = container.querySelector('#background-image'); - const baseCoordinates = container.querySelector('#base-coordinates'); - const dimensionsInfo = container.querySelector('#dimensions-info'); - const currentDimensions = container.querySelector('#current-dimensions'); - const zoomInBtn = container.querySelector('#zoom-in-btn'); - const zoomOutBtn = container.querySelector('#zoom-out-btn'); - const zoomValue = container.querySelector('#zoom-value'); - const borderSlider = container.querySelector('#border-slider'); - const borderValue = container.querySelector('#border-value'); - const resetBtn = container.querySelector('#reset-btn'); - const borderPositionRadios = container.querySelectorAll('input[name="border-position"]'); - const imageUpload = container.querySelector('#image-upload'); - const uploadBtn = container.querySelector('#upload-btn'); - const imageName = container.querySelector('#image-name'); - - // Variabili di stato - 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 = 0; - let borderPosition = 'inside'; - let rectangleExists = false; - - // Dimensioni base - 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; - - // Carica l'immagine iniziale - if (imageUrl) { - backgroundImage.src = imageUrl; - const filename = imageUrl.split('/').pop(); - imageName.textContent = `Immagine: ${filename}`; - } - - // ========== EVENT LISTENERS ========== - - // Upload button - uploadBtn.addEventListener('click', () => imageUpload.click()); - - imageUpload.addEventListener('change', (e) => { - const file = e.target.files[0]; - if (file && file.type.startsWith('image/')) { - const reader = new FileReader(); - reader.onload = (event) => { - backgroundImage.src = event.target.result; - imageName.textContent = `Immagine: ${file.name}`; - if (currentRectangle && confirm('Cambiando immagine il rettangolo verrà cancellato. Continuare?')) { - resetRectangle(); - } - }; - reader.readAsDataURL(file); - } - }); - - // Zoom buttons - zoomInBtn.addEventListener('click', () => { - if (currentRectangle) { - currentZoom = Math.min(currentZoom + zoomStep, maxZoom); - updateZoom(); - updateAllDimensions(); - } - }); - - zoomOutBtn.addEventListener('click', () => { - if (currentRectangle) { - currentZoom = Math.max(currentZoom - zoomStep, minZoom); - updateZoom(); - updateAllDimensions(); - } - }); - - // Border slider - borderSlider.addEventListener('input', (e) => { - currentBorderWidth = parseInt(e.target.value); - borderValue.textContent = currentBorderWidth + 'px'; - - if (currentRectangle) { - if (currentRectangle.classList.contains('border-outside')) { - currentRectangle.style.outlineWidth = currentBorderWidth + 'px'; - } else { - currentRectangle.style.borderWidth = currentBorderWidth + 'px'; - } - - if (currentBorderWidth > defaultBorderWidth) { - currentRectangle.classList.add('thick-border'); - } else { - currentRectangle.classList.remove('thick-border'); - } - - updateAllDimensions(); - } - }); - - // Border position radios - borderPositionRadios.forEach(radio => { - radio.addEventListener('change', (e) => { - borderPosition = e.target.value; - - if (currentRectangle) { - if (borderPosition === 'outside') { - currentRectangle.classList.remove('border-inside'); - currentRectangle.classList.add('border-outside'); - currentRectangle.style.outlineWidth = currentBorderWidth + 'px'; - currentRectangle.style.borderWidth = '0px'; - } else { - currentRectangle.classList.remove('border-outside'); - currentRectangle.classList.add('border-inside'); - currentRectangle.style.borderWidth = currentBorderWidth + 'px'; - currentRectangle.style.outlineWidth = '0px'; - } - - updateAllDimensions(); - } - }); - }); - - // Reset button - resetBtn.addEventListener('click', () => { - if (confirm('Vuoi cancellare il rettangolo e disegnarne uno nuovo?')) { - resetRectangle(); - } - }); - - // Mouse down - inizio disegno - canvasContainer.addEventListener('mousedown', (e) => { - if (rectangleExists) return; - if (e.target.classList.contains('resize-handle')) return; - - const rect = canvasContainer.getBoundingClientRect(); - startX = e.clientX - rect.left; - startY = e.clientY - rect.top; - - isDrawing = true; - - currentRectangle = document.createElement('div'); - currentRectangle.className = 'rectangle'; - - if (borderPosition === 'outside') { - currentRectangle.classList.add('border-outside'); - currentRectangle.style.outlineWidth = currentBorderWidth + 'px'; - currentRectangle.style.outlineStyle = 'solid'; - currentRectangle.style.borderWidth = '0px'; - } else { - currentRectangle.classList.add('border-inside'); - currentRectangle.style.borderWidth = currentBorderWidth + 'px'; - } - - currentRectangle.style.left = startX + 'px'; - currentRectangle.style.top = startY + 'px'; - currentRectangle.style.width = '0px'; - currentRectangle.style.height = '0px'; - - if (currentBorderWidth > defaultBorderWidth) { - currentRectangle.classList.add('thick-border'); - } - - canvasContainer.appendChild(currentRectangle); - }); - - // Mouse move - document.addEventListener('mousemove', (e) => { - 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; - - if (width < 0) { - currentRectangle.style.left = currentX + 'px'; - currentRectangle.style.width = Math.abs(width) + 'px'; - } else { - currentRectangle.style.width = width + 'px'; - } - - if (height < 0) { - currentRectangle.style.top = currentY + 'px'; - currentRectangle.style.height = Math.abs(height) + 'px'; - } else { - currentRectangle.style.height = height + 'px'; - } - } - - if (isResizing && currentRectangle) { - const deltaX = e.clientX - startX; - 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'; - } - break; - - case 'bottom': - const bottomHeight = rectStartHeight + deltaY; - if (bottomHeight > 0) { - currentRectangle.style.height = bottomHeight + 'px'; - } - 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 'right': - const rightWidth = rectStartWidth + deltaX; - if (rightWidth > 0) { - currentRectangle.style.width = rightWidth + 'px'; - } - break; - - case 'top-left': - const tlNewTop = rectStartY + deltaY; - const tlNewLeft = rectStartX + deltaX; - const tlNewHeight = rectStartHeight - deltaY; - const tlNewWidth = rectStartWidth - deltaX; - 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'; - } - 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'; - } - 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'; - } - 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'; - } - break; - } - - baseX = parseFloat(currentRectangle.style.left); - baseY = parseFloat(currentRectangle.style.top); - baseWidth = parseFloat(currentRectangle.style.width); - baseHeight = parseFloat(currentRectangle.style.height); - - updateAllDimensions(); - } - }); - - // Mouse up - document.addEventListener('mouseup', (e) => { - if (isDrawing) { - const rect = canvasContainer.getBoundingClientRect(); - const endX = e.clientX - rect.left; - const endY = e.clientY - rect.top; - - const x1 = Math.min(startX, endX); - const y1 = Math.min(startY, endY); - const x2 = Math.max(startX, endX); - const y2 = Math.max(startY, endY); - - baseX = x1; - baseY = y1; - baseWidth = x2 - x1; - baseHeight = y2 - y1; - - rectangleExists = true; - currentRectangle.classList.add('complete'); - - canvasContainer.classList.add('drawing-disabled'); - canvasContainer.style.cursor = 'default'; - - resetBtn.disabled = false; - zoomInBtn.disabled = currentZoom >= maxZoom; - zoomOutBtn.disabled = currentZoom <= minZoom; - - dimensionsInfo.style.display = 'block'; - - addResizeHandles(); - updateAllDimensions(); - - isDrawing = false; - } - - if (isResizing) { - isResizing = false; - resizingEdge = null; - canvasContainer.style.cursor = 'default'; - } - }); - - // ========== FUNZIONI HELPER ========== - - function resetRectangle() { - if (currentRectangle) { - currentRectangle.remove(); - } - currentRectangle = null; - rectangleExists = false; - currentZoom = 1; - updateZoom(); - canvasContainer.classList.remove('drawing-disabled'); - canvasContainer.style.cursor = 'crosshair'; - resetBtn.disabled = true; - zoomInBtn.disabled = true; - zoomOutBtn.disabled = true; - dimensionsInfo.style.display = 'none'; - baseCoordinates.innerHTML = '
Clicca e trascina sull\'immagine per disegnare un rettangolo
'; - } - - 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; - } - - function addResizeHandles() { - const handles = ['top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right']; - handles.forEach(position => { - const handle = document.createElement('div'); - handle.className = `resize-handle ${position}`; - handle.dataset.edge = position; - currentRectangle.appendChild(handle); - - handle.addEventListener('mousedown', (e) => { - e.stopPropagation(); - isResizing = true; - resizingEdge = position; - startX = e.clientX; - startY = e.clientY; - - rectStartX = parseFloat(currentRectangle.style.left); - rectStartY = parseFloat(currentRectangle.style.top); - rectStartWidth = parseFloat(currentRectangle.style.width); - rectStartHeight = parseFloat(currentRectangle.style.height); - - canvasContainer.style.cursor = getComputedStyle(handle).cursor; - }); - }); - } - - function updateAllDimensions() { - if (!currentRectangle) return; - - const baseX1 = baseX; - const baseY1 = baseY; - const baseX2 = baseX + baseWidth; - const baseY2 = baseY + baseHeight; - - const centerX = baseX + (baseWidth / 2); - const centerY = baseY + (baseHeight / 2); - - const zoomedWidth = baseWidth * currentZoom; - const zoomedHeight = baseHeight * currentZoom; - - const zoomedX1 = centerX - (zoomedWidth / 2); - const zoomedY1 = centerY - (zoomedHeight / 2); - const zoomedX2 = centerX + (zoomedWidth / 2); - const zoomedY2 = centerY + (zoomedHeight / 2); - - let effectiveX1, effectiveY1, effectiveX2, effectiveY2; - let effectiveWidth, effectiveHeight; - - if (borderPosition === 'inside') { - effectiveX1 = zoomedX1; - effectiveY1 = zoomedY1; - effectiveX2 = zoomedX2; - effectiveY2 = zoomedY2; - effectiveWidth = zoomedWidth; - effectiveHeight = zoomedHeight; - } else { - effectiveX1 = zoomedX1 - currentBorderWidth; - effectiveY1 = zoomedY1 - currentBorderWidth; - effectiveX2 = zoomedX2 + currentBorderWidth; - effectiveY2 = zoomedY2 + currentBorderWidth; - effectiveWidth = zoomedWidth + (currentBorderWidth * 2); - effectiveHeight = zoomedHeight + (currentBorderWidth * 2); - } - - baseCoordinates.innerHTML = ` - 📍 Coordinate Base: