|
|
import modules.scripts as scripts
|
|
|
import gradio as gr
|
|
|
from modules import images, shared
|
|
|
from modules.processing import process_images, Processed
|
|
|
from modules.shared import opts, state
|
|
|
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageChops, ImageOps
|
|
|
import numpy as np
|
|
|
import random
|
|
|
import os
|
|
|
|
|
|
class SomeImageEffectsScript(scripts.Script):
|
|
|
def __init__(self):
|
|
|
super().__init__()
|
|
|
self.overlay_files = []
|
|
|
self.update_overlay_files()
|
|
|
|
|
|
def update_overlay_files(self):
|
|
|
|
|
|
print(f"Current working directory: {os.getcwd()}")
|
|
|
print(f"Script location: {os.path.dirname(os.path.abspath(__file__))}")
|
|
|
|
|
|
|
|
|
potential_dirs = [
|
|
|
os.path.join(scripts.basedir(), "overlays"),
|
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "overlays"),
|
|
|
os.path.join(os.getcwd(), "overlays"),
|
|
|
]
|
|
|
|
|
|
for overlay_dir in potential_dirs:
|
|
|
print(f"Checking for overlays in: {overlay_dir}")
|
|
|
if os.path.exists(overlay_dir):
|
|
|
self.overlay_files = [f for f in os.listdir(overlay_dir) if self.is_image_file(f)]
|
|
|
print(f"Found {len(self.overlay_files)} overlay files in {overlay_dir}")
|
|
|
return
|
|
|
|
|
|
print("Overlay directory not found in any of the checked locations.")
|
|
|
|
|
|
|
|
|
def is_image_file(self, filename):
|
|
|
image_extensions = ['.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.webp']
|
|
|
return any(filename.lower().endswith(ext) for ext in image_extensions)
|
|
|
|
|
|
def title(self):
|
|
|
return "Advanced Image Effects"
|
|
|
|
|
|
def show(self, is_img2img):
|
|
|
return scripts.AlwaysVisible
|
|
|
|
|
|
def ui(self, is_img2img):
|
|
|
with gr.Group():
|
|
|
with gr.Accordion("Some Image Effects", open=False):
|
|
|
save_original = gr.Checkbox(label="Save Original Image", value=True)
|
|
|
|
|
|
with gr.Row():
|
|
|
enable_grain = gr.Checkbox(label="Enable Grain", value=False)
|
|
|
enable_vignette = gr.Checkbox(label="Enable Vignette", value=False)
|
|
|
enable_random_blur = gr.Checkbox(label="Enable Random Blur", value=False)
|
|
|
enable_color_offset = gr.Checkbox(label="Enable Color Offset", value=False)
|
|
|
|
|
|
with gr.Row():
|
|
|
grain_intensity = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.3, label="Grain Intensity")
|
|
|
|
|
|
with gr.Row():
|
|
|
vignette_intensity = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.3, label="Vignette Intensity")
|
|
|
vignette_feather = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.3, label="Vignette Feather")
|
|
|
vignette_roundness = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.5, label="Vignette Roundness")
|
|
|
|
|
|
with gr.Row():
|
|
|
blur_max_size = gr.Slider(minimum=0.0, maximum=0.5, step=0.05, value=0.2, label="Max Blur Size (% of image)")
|
|
|
blur_strength = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=3.0, label="Blur Strength")
|
|
|
|
|
|
with gr.Row():
|
|
|
color_offset_x = gr.Slider(minimum=-50, maximum=50, step=1, value=0, label="Color Offset X")
|
|
|
color_offset_y = gr.Slider(minimum=-50, maximum=50, step=1, value=0, label="Color Offset Y")
|
|
|
|
|
|
with gr.Row():
|
|
|
enable_overlay = gr.Checkbox(label="Enable Overlay", value=False)
|
|
|
overlay_file = gr.Dropdown(label="Overlay File", choices=self.overlay_files)
|
|
|
overlay_fit = gr.Dropdown(label="Overlay Fit", choices=["stretch", "fit_out"], value="stretch")
|
|
|
|
|
|
with gr.Row():
|
|
|
overlay_opacity = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.5, label="Overlay Opacity")
|
|
|
overlay_blend_mode = gr.Dropdown(label="Overlay Blend Mode", choices=[
|
|
|
"normal", "multiply", "screen", "overlay", "darken", "lighten",
|
|
|
"color_dodge", "color_burn", "hard_light", "soft_light", "difference",
|
|
|
"exclusion", "hue", "saturation", "color", "luminosity"
|
|
|
], value="normal")
|
|
|
|
|
|
with gr.Row():
|
|
|
enable_luminosity = gr.Checkbox(label="Enable Luminosity Adjustment", value=False)
|
|
|
luminosity_factor = gr.Slider(minimum=-1.0, maximum=1.0, step=0.05, value=0, label="Luminosity Factor")
|
|
|
|
|
|
with gr.Row():
|
|
|
enable_contrast = gr.Checkbox(label="Enable Contrast Adjustment", value=False)
|
|
|
contrast_factor = gr.Slider(minimum=0.0, maximum=2.0, step=0.05, value=1, label="Contrast Factor")
|
|
|
|
|
|
with gr.Row():
|
|
|
enable_hue = gr.Checkbox(label="Enable Hue Adjustment", value=False)
|
|
|
hue_factor = gr.Slider(minimum=-180, maximum=180, step=1, value=0, label="Hue Shift")
|
|
|
|
|
|
with gr.Row():
|
|
|
enable_saturation = gr.Checkbox(label="Enable Saturation Adjustment", value=False)
|
|
|
saturation_factor = gr.Slider(minimum=0.0, maximum=2.0, step=0.05, value=1, label="Saturation Factor")
|
|
|
|
|
|
return [save_original, enable_grain, enable_vignette, enable_random_blur, enable_color_offset,
|
|
|
grain_intensity, vignette_intensity, vignette_feather, vignette_roundness,
|
|
|
blur_max_size, blur_strength, color_offset_x, color_offset_y,
|
|
|
enable_overlay, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode,
|
|
|
enable_luminosity, luminosity_factor, enable_contrast, contrast_factor,
|
|
|
enable_hue, hue_factor, enable_saturation, saturation_factor]
|
|
|
|
|
|
def process(self, p, *args):
|
|
|
enabled_effects = []
|
|
|
if args[1]:
|
|
|
enabled_effects.append("Grain")
|
|
|
if args[2]:
|
|
|
enabled_effects.append("Vignette")
|
|
|
if args[3]:
|
|
|
enabled_effects.append("Random Blur")
|
|
|
if args[4]:
|
|
|
enabled_effects.append("Color Offset")
|
|
|
if args[13]:
|
|
|
enabled_effects.append("Overlay")
|
|
|
if args[18]:
|
|
|
enabled_effects.append("Luminosity")
|
|
|
if args[20]:
|
|
|
enabled_effects.append("Contrast")
|
|
|
if args[22]:
|
|
|
enabled_effects.append("Hue")
|
|
|
if args[24]:
|
|
|
enabled_effects.append("Saturation")
|
|
|
|
|
|
if enabled_effects:
|
|
|
p.extra_generation_params["Some Image Effects"] = ", ".join(enabled_effects)
|
|
|
|
|
|
|
|
|
def postprocess_image(self, p, pp, *args):
|
|
|
save_original, enable_grain, enable_vignette, enable_random_blur, enable_color_offset, \
|
|
|
grain_intensity, vignette_intensity, vignette_feather, vignette_roundness, \
|
|
|
blur_max_size, blur_strength, color_offset_x, color_offset_y, \
|
|
|
enable_overlay, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode, \
|
|
|
enable_luminosity, luminosity_factor, enable_contrast, contrast_factor, \
|
|
|
enable_hue, hue_factor, enable_saturation, saturation_factor = args
|
|
|
|
|
|
if hasattr(pp, 'image'):
|
|
|
if save_original:
|
|
|
self.save_original_image(pp.image)
|
|
|
pp.image = self.add_effects(pp.image, *args)
|
|
|
elif hasattr(pp, 'images'):
|
|
|
for i, image in enumerate(pp.images):
|
|
|
if save_original:
|
|
|
self.save_original_image(image)
|
|
|
pp.images[i] = self.add_effects(image, *args)
|
|
|
|
|
|
|
|
|
def add_effects(self, img, save_original, enable_grain, enable_vignette, enable_random_blur, enable_color_offset,
|
|
|
grain_intensity, vignette_intensity, vignette_feather, vignette_roundness,
|
|
|
blur_max_size, blur_strength, color_offset_x, color_offset_y,
|
|
|
enable_overlay, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode):
|
|
|
if enable_grain:
|
|
|
img = self.add_grain(img, grain_intensity)
|
|
|
|
|
|
if enable_vignette:
|
|
|
img = self.add_vignette(img, vignette_intensity, vignette_feather, vignette_roundness)
|
|
|
|
|
|
if enable_random_blur:
|
|
|
img = self.add_random_blur(img, blur_max_size, blur_strength)
|
|
|
|
|
|
if enable_color_offset:
|
|
|
img = self.add_color_offset(img, color_offset_x, color_offset_y)
|
|
|
|
|
|
if enable_overlay and overlay_file:
|
|
|
img = self.add_overlay(img, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode)
|
|
|
|
|
|
return img
|
|
|
|
|
|
def add_grain(self, img, intensity):
|
|
|
img_np = np.array(img)
|
|
|
noise = np.random.randn(*img_np.shape) * 255 * intensity
|
|
|
noisy_img = np.clip(img_np + noise, 0, 255).astype(np.uint8)
|
|
|
return Image.fromarray(noisy_img)
|
|
|
|
|
|
def add_vignette(self, img, intensity, feather, roundness):
|
|
|
width, height = img.size
|
|
|
mask = Image.new('L', (width, height), 255)
|
|
|
draw = ImageDraw.Draw(mask)
|
|
|
|
|
|
x_center, y_center = width // 2, height // 2
|
|
|
max_radius = min(width, height) // 2
|
|
|
|
|
|
for i in range(max_radius):
|
|
|
alpha = int(255 * (1 - (i / max_radius) ** roundness) * intensity)
|
|
|
draw.ellipse([x_center - i, y_center - i, x_center + i, y_center + i], fill=alpha)
|
|
|
|
|
|
mask = mask.filter(ImageFilter.GaussianBlur(radius=max_radius * feather))
|
|
|
|
|
|
enhancer = ImageEnhance.Brightness(img)
|
|
|
darkened = enhancer.enhance(1 - intensity * 0.5)
|
|
|
|
|
|
return Image.composite(darkened, img, mask)
|
|
|
|
|
|
def add_random_blur(self, img, max_size, strength):
|
|
|
width, height = img.size
|
|
|
blur_size = int(min(width, height) * max_size)
|
|
|
x = random.randint(0, width - blur_size)
|
|
|
y = random.randint(0, height - blur_size)
|
|
|
|
|
|
mask = Image.new('L', (width, height), 0)
|
|
|
draw = ImageDraw.Draw(mask)
|
|
|
draw.ellipse([x, y, x + blur_size, y + blur_size], fill=255)
|
|
|
mask = mask.filter(ImageFilter.GaussianBlur(radius=blur_size // 4))
|
|
|
|
|
|
blurred = img.filter(ImageFilter.GaussianBlur(radius=strength))
|
|
|
return Image.composite(blurred, img, mask)
|
|
|
|
|
|
def add_color_offset(self, img, offset_x, offset_y):
|
|
|
r, g, b = img.split()
|
|
|
r = ImageChops.offset(r, offset_x, offset_y)
|
|
|
b = ImageChops.offset(b, -offset_x, -offset_y)
|
|
|
return Image.merge('RGB', (r, g, b))
|
|
|
|
|
|
def add_overlay(self, img, overlay_file, overlay_fit, opacity, blend_mode):
|
|
|
if img is None:
|
|
|
print("Error: Input image is None")
|
|
|
return None
|
|
|
|
|
|
|
|
|
potential_dirs = [
|
|
|
os.path.join(scripts.basedir(), "overlays"),
|
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "overlays"),
|
|
|
os.path.join(os.getcwd(), "overlays"),
|
|
|
]
|
|
|
|
|
|
overlay_path = None
|
|
|
for overlay_dir in potential_dirs:
|
|
|
temp_path = os.path.join(overlay_dir, overlay_file)
|
|
|
if os.path.exists(temp_path):
|
|
|
overlay_path = temp_path
|
|
|
break
|
|
|
|
|
|
if not overlay_path:
|
|
|
print(f"Overlay file not found: {overlay_file}")
|
|
|
return img
|
|
|
|
|
|
try:
|
|
|
overlay = Image.open(overlay_path).convert("RGBA")
|
|
|
except Exception as e:
|
|
|
print(f"Error opening overlay file: {e}")
|
|
|
return img
|
|
|
|
|
|
|
|
|
if overlay_fit == "stretch":
|
|
|
overlay = overlay.resize(img.size, Image.LANCZOS)
|
|
|
elif overlay_fit == "fit_out":
|
|
|
img_ratio = img.width / img.height
|
|
|
overlay_ratio = overlay.width / overlay.height
|
|
|
if img_ratio > overlay_ratio:
|
|
|
new_width = img.width
|
|
|
new_height = int(new_width / overlay_ratio)
|
|
|
else:
|
|
|
new_height = img.height
|
|
|
new_width = int(new_height * overlay_ratio)
|
|
|
overlay = overlay.resize((new_width, new_height), Image.LANCZOS)
|
|
|
|
|
|
|
|
|
left = (overlay.width - img.width) // 2
|
|
|
top = (overlay.height - img.height) // 2
|
|
|
right = left + img.width
|
|
|
bottom = top + img.height
|
|
|
overlay = overlay.crop((left, top, right, bottom))
|
|
|
|
|
|
|
|
|
overlay = Image.blend(Image.new('RGBA', img.size, (0, 0, 0, 0)), overlay, opacity)
|
|
|
|
|
|
|
|
|
if img.mode != 'RGBA':
|
|
|
img = img.convert('RGBA')
|
|
|
|
|
|
|
|
|
if img.mode != 'RGBA':
|
|
|
img = img.convert('RGBA')
|
|
|
|
|
|
if blend_mode == "multiply":
|
|
|
blended = ImageChops.multiply(img, overlay)
|
|
|
elif blend_mode == "screen":
|
|
|
blended = ImageChops.screen(img, overlay)
|
|
|
elif blend_mode == "overlay":
|
|
|
blended = self.overlay_blend(img, overlay)
|
|
|
elif blend_mode == "darken":
|
|
|
blended = ImageChops.darker(img, overlay)
|
|
|
elif blend_mode == "lighten":
|
|
|
blended = ImageChops.lighter(img, overlay)
|
|
|
elif blend_mode == "color_dodge":
|
|
|
blended = self.color_dodge(img, overlay)
|
|
|
elif blend_mode == "color_burn":
|
|
|
blended = self.color_burn(img, overlay)
|
|
|
elif blend_mode == "hard_light":
|
|
|
blended = self.hard_light(img, overlay)
|
|
|
elif blend_mode == "soft_light":
|
|
|
blended = self.soft_light(img, overlay)
|
|
|
elif blend_mode == "difference":
|
|
|
blended = ImageChops.difference(img, overlay)
|
|
|
elif blend_mode == "exclusion":
|
|
|
blended = self.exclusion(img, overlay)
|
|
|
elif blend_mode == "hue":
|
|
|
blended = self.hue_blend(img, overlay)
|
|
|
elif blend_mode == "saturation":
|
|
|
blended = self.saturation_blend(img, overlay)
|
|
|
elif blend_mode == "color":
|
|
|
blended = self.color_blend(img, overlay)
|
|
|
elif blend_mode == "luminosity":
|
|
|
blended = self.luminosity_blend(img, overlay)
|
|
|
else:
|
|
|
blended = Image.alpha_composite(img, overlay)
|
|
|
|
|
|
return blended.convert("RGB")
|
|
|
|
|
|
def adjust_luminosity(self, img, factor):
|
|
|
return ImageEnhance.Brightness(img).enhance(1 + factor)
|
|
|
|
|
|
def adjust_contrast(self, img, factor):
|
|
|
return ImageEnhance.Contrast(img).enhance(factor)
|
|
|
|
|
|
def adjust_hue(self, img, shift):
|
|
|
img = img.convert('HSV')
|
|
|
h, s, v = img.split()
|
|
|
h = h.point(lambda x: (x + shift) % 256)
|
|
|
return Image.merge('HSV', (h, s, v)).convert('RGB')
|
|
|
|
|
|
def adjust_saturation(self, img, factor):
|
|
|
return ImageEnhance.Color(img).enhance(factor)
|
|
|
|
|
|
def add_effects(self, img, save_original, enable_grain, enable_vignette, enable_random_blur, enable_color_offset,
|
|
|
grain_intensity, vignette_intensity, vignette_feather, vignette_roundness,
|
|
|
blur_max_size, blur_strength, color_offset_x, color_offset_y,
|
|
|
enable_overlay, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode,
|
|
|
enable_luminosity, luminosity_factor, enable_contrast, contrast_factor,
|
|
|
enable_hue, hue_factor, enable_saturation, saturation_factor):
|
|
|
if img is None:
|
|
|
print("Error: Input image is None")
|
|
|
return None
|
|
|
|
|
|
if enable_grain:
|
|
|
img = self.add_grain(img, grain_intensity)
|
|
|
|
|
|
if enable_vignette:
|
|
|
img = self.add_vignette(img, vignette_intensity, vignette_feather, vignette_roundness)
|
|
|
|
|
|
if enable_random_blur:
|
|
|
img = self.add_random_blur(img, blur_max_size, blur_strength)
|
|
|
|
|
|
if enable_color_offset:
|
|
|
img = self.add_color_offset(img, color_offset_x, color_offset_y)
|
|
|
|
|
|
if enable_overlay and overlay_file:
|
|
|
img = self.add_overlay(img, overlay_file, overlay_fit, overlay_opacity, overlay_blend_mode)
|
|
|
|
|
|
if enable_luminosity:
|
|
|
img = self.adjust_luminosity(img, luminosity_factor)
|
|
|
|
|
|
if enable_contrast:
|
|
|
img = self.adjust_contrast(img, contrast_factor)
|
|
|
|
|
|
if enable_hue:
|
|
|
img = self.adjust_hue(img, hue_factor)
|
|
|
|
|
|
if enable_saturation:
|
|
|
img = self.adjust_saturation(img, saturation_factor)
|
|
|
|
|
|
return img
|
|
|
|
|
|
def save_original_image(self, img):
|
|
|
save_dir = getattr(shared.opts, 'outdir_samples', None) or getattr(shared.opts, 'outdir_txt2img_samples', None) or getattr(shared.opts, 'outdir_img2img_samples', None) or getattr(shared.opts, 'outdir_extras_samples', None) or os.getcwd()
|
|
|
|
|
|
save_dir = os.path.join(save_dir, "originals")
|
|
|
os.makedirs(save_dir, exist_ok=True)
|
|
|
|
|
|
|
|
|
job_name = shared.state.job if shared.state.job else "image"
|
|
|
base_name, ext = os.path.splitext(job_name)
|
|
|
if not ext:
|
|
|
ext = ".png"
|
|
|
|
|
|
filename = f"{base_name}_original{ext}"
|
|
|
save_path = os.path.join(save_dir, filename)
|
|
|
|
|
|
try:
|
|
|
img.save(save_path)
|
|
|
print(f"Original image saved to: {save_path}")
|
|
|
except Exception as e:
|
|
|
print(f"Error saving original image: {e}")
|
|
|
|