From 90b2ceb4fbfafe98eeded38c2ff0b871a69d5ebf Mon Sep 17 00:00:00 2001 From: charlyad142 Date: Fri, 1 Aug 2025 16:21:06 -0400 Subject: [PATCH] first commit --- .gitignore | 55 +++++++++++++++ README.md | 64 +++++++++++++++++ __init__.py | 3 + charly_nodes.py | 178 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 300 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 __init__.py create mode 100644 charly_nodes.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f396140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Entornos virtuales +venv/ +env/ +ENV/ + +# IDEs y editores +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store + +# Archivos de configuración local +.env +.env.local +config.json +config.ini + +# Archivos de caché y logs +*.log +.cache/ +.pytest_cache/ + +# Archivos temporales +*.tmp +*.temp +.coverage +htmlcov/ + +# Archivos específicos de ComfyUI +models/ +outputs/ +temp/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8cc14a --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# ComfyUI Charly FitToAspectNode + +Un nodo personalizado para ComfyUI que ajusta imágenes a diferentes relaciones de aspecto manteniendo las proporciones originales. + +## Características + +- **Ajuste proporcional**: Redimensiona la imagen solo si es más grande que el objetivo, manteniendo las proporciones originales +- **Padding inteligente**: Calcula automáticamente el padding necesario para centrar la imagen en el tamaño objetivo +- **Múltiples relaciones de aspecto**: Soporta 11 relaciones de aspecto diferentes +- **Fondo negro**: Rellena las áreas vacías con color negro + +## Relaciones de Aspecto Soportadas + +| Relación | Resolución Objetivo | +|----------|-------------------| +| 1:1 | 1024 x 1024 | +| 16:9 | 1392 x 752 | +| 21:9 | 1568 x 672 | +| 3:2 | 1248 x 832 | +| 2:3 | 832 x 1248 | +| 4:5 | 944 x 1104 | +| 5:4 | 1104 x 944 | +| 3:4 | 880 x 1184 | +| 4:3 | 1184 x 880 | +| 9:16 | 752 x 1392 | +| 9:21 | 672 x 1568 | + +## Uso + +### Entradas +- **image**: Imagen de entrada (formato IMAGE) +- **aspect_ratio**: Relación de aspecto deseada (seleccionable desde la lista) + +### Salidas +- **image**: Imagen ajustada al tamaño objetivo con padding +- **pad_top**: Padding superior en píxeles +- **pad_bottom**: Padding inferior en píxeles +- **pad_left**: Padding izquierdo en píxeles +- **pad_right**: Padding derecho en píxeles + +## Ejemplo de Uso + +1. Conecta una imagen al nodo "Fit To Aspect Node" +2. Selecciona la relación de aspecto deseada +3. El nodo devolverá: + - La imagen ajustada al tamaño objetivo + - Los valores de padding aplicados en cada borde + +## Categoría + +El nodo se encuentra en la categoría **"image/resize"** en ComfyUI. + +## Instalación + +1. Copia esta carpeta a `ComfyUI/custom_nodes/` +2. Reinicia ComfyUI +3. El nodo aparecerá en la categoría "image/resize" + +## Requisitos + +- ComfyUI +- PyTorch +- NumPy +- Pillow (PIL) diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..7c990ba --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .charly_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/charly_nodes.py b/charly_nodes.py new file mode 100644 index 0000000..e6f5834 --- /dev/null +++ b/charly_nodes.py @@ -0,0 +1,178 @@ +import torch +import numpy as np +from PIL import Image +import io + +class Charly_FitToAspectNode: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "aspect_ratio": ("STRING", {"default": "16:9", "multiline": False}), + "expansion_direction": ("STRING", {"default": "center", "multiline": False}), + } + } + + RETURN_TYPES = ("IMAGE", "INT", "INT", "INT", "INT") + RETURN_NAMES = ("image", "pad_top", "pad_bottom", "pad_left", "pad_right") + FUNCTION = "fit_to_aspect" + CATEGORY = "image/resize" + + def fit_to_aspect(self, image, aspect_ratio, expansion_direction): + # Definir las resoluciones objetivo para cada relación de aspecto predefinida + aspect_resolutions = { + "1:1": (1024, 1024), + "16:9": (1392, 752), + "21:9": (1568, 672), + "3:2": (1248, 832), + "2:3": (832, 1248), + "4:5": (944, 1104), + "5:4": (1104, 944), + "3:4": (880, 1184), + "4:3": (1184, 880), + "9:16": (752, 1392), + "9:21": (672, 1568), + } + + # Procesar el aspect_ratio (puede ser predefinido o personalizado) + aspect_ratio = aspect_ratio.strip() + if aspect_ratio in aspect_resolutions: + target_width, target_height = aspect_resolutions[aspect_ratio] + else: + # Intentar parsear formato personalizado como "width:height" o "widthxheight" + try: + if ":" in aspect_ratio: + parts = aspect_ratio.split(":") + target_width = int(parts[0]) + target_height = int(parts[1]) + elif "x" in aspect_ratio.lower(): + parts = aspect_ratio.lower().split("x") + target_width = int(parts[0]) + target_height = int(parts[1]) + else: + # Si no se puede parsear, usar 16:9 por defecto + target_width, target_height = aspect_resolutions["16:9"] + except: + # Si hay error en el parsing, usar 16:9 por defecto + target_width, target_height = aspect_resolutions["16:9"] + + # Obtener las dimensiones de la imagen de entrada + # Las imágenes en ComfyUI vienen como tensor con formato (batch, height, width, channels) + if len(image.shape) == 4: + # Si hay batch, tomar la primera imagen + img_tensor = image[0] + else: + img_tensor = image + + current_height, current_width = img_tensor.shape[:2] + + # Calcular el factor de escala para mantener proporciones + # Escalar la imagen para que quepa dentro del aspect ratio objetivo + scale_x = target_width / current_width + scale_y = target_height / current_height + + # Usar el factor de escala más pequeño para mantener proporciones + # y asegurar que la imagen quepa completamente en el objetivo + scale_factor = min(scale_x, scale_y) + + # Si la imagen ya es más pequeña que el objetivo, no escalar + if scale_factor > 1: + scale_factor = 1 + new_width = current_width + new_height = current_height + else: + # Calcular las nuevas dimensiones de la imagen escalada + new_width = int(current_width * scale_factor) + new_height = int(current_height * scale_factor) + + # Convertir tensor a PIL Image + img_array = (img_tensor.numpy() * 255).astype(np.uint8) + pil_image = Image.fromarray(img_array) + + # Redimensionar la imagen solo si es necesario + if scale_factor != 1: + resized_image = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS) + else: + resized_image = pil_image + + # Calcular el padding total necesario + total_pad_width = target_width - new_width + total_pad_height = target_height - new_height + + # Normalizar la dirección de expansión + expansion_direction = expansion_direction.strip().lower() + + # Aplicar la dirección de expansión preferida + if expansion_direction == "center": + # Centrar la imagen + pad_left = total_pad_width // 2 + pad_right = total_pad_width - pad_left + pad_top = total_pad_height // 2 + pad_bottom = total_pad_height - pad_top + elif expansion_direction == "top": + # Expandir hacia arriba + pad_left = total_pad_width // 2 + pad_right = total_pad_width - pad_left + pad_top = total_pad_height + pad_bottom = 0 + elif expansion_direction == "bottom": + # Expandir hacia abajo + pad_left = total_pad_width // 2 + pad_right = total_pad_width - pad_left + pad_top = 0 + pad_bottom = total_pad_height + elif expansion_direction == "left": + # Expandir hacia la izquierda + pad_left = total_pad_width + pad_right = 0 + pad_top = total_pad_height // 2 + pad_bottom = total_pad_height - pad_top + elif expansion_direction == "right": + # Expandir hacia la derecha + pad_left = 0 + pad_right = total_pad_width + pad_top = total_pad_height // 2 + pad_bottom = total_pad_height - pad_top + elif expansion_direction == "top_left": + # Expandir hacia arriba e izquierda + pad_left = total_pad_width + pad_right = 0 + pad_top = total_pad_height + pad_bottom = 0 + elif expansion_direction == "top_right": + # Expandir hacia arriba y derecha + pad_left = 0 + pad_right = total_pad_width + pad_top = total_pad_height + pad_bottom = 0 + elif expansion_direction == "bottom_left": + # Expandir hacia abajo e izquierda + pad_left = total_pad_width + pad_right = 0 + pad_top = 0 + pad_bottom = total_pad_height + elif expansion_direction == "bottom_right": + # Expandir hacia abajo y derecha + pad_left = 0 + pad_right = total_pad_width + pad_top = 0 + pad_bottom = total_pad_height + + # Convertir la imagen redimensionada de vuelta a tensor + result_array = np.array(resized_image).astype(np.float32) / 255.0 + result_tensor = torch.from_numpy(result_array) + + # Agregar dimensión de batch si es necesario + if len(image.shape) == 4: + result_tensor = result_tensor.unsqueeze(0) + + return (result_tensor, pad_top, pad_bottom, pad_left, pad_right) + +NODE_CLASS_MAPPINGS = { + "Charly FitToAspectNode": Charly_FitToAspectNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "Charly FitToAspectNode": "Fit To Aspect Node", +} \ No newline at end of file