Gemini899 commited on
Commit
0aef016
·
verified ·
1 Parent(s): 820711f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -213
app.py CHANGED
@@ -11,12 +11,6 @@ from pipeline_fill_sd_xl import StableDiffusionXLFillPipeline
11
 
12
  from PIL import Image, ImageDraw
13
  import numpy as np
14
- import tempfile
15
-
16
-
17
- # ---------------------------
18
- # Load ControlNet-Union + VAE + SDXL Fill pipeline (same as your Space)
19
- # ---------------------------
20
 
21
  config_file = hf_hub_download(
22
  "xinsir/controlnet-union-sdxl-1.0",
@@ -45,6 +39,7 @@ result = ControlNetModel_Union._load_pretrained_model(
45
  model = result[0]
46
  model = model.to(device="cuda", dtype=torch.float16)
47
 
 
48
  vae = AutoencoderKL.from_pretrained(
49
  "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
50
  ).to("cuda")
@@ -60,10 +55,6 @@ pipe = StableDiffusionXLFillPipeline.from_pretrained(
60
  pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
61
 
62
 
63
- # ---------------------------
64
- # Helpers (unchanged behavior)
65
- # ---------------------------
66
-
67
  def can_expand(source_width, source_height, target_width, target_height, alignment):
68
  """Checks if the image can be expanded based on the alignment."""
69
  if alignment in ("Left", "Right") and source_width >= target_width:
@@ -72,17 +63,14 @@ def can_expand(source_width, source_height, target_width, target_height, alignme
72
  return False
73
  return True
74
 
75
-
76
- def prepare_image_and_mask(image, width, height, overlap_percentage,
77
- resize_option, custom_resize_percentage, alignment,
78
- overlap_left, overlap_right, overlap_top, overlap_bottom):
79
- target_size = (int(width), int(height))
80
 
81
  # Calculate the scaling factor to fit the image within the target size
82
  scale_factor = min(target_size[0] / image.width, target_size[1] / image.height)
83
  new_width = int(image.width * scale_factor)
84
  new_height = int(image.height * scale_factor)
85
-
86
  # Resize the source image to fit within target size
87
  source = image.resize((new_width, new_height), Image.LANCZOS)
88
 
@@ -95,20 +83,28 @@ def prepare_image_and_mask(image, width, height, overlap_percentage,
95
  resize_percentage = 33
96
  elif resize_option == "25%":
97
  resize_percentage = 25
98
- elif resize_option == "Custom":
99
- resize_percentage = max(1, min(400, int(custom_resize_percentage)))
100
- else:
101
- resize_percentage = 100
 
 
 
102
 
103
- # Apply the resize percentage to the already fitted source
104
- resize_factor = resize_percentage / 100.0
105
- new_width = max(64, int(source.width * resize_factor))
106
- new_height = max(64, int(source.height * resize_factor))
 
107
  source = source.resize((new_width, new_height), Image.LANCZOS)
108
 
109
  # Calculate the overlap in pixels based on the percentage
110
- overlap_x = max(1, int(new_width * (float(overlap_percentage) / 100.0)))
111
- overlap_y = max(1, int(new_height * (float(overlap_percentage) / 100.0)))
 
 
 
 
112
 
113
  # Calculate margins based on alignment
114
  if alignment == "Middle":
@@ -126,9 +122,6 @@ def prepare_image_and_mask(image, width, height, overlap_percentage,
126
  elif alignment == "Bottom":
127
  margin_x = (target_size[0] - new_width) // 2
128
  margin_y = target_size[1] - new_height
129
- else:
130
- margin_x = (target_size[0] - new_width) // 2
131
- margin_y = (target_size[1] - new_height) // 2
132
 
133
  # Adjust margins to eliminate gaps
134
  margin_x = max(0, min(margin_x, target_size[0] - new_width))
@@ -149,8 +142,7 @@ def prepare_image_and_mask(image, width, height, overlap_percentage,
149
  right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width - white_gaps_patch
150
  top_overlap = margin_y + overlap_y if overlap_top else margin_y + white_gaps_patch
151
  bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height - white_gaps_patch
152
-
153
- # Tighten edges further depending on chosen alignment
154
  if alignment == "Left":
155
  left_overlap = margin_x + overlap_x if overlap_left else margin_x
156
  elif alignment == "Right":
@@ -160,7 +152,8 @@ def prepare_image_and_mask(image, width, height, overlap_percentage,
160
  elif alignment == "Bottom":
161
  bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height
162
 
163
- # Draw the mask (black = keep, white = generate)
 
164
  mask_draw.rectangle([
165
  (left_overlap, top_overlap),
166
  (right_overlap, bottom_overlap)
@@ -168,40 +161,28 @@ def prepare_image_and_mask(image, width, height, overlap_percentage,
168
 
169
  return background, mask
170
 
171
-
172
- def preview_image_and_mask(image, width, height, overlap_percentage,
173
- resize_option, custom_resize_percentage, alignment,
174
- overlap_left, overlap_right, overlap_top, overlap_bottom):
175
- background, mask = prepare_image_and_mask(
176
- image, width, height, overlap_percentage,
177
- resize_option, custom_resize_percentage, alignment,
178
- overlap_left, overlap_right, overlap_top, overlap_bottom
179
- )
180
-
181
- # Create a preview image showing the mask overlay
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
-
190
- # ---------------------------
191
- # Main UI inference (returns ImageSlider tuple)
192
- # ---------------------------
193
-
194
  @spaces.GPU(duration=24)
195
- def infer(image, width, height, overlap_percentage, num_inference_steps,
196
- resize_option, custom_resize_percentage, prompt_input, alignment,
197
- overlap_left, overlap_right, overlap_top, overlap_bottom):
198
-
199
- background, mask = prepare_image_and_mask(
200
- image, width, height, overlap_percentage,
201
- resize_option, custom_resize_percentage, alignment,
202
- overlap_left, overlap_right, overlap_top, overlap_bottom
203
- )
204
-
205
  if not can_expand(background.width, background.height, width, height, alignment):
206
  alignment = "Middle"
207
 
@@ -210,7 +191,7 @@ def infer(image, width, height, overlap_percentage, num_inference_steps,
210
 
211
  final_prompt = f"{prompt_input} , high quality, 4k" if prompt_input else "high quality, 4k"
212
 
213
- # Encode prompt + run pipeline yielding previews then final
214
  with torch.autocast(device_type="cuda", dtype=torch.float16):
215
  (
216
  prompt_embeds,
@@ -227,80 +208,12 @@ def infer(image, width, height, overlap_percentage, num_inference_steps,
227
  image=cnet_image,
228
  num_inference_steps=num_inference_steps
229
  ):
230
- # Streaming preview to slider (left = control, right = preview)
231
  yield cnet_image, image
232
 
233
- # Final composite (place the original inside the masked area)
234
  image = image.convert("RGBA")
235
  cnet_image.paste(image, (0, 0), mask)
236
- yield background, cnet_image
237
-
238
-
239
- # ---------------------------
240
- # img2img-style API: /process_images (single file path string)
241
- # ---------------------------
242
-
243
- @spaces.GPU(duration=24)
244
- def process_images(
245
- image, # PIL image from handle_file
246
- prompt="", # str
247
- strength=0.75, # kept for client parity; unused
248
- seed=0, # int
249
- inference_step=8, # int
250
- width=720, # int
251
- height=1280, # int
252
- overlap_percentage=10, # float
253
- alignment="Middle", # str
254
- ):
255
- if image is None:
256
- return None
257
-
258
- # Use same prep as UI
259
- resize_option = "Full"
260
- custom_resize_percentage = 50
261
- overlap_left = overlap_right = overlap_top = overlap_bottom = True
262
-
263
- background, mask = prepare_image_and_mask(
264
- image, int(width), int(height), float(overlap_percentage),
265
- resize_option, float(custom_resize_percentage), alignment,
266
- overlap_left, overlap_right, overlap_top, overlap_bottom
267
- )
268
 
269
- cnet_image = background.copy()
270
- cnet_image.paste(0, (0, 0), mask)
271
-
272
- final_prompt = f"{prompt} , high quality, 4k" if prompt else "high quality, 4k"
273
-
274
- last_img = None
275
- with torch.autocast(device_type="cuda", dtype=torch.float16):
276
- (
277
- prompt_embeds,
278
- negative_prompt_embeds,
279
- pooled_prompt_embeds,
280
- negative_pooled_prompt_embeds,
281
- ) = pipe.encode_prompt(final_prompt, "cuda", True)
282
-
283
- for gen_img in pipe(
284
- prompt_embeds=prompt_embeds,
285
- negative_prompt_embeds=negative_prompt_embeds,
286
- pooled_prompt_embeds=pooled_prompt_embeds,
287
- negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
288
- image=cnet_image,
289
- num_inference_steps=int(inference_step)
290
- ):
291
- last_img = gen_img
292
-
293
- if last_img is None:
294
- return None
295
-
296
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
297
- last_img.save(tmp.name)
298
- return tmp.name
299
-
300
-
301
- # ---------------------------
302
- # Misc helpers & UI wiring
303
- # ---------------------------
304
 
305
  def clear_result():
306
  """Clears the result ImageSlider."""
@@ -320,33 +233,22 @@ def preload_presets(target_ratio, ui_width, ui_height):
320
  changed_width = 1024
321
  changed_height = 1024
322
  return changed_width, changed_height, gr.update()
323
- else:
324
- return ui_width, ui_height, gr.update()
325
 
326
  def select_the_right_preset(user_width, user_height):
327
- """Chooses the closest preset by ratio (for display)."""
328
- ratio = user_width / max(1, user_height)
329
- if abs(ratio - (9/16)) < 0.05:
330
  return "9:16"
331
- if abs(ratio - (16/9)) < 0.05:
332
  return "16:9"
333
- if abs(ratio - 1.0) < 0.05:
334
  return "1:1"
335
- return "Custom"
 
336
 
337
  def toggle_custom_resize_slider(resize_option):
338
- """Controls visibility of the custom resize slider."""
339
  return gr.update(visible=(resize_option == "Custom"))
340
 
341
- def use_output_as_input(x):
342
- """API bridge for ImageSlider -> Image. Returns right-hand image as next input."""
343
- if not x:
344
- return None
345
- if isinstance(x, (list, tuple)) and len(x) >= 2:
346
- # return the generated (right) image
347
- return x[1]
348
- return None
349
-
350
  def update_history(new_image, history):
351
  """Updates the history gallery with the new image."""
352
  if history is None:
@@ -354,15 +256,14 @@ def update_history(new_image, history):
354
  history.insert(0, new_image)
355
  return history
356
 
357
-
358
  css = """
359
  .gradio-container {
360
  width: 1200px !important;
361
  }
362
  """
363
 
 
364
  title = """<h1 align="center">Re-Size Image Outpaint</h1>
365
- <p align="center">Extend images with ControlNet-Union SDXL fill — with an ImageSlider preview.</p>
366
  """
367
 
368
  with gr.Blocks(theme="soft", css=css) as demo:
@@ -379,16 +280,29 @@ with gr.Blocks(theme="soft", css=css) as demo:
379
  with gr.Row():
380
  with gr.Column(scale=2):
381
  prompt_input = gr.Textbox(label="Prompt (Optional)")
 
 
382
 
383
  with gr.Row():
384
- with gr.Column(scale=2):
385
- target_ratio = gr.Radio(
386
- ["9:16", "16:9", "1:1", "Custom"], value="9:16", label="Expected Ratio"
387
- )
 
 
 
 
 
 
 
 
 
 
 
388
  with gr.Row():
389
  width_slider = gr.Slider(
390
  label="Target Width",
391
- minimum=512,
392
  maximum=1536,
393
  step=8,
394
  value=720,
@@ -400,7 +314,7 @@ with gr.Blocks(theme="soft", css=css) as demo:
400
  step=8,
401
  value=1280,
402
  )
403
-
404
  num_inference_steps = gr.Slider(label="Steps", minimum=4, maximum=12, step=1, value=8)
405
  with gr.Group():
406
  overlap_percentage = gr.Slider(
@@ -416,66 +330,86 @@ with gr.Blocks(theme="soft", css=css) as demo:
416
  with gr.Row():
417
  overlap_left = gr.Checkbox(label="Overlap Left", value=True)
418
  overlap_bottom = gr.Checkbox(label="Overlap Bottom", value=True)
419
-
420
- with gr.Column(scale=1):
421
- with gr.Group():
422
  resize_option = gr.Radio(
423
  label="Resize input image",
424
  choices=["Full", "50%", "33%", "25%", "Custom"],
425
  value="Full"
426
  )
427
- # FIX: set visibility here, do NOT call .update() on a component
428
  custom_resize_percentage = gr.Slider(
429
  label="Custom resize (%)",
430
  minimum=1,
431
  maximum=100,
432
  step=1,
433
  value=50,
434
- visible=False,
435
  )
436
-
437
  with gr.Column():
438
  preview_button = gr.Button("Preview alignment and mask")
439
-
 
440
  gr.Examples(
441
  examples=[
442
  ["./examples/example_2.jpg", 1440, 810, "Left"],
443
  ["./examples/example_3.jpg", 1024, 1024, "Top"],
444
  ["./examples/example_3.jpg", 1024, 1024, "Bottom"],
445
  ],
446
- inputs=[input_image, width_slider, height_slider, target_ratio],
447
- label="Quick examples",
448
  )
449
 
 
 
450
  with gr.Column():
451
- preview_image = gr.Image(label="Preview", height=300)
452
  result = ImageSlider(
453
- label="Generated Image",
454
- elem_id="gen_slider",
455
- show_label=True,
456
  interactive=False,
 
457
  )
458
- run_button = gr.Button("Generate", variant="primary")
459
- use_as_input_button = gr.Button("Use output as input", visible=False)
460
- history_gallery = gr.Gallery(label="History", columns=4, height=220)
461
 
462
- # Radio preset to width/height
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  target_ratio.change(
464
  fn=preload_presets,
465
  inputs=[target_ratio, width_slider, height_slider],
466
- outputs=[width_slider, height_slider, gr.State()],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  queue=False
468
  )
469
 
470
- # Toggle custom resize slider visibility
471
  resize_option.change(
472
  fn=toggle_custom_resize_slider,
473
  inputs=[resize_option],
474
  outputs=[custom_resize_percentage],
475
  queue=False
476
  )
477
-
478
- # Generate flow: clear slider -> stream infer -> update history -> show "use as input"
479
  run_button.click(
480
  fn=clear_result,
481
  inputs=None,
@@ -483,11 +417,12 @@ with gr.Blocks(theme="soft", css=css) as demo:
483
  ).then(
484
  fn=infer,
485
  inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
486
- resize_option, custom_resize_percentage, prompt_input, target_ratio,
487
  overlap_left, overlap_right, overlap_top, overlap_bottom],
488
  outputs=result,
489
  ).then(
490
- # Safely update history only if the result is not None
 
491
  fn=lambda x, history: update_history(x[1], history) if x else history,
492
  inputs=[result, history_gallery],
493
  outputs=history_gallery,
@@ -497,7 +432,6 @@ with gr.Blocks(theme="soft", css=css) as demo:
497
  outputs=use_as_input_button,
498
  )
499
 
500
- # Enter in prompt also triggers generate flow
501
  prompt_input.submit(
502
  fn=clear_result,
503
  inputs=None,
@@ -505,10 +439,12 @@ with gr.Blocks(theme="soft", css=css) as demo:
505
  ).then(
506
  fn=infer,
507
  inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
508
- resize_option, custom_resize_percentage, prompt_input, target_ratio,
509
  overlap_left, overlap_right, overlap_top, overlap_bottom],
510
  outputs=result,
511
  ).then(
 
 
512
  fn=lambda x, history: update_history(x[1], history) if x else history,
513
  inputs=[result, history_gallery],
514
  outputs=history_gallery,
@@ -518,43 +454,12 @@ with gr.Blocks(theme="soft", css=css) as demo:
518
  outputs=use_as_input_button,
519
  )
520
 
521
- # Preview button
522
  preview_button.click(
523
  fn=preview_image_and_mask,
524
- inputs=[input_image, width_slider, height_slider, overlap_percentage,
525
- resize_option, custom_resize_percentage, target_ratio,
526
  overlap_left, overlap_right, overlap_top, overlap_bottom],
527
  outputs=preview_image,
528
  queue=False
529
  )
530
 
531
- # Use output as next input (ImageSlider -> Image)
532
- use_as_input_button.click(
533
- fn=use_output_as_input,
534
- inputs=[result],
535
- outputs=[input_image],
536
- queue=False
537
- )
538
-
539
- # ===== Hidden API binding for img2img-compatible client =====
540
- # Returns a single PATH string (so your client can copy/handle it exactly like img2img)
541
- api_output_path = gr.Textbox(visible=False)
542
- api_trigger = gr.Button(visible=False)
543
- api_trigger.click(
544
- fn=process_images,
545
- inputs=[
546
- input_image, # image
547
- prompt_input, # prompt
548
- gr.Number(value=0.75), # strength (ignored)
549
- gr.Number(value=0), # seed
550
- num_inference_steps, # inference_step
551
- width_slider, # width
552
- height_slider, # height
553
- overlap_percentage, # overlap_percentage
554
- target_ratio # alignment (reusing same dropdown in this UI)
555
- ],
556
- outputs=[api_output_path],
557
- api_name="/process_images"
558
- )
559
-
560
- demo.queue(max_size=12).launch(share=False)
 
11
 
12
  from PIL import Image, ImageDraw
13
  import numpy as np
 
 
 
 
 
 
14
 
15
  config_file = hf_hub_download(
16
  "xinsir/controlnet-union-sdxl-1.0",
 
39
  model = result[0]
40
  model = model.to(device="cuda", dtype=torch.float16)
41
 
42
+
43
  vae = AutoencoderKL.from_pretrained(
44
  "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
45
  ).to("cuda")
 
55
  pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
56
 
57
 
 
 
 
 
58
  def can_expand(source_width, source_height, target_width, target_height, alignment):
59
  """Checks if the image can be expanded based on the alignment."""
60
  if alignment in ("Left", "Right") and source_width >= target_width:
 
63
  return False
64
  return True
65
 
66
+ def prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
67
+ target_size = (width, height)
 
 
 
68
 
69
  # Calculate the scaling factor to fit the image within the target size
70
  scale_factor = min(target_size[0] / image.width, target_size[1] / image.height)
71
  new_width = int(image.width * scale_factor)
72
  new_height = int(image.height * scale_factor)
73
+
74
  # Resize the source image to fit within target size
75
  source = image.resize((new_width, new_height), Image.LANCZOS)
76
 
 
83
  resize_percentage = 33
84
  elif resize_option == "25%":
85
  resize_percentage = 25
86
+ else: # Custom
87
+ resize_percentage = custom_resize_percentage
88
+
89
+ # Calculate new dimensions based on percentage
90
+ resize_factor = resize_percentage / 100
91
+ new_width = int(source.width * resize_factor)
92
+ new_height = int(source.height * resize_factor)
93
 
94
+ # Ensure minimum size of 64 pixels
95
+ new_width = max(new_width, 64)
96
+ new_height = max(new_height, 64)
97
+
98
+ # Resize the image
99
  source = source.resize((new_width, new_height), Image.LANCZOS)
100
 
101
  # Calculate the overlap in pixels based on the percentage
102
+ overlap_x = int(new_width * (overlap_percentage / 100))
103
+ overlap_y = int(new_height * (overlap_percentage / 100))
104
+
105
+ # Ensure minimum overlap of 1 pixel
106
+ overlap_x = max(overlap_x, 1)
107
+ overlap_y = max(overlap_y, 1)
108
 
109
  # Calculate margins based on alignment
110
  if alignment == "Middle":
 
122
  elif alignment == "Bottom":
123
  margin_x = (target_size[0] - new_width) // 2
124
  margin_y = target_size[1] - new_height
 
 
 
125
 
126
  # Adjust margins to eliminate gaps
127
  margin_x = max(0, min(margin_x, target_size[0] - new_width))
 
142
  right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width - white_gaps_patch
143
  top_overlap = margin_y + overlap_y if overlap_top else margin_y + white_gaps_patch
144
  bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height - white_gaps_patch
145
+
 
146
  if alignment == "Left":
147
  left_overlap = margin_x + overlap_x if overlap_left else margin_x
148
  elif alignment == "Right":
 
152
  elif alignment == "Bottom":
153
  bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height
154
 
155
+
156
+ # Draw the mask
157
  mask_draw.rectangle([
158
  (left_overlap, top_overlap),
159
  (right_overlap, bottom_overlap)
 
161
 
162
  return background, mask
163
 
164
+ def preview_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
165
+ 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)
166
+
167
+ # Create a preview image showing the mask
 
 
 
 
 
 
 
168
  preview = background.copy().convert('RGBA')
169
+
170
+ # Create a semi-transparent red overlay
171
+ red_overlay = Image.new('RGBA', background.size, (255, 0, 0, 64)) # Reduced alpha to 64 (25% opacity)
172
+
173
+ # Convert black pixels in the mask to semi-transparent red
174
  red_mask = Image.new('RGBA', background.size, (0, 0, 0, 0))
175
  red_mask.paste(red_overlay, (0, 0), mask)
176
+
177
+ # Overlay the red mask on the background
178
  preview = Image.alpha_composite(preview, red_mask)
179
+
180
  return preview
181
 
 
 
 
 
 
182
  @spaces.GPU(duration=24)
183
+ 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):
184
+ 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)
185
+
 
 
 
 
 
 
 
186
  if not can_expand(background.width, background.height, width, height, alignment):
187
  alignment = "Middle"
188
 
 
191
 
192
  final_prompt = f"{prompt_input} , high quality, 4k" if prompt_input else "high quality, 4k"
193
 
194
+ # Use with torch.autocast to ensure consistent dtype
195
  with torch.autocast(device_type="cuda", dtype=torch.float16):
196
  (
197
  prompt_embeds,
 
208
  image=cnet_image,
209
  num_inference_steps=num_inference_steps
210
  ):
 
211
  yield cnet_image, image
212
 
 
213
  image = image.convert("RGBA")
214
  cnet_image.paste(image, (0, 0), mask)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ yield background, cnet_image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
  def clear_result():
219
  """Clears the result ImageSlider."""
 
233
  changed_width = 1024
234
  changed_height = 1024
235
  return changed_width, changed_height, gr.update()
236
+ elif target_ratio == "Custom":
237
+ return ui_width, ui_height, gr.update(open=True)
238
 
239
  def select_the_right_preset(user_width, user_height):
240
+ if user_width == 720 and user_height == 1280:
 
 
241
  return "9:16"
242
+ elif user_width == 1280 and user_height == 720:
243
  return "16:9"
244
+ elif user_width == 1024 and user_height == 1024:
245
  return "1:1"
246
+ else:
247
+ return "Custom"
248
 
249
  def toggle_custom_resize_slider(resize_option):
 
250
  return gr.update(visible=(resize_option == "Custom"))
251
 
 
 
 
 
 
 
 
 
 
252
  def update_history(new_image, history):
253
  """Updates the history gallery with the new image."""
254
  if history is None:
 
256
  history.insert(0, new_image)
257
  return history
258
 
 
259
  css = """
260
  .gradio-container {
261
  width: 1200px !important;
262
  }
263
  """
264
 
265
+ # Define the title HTML string
266
  title = """<h1 align="center">Re-Size Image Outpaint</h1>
 
267
  """
268
 
269
  with gr.Blocks(theme="soft", css=css) as demo:
 
280
  with gr.Row():
281
  with gr.Column(scale=2):
282
  prompt_input = gr.Textbox(label="Prompt (Optional)")
283
+ with gr.Column(scale=1):
284
+ run_button = gr.Button("Generate")
285
 
286
  with gr.Row():
287
+ target_ratio = gr.Radio(
288
+ label="Expected Ratio",
289
+ choices=["9:16", "16:9", "1:1", "Custom"],
290
+ value="9:16",
291
+ scale=2
292
+ )
293
+
294
+ alignment_dropdown = gr.Dropdown(
295
+ choices=["Middle", "Left", "Right", "Top", "Bottom"],
296
+ value="Middle",
297
+ label="Alignment"
298
+ )
299
+
300
+ with gr.Accordion(label="Advanced settings", open=False) as settings_panel:
301
+ with gr.Column():
302
  with gr.Row():
303
  width_slider = gr.Slider(
304
  label="Target Width",
305
+ minimum=720,
306
  maximum=1536,
307
  step=8,
308
  value=720,
 
314
  step=8,
315
  value=1280,
316
  )
317
+
318
  num_inference_steps = gr.Slider(label="Steps", minimum=4, maximum=12, step=1, value=8)
319
  with gr.Group():
320
  overlap_percentage = gr.Slider(
 
330
  with gr.Row():
331
  overlap_left = gr.Checkbox(label="Overlap Left", value=True)
332
  overlap_bottom = gr.Checkbox(label="Overlap Bottom", value=True)
333
+ with gr.Row():
 
 
334
  resize_option = gr.Radio(
335
  label="Resize input image",
336
  choices=["Full", "50%", "33%", "25%", "Custom"],
337
  value="Full"
338
  )
 
339
  custom_resize_percentage = gr.Slider(
340
  label="Custom resize (%)",
341
  minimum=1,
342
  maximum=100,
343
  step=1,
344
  value=50,
345
+ visible=False
346
  )
347
+
348
  with gr.Column():
349
  preview_button = gr.Button("Preview alignment and mask")
350
+
351
+
352
  gr.Examples(
353
  examples=[
354
  ["./examples/example_2.jpg", 1440, 810, "Left"],
355
  ["./examples/example_3.jpg", 1024, 1024, "Top"],
356
  ["./examples/example_3.jpg", 1024, 1024, "Bottom"],
357
  ],
358
+ inputs=[input_image, width_slider, height_slider, alignment_dropdown],
 
359
  )
360
 
361
+
362
+
363
  with gr.Column():
 
364
  result = ImageSlider(
 
 
 
365
  interactive=False,
366
+ label="Generated Image",
367
  )
368
+ use_as_input_button = gr.Button("Use as Input Image", visible=False)
 
 
369
 
370
+ history_gallery = gr.Gallery(label="History", columns=6, object_fit="contain", interactive=False)
371
+ preview_image = gr.Image(label="Preview")
372
+
373
+
374
+
375
+ def use_output_as_input(output_image):
376
+ """Sets the generated output as the new input image."""
377
+ return gr.update(value=output_image[1])
378
+
379
+ use_as_input_button.click(
380
+ fn=use_output_as_input,
381
+ inputs=[result],
382
+ outputs=[input_image]
383
+ )
384
+
385
  target_ratio.change(
386
  fn=preload_presets,
387
  inputs=[target_ratio, width_slider, height_slider],
388
+ outputs=[width_slider, height_slider, settings_panel],
389
+ queue=False
390
+ )
391
+
392
+ width_slider.change(
393
+ fn=select_the_right_preset,
394
+ inputs=[width_slider, height_slider],
395
+ outputs=[target_ratio],
396
+ queue=False
397
+ )
398
+
399
+ height_slider.change(
400
+ fn=select_the_right_preset,
401
+ inputs=[width_slider, height_slider],
402
+ outputs=[target_ratio],
403
  queue=False
404
  )
405
 
 
406
  resize_option.change(
407
  fn=toggle_custom_resize_slider,
408
  inputs=[resize_option],
409
  outputs=[custom_resize_percentage],
410
  queue=False
411
  )
412
+
 
413
  run_button.click(
414
  fn=clear_result,
415
  inputs=None,
 
417
  ).then(
418
  fn=infer,
419
  inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
420
+ resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
421
  overlap_left, overlap_right, overlap_top, overlap_bottom],
422
  outputs=result,
423
  ).then(
424
+ # --- FIX APPLIED HERE ---
425
+ # Safely update history only if the result (x) is not None.
426
  fn=lambda x, history: update_history(x[1], history) if x else history,
427
  inputs=[result, history_gallery],
428
  outputs=history_gallery,
 
432
  outputs=use_as_input_button,
433
  )
434
 
 
435
  prompt_input.submit(
436
  fn=clear_result,
437
  inputs=None,
 
439
  ).then(
440
  fn=infer,
441
  inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
442
+ resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
443
  overlap_left, overlap_right, overlap_top, overlap_bottom],
444
  outputs=result,
445
  ).then(
446
+ # --- FIX APPLIED HERE ---
447
+ # Safely update history only if the result (x) is not None.
448
  fn=lambda x, history: update_history(x[1], history) if x else history,
449
  inputs=[result, history_gallery],
450
  outputs=history_gallery,
 
454
  outputs=use_as_input_button,
455
  )
456
 
 
457
  preview_button.click(
458
  fn=preview_image_and_mask,
459
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
 
460
  overlap_left, overlap_right, overlap_top, overlap_bottom],
461
  outputs=preview_image,
462
  queue=False
463
  )
464
 
465
+ demo.queue(max_size=12).launch(share=False)