Spaces:
Sleeping
Sleeping
import torch | |
import numpy as np | |
from PIL import Image, ImageOps, ImageEnhance | |
import cv2 | |
MAX_RESOLUTION = 4096 | |
def tensor2pil(image): | |
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) | |
# Adapt from https://github.com/sipherxyz/comfyui-art-venture | |
def color_correct( | |
image, | |
temperature: float, | |
hue: float, | |
brightness: float, | |
contrast: float, | |
saturation: float, | |
gamma: float, | |
): | |
brightness /= 100 | |
contrast /= 100 | |
saturation /= 100 | |
temperature /= 100 | |
brightness = 1 + brightness | |
contrast = 1 + contrast | |
saturation = 1 + saturation | |
modified_image = image | |
# brightness | |
modified_image = ImageEnhance.Brightness(modified_image).enhance(brightness) | |
# contrast | |
modified_image = ImageEnhance.Contrast(modified_image).enhance(contrast) | |
modified_image = np.array(modified_image).astype(np.float32) | |
# temperature | |
if temperature > 0: | |
modified_image[:, :, 0] *= 1 + temperature | |
modified_image[:, :, 1] *= 1 + temperature * 0.4 | |
elif temperature < 0: | |
modified_image[:, :, 2] *= 1 - temperature | |
modified_image = np.clip(modified_image, 0, 255) / 255 | |
# gamma | |
modified_image = np.clip(np.power(modified_image, gamma), 0, 1) | |
# saturation | |
hls_img = cv2.cvtColor(modified_image, cv2.COLOR_RGB2HLS) | |
hls_img[:, :, 2] = np.clip(saturation * hls_img[:, :, 2], 0, 1) | |
modified_image = cv2.cvtColor(hls_img, cv2.COLOR_HLS2RGB) * 255 | |
# hue | |
hsv_img = cv2.cvtColor(modified_image, cv2.COLOR_RGB2HSV) | |
hsv_img[:, :, 0] = (hsv_img[:, :, 0] + hue) % 360 | |
modified_image = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2RGB) | |
modified_image = modified_image.astype(np.uint8) | |
#modified_image = modified_image / 255 | |
#modified_image = torch.from_numpy(modified_image).unsqueeze(0) | |
return modified_image | |
def extract_pixels(image, side, num_pixels): | |
# Ottieni le dimensioni dell'immagine | |
width, height = image.size | |
# Determina la regione di ritaglio in base al lato specificato | |
if side == "l": | |
crop_box = (0, 0, num_pixels, height) | |
elif side == "r": | |
crop_box = (width - num_pixels, 0, width, height) | |
elif side == "t": | |
crop_box = (0, 0, width, num_pixels) | |
elif side == "b": | |
crop_box = (0, height - num_pixels, width, height) | |
else: | |
raise ValueError("Il lato specificato non Γ¨ valido. Utilizzare 'sinistro', 'destro', 'alto' o 'basso'.") | |
# Esegui il ritaglio dell'immagine | |
cropped_image = image.crop(crop_box) | |
return cropped_image | |
from PIL import Image | |
def make_pixelated(image, pixel_size): | |
if pixel_size > image.width or pixel_size > image.height: | |
raise ValueError("Top, bottom, left, and right padding must higher than the pixel_size!") | |
small_image = image.resize((image.width // pixel_size, image.height // pixel_size), Image.Resampling.NEAREST) | |
pixelated_image = small_image.resize(image.size, Image.Resampling.NEAREST) | |
return pixelated_image | |
def flip_and_stretch(image, flip_direction, stretch_value): | |
# Inverti l'immagine in base alla direzione specificata | |
# Calcola le nuove dimensioni dell'immagine con stretching | |
original_width, original_height = image.size | |
if flip_direction == "h": | |
flipped_image = image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) | |
stretched_height = original_height | |
stretched_width = stretch_value | |
elif flip_direction == "v": | |
flipped_image = image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) | |
stretched_width = original_width | |
stretched_height = stretch_value | |
else: | |
raise ValueError("La direzione specificata non Γ¨ valida. Utilizzare 'orizzontale' o 'verticale'.") | |
# "Stretcha" l'immagine alle nuove dimensioni | |
stretched_image = flipped_image.resize((stretched_width, stretched_height)) | |
return stretched_image | |
def create_noise_image(width, height): | |
noise_array = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8) | |
# Crea un'immagine PIL utilizzando i valori dei pixel generati | |
noise_image = Image.fromarray(noise_array) | |
return noise_image | |
def blend_images(image1, image2, blend_percentage): | |
# Assicurati che le due immagini abbiano le stesse dimensioni | |
if image1.size != image2.size: | |
raise ValueError("Le dimensioni delle due immagini devono essere uguali.") | |
# Blend delle due immagini in base alla percentuale specificata | |
blended_image = Image.blend(image1, image2, blend_percentage) | |
return blended_image | |
def image_paste(main_image, image_to_paste,side): | |
# Ottieni le dimensioni delle immagini | |
width_main, height_main = main_image.size | |
width_paste, height_paste = image_to_paste.size | |
# Calcola le coordinate di incollaggio in base alla posizione desiderata | |
if side == "t": | |
# Crea una nuova immagine che sarΓ la combinazione delle due immagini | |
new_width = width_main | |
new_height = height_main + height_paste | |
new_image = Image.new("RGB", (new_width, new_height)) | |
new_image.paste(image_to_paste, (0,0)) | |
new_image.paste(main_image, (0,height_paste)) | |
elif side == "b": | |
new_width = width_main | |
new_height = height_main + height_paste | |
new_image = Image.new("RGB", (new_width, new_height)) | |
new_image.paste(image_to_paste, (0,height_main)) | |
new_image.paste(main_image, (0,0)) | |
elif side == "r": | |
new_width = width_main + width_paste | |
new_height = height_main | |
new_image = Image.new("RGB", (new_width, new_height)) | |
new_image.paste(image_to_paste, (width_main, 0)) | |
new_image.paste(main_image, (0, 0)) | |
elif side == "l": | |
new_width = width_main + width_paste | |
new_height = height_main | |
new_image = Image.new("RGB", (new_width, new_height)) | |
new_image.paste(image_to_paste, (0, 0)) | |
new_image.paste(main_image, (width_paste, 0)) | |
return new_image | |
def resize_image(image,noise,pixel_size, pixel_to_copy, left, right, top, bottom, temperature=5.0,hue=0,brightness=32,contrast=0,saturation=0,gamma=2): | |
# RIGHT SIDE | |
if right != 0: | |
r_image = extract_pixels(image, "r", pixel_to_copy) | |
r_image= flip_and_stretch(r_image, "h", right) | |
r_image = make_pixelated(r_image, pixel_size) | |
r_noise = create_noise_image(r_image.size[0], r_image.size[1]) | |
r_image = blend_images(r_image, r_noise, noise) | |
r_image = color_correct(r_image, temperature,hue,brightness,contrast,saturation,gamma) | |
r_image= Image.fromarray(r_image) | |
r_image = image_paste(image, r_image,"r") | |
else: | |
r_image = image | |
# LEFT SIDE | |
if left != 0: | |
l_image = extract_pixels(r_image, "l", pixel_to_copy) | |
l_image = flip_and_stretch(l_image, "h", left) | |
l_image = make_pixelated(l_image, pixel_size) | |
l_noise = create_noise_image(l_image.size[0], l_image.size[1]) | |
l_image = blend_images(l_image, l_noise, noise) | |
l_image = color_correct(l_image, temperature,hue,brightness,contrast,saturation,gamma) | |
l_image= Image.fromarray(l_image) | |
l_image = image_paste(r_image, l_image,"l") | |
else: | |
l_image = r_image | |
# TOP | |
if top != 0: | |
t_image = extract_pixels(l_image, "t", pixel_to_copy) | |
t_image = flip_and_stretch(t_image, "v", top) | |
t_image = make_pixelated(t_image, pixel_size) | |
t_noise = create_noise_image(t_image.size[0], t_image.size[1]) | |
t_image = blend_images(t_image, t_noise, noise) | |
t_image = color_correct(t_image, temperature,hue,brightness,contrast,saturation,gamma) | |
t_image= Image.fromarray(t_image) | |
t_image = image_paste(l_image, t_image,"t") | |
else: | |
t_image = l_image | |
# BOTTOM | |
if bottom != 0: | |
b_image = extract_pixels(t_image, "b", pixel_to_copy) | |
b_image = flip_and_stretch(b_image, "v", bottom) | |
b_image = make_pixelated(b_image, pixel_size) | |
b_noise = create_noise_image(b_image.size[0], b_image.size[1]) | |
b_image = blend_images(b_image, b_noise, noise) | |
b_image = color_correct(b_image, temperature,hue,brightness,contrast,saturation,gamma) | |
b_image= Image.fromarray(b_image) | |
b_image = image_paste(t_image, b_image,"b") | |
else: | |
b_image = t_image | |
final_image = b_image | |
return final_image | |
class ImagePadForOutpaintAdvanced: | |
def INPUT_TYPES(s): | |
return { | |
"required": { | |
"image": ("IMAGE",), | |
"left": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), | |
"top": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), | |
"right": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), | |
"bottom": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), | |
"feathering": ("INT", {"default": 40, "min": 0, "max": MAX_RESOLUTION, "step": 1}), | |
"noise": ("FLOAT", {"default": 0.1, "min": 0, "max": 1.0, "step": 0.01}), | |
"pixel_size": ("INT", {"default": 8, "min": 8, "max": 64, "step": 8}), | |
"pixel_to_copy": ("INT", {"default": 32, "min": 0, "max": MAX_RESOLUTION, "step": 1}), | |
"temperature": ("FLOAT",{"default": 0, "min": -100, "max": 100, "step": 5},), | |
"hue": ("FLOAT", {"default": 0, "min": -90, "max": 90, "step": 5}), | |
"brightness": ("FLOAT",{"default": 0, "min": -100, "max": 100, "step": 5},), | |
"contrast": ("FLOAT",{"default": 0, "min": -100, "max": 100, "step": 5},), | |
"saturation": ("FLOAT",{"default": 0, "min": -100, "max": 100, "step": 5},), | |
"gamma": ("FLOAT", {"default": 1, "min": 0.2, "max": 2.2, "step": 0.1}), | |
}, | |
} | |
RETURN_TYPES = ("IMAGE", "MASK") | |
FUNCTION = "expand_image" | |
CATEGORY = "image" | |
def expand_image(self,image,feathering,noise,pixel_size, pixel_to_copy, left, right, top, bottom, temperature=5.0,hue=0,brightness=32,contrast=0,saturation=0,gamma=2): | |
d1, d2, d3, d4 = image.size() | |
#new_image = torch.zeros( | |
# (d1, d2 + top + bottom, d3 + left + right, d4), | |
# dtype=torch.float32, | |
#) | |
#new_image[:, top:top + d2, left:left + d3, :] = image | |
image = tensor2pil(image) | |
#image = Image.fromarray(image.astype(np.uint8)) | |
new_image = resize_image(image,noise,pixel_size, pixel_to_copy, left, right, top, bottom, temperature,hue,brightness,contrast,saturation,gamma) | |
i = ImageOps.exif_transpose(new_image) | |
image = i.convert("RGB") | |
image = np.array(image).astype(np.float32) / 255.0 | |
new_image = torch.from_numpy(image)[None,] | |
mask = torch.ones( | |
(d2 + top + bottom, d3 + left + right), | |
dtype=torch.float32, | |
) | |
t = torch.zeros( | |
(d2, d3), | |
dtype=torch.float32 | |
) | |
if feathering > 0 and feathering * 2 < d2 and feathering * 2 < d3: | |
for i in range(d2): | |
for j in range(d3): | |
dt = i if top != 0 else d2 | |
db = d2 - i if bottom != 0 else d2 | |
dl = j if left != 0 else d3 | |
dr = d3 - j if right != 0 else d3 | |
d = min(dt, db, dl, dr) | |
if d >= feathering: | |
continue | |
v = (feathering - d) / feathering | |
t[i, j] = v * v | |
mask[top:top + d2, left:left + d3] = t | |
return (new_image, mask) | |
# A dictionary that contains all nodes you want to export with their names | |
# NOTE: names should be globally unique | |
NODE_CLASS_MAPPINGS = { | |
"ImagePadForOutpaintAdvanced [n-suite]": ImagePadForOutpaintAdvanced | |
} | |
# A dictionary that contains the friendly/humanly readable titles for the nodes | |
NODE_DISPLAY_NAME_MAPPINGS = { | |
"ImagePadForOutpaintAdvanced [n-suite]": "Image Pad For Outpainting Advanced [π -π ’π €π π £π ]" | |
} | |