116 lines
4.2 KiB
Python
116 lines
4.2 KiB
Python
"""
|
|
BoxSelector Node - Interactive region selection with auto-scaling for large images.
|
|
Automatically scales images > 1024px in preview for smooth selection.
|
|
Outputs box_metadata with coordinates and displayScaleFactor.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
from PIL import Image
|
|
import folder_paths
|
|
import server
|
|
|
|
|
|
class RegionSelectorNode:
|
|
def __init__(self):
|
|
self.last_metadata = json.dumps({
|
|
"x1": 0, "y1": 0, "x2": 0, "y2": 0,
|
|
"zoom": 1, "borderWidth": 0,
|
|
"borderPosition": "inside", "selected": False
|
|
})
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {"image": ("IMAGE",)},
|
|
"optional": {"box_metadata": ("STRING", {"default": "", "multiline": True})}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", "STRING")
|
|
RETURN_NAMES = ("image", "box_metadata")
|
|
FUNCTION = "process_region_selection"
|
|
CATEGORY = "image/region"
|
|
OUTPUT_NODE = True
|
|
|
|
def process_region_selection(self, image, box_metadata="{}"):
|
|
if box_metadata.strip() and box_metadata != "{}":
|
|
self.last_metadata = box_metadata
|
|
return (image, self.last_metadata)
|
|
|
|
|
|
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": 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?{path_params}"}
|
|
|
|
scale_factor = min(max_size / w, max_size / h)
|
|
new_size = (int(w * scale_factor), int(h * scale_factor))
|
|
im_resized = im.resize(new_size, Image.Resampling.LANCZOS)
|
|
|
|
# Use ComfyUI's temp folder
|
|
temp_dir = folder_paths.get_temp_directory()
|
|
os.makedirs(temp_dir, exist_ok=True)
|
|
|
|
# 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)
|
|
print(f"[RegionSelector] Image scaled {w}x{h} -> {new_size[0]}x{new_size[1]}")
|
|
|
|
return {"scaled": True, "path": f"/view?filename={scaled_name}&type=temp", "scale": scale_factor}
|
|
|
|
|
|
@server.PromptServer.instance.routes.post("/region_selector/scale")
|
|
async def scale_image_endpoint(request):
|
|
from aiohttp import web
|
|
try:
|
|
# 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"}, status=400)
|
|
|
|
result = scale_image_if_needed(filename, type_, subfolder)
|
|
return web.json_response(result)
|
|
except Exception as e:
|
|
print(f"[BoxBox] Error in scale endpoint: {e}")
|
|
return web.json_response({"error": str(e)}, status=500)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {"BoxSelector": RegionSelectorNode}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {"BoxSelector": "📦 BoxSelector"}
|