Astridkraft commited on
Commit
33d55e6
·
verified ·
1 Parent(s): e3dadd3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +198 -62
app.py CHANGED
@@ -104,24 +104,44 @@ def auto_negative_prompt(positive_prompt):
104
  else:
105
  return base_negatives
106
 
107
- # === GESICHTSMASKEN-FUNKTIONEN ===
108
- def create_face_mask(image, bbox_coords, face_preserve):
109
- """Erzeugt eine Gesichtsmaske - WEIßE Bereiche werden VERÄNDERT, SCHWARZE BLEIBEN"""
 
 
 
 
 
 
 
 
 
 
 
110
  mask = Image.new("L", image.size, 0) # Start mit komplett schwarzer Maske (alles geschützt)
111
 
112
  if bbox_coords and all(coord is not None for coord in bbox_coords):
113
  x1, y1, x2, y2 = bbox_coords
114
  draw = ImageDraw.Draw(mask)
115
 
116
- if face_preserve:
117
- # GESICHTSERHALTUNG: Maske um das Gesicht herum zeichnen
 
118
  draw.rectangle([0, 0, image.size[0], image.size[1]], fill=255) # Alles weiß = verändern
119
  draw.rectangle([x1, y1, x2, y2], fill=0) # Gesicht schwarz = geschützt (rechteckig)
120
- print("Gesicht wird GESCHÜTZT - Umgebung wird verändert (rechteckige Maske)")
121
- else:
122
- # NUR GESICHT VERÄNDERN: Nur das Gesicht wird weiß (verändert)
123
- draw.rectangle([x1, y1, x2, y2], fill=255) # Gesicht weiß = verändern (rechteckig)
124
- print("Nur Gesicht wird verändert - Umgebung bleibt erhalten (rechteckige Maske)")
 
 
 
 
 
 
 
 
125
 
126
  return mask
127
 
@@ -361,22 +381,51 @@ class ImageToImageProgressCallback:
361
  self.progress(progress_percent / 100, desc="Generierung läuft...")
362
  return callback_kwargs
363
 
364
- # === NEUE FUNKTIONEN FÜR DIE FEATURES ===
365
- def create_preview_image(image, bbox_coords, face_preserve, mode_color):
366
- """Erstellt eine Vorschau mit farbigem Rahmen basierend auf dem Modus"""
 
 
 
 
 
 
 
 
 
 
367
  if image is None:
368
  return None
369
 
370
  preview = image.copy()
371
  draw = ImageDraw.Draw(preview)
372
 
373
- if mode_color == "red":
374
- border_color = (255, 0, 0, 180)
375
- mode_text = "NUR BILDELEMENT VERÄNDERN"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  else:
377
- border_color = (0, 255, 0, 180)
378
- mode_text = "BILDELEMENT BEIBEHALTEN"
 
 
 
379
 
 
380
  border_width = 8
381
  draw.rectangle([0, 0, preview.width-1, preview.height-1],
382
  outline=border_color, width=border_width)
@@ -384,29 +433,33 @@ def create_preview_image(image, bbox_coords, face_preserve, mode_color):
384
  if bbox_coords and all(coord is not None for coord in bbox_coords):
385
  x1, y1, x2, y2 = bbox_coords
386
 
387
- box_color = (255, 255, 0, 200)
388
  draw.rectangle([x1, y1, x2, y2], outline=box_color, width=3)
389
 
 
390
  text_color = (255, 255, 255)
391
- bg_color = (0, 0, 0, 160)
392
 
 
393
  text_bbox = draw.textbbox((x1, y1 - 25), mode_text)
394
  draw.rectangle([text_bbox[0]-5, text_bbox[1]-2, text_bbox[2]+5, text_bbox[3]+2],
395
- fill=bg_color)
396
 
 
397
  draw.text((x1, y1 - 25), mode_text, fill=text_color)
398
 
399
  return preview
400
 
401
- def update_live_preview(image, bbox_x1, bbox_y1, bbox_x2, bbox_y2, face_preserve):
402
- """Aktualisiert die Live-Vorschau bei Koordinaten-Änderungen"""
 
 
 
