prithivMLmods commited on
Commit
34d02f1
·
verified ·
1 Parent(s): f098d60

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +357 -384
app.py CHANGED
@@ -1,399 +1,372 @@
1
- import os
2
- import random
3
- import uuid
4
- from typing import Tuple
5
  import gradio as gr
 
 
 
 
6
  import numpy as np
7
- from PIL import Image
8
  import spaces
9
- import torch
10
- from diffusers import StableDiffusionXLPipeline, EulerAncestralDiscreteScheduler
11
-
12
- title = """<h1>SDXL LoRA DLC 🤩</h1>"""
13
-
14
- # Ensure assets directory exists if needed for predefined images
15
- if not os.path.exists("assets"):
16
- print("Warning: 'assets' directory not found. Predefined gallery might be empty.")
17
- # Optionally create it: os.makedirs("assets")
18
-
19
- def save_image(img):
20
- # Ensure an 'outputs' directory exists to save generated images (optional, good practice)
21
- output_dir = "outputs"
22
- if not os.path.exists(output_dir):
23
- os.makedirs(output_dir)
24
- unique_name = os.path.join(output_dir, str(uuid.uuid4()) + ".png")
25
- img.save(unique_name)
26
- return unique_name
27
-
28
- def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
29
- if randomize_seed:
30
- seed = random.randint(0, MAX_SEED)
31
- return seed
32
-
33
- MAX_SEED = np.iinfo(np.int32).max
34
- pipe = None # Initialize pipe to None
35
-
36
- if not torch.cuda.is_available():
37
- DESCRIPTIONz += "\n<p>⚠️Running on CPU, This may not work on CPU. If it runs for an extended time or if you encounter errors, try running it on a GPU by duplicating the space using @spaces.GPU(). +import spaces.📍</p>"
38
- # Optionally, you could add a placeholder or disable functionality here
39
- else:
40
- USE_TORCH_COMPILE = False # Set to False as 0 is not standard boolean
41
- ENABLE_CPU_OFFLOAD = False # Set to False as 0 is not standard boolean
42
-
43
- # Moved pipe initialization inside the CUDA check
44
- pipe = StableDiffusionXLPipeline.from_pretrained(
45
- "SG161222/RealVisXL_V4.0_Lightning", # [or] SG161222/RealVisXL_V5.0_Lightning
46
- torch_dtype=torch.float16,
47
- use_safetensors=True,
48
- )
49
- pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
50
-
51
- LORA_OPTIONS = {
52
- "Realism (face/character)👦🏻": ("prithivMLmods/Canopus-Realism-LoRA", "Canopus-Realism-LoRA.safetensors", "rlms"),
53
- "Pixar (art/toons)🙀": ("prithivMLmods/Canopus-Pixar-Art", "Canopus-Pixar-Art.safetensors", "pixar"),
54
- "Photoshoot (camera/film)📸": ("prithivMLmods/Canopus-Photo-Shoot-Mini-LoRA", "Canopus-Photo-Shoot-Mini-LoRA.safetensors", "photo"),
55
- "Clothing (hoodies/pant/shirts)👔": ("prithivMLmods/Canopus-Clothing-Adp-LoRA", "Canopus-Dress-Clothing-LoRA.safetensors", "clth"),
56
- "Interior Architecture (house/hotel)🏠": ("prithivMLmods/Canopus-Interior-Architecture-0.1", "Canopus-Interior-Architecture-0.1δ.safetensors", "arch"),
57
- "Fashion Product (wearing/usable)👜": ("prithivMLmods/Canopus-Fashion-Product-Dilation", "Canopus-Fashion-Product-Dilation.safetensors", "fashion"),
58
- "Minimalistic Image (minimal/detailed)🏞️": ("prithivMLmods/Pegasi-Minimalist-Image-Style", "Pegasi-Minimalist-Image-Style.safetensors", "minimalist"),
59
- "Modern Clothing (trend/new)👕": ("prithivMLmods/Canopus-Modern-Clothing-Design", "Canopus-Modern-Clothing-Design.safetensors", "mdrnclth"),
60
- "Animaliea (farm/wild)🫎": ("prithivMLmods/Canopus-Animaliea-Artism", "Canopus-Animaliea-Artism.safetensors", "Animaliea"),
61
- "Liquid Wallpaper (minimal/illustration)🖼️": ("prithivMLmods/Canopus-Liquid-Wallpaper-Art", "Canopus-Liquid-Wallpaper-Minimalize-LoRA.safetensors", "liquid"),
62
- "Canes Cars (realistic/futurecars)🚘": ("prithivMLmods/Canes-Cars-Model-LoRA", "Canes-Cars-Model-LoRA.safetensors", "car"),
63
- "Pencil Art (characteristic/creative)✏️": ("prithivMLmods/Canopus-Pencil-Art-LoRA", "Canopus-Pencil-Art-LoRA.safetensors", "Pencil Art"),
64
- "Art Minimalistic (paint/semireal)🎨": ("prithivMLmods/Canopus-Art-Medium-LoRA", "Canopus-Art-Medium-LoRA.safetensors", "mdm"),
65
- }
66
-
67
- # Load LoRAs only if pipe is initialized
68
- if pipe:
69
- for model_name, weight_name, adapter_name in LORA_OPTIONS.values():
70
- try:
71
- pipe.load_lora_weights(model_name, weight_name=weight_name, adapter_name=adapter_name)
72
- print(f"Loaded LoRA: {adapter_name}")
73
- except Exception as e:
74
- print(f"Warning: Could not load LoRA {adapter_name} from {model_name}. Error: {e}")
75
- pipe.to("cuda")
76
- print("Pipeline and LoRAs loaded to CUDA.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  else:
78
- print("Pipeline not initialized (likely no CUDA available).")
79
-
80
-
81
- style_list = [
82
- {
83
- "name": "3840 x 2160",
84
- "prompt": "hyper-realistic 8K image of {prompt}. ultra-detailed, lifelike, high-resolution, sharp, vibrant colors, photorealistic",
85
- "negative_prompt": "cartoonish, low resolution, blurry, simplistic, abstract, deformed, ugly",
86
- },
87
- {
88
- "name": "2560 x 1440",
89
- "prompt": "hyper-realistic 4K image of {prompt}. ultra-detailed, lifelike, high-resolution, sharp, vibrant colors, photorealistic",
90
- "negative_prompt": "cartoonish, low resolution, blurry, simplistic, abstract, deformed, ugly",
91
- },
92
- {
93
- "name": "HD+",
94
- "prompt": "hyper-realistic 2K image of {prompt}. ultra-detailed, lifelike, high-resolution, sharp, vibrant colors, photorealistic",
95
- "negative_prompt": "cartoonish, low resolution, blurry, simplistic, abstract, deformed, ugly",
96
- },
97
- {
98
- "name": "Style Zero",
99
- "prompt": "{prompt}",
100
- "negative_prompt": "",
101
- },
102
- ]
103
-
104
- styles = {k["name"]: (k["prompt"], k["negative_prompt"]) for k in style_list}
105
-
106
- DEFAULT_STYLE_NAME = "3840 x 2160"
107
- STYLE_NAMES = list(styles.keys())
108
-
109
- def apply_style(style_name: str, positive: str, negative: str = "") -> Tuple[str, str]:
110
- p, n = styles.get(style_name, styles[DEFAULT_STYLE_NAME]) # Use .get for safety
111
-
112
- if not negative:
113
- negative = ""
114
- return p.replace("{prompt}", positive), n + " " + negative # Add space for clarity
115
-
116
- @spaces.GPU(duration=180, enable_queue=True)
117
- def generate(
118
- prompt: str,
119
- negative_prompt: str = "",
120
- use_negative_prompt: bool = False,
121
- seed: int = 0,
122
- width: int = 1024,
123
- height: int = 1024,
124
- guidance_scale: float = 3,
125
- randomize_seed: bool = False,
126
- style_name: str = DEFAULT_STYLE_NAME,
127
- lora_model: str = "Realism (face/character)👦🏻",
128
- progress=gr.Progress(track_tqdm=True),
129
- ):
130
- if pipe is None:
131
- raise gr.Error("Pipeline not initialized. Check if CUDA is available and drivers are installed.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- seed = int(randomize_seed_fn(seed, randomize_seed))
134
-
135
- # Apply style first
136
- positive_prompt, base_negative_prompt = apply_style(style_name, prompt, negative_prompt if use_negative_prompt else "")
137
-
138
- # If user explicitly provided a negative prompt and wants to use it, append it
139
- # (apply_style already incorporates the style's negative prompt)
140
- # This logic might need adjustment depending on desired behavior: replace or append?
141
- # Current: Style neg prompt + user neg prompt
142
- effective_negative_prompt = base_negative_prompt
143
- if use_negative_prompt and negative_prompt:
144
- # Check if the negative prompt from apply_style is already there to avoid duplication
145
- if not negative_prompt in effective_negative_prompt:
146
- effective_negative_prompt = (effective_negative_prompt + " " + negative_prompt).strip()
147
-
148
-
149
- # Ensure LoRA selection is valid
150
- if lora_model not in LORA_OPTIONS:
151
- print(f"Warning: Invalid LoRA selection '{lora_model}'. Using default or first available.")
152
- # Fallback logic could be added here, e.g., use the first key
153
- lora_model = next(iter(LORA_OPTIONS)) # Get the first key as a fallback
154
-
155
- model_name, weight_name, adapter_name = LORA_OPTIONS[lora_model]
156
-
157
- try:
158
- print(f"Setting adapter: {adapter_name}")
159
- pipe.set_adapters(adapter_name)
160
- # Optional: Add LoRA scale if needed, often done via cross_attention_kwargs
161
- # Example: cross_attention_kwargs={"scale": lora_scale}
162
- # Note: RealVisXL Lightning might not need explicit scale adjustments like older models.
163
- # Using 0.65 as hardcoded before. Keeping it.
164
- lora_scale = 0.65
165
-
166
- print(f"Generating with prompt: '{positive_prompt}'")
167
- print(f"Negative prompt: '{effective_negative_prompt}'")
168
- print(f"Seed: {seed}, W: {width}, H: {height}, Scale: {guidance_scale}, Steps: 20")
169
-
170
- images = pipe(
171
- prompt=positive_prompt,
172
- negative_prompt=effective_negative_prompt,
173
- width=width,
174
- height=height,
175
- guidance_scale=guidance_scale,
176
- num_inference_steps=20, # Lightning models use fewer steps
177
- num_images_per_prompt=1,
178
- generator=torch.Generator("cuda").manual_seed(seed), # Ensure reproducibility
179
- cross_attention_kwargs={"scale": lora_scale}, # Apply LoRA scale if needed
180
- output_type="pil",
181
- ).images
182
-
183
- image_paths = [save_image(img) for img in images]
184
- print(f"Generated {len(image_paths)} image(s).")
185
- return image_paths, seed
186
-
187
- except Exception as e:
188
- print(f"Error during generation: {e}")
189
- # Raise a Gradio error to display it in the UI
190
- import traceback
191
- traceback.print_exc()
192
- raise gr.Error(f"Generation failed: {e}")
193
-
194
-
195
- examples = [
196
- ["Realism: Man in the style of dark beige and brown, uhd image, youthful protagonists, nonrepresentational"],
197
- ["Pixar: A young man with light brown wavy hair and light brown eyes sitting in an armchair and looking directly at the camera, pixar style, disney pixar, office background, ultra detailed, 1 man"],
198
- ["Hoodie: Front view, capture a urban style, Superman Hoodie, technical materials, fabric small point label on text Blue theory, the design is minimal, with a raised collar, fabric is a Light yellow, low angle to capture the Hoodies form and detailing, f/5.6 to focus on the hoodies craftsmanship, solid grey background, studio light setting, with batman logo in the chest region of the t-shirt"],
199
- ]
200
-
201
- css = '''
202
- .gradio-container{max-width: 680px !important; margin: auto;}
203
- h1{text-align:center}
204
- #gallery { min-height: 400px; }
205
- footer { display: none !important; visibility: hidden !important; }
206
- '''
207
-
208
- def load_predefined_images():
209
- predefined_images = []
210
- asset_dir = "assets"
211
- if os.path.exists(asset_dir):
212
- valid_extensions = {".png", ".jpg", ".jpeg", ".webp"}
213
- try:
214
- for i in range(1, 10): # Try loading 1.png to 9.png
215
- for ext in valid_extensions:
216
- img_path = os.path.join(asset_dir, f"{i}{ext}")
217
- if os.path.exists(img_path):
218
- predefined_images.append(img_path)
219
- break # Found image for this number, move to next
220
- except Exception as e:
221
- print(f"Error loading predefined images: {e}")
222
- if not predefined_images:
223
- print("No predefined images found in assets folder (e.g., assets/1.png, assets/2.jpg).")
224
- return predefined_images
225
-
226
-
227
- # --- Gradio UI Definition ---
228
- with gr.Blocks(css=css, theme="Yntec/HaleyCH_Theme_craiyon_alt") as demo:
229
- gr.HTML(title)
230
-
231
- # Define the output gallery component first
232
- result_gallery = gr.Gallery(
233
- label="Generated Images",
234
- show_label=False,
235
- elem_id="gallery", # For CSS styling
236
- columns=1, # Adjust as needed
237
- height="auto"
238
  )
239
- # Define the output seed component
240
- output_seed = gr.State(value=0) # Use gr.State for non-displayed outputs or values needing persistence
241
-
242
- with gr.Row():
243
- prompt = gr.Textbox(
244
- label="Prompt",
245
- show_label=False,
246
- max_lines=2,
247
- placeholder="Enter your prompt here...",
248
- container=False,
249
- scale=7 # Give more space to prompt
250
- )
251
- run_button = gr.Button("Generate", scale=1, variant="primary")
252
-
253
- with gr.Row():
254
- model_choice = gr.Dropdown(
255
- label="LoRA Selection",
256
- choices=list(LORA_OPTIONS.keys()),
257
- value="Realism (face/character)👦🏻", # Default selection
258
- scale=3
259
- )
260
- style_selection = gr.Radio(
261
- show_label=False, # Label provided by Row context or Accordion
262
- container=True,
263
- interactive=True,
264
- choices=STYLE_NAMES,
265
- value=DEFAULT_STYLE_NAME,
266
- label="Quality Style",
267
- scale=2
268
- )
269
-
270
-
271
- with gr.Accordion("Advanced options", open=False):
272
- with gr.Row():
273
- use_negative_prompt = gr.Checkbox(label="Use Negative Prompt", value=True, scale=1)
274
- randomize_seed = gr.Checkbox(label="Randomize Seed", value=True, scale=1)
275
- seed = gr.Slider(
276
- label="Seed",
277
- minimum=0,
278
- maximum=MAX_SEED,
279
- step=1,
280
- value=0, # Initial value
281
- visible=True, # Controlled by randomize_seed logic later if needed
282
- scale=3
283
- )
284
-
285
-
286
- negative_prompt = gr.Textbox(
287
- label="Negative Prompt",
288
- lines=2,
289
- max_lines=4,
290
- value="(deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, (mutated hands and fingers:1.4), disconnected limbs, mutation, mutated, ugly, disgusting, blurry, amputation",
291
- placeholder="Enter things to avoid...",
292
- visible=True, # Controlled by use_negative_prompt checkbox
293
- )
294
 
295
- with gr.Row():
296
- width = gr.Slider(
297
- label="Width",
298
- minimum=512,
299
- maximum=1536, # Adjusted max for typical SDXL usage
300
- step=64, # Step by 64 for common resolutions
301
- value=1024,
302
- )
303
- height = gr.Slider(
304
- label="Height",
305
- minimum=512,
306
- maximum=1536, # Adjusted max
307
- step=64, # Step by 64
308
- value=1024,
309
- )
310
- guidance_scale = gr.Slider(
311
- label="Guidance Scale (CFG)",
312
- minimum=1.0, # Usually start CFG from 1
313
- maximum=10.0, # Lightning models often use low CFG
314
- step=0.1,
315
- value=3.0,
316
- )
317
-
318
- # --- Event Listeners ---
319
-
320
- # Toggle negative prompt visibility
321
- use_negative_prompt.change(
322
- fn=lambda x: gr.update(visible=x),
323
- inputs=use_negative_prompt,
324
- outputs=negative_prompt,
325
- api_name=False,
326
  )
327
 
328
- # Toggle seed slider visibility based on randomize checkbox
329
- # def toggle_seed_visibility(randomize):
330
- # return gr.update(interactive=not randomize)
331
- # randomize_seed.change(
332
- # fn=toggle_seed_visibility,
333
- # inputs=randomize_seed,
334
- # outputs=seed,
335
- # api_name=False
336
- # )
337
-
338
- # --- Image Generation Trigger ---
339
- inputs = [
340
- prompt,
341
- negative_prompt,
342
- use_negative_prompt,
343
- seed,
344
- width,
345
- height,
346
- guidance_scale,
347
- randomize_seed,
348
- style_selection,
349
- model_choice,
350
- ]
351
- # Define outputs using the created components
352
- outputs = [
353
- result_gallery, # The gallery to display images
354
- output_seed # The state to hold the used seed
355
- ]
356
-
357
- # Connect the generate function to the button click and prompt submit
358
- gr.on(
359
- triggers=[run_button.click, prompt.submit],
360
- fn=generate,
361
- inputs=inputs,
362
- outputs=outputs,
363
- api_name="run" # Keep API name if needed
364
  )
365
 
366
- # Update the seed slider display when a new seed is generated and returned via output_seed
367
- output_seed.change(fn=lambda x: x, inputs=output_seed, outputs=seed, api_name=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
- # --- Examples ---
371
- gr.Examples(
372
- examples=examples,
373
- inputs=[prompt], # Only prompt needed for examples
374
- outputs=[result_gallery, output_seed], # Update example outputs as well
375
- fn=generate, # Function to run when example is clicked
376
- cache_examples=os.getenv("CACHE_EXAMPLES", "False").lower() == "true" # Cache examples in Spaces
377
  )
378
 
379
- # --- Predefined Image Gallery (Static) ---
380
- with gr.Column(): # Use column for better layout control if needed
381
- gr.Markdown("### Example Gallery (Predefined)")
382
- try:
383
- predefined_gallery_images = load_predefined_images()
384
- if predefined_gallery_images:
385
- predefined_gallery = gr.Gallery(
386
- label="Predefined Images",
387
- value=predefined_gallery_images,
388
- columns=3,
389
- show_label=False
390
- )
391
- else:
392
- gr.Markdown("_(No predefined images found in 'assets' folder)_")
393
- except Exception as e:
394
- gr.Markdown(f"_Error loading predefined gallery: {e}_")
395
-
396
-
397
- # --- Launch the App ---
398
- if __name__ == "__main__":
399
- demo.queue(max_size=20).launch(ssr_mode=True, debug=True) # Add debug=True for more detailed logs
 
 
 
 
 
1
  import gradio as gr
2
+ import torch
3
+ from diffusers import FluxFillPipeline
4
+ from diffusers.utils import load_image
5
+ from PIL import Image, ImageDraw
6
  import numpy as np
 
7
  import spaces
8
+ from huggingface_hub import hf_hub_download
9
+
10
+ pipe = FluxFillPipeline.from_pretrained(
11
+ "black-forest-labs/FLUX.1-Fill-dev",
12
+ torch_dtype=torch.bfloat16
13
+ ).to("cuda")
14
+
15
+ def can_expand(source_width, source_height, target_width, target_height, alignment):
16
+ if alignment in ("Left", "Right") and source_width >= target_width:
17
+ return False
18
+ if alignment in ("Top", "Bottom") and source_height >= target_height:
19
+ return False
20
+ return True
21
+
22
+ def prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
23
+ target_size = (width, height)
24
+
25
+ scale_factor = min(target_size[0] / image.width, target_size[1] / image.height)
26
+ new_width = int(image.width * scale_factor)
27
+ new_height = int(image.height * scale_factor)
28
+
29
+ source = image.resize((new_width, new_height), Image.LANCZOS)
30
+
31
+ if resize_option == "Full":
32
+ resize_percentage = 100
33
+ elif resize_option == "75%":
34
+ resize_percentage = 75
35
+ elif resize_option == "50%":
36
+ resize_percentage = 50
37
+ elif resize_option == "33%":
38
+ resize_percentage = 33
39
+ elif resize_option == "25%":
40
+ resize_percentage = 25
41
+ else: # Custom
42
+ resize_percentage = custom_resize_percentage
43
+
44
+ # Calculate new dimensions based on percentage
45
+ resize_factor = resize_percentage / 100
46
+ new_width = int(source.width * resize_factor)
47
+ new_height = int(source.height * resize_factor)
48
+
49
+ # Ensure minimum size of 64 pixels
50
+ new_width = max(new_width, 64)
51
+ new_height = max(new_height, 64)
52
+
53
+ # Resize the image
54
+ source = source.resize((new_width, new_height), Image.LANCZOS)
55
+
56
+ # Calculate the overlap in pixels based on the percentage
57
+ overlap_x = int(new_width * (overlap_percentage / 100))
58
+ overlap_y = int(new_height * (overlap_percentage / 100))
59
+
60
+ # Ensure minimum overlap of 1 pixel
61
+ overlap_x = max(overlap_x, 1)
62
+ overlap_y = max(overlap_y, 1)
63
+
64
+ # Calculate margins based on alignment
65
+ if alignment == "Middle":
66
+ margin_x = (target_size[0] - new_width) // 2
67
+ margin_y = (target_size[1] - new_height) // 2
68
+ elif alignment == "Left":
69
+ margin_x = 0
70
+ margin_y = (target_size[1] - new_height) // 2
71
+ elif alignment == "Right":
72
+ margin_x = target_size[0] - new_width
73
+ margin_y = (target_size[1] - new_height) // 2
74
+ elif alignment == "Top":
75
+ margin_x = (target_size[0] - new_width) // 2
76
+ margin_y = 0
77
+ elif alignment == "Bottom":
78
+ margin_x = (target_size[0] - new_width) // 2
79
+ margin_y = target_size[1] - new_height
80
+
81
+ # Adjust margins to eliminate gaps
82
+ margin_x = max(0, min(margin_x, target_size[0] - new_width))
83
+ margin_y = max(0, min(margin_y, target_size[1] - new_height))
84
+
85
+ # Create a new background image and paste the resized source image
86
+ background = Image.new('RGB', target_size, (255, 255, 255))
87
+ background.paste(source, (margin_x, margin_y))
88
+
89
+ # Create the mask
90
+ mask = Image.new('L', target_size, 255)
91
+ mask_draw = ImageDraw.Draw(mask)
92
+
93
+ # Calculate overlap areas
94
+ white_gaps_patch = 2
95
+
96
+ left_overlap = margin_x + overlap_x if overlap_left else margin_x + white_gaps_patch
97
+ right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width - white_gaps_patch
98
+ top_overlap = margin_y + overlap_y if overlap_top else margin_y + white_gaps_patch
99
+ bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height - white_gaps_patch
100
+
101
+ if alignment == "Left":
102
+ left_overlap = margin_x + overlap_x if overlap_left else margin_x
103
+ elif alignment == "Right":
104
+ right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width
105
+ elif alignment == "Top":
106
+ top_overlap = margin_y + overlap_y if overlap_top else margin_y
107
+ elif alignment == "Bottom":
108
+ bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height
109
+
110
+ # Draw the mask
111
+ mask_draw.rectangle([
112
+ (left_overlap, top_overlap),
113
+ (right_overlap, bottom_overlap)
114
+ ], fill=0)
115
+
116
+ return background, mask
117
+
118
+ @spaces.GPU
119
+ def inpaint(image, width, height, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom, progress=gr.Progress(track_tqdm=True)):
120
+
121
+ 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)
122
+
123
+ if not can_expand(background.width, background.height, width, height, alignment):
124
+ alignment = "Middle"
125
+
126
+ cnet_image = background.copy()
127
+ cnet_image.paste(0, (0, 0), mask)
128
+
129
+ final_prompt = prompt_input
130
+
131
+ #generator = torch.Generator(device="cuda").manual_seed(42)
132
+
133
+ result = pipe(
134
+ prompt=final_prompt,
135
+ height=height,
136
+ width=width,
137
+ image=cnet_image,
138
+ mask_image=mask,
139
+ num_inference_steps=num_inference_steps,
140
+ guidance_scale=30,
141
+ ).images[0]
142
+
143
+ result = result.convert("RGBA")
144
+ cnet_image.paste(result, (0, 0), mask)
145
+
146
+ return cnet_image, background
147
+
148
+ def preview_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
149
+ 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)
150
+
151
+ preview = background.copy().convert('RGBA')
152
+ red_overlay = Image.new('RGBA', background.size, (255, 0, 0, 64))
153
+ red_mask = Image.new('RGBA', background.size, (0, 0, 0, 0))
154
+ red_mask.paste(red_overlay, (0, 0), mask)
155
+ preview = Image.alpha_composite(preview, red_mask)
156
+
157
+ return preview
158
+
159
+ def clear_result():
160
+ return gr.update(value=None)
161
+
162
+ def preload_presets(target_ratio, ui_width, ui_height):
163
+ if target_ratio == "9:16":
164
+ return 720, 1280, gr.update()
165
+ elif target_ratio == "16:9":
166
+ return 1280, 720, gr.update()
167
+ elif target_ratio == "1:1":
168
+ return 1024, 1024, gr.update()
169
+ elif target_ratio == "Custom":
170
+ return ui_width, ui_height, gr.update(open=True)
171
+
172
+ def select_the_right_preset(user_width, user_height):
173
+ if user_width == 720 and user_height == 1280:
174
+ return "9:16"
175
+ elif user_width == 1280 and user_height == 720:
176
+ return "16:9"
177
+ elif user_width == 1024 and user_height == 1024:
178
+ return "1:1"
179
  else:
180
+ return "Custom"
181
+
182
+ def toggle_custom_resize_slider(resize_option):
183
+ return gr.update(visible=(resize_option == "Custom"))
184
+
185
+ def update_history(new_image, history):
186
+ if history is None:
187
+ history = []
188
+ history.insert(0, new_image)
189
+ return history
190
+
191
+ css = """
192
+ .gradio-container {
193
+ max-width: 1250px !important;
194
+ }
195
+ """
196
+
197
+ title = """<h1 align="center">Flux Outpaint Dev 🤩</h1>"""
198
+
199
+ with gr.Blocks(css=css) as demo:
200
+ with gr.Column():
201
+ gr.HTML(title)
202
+
203
+ with gr.Row():
204
+ with gr.Column():
205
+ input_image = gr.Image(
206
+ type="pil",
207
+ label="Input Image"
208
+ )
209
+
210
+ with gr.Row():
211
+ with gr.Column(scale=2):
212
+ prompt_input = gr.Textbox(label="Prompt (Optional)")
213
+ with gr.Column(scale=1):
214
+ run_button = gr.Button("Generate")
215
+
216
+ with gr.Row():
217
+ target_ratio = gr.Radio(
218
+ label="Image Ratio",
219
+ choices=["9:16", "16:9", "1:1", "Custom"],
220
+ value="9:16",
221
+ scale=3
222
+ )
223
+ alignment_dropdown = gr.Dropdown(
224
+ choices=["Middle", "Left", "Right", "Top", "Bottom"],
225
+ value="Middle",
226
+ label="Alignment",
227
+ )
228
+ resize_option = gr.Radio(
229
+ label="Resize input image",
230
+ choices=["Full", "75%", "50%", "33%", "25%", "Custom"],
231
+ value="75%"
232
+ )
233
+ custom_resize_percentage = gr.Slider(
234
+ label="Custom resize (%)",
235
+ minimum=1,
236
+ maximum=100,
237
+ step=1,
238
+ value=50,
239
+ visible=False
240
+ )
241
+ with gr.Accordion(label="Advanced settings", open=False) as settings_panel:
242
+ with gr.Column():
243
+ with gr.Row():
244
+ width_slider = gr.Slider(
245
+ label="Target Width",
246
+ minimum=720,
247
+ maximum=1536,
248
+ step=8,
249
+ value=720,
250
+ )
251
+ height_slider = gr.Slider(
252
+ label="Target Height",
253
+ minimum=720,
254
+ maximum=1536,
255
+ step=8,
256
+ value=1280,
257
+ )
258
+
259
+ num_inference_steps = gr.Slider(label="Steps", minimum=2, maximum=50, step=1, value=28)
260
+ with gr.Group():
261
+ overlap_percentage = gr.Slider(
262
+ label="Mask overlap (%)",
263
+ minimum=1,
264
+ maximum=50,
265
+ value=10,
266
+ step=1
267
+ )
268
+ with gr.Row():
269
+ overlap_top = gr.Checkbox(label="Overlap Top", value=True)
270
+ overlap_right = gr.Checkbox(label="Overlap Right", value=True)
271
+ with gr.Row():
272
+ overlap_left = gr.Checkbox(label="Overlap Left", value=True)
273
+ overlap_bottom = gr.Checkbox(label="Overlap Bottom", value=True)
274
+
275
+ with gr.Column():
276
+ preview_button = gr.Button("Preview alignment and mask")
277
+
278
+ with gr.Column():
279
+ result = gr.Image(
280
+ interactive=False,
281
+ label="Generated Image",
282
+ )
283
+ use_as_input_button = gr.Button("Use as Input Image", visible=False)
284
+ with gr.Accordion("History and Mask", open=False):
285
+ history_gallery = gr.Gallery(label="History", columns=6, object_fit="contain", interactive=False)
286
+ preview_image = gr.Image(label="Mask preview")
287
+
288
+ def use_output_as_input(output_image):
289
+ return output_image
290
+
291
+ use_as_input_button.click(
292
+ fn=use_output_as_input,
293
+ inputs=[result],
294
+ outputs=[input_image]
295
+ )
296
 
297
+ target_ratio.change(
298
+ fn=preload_presets,
299
+ inputs=[target_ratio, width_slider, height_slider],
300
+ outputs=[width_slider, height_slider, settings_panel],
301
+ queue=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
+ width_slider.change(
305
+ fn=select_the_right_preset,
306
+ inputs=[width_slider, height_slider],
307
+ outputs=[target_ratio],
308
+ queue=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  )
310
 
311
+ height_slider.change(
312
+ fn=select_the_right_preset,
313
+ inputs=[width_slider, height_slider],
314
+ outputs=[target_ratio],
315
+ queue=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  )
317
 
318
+ resize_option.change(
319
+ fn=toggle_custom_resize_slider,
320
+ inputs=[resize_option],
321
+ outputs=[custom_resize_percentage],
322
+ queue=False
323
+ )
324
+
325
+ run_button.click(
326
+ fn=clear_result,
327
+ inputs=None,
328
+ outputs=result,
329
+ ).then(
330
+ fn=inpaint,
331
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps,
332
+ resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
333
+ overlap_left, overlap_right, overlap_top, overlap_bottom],
334
+ outputs=[result, preview_image],
335
+ ).then(
336
+ fn=lambda x, history: update_history(x, history),
337
+ inputs=[result, history_gallery],
338
+ outputs=history_gallery,
339
+ ).then(
340
+ fn=lambda: gr.update(visible=True),
341
+ inputs=None,
342
+ outputs=use_as_input_button,
343
+ )
344
 
345
+ prompt_input.submit(
346
+ fn=clear_result,
347
+ inputs=None,
348
+ outputs=result,
349
+ ).then(
350
+ fn=inpaint,
351
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, num_inference_steps, resize_option, custom_resize_percentage, prompt_input, alignment_dropdown,
352
+ overlap_left, overlap_right, overlap_top, overlap_bottom],
353
+ outputs=[result, preview_image],
354
+ ).then(
355
+ fn=lambda x, history: update_history(x, history),
356
+ inputs=[result, history_gallery],
357
+ outputs=history_gallery,
358
+ ).then(
359
+ fn=lambda: gr.update(visible=True),
360
+ inputs=None,
361
+ outputs=use_as_input_button,
362
+ )
363
 
364
+ preview_button.click(
365
+ fn=preview_image_and_mask,
366
+ inputs=[input_image, width_slider, height_slider, overlap_percentage, resize_option, custom_resize_percentage, alignment_dropdown,
367
+ overlap_left, overlap_right, overlap_top, overlap_bottom],
368
+ outputs=preview_image,
369
+ queue=False
 
370
  )
371
 
372
+ demo.queue(max_size=12).launch(share=False)