Astridkraft's picture
Update app.py
acd60f4 verified
raw
history blame
32.3 kB
import gradio as gr
from diffusers import StableDiffusionPipeline, StableDiffusionImg2ImgPipeline
from diffusers import StableDiffusionInpaintPipeline, AutoencoderKL
from diffusers import DPMSolverMultistepScheduler, PNDMScheduler
from controlnet_module import controlnet_processor
import torch
from PIL import Image, ImageDraw
import time
import os
import tempfile
import random
import re
# === FACE-FIX IMPORT (automatisch nur bei Personen) ===
try:
from controlnet_facefix import apply_facefix
FACEFIX_AVAILABLE = True
print("Face-Fix (OpenPose_faceonly + Depth) erfolgreich geladen")
except Exception as e:
print(f"Face-Fix nicht verfügbar: {e}")
FACEFIX_AVAILABLE = False
# === OPTIMIERTE EINSTELLUNGEN ===
device = "cuda" if torch.cuda.is_available() else "cpu"
torch_dtype = torch.float16 if device == "cuda" else torch.float32
IMG_SIZE = 512
print(f"Running on: {device}")
# === MODELLKONFIGURATION (NUR 2 MODELLE) ===
MODEL_CONFIGS = {
"runwayml/stable-diffusion-v1-5": {
"name": "Stable Diffusion 1.5 (Universal)",
"description": "Universal model, good all-rounder, reliable results",
"requires_vae": False,
"recommended_steps": 35,
"recommended_cfg": 7.5,
"supports_fp16": True
},
"SG161222/Realistic_Vision_V6.0_B1_noVAE": {
"name": "Realistic Vision V6.0 (Portraits)",
"description": "Best for photorealistic faces, skin details, human portraits",
"requires_vae": True,
"vae_model": "stabilityai/sd-vae-ft-mse",
"recommended_steps": 40,
"recommended_cfg": 7.0,
"supports_fp16": False
}
}
SAFETENSORS_MODELS = ["runwayml/stable-diffusion-v1-5"]
current_model_id = "runwayml/stable-diffusion-v1-5"
# === AUTOMATISCHE NEGATIVE PROMPT GENERIERUNG ===
def auto_negative_prompt(positive_prompt):
p = positive_prompt.lower()
negatives = []
if any(w in p for w in [
"person", "man", "woman", "face", "portrait", "team", "employee",
"people", "crowd", "character", "figure", "human", "child", "baby",
"girl", "boy", "lady", "gentleman", "fairy", "elf", "dwarf", "santa claus",
"mermaid", "angel", "demon", "witch", "wizard", "creature", "being",
"model", "actor", "actress", "celebrity", "avatar", "group"
]):
negatives.append(
"blurry face, lowres face, deformed pupils, bad anatomy, malformed hands, extra fingers, uneven eyes, distorted face, "
"unrealistic skin, mutated, ugly, disfigured, poorly drawn face, "
"missing limbs, extra limbs, fused fingers, too many fingers, bad teeth, "
"mutated hands, long neck, extra wings, multiple wings, grainy face, noisy face, "
"compression artifacts, rendering artifacts, digital artifacts, overprocessed face, oversmoothed face "
)
if any(w in p for w in ["office", "business", "team", "meeting", "corporate", "company", "workplace"]):
negatives.append("overexposed, oversaturated, harsh lighting, watermark, text, logo, brand")
if any(w in p for w in ["product", "packshot", "mockup", "render", "3d", "cgi", "packaging"]):
negatives.append("plastic texture, noisy, overly reflective surfaces, watermark, text, low poly")
if any(w in p for w in ["landscape", "nature", "mountain", "forest", "outdoor", "beach", "sky"]):
negatives.append("blurry, oversaturated, unnatural colors, distorted horizon, floating objects")
if any(w in p for w in ["logo", "symbol", "icon", "typography", "badge", "emblem"]):
negatives.append("watermark, signature, username, text, writing, scribble, messy")
if any(w in p for w in ["building", "architecture", "house", "interior", "room", "facade"]):
negatives.append("deformed, distorted perspective, floating objects, collapsing structure")
base_negatives = "low quality, worst quality, blurry, jpeg artifacts, ugly, deformed"
return base_negatives + ", " + ", ".join(negatives) if negatives else base_negatives
# === PERSONEN-ERKENNUNG (für Face-Fix) ===
def is_person_prompt(prompt: str) -> bool:
p = prompt.lower()
person_keywords = [
"person", "man", "woman", "face", "portrait", "people", "child", "girl", "boy",
"fairy", "elf", "witch", "santa", "nikolaus", "human", "character", "figure"
]
return any(w in p for w in person_keywords)
# === GESICHTSMASKEN-FUNKTIONEN ===
def create_face_mask(image, bbox_coords, face_preserve):
mask = Image.new("L", image.size, 0)
if bbox_coords and all(coord is not None for coord in bbox_coords):
x1, y1, x2, y2 = bbox_coords
draw = ImageDraw.Draw(mask)
if face_preserve:
draw.rectangle([0, 0, image.size[0], image.size[1]], fill=255)
draw.rectangle([x1, y1, x2, y2], fill=0)
else:
draw.rectangle([x1, y1, x2, y2], fill=255)
return mask
def auto_detect_face_area(image):
width, height = image.size
face_size = min(width, height) * 0.4
x1 = (width - face_size) / 2
y1 = (height - face_size) / 4
x2 = x1 + face_size
y2 = y1 + face_size * 1.2
x1, y1 = max(0, int(x1)), max(0, int(y1))
x2, y2 = min(width, int(x2)), min(height, int(y2))
return [x1, y1, x2, y2]
# === PIPELINES ===
pipe_txt2img = None
current_pipe_model_id = None
pipe_img2img = None
def load_txt2img(model_id):
global pipe_txt2img, current_pipe_model_id
if pipe_txt2img is not None and current_pipe_model_id == model_id:
return pipe_txt2img
print(f"Lade Modell: {model_id}")
config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
try:
vae = None
if config.get("requires_vae", False):
vae = AutoencoderKL.from_pretrained(config["vae_model"], torch_dtype=torch_dtype).to(device)
model_params = {
"torch_dtype": torch_dtype,
"safety_checker": None,
"requires_safety_checker": False,
}
if model_id in SAFETENSORS_MODELS:
model_params["use_safetensors"] = True
if config.get("supports_fp16", False) and torch_dtype == torch.float16:
model_params["variant"] = "fp16"
if vae is not None:
model_params["vae"] = vae
pipe_txt2img = StableDiffusionPipeline.from_pretrained(model_id, **model_params).to(device)
pipe_txt2img.enable_attention_slicing()
try:
pipe_txt2img.scheduler = DPMSolverMultistepScheduler.from_config(
pipe_txt2img.scheduler.config,
use_karras_sigmas=True,
algorithm_type="sde-dpmsolver++"
)
except:
pass
current_pipe_model_id = model_id
return pipe_txt2img
except Exception as e:
print(f"Fehler beim Laden, Fallback auf SD 1.5: {e}")
pipe_txt2img = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", torch_dtype=torch_dtype, use_safetensors=True
).to(device)
pipe_txt2img.enable_attention_slicing()
current_pipe_model_id = "runwayml/stable-diffusion-v1-5"
return pipe_txt2img
def load_img2img():
global pipe_img2img
if pipe_img2img is None:
pipe_img2img = StableDiffusionInpaintPipeline.from_pretrained(
"runwayml/stable-diffusion-inpainting", torch_dtype=torch_dtype, safety_checker=None
).to(device)
pipe_img2img.enable_attention_slicing()
pipe_img2img.enable_vae_tiling()
return pipe_img2img
# === CALLBACKS ===
class TextToImageProgressCallback:
def __init__(self, progress, total_steps):
self.progress = progress
self.total_steps = total_steps
def __call__(self, pipe, step, timestep, callback_kwargs):
self.progress(step / self.total_steps, desc="Generierung läuft...")
return callback_kwargs
class ImageToImageProgressCallback:
def __init__(self, progress, total_steps, strength):
self.progress = progress
self.total_steps = total_steps
self.strength = strength
self.actual_steps = None
def __call__(self, pipe, step, timestep, callback_kwargs):
if self.actual_steps is None:
self.actual_steps = int(self.total_steps * self.strength)
progress_val = step / self.actual_steps
self.progress(progress_val, desc="Generierung läuft...")
return callback_kwargs
# === HAUPTFUNKTION: TEXT ZU BILD MIT AUTOMATISCHEM FACE-FIX ===
def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress()):
try:
if not prompt or not prompt.strip():
return None, "Bitte einen Prompt eingeben"
print(f"Generierung mit Modell: {model_id}")
auto_negatives = auto_negative_prompt(prompt)
start_time = time.time()
# Qualitäts-Boost nur wenn nicht vorhanden
quality_keywords = ['masterpiece', 'best quality', 'raw', 'highly detailed', 'ultra realistic']
has_quality = any(kw in prompt.lower() for kw in quality_keywords)
has_weights = bool(re.search(r':\d+\.\d+|\([^)]+:\d', prompt))
enhanced_prompt = f"masterpiece, raw, best quality, highly detailed, {prompt}" if not (has_quality or has_weights) else prompt
progress(0, desc="Lade Modell...")
pipe = load_txt2img(model_id)
seed = random.randint(0, 2**32 - 1)
generator = torch.Generator(device=device).manual_seed(seed)
image = pipe(
prompt=enhanced_prompt,
negative_prompt=auto_negatives,
height=512, width=512,
num_inference_steps=int(steps),
guidance_scale=guidance_scale,
generator=generator,
callback_on_step_end=TextToImageProgressCallback(progress, steps),
callback_on_step_end_tensor_inputs=[],
).images[0]
# AUTOMATISCHER FACE-FIX NUR BEI PERSONEN
if FACEFIX_AVAILABLE and is_person_prompt(enhanced_prompt):
print("Person erkannt → Starte 20-Sekunden Face-Fix...")
progress(0.92, desc="Perfektioniere Gesicht & Hände...")
try:
image = apply_facefix(
image=image,
prompt=enhanced_prompt,
negative_prompt=auto_negatives,
seed=seed,
model_id=model_id
)
print("Face-Fix abgeschlossen!")
except Exception as e:
print(f"Face-Fix fehlgeschlagen (ignoriert): {e}")
duration = time.time() - start_time
config = MODEL_CONFIGS.get(model_id, {"name": model_id})
status_msg = f"Generiert mit {config.get('name', model_id)} in {duration:.1f}s"
return image, status_msg
except Exception as e:
print(f"Fehler in text_to_image: {e}")
import traceback
traceback.print_exc()
return None, f"Fehler: {str(e)}"
def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
face_preserve, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
progress=gr.Progress()):
try:
if image is None:
return None
import time, random
start_time = time.time()
print(f"Img2Img Start → Strength: {strength}, Steps: {steps}, Guidance: {guidance_scale}")
print(f"Prompt: {prompt}")
print(f"Negativ-Prompt: {neg_prompt}")
print(f"Gesicht beibehalten: {face_preserve}")
# ===== NEU: AUTOMATISCHEN NEGATIV-PROMPT GENERIEREN =====
auto_negatives = auto_negative_prompt(prompt)
print(f"🤖 Automatisch generierter Negativ-Prompt: {auto_negatives}")
# ===== NEU: KOMBINIERE MANUELLEN UND AUTOMATISCHEN PROMPT =====
combined_negative_prompt = ""
if neg_prompt and neg_prompt.strip():
# Benutzer hat einen Negativ-Prompt eingegeben
user_neg = neg_prompt.strip()
print(f"👤 Benutzer Negativ-Prompt: {user_neg}")
# Entferne Duplikate zwischen automatischen und manuellen Prompts
# Konvertiere beide in Sets für einfachen Duplikatvergleich
user_words = [word.strip().lower() for word in user_neg.split(",")]
auto_words = [word.strip().lower() for word in auto_negatives.split(",")]
# Starte mit dem Benutzer-Prompt
combined_words = user_words.copy()
# Füge automatische Wörter hinzu, die nicht bereits vorhanden sind
for auto_word in auto_words:
if auto_word and auto_word not in user_words:
combined_words.append(auto_word)
# Zusammenfügen und Duplikate entfernen (für den Fall von Duplikaten innerhalb des gleichen Prompts)
unique_words = []
seen_words = set()
for word in combined_words:
if word and word not in seen_words:
unique_words.append(word)
seen_words.add(word)
combined_negative_prompt = ", ".join(unique_words)
else:
# Kein Benutzer-Prompt, verwende nur den automatischen
combined_negative_prompt = auto_negatives
print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
# ===== ENDE DER NEUEN LOGIK =====
progress(0, desc="Starte Generierung mit ControlNet...")
adj_strength = min(0.85, strength * 1.25)
if face_preserve:
controlnet_strength = adj_strength * 0.8
print(f"🎯 ControlNet Modus: Umgebung beibehalten (Strength = {controlnet_strength:.3f})")
else:
controlnet_strength = adj_strength * 0.5
print(f"🎯 ControlNet Modus: Person beibehalten (Strength = {controlnet_strength:.3f})")
controlnet_steps = min(25, int(steps * 0.8))
print(f"🎯 Steps={steps}, ControlNet-Steps={controlnet_steps}, Strength={controlnet_strength:.3f}")
progress(0.05, desc="Erstelle ControlNet Maps...")
controlnet_output, inpaint_input = controlnet_processor.generate_with_controlnet(
image=image,
prompt=prompt,
negative_prompt=combined_negative_prompt,
steps=controlnet_steps,
guidance_scale=guidance_scale,
controlnet_strength=controlnet_strength,
progress=progress,
keep_environment=face_preserve
)
print(f"✅ ControlNet Output erhalten: {type(controlnet_output)}")
print(f"✅ Inpaint Input erhalten: {type(inpaint_input)}")
progress(0.3, desc="ControlNet abgeschlossen – starte Inpaint...")
pipe = load_img2img()
img_resized = inpaint_input.convert("RGB").resize((512, 512))
adj_guidance = min(guidance_scale, 12.0)
seed = random.randint(0, 2**32 - 1)
generator = torch.Generator(device=device).manual_seed(seed)
print(f"Using seed: {seed}")
mask = None
if bbox_x1 and bbox_y1 and bbox_x2 and bbox_y2:
orig_w, orig_h = image.size
scale_x, scale_y = 512 / orig_w, 512 / orig_h
bbox_coords = [
int(bbox_x1 * scale_x),
int(bbox_y1 * scale_y),
int(bbox_x2 * scale_x),
int(bbox_y2 * scale_y)
]
print(f"Skalierte Koordinaten: {bbox_coords}")
mask = create_face_mask(img_resized, bbox_coords, face_preserve)
if mask:
print("✅ Maske erfolgreich erstellt")
else:
print("⚠️ Keine gültigen Koordinaten – keine Maske")
from diffusers import EulerAncestralDiscreteScheduler
if not isinstance(pipe.scheduler, EulerAncestralDiscreteScheduler):
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
callback = ImageToImageProgressCallback(progress, int(steps), adj_strength)
result = pipe(
prompt=prompt,
negative_prompt=combined_negative_prompt,
image=img_resized,
mask_image=mask,
strength=adj_strength,
num_inference_steps=int(steps),
guidance_scale=adj_guidance,
generator=generator,
callback_on_step_end=callback,
callback_on_step_end_tensor_inputs=[],
)
end_time = time.time()
print(f"🕒 Dauer: {end_time - start_time:.2f} Sekunden")
generated_image = result.images[0]
return generated_image
except Exception as e:
print(f"❌ Fehler in img_to_image: {e}")
import traceback
traceback.print_exc()
return None
def update_bbox_from_image(image):
"""Aktualisiert die Bounding-Box-Koordinaten wenn ein Bild hochgeladen wird"""
if image is None:
return None, None, None, None
bbox = auto_detect_face_area(image)
return bbox[0], bbox[1], bbox[2], bbox[3]
def update_model_settings(model_id):
"""Aktualisiert die empfohlenen Einstellungen basierend auf Modellauswahl"""
config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
return (
config["recommended_steps"], # steps
config["recommended_cfg"], # guidance_scale
f"📊 Empfohlene Einstellungen: {config['steps']} Steps, CFG {config['cfg']}"
)
def main_ui():
with gr.Blocks(
title="AI Image Generator",
theme=gr.themes.Base(),
css="""
.info-box {
background-color: #f8f4f0;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #8B7355;
margin: 20px 0;
}
.clickable-file {
color: #1976d2;
cursor: pointer;
text-decoration: none;
font-family: 'Monaco', 'Consolas', monospace;
background: #e3f2fd;
padding: 2px 6px;
border-radius: 4px;
border: 1px solid #bbdefb;
}
.clickable-file:hover {
background: #bbdefb;
text-decoration: underline;
}
.model-info-box {
background: #e8f4fd;
padding: 12px;
border-radius: 6px;
margin: 10px 0;
border-left: 4px solid #2196f3;
font-size: 14px;
}
#generate-button {
background-color: #0080FF !important;
border: none !important;
margin: 20px auto !important;
display: block !important;
font-weight: 600;
width: 280px;
}
#generate-button:hover {
background-color: #0066CC !important;
}
.hint-box {
margin-top: 20px;
}
.custom-text {
font-size: 25px !important;
}
.image-upload .svelte-1p4f8co {
display: block !important;
}
.preview-box {
border: 2px dashed #ccc;
padding: 10px;
border-radius: 8px;
margin: 10px 0;
}
.mode-red {
border: 3px solid #ff4444 !important;
}
.mode-green {
border: 3px solid #44ff44 !important;
}
.coordinate-sliders {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
}
.gr-checkbox .wrap .text-gray {
font-size: 14px !important;
font-weight: 600 !important;
line-height: 1.4 !important;
}
.status-message {
padding: 10px;
border-radius: 5px;
margin: 10px 0;
text-align: center;
font-weight: 500;
}
.status-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
"""
) as demo:
with gr.Column(visible=True) as content_area:
with gr.Tab("Text zu Bild"):
gr.Markdown("## 🎨 Text zu Bild Generator")
with gr.Row():
with gr.Column(scale=2):
# Modellauswahl Dropdown (NUR 2 MODELLE)
model_dropdown = gr.Dropdown(
choices=[
(config["name"], model_id)
for model_id, config in MODEL_CONFIGS.items()
],
value="runwayml/stable-diffusion-v1-5",
label="📁 Modellauswahl",
info="🏠 Universal vs 👤 Portraits"
)
# Modellinformationen Box
model_info_box = gr.Markdown(
value="<div class='model-info-box'>"
"**🏠 Stable Diffusion 1.5 (Universal)**<br>"
"Universal model, good all-rounder, reliable results<br>"
"Empfohlene Einstellungen: 35 Steps, CFG 7.5"
"</div>",
label="Modellinformationen"
)
with gr.Column(scale=3):
txt_input = gr.Textbox(
placeholder="z.B. ultra realistic mountain landscape at sunrise, soft mist over the valley, detailed foliage, crisp textures, depth of field, sunlight rays through clouds, shot on medium format camera, 8k, HDR, hyper-detailed, natural lighting, masterpiece",
lines=3,
label="🎯 Prompt (Englisch)",
info="Beschreibe detailliert, was du sehen möchtest. Negative Prompts werden automatisch generiert."
)
with gr.Row():
with gr.Column():
txt_steps = gr.Slider(
minimum=10, maximum=100, value=35, step=1,
label="⚙️ Inferenz-Schritte",
info="Mehr Schritte = bessere Qualität, aber langsamer (20-50 empfohlen)"
)
with gr.Column():
txt_guidance = gr.Slider(
minimum=1.0, maximum=20.0, value=7.5, step=0.5,
label="🎛️ Prompt-Stärke (CFG Scale)",
info="Wie stark der Prompt befolgt wird (7-12 für gute Balance)"
)
# Status-Nachricht
status_output = gr.Markdown(
value="",
elem_classes="status-message"
)
generate_btn = gr.Button("🚀 Bild generieren", variant="primary", elem_id="generate-button")
with gr.Row():
txt_output = gr.Image(
label="🖼️ Generiertes Bild",
show_download_button=True,
type="pil",
height=400
)
# Event-Handler für Modelländerung
def update_model_info(model_id):
config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
info_html = f"""
<div class='model-info-box'>
<strong>{config['name']}</strong><br>
{config['description']}<br>
<em>Empfohlene Einstellungen: {config['recommended_steps']} Steps, CFG {config['recommended_cfg']}</em>
</div>
"""
return info_html, config["recommended_steps"], config["recommended_cfg"]
model_dropdown.change(
fn=update_model_info,
inputs=[model_dropdown],
outputs=[model_info_box, txt_steps, txt_guidance]
)
generate_btn.click(
fn=text_to_image,
inputs=[txt_input, model_dropdown, txt_steps, txt_guidance],
outputs=[txt_output, status_output],
concurrency_limit=1
)
with gr.Tab("Bild zu Bild"):
gr.Markdown("## 🖼️ Bild zu Bild Transformation")
with gr.Row():
with gr.Column():
img_input = gr.Image(
type="pil",
label="📤 Eingabebild",
height=300,
sources=["upload"],
elem_id="image-upload"
)
with gr.Column():
preview_output = gr.Image(
label="🎯 Live-Vorschau mit Maske",
height=300,
interactive=False,
show_download_button=False
)
with gr.Row():
face_preserve = gr.Checkbox(
label="🛡️ Schutzmodus",
value=True,
info="🟢 AN: Alles AUSSERHALB des gelben Rahmens verändern | 🔴 AUS: Nur INNERHALB des gelben Rahmens verändern"
)
with gr.Row():
gr.Markdown("### 📐 Bildelementbereich anpassen")
with gr.Row():
with gr.Column():
bbox_x1 = gr.Slider(
label="← Links (x1)",
minimum=0, maximum=512, value=100, step=1,
info="Linke Kante des Bildelementbereichs"
)
with gr.Column():
bbox_y1 = gr.Slider(
label="↑ Oben (y1)",
minimum=0, maximum=512, value=100, step=1,
info="Obere Kante des Bildelementbereichs"
)
with gr.Row():
with gr.Column():
bbox_x2 = gr.Slider(
label="→ Rechts (x2)",
minimum=0, maximum=512, value=300, step=1,
info="Rechte Kante des Bildelementbereichs"
)
with gr.Column():
bbox_y2 = gr.Slider(
label="↓ Unten (y2)",
minimum=0, maximum=512, value=300, step=1,
info="Untere Kante des Bildelementbereichs"
)
with gr.Row():
with gr.Column():
img_prompt = gr.Textbox(
placeholder="change background to beach with palm trees, keep person unchanged, sunny day",
lines=2,
label="🎯 Transformations-Prompt (Englisch)",
info="Was soll verändert werden? Sei spezifisch."
)
with gr.Column():
img_neg_prompt = gr.Textbox(
placeholder="blurry, deformed, ugly, bad anatomy, extra limbs, poorly drawn hands",
lines=2,
label="🚫 Negativ-Prompt (Englisch)",
info="Was soll vermieden werden? Unerwünschte Elemente auflisten."
)
with gr.Row():
with gr.Column():
strength_slider = gr.Slider(
minimum=0.1, maximum=0.9, value=0.4, step=0.05,
label="💪 Veränderungs-Stärke",
info="0.1-0.3: Leichte Anpassungen, 0.4-0.6: Mittlere Veränderungen, 0.7-0.9: Starke Umgestaltung"
)
with gr.Column():
img_steps = gr.Slider(
minimum=10, maximum=100, value=35, step=1,
label="⚙️ Inferenz-Schritte",
info="Anzahl der Verarbeitungsschritte (25-45 für gute Ergebnisse)"
)
with gr.Column():
img_guidance = gr.Slider(
minimum=1.0, maximum=20.0, value=7.5, step=0.5,
label="🎛️ Prompt-Stärke",
info="Einfluss des Prompts auf das Ergebnis (6-10 für natürliche Ergebnisse)"
)
with gr.Row():
gr.Markdown(
"### 📋 Hinweise:\n"
"• **🆕 Automatische Bildelementerkennung** setzt Koordinaten beim Upload\n"
"• **🆕 Live-Vorschau** zeigt farbige Rahmen je nach Modus (🔴 Rot / 🟢 Grün)\n"
"• **🆕 Koordinaten-Schieberegler** für präzise Anpassung mit Live-Update\n"
"• **Koordinaten nur bei erkennbaren Verzerrungen anpassen** (Bereiche leicht verschieben)"
)
transform_btn = gr.Button("🔄 Bild transformieren", variant="primary")
with gr.Row():
img_output = gr.Image(
label="✨ Transformiertes Bild",
show_download_button=True,
type="pil",
height=400
)
img_input.change(
fn=process_image_upload,
inputs=[img_input],
outputs=[preview_output, bbox_x1, bbox_y1, bbox_x2, bbox_y2]
)
coordinate_inputs = [img_input, bbox_x1, bbox_y1, bbox_x2, bbox_y2, face_preserve]
for slider in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]:
slider.change(
fn=update_live_preview,
inputs=coordinate_inputs,
outputs=preview_output
)
face_preserve.change(
fn=update_live_preview,
inputs=coordinate_inputs,
outputs=preview_output
)
transform_btn.click(
fn=img_to_image,
inputs=[
img_input, img_prompt, img_neg_prompt,
strength_slider, img_steps, img_guidance,
face_preserve, bbox_x1, bbox_y1, bbox_x2, bbox_y2
],
outputs=img_output,
concurrency_limit=1
)
return demo
if __name__ == "__main__":
demo = main_ui()
demo.queue(max_size=3)
demo.launch(
server_name="0.0.0.0",
server_port=7860,
max_file_size="10MB",
show_error=True,
share=False,
ssr_mode=False # SSR deaktivieren für Stabilität
)