403
  if image is None:
404
  return None
405
 
406
  bbox_coords = [bbox_x1, bbox_y1, bbox_x2, bbox_y2]
407
- mode_color = "green" if face_preserve else "red"
408
 
409
- return create_preview_image(image, bbox_coords, face_preserve, mode_color)
410
 
411
  def process_image_upload(image):
412
  """Verarbeitet Bild-Upload und gibt Bild + Koordinaten zurück"""
@@ -420,11 +473,12 @@ def process_image_upload(image):
420
  bbox = auto_detect_face_area(image)
421
  bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bbox
422
 
423
- preview = create_preview_image(image, bbox, True, "green")
 
424
 
425
  return preview, bbox_x1, bbox_y1, bbox_x2, bbox_y2
426
 
427
- # === HAUPTFUNKTIONEN ===
428
  def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress()):
429
  try:
430
  if not prompt or not prompt.strip():
@@ -518,8 +572,20 @@ def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress(
518
  return None, error_msg
519
 
520
  def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
521
- face_preserve, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
522
  progress=gr.Progress()):
 
 
 
 
 
 
 
 
 
 
 
 
523
  try:
524
  if image is None:
525
  return None
@@ -527,17 +593,17 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
527
  import time, random
528
  start_time = time.time()
529
 
530
- print(f"Img2Img Start → Strength: {strength}, Steps: {steps}, Guidance: {guidance_scale}")
531
- print(f"Prompt: {prompt}")
532
- print(f"Negativ-Prompt: {neg_prompt}")
533
- print(f"Gesicht beibehalten: {face_preserve}")
534
 
535
 
536
- # ===== NEU: AUTOMATISCHEN NEGATIV-PROMPT GENERIEREN =====
537
  auto_negatives = auto_negative_prompt(prompt)
538
  print(f"🤖 Automatisch generierter Negativ-Prompt: {auto_negatives}")
539
 
540
- # ===== NEU: KOMBINIERE MANUELLEN UND AUTOMATISCHEN PROMPT =====
541
  combined_negative_prompt = ""
542
 
543
  if neg_prompt and neg_prompt.strip():
@@ -546,7 +612,6 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
546
  print(f"👤 Benutzer Negativ-Prompt: {user_neg}")
547
 
548
  # Entferne Duplikate zwischen automatischen und manuellen Prompts
549
- # Konvertiere beide in Sets für einfachen Duplikatvergleich
550
  user_words = [word.strip().lower() for word in user_neg.split(",")]
551
  auto_words = [word.strip().lower() for word in auto_negatives.split(",")]
552
 
@@ -558,7 +623,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
558
  if auto_word and auto_word not in user_words:
559
  combined_words.append(auto_word)
560
 
561
- # Zusammenfügen und Duplikate entfernen (für den Fall von Duplikaten innerhalb des gleichen Prompts)
562
  unique_words = []
563
  seen_words = set()
564
  for word in combined_words:
@@ -573,26 +638,34 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
573
  print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
574
 
575
  print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
576
- # ===== ENDE DER NEUEN LOGIK =====
577
 
578
 
579
  progress(0, desc="Starte Generierung mit ControlNet...")
580
 
 
581
  adj_strength = min(0.85, strength * 1.25)
582
 
583
- if face_preserve:
584
- controlnet_strength = adj_strength * 0.8
585
- print(f"🎯 ControlNet Modus: Umgebung beibehalten (Strength = {controlnet_strength:.3f})")
586
- else:
587
  controlnet_strength = adj_strength * 0.5
588
- print(f"🎯 ControlNet Modus: Person beibehalten (Strength = {controlnet_strength:.3f})")
 
 
 
 
 
 
 
 
589
 
590
  controlnet_steps = min(25, int(steps * 0.8))
591
-
592
- print(f"🎯 Steps={steps}, ControlNet-Steps={controlnet_steps}, Strength={controlnet_strength:.3f}")
593
 
594
  progress(0.05, desc="Erstelle ControlNet Maps...")
