Delete nodes/FV_NodeGroup_1.py
This commit is contained in:
@@ -1,719 +0,0 @@
|
||||
from re import S
|
||||
import cv2
|
||||
import numpy as np
|
||||
from skimage.exposure import match_histograms
|
||||
from PIL import Image
|
||||
from enum import Enum
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torchvision import transforms
|
||||
from random import randint
|
||||
from PIL import ImageFilter
|
||||
|
||||
# PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
|
||||
class Color_Correction:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"original_image": ("IMAGE",),
|
||||
"correction": ("IMAGE",),
|
||||
"blend_factor": ("FLOAT", {"default": 1, "min": 0.01, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "color_correction"
|
||||
CATEGORY = "Fictiverse"
|
||||
|
||||
class BlendType(Enum):
|
||||
LUMINOSITY = 1 # Replace with your actual BlendType definition
|
||||
|
||||
def color_correction(self, original_image, correction, blend_factor):
|
||||
|
||||
pil_original_image = np.array(tensor2pil(original_image))
|
||||
pil_correction = np.array(tensor2pil(correction))
|
||||
|
||||
original_lab = cv2.cvtColor(pil_original_image, cv2.COLOR_RGB2LAB)
|
||||
corrected_lab = cv2.cvtColor(pil_correction, cv2.COLOR_RGB2LAB)
|
||||
corrected_image = cv2.cvtColor(match_histograms(original_lab, corrected_lab, channel_axis=2), cv2.COLOR_LAB2RGB).astype("uint8")
|
||||
|
||||
# Use 'correction' as the template image
|
||||
template_image = corrected_image # Use the 'correction' as the template image
|
||||
|
||||
# Perform template matching with 'correction' as the template
|
||||
result = cv2.matchTemplate(corrected_image, template_image, cv2.TM_CCOEFF_NORMED)
|
||||
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
|
||||
top_left = max_loc
|
||||
h, w = template_image.shape[:2]
|
||||
bottom_right = (top_left[0] + w, top_left[1] + h)
|
||||
|
||||
# Draw a rectangle around the matched area (you can modify this part)
|
||||
cv2.rectangle(corrected_image, top_left, bottom_right, (0, 0, 255), 2)
|
||||
|
||||
# Apply the blend factor to the result
|
||||
blended_image = cv2.addWeighted(pil_original_image, 1 - blend_factor, corrected_image, blend_factor, 0)
|
||||
|
||||
# Convert the result back to a PIL image
|
||||
result_image = Image.fromarray(blended_image)
|
||||
|
||||
img = pil2tensor(result_image)
|
||||
return (img,)
|
||||
|
||||
|
||||
class Displace_Image: #Modified version of WAS node : https://github.com/WASasquatch/was-node-suite-comfyui
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"displacement_maps": ("IMAGE",),
|
||||
"amplitudeX": ("FLOAT", {"default": 25.0, "min": -4096, "max": 4096, "step": 0.1}),
|
||||
"amplitudeY": ("FLOAT", {"default": 25.0, "min": -4096, "max": 4096, "step": 0.1}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("images",)
|
||||
FUNCTION = "displace_image"
|
||||
CATEGORY = "Fictiverse"
|
||||
|
||||
def displace_image(self, images, displacement_maps, amplitudeX, amplitudeY):
|
||||
|
||||
Tools = Tools_Class()
|
||||
|
||||
displaced_images = []
|
||||
for i in range(len(images)):
|
||||
img = tensor2pil(images[i])
|
||||
if i < len(displacement_maps):
|
||||
disp = tensor2pil(displacement_maps[i])
|
||||
else:
|
||||
disp = tensor2pil(displacement_maps[-1])
|
||||
disp = Tools.resize_and_crop(disp, img.size)
|
||||
displaced_images.append(pil2tensor(Tools.displace_imageNP(img, disp, amplitudeX, amplitudeY)))
|
||||
|
||||
displaced_images = torch.cat(displaced_images, dim=0)
|
||||
|
||||
return (displaced_images, )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AddNoiseToImageWithMask: #Modified version of WAS node : https://github.com/WASasquatch/was-node-suite-comfyui
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"masks": ("IMAGE",),
|
||||
"strength": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.05}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("images",)
|
||||
FUNCTION = "addNoiseToImageWithMask"
|
||||
CATEGORY = "Fictiverse"
|
||||
|
||||
def addNoiseToImageWithMask(self, images, masks, strength):
|
||||
|
||||
Tools = Tools_Class()
|
||||
|
||||
out_images = []
|
||||
for i in range(len(images)):
|
||||
img = tensor2pil(images[i])
|
||||
if i < len(masks):
|
||||
mask = tensor2pil(masks[i])
|
||||
else:
|
||||
mask = tensor2pil(masks[-1])
|
||||
mask = Tools.resize_and_crop(mask, img.size)
|
||||
out_images.append(pil2tensor(Tools.add_noise_with_mask(img, mask, strength)))
|
||||
|
||||
out_images = torch.cat(out_images, dim=0)
|
||||
|
||||
return (out_images, )
|
||||
|
||||
|
||||
|
||||
|
||||
####################################################################
|
||||
|
||||
class DisplaceImageWithDepth: #Modified version of WAS node : https://github.com/WASasquatch/was-node-suite-comfyui
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Image": ("IMAGE",),
|
||||
"Depth": ("IMAGE",),
|
||||
"X": ("INT", {"default": 0, "min": -4096, "max": 4096, "step": 1}),
|
||||
"Y": ("INT", {"default": 0, "min": -4096, "max": 4096, "step": 1}),
|
||||
"Zoom": ("FLOAT", {"default": 0.0, "min": -1, "max": 1, "step": 0.1}),
|
||||
"Rotation": ("FLOAT", {"default": 0.0, "min": -90, "max": 90, "step": 1}),
|
||||
"Shake": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
"LayerCount": ("INT", {"default": 8, "min": 2, "max": 255, "step": 1}),
|
||||
"Frames": ("INT", {"default": 4, "min": 2, "max": 128, "step": 1}),
|
||||
"Fill": ("BOOLEAN", {"default": True, "label_on": "Yes", "label_off": "No"}),
|
||||
"Erode": ("INT", {"default": 3, "min": 0, "max": 20, "step": 1}),
|
||||
"Blur": ("INT", {"default": 10, "min": 0, "max": 20, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE","IMAGE",)
|
||||
RETURN_NAMES = ("Frames", "Layers",)
|
||||
FUNCTION = "displaceImageWithDepth"
|
||||
CATEGORY = "Fictiverse"
|
||||
|
||||
def displaceImageWithDepth(self, Image, Depth, X, Y, Zoom, Rotation, Shake, LayerCount, Frames, Fill, Erode, Blur):
|
||||
|
||||
Tools = Tools_Class()
|
||||
|
||||
result_layers = []
|
||||
result_images = []
|
||||
img = tensor2pil(Image[0])
|
||||
mask = tensor2pil(Depth[0])
|
||||
mask = Tools.resize_and_crop(mask, img.size)
|
||||
|
||||
shakeX = 0
|
||||
shakeY = 0
|
||||
fX = X/Frames
|
||||
fY = Y/Frames
|
||||
fZ = Zoom/Frames
|
||||
fR = Rotation/Frames
|
||||
|
||||
for f in range(Frames):
|
||||
|
||||
shakeX = shakeX + np.random.randint(low=-100, high=100)
|
||||
shakeY = shakeY + np.random.randint(low=-100, high=100)
|
||||
|
||||
tx = fX * f + shakeX*(Shake/100)
|
||||
ty = fY * f + shakeY*(Shake/100)
|
||||
z = fZ * f
|
||||
r = fR * f
|
||||
|
||||
layers, combined = Tools.apply_perspective_transformation(img, mask, tx, ty, z, r, LayerCount, Fill, Erode, Blur)
|
||||
result_images.append(pil2tensor(combined))
|
||||
|
||||
if f == 0:
|
||||
for layer in layers:
|
||||
result_layers.append(pil2tensor(layer))
|
||||
|
||||
result_layers = torch.cat(result_layers, dim=0)
|
||||
result_images = torch.cat(result_images, dim=0)
|
||||
|
||||
return (result_images, result_layers)
|
||||
|
||||
####################################################################
|
||||
|
||||
|
||||
|
||||
|
||||
class ZoomWithDepth:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"Image": ("IMAGE",),
|
||||
"Depth": ("IMAGE",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "zoomWithDepth"
|
||||
CATEGORY = "Fictiverse"
|
||||
|
||||
def zoomWithDepth(self, Image, Depth):
|
||||
|
||||
Tools = Tools_Class()
|
||||
|
||||
img = tensor2pil(Image[0])
|
||||
mask = tensor2pil(Depth[0])
|
||||
mask = Tools.resize_and_crop(mask, img.size)
|
||||
|
||||
combined= Tools.parallax_zoom(img, mask)
|
||||
|
||||
|
||||
return ( pil2tensor(combined))
|
||||
####################################################################
|
||||
|
||||
|
||||
|
||||
class Tools_Class():
|
||||
|
||||
def resize_and_crop(self, image, target_size):
|
||||
width, height = image.size
|
||||
target_width, target_height = target_size
|
||||
aspect_ratio = width / height
|
||||
target_aspect_ratio = target_width / target_height
|
||||
|
||||
if aspect_ratio > target_aspect_ratio:
|
||||
new_height = target_height
|
||||
new_width = int(new_height * aspect_ratio)
|
||||
else:
|
||||
new_width = target_width
|
||||
new_height = int(new_width / aspect_ratio)
|
||||
|
||||
image = image.resize((new_width, new_height))
|
||||
left = (new_width - target_width) // 2
|
||||
top = (new_height - target_height) // 2
|
||||
right = left + target_width
|
||||
bottom = top + target_height
|
||||
image = image.crop((left, top, right, bottom))
|
||||
|
||||
return image
|
||||
|
||||
def displace_image(self, image, displacement_map, amplitudeX, amplitudeY):
|
||||
|
||||
image = image.convert('RGB')
|
||||
displacement_map = displacement_map.convert('L')
|
||||
width, height = image.size
|
||||
result = Image.new('RGB', (width, height))
|
||||
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
|
||||
# Calculate the displacements n' stuff
|
||||
displacement = displacement_map.getpixel((x, y))
|
||||
displacement_amountX = amplitudeX * (displacement / 255)
|
||||
displacement_amountY = amplitudeY * (displacement / 255)
|
||||
new_x = x + int(displacement_amountX)
|
||||
new_y = y + int(displacement_amountY)
|
||||
|
||||
# Apply mirror reflection at edges and corners
|
||||
if new_x < 0:
|
||||
new_x = abs(new_x)
|
||||
elif new_x >= width:
|
||||
new_x = 2 * width - new_x - 1
|
||||
|
||||
if new_y < 0:
|
||||
new_y = abs(new_y)
|
||||
elif new_y >= height:
|
||||
new_y = 2 * height - new_y - 1
|
||||
|
||||
if new_x < 0:
|
||||
new_x = abs(new_x)
|
||||
if new_y < 0:
|
||||
new_y = abs(new_y)
|
||||
|
||||
if new_x >= width:
|
||||
new_x = 2 * width - new_x - 1
|
||||
if new_y >= height:
|
||||
new_y = 2 * height - new_y - 1
|
||||
|
||||
# Consider original image color at new location for RGB results, oops
|
||||
pixel = image.getpixel((new_x, new_y))
|
||||
result.putpixel((x, y), pixel)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def displace_imageNP(self, image, displacement_map, amplitudeX, amplitudeY):
|
||||
image = image.convert('RGB')
|
||||
displacement_map = displacement_map.convert('L')
|
||||
|
||||
# Convert PIL images to NumPy arrays
|
||||
image_arr = np.array(image)
|
||||
displacement_arr = np.array(displacement_map)
|
||||
|
||||
height, width, _ = image_arr.shape
|
||||
result_arr = np.zeros((height, width, 3), dtype=np.uint8)
|
||||
|
||||
# Calculate displacements
|
||||
displacement_normalized = displacement_arr / 255.0
|
||||
displacement_amountX = amplitudeX * displacement_normalized
|
||||
displacement_amountY = amplitudeY * displacement_normalized
|
||||
|
||||
# Create grids of x and y coordinates
|
||||
x_coords, y_coords = np.meshgrid(np.arange(width), np.arange(height))
|
||||
|
||||
# Calculate new coordinates
|
||||
new_x = np.clip(x_coords + displacement_amountX, 0, width - 1).astype(int)
|
||||
new_y = np.clip(y_coords + displacement_amountY, 0, height - 1).astype(int)
|
||||
|
||||
# Apply mirror reflection at edges and corners
|
||||
new_x = np.where(new_x < 0, -new_x, new_x)
|
||||
new_x = np.where(new_x >= width, 2 * width - new_x - 1, new_x)
|
||||
new_y = np.where(new_y < 0, -new_y, new_y)
|
||||
new_y = np.where(new_y >= height, 2 * height - new_y - 1, new_y)
|
||||
|
||||
# Fetch pixels from original image at new locations
|
||||
result_arr = image_arr[new_y, new_x]
|
||||
|
||||
# Create PIL Image from NumPy array
|
||||
result = Image.fromarray(result_arr)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def displaceImageWithDepth(self, image, mask, amplitudeX, amplitudeY, amplitudeZ, layerCount):
|
||||
|
||||
image = image.convert('RGB')
|
||||
mask = mask.convert('L')
|
||||
width, height = image.size
|
||||
|
||||
layerStep = int(255/layerCount)
|
||||
imageLayers = []
|
||||
|
||||
for l in range(layerCount):
|
||||
|
||||
layer = Image.new('RGBA', (width, height))
|
||||
colorTarget = max(0, min(l*layerStep, 255))
|
||||
colorRangeMin = max(0, min(colorTarget-layerStep, 255))
|
||||
colorRangeMax = max(0, min(colorTarget+layerStep, 255))
|
||||
colorRange = range(colorRangeMin, colorRangeMax, 1)
|
||||
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
maskValue = mask.getpixel((x, y))
|
||||
if maskValue in colorRange:
|
||||
|
||||
amplitude = l/layerCount
|
||||
offsetX = int(amplitudeX*amplitude*amplitude)
|
||||
offsetY = int(amplitudeY*amplitude*amplitude)
|
||||
offsetZ = int(amplitudeZ*amplitude*amplitude)
|
||||
nX = x+offsetX
|
||||
nY = y+offsetY
|
||||
|
||||
if nX >=0 and nX<width and nY >=0 and nY<height:
|
||||
layer.putpixel((nX, nY), image.getpixel((x, y)))
|
||||
|
||||
#layer.putpixel((x, y), image.getpixel((x, y)))
|
||||
|
||||
imageLayers.append(layer)
|
||||
|
||||
return imageLayers
|
||||
|
||||
def apply_perspective_transformation(self, image_pil, depth_map_pil, tx, ty, zoom, rot, num_layers, Fill, erode, blur):
|
||||
|
||||
# Convert PIL images to NumPy arrays
|
||||
image = np.array(image_pil)
|
||||
depth_map = np.array(depth_map_pil)
|
||||
parallax_factor = 1
|
||||
if num_layers < 1:
|
||||
raise ValueError("Layers Count must be > 1.")
|
||||
|
||||
# Créer un tableau vide pour stocker les couches
|
||||
layers = []
|
||||
|
||||
# Calculer la plage de profondeur
|
||||
min_depth = np.min(depth_map)
|
||||
max_depth = np.max(depth_map)
|
||||
depth_range = max_depth - min_depth
|
||||
|
||||
# Create an alpha channel (example)
|
||||
alpha_channel = np.full((image.shape[0], image.shape[1]), 255, dtype=np.uint8)
|
||||
# Create an empty RGBA image with the combined dimensions
|
||||
combined_image = np.dstack((image, alpha_channel))
|
||||
|
||||
# Get the size (width and height) of the target image
|
||||
width, height = image_pil.size
|
||||
imagesCombined = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
|
||||
# Créer une version floutée de l'image
|
||||
image_pil_blurred = image_pil.filter(ImageFilter.GaussianBlur(radius=blur)) # Ajustez le rayon de flou selon vos besoins
|
||||
image_blurred = np.array(image_pil_blurred)
|
||||
|
||||
# Créer les couches en fonction du nombre spécifié
|
||||
for i in range(num_layers):
|
||||
# Déterminer les valeurs de profondeur minimale et maximale pour cette couche
|
||||
layer_min_depth = min_depth + (i / num_layers) * depth_range
|
||||
layer_max_depth = min_depth + ((i + 1) / num_layers) * depth_range
|
||||
|
||||
# Sélectionner les pixels de la depth map qui appartiennent à cette couche
|
||||
layer_mask = np.logical_and(depth_map >= layer_min_depth, depth_map <= layer_max_depth)
|
||||
if Fill:
|
||||
layer_mask = depth_map >= layer_min_depth-10
|
||||
|
||||
fill_mask = depth_map <= layer_max_depth
|
||||
|
||||
image_rgb = image[:, :, :3]
|
||||
if Fill:
|
||||
image_rgb = np.where(fill_mask, image_rgb, image_blurred)
|
||||
|
||||
|
||||
layer_alpha = (layer_mask[:, :, 0] * 255).astype(np.uint8)
|
||||
|
||||
# Dilate le masque
|
||||
kernel = np.ones((erode, erode), np.uint8) # Ajustez la taille du noyau selon vos besoins
|
||||
layer_alpha_dilated = cv2.erode(layer_alpha, kernel, iterations=1)
|
||||
|
||||
#layer_alpha_pil = Image.fromarray(layer_alpha_dilated)
|
||||
#layer_alpha_pil_blurred = layer_alpha_pil.filter(ImageFilter.GaussianBlur(radius=5)) # Ajustez le rayon de flou selon vos besoins
|
||||
#layer_alpha_blurred = np.array(layer_alpha_pil_blurred)
|
||||
|
||||
# Create an RGBA image by stacking the RGB channels with the alpha channel
|
||||
layer_rgba = np.dstack((image_rgb, layer_alpha_dilated))
|
||||
|
||||
#layer_rgba = self.edge_padding(layer_rgba, 20)
|
||||
|
||||
# Normalize i to be in the range [0, 1]
|
||||
t = i / (num_layers - 1)
|
||||
|
||||
# Apply the easing function
|
||||
parallax_factor = self.ease_in_out_cubic(t)
|
||||
|
||||
# Calculate the translation for this layer
|
||||
tx_offset = int(tx * parallax_factor)
|
||||
ty_offset = int(ty * parallax_factor)
|
||||
|
||||
translated_mask = self.translate_layer(layer_rgba,tx_offset,ty_offset)
|
||||
|
||||
z = i*zoom/num_layers + 1
|
||||
translated_mask = self.cv2_clipped_zoom(translated_mask, z)
|
||||
|
||||
# Apply rotation
|
||||
translated_mask = self.rotate_image(translated_mask, rot)
|
||||
|
||||
layer_image = Image.fromarray(translated_mask)
|
||||
layers.append(layer_image)
|
||||
|
||||
# Replace visible pixels of image1 with corresponding pixels from image2
|
||||
imagesCombined.paste(layer_image, (0, 0), layer_image)
|
||||
|
||||
|
||||
return layers, imagesCombined
|
||||
|
||||
def ease_in_out_cubic(self, x):
|
||||
"""Cubic ease-in-out function."""
|
||||
if x < 0.5:
|
||||
return 4 * x * x * x
|
||||
else:
|
||||
return 1 - pow(-2 * x + 2, 3) / 2
|
||||
|
||||
def alpha_composite(self, foreground, background):
|
||||
if foreground.shape != background.shape:
|
||||
raise ValueError("Les images doivent avoir la même taille pour la composition alpha")
|
||||
|
||||
fg_rgb = foreground[:, :, :3]
|
||||
fg_alpha = foreground[:, :, 3] / 255.0
|
||||
bg_rgb = background[:, :, :3]
|
||||
bg_alpha = background[:, :, 3] / 255.0
|
||||
|
||||
out_alpha = fg_alpha + bg_alpha * (1 - fg_alpha)
|
||||
out_rgb = np.zeros_like(fg_rgb)
|
||||
for c in range(3):
|
||||
out_rgb[:, :, c] = (fg_rgb[:, :, c] * fg_alpha + bg_rgb[:, :, c] * bg_alpha * (1 - fg_alpha))
|
||||
|
||||
out_rgba = np.dstack((out_rgb, out_alpha * 255)).astype(np.uint8)
|
||||
return out_rgba
|
||||
|
||||
|
||||
|
||||
def parallax_zoom(self, image, depth_map):
|
||||
# Convertir les images PIL en tableaux NumPy
|
||||
image_array = np.array(image)
|
||||
depth_map_array = np.array(depth_map)
|
||||
depth_map_array = depth_map_array[:, :, 0]
|
||||
radius = 5
|
||||
# Normaliser la carte de profondeur entre 0 et 1
|
||||
normalized_depth_map = depth_map_array.astype(float) / 255.0
|
||||
|
||||
# Calculer le centre de l'image pour l'utiliser comme point de référence
|
||||
center_x, center_y = image.width // 2, image.height // 2
|
||||
|
||||
# Créer une grille de coordonnées pour l'image
|
||||
y_coords, x_coords = np.mgrid[0:image.height, 0:image.width]
|
||||
|
||||
# Calculer les distances par rapport au centre de l'image
|
||||
distances = np.sqrt((x_coords - center_x)**2 + (y_coords - center_y)**2)
|
||||
|
||||
# Agrandir les pixels en fonction de la carte de profondeur
|
||||
scaled_distances = distances + (normalized_depth_map * radius)
|
||||
|
||||
# Interpoler les nouvelles positions des pixels
|
||||
new_x_coords = ((x_coords - center_x) * (scaled_distances / distances)) + center_x
|
||||
new_y_coords = ((y_coords - center_y) * (scaled_distances / distances)) + center_y
|
||||
|
||||
# Limiter les valeurs pour éviter les débordements
|
||||
new_x_coords = np.clip(new_x_coords, 0, image.width - 1)
|
||||
new_y_coords = np.clip(new_y_coords, 0, image.height - 1)
|
||||
|
||||
# Interpoler les valeurs des pixels pour obtenir la nouvelle image
|
||||
new_image = np.zeros_like(image_array)
|
||||
for i in range(image.height):
|
||||
for j in range(image.width):
|
||||
new_image[i, j] = image_array[new_y_coords[i, j].astype(int), new_x_coords[i, j].astype(int)]
|
||||
|
||||
# Convertir le tableau NumPy en image PIL
|
||||
new_image_pil = Image.fromarray(new_image.astype(np.uint8))
|
||||
|
||||
return new_image_pil
|
||||
|
||||
|
||||
|
||||
def rotate_image(self, image, angle):
|
||||
(h, w) = image.shape[:2]
|
||||
(cx, cy) = (w // 2, h // 2)
|
||||
M = cv2.getRotationMatrix2D((cx, cy), angle, 1.0)
|
||||
rotated_image = cv2.warpAffine(image, M, (w, h))
|
||||
return rotated_image
|
||||
|
||||
|
||||
|
||||
|
||||
def cv2_clipped_zoom(self, img, zoom_factor=0):
|
||||
|
||||
"""
|
||||
Center zoom in/out of the given image and returning an enlarged/shrinked view of
|
||||
the image without changing dimensions
|
||||
------
|
||||
Args:
|
||||
img : ndarray
|
||||
Image array
|
||||
zoom_factor : float
|
||||
amount of zoom as a ratio [0 to Inf). Default 0.
|
||||
------
|
||||
Returns:
|
||||
result: ndarray
|
||||
numpy ndarray of the same shape of the input img zoomed by the specified factor.
|
||||
"""
|
||||
if zoom_factor == 0:
|
||||
return img
|
||||
|
||||
|
||||
height, width = img.shape[:2] # It's also the final desired shape
|
||||
new_height, new_width = int(height * zoom_factor), int(width * zoom_factor)
|
||||
|
||||
### Crop only the part that will remain in the result (more efficient)
|
||||
# Centered bbox of the final desired size in resized (larger/smaller) image coordinates
|
||||
y1, x1 = max(0, new_height - height) // 2, max(0, new_width - width) // 2
|
||||
y2, x2 = y1 + height, x1 + width
|
||||
bbox = np.array([y1,x1,y2,x2])
|
||||
# Map back to original image coordinates
|
||||
bbox = (bbox / zoom_factor).astype(np.int32)
|
||||
y1, x1, y2, x2 = bbox
|
||||
cropped_img = img[y1:y2, x1:x2]
|
||||
|
||||
# Handle padding when downscaling
|
||||
resize_height, resize_width = min(new_height, height), min(new_width, width)
|
||||
pad_height1, pad_width1 = (height - resize_height) // 2, (width - resize_width) //2
|
||||
pad_height2, pad_width2 = (height - resize_height) - pad_height1, (width - resize_width) - pad_width1
|
||||
pad_spec = [(pad_height1, pad_height2), (pad_width1, pad_width2)] + [(0,0)] * (img.ndim - 2)
|
||||
|
||||
result = cv2.resize(cropped_img, (resize_width, resize_height))
|
||||
result = np.pad(result, pad_spec, mode='constant')
|
||||
assert result.shape[0] == height and result.shape[1] == width
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def translate_layer(self, layer_rgba, tx_offset, ty_offset):
|
||||
|
||||
|
||||
# Determine the new dimensions of the translated image
|
||||
new_height = layer_rgba.shape[0]
|
||||
new_width = layer_rgba.shape[1]
|
||||
|
||||
# Create an empty image with the same shape as merged_image
|
||||
translated_mask = np.zeros_like(layer_rgba)
|
||||
|
||||
# Calculate the cropping box
|
||||
x1, x2 = max(0, -tx_offset), min(new_width, new_width - tx_offset)
|
||||
y1, y2 = max(0, -ty_offset), min(new_height, new_height - ty_offset)
|
||||
|
||||
# Calculate the region to copy from the original image
|
||||
src_x1, src_x2 = max(0, tx_offset), min(new_width, new_width + tx_offset)
|
||||
src_y1, src_y2 = max(0, ty_offset), min(new_height, new_height + ty_offset)
|
||||
|
||||
# Copy the pixels from the original image to the translated image
|
||||
translated_mask[y1:y2, x1:x2] = layer_rgba[src_y1:src_y2, src_x1:src_x2]
|
||||
|
||||
return translated_mask
|
||||
|
||||
def edge_padding(self, image, padding_size):
|
||||
height, width, channels = image.shape
|
||||
|
||||
# Extraction du canal alpha pour déterminer les bords
|
||||
alpha_channel = image[:, :, 3]
|
||||
|
||||
# Création d'un masque autour du contour alpha
|
||||
alpha_mask = np.zeros((height, width), dtype=np.uint8)
|
||||
alpha_mask[alpha_channel < 255] = 1 # Si le pixel n'est pas complètement opaque (alpha < 255), c'est un bord
|
||||
|
||||
# Dilatation du masque pour ajouter du padding
|
||||
kernel = np.ones((padding_size, padding_size), dtype=np.uint8)
|
||||
dilated_mask = cv2.dilate(alpha_mask, kernel, iterations=1)
|
||||
|
||||
# Création d'une copie de l'image avec le padding
|
||||
padded_image = np.copy(image)
|
||||
|
||||
for c in range(channels): # Appliquer le padding pour chaque canal de couleur
|
||||
padded_image[:, :, c][dilated_mask == 1] = 0 # Mettre à zéro les pixels du bord
|
||||
|
||||
return padded_image
|
||||
|
||||
|
||||
|
||||
def clampPx(self, value):
|
||||
return max(0, min(value, 255))
|
||||
|
||||
|
||||
|
||||
|
||||
def add_noise_with_mask(self, image, mask, strength):
|
||||
# Convert the input image and mask to NumPy arrays
|
||||
image_array = np.array(image)
|
||||
mask_array = np.array(mask)
|
||||
|
||||
# Generate random noise with the same shape as the image
|
||||
noise = np.random.normal(scale=strength, size=image_array.shape)
|
||||
|
||||
# Apply the mask to the noise
|
||||
noise *= mask_array
|
||||
|
||||
# Add the noise to the image
|
||||
noisy_image_array = image_array + noise
|
||||
|
||||
# Clip the pixel values to the valid range (0-255)
|
||||
noisy_image_array = np.clip(noisy_image_array, 0, 255).astype(np.uint8)
|
||||
|
||||
# Convert the NumPy array back to an image
|
||||
noisy_image = Image.fromarray(noisy_image_array)
|
||||
|
||||
return noisy_image
|
||||
|
||||
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Color correction": Color_Correction,
|
||||
"Displace Images with Mask": Displace_Image,
|
||||
"Add Noise to Image with Mask": AddNoiseToImageWithMask,
|
||||
"Displace Image with Depth": DisplaceImageWithDepth,
|
||||
"Zoom Image with Depth": ZoomWithDepth,
|
||||
}
|
||||
Reference in New Issue
Block a user