Files
orion4d-illusion_node/TileImageRepeaterNode.py
2025-06-22 10:41:52 +02:00

131 lines
6.9 KiB
Python

import numpy as np
import torch
from PIL import Image
class TileImageRepeaterNode:
RESIZE_MODES = ["None", "Width", "Height", "Shortest Side", "Longest Side"] # Ajout de None, et de Shortest/Longest Side
RESAMPLING_FILTERS = ["lanczos", "bicubic", "bilinear", "nearest"]
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"horizontal_repeats": ("INT", {"default": 3, "min": 1, "max": 32, "step": 1}),
"vertical_repeats": ("INT", {"default": 3, "min": 1, "max": 32, "step": 1}),
"resize_mode": (cls.RESIZE_MODES, {"default": "None"}),
"tile_target_size": ("INT", {"default": 256, "min": 0, "max": 8192, "step": 8, "tooltip": "Target size for the chosen dimension (Width, Height, Shortest/Longest Side). 0 or 'None' mode to disable resize."}),
"resampling_filter": (cls.RESAMPLING_FILTERS, {"default": "lanczos"}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "repeat_image_as_tiles"
CATEGORY = "illusion"
def repeat_image_as_tiles(self, image, horizontal_repeats, vertical_repeats, resize_mode, tile_target_size, resampling_filter):
if not isinstance(image, torch.Tensor):
if isinstance(image, list) and len(image) > 0 and isinstance(image[0], torch.Tensor):
image_tensor = image[0]
else:
raise TypeError(f"Input image must be a torch.Tensor or a list containing one, got {type(image)}")
else:
image_tensor = image
if image_tensor.ndim == 3:
image_bchw_float = image_tensor.unsqueeze(0)
elif image_tensor.ndim == 4:
image_bchw_float = image_tensor
else:
raise ValueError(f"Input image tensor must be 3D (H,W,C) or 4D (B,H,W,C), got {image_tensor.ndim}D shape: {image_tensor.shape}")
single_image_hwc_float = image_bchw_float[0].cpu().numpy()
pil_image_mode = 'RGB'
if single_image_hwc_float.ndim == 3 and single_image_hwc_float.shape[2] == 1:
pil_image_mode = 'L'
elif single_image_hwc_float.ndim == 2: # Si c'est déjà 2D (grayscale)
pil_image_mode = 'L'
pil_image = Image.fromarray((single_image_hwc_float * 255).squeeze().astype(np.uint8), mode=pil_image_mode)
original_width, original_height = pil_image.size
resized_image_hwc_float = single_image_hwc_float # Par défaut, pas de redimensionnement
if resize_mode != "None" and tile_target_size > 0:
target_w = original_width
target_h = original_height
aspect_ratio = original_width / original_height if original_height != 0 else 1
if resize_mode == "Width":
target_w = tile_target_size
target_h = int(target_w / aspect_ratio) if aspect_ratio != 0 else original_height
elif resize_mode == "Height":
target_h = tile_target_size
target_w = int(target_h * aspect_ratio)
elif resize_mode == "Shortest Side":
if original_width < original_height: # Width is shortest
target_w = tile_target_size
target_h = int(target_w / aspect_ratio) if aspect_ratio != 0 else original_height
else: # Height is shortest (or square)
target_h = tile_target_size
target_w = int(target_h * aspect_ratio)
elif resize_mode == "Longest Side":
if original_width > original_height: # Width is longest
target_w = tile_target_size
target_h = int(target_w / aspect_ratio) if aspect_ratio != 0 else original_height
else: # Height is longest (or square)
target_h = tile_target_size
target_w = int(target_h * aspect_ratio)
# S'assurer que les dimensions cibles ne sont pas nulles
target_w = max(1, target_w)
target_h = max(1, target_h)
if (target_w != original_width or target_h != original_height):
resampling_map = {
"lanczos": Image.Resampling.LANCZOS, "bicubic": Image.Resampling.BICUBIC,
"bilinear": Image.Resampling.BILINEAR, "nearest": Image.Resampling.NEAREST
}
resample_pil = resampling_map.get(resampling_filter, Image.Resampling.LANCZOS)
print(f"TileImageRepeaterNode: Resizing tile from {original_width}x{original_height} to {target_w}x{target_h} using {resampling_filter}")
pil_image_resized = pil_image.resize((target_w, target_h), resample=resample_pil)
# Reconvertir en NumPy array et s'assurer qu'il a 3 canaux si l'original en avait 3
resized_np = np.array(pil_image_resized).astype(np.float32) / 255.0
if resized_np.ndim == 2: # Si PIL retourne une image en niveaux de gris (L mode)
resized_image_hwc_float = np.stack((resized_np,) * 3, axis=-1) if single_image_hwc_float.shape[2] == 3 else resized_np[:,:,np.newaxis]
else: # Déjà RGB
resized_image_hwc_float = resized_np
else:
resized_image_hwc_float = single_image_hwc_float # Aucune redimension effective
else: # resize_mode == "None" or tile_target_size == 0
resized_image_hwc_float = single_image_hwc_float
# S'assurer que le nombre de canaux est correct après toutes les opérations
# Surtout si l'entrée était grayscale
if single_image_hwc_float.shape[2] == 1 and resized_image_hwc_float.ndim == 3 and resized_image_hwc_float.shape[2] == 3:
# Si l'entrée était grayscale mais que le redimensionnement a produit RGB (ex: mode 'L' vers 'RGB'), prendre la moyenne
resized_image_hwc_float = np.mean(resized_image_hwc_float, axis=2, keepdims=True)
elif single_image_hwc_float.shape[2] == 3 and resized_image_hwc_float.ndim == 2:
# Si l'entrée était RGB mais que le redimensionnement a produit Grayscale, répéter le canal
resized_image_hwc_float = np.stack((resized_image_hwc_float,) * 3, axis=-1)
elif single_image_hwc_float.shape[2] == 3 and resized_image_hwc_float.ndim == 3 and resized_image_hwc_float.shape[2] == 1:
resized_image_hwc_float = np.repeat(resized_image_hwc_float, 3, axis=2)
tiled_image_np_float = np.tile(resized_image_hwc_float,
(vertical_repeats, horizontal_repeats, 1))
output_tensor_bhwc = torch.from_numpy(tiled_image_np_float).unsqueeze(0)
return (output_tensor_bhwc,)
NODE_CLASS_MAPPINGS = {
"TileImageRepeaterNode": TileImageRepeaterNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"TileImageRepeaterNode": "Tile Image Repeater (Smart Resize)"
}