595
 
 
596
  controlnet_output, inpaint_input = controlnet_processor.generate_with_controlnet(
597
  image=image,
598
  prompt=prompt,
@@ -601,7 +674,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
601
  guidance_scale=guidance_scale,
602
  controlnet_strength=controlnet_strength,
603
  progress=progress,
604
- keep_environment=face_preserve
605
  )
606
 
607
  print(f"✅ ControlNet Output erhalten: {type(controlnet_output)}")
@@ -616,8 +689,9 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
616
  adj_guidance = min(guidance_scale, 12.0)
617
  seed = random.randint(0, 2**32 - 1)
618
  generator = torch.Generator(device=device).manual_seed(seed)
619
- print(f"Using seed: {seed}")
620
 
 
621
  mask = None
622
  if bbox_x1 and bbox_y1 and bbox_x2 and bbox_y2:
623
  orig_w, orig_h = image.size
@@ -628,10 +702,12 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
628
  int(bbox_x2 * scale_x),
629
  int(bbox_y2 * scale_y)
630
  ]
631
- print(f"Skalierte Koordinaten: {bbox_coords}")
632
- mask = create_face_mask(img_resized, bbox_coords, face_preserve)
 
 
633
  if mask:
634
- print("✅ Maske erfolgreich erstellt")
635
  else:
636
  print("⚠️ Keine gültigen Koordinaten – keine Maske")
637
 
@@ -641,6 +717,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
641
 
642
  callback = ImageToImageProgressCallback(progress, int(steps), adj_strength)
643
 
 
644
  result = pipe(
645
  prompt=prompt,
646
  negative_prompt=combined_negative_prompt,
@@ -655,7 +732,10 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
655
  )
656
 
657
  end_time = time.time()
658
- print(f"🕒 Dauer: {end_time - start_time:.2f} Sekunden")
 
 
 
659
 
660
  generated_image = result.images[0]
661
  return generated_image
@@ -685,6 +765,10 @@ def update_model_settings(model_id):
685
  )
686
 
687
  def main_ui():
 
 
 
 
