Files
orion4d-ComfyUI-Image-Effects/geometric/triangulate_node.py
T
Bouletto 4e5b534b04 Initial release - 32 image effect nodes
Complete package with Core, Creative, Vintage, Deformation, Light Effects, and Geometric categories
2025-05-28 00:18:09 +02:00

169 lines
6.9 KiB
Python

import numpy as np
import torch
import cv2
from scipy.spatial import Delaunay
class TriangulateNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"num_points": ("INT", {"default": 500, "min": 50, "max": 2000, "step": 50}),
"edge_threshold": ("FLOAT", {"default": 0.3, "min": 0.1, "max": 1.0, "step": 0.05}),
"color_mode": (["average", "dominant", "gradient"], {"default": "average"}),
},
"optional": {
"point_distribution": (["random", "edge_based", "grid"], {"default": "edge_based"}),
"triangle_outline": ("BOOLEAN", {"default": False}),
"outline_thickness": ("INT", {"default": 1, "min": 1, "max": 5, "step": 1}),
"smoothing": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.1}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "apply_triangulate"
CATEGORY = "Image Effects"
def apply_triangulate(self, image, num_points, edge_threshold, color_mode,
point_distribution="edge_based", triangle_outline=False,
outline_thickness=1, smoothing=0.0):
if len(image.shape) == 4:
img_tensor = image[0]
else:
img_tensor = image
img_np = (img_tensor.cpu().numpy() * 255).astype(np.uint8)
h, w, c = img_np.shape
# Générer les points selon la distribution
points = self._generate_points(img_np, num_points, point_distribution, edge_threshold)
# Triangulation de Delaunay
tri = Delaunay(points)
# Créer l'image triangulée
result = self._create_triangulated_image(img_np, points, tri.simplices,
color_mode, triangle_outline, outline_thickness)
# Appliquer le lissage si demandé
if smoothing > 0:
result = self._apply_smoothing(result, smoothing)
result_tensor = torch.from_numpy(result.astype(np.float32) / 255.0).unsqueeze(0)
return (result_tensor,)
def _generate_points(self, image, num_points, distribution, edge_threshold):
"""Générer les points pour la triangulation"""
h, w = image.shape[:2]
points = []
# Ajouter les coins
points.extend([[0, 0], [w-1, 0], [w-1, h-1], [0, h-1]])
if distribution == "random":
# Distribution aléatoire
for _ in range(num_points - 4):
x = np.random.randint(0, w)
y = np.random.randint(0, h)
points.append([x, y])
elif distribution == "edge_based":
# Basé sur les contours
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, int(edge_threshold * 100), int(edge_threshold * 200))
# Points sur les contours
edge_points = np.column_stack(np.where(edges > 0))
if len(edge_points) > 0:
# Échantillonner les points de contour
indices = np.random.choice(len(edge_points),
min(num_points // 2, len(edge_points)),
replace=False)
for idx in indices:
y, x = edge_points[idx]
points.append([x, y])
# Points aléatoires pour compléter
remaining = num_points - len(points)
for _ in range(remaining):
x = np.random.randint(0, w)
y = np.random.randint(0, h)
points.append([x, y])
elif distribution == "grid":
# Distribution en grille avec variation
grid_size = int(np.sqrt(num_points))
for i in range(grid_size):
for j in range(grid_size):
if len(points) >= num_points:
break
x = int((j + 0.5) * w / grid_size) + np.random.randint(-w//20, w//20)
y = int((i + 0.5) * h / grid_size) + np.random.randint(-h//20, h//20)
x = np.clip(x, 0, w-1)
y = np.clip(y, 0, h-1)
points.append([x, y])
return np.array(points)
def _create_triangulated_image(self, image, points, triangles, color_mode, outline, thickness):
"""Créer l'image triangulée"""
h, w, c = image.shape
result = np.zeros_like(image)
for triangle in triangles:
# Points du triangle
pts = points[triangle].astype(np.int32)
# Calculer la couleur du triangle
color = self._get_triangle_color(image, pts, color_mode)
# Dessiner le triangle
cv2.fillPoly(result, [pts], color.tolist())
# Dessiner le contour si demandé
if outline:
cv2.polylines(result, [pts], True, (0, 0, 0), thickness)
return result
def _get_triangle_color(self, image, triangle_points, mode):
"""Calculer la couleur d'un triangle"""
# Créer un masque pour le triangle
mask = np.zeros(image.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [triangle_points], 255)
if mode == "average":
# Couleur moyenne
masked_pixels = image[mask > 0]
if len(masked_pixels) > 0:
return np.mean(masked_pixels, axis=0)
else:
return np.array([128, 128, 128])
elif mode == "dominant":
# Couleur dominante (approximation)
masked_pixels = image[mask > 0]
if len(masked_pixels) > 0:
# Quantifier les couleurs et prendre la plus fréquente
pixels_reshaped = masked_pixels.reshape(-1, 3)
unique_colors, counts = np.unique(pixels_reshaped, axis=0, return_counts=True)
dominant_color = unique_colors[np.argmax(counts)]
return dominant_color
else:
return np.array([128, 128, 128])
elif mode == "gradient":
# Gradient basé sur la position
center = np.mean(triangle_points, axis=0)
h, w = image.shape[:2]
gradient_factor = center[1] / h # Gradient vertical
base_color = np.mean(image[mask > 0], axis=0) if np.any(mask > 0) else np.array([128, 128, 128])
return base_color * (0.5 + 0.5 * gradient_factor)
def _apply_smoothing(self, image, smoothing):
"""Appliquer un lissage à l'image"""
kernel_size = int(smoothing * 10) * 2 + 1
smoothed = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
return image * (1 - smoothing) + smoothed * smoothing