Test / ui.py
Astridkraft's picture
Update ui.py
47b01fe verified
"""
Gradio UI für AI Image Generator - Mit Mock-Funktionen für CPU-Testing
"""
import gradio as gr
from PIL import Image, ImageDraw
import random
import time
# ==================== KONSTANTEN ====================
MAX_IMAGE_SIZE = 4096 # Maximale Bildgröße für Verarbeitung
# ==================== MOCK-FUNKTIONEN ====================
def mock_text_to_image(prompt, model_id, steps, guidance_scale, progress=None):
"""Mock für Text-zu-Bild: Erzeugt einfaches Farbbild"""
print(f"📝 Mock text_to_image aufgerufen: '{prompt[:50]}...'")
if progress:
for i in range(10):
time.sleep(0.05)
progress((i+1)/10, desc="Mock-Generierung läuft...")
colors = ["lightblue", "lightgreen", "lavender", "peachpuff"]
img = Image.new('RGB', (512, 512), color=random.choice(colors))
draw = ImageDraw.Draw(img)
text = f"Mock: {prompt[:30]}..." if len(prompt) > 30 else f"Mock: {prompt}"
draw.text((50, 256), text, fill="black")
status = f"✅ Mock: '{prompt[:30]}...' würde mit {model_id} generiert (Steps: {steps}, CFG: {guidance_scale})"
return img, status
def mock_img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
mode, bbox_x1, bbox_y1, bbox_x2, bbox_y2, progress=None):
"""Mock für Bild-zu-Bild: Fügt Rahmen und Text hinzu"""
print(f"🎨 Mock img_to_image aufgerufen: Modus={mode}, Prompt='{prompt[:30]}...'")
if not image:
return None, None, None, None, None
if progress:
for i in range(10):
time.sleep(0.05)
progress((i+1)/10, desc=f"Mock-{mode} läuft...")
img = image.copy().convert("RGB")
draw = ImageDraw.Draw(img)
colors = {
"environment_change": "green",
"focus_change": "orange",
"face_only_change": "red"
}
border_color = colors.get(mode, "blue")
draw.rectangle([20, 20, img.width-20, img.height-20],
outline=border_color, width=10)
if all(v is not None for v in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]):
x1, y1 = min(bbox_x1, bbox_x2), min(bbox_y1, bbox_y2)
x2, y2 = max(bbox_x1, bbox_x2), max(bbox_y1, bbox_y2)
draw.rectangle([x1, y1, x2, y2], outline="yellow", width=5)
draw.text((x1+5, y1+5), f"{mode}", fill="white")
draw.text((30, 30), f"Mock: {mode}", fill=border_color)
draw.text((30, 60), f"Strength: {strength}", fill="black")
mask_preview = Image.new('RGB', (256, 256), color='gray')
controlnet_map = Image.new('RGB', (256, 256), color='darkblue')
canny_map = Image.new('RGB', (256, 256), color='black')
status = f"✅ Mock: {mode} angewendet (Strength: {strength}, Steps: {steps})"
print(status)
return img, mask_preview, mask_preview, controlnet_map, canny_map
def sort_coordinates(x1, y1, x2, y2):
"""Sortiert Koordinaten, so dass x1 <= x2 und y1 <= y2"""
sorted_x1 = min(x1, x2)
sorted_x2 = max(x1, x2)
sorted_y1 = min(y1, y2)
sorted_y2 = max(y1, y2)
return sorted_x1, sorted_y1, sorted_x2, sorted_y2
def create_preview_image(image, bbox_coords, mode):
"""Vorschau mit dynamischer Rahmendicke"""
if image is None:
return None
preview = image.copy()
draw = ImageDraw.Draw(preview)
if mode == "environment_change":
border_color = (0, 255, 0, 180)
mode_text = "UMGEBUNG ÄNDERN"
box_color = (255, 255, 0, 200)
text_bg_color = (0, 128, 0, 160)
elif mode == "focus_change":
border_color = (255, 165, 0, 180)
mode_text = "FOCUS VERÄNDERN"
box_color = (255, 0, 0, 200)
text_bg_color = (255, 140, 0, 160)
elif mode == "face_only_change":
border_color = (255, 0, 0, 180)
mode_text = "NUR BEREICH"
box_color = (255, 0, 0, 200)
text_bg_color = (128, 0, 0, 160)
else:
border_color = (128, 128, 128, 180)
mode_text = "UNBEKANNT"
box_color = (128, 128, 128, 200)
text_bg_color = (64, 64, 64, 160)
border_width = max(8, image.width // 200)
box_width = max(3, image.width // 400)
draw.rectangle([0, 0, preview.width-1, preview.height-1],
outline=border_color, width=border_width)
if bbox_coords and all(coord is not None for coord in bbox_coords):
x1, y1, x2, y2 = sort_coordinates(*bbox_coords)
x1 = max(0, min(x1, preview.width-1))
y1 = max(0, min(y1, preview.height-1))
x2 = max(0, min(x2, preview.width-1))
y2 = max(0, min(y2, preview.height-1))
if x2 > x1 and y2 > y1:
draw.rectangle([x1, y1, x2, y2], outline=box_color, width=box_width)
text_color = (255, 255, 255)
text_y = max(0, y1 - 25)
try:
from PIL import ImageFont
font_size = max(12, image.width // 50)
font = ImageFont.truetype("arial.ttf", font_size)
text_bbox = draw.textbbox((x1, text_y), mode_text, font=font)
draw.rectangle([text_bbox[0]-5, text_bbox[1]-2,
text_bbox[2]+5, text_bbox[3]+2],
fill=text_bg_color)
draw.text((x1, text_y), mode_text, fill=text_color, font=font)
except:
text_bbox = draw.textbbox((x1, text_y), mode_text)
draw.rectangle([text_bbox[0]-5, text_bbox[1]-2,
text_bbox[2]+5, text_bbox[3]+2],
fill=text_bg_color)
draw.text((x1, text_y), mode_text, fill=text_color)
return preview
def mock_update_live_preview(image, bbox_x1, bbox_y1, bbox_x2, bbox_y2, mode):
"""Mock für Live-Vorschau MIT dynamischen Rahmen"""
if not image:
return None
bbox_coords = sort_coordinates(bbox_x1, bbox_y1, bbox_x2, bbox_y2)
return create_preview_image(image, bbox_coords, mode)
def mock_process_image_upload(image):
"""Mock für Bild-Upload: Setzt BBox in der Mitte"""
if not image:
return None, 100, 100, 300, 300 # x1, y1, x2, y2
w, h = image.size
bbox_size = min(w, h) * 0.3
x1 = (w - bbox_size) / 2
y1 = (h - bbox_size) / 4
x2 = x1 + bbox_size
y2 = y1 + bbox_size * 1.2
print(f"📐 Bildgröße erkannt: {w}x{h} -> BBox: {int(x1)}-{int(x2)}, {int(y1)}-{int(y2)}")
preview = mock_update_live_preview(image, x1, y1, x2, y2, "environment_change")
return preview, int(x1), int(y1), int(x2), int(y2) # x1, y1, x2, y2
def mock_update_slider_for_image(image):
"""KORRIGIERT: Slider-Update mit getrennten Maxima für Breite und Höhe"""
if not image:
return (
gr.update(maximum=MAX_IMAGE_SIZE),
gr.update(maximum=MAX_IMAGE_SIZE),
gr.update(maximum=MAX_IMAGE_SIZE),
gr.update(maximum=MAX_IMAGE_SIZE)
)
w, h = image.size
max_x = min(w, MAX_IMAGE_SIZE)
max_y = min(h, MAX_IMAGE_SIZE)
print(f"📐 Slider-Maxima gesetzt: X={max_x}, Y={max_y} (Bild: {w}x{h})")
return (
gr.update(maximum=max_x), # bbox_x1: linke Kante max = Bildbreite
gr.update(maximum=max_y), # bbox_y1: obere Kante max = Bildhöhe
gr.update(maximum=max_x), # bbox_x2: rechte Kante max = Bildbreite
gr.update(maximum=max_y), # bbox_y2: untere Kante max = Bildhöhe
)
def mock_update_model_settings(model_id):
configs = {
"runwayml/stable-diffusion-v1-5": (35, 7.5, "🏠 SD 1.5 Mock"),
"SG161222/Realistic_Vision_V6.0_B1_noVAE": (40, 7.0, "👤 Realistic Vision Mock")
}
steps, cfg, msg = configs.get(model_id, (35, 7.5, "Standard Mock"))
# WICHTIG: Die dritte Rückgabe muss der HTML-String für die Info-Box sein.
info_html = f"<div class='model-info-box'><strong>{msg}</strong><br>Mock-Einstellungen: {steps} Steps, CFG {cfg}</div>"
return steps, cfg, info_html
def mock_update_info(mode):
"""Mock für Prompt-Info-Update - ALTE TEXTE WIEDERHERGESTELLT"""
if mode == "environment_change":
return (
"`[STIL-MOTIV],[UMGEBUNG],[PERSPEKTIVE],[DETAILS],[QUALITÄT],[BELEUCHTUNG]`",
"`[GESICHTER/ANATOMIE], [FEHLER], [QUALITÄT], [UNERWÜNSCHTES]`"
)
elif mode == "focus_change":
return (
"`[GESICHTSBESCHREIBUNG], [KLEIDUNG], [POSITION], [DETAILS], [STIL]`",
"`[DEFORMIERT], [UNSCHÄRFE], [ANATOMIEFEHLER], [UNERWÜNSCHTES]`"
)
else:
return (
"`[STIL/KOPFART],[HAARFARBE],[AUGEN],[GESICHTSAUSDRUCK],[DETAILS],[BELEUCHTUNG]`",
"`[UNREALISTISCH], [ASYMETRISCH], [FEHLER], [UNERWÜNSCHTES]`"
)
# ==================== UI-DEFINITION ====================
def main_ui():
"""Haupt-UI-Funktion (angepasst für 3 Modi)"""
with gr.Blocks(
title="AI Image Generator - Mock Version",
theme=gr.themes.Base(),
css="""
/* ===== INFO-BOXEN MIT GRÖßEREM TEXT ===== */
.info-box {
background: #f8fafc;
padding: 4px 4px;
border-radius: 4px;
border: none;
margin-bottom: 3px;
font-size: 12px;
line-height: 1.3;
height: 40px; <!-- FESTE Höhe statt auto
min-height: 40px;
color: #475569;
display: flex; <!-- FLEX statt block!
align-items: center; <!-- Jetzt funktioniert vertikale Zentrierung
justify-content: center; <!-- Horizontale Zentrierung
text-align: center;
font-family: 'Segoe UI', 'Monaco', monospace;
overflow: hidden; <!-- Bei zu langem Text
white-space: normal;
word-wrap: break-word;
}
/* Code in Info-Boxen größer */
.info-box code {
background: #ffffff;
padding: 2px 2px;
border-radius: 3px;
font-size: 12px;
border: none;
color: #334155;
font-weight: 500;
}
/* ===== DICKE SCHWARZE LABELS ===== */
.gr-column:first-child .gr-box .wrap .title {
color: #000000;
font-weight: 900;
font-size: 16px;
}
.gr-column:last-child .gr-box .wrap .title {
color: #000000;
font-weight: 900;
font-size: 16px;
}
/* ===== TEXTBOXEN (FESTE HÖHE) ===== */
.prompt-box textarea {
min-height: 90px !important;
max-height: 90px !important;
height: 90px !important;
border-radius: 6px !important;
border: 2px solid #3b82f6 !important;
padding: 12px !important;
font-size: 14px !important;
background: white !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.05) !important;
resize: vertical !important;
overflow-y: auto !important;
}
.prompt-box textarea:focus {
border-color: #1d4ed8 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
/* ===== VISUELLE HIERARCHIE ===== */
.gr-column:first-child .info-box {
background: #f0f9ff !important; /* KEIN border-left mehr! */
}
.gr-column:last-child .info-box {
background: #fef2f2 !important; /* KEIN border-left mehr! */
}
.model-info-box {
background: #e8f4fd;
padding: 12px;
border-radius: 6px;
margin: 10px 0;
border-left: 4px solid #2196f3;
font-size: 14px !important;
}
#generate-button {
background-color: #0080FF !important;
margin: 20px auto !important;
width: 280px;
font-size: 16px !important;
font-weight: 600 !important;
}
.radio-group {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
font-size: 14px !important;
}
.status-message {
padding: 10px;
border-radius: 5px;
margin: 10px 0;
font-size: 14px;
background: #f0f9ff;
border-left: 4px solid #3b82f6;
}
/* Slider besser sichtbar */
.gr-slider {
margin: 8px 0 !important;
}
.gr-slider .wrap {
padding: 8px 0 !important;
}
"""
) as demo:
with gr.Tab("Text zu Bild"):
gr.Markdown("## 🎨 Text zu Bild Generator (Mock Version)")
with gr.Row():
with gr.Column(scale=2):
model_dropdown = gr.Dropdown(
choices=[
("🏠 Stable Diffusion 1.5 (Mock)", "runwayml/stable-diffusion-v1-5"),
("👤 Realistic Vision V6.0 (Mock)", "SG161222/Realistic_Vision_V6.0_B1_noVAE")
],
value="runwayml/stable-diffusion-v1-5",
label="📁 Modellauswahl"
)
model_info_box = gr.Markdown(
value="<div class='model-info-box'><strong>🏠 Stable Diffusion 1.5 (Mock)</strong><br>Universal model, good all-rounder<br><em>Mock-Einstellungen: 35 Steps, CFG 7.5</em></div>",
label="Modellinformationen"
)
with gr.Column(scale=3):
txt_input = gr.Textbox(
placeholder="z.B. ultra realistic mountain landscape at sunrise...",
lines=3,
label="🎯 Prompt (Englisch)",
#value="beautiful landscape sunset mountains",
elem_classes=["prompt-box"]
)
with gr.Row():
with gr.Column():
txt_steps = gr.Slider(
minimum=10, maximum=100, value=35, step=1,
label="⚙️ Inferenz-Schritte"
)
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)"
)
status_output = gr.Markdown(
value="<div class='status-message'>ℹ️ Wählen Sie ein Modell und geben Sie einen Prompt ein.</div>",
elem_classes="status-message"
)
generate_btn = gr.Button("🚀 Mock-Bild generieren", variant="primary", elem_id="generate-button")
with gr.Row():
txt_output = gr.Image(
label="🖼️ Generiertes Mock-Bild",
show_download_button=True,
type="pil",
height=400
)
model_dropdown.change(
fn=mock_update_model_settings,
inputs=[model_dropdown],
outputs=[txt_steps, txt_guidance, model_info_box]
)
generate_btn.click(
fn=mock_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 (Mock Version)")
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="Vorschau",
height=300,
interactive=False
)
with gr.Row():
with gr.Column():
mode_radio = gr.Radio(
choices=[
("🌳 Umgebung ändern", "environment_change"),
("🎯 Focus verändern", "focus_change"),
("👤 Ausschließlich Gesicht", "face_only_change")
],
value="environment_change",
label="Wähle den Transformationsmodus:",
elem_classes="radio-group"
)
with gr.Row():
gr.Markdown("### 📐 Bildelementbereich anpassen")
with gr.Row():
with gr.Column():
bbox_x1 = gr.Slider(
label="← Links (x1)",
minimum=0, maximum=MAX_IMAGE_SIZE, value=100, step=1,
info="Linke Kante des Bildelementbereichs"
)
with gr.Column():
bbox_y1 = gr.Slider(
label="↑ Oben (y1)",
minimum=0, maximum=MAX_IMAGE_SIZE, 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=MAX_IMAGE_SIZE, value=300, step=1,
info="Rechte Kante des Bildelementbereichs"
)
with gr.Column():
bbox_y2 = gr.Slider(
label="↓ Unten (y2)",
minimum=0, maximum=MAX_IMAGE_SIZE, value=300, step=1,
info="Untere Kante des Bildelementbereichs"
)
with gr.Row():
with gr.Column():
pos_info = gr.Markdown(
value="`[STIL-MOTIV],[UMGEBUNG],[PERSPEKTIVE],[DETAILS],[QUALITÄT],[BELEUCHTUNG]`",
elem_classes=["info-box"]
)
img_prompt = gr.Textbox(
placeholder="photorealistic coastal beach, keep person unchanged...",
lines=2,
#label="<span style='color: black; font-weight: 900; font-size: 16px;'>🎯 Transformations-Prompt</span>",
label="🎯 Transformations-Prompt",
elem_classes=["prompt-box"]
)
with gr.Column():
neg_info = gr.Markdown(
value="`[GESICHTER/ANATOMIE], [FEHLER], [QUALITÄT], [UNERWÜNSCHTES]`",
elem_classes=["info-box"]
)
img_neg_prompt = gr.Textbox(
placeholder="blurry face, deformed anatomy...",
lines=2,
#label="<span style='color: black; font-weight: 900; font-size: 16px;'>🚫 Negativ-Prompt</span>",
label="🚫 Negativ-Prompt",
elem_classes=["prompt-box"]
)
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 (strength)"
)
with gr.Column():
img_steps = gr.Slider(
minimum=10, maximum=45, value=35, step=1,
label="⚙️ Inferenz-Schritte"
)
with gr.Column():
img_guidance = gr.Slider(
minimum=1.0, maximum=15.0, value=7.5, step=0.5,
label="🎛️ Prompt-Stärke (guidance)"
)
transform_btn = gr.Button("🔄 Mock-Bild transformieren", variant="primary")
with gr.Row():
img_output = gr.Image(
label="✨ Transformiertes Mock-Bild",
show_download_button=True,
type="pil",
height=400
)
with gr.Row():
sam_raw_mask_output = gr.Image(
label="🔍 SAM-Rohmaske (Mock)",
type="pil",
height=250,
show_download_button=False
)
processed_mask_output = gr.Image(
label="🛠️ Nachbearbeitete Maske (Mock)",
type="pil",
height=250,
show_download_button=False
)
with gr.Row():
pose_map_output = gr.Image(
label="🎭 Pose/Depth Map (Mock)",
type="pil",
height=250,
show_download_button=False
)
canny_map_output = gr.Image(
label="📐 Canny Edge Map (Mock)",
type="pil",
height=250,
show_download_button=False
)
# KORREKT: coordinate_inputs in der richtigen Reihenfolge (x1, y1, x2, y2)
coordinate_inputs = [img_input, bbox_x1, bbox_y1, bbox_x2, bbox_y2, mode_radio]
img_input.upload(
fn=mock_process_image_upload,
inputs=[img_input],
outputs=[preview_output, bbox_x1, bbox_y1, bbox_x2, bbox_y2]
).then(
fn=mock_update_slider_for_image,
inputs=[img_input],
outputs=[bbox_x1, bbox_y1, bbox_x2, bbox_y2]
)
for slider in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]:
slider.release(
fn=mock_update_live_preview,
inputs=coordinate_inputs,
outputs=preview_output
)
mode_radio.change(
fn=mock_update_info,
inputs=[mode_radio],
outputs=[pos_info, neg_info]
).then(
fn=mock_update_live_preview,
inputs=coordinate_inputs,
outputs=preview_output
)
transform_btn.click(
fn=mock_img_to_image,
inputs=[
img_input, img_prompt, img_neg_prompt,
strength_slider, img_steps, img_guidance,
mode_radio, bbox_x1, bbox_y1, bbox_x2, bbox_y2
],
outputs=[img_output, sam_raw_mask_output, processed_mask_output,
pose_map_output, canny_map_output],
concurrency_limit=1
)
return demo
if __name__ == "__main__":
demo = main_ui()
demo.launch(server_name="0.0.0.0", server_port=7860)