688
  with gr.Blocks(
689
  title="AI Image Generator",
690
  theme=gr.themes.Base(),
@@ -778,6 +862,31 @@ def main_ui():
778
  color: #721c24;
779
  border: 1px solid #f5c6cb;
780
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
781
  """
782
  ) as demo:
783
 
@@ -872,7 +981,7 @@ def main_ui():
872
  )
873
 
874
  with gr.Tab("Bild zu Bild"):
875
- gr.Markdown("## 🖼️ Bild zu Bild Transformation")
876
 
877
  with gr.Row():
878
  with gr.Column():
@@ -891,12 +1000,33 @@ def main_ui():
891
  show_download_button=False
892
  )
893
 
 
894
  with gr.Row():
895
- face_preserve = gr.Checkbox(
896
- label="🛡️ Schutzmodus",
897
- value=True,
898
- info="🟢 AN: Alles AUSSERHALB des gelben Rahmens verändern | 🔴 AUS: Nur INNERHALB des gelben Rahmens verändern"
899
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
 
901
  with gr.Row():
902
  gr.Markdown("### 📐 Bildelementbereich anpassen")
@@ -967,10 +1097,12 @@ def main_ui():
967
  with gr.Row():
968
  gr.Markdown(
969
  "### 📋 Hinweise:\n"
 
970
  "• **🆕 Automatische Bildelementerkennung** setzt Koordinaten beim Upload\n"
971
- "• **🆕 Live-Vorschau** zeigt farbige Rahmen je nach Modus (🔴 Rot / 🟢 Grün)\n"
972
  "• **🆕 Koordinaten-Schieberegler** für präzise Anpassung mit Live-Update\n"
973
- "• **Koordinaten nur bei erkennbaren Verzerrungen anpassen** (Bereiche leicht verschieben)"
 
974
  )
975
 
976
  transform_btn = gr.Button("🔄 Bild transformieren", variant="primary")
@@ -989,8 +1121,10 @@ def main_ui():
989
  outputs=[preview_output, bbox_x1, bbox_y1, bbox_x2, bbox_y2]
990
  )
991
 
992
- coordinate_inputs = [img_input, bbox_x1, bbox_y1, bbox_x2, bbox_y2, face_preserve]
 
993
 
 
994
  for slider in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]:
995
  slider.change(
996
  fn=update_live_preview,
@@ -998,18 +1132,20 @@ def main_ui():
998
  outputs=preview_output
999
  )
1000
 
1001
- face_preserve.change(
 
1002
  fn=update_live_preview,
1003
  inputs=coordinate_inputs,
1004
  outputs=preview_output
1005
  )
1006
 
 
1007
  transform_btn.click(
1008
  fn=img_to_image,
1009
  inputs=[
1010
  img_input, img_prompt, img_neg_prompt,
1011
  strength_slider, img_steps, img_guidance,
1012
- face_preserve, bbox_x1, bbox_y1, bbox_x2, bbox_y2
1013
  ],
1014
  outputs=img_output,
1015
  concurrency_limit=1
@@ -1027,4 +1163,4 @@ if __name__ == "__main__":
1027
  show_error=True,
1028
  share=False,
1029
  ssr_mode=False # SSR deaktivieren für Stabilität
1030
- )
 
104
  else:
105
  return base_negatives
106
 
107
+ # === GESICHTSMASKEN-FUNKTIONEN (ERWEITERT FÜR 3 MODI) ===
108
+ def create_face_mask(image, bbox_coords, mode):
109
+ """
110
+ ERWEITERTE FUNKTION: Erzeugt Maske basierend auf 3 Modi
111
+ Weiße Bereiche werden VERÄNDERT, Schwarze bleiben ERHALTEN
112
+
113
+ Parameter:
114
+ - image: PIL Image
115
+ - bbox_coords: [x1, y1, x2, y2]
116
+ - mode: "environment_change", "focus_change", "face_only_change"
117
+
118
+ Returns:
119
+ - PIL Image (L-Modus, 0=schwarz=erhalten, 255=weiß=verändern)
120
+ """
121
  mask = Image.new("L", image.size, 0) # Start mit komplett schwarzer Maske (alles geschützt)
122
 
123
  if bbox_coords and all(coord is not None for coord in bbox_coords):
124
  x1, y1, x2, y2 = bbox_coords
125
  draw = ImageDraw.Draw(mask)
126
 
127
+ if mode == "environment_change":
128
+ # MODUS 1: Umgebung ändern (Depth + Canny)
129
+ # Maske: Alles weiß AUSSER Gesicht (schwarz)
130
  draw.rectangle([0, 0, image.size[0], image.size[1]], fill=255) # Alles weiß = verändern
131
  draw.rectangle([x1, y1, x2, y2], fill=0) # Gesicht schwarz = geschützt (rechteckig)
132
+ print("🎯 MODUS: Umgebung ändern - Alles außer Gesicht wird verändert")
133
+
134
+ elif mode == "focus_change":
135
+ # MODUS 2: Focus verändern (OpenPose + Canny)
136
+ # Maske: Nur innerhalb der Box weiß (Rest schwarz)
137
+ draw.rectangle([x1, y1, x2, y2], fill=255) # Nur Box weiß = verändern
138
+ print("🎯 MODUS: Focus verändern - Nur innerhalb der Box wird verändert")
139
+
140
+ elif mode == "face_only_change":
141
+ # MODUS 3: Ausschließlich Gesicht (Depth + Canny)
142
+ # Maske: Nur innerhalb der Box weiß (Rest schwarz) - wie focus_change
143
+ draw.rectangle([x1, y1, x2, y2], fill=255) # Nur Box weiß = verändern
144
+ print("🎯 MODUS: Ausschließlich Gesicht - Nur Gesicht wird verändert")
145
 
146
  return mask
147
 
 
381
  self.progress(progress_percent / 100, desc="Generierung läuft...")
382
  return callback_kwargs
383
 
384
+ # === NEUE FUNKTIONEN FÜR DIE FEATURES (ANGEPASST FÜR 3 MODI) ===
385
+ def create_preview_image(image, bbox_coords, mode):
386
+ """
387
+ NEUE FUNKTION: Erstellt Vorschau basierend auf 3 Modi mit farbigen Rahmen
388
+
389
+ Parameter:
390
+ - image: PIL Image
391
+ - bbox_coords: [x1, y1, x2, y2]
392
+ - mode: "environment_change", "focus_change", "face_only_change"
393
+
394
+ Returns:
395
+ - PIL Image mit farbigem Rahmen und Text
396
+ """
397
  if image is None:
398
  return None
399
 
400
  preview = image.copy()
401
  draw = ImageDraw.Draw(preview)
402
 
403
+ # Farben basierend auf Modus
404
+ if mode == "environment_change":
405
+ border_color = (0, 255, 0, 180) # Grün für Umgebung
406
+ mode_text = "UMGEBUNG ÄNDERN (Gesicht geschützt)"
407
+ box_color = (255, 255, 0, 200) # Gelb für geschütztes Gesicht
408
+ text_bg_color = (0, 128, 0, 160) # Dunkelgrün
409
+
410
+ elif mode == "focus_change":
411
+ border_color = (255, 165, 0, 180) # Orange für Focus
412
+ mode_text = "FOCUS VERÄNDERN (Gesicht+Körper)"
413
+ box_color = (255, 0, 0, 200) # Rot für Veränderungsbereich
414
+ text_bg_color = (255, 140, 0, 160) # Dunkelorange
415
+
416
+ elif mode == "face_only_change":
417
+ border_color = (255, 0, 0, 180) # Rot für nur Gesicht
418
+ mode_text = "NUR GESICHT VERÄNDERN"
419
+ box_color = (255, 0, 0, 200) # Rot für Veränderungsbereich
420
+ text_bg_color = (128, 0, 0, 160) # Dunkelrot
421
  else:
422
+ # Fallback
423
+ border_color = (128, 128, 128, 180)
424
+ mode_text = "UNBEKANNTER MODUS"
425
+ box_color = (128, 128, 128, 200)
426
+ text_bg_color = (64, 64, 64, 160)
427
 
428
+ # Rahmen um das gesamte Bild
429
  border_width = 8
430
  draw.rectangle([0, 0, preview.width-1, preview.height-1],
431
  outline=border_color, width=border_width)
 
433
  if bbox_coords and all(coord is not None for coord in bbox_coords):
434
  x1, y1, x2, y2 = bbox_coords
435
 
436
+ # Bounding Box zeichnen
437
  draw.rectangle([x1, y1, x2, y2], outline=box_color, width=3)
438
 
439
+ # Modus-Text anzeigen
440
  text_color = (255, 255, 255)
 
441
 
442
+ # Text-Hintergrund zeichnen
443
  text_bbox = draw.textbbox((x1, y1 - 25), mode_text)
444
  draw.rectangle([text_bbox[0]-5, text_bbox[1]-2, text_bbox[2]+5, text_bbox[3]+2],
445
+ fill=text_bg_color)
446
 
447
+ # Text zeichnen
448
  draw.text((x1, y1 - 25), mode_text, fill=text_color)
449
 
450
  return preview
451
 
452
+ def update_live_preview(image, bbox_x1, bbox_y1, bbox_x2, bbox_y2, mode):
453
+ """
454
+ Aktualisiert die Live-Vorschau bei Koordinaten-Änderungen
455
+ NEU: Verwendet 3 Modi statt Boolean
456
+ """
457
  if image is None:
458
  return None
459
 
460
  bbox_coords = [bbox_x1, bbox_y1, bbox_x2, bbox_y2]
 
461
 
462
+ return create_preview_image(image, bbox_coords, mode)
463
 
464
  def process_image_upload(image):
465
  """Verarbeitet Bild-Upload und gibt Bild + Koordinaten zurück"""
 
473
  bbox = auto_detect_face_area(image)
474
  bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bbox
475
 
476
+ # Standardmodus: "environment_change" (Umgebung ändern)
477
+ preview = create_preview_image(image, bbox, "environment_change")
478
 
479
  return preview, bbox_x1, bbox_y1, bbox_x2, bbox_y2
480
 
481
+ # === HAUPTFUNKTIONEN (ANGEPASST FÜR 3 MODI) ===
482
  def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress()):
483
  try:
484
  if not prompt or not prompt.strip():
 
572
  return None, error_msg
573
 
574
  def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
575
+ mode, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
576
  progress=gr.Progress()):
577
+ """
578
+ HAUPTFUNKTION FÜR BILD-zu-BILD (ANGEPASST FÜR 3 MODI)
579
+
580
+ WICHTIG: Der 'mode' Parameter bestimmt:
581
+ - "environment_change": Depth + Canny, Maske außen weiß
582
+ - "focus_change": OpenPose + Canny, Maske innen weiß
583
+ - "face_only_change": Depth + Canny, Maske innen weiß
584
+
585
+ keep_environment Parameter für ControlNet:
586
+ - True für "environment_change" und "face_only_change" (Depth+Canny)
587
+ - False für "focus_change" (OpenPose+Canny)
588
+ """
589
  try:
590
  if image is None:
591
  return None
 
593
  import time, random
594
  start_time = time.time()
595
 
596
+ print(f"🚀 Img2Img Start → Modus: {mode}")
597
+ print(f"📊 Einstellungen: Strength: {strength}, Steps: {steps}, Guidance: {guidance_scale}")
598
+ print(f"📝 Prompt: {prompt}")
599
+ print(f"🚫 Negativ-Prompt: {neg_prompt}")
600
 
601
 
602
+ # ===== AUTOMATISCHEN NEGATIV-PROMPT GENERIEREN =====
603
  auto_negatives = auto_negative_prompt(prompt)
604
  print(f"🤖 Automatisch generierter Negativ-Prompt: {auto_negatives}")
605
 
606
+ # ===== KOMBINIERE MANUELLEN UND AUTOMATISCHEN PROMPT =====
607
  combined_negative_prompt = ""
608
 
609
  if neg_prompt and neg_prompt.strip():
 
612
  print(f"👤 Benutzer Negativ-Prompt: {user_neg}")
613
 
614
  # Entferne Duplikate zwischen automatischen und manuellen Prompts
 
615
  user_words = [word.strip().lower() for word in user_neg.split(",")]
616
  auto_words = [word.strip().lower() for word in auto_negatives.split(",")]
617
 
 
623
  if auto_word and auto_word not in user_words:
624
  combined_words.append(auto_word)
625
 
626
+ # Zusammenfügen und Duplikate entfernen
627
  unique_words = []
628
  seen_words = set()
629
  for word in combined_words:
 
638
  print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
639
 
640
  print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
 
641
 
642
 
643
  progress(0, desc="Starte Generierung mit ControlNet...")
644
 
645
+ # ===== MODUS-SPEZIFISCHE EINSTELLUNGEN =====
646
  adj_strength = min(0.85, strength * 1.25)
647
 
648
+ # Bestimme keep_environment basierend auf Modus
649
+ if mode == "focus_change":
650
+ # MODUS 2: Focus verändern OpenPose + Canny
651
+ keep_environment = False
652
  controlnet_strength = adj_strength * 0.5
653
+ print(f"🎯 MODUS: Focus verändern → OpenPose+Canny (keep_environment=False)")
654
+ else:
655
+ # MODUS 1 & 3: Umgebung/Gesicht ändern → Depth + Canny
656
+ keep_environment = True
657
+ controlnet_strength = adj_strength * 0.8
658
+ if mode == "environment_change":
659
+ print(f"🎯 MODUS: Umgebung ändern → Depth+Canny (keep_environment=True)")
660
+ else:
661
+ print(f"🎯 MODUS: Ausschließlich Gesicht → Depth+Canny (keep_environment=True)")
662
 
663
  controlnet_steps = min(25, int(steps * 0.8))
664
+ print(f"⚙️ ControlNet Settings: Strength={controlnet_strength:.3f}, Steps={controlnet_steps}")
 
665
 
666
  progress(0.05, desc="Erstelle ControlNet Maps...")
667
 
668
+ # ===== CONTROLNET AUFRUF =====
669
  controlnet_output, inpaint_input = controlnet_processor.generate_with_controlnet(
670
  image=image,
671
  prompt=prompt,
 
674
  guidance_scale=guidance_scale,
675
  controlnet_strength=controlnet_strength,
676
  progress=progress,
677
+ keep_environment=keep_environment # Wichtig: Bestimmt ControlNet Typ!
678
  )
679
 
680
  print(f"✅ ControlNet Output erhalten: {type(controlnet_output)}")
 
689
  adj_guidance = min(guidance_scale, 12.0)
690
  seed = random.randint(0, 2**32 - 1)
691
  generator = torch.Generator(device=device).manual_seed(seed)
692
+ print(f"🌱 Inpaint Seed: {seed}")
693
 
694
+ # ===== MASKE ERSTELLEN (BASIEREND AUF MODUS) =====
695
  mask = None
696
  if bbox_x1 and bbox_y1 and bbox_x2 and bbox_y2:
697
  orig_w, orig_h = image.size
 
702
  int(bbox_x2 * scale_x),
703
  int(bbox_y2 * scale_y)
704
  ]
705
+ print(f"📐 Skalierte Koordinaten: {bbox_coords}")
706
+
707
+ # NEU: Modus-spezifische Maskenerstellung
708
+ mask = create_face_mask(img_resized, bbox_coords, mode)
709
  if mask:
710
+ print(f"✅ Maske erstellt für Modus: {mode}")
711
  else:
712
  print("⚠️ Keine gültigen Koordinaten – keine Maske")
713
 
 
717
 
718
  callback = ImageToImageProgressCallback(progress, int(steps), adj_strength)
719
 
720
+ # ===== INPAINT DURCHFÜHREN =====
721
  result = pipe(
722
  prompt=prompt,
723
  negative_prompt=combined_negative_prompt,
 
732
  )
733
 
734
  end_time = time.time()
735
+ duration = end_time - start_time
736
+ print(f"✅ Transformation abgeschlossen in {duration:.2f} Sekunden")
737
+ print(f"🎯 Verwendeter Modus: {mode}")
738
+ print(f"⚙️ ControlNet: {'Depth+Canny' if keep_environment else 'OpenPose+Canny'}")
739
 
740
  generated_image = result.images[0]
741
  return generated_image
 
765
  )
766
 
767
  def main_ui():
768
+ """
769
+ HAUPT-UI (ANGEPASST FÜR 3 MODI)
770
+ Wichtigste Änderung: Ersetzung der Checkbox durch Radio-Buttons
771
+ """
772
  with gr.Blocks(
773
  title="AI Image Generator",
774
  theme=gr.themes.Base(),
 
862
  color: #721c24;
863
  border: 1px solid #f5c6cb;
864
  }
865
+ .radio-group {
866
+ background: #f8f9fa;
867
+ padding: 15px;
868
+ border-radius: 8px;
869
+ margin: 10px 0;
870
+ border: 2px solid #e9ecef;
871
+ }
872
+ .radio-item {
873
+ padding: 8px 12px;
874
+ margin: 5px 0;
875
+ border-radius: 4px;
876
+ transition: background 0.3s;
877
+ }
878
+ .radio-item:hover {
879
+ background: #e9ecef;
880
+ }
881
+ .radio-label {
882
+ font-weight: 600;
883
+ font-size: 14px;
884
+ }
885
+ .radio-description {
886
+ font-size: 12px;
887
+ color: #6c757d;
888
+ margin-left: 24px;
889
+ }
890
  """
891
  ) as demo:
