Complete package with Core, Creative, Vintage, Deformation, Light Effects, and Geometric categories
103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
import numpy as np
|
|
import torch
|
|
import cv2
|
|
|
|
class FisheyeNode:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"fov": ("FLOAT", {"default": 180.0, "min": 30.0, "max": 360.0, "step": 1.0}),
|
|
"mapping": (["equidistant", "equisolid", "orthographic", "stereographic"], {"default": "equidistant"}),
|
|
"format": (["fullframe", "circular"], {"default": "fullframe"}),
|
|
},
|
|
"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}),
|
|
"strength": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 2.0, "step": 0.1}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "apply_fisheye"
|
|
CATEGORY = "Image Effects"
|
|
|
|
def apply_fisheye(self, image, fov, mapping, format, center_x=0.5, center_y=0.5, strength=1.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 de l'effet
|
|
cx = int(w * center_x)
|
|
cy = int(h * center_y)
|
|
|
|
# Rayon maximum
|
|
max_radius = min(w, h) // 2
|
|
|
|
# Créer les grilles de coordonnées
|
|
result = self._apply_fisheye_distortion(img_np, cx, cy, max_radius, fov, mapping, format, strength)
|
|
|
|
result_tensor = torch.from_numpy(result.astype(np.float32) / 255.0).unsqueeze(0)
|
|
return (result_tensor,)
|
|
|
|
def _apply_fisheye_distortion(self, image, cx, cy, max_radius, fov, mapping, format, strength):
|
|
h, w = image.shape[:2]
|
|
|
|
# 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)
|
|
|
|
fov_rad = np.radians(fov)
|
|
|
|
for y in range(h):
|
|
for x in range(w):
|
|
# Distance du centre
|
|
dx = x - cx
|
|
dy = y - cy
|
|
r = np.sqrt(dx*dx + dy*dy)
|
|
|
|
if r == 0:
|
|
map_x[y, x] = x
|
|
map_y[y, x] = y
|
|
continue
|
|
|
|
# Angle
|
|
theta = np.arctan2(dy, dx)
|
|
|
|
# Normaliser le rayon
|
|
r_norm = r / max_radius
|
|
|
|
if format == "circular" and r_norm > 1.0:
|
|
map_x[y, x] = x
|
|
map_y[y, x] = y
|
|
continue
|
|
|
|
# Appliquer la projection selon le mapping
|
|
if mapping == "equidistant":
|
|
r_fish = r_norm * fov_rad / (2 * np.pi) * max_radius
|
|
elif mapping == "equisolid":
|
|
r_fish = 2 * max_radius * np.sin(r_norm * fov_rad / 4)
|
|
elif mapping == "orthographic":
|
|
r_fish = max_radius * np.sin(r_norm * fov_rad / 2)
|
|
elif mapping == "stereographic":
|
|
r_fish = 2 * max_radius * np.tan(r_norm * fov_rad / 4)
|
|
|
|
# Appliquer la force
|
|
r_fish *= strength
|
|
|
|
# Nouvelles coordonnées
|
|
new_x = cx + r_fish * np.cos(theta)
|
|
new_y = cy + r_fish * np.sin(theta)
|
|
|
|
map_x[y, x] = np.clip(new_x, 0, w - 1)
|
|
map_y[y, x] = np.clip(new_y, 0, h - 1)
|
|
|
|
# Appliquer la transformation
|
|
result = cv2.remap(image, map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT)
|
|
return result
|