Files changed (1) hide show
  1. app.py +50 -220
app.py CHANGED
@@ -1,21 +1,18 @@
1
- import gradio as gr
2
  import spaces
 
3
  import torch
4
  from diffusers import AutoencoderKL, TCDScheduler
5
  from diffusers.models.model_loading_utils import load_state_dict
6
  from gradio_imageslider import ImageSlider
7
  from huggingface_hub import hf_hub_download
8
-
9
  from controlnet_union import ControlNetModel_Union
10
  from pipeline_fill_sd_xl import StableDiffusionXLFillPipeline
11
-
12
  from PIL import Image, ImageDraw
13
  import numpy as np
14
 
15
  MODELS = {
16
  "RealVisXL V5.0 Lightning": "SG161222/RealVisXL_V5.0_Lightning",
17
  "Lustify Lightning": "GraydientPlatformAPI/lustify-lightning",
18
- # "Lustify Inpaint": "andro-flock/LUSTIFY-SDXL-NSFW-checkpoint-v2-0-INPAINTING",
19
  "Juggernaut XL Lightning": "RunDiffusion/Juggernaut-XL-Lightning",
20
  }
21
 
@@ -23,7 +20,6 @@ config_file = hf_hub_download(
23
  "xinsir/controlnet-union-sdxl-1.0",
24
  filename="config_promax.json",
25
  )
26
-
27
  config = ControlNetModel_Union.load_config(config_file)
28
  controlnet_model = ControlNetModel_Union.from_config(config)
