Complete package with Core, Creative, Vintage, Deformation, Light Effects, and Geometric categories
85 lines
3.3 KiB
Python
85 lines
3.3 KiB
Python
import numpy as np
|
|
import torch
|
|
import cv2
|
|
|
|
class RippleNode:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"amplitude": ("FLOAT", {"default": 20.0, "min": 0.0, "max": 100.0, "step": 1.0}),
|
|
"frequency": ("FLOAT", {"default": 0.02, "min": 0.001, "max": 0.1, "step": 0.001}),
|
|
"wave_type": (["sine", "cosine", "both"], {"default": "sine"}),
|
|
},
|
|
"optional": {
|
|
"center_x": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"center_y": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
"phase": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 6.28, "step": 0.1}),
|
|
"decay": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "apply_ripple"
|
|
CATEGORY = "Image Effects"
|
|
|
|
def apply_ripple(self, image, amplitude, frequency, wave_type,
|
|
center_x=0.5, center_y=0.5, phase=0.0, decay=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
|
|
|
|
# Centre des ondulations
|
|
cx = w * center_x
|
|
cy = h * center_y
|
|
|
|
# Créer les grilles de coordonnées
|
|
map_x = np.zeros((h, w), dtype=np.float32)
|
|
map_y = np.zeros((h, w), dtype=np.float32)
|
|
|
|
for y in range(h):
|
|
for x in range(w):
|
|
# Distance du centre
|
|
dx = x - cx
|
|
dy = y - cy
|
|
distance = np.sqrt(dx*dx + dy*dy)
|
|
|
|
# Facteur de décroissance
|
|
decay_factor = 1.0
|
|
if decay > 0:
|
|
max_dist = np.sqrt(w*w + h*h) / 2
|
|
decay_factor = np.exp(-decay * distance / max_dist)
|
|
|
|
# Calcul de l'ondulation
|
|
if wave_type == "sine":
|
|
ripple = np.sin(distance * frequency + phase)
|
|
elif wave_type == "cosine":
|
|
ripple = np.cos(distance * frequency + phase)
|
|
else: # both
|
|
ripple = (np.sin(distance * frequency + phase) + np.cos(distance * frequency + phase)) / 2
|
|
|
|
# Amplitude avec décroissance
|
|
effective_amplitude = amplitude * decay_factor * ripple
|
|
|
|
# Direction de l'ondulation (radiale)
|
|
if distance > 0:
|
|
angle = np.arctan2(dy, dx)
|
|
offset_x = effective_amplitude * np.cos(angle)
|
|
offset_y = effective_amplitude * np.sin(angle)
|
|
else:
|
|
offset_x = offset_y = 0
|
|
|
|
map_x[y, x] = np.clip(x + offset_x, 0, w - 1)
|
|
map_y[y, x] = np.clip(y + offset_y, 0, h - 1)
|
|
|
|
# Appliquer la transformation
|
|
result = cv2.remap(img_np, map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT)
|
|
|
|
result_tensor = torch.from_numpy(result.astype(np.float32) / 255.0).unsqueeze(0)
|
|
return (result_tensor,)
|