Astridkraft's picture
Update app.py
36daa31 verified
raw
history blame
38.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, ImageFont
import time
import os
import tempfile
import random
import re
# === FACE-FIX IMPORT - MIT DETAILLIERTEM DEBUGGING ===
try:
print("Versuch 1: Importiere controlnet_facefix...")
from controlnet_facefix import apply_facefix
FACEFIX_AVAILABLE = True
print("✅ Face-Fix erfolgreich geladen")
except ImportError as e1:
print(f"❌ ImportError: {e1}")
try:
print("Versuch 2: Import mit sys.path...")
import sys
sys.path.append(".")
from controlnet_facefix import apply_facefix
FACEFIX_AVAILABLE = True
print("✅ Face-Fix erfolgreich geladen (mit sys.path)")
except Exception as e2:
print(f"❌ Endgültiger Fehler: {e2}")
FACEFIX_AVAILABLE = False
import traceback
traceback.print_exc()
except Exception as e:
print(f"❌ Anderer Fehler: {e}")
FACEFIX_AVAILABLE = False
import traceback
traceback.print_exc()
# === 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
def is_person_prompt(prompt: str) -> bool:
p = prompt.lower()
print(f"DEBUG: Prüfe '{p}' auf Personen...")
# EINFACHE Version die garantiert funktioniert:
keywords = ["fairy", "person", "man", "woman", "face", "portrait"]
for keyword in keywords:
if keyword in p: # Einfach 'in' ohne Leerzeichen
print(f"✅ Person erkannt durch '{keyword}'")
return True
print(f"❌ Keine Person erkannt")
return False
# === 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
# === BILDVERARBEITUNGS-FUNKTIONEN FÜR BILD-TO-BILD TAB ===
def process_image_upload(image):
"""Verarbeitet den Bild-Upload und aktualisiert die Koordinaten und Vorschau"""
if image is None:
return None, 100, 100, 300, 300
# Automatische Gesichtserkennung
bbox = auto_detect_face_area(image)
# Live-Vorschau erstellen
preview = update_live_preview(image, bbox[0], bbox[1], bbox[2], bbox[3], True)
return preview, bbox[0], bbox[1], bbox[2], bbox[3]
def update_live_preview(image, x1, y1, x2, y2, face_preserve):
"""Erstellt eine Live-Vorschau mit farbigem Rahmen basierend auf dem Modus"""
if image is None:
return None
# Kopie des Bildes für Vorschau
preview = image.copy()
# Zeichne Rahmen basierend auf Modus
draw = ImageDraw.Draw(preview)
# Rahmenfarbe basierend auf Modus
if face_preserve:
# 🟢 GRÜN: Alles außerhalb des Rahmens wird verändert
outline_color = "green"
else:
# 🔴 ROT: Nur innerhalb des Rahmens wird verändert
outline_color = "red"
# Rahmen zeichnen
draw.rectangle([x1, y1, x2, y2], outline=outline_color, width=3)
# Hinweis-Text hinzufügen
try:
# Versuche, eine Schriftart zu laden (falls verfügbar)
try:
font = ImageFont.truetype("arial.ttf", 16)
except:
font = ImageFont.load_default()
text = f"{'🟢 Schutzmodus AN' if face_preserve else '🔴 Schutzmodus AUS'}"
# Text-Hintergrund
text_bbox = draw.textbbox((x1, y1 - 25), text, font=font)
draw.rectangle(text_bbox, fill="white")
# Text
draw.text((x1, y1 - 25), text, fill=outline_color, font=font)
except:
pass # Falls Schrift nicht geladen werden kann
return preview
# === HAUPTFUNKTION: TEXT ZU BILD MIT AUTOMATISCHEM FACE-FIX - KORRIGIERT ===
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"\n" + "="*60)
print(f"🔧 START: Text zu Bild Generierung")
print(f"🔧 Prompt: {prompt}")
print(f"🔧 Modell: {model_id}")
# Personenerkennung ZUERST auf dem ORIGINAL Prompt!
is_person = is_person_prompt(prompt)
print(f"🔧 Person erkannt? {is_person}")
auto_negatives = auto_negative_prompt(prompt)
print(f"🔧 Auto Negative Prompt: {auto_negatives[:100]}...")
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
print(f"🔧 Enhanced Prompt: {enhanced_prompt[:100]}...")
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)
print(f"🔧 Seed: {seed}")
print(f"🔧 Starte Bildgenerierung...")
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]
print(f"✅ Bildgenerierung abgeschlossen")
# AUTOMATISCHER FACE-FIX NUR BEI PERSONEN
if FACEFIX_AVAILABLE and is_person:
print("\n" + "🎭"*30)
print("🎭 PERSON ERKANNT → Starte Face-Fix für perfekte Gesichter...")
print("🎭"*30)
progress(0.9, desc="Perfektioniere Gesicht & Hände...")
try:
# Originalbild speichern für Vergleich
original_image = image.copy()
print("🎭 Originalbild gespeichert, starte Face-Fix...")
# Face-Fix anwenden
fixed_image = apply_facefix(
image=image,
prompt=enhanced_prompt,
negative_prompt=auto_negatives,
seed=seed,
model_id=model_id
)
image = fixed_image
print("✅✅✅ Face-Fix ABGESCHLOSSEN! ✅✅✅")
# Optional: Vergleichsbild erstellen
try:
width, height = image.size
comparison = Image.new('RGB', (width * 2, height))
comparison.paste(original_image, (0, 0))
comparison.paste(image, (width, 0))
# Trennlinie
draw = ImageDraw.Draw(comparison)
draw.line([(width, 0), (width, height)], fill="white", width=2)
# Beschriftung hinzufügen
try:
font = ImageFont.truetype("arial.ttf", 20)
except:
font = ImageFont.load_default()
draw.text((10, 10), "Vor Face-Fix", fill="white", font=font)
draw.text((width + 10, 10), "Nach Face-Fix", fill="white", font=font)
# Vergleichsbild als Option zurückgeben
image = comparison
print("✅ Vergleichsbild erstellt")
except Exception as e:
print(f"⚠️ Vergleichsbild konnte nicht erstellt werden: {e}")
except Exception as e:
print(f"❌❌❌ Face-Fix FEHLGESCHLAGEN: {e} ❌❌❌")
import traceback
traceback.print_exc()
else:
if not FACEFIX_AVAILABLE:
print("ℹ️ Face-Fix nicht verfügbar")
if not is_person:
print("ℹ️ Keine Person im Prompt erkannt")
duration = time.time() - start_time
config = MODEL_CONFIGS.get(model_id, {"name": model_id})
# Status-Nachricht mit Face-Fix Info
if FACEFIX_AVAILABLE and is_person:
status_msg = f"✅ Generiert mit {config.get('name', model_id)} + Face-Fix in {duration:.1f}s"
else:
status_msg = f"Generiert mit {config.get('name', model_id)} in {duration:.1f}s"
print(f"\n" + "="*60)
print(f"✅ FERTIG: {status_msg}")
print(f"="*60 + "\n")
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}")
# Automatischen Negativ-Prompt generieren
auto_negatives = auto_negative_prompt(prompt)
print(f"🤖 Automatisch generierter Negativ-Prompt: {auto_negatives}")
# 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
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
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}")
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]
# Optional: Face-Fix auch auf das transformierte Bild anwenden
if FACEFIX_AVAILABLE and is_person_prompt(prompt):
print("Transformiertes Bild → Wende Face-Fix an...")
try:
generated_image = apply_facefix(
image=generated_image,
prompt=prompt,
negative_prompt=combined_negative_prompt,
seed=seed,
model_id="runwayml/stable-diffusion-v1-5"
)
print("Face-Fix auf transformiertem Bild abgeschlossen!")
except Exception as e:
print(f"Face-Fix auf transformiertem Bild fehlgeschlagen: {e}")
return generated_image
except Exception as e:
print(f"❌ Fehler in img_to_image: {e}")
import traceback
traceback.print_exc()
return None
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;
}
.face-fix-badge {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
margin-left: 10px;
display: inline-block;
}
"""
) as demo:
with gr.Column(visible=True) as content_area:
with gr.Tab("Text zu Bild"):
gr.Markdown("## 🎨 Text zu Bild Generator")
# Face-Fix Info Badge
if FACEFIX_AVAILABLE:
gr.Markdown(
f"""
<div style="background: #e3f2fd; padding: 10px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #2196f3;">
🎭 <strong>Face-Fix aktiviert!</strong> Gesichter werden automatisch verbessert.
</div>
"""
)
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 portrait of a beautiful woman with detailed skin, perfect eyes, sharp focus, cinematic lighting",
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
)