29
  model_file = hf_hub_download(
@@ -35,11 +31,9 @@ model, _, _, _, _ = ControlNetModel_Union._load_pretrained_model(
35
  controlnet_model, state_dict, model_file, "xinsir/controlnet-union-sdxl-1.0"
36
  )
37
  model.to(device="cuda", dtype=torch.float16)
38
-
39
  vae = AutoencoderKL.from_pretrained(
40
  "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
41
  ).to("cuda")
42
-
43
  pipe = StableDiffusionXLFillPipeline.from_pretrained(
44
  "SG161222/RealVisXL_V5.0_Lightning",
45
  torch_dtype=torch.float16,
@@ -47,34 +41,21 @@ pipe = StableDiffusionXLFillPipeline.from_pretrained(
47
  controlnet=model,
48
  variant="fp16",
49
  )
50
-
51
  pipe = StableDiffusionXLFillPipeline.from_pretrained(
52
  "GraydientPlatformAPI/lustify-lightning",
53
  torch_dtype=torch.float16,
54
  vae=vae,
55
  controlnet=model,
56
  )
57
-
58
  pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
59
-
60
  pipe.to("cuda")
61
 
62
- # inpaint_model = hf_hub_download(
63
- # "andro-flock/LUSTIFY-SDXL-NSFW-checkpoint-v2-0-INPAINTING",
64
- # "lustifySDXLNSFW_v20-inpainting.safetensors",
65
- # )
66
- # pipe_inpaint = StableDiffusionXLFillPipeline.from_single_file(
67
- # "https://huggingface.co/andro-flock/LUSTIFY-SDXL-NSFW-checkpoint-v2-0-INPAINTING/raw/main/lustifySDXLNSFW_v20-inpainting.safetensors",
68
- # torch_dtype=torch.float16,
69
- # vae=vae,
70
- # controlnet=model,
71
- # use_safetensors=True
72
- # )
73
- # pipe_inpaint.to("cuda")
74
-
75
- @spaces.GPU(duration=12)
76
  def fill_image(prompt, image, model_selection, paste_back):
77
- print(f"Received image: {image}") # Debugging statement
 
 
 
78
 
79
  (
80
  prompt_embeds,
@@ -82,10 +63,8 @@ def fill_image(prompt, image, model_selection, paste_back):
82
  pooled_prompt_embeds,
83
  negative_pooled_prompt_embeds,
84
  ) = pipe.encode_prompt(prompt, "cuda", True)
85
-
86
  source = image["background"]
87
  mask = image["layers"][0]
88
-
89
  alpha_channel = mask.split()[3]
90
  binary_mask = alpha_channel.point(lambda p: p > 0 and 255)
91
  cnet_image = source.copy()
@@ -102,21 +81,17 @@ def fill_image(prompt, image, model_selection, paste_back):
102
 
103
  print(f"{model_selection=}")
104
  print(f"{paste_back=}")
105
-
106
  if paste_back:
107
  image = image.convert("RGBA")
108
  cnet_image.paste(image, (0, 0), binary_mask)
109
  else:
110
  cnet_image = image
111
-
112
  yield source, cnet_image
113
 
114
-
115
  def clear_result():
116
  return gr.update(value=None)
117
-
118
  def can_expand(source_width, source_height, target_width, target_height, alignment):
119
- """Checks if the image can be expanded based on the alignment."""
120
  if alignment in ("Left", "Right") and source_width >= target_width:
121
  return False
122
  if alignment in ("Top", "Bottom") and source_height >= target_height:
@@ -125,16 +100,11 @@ def can_expand(source_width, source_height, target_width, target_height, alignme
125
 
126
  def prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
127
  target_size = (width, height)
128
-
129
- # Calculate the scaling factor to fit the image within the target size
130
  scale_factor = min(target_size[0] / image.width, target_size[1] / image.height)
131
  new_width = int(image.width * scale_factor)
132
  new_height = int(image.height * scale_factor)
133
-
134
- # Resize the source image to fit within target size
135
- source = image.resize((new_width, new_height), Image.LANCZOS)
136
 
137
- # Apply resize option using percentages
138
  if resize_option == "Full":
139
  resize_percentage = 100
140
  elif resize_option == "80%":
@@ -148,27 +118,19 @@ def prepare_image_and_mask(image, width, height, overlap_percentage, resize_opti
148
  else: # Custom
149
  resize_percentage = custom_resize_percentage
150
 
151
- # Calculate new dimensions based on percentage
152
  resize_factor = resize_percentage / 100
153
  new_width = int(source.width * resize_factor)
154
  new_height = int(source.height * resize_factor)
155
-
156
- # Ensure minimum size of 64 pixels
157
  new_width = max(new_width, 64)
158
  new_height = max(new_height, 64)
159
 
160
- # Resize the image
161
  source = source.resize((new_width, new_height), Image.LANCZOS)
162
 
163
- # Calculate the overlap in pixels based on the percentage
164
  overlap_x = int(new_width * (overlap_percentage / 100))
165
  overlap_y = int(new_height * (overlap_percentage / 100))
166
-
167
- # Ensure minimum overlap of 1 pixel
168
  overlap_x = max(overlap_x, 1)
169
  overlap_y = max(overlap_y, 1)
170
 
171
- # Calculate margins based on alignment
172
  if alignment == "Middle":
173
  margin_x = (target_size[0] - new_width) // 2
174
  margin_y = (target_size[1] - new_height) // 2
@@ -185,26 +147,21 @@ def prepare_image_and_mask(image, width, height, overlap_percentage, resize_opti
185
  margin_x = (target_size[0] - new_width) // 2
186
  margin_y = target_size[1] - new_height
187
 
188
- # Adjust margins to eliminate gaps
189
  margin_x = max(0, min(margin_x, target_size[0] - new_width))
190
  margin_y = max(0, min(margin_y, target_size[1] - new_height))
191
 
192
- # Create a new background image and paste the resized source image
193
  background = Image.new('RGB', target_size, (255, 255, 255))
194
  background.paste(source, (margin_x, margin_y))
195
 
196
- # Create the mask
197
  mask = Image.new('L', target_size, 255)
198
  mask_draw = ImageDraw.Draw(mask)
199
 
200
- # Calculate overlap areas
201
  white_gaps_patch = 2
202
-
203
  left_overlap = margin_x + overlap_x if overlap_left else margin_x + white_gaps_patch
204
  right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width - white_gaps_patch
205
  top_overlap = margin_y + overlap_y if overlap_top else margin_y + white_gaps_patch
206
  bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height - white_gaps_patch
207
-
208
  if alignment == "Left":
209
  left_overlap = margin_x + overlap_x if overlap_left else margin_x
210
  elif alignment == "Right":
@@ -214,35 +171,22 @@ def prepare_image_and_mask(image, width, height, overlap_percentage, resize_opti
214
  elif alignment == "Bottom":
215
  bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height
216
 
217
-
218
- # Draw the mask
219
  mask_draw.rectangle([
220
  (left_overlap, top_overlap),
221
  (right_overlap, bottom_overlap)
222
  ], fill=0)
223
-
224
  return background, mask
225
 
226
  def preview_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
227
  background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
228
-
229
- # Create a preview image showing the mask
230
  preview = background.copy().convert('RGBA')
231
-
232
- # Create a semi-transparent red overlay
233
- red_overlay = Image.new('RGBA', background.size, (255, 0, 0, 64)) # Reduced alpha to 64 (25% opacity)
234
-
235
- # Convert black pixels in the mask to semi-transparent red
236
  red_mask = Image.new('RGBA', background.size, (0, 0, 0, 0))
237
  red_mask.paste(red_overlay, (0, 0), mask)
238
-
239
- # Overlay the red mask on the background
240
  preview = Image.alpha_composite(preview, red_mask)
241
-
242
  return preview
243
 
244
-
245
- @spaces.GPU(duration=12)
246
  def inpaint(prompt, image, inpaint_model, paste_back):
247
  global pipe
248
  if pipe.config.model_name != MODELS[model_name]:
@@ -252,39 +196,27 @@ def inpaint(prompt, image, inpaint_model, paste_back):
252
  vae=vae,
253
  controlnet=model,
254
  ).to("cuda")
255
- # if pipe.config.model_name == "Lustify Inpaint":
256
-
257
-
258
  mask = Image.fromarray(image["mask"]).convert("L")
259
  image = Image.fromarray(image["image"])
260
-
261
  result = pipe(prompt=prompt, image=image, mask_image=mask).images[0]
262
- # result = pipe_inpaint(prompt=prompt, image=image, mask_image=mask).images[0]
263
-
264
  if paste_back:
265
  result.paste(image, (0, 0), Image.fromarray(255 - np.array(mask)))
266
-
267
  return result
268
 
269
- @spaces.GPU(duration=12)
270
  def outpaint(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
271
  background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
272
-
273
  if not can_expand(background.width, background.height, width, height, alignment):
274
  alignment = "Middle"
275
-
276
  cnet_image = background.copy()
277
  cnet_image.paste(0, (0, 0), mask)
278
-
279
  final_prompt = f"{prompt_input} , high quality, 4k"
280
-
281
  (
282
  prompt_embeds,
283
  negative_prompt_embeds,
284
  pooled_prompt_embeds,
285
  negative_pooled_prompt_embeds,
286
  ) = pipe.encode_prompt(final_prompt, "cuda", True)
287
-
288
  for image in pipe(
289
  prompt_embeds=prompt_embeds,
290
  negative_prompt_embeds=negative_prompt_embeds,
@@ -294,31 +226,24 @@ def outpaint(image, width, height, overlap_percentage, num_inference_steps, resi
294
  num_inference_steps=num_inference_steps
295
  ):
296
  yield cnet_image, image
297
-
298
  image = image.convert("RGBA")
299
  cnet_image.paste(image, (0, 0), mask)
300
-
301
  yield background, cnet_image
302
 
303
- @spaces.GPU(duration=12)
304
  def infer(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
305
  background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
306
-
307
  if not can_expand(background.width, background.height, width, height, alignment):
308
  alignment = "Middle"
309
-
310
  cnet_image = background.copy()
311
  cnet_image.paste(0, (0, 0), mask)
312
-
313
  final_prompt = f"{prompt_input} , high quality, 4k"
314
-
315
  (
316
  prompt_embeds,
317
  negative_prompt_embeds,
318
  pooled_prompt_embeds,
319
  negative_pooled_prompt_embeds,
320
  ) = pipe.encode_prompt(final_prompt, "cuda", True)
321
-
322
  for image in pipe(
323
  prompt_embeds=prompt_embeds,
324
  negative_prompt_embeds=negative_prompt_embeds,
@@ -328,17 +253,14 @@ def infer(image, width, height, overlap_percentage, num_inference_steps, resize_
328
  num_inference_steps=num_inference_steps
329
  ):
330
  yield cnet_image, image
331
-
332
  image = image.convert("RGBA")
333
  cnet_image.paste(image, (0, 0), mask)
334
-
335
  yield background, cnet_image
336
- def clear_result():
337
- """Clears the result ImageSlider."""
338
- return gr.update(value=None)
339
 
340
  def preload_presets(target_ratio, ui_width, ui_height):
341
- """Updates the width and height sliders based on the selected aspect ratio."""
342
  if target_ratio == "9:16":
343
  changed_width = 720
344
  changed_height = 1280
@@ -357,6 +279,8 @@ def preload_presets(target_ratio, ui_width, ui_height):
357
  return changed_width, changed_height, gr.update()
358
  elif target_ratio == "Custom":
359
  return ui_width, ui_height, gr.update(open=True)
 
 
360
 
361
  def select_the_right_preset(user_width, user_height):
362
  if user_width == 720 and user_height == 1280:
@@ -374,7 +298,6 @@ def toggle_custom_resize_slider(resize_option):
374
  return gr.update(visible=(resize_option == "Custom"))
375
 
376
  def update_history(new_image, history):
377
- """Updates the history gallery with the new image."""
378
  if history is None:
379
  history = []
380
  history.insert(0, new_image)
@@ -399,14 +322,13 @@ title = """<h1 align="center">Diffusers Image Outpaint</h1>
399
  <p style="display: flex;gap: 6px;">
400
  <a href="https://huggingface.co/spaces/fffiloni/diffusers-image-outpout?duplicate=true">
401
  <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-md.svg" alt="Duplicate this Space">
402
- </a> to skip the queue and enjoy faster inference on the GPU of your choice
403
  </p>
404
  </div>
405
  """
406
 
407
  with gr.Blocks(css=css, fill_height=True) as demo:
408
  gr.Markdown("# Diffusers Inpaint and Outpaint")
409
-
410
  with gr.Tabs():
411
  with gr.TabItem("Inpaint"):
412
  with gr.Column():
@@ -423,31 +345,21 @@ with gr.Blocks(css=css, fill_height=True) as demo:
423
  value="RealVisXL V5.0 Lightning",
424
  label="Model",
425
  )
426
-
427
  with gr.Row():
428
  run_button = gr.Button("Generate")
429
  paste_back = gr.Checkbox(True, label="Paste back original")
430
-
431
  with gr.Row(equal_height=False):
432
  input_image = gr.ImageMask(
433
  type="pil", label="Input Image", layers=True
434
- # type="pil", label="Input Image", crop_size=(1024, 1024), layers=False
435
  )
436
-
437
  result = ImageSlider(
438
  interactive=False,
439
  label="Generated Image",
440
  )
441
-
442
  use_as_input_button = gr.Button("Use as Input Image", visible=False)
443
-
444
- def use_output_as_input(output_image):
445
- return gr.update(value=output_image[1])
446
-
447
  use_as_input_button.click(
448
  fn=use_output_as_input, inputs=[result], outputs=[input_image]
449
  )
450
-
451
  run_button.click(
452
  fn=clear_result,
453
  inputs=None,
@@ -459,13 +371,12 @@ with gr.Blocks(css=css, fill_height=True) as demo:
459
  ).then(
460
  fn=fill_image,
461
  inputs=[prompt, input_image, model_selection, paste_back],
462
- outputs=result,
463
  ).then(
464
  fn=lambda: gr.update(visible=True),
465
  inputs=None,
466
  outputs=use_as_input_button,
467
  )
468
-
469
  prompt.submit(
470
  fn=clear_result,
471
  inputs=None,
@@ -477,29 +388,25 @@ with gr.Blocks(css=css, fill_height=True) as demo:
477
  ).then(
478
  fn=fill_image,
479
  inputs=[prompt, input_image, model_selection, paste_back],
480
- outputs=result,
481
  ).then(
482
  fn=lambda: gr.update(visible=True),
483
  inputs=None,
484
  outputs=use_as_input_button,
485
  )
486
-
487
  with gr.TabItem("Outpaint"):
488
  with gr.Column():
489
-
490
  with gr.Row():
491
  with gr.Column():
492
- outpaint_input_image = gr.Image(
493
  type="pil",
494
  label="Input Image"
495
  )
496
-
497
  with gr.Row():
498
  with gr.Column(scale=2):
499
  prompt_input = gr.Textbox(label="Prompt (Optional)")
500
  with gr.Column(scale=1):
501
  runout_button = gr.Button("Generate")
502
-
503
  with gr.Row():
504
  target_ratio = gr.Radio(
505
  label="Expected Ratio",
@@ -507,13 +414,11 @@ with gr.Blocks(css=css, fill_height=True) as demo:
507
  value="1:1",
508
  scale=2
509
  )
510
-
511
  alignment_dropdown = gr.Dropdown(
512
  choices=["Middle", "Left", "Right", "Top", "Bottom"],
513
  value="Middle",
514
  label="Alignment"
515
  )
516
-
517
  with gr.Accordion(label="Advanced settings", open=False) as settings_panel:
518
  with gr.Column():
519
  with gr.Row():
@@ -522,16 +427,15 @@ with gr.Blocks(css=css, fill_height=True) as demo:
522
  minimum=720,
523
  maximum=1536,
524
  step=8,
525
- value=1280, # Set a default value
526
  )
527
  height_slider = gr.Slider(
528
  label="Target Height",
529
  minimum=720,
530
  maximum=1536,
531
  step=8,
532
- value=1280, # Set a default value
533
  )
534
-
535
  num_inference_steps = gr.Slider(label="Steps", minimum=4, maximum=12, step=1, value=8)
536
  with gr.Group():
537
  overlap_percentage = gr.Slider(
@@ -561,11 +465,8 @@ with gr.Blocks(css=css, fill_height=True) as demo:
561
  value=50,
562
  visible=False
563
  )
564
-
565
  with gr.Column():
566
  preview_button = gr.Button("Preview alignment and mask")
567
-
568
-
569
  gr.Examples(
570
  examples=[
571
  ["./examples/example_1.webp", 1280, 720, "Middle"],
@@ -573,136 +474,65 @@ with gr.Blocks(css=css, fill_height=True) as demo:
573
  ["./examples/example_3.jpg", 1024, 1024, "Top"],
574
  ["./examples/example_3.jpg", 1024, 1024, "Bottom"],
575
  ],
576
- inputs=[outpaint_input_image, width_slider, height_slider, alignment_dropdown],
577
  )
578
-
579
-
580
-
581
  with gr.Column():
582
- result = ImageSlider(
583
  interactive=False,
584
  label="Generated Image",
585
  )
586
- use_as_input_button = gr.Button("Use as Input Image", visible=False)
587
-
588
  history_gallery = gr.Gallery(label="History", columns=6, object_fit="contain", interactive=False)
589
  preview_image = gr.Image(label="Preview")
590
-
591
-
592
-
593
- def use_output_as_input(output_image):
594
- """Sets the generated output as the new input image."""
595
- return gr.update(value=output_image[1])
596
-
597
- use_as_input_button.click(
598
  fn=use_output_as_input,
599
- inputs=[result],
600
- outputs=[outpaint_input_image]
601
- )
602
-
603
- # Set up event handlers
604
- run_button.click(
605
- fn=fill_image,
606
- inputs=[prompt, input_image, model_selection, paste_back],
607
- outputs=result,
608
- )
609
-
610
- target_ratio.change(
611
- fn=preload_presets,
612
- inputs=[target_ratio, width_slider, height_slider],
613
- outputs=[width_slider, height_slider, settings_panel],
614
- queue=False
615
- )
616
-
617
- width_slider.change(
618
- fn=select_the_right_preset,
619
- inputs=[width_slider, height_slider],
620
- outputs=[target_ratio],
621
- queue=False
622
- )
623
-
624
- height_slider.change(
625
- fn=select_the_right_preset,
626
- inputs=[width_slider, height_slider],
627
- outputs=[target_ratio],
628
- queue=False
629
  )
630
-
631
- resize_option.change(
632
- fn=toggle_custom_resize_slider,
633
- inputs=[resize_option],
634
- outputs=[custom_resize_percentage],
635
- queue=False
636
- )
637
-
638
- runout_button.click( # Clear the result
639
  fn=clear_result,
640
  inputs=None,
641
- outputs=result,
642
- ).then( # Generate the new image
643
  fn=infer,
644
- inputs=[outpaint_input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
645
  resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
646
  overlap_left, overlap_right, overlap_top, overlap_bottom],
647
- outputs=result,
648
- ).then( # Update the history gallery
649
  fn=lambda x, history: update_history(x[1], history),
650
- inputs=[result, history_gallery],
651
  outputs=history_gallery,
652
- ).then( # Show the "Use as Input Image" button
653
  fn=lambda: gr.update(visible=True),
654
  inputs=None,
655
- outputs=use_as_input_button,
656
  )
657
-
658
- prompt_input.submit( # Clear the result
659
  fn=clear_result,
660
  inputs=None,
661
- outputs=result,
662
- ).then( # Generate the new image
663
  fn=infer,
664
- inputs=[outpaint_input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
665
  resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
666
  overlap_left, overlap_right, overlap_top, overlap_bottom],
667
- outputs=result,
668
- ).then( # Update the history gallery
669
  fn=lambda x, history: update_history(x[1], history),
670
- inputs=[result, history_gallery],
671
  outputs=history_gallery,
672
- ).then( # Show the "Use as Input Image" button
673
  fn=lambda: gr.update(visible=True),
674
  inputs=None,
675
- outputs=use_as_input_button,
676
- )
677
-
678
- preview_button.click(
679
- fn=preview_image_and_mask,
680
- inputs=[outpaint_input_image, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
681
- overlap_left, overlap_right, overlap_top, overlap_bottom],
682
- outputs=preview_image,
683
- queue=False
684
- )
685
-
686
- runout_button.click(
687
- fn=infer,
688
- inputs=[outpaint_input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
689
- resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
690
- overlap_left, overlap_right, overlap_top, overlap_bottom],
691
- outputs=result,
692
  )
693
-
694
  preview_button.click(
695
  fn=preview_image_and_mask,
696
- inputs=[outpaint_input_image, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
697
  overlap_left, overlap_right, overlap_top, overlap_bottom],
698
- outputs=preview_image,
699
  queue=False
700
  )
701
 
702
- resize_option.change(
703
- fn=lambda x: gr.update(visible=(x == "Custom")),
704
- inputs=[resize_option],
705
- outputs=[custom_resize_percentage]
706
- )
707
-
708
  demo.launch(show_error=True)
 
 
1
  import spaces
2
+ import gradio as gr
3
  import torch
4
  from diffusers import AutoencoderKL, TCDScheduler
5
  from diffusers.models.model_loading_utils import load_state_dict
6
  from gradio_imageslider import ImageSlider
7
  from huggingface_hub import hf_hub_download
 
8
  from controlnet_union import ControlNetModel_Union
9
  from pipeline_fill_sd_xl import StableDiffusionXLFillPipeline
 
10
  from PIL import Image, ImageDraw
11
  import numpy as np
12
 
13
  MODELS = {
14
  "RealVisXL V5.0 Lightning": "SG161222/RealVisXL_V5.0_Lightning",
15
  "Lustify Lightning": "GraydientPlatformAPI/lustify-lightning",
 
16
  "Juggernaut XL Lightning": "RunDiffusion/Juggernaut-XL-Lightning",
17
  }
18
 
 
20
  "xinsir/controlnet-union-sdxl-1.0",
21
  filename="config_promax.json",
22
  )
 
23
  config = ControlNetModel_Union.load_config(config_file)
24
  controlnet_model = ControlNetModel_Union.from_config(config)
25
  model_file = hf_hub_download(
 
31
  controlnet_model, state_dict, model_file, "xinsir/controlnet-union-sdxl-1.0"
32
  )
33
  model.to(device="cuda", dtype=torch.float16)
 
34
  vae = AutoencoderKL.from_pretrained(
35
  "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
36
  ).to("cuda")
 
37
  pipe = StableDiffusionXLFillPipeline.from_pretrained(
38
  "SG161222/RealVisXL_V5.0_Lightning",
39
  torch_dtype=torch.float16,
 
41
  controlnet=model,
42
  variant="fp16",
43
  )
 
44
  pipe = StableDiffusionXLFillPipeline.from_pretrained(
45
  "GraydientPlatformAPI/lustify-lightning",
46
  torch_dtype=torch.float16,
47
  vae=vae,
48
  controlnet=model,
49
  )
 
50
  pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
 
51
  pipe.to("cuda")
52
 
53
+ @gr.GPU(duration=12)
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  def fill_image(prompt, image, model_selection, paste_back):
55
+ print(f"Received image: {image}")
56
+ if image is None:
57
+ yield None, None
58
+ return
59
 
60
  (
61
  prompt_embeds,
 
63
  pooled_prompt_embeds,
64
  negative_pooled_prompt_embeds,
65
  ) = pipe.encode_prompt(prompt, "cuda", True)
 
66
  source = image["background"]
67
  mask = image["layers"][0]
 
68
  alpha_channel = mask.split()[3]
69
  binary_mask = alpha_channel.point(lambda p: p > 0 and 255)
70
  cnet_image = source.copy()
 
81
 
82
  print(f"{model_selection=}")
83
  print(f"{paste_back=}")
 
84
  if paste_back:
85
  image = image.convert("RGBA")
86
  cnet_image.paste(image, (0, 0), binary_mask)
87
  else:
88
  cnet_image = image
 
89
  yield source, cnet_image
90
 
 
91
  def clear_result():
92
  return gr.update(value=None)
93
+
94
  def can_expand(source_width, source_height, target_width, target_height, alignment):
 
95
  if alignment in ("Left", "Right") and source_width >= target_width:
96
  return False
97
  if alignment in ("Top", "Bottom") and source_height >= target_height:
 
100
 
101
  def prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
102
  target_size = (width, height)
 
 
103
  scale_factor = min(target_size[0] / image.width, target_size[1] / image.height)
104
  new_width = int(image.width * scale_factor)
105
  new_height = int(image.height * scale_factor)
 
 
 
106
 
107
+ source = image.resize((new_width, new_height), Image.LANCZOS)
108
  if resize_option == "Full":
109
  resize_percentage = 100
110
  elif resize_option == "80%":
 
118
  else: # Custom
119
  resize_percentage = custom_resize_percentage
120
 
 
121
  resize_factor = resize_percentage / 100
122
  new_width = int(source.width * resize_factor)
123
  new_height = int(source.height * resize_factor)
 
 
124
  new_width = max(new_width, 64)
125
  new_height = max(new_height, 64)
126
 
 
127
  source = source.resize((new_width, new_height), Image.LANCZOS)
128
 
 
129
  overlap_x = int(new_width * (overlap_percentage / 100))
130
  overlap_y = int(new_height * (overlap_percentage / 100))
 
 
131
  overlap_x = max(overlap_x, 1)
132
  overlap_y = max(overlap_y, 1)
133
 
 
134
  if alignment == "Middle":
135
  margin_x = (target_size[0] - new_width) // 2
136
  margin_y = (target_size[1] - new_height) // 2
 
147
  margin_x = (target_size[0] - new_width) // 2
148
  margin_y = target_size[1] - new_height
149
 
 
150
  margin_x = max(0, min(margin_x, target_size[0] - new_width))
151
  margin_y = max(0, min(margin_y, target_size[1] - new_height))
152
 
 
153
  background = Image.new('RGB', target_size, (255, 255, 255))
154
  background.paste(source, (margin_x, margin_y))
155
 
 
156
  mask = Image.new('L', target_size, 255)
157
  mask_draw = ImageDraw.Draw(mask)
158
 
 
159
  white_gaps_patch = 2
 
160
  left_overlap = margin_x + overlap_x if overlap_left else margin_x + white_gaps_patch
161
  right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width - white_gaps_patch
162
  top_overlap = margin_y + overlap_y if overlap_top else margin_y + white_gaps_patch
163
  bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height - white_gaps_patch
164
+
165
  if alignment == "Left":
166
  left_overlap = margin_x + overlap_x if overlap_left else margin_x
167
  elif alignment == "Right":
 
171
  elif alignment == "Bottom":
172
  bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height
173
 
 
 
174
  mask_draw.rectangle([
175
  (left_overlap, top_overlap),
176
  (right_overlap, bottom_overlap)
177
  ], fill=0)
 
178
  return background, mask
179
 
180
  def preview_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
181
  background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
 
 
182
  preview = background.copy().convert('RGBA')
183
+ red_overlay = Image.new('RGBA', background.size, (255, 0, 0, 64))
 
 
 
 
184
  red_mask = Image.new('RGBA', background.size, (0, 0, 0, 0))
185
  red_mask.paste(red_overlay, (0, 0), mask)
 
 
186
  preview = Image.alpha_composite(preview, red_mask)
 
187
  return preview
188
 
189
+ @gr.GPU(duration=12)
 
190
  def inpaint(prompt, image, inpaint_model, paste_back):
191
  global pipe
192
  if pipe.config.model_name != MODELS[model_name]:
 
196
  vae=vae,
197
  controlnet=model,
198
  ).to("cuda")
 
 
 
199
  mask = Image.fromarray(image["mask"]).convert("L")
200
  image = Image.fromarray(image["image"])
 
201
  result = pipe(prompt=prompt, image=image, mask_image=mask).images[0]
 
 
202
  if paste_back:
203
  result.paste(image, (0, 0), Image.fromarray(255 - np.array(mask)))
 
204
  return result
205
 
206
+ @gr.GPU(duration=12)
207
  def outpaint(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
208
  background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
 
209
  if not can_expand(background.width, background.height, width, height, alignment):
210
  alignment = "Middle"
 
211
  cnet_image = background.copy()
212
  cnet_image.paste(0, (0, 0), mask)
 
213
  final_prompt = f"{prompt_input} , high quality, 4k"
 
214
  (
215
  prompt_embeds,
216
  negative_prompt_embeds,
217
  pooled_prompt_embeds,
218
  negative_pooled_prompt_embeds,
219
  ) = pipe.encode_prompt(final_prompt, "cuda", True)
 
220
  for image in pipe(
221
  prompt_embeds=prompt_embeds,
222
  negative_prompt_embeds=negative_prompt_embeds,
 
226
  num_inference_steps=num_inference_steps
227
  ):
228
  yield cnet_image, image
 
229
  image = image.convert("RGBA")
230
  cnet_image.paste(image, (0, 0), mask)
 
231
  yield background, cnet_image
232
 
233
+ @gr.GPU(duration=12)
234
  def infer(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
235
  background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
 
236
  if not can_expand(background.width, background.height, width, height, alignment):
237
  alignment = "Middle"
 
238
  cnet_image = background.copy()
239
  cnet_image.paste(0, (0, 0), mask)
 
240
  final_prompt = f"{prompt_input} , high quality, 4k"
 
241
  (
242
  prompt_embeds,
243
  negative_prompt_embeds,
244
  pooled_prompt_embeds,
245
  negative_pooled_prompt_embeds,
246
  ) = pipe.encode_prompt(final_prompt, "cuda", True)
 
247
  for image in pipe(
248
  prompt_embeds=prompt_embeds,
249
  negative_prompt_embeds=negative_prompt_embeds,
 
253
  num_inference_steps=num_inference_steps
254
  ):
255
  yield cnet_image, image
 
256
  image = image.convert("RGBA")
257
  cnet_image.paste(image, (0, 0), mask)
 
258
  yield background, cnet_image
259
+
260
+ def use_output_as_input(output_image):
261
+ return gr.update(value=output_image[1])
262
 
263
  def preload_presets(target_ratio, ui_width, ui_height):
 
264
  if target_ratio == "9:16":
265
  changed_width = 720
266
  changed_height = 1280
 
279
  return changed_width, changed_height, gr.update()
280
  elif target_ratio == "Custom":
281
  return ui_width, ui_height, gr.update(open=True)
282
+ else:
283
+ return ui_width, ui_height, gr.update()
284
 
285
  def select_the_right_preset(user_width, user_height):
286
  if user_width == 720 and user_height == 1280:
 
298
  return gr.update(visible=(resize_option == "Custom"))
299
 
300
  def update_history(new_image, history):
 
301
  if history is None:
302
  history = []
303
  history.insert(0, new_image)
 
322
  <p style="display: flex;gap: 6px;">
323
  <a href="https://huggingface.co/spaces/fffiloni/diffusers-image-outpout?duplicate=true">
324
  <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-md.svg" alt="Duplicate this Space">
325
+ </a> to skip the queue and enjoy faster inference on the GPU of your choice
326
  </p>
327
  </div>
328
  """
329
 
330
  with gr.Blocks(css=css, fill_height=True) as demo:
331
  gr.Markdown("# Diffusers Inpaint and Outpaint")
 
332
  with gr.Tabs():
333
  with gr.TabItem("Inpaint"):
334
  with gr.Column():
 
345
  value="RealVisXL V5.0 Lightning",
346
  label="Model",
347
  )
 
348
  with gr.Row():
349
  run_button = gr.Button("Generate")
350
  paste_back = gr.Checkbox(True, label="Paste back original")
 
351
  with gr.Row(equal_height=False):
352
  input_image = gr.ImageMask(
353
  type="pil", label="Input Image", layers=True
 
354
  )
 
355
  result = ImageSlider(
356
  interactive=False,
357
  label="Generated Image",
358
  )
 
359
  use_as_input_button = gr.Button("Use as Input Image", visible=False)
 
 
 
 
360
  use_as_input_button.click(
361
  fn=use_output_as_input, inputs=[result], outputs=[input_image]
362
  )
 
363
  run_button.click(
364
  fn=clear_result,
365
  inputs=None,
 
371
  ).then(
372
  fn=fill_image,
373
  inputs=[prompt, input_image, model_selection, paste_back],
374
+ outputs=[result],
375
  ).then(
376
  fn=lambda: gr.update(visible=True),
377
  inputs=None,
378
  outputs=use_as_input_button,
379
  )
 
380
  prompt.submit(
381
  fn=clear_result,
382
  inputs=None,
 
388
  ).then(
389
  fn=fill_image,
390
  inputs=[prompt, input_image, model_selection, paste_back],
391
+ outputs=[result],
392
  ).then(
393
  fn=lambda: gr.update(visible=True),
394
  inputs=None,
395
  outputs=use_as_input_button,
396
  )
 
397
  with gr.TabItem("Outpaint"):
398
  with gr.Column():
 
399
  with gr.Row():
400
  with gr.Column():
401
+ input_image_outpaint = gr.Image(
402
  type="pil",
403
  label="Input Image"
404
  )
 
405
  with gr.Row():
406
  with gr.Column(scale=2):
407
  prompt_input = gr.Textbox(label="Prompt (Optional)")
408
  with gr.Column(scale=1):
409
  runout_button = gr.Button("Generate")
 
410
  with gr.Row():
411
  target_ratio = gr.Radio(
412
  label="Expected Ratio",
 
414
  value="1:1",
415
  scale=2
416
  )
 
417
  alignment_dropdown = gr.Dropdown(
418
  choices=["Middle", "Left", "Right", "Top", "Bottom"],
419
  value="Middle",
420
  label="Alignment"
421
  )
 
422
  with gr.Accordion(label="Advanced settings", open=False) as settings_panel:
423
  with gr.Column():
424
  with gr.Row():
 
427
  minimum=720,
428
  maximum=1536,
429
  step=8,
430
+ value=1280,
431
  )
432
  height_slider = gr.Slider(
433
  label="Target Height",
434
  minimum=720,
435
  maximum=1536,
436
  step=8,
437
+ value=1280,
438
  )
 
439
  num_inference_steps = gr.Slider(label="Steps", minimum=4, maximum=12, step=1, value=8)
440
  with gr.Group():
441
  overlap_percentage = gr.Slider(
 
465
  value=50,
466
  visible=False
467
  )
 
468
  with gr.Column():
469
  preview_button = gr.Button("Preview alignment and mask")
 
 
470
  gr.Examples(
471
  examples=[
472
  ["./examples/example_1.webp", 1280, 720, "Middle"],
 
474
  ["./examples/example_3.jpg", 1024, 1024, "Top"],
475
  ["./examples/example_3.jpg", 1024, 1024, "Bottom"],
476
  ],
477
+ inputs=[input_image_outpaint, width_slider, height_slider, alignment_dropdown],
478
  )
 
 
 
479
  with gr.Column():
480
+ result_outpaint = ImageSlider(
481
  interactive=False,
482
  label="Generated Image",
483
  )
484
+ use_as_input_button_outpaint = gr.Button("Use as Input Image", visible=False)
 
485
  history_gallery = gr.Gallery(label="History", columns=6, object_fit="contain", interactive=False)
486
  preview_image = gr.Image(label="Preview")
487
+ use_as_input_button_outpaint.click(
 
 
 
 
 
 
 
488
  fn=use_output_as_input,
489
+ inputs=[result_outpaint],
490
+ outputs=[input_image_outpaint]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  )
492
+ runout_button.click(
 
 
 
 
 
 
 
 
493
  fn=clear_result,
494
  inputs=None,
495
+ outputs=result_outpaint,
496
+ ).then(
497
  fn=infer,
498
+ inputs=[input_image_outpaint, width_slider, height_slider, overlap_percentage, num_inference_steps,
499
  resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
500
  overlap_left, overlap_right, overlap_top, overlap_bottom],
501
+ outputs=[result_outpaint],
502
+ ).then(
503
  fn=lambda x, history: update_history(x[1], history),
504
+ inputs=[result_outpaint, history_gallery],
505
  outputs=history_gallery,
506
+ ).then(
507
  fn=lambda: gr.update(visible=True),
508
  inputs=None,
509
+ outputs=[use_as_input_button_outpaint],
510
  )
511
+ prompt_input.submit(
 
512
  fn=clear_result,
513
  inputs=None,
514
+ outputs=result_outpaint,
515
+ ).then(
516
  fn=infer,
517
+ inputs=[input_image_outpaint, width_slider, height_slider, overlap_percentage, num_inference_steps,
518
  resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
519
  overlap_left, overlap_right, overlap_top, overlap_bottom],
520
+ outputs=[result_outpaint],
521
+ ).then(
522
  fn=lambda x, history: update_history(x[1], history),
523
+ inputs=[result_outpaint, history_gallery],
524
  outputs=history_gallery,
525
+ ).then(
526
  fn=lambda: gr.update(visible=True),
527
  inputs=None,
528
+ outputs=[use_as_input_button_outpaint],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  )
 
530
  preview_button.click(
531
  fn=preview_image_and_mask,
532
+ inputs=[input_image_outpaint, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
533
  overlap_left, overlap_right, overlap_top, overlap_bottom],
534
+ outputs=[preview_image],
535
  queue=False
536
  )
537
 
 
 
 
 
 
 
538
  demo.launch(show_error=True)