aiqtech commited on
Commit
3733e2d
·
verified ·
1 Parent(s): bbefa55

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1395
app.py DELETED
@@ -1,1395 +0,0 @@
1
- import tempfile
2
- import time
3
- from collections.abc import Sequence
4
- from typing import Any, cast
5
- import os
6
- from huggingface_hub import login, hf_hub_download
7
-
8
- import gradio as gr
9
- import numpy as np
10
- import pillow_heif
11
- import spaces
12
- import torch
13
- from gradio_image_annotation import image_annotator
14
- from gradio_imageslider import ImageSlider
15
- from PIL import Image
16
- from pymatting.foreground.estimate_foreground_ml import estimate_foreground_ml
17
- from refiners.fluxion.utils import no_grad
18
- from refiners.solutions import BoxSegmenter
19
- from transformers import GroundingDinoForObjectDetection, GroundingDinoProcessor
20
- from diffusers import FluxPipeline
21
- from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM
22
- import gc
23
-
24
- from PIL import Image, ImageDraw, ImageFont
25
- from PIL import Image
26
- from gradio_client import Client, handle_file
27
- import uuid
28
-
29
- import gradio as gr
30
- import spaces
31
- import torch
32
- from diffusers import AutoencoderKL, TCDScheduler
33
- from diffusers.models.model_loading_utils import load_state_dict
34
- from gradio_imageslider import ImageSlider
35
- from huggingface_hub import hf_hub_download
36
- from transformers import pipeline
37
-
38
- from controlnet_union import ControlNetModel_Union
39
- from pipeline_fill_sd_xl import StableDiffusionXLFillPipeline
40
-
41
- def initialize_ui():
42
- """UI 초기화 함수"""
43
- return {
44
- "position": "bottom-center",
45
- "processing_status": "idle",
46
- "status_message": gr.update(visible=False)
47
- }
48
-
49
- def debug_event(event_name, *args):
50
- """이벤트 디버깅 유틸리티"""
51
- print(f"Event '{event_name}' triggered at {time.strftime('%H:%M:%S')}")
52
- print(f"Arguments: {args}")
53
- return args
54
-
55
- def clear_memory():
56
- """메모리 정리 함수"""
57
- gc.collect()
58
- try:
59
- if torch.cuda.is_available():
60
- with torch.cuda.device(0): # 명시적으로 device 0 사용
61
- torch.cuda.empty_cache()
62
- except:
63
- pass
64
-
65
- # GPU 설정
66
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 명시적으로 cuda:0 지정
67
-
68
- # GPU 설정을 try-except로 감싸기
69
- if torch.cuda.is_available():
70
- try:
71
- with torch.cuda.device(0):
72
- torch.cuda.empty_cache()
73
- torch.backends.cudnn.benchmark = True
74
- torch.backends.cuda.matmul.allow_tf32 = True
75
- except:
76
- print("Warning: Could not configure CUDA settings")
77
-
78
- # 번역 모델 초기화
79
- model_name = "Helsinki-NLP/opus-mt-ko-en"
80
- tokenizer = AutoTokenizer.from_pretrained(model_name)
81
- model = AutoModelForSeq2SeqLM.from_pretrained(model_name).to('cpu')
82
- translator = pipeline("translation", model=model, tokenizer=tokenizer, device=-1)
83
-
84
- def translate_to_english(text: str) -> str:
85
- """한글 텍스트를 영어로 번역"""
86
- try:
87
- if any(ord('가') <= ord(char) <= ord('힣') for char in text):
88
- translated = translator(text, max_length=128)[0]['translation_text']
89
- print(f"Translated '{text}' to '{translated}'")
90
- return translated
91
- return text
92
- except Exception as e:
93
- print(f"Translation error: {str(e)}")
94
- return text
95
-
96
- BoundingBox = tuple[int, int, int, int]
97
-
98
- pillow_heif.register_heif_opener()
99
- pillow_heif.register_avif_opener()
100
-
101
- # HF 토큰 설정
102
- HF_TOKEN = os.getenv("HF_TOKEN")
103
- if HF_TOKEN is None:
104
- raise ValueError("Please set the HF_TOKEN environment variable")
105
-
106
- try:
107
- login(token=HF_TOKEN)
108
- except Exception as e:
109
- raise ValueError(f"Failed to login to Hugging Face: {str(e)}")
110
-
111
- # 모델 초기화
112
- segmenter = BoxSegmenter(device="cpu")
113
- segmenter.device = device
114
- segmenter.model = segmenter.model.to(device=segmenter.device)
115
-
116
- gd_model_path = "IDEA-Research/grounding-dino-base"
117
- gd_processor = GroundingDinoProcessor.from_pretrained(gd_model_path)
118
- gd_model = GroundingDinoForObjectDetection.from_pretrained(gd_model_path, torch_dtype=torch.float32)
119
- gd_model = gd_model.to(device=device)
120
- assert isinstance(gd_model, GroundingDinoForObjectDetection)
121
-
122
- # FLUX 파이프라인 초기화
123
- pipe = FluxPipeline.from_pretrained(
124
- "black-forest-labs/FLUX.1-dev",
125
- torch_dtype=torch.float16,
126
- use_auth_token=HF_TOKEN
127
- )
128
- pipe.enable_attention_slicing(slice_size="auto")
129
-
130
- # LoRA 가중치 로드
131
- pipe.load_lora_weights(
132
- hf_hub_download(
133
- "ByteDance/Hyper-SD",
134
- "Hyper-FLUX.1-dev-8steps-lora.safetensors",
135
- use_auth_token=HF_TOKEN
136
- )
137
- )
138
- pipe.fuse_lora(lora_scale=0.125)
139
-
140
- # GPU 설정을 try-except로 감싸기
141
- try:
142
- if torch.cuda.is_available():
143
- pipe = pipe.to("cuda:0") # 명시적으로 cuda:0 지정
144
- except Exception as e:
145
- print(f"Warning: Could not move pipeline to CUDA: {str(e)}")
146
-
147
-
148
- #------------------------------- 이미지 인페인팅 ----------------------
149
-
150
- client = Client("NabeelShar/BiRefNet_for_text_writing")
151
-
152
- MODELS = {
153
- "RealVisXL V5.0 Lightning": "SG161222/RealVisXL_V5.0_Lightning",
154
- }
155
-
156
- config_file = hf_hub_download(
157
- "xinsir/controlnet-union-sdxl-1.0",
158
- filename="config_promax.json",
159
- )
160
-
161
- config = ControlNetModel_Union.load_config(config_file)
162
- controlnet_model = ControlNetModel_Union.from_config(config)
163
- model_file = hf_hub_download(
164
- "xinsir/controlnet-union-sdxl-1.0",
165
- filename="diffusion_pytorch_model_promax.safetensors",
166
- )
167
- state_dict = load_state_dict(model_file)
168
- model, _, _, _, _ = ControlNetModel_Union._load_pretrained_model(
169
- controlnet_model, state_dict, model_file, "xinsir/controlnet-union-sdxl-1.0"
170
- )
171
- model.to(device="cuda", dtype=torch.float16)
172
-
173
- vae = AutoencoderKL.from_pretrained(
174
- "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
175
- ).to("cuda")
176
-
177
- pipe = StableDiffusionXLFillPipeline.from_pretrained(
178
- "SG161222/RealVisXL_V5.0_Lightning",
179
- torch_dtype=torch.float16,
180
- vae=vae,
181
- controlnet=model,
182
- variant="fp16",
183
- ).to("cuda")
184
-
185
- pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
186
-
187
- def translate_if_korean(text):
188
- # 입력된 텍스트가 한글을 포함하고 있는지 확인
189
- if any('\u3131' <= char <= '\u318E' or '\uAC00' <= char <= '\uD7A3' for char in text):
190
- # 한글이 포함되어 있다면 번역
191
- translated = translator(text)[0]['translation_text']
192
- print(f"Translated prompt: {translated}") # 디버깅을 위한 출력
193
- return translated
194
- return text
195
-
196
- @spaces.GPU
197
- def fill_image(prompt, image, model_selection):
198
- # 프롬프트 번역
199
- translated_prompt = translate_if_korean(prompt)
200
-
201
- (
202
- prompt_embeds,
203
- negative_prompt_embeds,
204
- pooled_prompt_embeds,
205
- negative_pooled_prompt_embeds,
206
- ) = pipe.encode_prompt(translated_prompt, "cuda", True)
207
-
208
- source = image["background"]
209
- mask = image["layers"][0]
210
-
211
- alpha_channel = mask.split()[3]
212
- binary_mask = alpha_channel.point(lambda p: p > 0 and 255)
213
- cnet_image = source.copy()
214
- cnet_image.paste(0, (0, 0), binary_mask)
215
-
216
- for image in pipe(
217
- prompt_embeds=prompt_embeds,
218
- negative_prompt_embeds=negative_prompt_embeds,
219
- pooled_prompt_embeds=pooled_prompt_embeds,
220
- negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
221
- image=cnet_image,
222
- ):
223
- yield image, cnet_image
224
-
225
- image = image.convert("RGBA")
226
- cnet_image.paste(image, (0, 0), binary_mask)
227
-
228
- yield source, cnet_image
229
-
230
- def clear_result():
231
- return gr.update(value=None)
232
-
233
- def process_inpainting(image, mask_input, prompt):
234
- """이미지 인페인팅 처리 함수"""
235
- try:
236
- if image is None or mask_input is None or not prompt:
237
- raise gr.Error("Please provide image, mask, and prompt")
238
-
239
- # 프롬프트 번역 (한글인 경우)
240
- translated_prompt = translate_if_korean(prompt)
241
-
242
- # 마스크 처리
243
- source = image
244
- if isinstance(mask_input, dict):
245
- mask = mask_input["layers"][0]
246
- alpha_channel = mask.split()[3]
247
- binary_mask = alpha_channel.point(lambda p: p > 0 and 255)
248
- else:
249
- raise gr.Error("Invalid mask input")
250
-
251
- # 인페인팅을 위한 이미지 준비
252
- cnet_image = source.copy()
253
- cnet_image.paste(0, (0, 0), binary_mask)
254
-
255
- # 프롬프트 임베딩
256
- (
257
- prompt_embeds,
258
- negative_prompt_embeds,
259
- pooled_prompt_embeds,
260
- negative_pooled_prompt_embeds,
261
- ) = pipe.encode_prompt(translated_prompt, "cuda", True)
262
-
263
- # 인페인팅 실행
264
- result = None
265
- for image in pipe(
266
- prompt_embeds=prompt_embeds,
267
- negative_prompt_embeds=negative_prompt_embeds,
268
- pooled_prompt_embeds=pooled_prompt_embeds,
269
- negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
270
- image=cnet_image,
271
- ):
272
- result = image
273
-
274
- if result is None:
275
- raise gr.Error("Inpainting failed")
276
-
277
- # 결과 이미지 처리
278
- result = result.convert("RGBA")
279
- cnet_image.paste(result, (0, 0), binary_mask)
280
-
281
- return cnet_image
282
-
283
- except Exception as e:
284
- print(f"Inpainting error: {str(e)}")
285
- raise gr.Error(f"Inpainting failed: {str(e)}")
286
- finally:
287
- clear_memory()
288
-
289
- #--------------- 이미지 인페인팅 끝 ----------------
290
-
291
- class timer:
292
- def __init__(self, method_name="timed process"):
293
- self.method = method_name
294
- def __enter__(self):
295
- self.start = time.time()
296
- print(f"{self.method} starts")
297
- def __exit__(self, exc_type, exc_val, exc_tb):
298
- end = time.time()
299
- print(f"{self.method} took {str(round(end - self.start, 2))}s")
300
-
301
- def bbox_union(bboxes: Sequence[list[int]]) -> BoundingBox | None:
302
- if not bboxes:
303
- return None
304
- for bbox in bboxes:
305
- assert len(bbox) == 4
306
- assert all(isinstance(x, int) for x in bbox)
307
- return (
308
- min(bbox[0] for bbox in bboxes),
309
- min(bbox[1] for bbox in bboxes),
310
- max(bbox[2] for bbox in bboxes),
311
- max(bbox[3] for bbox in bboxes),
312
- )
313
-
314
- def corners_to_pixels_format(bboxes: torch.Tensor, width: int, height: int) -> torch.Tensor:
315
- x1, y1, x2, y2 = bboxes.round().to(torch.int32).unbind(-1)
316
- return torch.stack((x1.clamp_(0, width), y1.clamp_(0, height), x2.clamp_(0, width), y2.clamp_(0, height)), dim=-1)
317
-
318
- def gd_detect(img: Image.Image, prompt: str) -> BoundingBox | None:
319
- inputs = gd_processor(images=img, text=f"{prompt}.", return_tensors="pt").to(device=device)
320
- with no_grad():
321
- outputs = gd_model(**inputs)
322
- width, height = img.size
323
- results: dict[str, Any] = gd_processor.post_process_grounded_object_detection(
324
- outputs,
325
- inputs["input_ids"],
326
- target_sizes=[(height, width)],
327
- )[0]
328
- assert "boxes" in results and isinstance(results["boxes"], torch.Tensor)
329
- bboxes = corners_to_pixels_format(results["boxes"].cpu(), width, height)
330
- return bbox_union(bboxes.numpy().tolist())
331
-
332
- def apply_mask(img: Image.Image, mask_img: Image.Image, defringe: bool = True) -> Image.Image:
333
- assert img.size == mask_img.size
334
- img = img.convert("RGB")
335
- mask_img = mask_img.convert("L")
336
- if defringe:
337
- rgb, alpha = np.asarray(img) / 255.0, np.asarray(mask_img) / 255.0
338
- foreground = cast(np.ndarray[Any, np.dtype[np.uint8]], estimate_foreground_ml(rgb, alpha))
339
- img = Image.fromarray((foreground * 255).astype("uint8"))
340
- result = Image.new("RGBA", img.size)
341
- result.paste(img, (0, 0), mask_img)
342
- return result
343
-
344
-
345
- def adjust_size_to_multiple_of_8(width: int, height: int) -> tuple[int, int]:
346
- """이미지 크기를 8의 배수로 조정하는 함수"""
347
- new_width = ((width + 7) // 8) * 8
348
- new_height = ((height + 7) // 8) * 8
349
- return new_width, new_height
350
-
351
- def calculate_dimensions(aspect_ratio: str, base_size: int = 512) -> tuple[int, int]:
352
- """선택된 비율에 따라 이미지 크기 계산"""
353
- if aspect_ratio == "1:1":
354
- return base_size, base_size
355
- elif aspect_ratio == "16:9":
356
- return base_size * 16 // 9, base_size
357
- elif aspect_ratio == "9:16":
358
- return base_size, base_size * 16 // 9
359
- elif aspect_ratio == "4:3":
360
- return base_size * 4 // 3, base_size
361
- return base_size, base_size
362
-
363
- @spaces.GPU(duration=20) # 40초에서 20초로 감소
364
- def generate_background(prompt: str, aspect_ratio: str) -> Image.Image:
365
- try:
366
- width, height = calculate_dimensions(aspect_ratio)
367
- width, height = adjust_size_to_multiple_of_8(width, height)
368
-
369
- max_size = 768
370
- if width > max_size or height > max_size:
371
- ratio = max_size / max(width, height)
372
- width = int(width * ratio)
373
- height = int(height * ratio)
374
- width, height = adjust_size_to_multiple_of_8(width, height)
375
-
376
- with timer("Background generation"):
377
- try:
378
- with torch.inference_mode():
379
- image = pipe(
380
- prompt=prompt,
381
- width=width,
382
- height=height,
383
- num_inference_steps=8,
384
- guidance_scale=4.0
385
- ).images[0]
386
- except Exception as e:
387
- print(f"Pipeline error: {str(e)}")
388
- return Image.new('RGB', (width, height), 'white')
389
-
390
- return image
391
- except Exception as e:
392
- print(f"Background generation error: {str(e)}")
393
- return Image.new('RGB', (512, 512), 'white')
394
-
395
- def create_position_grid():
396
- return """
397
- <div class="position-grid" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; width: 150px; margin: auto;">
398
- <button class="position-btn" data-pos="top-left">↖</button>
399
- <button class="position-btn" data-pos="top-center">↑</button>
400
- <button class="position-btn" data-pos="top-right">↗</button>
401
- <button class="position-btn" data-pos="middle-left">←</button>
402
- <button class="position-btn" data-pos="middle-center">•</button>
403
- <button class="position-btn" data-pos="middle-right">→</button>
404
- <button class="position-btn" data-pos="bottom-left">↙</button>
405
- <button class="position-btn" data-pos="bottom-center" data-default="true">↓</button>
406
- <button class="position-btn" data-pos="bottom-right">↘</button>
407
- </div>
408
- """
409
-
410
- def calculate_object_position(position: str, bg_size: tuple[int, int], obj_size: tuple[int, int]) -> tuple[int, int]:
411
- """오브젝트의 위치 계산"""
412
- bg_width, bg_height = bg_size
413
- obj_width, obj_height = obj_size
414
-
415
- positions = {
416
- "top-left": (0, 0),
417
- "top-center": ((bg_width - obj_width) // 2, 0),
418
- "top-right": (bg_width - obj_width, 0),
419
- "middle-left": (0, (bg_height - obj_height) // 2),
420
- "middle-center": ((bg_width - obj_width) // 2, (bg_height - obj_height) // 2),
421
- "middle-right": (bg_width - obj_width, (bg_height - obj_height) // 2),
422
- "bottom-left": (0, bg_height - obj_height),
423
- "bottom-center": ((bg_width - obj_width) // 2, bg_height - obj_height),
424
- "bottom-right": (bg_width - obj_width, bg_height - obj_height)
425
- }
426
-
427
- return positions.get(position, positions["bottom-center"])
428
-
429
- def resize_object(image: Image.Image, scale_percent: float) -> Image.Image:
430
- """오브젝트 크기 조정"""
431
- width = int(image.width * scale_percent / 100)
432
- height = int(image.height * scale_percent / 100)
433
- return image.resize((width, height), Image.Resampling.LANCZOS)
434
-
435
- def combine_with_background(foreground: Image.Image, background: Image.Image,
436
- position: str = "bottom-center", scale_percent: float = 100) -> Image.Image:
437
- """전경과 배경 합성 함수"""
438
- print(f"Combining with position: {position}, scale: {scale_percent}")
439
-
440
- result = background.convert('RGBA')
441
- scaled_foreground = resize_object(foreground, scale_percent)
442
-
443
- x, y = calculate_object_position(position, result.size, scaled_foreground.size)
444
- print(f"Calculated position coordinates: ({x}, {y})")
445
-
446
- result.paste(scaled_foreground, (x, y), scaled_foreground)
447
- return result
448
-
449
- @spaces.GPU(duration=30) # 120초에서 30초로 감소
450
- def _gpu_process(img: Image.Image, prompt: str | BoundingBox | None) -> tuple[Image.Image, BoundingBox | None, list[str]]:
451
- time_log: list[str] = []
452
- try:
453
- if isinstance(prompt, str):
454
- t0 = time.time()
455
- bbox = gd_detect(img, prompt)
456
- time_log.append(f"detect: {time.time() - t0}")
457
- if not bbox:
458
- print(time_log[0])
459
- raise gr.Error("No object detected")
460
- else:
461
- bbox = prompt
462
- t0 = time.time()
463
- mask = segmenter(img, bbox)
464
- time_log.append(f"segment: {time.time() - t0}")
465
- return mask, bbox, time_log
466
- except Exception as e:
467
- print(f"GPU process error: {str(e)}")
468
- raise
469
-
470
- def _process(img: Image.Image, prompt: str | BoundingBox | None, bg_prompt: str | None = None, aspect_ratio: str = "1:1") -> tuple[tuple[Image.Image, Image.Image, Image.Image], gr.DownloadButton]:
471
- try:
472
- # 입력 이미지 크기 제한
473
- max_size = 1024
474
- if img.width > max_size or img.height > max_size:
475
- ratio = max_size / max(img.width, img.height)
476
- new_size = (int(img.width * ratio), int(img.height * ratio))
477
- img = img.resize(new_size, Image.LANCZOS)
478
-
479
- # CUDA 메모리 관리 수정
480
- try:
481
- if torch.cuda.is_available():
482
- current_device = torch.cuda.current_device()
483
- with torch.cuda.device(current_device):
484
- torch.cuda.empty_cache()
485
- except Exception as e:
486
- print(f"CUDA memory management failed: {e}")
487
-
488
- with torch.cuda.amp.autocast(enabled=torch.cuda.is_available()):
489
- mask, bbox, time_log = _gpu_process(img, prompt)
490
- masked_alpha = apply_mask(img, mask, defringe=True)
491
-
492
- if bg_prompt:
493
- background = generate_background(bg_prompt, aspect_ratio)
494
- combined = background
495
- else:
496
- combined = Image.alpha_composite(Image.new("RGBA", masked_alpha.size, "white"), masked_alpha)
497
-
498
- clear_memory()
499
-
500
- with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp:
501
- combined.save(temp.name)
502
- return (img, combined, masked_alpha), gr.DownloadButton(value=temp.name, interactive=True)
503
- except Exception as e:
504
- clear_memory()
505
- print(f"Processing error: {str(e)}")
506
- raise gr.Error(f"Processing failed: {str(e)}")
507
-
508
- def on_change_bbox(prompts: dict[str, Any] | None):
509
- return gr.update(interactive=prompts is not None)
510
-
511
-
512
- def on_change_prompt(img: Image.Image | None, prompt: str | None, bg_prompt: str | None = None):
513
- return gr.update(interactive=bool(img and prompt))
514
-
515
-
516
- def process_prompt(img: Image.Image, prompt: str, bg_prompt: str | None = None,
517
- aspect_ratio: str = "1:1", position: str = "bottom-center",
518
- scale_percent: float = 100) -> tuple[Image.Image, Image.Image]:
519
- try:
520
- if img is None or prompt.strip() == "":
521
- raise gr.Error("Please provide both image and prompt")
522
-
523
- print(f"Processing with position: {position}, scale: {scale_percent}") # 디버깅용
524
-
525
- try:
526
- prompt = translate_to_english(prompt)
527
- if bg_prompt:
528
- bg_prompt = translate_to_english(bg_prompt)
529
- except Exception as e:
530
- print(f"Translation error (continuing with original text): {str(e)}")
531
-
532
- results, _ = _process(img, prompt, bg_prompt, aspect_ratio)
533
-
534
- if bg_prompt:
535
- try:
536
- print(f"Using position: {position}") # 디버깅용
537
- # 위치 값 검증
538
- valid_positions = ["top-left", "top-center", "top-right",
539
- "middle-left", "middle-center", "middle-right",
540
- "bottom-left", "bottom-center", "bottom-right"]
541
- if position not in valid_positions:
542
- position = "bottom-center"
543
- print(f"Invalid position, using default: {position}")
544
-
545
- combined = combine_with_background(
546
- foreground=results[2],
547
- background=results[1],
548
- position=position,
549
- scale_percent=scale_percent
550
- )
551
- return combined, results[2]
552
- except Exception as e:
553
- print(f"Combination error: {str(e)}")
554
- return results[1], results[2]
555
-
556
- return results[1], results[2] # 기본 반환 추가
557
- except Exception as e:
558
- print(f"Error in process_prompt: {str(e)}")
559
- raise gr.Error(str(e))
560
- finally:
561
- clear_memory()
562
-
563
-
564
- def process_bbox(img: Image.Image, box_input: str) -> tuple[Image.Image, Image.Image]:
565
- try:
566
- if img is None or box_input.strip() == "":
567
- raise gr.Error("Please provide both image and bounding box coordinates")
568
-
569
- try:
570
- coords = eval(box_input)
571
- if not isinstance(coords, list) or len(coords) != 4:
572
- raise ValueError("Invalid box format")
573
- bbox = tuple(int(x) for x in coords)
574
- except:
575
- raise gr.Error("Invalid box format. Please provide [xmin, ymin, xmax, ymax]")
576
-
577
- # Process the image
578
- results, _ = _process(img, bbox)
579
-
580
- # 합성된 이미지와 추출된 이미지만 반환
581
- return results[1], results[2]
582
- except Exception as e:
583
- raise gr.Error(str(e))
584
-
585
- # Event handler functions 수정
586
- def update_process_button(img, prompt):
587
- return gr.update(
588
- interactive=bool(img and prompt),
589
- variant="primary" if bool(img and prompt) else "secondary"
590
- )
591
-
592
- def update_box_button(img, box_input):
593
- try:
594
- if img and box_input:
595
- coords = eval(box_input)
596
- if isinstance(coords, list) and len(coords) == 4:
597
- return gr.update(interactive=True, variant="primary")
598
- return gr.update(interactive=False, variant="secondary")
599
- except:
600
- return gr.update(interactive=False, variant="secondary")
601
-
602
-
603
- css = """
604
- /* 기본 레이아웃 */
605
- footer {display: none !important}
606
- body {background: #f5f7fa !important}
607
-
608
- /* 메인 타이틀 */
609
- .main-title {
610
- text-align: center;
611
- margin: 1.5em auto;
612
- padding: 2em;
613
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
614
- border-radius: 15px;
615
- box-shadow: 0 8px 16px rgba(0,0,0,0.1);
616
- max-width: 1200px;
617
- }
618
-
619
- .main-title h1 {
620
- color: #2196F3;
621
- font-size: 3em;
622
- margin-bottom: 0.5em;
623
- font-weight: 700;
624
- text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
625
- }
626
-
627
- .main-title p {
628
- color: #555;
629
- font-size: 1.4em;
630
- line-height: 1.6;
631
- max-width: 800px;
632
- margin: 0 auto;
633
- }
634
-
635
- /* 탭 스타일링 */
636
- .tabs-container {
637
- margin-top: 1em;
638
- }
639
-
640
- .tab-nav {
641
- pointer-events: auto !important;
642
- cursor: pointer !important;
643
- border-bottom: 2px solid #e0e0e0;
644
- margin-bottom: 1em;
645
- }
646
-
647
- .tab-nav button {
648
- pointer-events: auto !important;
649
- cursor: pointer !important;
650
- padding: 0.8em 1.2em;
651
- margin-right: 0.5em;
652
- border: none;
653
- background: none;
654
- color: #666;
655
- transition: all 0.3s ease;
656
- }
657
-
658
- .tab-nav button:hover {
659
- color: #2196F3;
660
- background: rgba(33, 150, 243, 0.1);
661
- }
662
-
663
- .tab-nav button.selected {
664
- color: #2196F3;
665
- border-bottom: 2px solid #2196F3;
666
- font-weight: bold;
667
- }
668
-
669
- /* 패널 스타일링 */
670
- .input-panel, .output-panel {
671
- background: white;
672
- padding: 2em;
673
- border-radius: 15px;
674
- box-shadow: 0 4px 12px rgba(0,0,0,0.05);
675
- margin-bottom: 1.5em;
676
- transition: all 0.3s ease;
677
- }
678
-
679
- .input-panel:hover, .output-panel:hover {
680
- box-shadow: 0 6px 16px rgba(0,0,0,0.1);
681
- }
682
-
683
- /* 컨트롤 패널 */
684
- .controls-panel {
685
- background: #f8f9fa;
686
- padding: 1.5em;
687
- border-radius: 12px;
688
- margin: 1.5em 0;
689
- border: 1px solid #e9ecef;
690
- }
691
-
692
- /* 이미지 디스플레이 */
693
- .image-display {
694
- min-height: 512px;
695
- display: flex;
696
- align-items: center;
697
- justify-content: center;
698
- background: #fafafa;
699
- border-radius: 12px;
700
- margin: 1.5em 0;
701
- border: 2px dashed #e0e0e0;
702
- }
703
-
704
- /* 버튼 스타일링 */
705
- .position-btn {
706
- padding: 12px !important;
707
- border: 2px solid #ddd !important;
708
- border-radius: 8px !important;
709
- background: white !important;
710
- cursor: pointer !important;
711
- transition: all 0.2s ease !important;
712
- width: 48px !important;
713
- height: 48px !important;
714
- display: flex !important;
715
- align-items: center !important;
716
- justify-content: center !important;
717
- font-size: 1.2em !important;
718
- margin: 4px !important;
719
- pointer-events: auto !important;
720
- }
721
-
722
- .position-btn:hover {
723
- background: #e3f2fd !important;
724
- transform: translateY(-2px);
725
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
726
- }
727
-
728
- .position-btn.selected {
729
- background-color: #2196F3 !important;
730
- color: white !important;
731
- border-color: #1976D2 !important;
732
- box-shadow: 0 4px 12px rgba(33,150,243,0.3) !important;
733
- }
734
-
735
- /* 그리드 레이아웃 */
736
- .position-grid {
737
- display: grid;
738
- grid-template-columns: repeat(3, 1fr);
739
- gap: 10px;
740
- margin: 1.5em 0;
741
- padding: 10px;
742
- background: #f5f5f5;
743
- border-radius: 12px;
744
- }
745
-
746
- /* 입력 필드 스타일링 */
747
- input[type="text"], textarea {
748
- border: 2px solid #e0e0e0 !important;
749
- border-radius: 8px !important;
750
- padding: 12px !important;
751
- font-size: 1.1em !important;
752
- transition: all 0.3s ease !important;
753
- }
754
-
755
- input[type="text"]:focus, textarea:focus {
756
- border-color: #2196F3 !important;
757
- box-shadow: 0 0 0 3px rgba(33,150,243,0.2) !important;
758
- }
759
-
760
- /* 슬라이더 스타일링 */
761
- .slider-container {
762
- margin: 1.5em 0;
763
- }
764
-
765
- .slider {
766
- height: 6px !important;
767
- background: #e0e0e0 !important;
768
- border-radius: 3px !important;
769
- }
770
-
771
- .slider-handle {
772
- width: 20px !important;
773
- height: 20px !important;
774
- background: #2196F3 !important;
775
- border: 2px solid white !important;
776
- box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important;
777
- }
778
-
779
- /* 상태 메시지 */
780
- .status-message {
781
- padding: 10px;
782
- border-radius: 8px;
783
- margin: 10px 0;
784
- font-size: 0.9em;
785
- transition: all 0.3s ease;
786
- animation: slideIn 0.3s ease-out;
787
- }
788
-
789
- .status-success {
790
- background: #e8f5e9;
791
- color: #2e7d32;
792
- border: 1px solid #a5d6a7;
793
- }
794
-
795
- .status-error {
796
- background: #ffebee;
797
- color: #c62828;
798
- border: 1px solid #ef9a9a;
799
- }
800
-
801
- /* 반응형 디자인 */
802
- @media (max-width: 768px) {
803
- .main-title h1 {
804
- font-size: 2em;
805
- }
806
-
807
- .main-title p {
808
- font-size: 1.1em;
809
- }
810
-
811
- .input-panel, .output-panel {
812
- padding: 1em;
813
- }
814
-
815
- .position-btn {
816
- width: 40px !important;
817
- height: 40px !important;
818
- font-size: 1em !important;
819
- }
820
- }
821
-
822
- /* 애니메이션 효과 */
823
- @keyframes fadeIn {
824
- from {opacity: 0; transform: translateY(10px);}
825
- to {opacity: 1; transform: translateY(0);}
826
- }
827
-
828
- .fade-in {
829
- animation: fadeIn 0.3s ease-out;
830
- }
831
-
832
- @keyframes slideIn {
833
- from {
834
- transform: translateY(-10px);
835
- opacity: 0;
836
- }
837
- to {
838
- transform: translateY(0);
839
- opacity: 1;
840
- }
841
- }
842
-
843
- /* 인터랙티브 요소 공통 스타일 */
844
- .interactive-element {
845
- pointer-events: auto !important;
846
- cursor: pointer !important;
847
- user-select: none !important;
848
- }
849
-
850
- /* 버튼 공통 스타일 */
851
- button, .button {
852
- pointer-events: auto !important;
853
- cursor: pointer !important;
854
- user-select: none !important;
855
- }
856
-
857
- /* 탭 컨테이너 스타일 */
858
- .tabs-container {
859
- pointer-events: auto !important;
860
- }
861
-
862
- .tab-item {
863
- pointer-events: auto !important;
864
- }
865
-
866
- /* 호버 효과 */
867
- .interactive-element:hover {
868
- transform: translateY(-1px);
869
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
870
- }
871
-
872
- /* 클릭 효과 */
873
- .interactive-element:active {
874
- transform: translateY(1px);
875
- }
876
- """
877
-
878
- js_code = """
879
- <script>
880
- document.addEventListener('DOMContentLoaded', function() {
881
- function enableInteraction(selector) {
882
- const elements = document.querySelectorAll(selector);
883
- elements.forEach(el => {
884
- el.style.pointerEvents = 'auto';
885
- el.style.cursor = 'pointer';
886
- });
887
- }
888
-
889
- // 탭 활성화
890
- enableInteraction('.tab-nav button');
891
- enableInteraction('.position-btn');
892
- enableInteraction('.interactive-element');
893
-
894
- // 버튼 클릭 이벤트 리스너
895
- document.querySelectorAll('.position-btn').forEach(btn => {
896
- btn.addEventListener('click', function() {
897
- document.querySelectorAll('.position-btn').forEach(b => b.classList.remove('selected'));
898
- this.classList.add('selected');
899
- });
900
- });
901
- });
902
- </script>
903
- """
904
-
905
- def add_text_with_stroke(draw, text, x, y, font, text_color, stroke_width):
906
- """Helper function to draw text with stroke"""
907
- # Draw the stroke/outline
908
- for adj_x in range(-stroke_width, stroke_width + 1):
909
- for adj_y in range(-stroke_width, stroke_width + 1):
910
- draw.text((x + adj_x, y + adj_y), text, font=font, fill=text_color)
911
-
912
- def remove_background(image):
913
- # Save the image to a specific location
914
- filename = f"image_{uuid.uuid4()}.png" # Generates a universally unique identifier (UUID) for the filename
915
- image.save(filename)
916
- # Call gradio client for background removal
917
- result = client.predict(images=handle_file(filename), api_name="/image")
918
- return Image.open(result[0])
919
-
920
- def superimpose(image_with_text, overlay_image):
921
- # Open image as RGBA to handle transparency
922
- overlay_image = overlay_image.convert("RGBA")
923
- # Paste overlay on the background
924
- image_with_text.paste(overlay_image, (0, 0), overlay_image)
925
- # Save the final image
926
- # image_with_text.save("output_image.png")
927
- return image_with_text
928
-
929
- def add_text_to_image(
930
- input_image,
931
- text,
932
- font_size,
933
- color,
934
- opacity,
935
- x_position,
936
- y_position,
937
- thickness,
938
- text_position_type,
939
- font_choice
940
- ):
941
- try:
942
- if input_image is None or text.strip() == "":
943
- return input_image
944
-
945
- # PIL Image 객체로 변환
946
- if not isinstance(input_image, Image.Image):
947
- if isinstance(input_image, np.ndarray):
948
- image = Image.fromarray(input_image)
949
- else:
950
- raise ValueError("Unsupported image type")
951
- else:
952
- image = input_image.copy()
953
-
954
- # 이미지를 RGBA 모드로 변환
955
- if image.mode != 'RGBA':
956
- image = image.convert('RGBA')
957
-
958
- # 폰트 설정
959
- font_files = {
960
- "Default": "DejaVuSans.ttf",
961
- "Korean Regular": "ko-Regular.ttf"
962
- }
963
-
964
- try:
965
- font_file = font_files.get(font_choice, "DejaVuSans.ttf")
966
- font = ImageFont.truetype(font_file, int(font_size))
967
- except Exception as e:
968
- print(f"Font loading error ({font_choice}): {str(e)}")
969
- font = ImageFont.load_default()
970
-
971
- # 색상 설정
972
- color_map = {
973
- 'White': (255, 255, 255),
974
- 'Black': (0, 0, 0),
975
- 'Red': (255, 0, 0),
976
- 'Green': (0, 255, 0),
977
- 'Blue': (0, 0, 255),
978
- 'Yellow': (255, 255, 0),
979
- 'Purple': (128, 0, 128)
980
- }
981
- rgb_color = color_map.get(color, (255, 255, 255))
982
-
983
- # 임시 Draw 객체 생성하여 텍스트 크기 계산
984
- temp_draw = ImageDraw.Draw(image)
985
- text_bbox = temp_draw.textbbox((0, 0), text, font=font)
986
- text_width = text_bbox[2] - text_bbox[0]
987
- text_height = text_bbox[3] - text_bbox[1]
988
-
989
- # 위치 계산
990
- actual_x = int((image.width - text_width) * (x_position / 100))
991
- actual_y = int((image.height - text_height) * (y_position / 100))
992
-
993
- # 텍스트 색상 설정
994
- text_color = (*rgb_color, int(opacity))
995
-
996
- if text_position_type == "Text Behind Image":
997
- try:
998
- # 원본 이미지에서 전경 객체만 추출
999
- foreground = remove_background(image)
1000
-
1001
- # 배경 이미지 생성 (원본 이미지 복사)
1002
- background = image.copy()
1003
-
1004
- # 텍스트를 그릴 임시 레이어 생성
1005
- text_layer = Image.new('RGBA', image.size, (255, 255, 255, 0))
1006
- draw_text = ImageDraw.Draw(text_layer)
1007
-
1008
- # 텍스트 그리기
1009
- add_text_with_stroke(
1010
- draw_text,
1011
- text,
1012
- actual_x,
1013
- actual_y,
1014
- font,
1015
- text_color,
1016
- int(thickness)
1017
- )
1018
-
1019
- # 배경에 텍스트 합성
1020
- background = Image.alpha_composite(background, text_layer)
1021
-
1022
- # 텍스트가 있는 배경 위에 전경 객체 합성
1023
- output_image = Image.alpha_composite(background, foreground)
1024
- except Exception as e:
1025
- print(f"Error in Text Behind Image processing: {str(e)}")
1026
- return input_image
1027
- else:
1028
- # 텍스트 오버레이 생성
1029
- txt_overlay = Image.new('RGBA', image.size, (255, 255, 255, 0))
1030
- draw = ImageDraw.Draw(txt_overlay)
1031
-
1032
- # 텍스트를 이미지 위에 그리기
1033
- add_text_with_stroke(
1034
- draw,
1035
- text,
1036
- actual_x,
1037
- actual_y,
1038
- font,
1039
- text_color,
1040
- int(thickness)
1041
- )
1042
- output_image = Image.alpha_composite(image, txt_overlay)
1043
-
1044
- # RGB로 변환
1045
- output_image = output_image.convert('RGB')
1046
-
1047
- return output_image
1048
-
1049
- except Exception as e:
1050
- print(f"Error in add_text_to_image: {str(e)}")
1051
- return input_image
1052
-
1053
-
1054
- def update_position(new_position):
1055
- """위치 업데이트 함수"""
1056
- print(f"Position updated to: {new_position}")
1057
- return new_position
1058
-
1059
- def update_position_and_ui(pos):
1060
- """위치 업데이트 및 UI 반영"""
1061
- updates = {btn: gr.update(value="selected" if pos_val == pos else "")
1062
- for btn, pos_val in position_mapping.items()}
1063
- updates['position'] = pos
1064
- return [pos] + [updates[btn] for btn in position_mapping.keys()]
1065
-
1066
- def process_inpainting_with_feedback(image, mask, prompt):
1067
- try:
1068
- result = process_inpainting(image, mask, prompt)
1069
- return result, update_status_message("Inpainting completed successfully!")
1070
- except Exception as e:
1071
- return None, update_status_message(f"Error: {str(e)}", is_error=True)
1072
-
1073
- def update_controls(bg_prompt):
1074
- """배경 프롬프트 입력 여부에 따라 컨트롤 표시 업데이트"""
1075
- is_visible = bool(bg_prompt)
1076
- return [
1077
- gr.update(visible=is_visible), # aspect_ratio
1078
- gr.update(visible=is_visible), # object_controls
1079
- ]
1080
-
1081
- with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
1082
- position = gr.State(value="bottom-center")
1083
- processing_status = gr.State(value="idle")
1084
- gr.HTML(js_code) # JavaScript 코드 추가
1085
-
1086
- gr.HTML("""
1087
- <div class="main-title">
1088
- <h1>🎨 GiniGen Canvas-o3</h1>
1089
- <p>Remove background of specified objects, generate new backgrounds, and insert text over or behind images with prompts.</p>
1090
- </div>
1091
- """)
1092
-
1093
- status_message = gr.HTML(
1094
- value='<div class="status-message"></div>',
1095
- visible=False
1096
- )
1097
-
1098
-
1099
- with gr.Row(equal_height=True):
1100
- # 왼쪽 패널 (입력)
1101
- with gr.Column(scale=1):
1102
- with gr.Group(elem_classes="input-panel"):
1103
- with gr.Tabs(elem_classes="tabs-container") as tabs:
1104
- with gr.Tab("Image Upload & Inpainting", elem_classes="tab-item"):
1105
-
1106
- input_image = gr.Image(
1107
- type="pil",
1108
- label="Upload Image",
1109
- interactive=True,
1110
- height=400,
1111
- elem_classes="fade-in"
1112
- )
1113
-
1114
- with gr.Group():
1115
- inpaint_prompt = gr.Textbox(
1116
- label="Inpainting Prompt",
1117
- placeholder="Describe what you want to add in the masked area..."
1118
- )
1119
- mask_input = image_annotator(
1120
- label="Draw mask for inpainting",
1121
- height=400
1122
- )
1123
- inpaint_btn = gr.Button("Apply Inpainting", variant="primary")
1124
-
1125
- # 두 번째 탭: 배경 제거 및 생성
1126
- with gr.Tab("Background Removal", elem_classes="tab-item"):
1127
-
1128
-
1129
- text_prompt = gr.Textbox(
1130
- label="Object to Extract",
1131
- placeholder="Enter what you want to extract...",
1132
- interactive=True,
1133
- elem_classes="fade-in"
1134
- )
1135
-
1136
- with gr.Row():
1137
- bg_prompt = gr.Textbox(
1138
- label="Background Prompt (optional)",
1139
- placeholder="Describe the background...",
1140
- interactive=True,
1141
- scale=3
1142
- )
1143
- aspect_ratio = gr.Dropdown(
1144
- choices=["1:1", "16:9", "9:16", "4:3"],
1145
- value="1:1",
1146
- label="Aspect Ratio",
1147
- interactive=True,
1148
- visible=True,
1149
- scale=1
1150
- )
1151
-
1152
- with gr.Group(elem_classes="controls-panel", visible=False) as object_controls:
1153
- with gr.Column(scale=1):
1154
- with gr.Row():
1155
-
1156
- btn_top_left = gr.Button("↖", elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary")
1157
- btn_top_center = gr.Button("↑",elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary")
1158
- btn_top_right = gr.Button("↗", elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary")
1159
-
1160
- with gr.Row():
1161
- btn_middle_left = gr.Button("←", elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary")
1162
- btn_middle_center = gr.Button("•",elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary")
1163
- btn_middle_right = gr.Button("→", elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary")
1164
-
1165
- with gr.Row():
1166
- btn_bottom_left = gr.Button("↙", elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary")
1167
- btn_bottom_center = gr.Button("↓", elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary", value="selected")
1168
- btn_bottom_right = gr.Button("↘", elem_classes=["position-btn", "interactive-element"],interactive=True,variant="secondary")
1169
-
1170
- with gr.Column(scale=1):
1171
- scale_slider = gr.Slider(
1172
- minimum=10,
1173
- maximum=200,
1174
- value=50,
1175
- step=5,
1176
- label="Object Size (%)"
1177
- )
1178
-
1179
- process_btn = gr.Button(
1180
- "Process",
1181
- variant="primary",
1182
- interactive=False,
1183
- size="lg"
1184
- )
1185
-
1186
-
1187
-
1188
- # 오른쪽 패널 (출력)
1189
- with gr.Column(scale=1):
1190
- with gr.Group(elem_classes="output-panel"):
1191
- with gr.Tab("Result"):
1192
- combined_image = gr.Image(
1193
- label="Combined Result",
1194
- show_download_button=True,
1195
- type="pil",
1196
- height=400
1197
- )
1198
-
1199
- with gr.Accordion("Text Insertion Options", open=False):
1200
- with gr.Group():
1201
- with gr.Row():
1202
- text_input = gr.Textbox(
1203
- label="Text Content",
1204
- placeholder="Enter text to add..."
1205
- )
1206
- text_position_type = gr.Radio(
1207
- choices=["Text Over Image", "Text Behind Image"],
1208
- value="Text Over Image",
1209
- label="Text Position"
1210
- )
1211
-
1212
- with gr.Row():
1213
- with gr.Column(scale=1):
1214
- font_choice = gr.Dropdown(
1215
- choices=["Default", "Korean Regular"],
1216
- value="Default",
1217
- label="Font Selection",
1218
- interactive=True
1219
- )
1220
- font_size = gr.Slider(
1221
- minimum=10,
1222
- maximum=200,
1223
- value=40,
1224
- step=5,
1225
- label="Font Size"
1226
- )
1227
- color_dropdown = gr.Dropdown(
1228
- choices=["White", "Black", "Red", "Green", "Blue", "Yellow", "Purple"],
1229
- value="White",
1230
- label="Text Color"
1231
- )
1232
- thickness = gr.Slider(
1233
- minimum=0,
1234
- maximum=10,
1235
- value=1,
1236
- step=1,
1237
- label="Text Thickness"
1238
- )
1239
- with gr.Column(scale=1):
1240
- opacity_slider = gr.Slider(
1241
- minimum=0,
1242
- maximum=255,
1243
- value=255,
1244
- step=1,
1245
- label="Opacity"
1246
- )
1247
- x_position = gr.Slider(
1248
- minimum=0,
1249
- maximum=100,
1250
- value=50,
1251
- step=1,
1252
- label="Left(0%)~Right(100%)"
1253
- )
1254
- y_position = gr.Slider(
1255
- minimum=0,
1256
- maximum=100,
1257
- value=50,
1258
- step=1,
1259
- label="High(0%)~Low(100%)"
1260
- )
1261
- add_text_btn = gr.Button("Apply Text", variant="primary")
1262
-
1263
- extracted_image = gr.Image(
1264
- label="Extracted Object",
1265
- show_download_button=True,
1266
- type="pil",
1267
- height=200
1268
- )
1269
-
1270
- # CSS 스타일
1271
- gr.HTML("""
1272
- <style>
1273
- .position-btn.selected {
1274
- background-color: #2196F3 !important;
1275
- color: white !important;
1276
- }
1277
- </style>
1278
- """)
1279
-
1280
- # 이벤트 바인딩
1281
- position_mapping = {
1282
- btn_top_left: "top-left",
1283
- btn_top_center: "top-center",
1284
- btn_top_right: "top-right",
1285
- btn_middle_left: "middle-left",
1286
- btn_middle_center: "middle-center",
1287
- btn_middle_right: "middle-right",
1288
- btn_bottom_left: "bottom-left",
1289
- btn_bottom_center: "bottom-center",
1290
- btn_bottom_right: "bottom-right"
1291
- }
1292
-
1293
-
1294
- def update_status_message(message: str, is_error: bool = False) -> dict:
1295
- return {
1296
- "visible": True,
1297
- "value": f'<div class="status-message {"status-error" if is_error else "status-success"}">{message}</div>'
1298
- }
1299
-
1300
- def update_ui_state(component_id, value, is_error=False):
1301
- class_name = "status-error" if is_error else "status-success"
1302
- return gr.update(
1303
- value=f'<div class="status-message {class_name}">{value}</div>',
1304
- visible=True
1305
- )
1306
-
1307
- def handle_button_click(btn_pos):
1308
- """버튼 클릭 이벤트 핸들러"""
1309
- print(f"Button clicked: {btn_pos}")
1310
- updates = {btn: gr.update(variant="secondary", value="") for btn in position_mapping.keys()}
1311
- updates[btn_pos] = gr.update(variant="primary", value="selected")
1312
- return [btn_pos] + [updates[btn] for btn in position_mapping.keys()]
1313
-
1314
- # 버튼 이벤트 바인딩
1315
- for btn, pos in position_mapping.items():
1316
- btn.click(
1317
- fn=handle_button_click,
1318
- inputs=[gr.State(pos)],
1319
- outputs=[position] + list(position_mapping.keys()),
1320
- api_name=f"btn_click_{pos}"
1321
- )
1322
-
1323
-
1324
-
1325
- inpaint_btn.click(
1326
- fn=process_inpainting_with_feedback,
1327
- inputs=[input_image, mask_input, inpaint_prompt],
1328
- outputs=[input_image, status_message],
1329
- api_name="inpaint"
1330
- )
1331
-
1332
- process_btn.click(
1333
- fn=process_prompt,
1334
- inputs=[
1335
- input_image,
1336
- text_prompt,
1337
- bg_prompt,
1338
- aspect_ratio,
1339
- position,
1340
- scale_slider
1341
- ],
1342
- outputs=[combined_image, extracted_image, status_message],
1343
- api_name="process"
1344
- )
1345
-
1346
- bg_prompt.change(
1347
- fn=update_controls,
1348
- inputs=bg_prompt,
1349
- outputs=[aspect_ratio, object_controls],
1350
- queue=False,
1351
- api_name="update_controls"
1352
- )
1353
-
1354
- input_image.change(
1355
- fn=update_process_button,
1356
- inputs=[input_image, text_prompt],
1357
- outputs=process_btn,
1358
- queue=False,
1359
- api_name="update_process_button"
1360
- )
1361
-
1362
- text_prompt.change(
1363
- fn=update_process_button,
1364
- inputs=[input_image, text_prompt],
1365
- outputs=process_btn,
1366
- queue=False,
1367
- api_name="update_text_prompt"
1368
- )
1369
-
1370
- add_text_btn.click(
1371
- fn=add_text_to_image,
1372
- inputs=[
1373
- combined_image,
1374
- text_input,
1375
- font_size,
1376
- color_dropdown,
1377
- opacity_slider,
1378
- x_position,
1379
- y_position,
1380
- thickness,
1381
- text_position_type,
1382
- font_choice
1383
- ],
1384
- outputs=[combined_image, status_message],
1385
- api_name="add_text"
1386
- )
1387
-
1388
-
1389
- demo.queue(max_size=5)
1390
- demo.launch(
1391
- server_name="0.0.0.0",
1392
- server_port=7860,
1393
- share=False,
1394
- max_threads=2
1395
- )