892
 
 
981
  )
982
 
983
  with gr.Tab("Bild zu Bild"):
984
+ gr.Markdown("## 🖼️ Bild zu Bild Transformation (3 MODI)")
985
 
986
  with gr.Row():
987
  with gr.Column():
 
1000
  show_download_button=False
1001
  )
1002
 
1003
+ # ===== NEUE RADIO-BUTTONS STATT CHECKBOX =====
1004
  with gr.Row():
1005
+ with gr.Column():
1006
+ gr.Markdown("### 🎛️ Transformations-Modus")
1007
+
1008
+ # NEU: 3 Radio-Buttons statt 1 Checkbox
1009
+ mode_radio = gr.Radio(
1010
+ choices=[
1011
+ ("🌳 Umgebung ändern", "environment_change"),
1012
+ ("🎯 Focus verändern", "focus_change"),
1013
+ ("👤 Ausschließlich Gesicht", "face_only_change")
1014
+ ],
1015
+ value="environment_change", # Standardmodus
1016
+ label="Wähle den Transformationsmodus:",
1017
+ info="Steuert, welcher Teil des Bildes verändert wird",
1018
+ elem_classes="radio-group"
1019
+ )
1020
+
1021
+ # Detailierte Erklärungen
1022
+ gr.Markdown("""
1023
+ <div style="font-size: 12px; color: #666; margin-top: 10px;">
1024
+ <strong>Modus-Erklärungen:</strong><br>
1025
+ • <strong>🌳 Umgebung ändern:</strong> Ändert alles AUSSER dem Gesicht (Depth+Canny)<br>
1026
+ • <strong>🎯 Focus verändern:</strong> Ändert Gesicht+Körper (OpenPose+Canny)<br>
1027
+ • <strong>👤 Ausschließlich Gesicht:</strong> Ändert NUR das Gesicht (Depth+Canny)
1028
+ </div>
1029
+ """)
1030
 
