Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
@@ -268,16 +268,14 @@ def resize_object(image: Image.Image, scale_percent: float) -> Image.Image:
|
|
268 |
def combine_with_background(foreground: Image.Image, background: Image.Image,
|
269 |
position: str = "bottom-center", scale_percent: float = 100) -> Image.Image:
|
270 |
"""전경과 배경 합성 함수"""
|
271 |
-
|
272 |
-
result = background.convert('RGBA')
|
273 |
|
274 |
-
|
275 |
scaled_foreground = resize_object(foreground, scale_percent)
|
276 |
|
277 |
-
# 오브젝트 위치 계산
|
278 |
x, y = calculate_object_position(position, result.size, scaled_foreground.size)
|
|
|
279 |
|
280 |
-
# 합성
|
281 |
result.paste(scaled_foreground, (x, y), scaled_foreground)
|
282 |
return result
|
283 |
|
@@ -348,7 +346,6 @@ def on_change_prompt(img: Image.Image | None, prompt: str | None, bg_prompt: str
|
|
348 |
return gr.update(interactive=bool(img and prompt))
|
349 |
|
350 |
|
351 |
-
|
352 |
def process_prompt(img: Image.Image, prompt: str, bg_prompt: str | None = None,
|
353 |
aspect_ratio: str = "1:1", position: str = "bottom-center",
|
354 |
scale_percent: float = 100) -> tuple[Image.Image, Image.Image]:
|
@@ -356,7 +353,7 @@ def process_prompt(img: Image.Image, prompt: str, bg_prompt: str | None = None,
|
|
356 |
if img is None or prompt.strip() == "":
|
357 |
raise gr.Error("Please provide both image and prompt")
|
358 |
|
359 |
-
print(f"Processing with position: {position}, scale: {scale_percent}")
|
360 |
|
361 |
try:
|
362 |
prompt = translate_to_english(prompt)
|
@@ -369,25 +366,34 @@ def process_prompt(img: Image.Image, prompt: str, bg_prompt: str | None = None,
|
|
369 |
|
370 |
if bg_prompt:
|
371 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
combined = combine_with_background(
|
373 |
foreground=results[2],
|
374 |
background=results[1],
|
375 |
position=position,
|
376 |
scale_percent=scale_percent
|
377 |
)
|
378 |
-
print(f"Combined image created with position: {position}")
|
379 |
return combined, results[2]
|
380 |
except Exception as e:
|
381 |
print(f"Combination error: {str(e)}")
|
382 |
return results[1], results[2]
|
383 |
|
384 |
-
return results[1], results[2]
|
385 |
except Exception as e:
|
386 |
print(f"Error in process_prompt: {str(e)}")
|
387 |
raise gr.Error(str(e))
|
388 |
finally:
|
389 |
clear_memory()
|
390 |
-
|
|
|
391 |
def process_bbox(img: Image.Image, box_input: str) -> tuple[Image.Image, Image.Image]:
|
392 |
try:
|
393 |
if img is None or box_input.strip() == "":
|
@@ -427,71 +433,117 @@ def update_box_button(img, box_input):
|
|
427 |
return gr.update(interactive=False, variant="secondary")
|
428 |
|
429 |
|
430 |
-
# CSS 정의
|
431 |
css = """
|
432 |
footer {display: none}
|
433 |
.main-title {
|
434 |
text-align: center;
|
435 |
-
margin:
|
436 |
-
padding:
|
437 |
-
background: #
|
438 |
-
border-radius:
|
|
|
439 |
}
|
440 |
.main-title h1 {
|
441 |
color: #2196F3;
|
442 |
-
font-size: 2.
|
443 |
-
margin-bottom: 0.
|
|
|
444 |
}
|
445 |
.main-title p {
|
446 |
-
color: #
|
447 |
-
font-size: 1.
|
|
|
448 |
}
|
449 |
.container {
|
450 |
max-width: 1200px;
|
451 |
margin: auto;
|
452 |
padding: 20px;
|
453 |
}
|
454 |
-
.
|
455 |
-
margin-top: 1em;
|
456 |
-
}
|
457 |
-
.input-group {
|
458 |
background: white;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
459 |
padding: 1em;
|
460 |
border-radius: 8px;
|
461 |
-
|
462 |
}
|
463 |
-
.
|
464 |
-
|
465 |
-
|
|
|
|
|
|
|
466 |
border-radius: 8px;
|
467 |
-
|
468 |
}
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
475 |
cursor: pointer;
|
476 |
-
transition: background 0.3s ease;
|
477 |
}
|
478 |
-
|
479 |
-
|
|
|
|
|
|
|
|
|
480 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
481 |
.position-btn {
|
|
|
|
|
|
|
|
|
|
|
482 |
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
483 |
}
|
|
|
484 |
.position-btn:hover {
|
485 |
-
background
|
486 |
}
|
|
|
487 |
.position-btn.selected {
|
488 |
background-color: #2196F3;
|
489 |
color: white;
|
|
|
490 |
}
|
491 |
"""
|
492 |
|
493 |
|
494 |
-
|
495 |
def add_text_with_stroke(draw, text, x, y, font, text_color, stroke_width):
|
496 |
"""Helper function to draw text with stroke"""
|
497 |
# Draw the stroke/outline
|
@@ -628,171 +680,212 @@ def add_text_to_image(
|
|
628 |
return input_image
|
629 |
|
630 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
631 |
with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
|
|
|
|
632 |
gr.HTML("""
|
633 |
<div class="main-title">
|
634 |
-
<h1>🎨GiniGen Canvas-o3</h1>
|
635 |
<p>Remove background of specified objects, generate new backgrounds, and insert text over or behind images with prompts.</p>
|
636 |
</div>
|
637 |
""")
|
638 |
|
639 |
-
with gr.Row():
|
|
|
640 |
with gr.Column(scale=1):
|
641 |
-
|
642 |
-
|
643 |
-
|
644 |
-
|
645 |
-
)
|
646 |
-
text_prompt = gr.Textbox(
|
647 |
-
label="Object to Extract",
|
648 |
-
placeholder="Enter what you want to extract...",
|
649 |
-
interactive=True
|
650 |
-
)
|
651 |
-
with gr.Row():
|
652 |
-
bg_prompt = gr.Textbox(
|
653 |
-
label="Background Prompt (optional)",
|
654 |
-
placeholder="Describe the background...",
|
655 |
interactive=True,
|
656 |
-
|
657 |
)
|
658 |
-
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
-
interactive=True,
|
663 |
-
visible=True,
|
664 |
-
scale=1
|
665 |
)
|
666 |
-
|
667 |
-
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
680 |
-
btn_bottom_center = gr.Button("↓")
|
681 |
-
btn_bottom_right = gr.Button("↘")
|
682 |
-
with gr.Column(scale=1):
|
683 |
-
scale_slider = gr.Slider(
|
684 |
-
minimum=10,
|
685 |
-
maximum=200,
|
686 |
-
value=50,
|
687 |
-
step=5,
|
688 |
-
label="Object Size (%)"
|
689 |
)
|
690 |
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
696 |
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
702 |
-
type="pil",
|
703 |
-
height=512
|
704 |
)
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
712 |
-
|
713 |
-
|
714 |
-
|
715 |
-
|
716 |
-
value="Text Over Image",
|
717 |
-
label="Text Position Type",
|
718 |
-
interactive=True
|
719 |
-
)
|
720 |
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
|
725 |
-
|
726 |
-
|
727 |
-
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
|
742 |
-
|
743 |
-
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
|
749 |
-
|
750 |
-
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
|
755 |
-
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
772 |
|
773 |
-
with gr.Row():
|
774 |
extracted_image = gr.Image(
|
775 |
label="Extracted Object",
|
776 |
show_download_button=True,
|
777 |
type="pil",
|
778 |
-
height=
|
779 |
)
|
780 |
|
781 |
-
#
|
782 |
-
|
783 |
-
|
784 |
-
|
785 |
-
|
786 |
-
|
787 |
-
|
788 |
-
|
789 |
-
|
790 |
-
|
791 |
-
|
792 |
-
|
793 |
-
|
794 |
-
|
795 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
796 |
input_image.change(
|
797 |
fn=update_process_button,
|
798 |
inputs=[input_image, text_prompt],
|
@@ -807,21 +900,6 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
|
807 |
queue=False
|
808 |
)
|
809 |
|
810 |
-
def update_controls(bg_prompt):
|
811 |
-
"""배경 프롬프트 입력 여부에 따라 컨트롤 표시 업데이트"""
|
812 |
-
is_visible = bool(bg_prompt)
|
813 |
-
return [
|
814 |
-
gr.update(visible=is_visible), # aspect_ratio
|
815 |
-
gr.update(visible=is_visible), # object_controls
|
816 |
-
]
|
817 |
-
|
818 |
-
bg_prompt.change(
|
819 |
-
fn=update_controls,
|
820 |
-
inputs=bg_prompt,
|
821 |
-
outputs=[aspect_ratio, object_controls],
|
822 |
-
queue=False
|
823 |
-
)
|
824 |
-
|
825 |
process_btn.click(
|
826 |
fn=process_prompt,
|
827 |
inputs=[
|
@@ -836,7 +914,6 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
|
836 |
queue=True
|
837 |
)
|
838 |
|
839 |
-
# 텍스트 추가 버튼 이벤트 연결 수정
|
840 |
add_text_btn.click(
|
841 |
fn=add_text_to_image,
|
842 |
inputs=[
|
@@ -849,15 +926,15 @@ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
|
849 |
y_position,
|
850 |
thickness,
|
851 |
text_position_type,
|
852 |
-
font_choice
|
853 |
],
|
854 |
outputs=combined_image
|
855 |
)
|
856 |
|
857 |
-
demo.queue(max_size=5)
|
858 |
demo.launch(
|
859 |
server_name="0.0.0.0",
|
860 |
server_port=7860,
|
861 |
share=False,
|
862 |
-
max_threads=2
|
863 |
)
|
|
|
268 |
def combine_with_background(foreground: Image.Image, background: Image.Image,
|
269 |
position: str = "bottom-center", scale_percent: float = 100) -> Image.Image:
|
270 |
"""전경과 배경 합성 함수"""
|
271 |
+
print(f"Combining with position: {position}, scale: {scale_percent}")
|
|
|
272 |
|
273 |
+
result = background.convert('RGBA')
|
274 |
scaled_foreground = resize_object(foreground, scale_percent)
|
275 |
|
|
|
276 |
x, y = calculate_object_position(position, result.size, scaled_foreground.size)
|
277 |
+
print(f"Calculated position coordinates: ({x}, {y})")
|
278 |
|
|
|
279 |
result.paste(scaled_foreground, (x, y), scaled_foreground)
|
280 |
return result
|
281 |
|
|
|
346 |
return gr.update(interactive=bool(img and prompt))
|
347 |
|
348 |
|
|
|
349 |
def process_prompt(img: Image.Image, prompt: str, bg_prompt: str | None = None,
|
350 |
aspect_ratio: str = "1:1", position: str = "bottom-center",
|
351 |
scale_percent: float = 100) -> tuple[Image.Image, Image.Image]:
|
|
|
353 |
if img is None or prompt.strip() == "":
|
354 |
raise gr.Error("Please provide both image and prompt")
|
355 |
|
356 |
+
print(f"Processing with position: {position}, scale: {scale_percent}") # 디버깅용
|
357 |
|
358 |
try:
|
359 |
prompt = translate_to_english(prompt)
|
|
|
366 |
|
367 |
if bg_prompt:
|
368 |
try:
|
369 |
+
print(f"Using position: {position}") # 디버깅용
|
370 |
+
# 위치 값 검증
|
371 |
+
valid_positions = ["top-left", "top-center", "top-right",
|
372 |
+
"middle-left", "middle-center", "middle-right",
|
373 |
+
"bottom-left", "bottom-center", "bottom-right"]
|
374 |
+
if position not in valid_positions:
|
375 |
+
position = "bottom-center"
|
376 |
+
print(f"Invalid position, using default: {position}")
|
377 |
+
|
378 |
combined = combine_with_background(
|
379 |
foreground=results[2],
|
380 |
background=results[1],
|
381 |
position=position,
|
382 |
scale_percent=scale_percent
|
383 |
)
|
|
|
384 |
return combined, results[2]
|
385 |
except Exception as e:
|
386 |
print(f"Combination error: {str(e)}")
|
387 |
return results[1], results[2]
|
388 |
|
389 |
+
return results[1], results[2] # 기본 반환 추가
|
390 |
except Exception as e:
|
391 |
print(f"Error in process_prompt: {str(e)}")
|
392 |
raise gr.Error(str(e))
|
393 |
finally:
|
394 |
clear_memory()
|
395 |
+
|
396 |
+
|
397 |
def process_bbox(img: Image.Image, box_input: str) -> tuple[Image.Image, Image.Image]:
|
398 |
try:
|
399 |
if img is None or box_input.strip() == "":
|
|
|
433 |
return gr.update(interactive=False, variant="secondary")
|
434 |
|
435 |
|
|
|
436 |
css = """
|
437 |
footer {display: none}
|
438 |
.main-title {
|
439 |
text-align: center;
|
440 |
+
margin: 1em 0;
|
441 |
+
padding: 1.5em;
|
442 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
443 |
+
border-radius: 15px;
|
444 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
445 |
}
|
446 |
.main-title h1 {
|
447 |
color: #2196F3;
|
448 |
+
font-size: 2.8em;
|
449 |
+
margin-bottom: 0.3em;
|
450 |
+
font-weight: 700;
|
451 |
}
|
452 |
.main-title p {
|
453 |
+
color: #555;
|
454 |
+
font-size: 1.3em;
|
455 |
+
line-height: 1.4;
|
456 |
}
|
457 |
.container {
|
458 |
max-width: 1200px;
|
459 |
margin: auto;
|
460 |
padding: 20px;
|
461 |
}
|
462 |
+
.input-panel, .output-panel {
|
|
|
|
|
|
|
463 |
background: white;
|
464 |
+
padding: 1.5em;
|
465 |
+
border-radius: 12px;
|
466 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
467 |
+
margin-bottom: 1em;
|
468 |
+
}
|
469 |
+
.controls-panel {
|
470 |
+
background: #f8f9fa;
|
471 |
padding: 1em;
|
472 |
border-radius: 8px;
|
473 |
+
margin: 1em 0;
|
474 |
}
|
475 |
+
.image-display {
|
476 |
+
min-height: 512px;
|
477 |
+
display: flex;
|
478 |
+
align-items: center;
|
479 |
+
justify-content: center;
|
480 |
+
background: #fafafa;
|
481 |
border-radius: 8px;
|
482 |
+
margin: 1em 0;
|
483 |
}
|
484 |
+
.example-section {
|
485 |
+
text-align: center;
|
486 |
+
padding: 2em;
|
487 |
+
background: #f5f5f5;
|
488 |
+
border-radius: 12px;
|
489 |
+
margin-top: 2em;
|
490 |
+
}
|
491 |
+
.example-section img {
|
492 |
+
max-width: 100%;
|
493 |
+
border-radius: 8px;
|
494 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
495 |
+
}
|
496 |
+
.accordion {
|
497 |
+
border: 1px solid #e0e0e0;
|
498 |
+
border-radius: 8px;
|
499 |
+
margin: 1em 0;
|
500 |
+
}
|
501 |
+
.accordion-header {
|
502 |
+
padding: 1em;
|
503 |
+
background: #f5f5f5;
|
504 |
cursor: pointer;
|
|
|
505 |
}
|
506 |
+
.accordion-content {
|
507 |
+
padding: 1em;
|
508 |
+
display: none;
|
509 |
+
}
|
510 |
+
.accordion.open .accordion-content {
|
511 |
+
display: block;
|
512 |
}
|
513 |
+
.position-grid {
|
514 |
+
display: grid;
|
515 |
+
grid-template-columns: repeat(3, 1fr);
|
516 |
+
gap: 8px;
|
517 |
+
margin: 1em 0;
|
518 |
+
}
|
519 |
+
|
520 |
+
|
521 |
.position-btn {
|
522 |
+
padding: 10px;
|
523 |
+
border: 1px solid #ddd;
|
524 |
+
border-radius: 4px;
|
525 |
+
background: white;
|
526 |
+
cursor: pointer;
|
527 |
transition: all 0.3s ease;
|
528 |
+
width: 40px;
|
529 |
+
height: 40px;
|
530 |
+
display: flex;
|
531 |
+
align-items: center;
|
532 |
+
justify-content: center;
|
533 |
}
|
534 |
+
|
535 |
.position-btn:hover {
|
536 |
+
background: #e3f2fd;
|
537 |
}
|
538 |
+
|
539 |
.position-btn.selected {
|
540 |
background-color: #2196F3;
|
541 |
color: white;
|
542 |
+
border-color: #1976D2;
|
543 |
}
|
544 |
"""
|
545 |
|
546 |
|
|
|
547 |
def add_text_with_stroke(draw, text, x, y, font, text_color, stroke_width):
|
548 |
"""Helper function to draw text with stroke"""
|
549 |
# Draw the stroke/outline
|
|
|
680 |
return input_image
|
681 |
|
682 |
|
683 |
+
def update_position(new_position):
|
684 |
+
"""위치 업데이트 함수"""
|
685 |
+
print(f"Position updated to: {new_position}")
|
686 |
+
return new_position
|
687 |
+
|
688 |
+
def update_controls(bg_prompt):
|
689 |
+
"""배경 프롬프트 입력 여부에 따라 컨트롤 표시 업데이트"""
|
690 |
+
is_visible = bool(bg_prompt)
|
691 |
+
return [
|
692 |
+
gr.update(visible=is_visible), # aspect_ratio
|
693 |
+
gr.update(visible=is_visible), # object_controls
|
694 |
+
]
|
695 |
+
|
696 |
with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
697 |
+
position = gr.State(value="bottom-center") # 여기로 이동
|
698 |
+
|
699 |
gr.HTML("""
|
700 |
<div class="main-title">
|
701 |
+
<h1>🎨 GiniGen Canvas-o3</h1>
|
702 |
<p>Remove background of specified objects, generate new backgrounds, and insert text over or behind images with prompts.</p>
|
703 |
</div>
|
704 |
""")
|
705 |
|
706 |
+
with gr.Row(equal_height=True):
|
707 |
+
# 왼쪽 패널 (입력)
|
708 |
with gr.Column(scale=1):
|
709 |
+
with gr.Group(elem_classes="input-panel"):
|
710 |
+
input_image = gr.Image(
|
711 |
+
type="pil",
|
712 |
+
label="Upload Image",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
713 |
interactive=True,
|
714 |
+
height=400
|
715 |
)
|
716 |
+
text_prompt = gr.Textbox(
|
717 |
+
label="Object to Extract",
|
718 |
+
placeholder="Enter what you want to extract...",
|
719 |
+
interactive=True
|
|
|
|
|
|
|
720 |
)
|
721 |
+
with gr.Row():
|
722 |
+
bg_prompt = gr.Textbox(
|
723 |
+
label="Background Prompt (optional)",
|
724 |
+
placeholder="Describe the background...",
|
725 |
+
interactive=True,
|
726 |
+
scale=3
|
727 |
+
)
|
728 |
+
aspect_ratio = gr.Dropdown(
|
729 |
+
choices=["1:1", "16:9", "9:16", "4:3"],
|
730 |
+
value="1:1",
|
731 |
+
label="Aspect Ratio",
|
732 |
+
interactive=True,
|
733 |
+
visible=True,
|
734 |
+
scale=1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
735 |
)
|
736 |
|
737 |
+
with gr.Group(elem_classes="controls-panel", visible=False) as object_controls:
|
738 |
+
with gr.Column(scale=1):
|
739 |
+
position = gr.State(value="bottom-center") # 초기값 설정
|
740 |
+
with gr.Row():
|
741 |
+
btn_top_left = gr.Button("↖", elem_classes="position-btn")
|
742 |
+
btn_top_center = gr.Button("↑", elem_classes="position-btn")
|
743 |
+
btn_top_right = gr.Button("↗", elem_classes="position-btn")
|
744 |
+
with gr.Row():
|
745 |
+
btn_middle_left = gr.Button("←", elem_classes="position-btn")
|
746 |
+
btn_middle_center = gr.Button("•", elem_classes="position-btn")
|
747 |
+
btn_middle_right = gr.Button("→", elem_classes="position-btn")
|
748 |
+
with gr.Row():
|
749 |
+
btn_bottom_left = gr.Button("↙", elem_classes="position-btn")
|
750 |
+
btn_bottom_center = gr.Button("↓", elem_classes="position-btn", value="selected")
|
751 |
+
btn_bottom_right = gr.Button("↘", elem_classes="position-btn")
|
752 |
+
with gr.Column(scale=1):
|
753 |
+
scale_slider = gr.Slider(
|
754 |
+
minimum=10,
|
755 |
+
maximum=200,
|
756 |
+
value=50,
|
757 |
+
step=5,
|
758 |
+
label="Object Size (%)"
|
759 |
+
)
|
760 |
|
761 |
+
process_btn = gr.Button(
|
762 |
+
"Process",
|
763 |
+
variant="primary",
|
764 |
+
interactive=False,
|
765 |
+
size="lg"
|
|
|
|
|
766 |
)
|
767 |
+
|
768 |
+
# 오른쪽 패널 (출력)
|
769 |
+
with gr.Column(scale=1):
|
770 |
+
with gr.Group(elem_classes="output-panel"):
|
771 |
+
with gr.Tab("Result"):
|
772 |
+
combined_image = gr.Image(
|
773 |
+
label="Combined Result",
|
774 |
+
show_download_button=True,
|
775 |
+
type="pil",
|
776 |
+
height=400
|
777 |
+
)
|
|
|
|
|
|
|
|
|
778 |
|
779 |
+
# 텍스트 삽입 옵션을 Accordion으로 변경
|
780 |
+
with gr.Accordion("Text Insertion Options", open=False):
|
781 |
+
with gr.Group():
|
782 |
+
with gr.Row():
|
783 |
+
text_input = gr.Textbox(
|
784 |
+
label="Text Content",
|
785 |
+
placeholder="Enter text to add..."
|
786 |
+
)
|
787 |
+
text_position_type = gr.Radio(
|
788 |
+
choices=["Text Over Image", "Text Behind Image"],
|
789 |
+
value="Text Over Image",
|
790 |
+
label="Text Position"
|
791 |
+
)
|
792 |
+
|
793 |
+
with gr.Row():
|
794 |
+
with gr.Column(scale=1):
|
795 |
+
font_choice = gr.Dropdown(
|
796 |
+
choices=["Default", "Korean Regular", "Korean Son"],
|
797 |
+
value="Default",
|
798 |
+
label="Font Selection",
|
799 |
+
interactive=True
|
800 |
+
)
|
801 |
+
font_size = gr.Slider(
|
802 |
+
minimum=10,
|
803 |
+
maximum=200,
|
804 |
+
value=40,
|
805 |
+
step=5,
|
806 |
+
label="Font Size"
|
807 |
+
)
|
808 |
+
color_dropdown = gr.Dropdown(
|
809 |
+
choices=["White", "Black", "Red", "Green", "Blue", "Yellow", "Purple"],
|
810 |
+
value="White",
|
811 |
+
label="Text Color"
|
812 |
+
)
|
813 |
+
thickness = gr.Slider(
|
814 |
+
minimum=0,
|
815 |
+
maximum=10,
|
816 |
+
value=1,
|
817 |
+
step=1,
|
818 |
+
label="Text Thickness"
|
819 |
+
)
|
820 |
+
with gr.Column(scale=1):
|
821 |
+
opacity_slider = gr.Slider(
|
822 |
+
minimum=0,
|
823 |
+
maximum=255,
|
824 |
+
value=255,
|
825 |
+
step=1,
|
826 |
+
label="Opacity"
|
827 |
+
)
|
828 |
+
x_position = gr.Slider(
|
829 |
+
minimum=0,
|
830 |
+
maximum=100,
|
831 |
+
value=50,
|
832 |
+
step=1,
|
833 |
+
label="X Position (%)"
|
834 |
+
)
|
835 |
+
y_position = gr.Slider(
|
836 |
+
minimum=0,
|
837 |
+
maximum=100,
|
838 |
+
value=50,
|
839 |
+
step=1,
|
840 |
+
label="Y Position (%)"
|
841 |
+
)
|
842 |
+
add_text_btn = gr.Button("Apply Text", variant="primary")
|
843 |
|
|
|
844 |
extracted_image = gr.Image(
|
845 |
label="Extracted Object",
|
846 |
show_download_button=True,
|
847 |
type="pil",
|
848 |
+
height=200
|
849 |
)
|
850 |
|
851 |
+
# CSS 클래스를 위한 스타일 추가
|
852 |
+
gr.HTML("""
|
853 |
+
<style>
|
854 |
+
.position-btn.selected {
|
855 |
+
background-color: #2196F3 !important;
|
856 |
+
color: white !important;
|
857 |
+
}
|
858 |
+
</style>
|
859 |
+
""")
|
860 |
+
|
861 |
+
# 버튼 클릭 이벤트 바인딩
|
862 |
+
position_mapping = {
|
863 |
+
btn_top_left: "top-left",
|
864 |
+
btn_top_center: "top-center",
|
865 |
+
btn_top_right: "top-right",
|
866 |
+
btn_middle_left: "middle-left",
|
867 |
+
btn_middle_center: "middle-center",
|
868 |
+
btn_middle_right: "middle-right",
|
869 |
+
btn_bottom_left: "bottom-left",
|
870 |
+
btn_bottom_center: "bottom-center",
|
871 |
+
btn_bottom_right: "bottom-right"
|
872 |
+
}
|
873 |
+
|
874 |
+
for btn, pos in position_mapping.items():
|
875 |
+
btn.click(
|
876 |
+
fn=lambda pos=pos: update_position(pos), # 클로저 문제 해결을 위해 수정
|
877 |
+
outputs=position
|
878 |
+
)
|
879 |
+
|
880 |
+
|
881 |
+
# 이벤트 바인딩
|
882 |
+
bg_prompt.change(
|
883 |
+
fn=update_controls,
|
884 |
+
inputs=bg_prompt,
|
885 |
+
outputs=[aspect_ratio, object_controls],
|
886 |
+
queue=False
|
887 |
+
)
|
888 |
+
|
889 |
input_image.change(
|
890 |
fn=update_process_button,
|
891 |
inputs=[input_image, text_prompt],
|
|
|
900 |
queue=False
|
901 |
)
|
902 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
903 |
process_btn.click(
|
904 |
fn=process_prompt,
|
905 |
inputs=[
|
|
|
914 |
queue=True
|
915 |
)
|
916 |
|
|
|
917 |
add_text_btn.click(
|
918 |
fn=add_text_to_image,
|
919 |
inputs=[
|
|
|
926 |
y_position,
|
927 |
thickness,
|
928 |
text_position_type,
|
929 |
+
font_choice
|
930 |
],
|
931 |
outputs=combined_image
|
932 |
)
|
933 |
|
934 |
+
demo.queue(max_size=5)
|
935 |
demo.launch(
|
936 |
server_name="0.0.0.0",
|
937 |
server_port=7860,
|
938 |
share=False,
|
939 |
+
max_threads=2
|
940 |
)
|