1031
  with gr.Row():
1032
  gr.Markdown("### 📐 Bildelementbereich anpassen")
 
1097
  with gr.Row():
1098
  gr.Markdown(
1099
  "### 📋 Hinweise:\n"
1100
+ "• **🆕 3 Transformations-Modi** für präzise Kontrolle\n"
1101
  "• **🆕 Automatische Bildelementerkennung** setzt Koordinaten beim Upload\n"
1102
+ "• **🆕 Live-Vorschau** zeigt farbige Rahmen je nach Modus\n"
1103
  "• **🆕 Koordinaten-Schieberegler** für präzise Anpassung mit Live-Update\n"
1104
+ "• **ControlNet-Technologie** für konsistente Ergebnisse\n"
1105
+ "• **Automatische Negative Prompts** für bessere Qualität"
1106
  )
1107
 
1108
  transform_btn = gr.Button("🔄 Bild transformieren", variant="primary")
 
1121
  outputs=[preview_output, bbox_x1, bbox_y1, bbox_x2, bbox_y2]
1122
  )
1123
 
1124
+ # NEUE Input-Liste mit mode_radio statt face_preserve
1125
+ coordinate_inputs = [img_input, bbox_x1, bbox_y1, bbox_x2, bbox_y2, mode_radio]
1126
 
1127
+ # Live-Vorschau Updates für alle Steuerelemente
1128
  for slider in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]:
1129
  slider.change(
1130
  fn=update_live_preview,
 
1132
  outputs=preview_output
1133
  )
1134
 
1135
+ # NEU: Mode-Radio-Button ändert auch Live-Vorschau
1136
+ mode_radio.change(
1137
  fn=update_live_preview,
1138
  inputs=coordinate_inputs,
1139
  outputs=preview_output
1140
  )
1141
 
1142
+ # NEU: Transform-Button mit mode_radio statt face_preserve
1143
  transform_btn.click(
1144
  fn=img_to_image,
1145
  inputs=[
1146
  img_input, img_prompt, img_neg_prompt,
1147
  strength_slider, img_steps, img_guidance,
1148
+ mode_radio, bbox_x1, bbox_y1, bbox_x2, bbox_y2
1149
  ],
1150
  outputs=img_output,
1151
  concurrency_limit=1
 
1163
  show_error=True,
1164
  share=False,
1165
  ssr_mode=False # SSR deaktivieren für Stabilität
1166
+ )